Add linters configs

This commit is contained in:
2022-01-01 20:54:59 +03:00
parent 4a78d4f987
commit cbba30f2af
30 changed files with 580 additions and 298 deletions

35
.github/workflows/linters.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Linters
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs:
Run-Pre-Commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 32
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install pre-commit
run: pip3 install pre-commit
- name: Pre-commit (Push)
env:
SETUPTOOLS_USE_DISTUTILS: stdlib
if: ${{ github.event_name == 'push' }}
run: pre-commit run --source ${{ github.event.before }} --origin ${{ github.event.after }} --show-diff-on-failure
- name: Pre-commit (Pull-Request)
env:
SETUPTOOLS_USE_DISTUTILS: stdlib
if: ${{ github.event_name == 'pull_request' }}
run: pre-commit run --source ${{ github.event.pull_request.base.sha }} --origin ${{ github.event.pull_request.head.sha }} --show-diff-on-failure

20
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,20 @@
exclude: 'docs|node_modules|migrations|.git|.tox'
repos:
- repo: https://github.com/ambv/black
rev: 21.12b0
hooks:
- id: black
language_version: python3.9
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/csachs/pyproject-flake8
rev: v0.0.1a2.post1
hooks:
- id: pyproject-flake8
additional_dependencies: [
'-e', 'git+https://github.com/pycqa/pyflakes@1911c20#egg=pyflakes',
'-e', 'git+https://github.com/pycqa/pycodestyle@d219c68#egg=pycodestyle',
]

View File

@@ -1 +1 @@
__version__ = '0.1.0' __version__ = "0.1.0"

View File

@@ -1,20 +1,22 @@
from logging.config import fileConfig from logging.config import fileConfig
import os
import sys
from alembic import context from alembic import context
import sys, os
from sqlalchemy.engine import create_engine from sqlalchemy.engine import create_engine
from core.db import DATABASE_URL from core.db import DATABASE_URL
myPath = os.path.dirname(os.path.abspath(__file__)) myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../../') sys.path.insert(0, myPath + "/../../")
config = context.config config = context.config
from app.models import BaseMeta from app.models import BaseMeta
target_metadata = BaseMeta.metadata target_metadata = BaseMeta.metadata

View File

@@ -10,35 +10,77 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '08193b547a80' revision = "08193b547a80"
down_revision = 'b44117a41998' down_revision = "b44117a41998"
branch_labels = None branch_labels = None
depends_on = None depends_on = None
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_index(op.f('ix_books_title'), 'books', ['title'], unique=False) op.create_index(op.f("ix_books_title"), "books", ["title"], unique=False)
op.create_index(op.f('ix_sequences_name'), 'sequences', ['name'], unique=False) op.create_index(op.f("ix_sequences_name"), "sequences", ["name"], unique=False)
op.create_index(op.f('tgrm_books_title'), 'books', ['title'], postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) op.create_index(
op.create_index(op.f('tgrm_sequences_name'), 'sequences', ['name'], postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) op.f("tgrm_books_title"),
op.create_index(op.f('tgrm_authors_lfm'), 'authors', [sa.text("(last_name || ' ' || first_name || ' ' || middle_name)")] ,postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) "books",
op.create_index(op.f('tgrm_authors_lf'), 'authors', [sa.text("(last_name || ' ' || first_name)")] ,postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) ["title"],
op.create_index(op.f('tgrm_authors_l'), 'authors', ['last_name'] ,postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) postgresql_using="gin",
op.create_index(op.f('book_authors_book'), 'book_authors', ['book'], unique=False, postgresql_using='btree') postgresql_ops={"description": "gin_trgm_ops"},
op.create_index(op.f('book_authors_author'), 'book_authors', ['author'], unique=False, postgresql_using='btree') )
op.create_index(
op.f("tgrm_sequences_name"),
"sequences",
["name"],
postgresql_using="gin",
postgresql_ops={"description": "gin_trgm_ops"},
)
op.create_index(
op.f("tgrm_authors_lfm"),
"authors",
[sa.text("(last_name || ' ' || first_name || ' ' || middle_name)")],
postgresql_using="gin",
postgresql_ops={"description": "gin_trgm_ops"},
)
op.create_index(
op.f("tgrm_authors_lf"),
"authors",
[sa.text("(last_name || ' ' || first_name)")],
postgresql_using="gin",
postgresql_ops={"description": "gin_trgm_ops"},
)
op.create_index(
op.f("tgrm_authors_l"),
"authors",
["last_name"],
postgresql_using="gin",
postgresql_ops={"description": "gin_trgm_ops"},
)
op.create_index(
op.f("book_authors_book"),
"book_authors",
["book"],
unique=False,
postgresql_using="btree",
)
op.create_index(
op.f("book_authors_author"),
"book_authors",
["author"],
unique=False,
postgresql_using="btree",
)
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_sequences_name'), table_name='sequences') op.drop_index(op.f("ix_sequences_name"), table_name="sequences")
op.drop_index(op.f('ix_books_title'), table_name='books') op.drop_index(op.f("ix_books_title"), table_name="books")
op.drop_index(op.f('tgrm_books_title'), table_name='books') op.drop_index(op.f("tgrm_books_title"), table_name="books")
op.drop_index(op.f('tgrm_sequences_name'), table_name='books') op.drop_index(op.f("tgrm_sequences_name"), table_name="books")
op.drop_index(op.f('tgrm_authors_lfm'), table_name='books') op.drop_index(op.f("tgrm_authors_lfm"), table_name="books")
op.drop_index(op.f('tgrm_authors_lf'), table_name='books') op.drop_index(op.f("tgrm_authors_lf"), table_name="books")
op.drop_index(op.f('tgrm_authors_l'), table_name='books') op.drop_index(op.f("tgrm_authors_l"), table_name="books")
op.drop_index(op.f('book_authors_book'), table_name='book_authors') op.drop_index(op.f("book_authors_book"), table_name="book_authors")
op.drop_index(op.f('book_authors_author'), table_name='book_authors') op.drop_index(op.f("book_authors_author"), table_name="book_authors")
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -11,7 +11,7 @@ from sqlalchemy.sql.schema import UniqueConstraint
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = 'b44117a41998' revision = "b44117a41998"
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@@ -19,128 +19,203 @@ depends_on = None
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('sources', op.create_table(
sa.Column('id', sa.SmallInteger(), nullable=False), "sources",
sa.Column('name', sa.String(length=32), nullable=False), sa.Column("id", sa.SmallInteger(), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.Column("name", sa.String(length=32), nullable=False),
sa.UniqueConstraint('name') sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("name"),
) )
op.create_table('authors', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "authors",
sa.Column('source', sa.SmallInteger(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('remote_id', sa.Integer(), nullable=False), sa.Column("source", sa.SmallInteger(), nullable=False),
sa.Column('first_name', sa.String(length=256), nullable=False), sa.Column("remote_id", sa.Integer(), nullable=False),
sa.Column('last_name', sa.String(length=256), nullable=False), sa.Column("first_name", sa.String(length=256), nullable=False),
sa.Column('middle_name', sa.String(length=256), nullable=True), sa.Column("last_name", sa.String(length=256), nullable=False),
sa.ForeignKeyConstraint(['source'], ['sources.id'], name='fk_authors_sources_id_source'), sa.Column("middle_name", sa.String(length=256), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('source', 'remote_id', name='uc_authors_source_remote_id') ["source"], ["sources.id"], name="fk_authors_sources_id_source"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("source", "remote_id", name="uc_authors_source_remote_id"),
) )
op.create_table('books', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "books",
sa.Column('source', sa.SmallInteger(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('remote_id', sa.Integer(), nullable=False), sa.Column("source", sa.SmallInteger(), nullable=False),
sa.Column('title', sa.String(length=256), nullable=False), sa.Column("remote_id", sa.Integer(), nullable=False),
sa.Column('lang', sa.String(length=3), nullable=False), sa.Column("title", sa.String(length=256), nullable=False),
sa.Column('file_type', sa.String(length=4), nullable=False), sa.Column("lang", sa.String(length=3), nullable=False),
sa.Column('uploaded', sa.Date(), nullable=False), sa.Column("file_type", sa.String(length=4), nullable=False),
sa.Column('is_deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False), sa.Column("uploaded", sa.Date(), nullable=False),
sa.ForeignKeyConstraint(['source'], ['sources.id'], name='fk_books_sources_id_source'), sa.Column(
sa.PrimaryKeyConstraint('id'), "is_deleted", sa.Boolean(), server_default=sa.text("false"), nullable=False
sa.UniqueConstraint('source', 'remote_id', name='uc_books_source_remote_id') ),
sa.ForeignKeyConstraint(
["source"], ["sources.id"], name="fk_books_sources_id_source"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("source", "remote_id", name="uc_books_source_remote_id"),
) )
op.create_table('genres', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "genres",
sa.Column('source', sa.SmallInteger(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('remote_id', sa.Integer(), nullable=False), sa.Column("source", sa.SmallInteger(), nullable=False),
sa.Column('code', sa.String(length=45), nullable=False), sa.Column("remote_id", sa.Integer(), nullable=False),
sa.Column('description', sa.String(length=99), nullable=False), sa.Column("code", sa.String(length=45), nullable=False),
sa.Column('meta', sa.String(length=45), nullable=False), sa.Column("description", sa.String(length=99), nullable=False),
sa.ForeignKeyConstraint(['source'], ['sources.id'], name='fk_genres_sources_id_source'), sa.Column("meta", sa.String(length=45), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('source', 'remote_id', name='uc_genres_source_remote_id') ["source"], ["sources.id"], name="fk_genres_sources_id_source"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("source", "remote_id", name="uc_genres_source_remote_id"),
) )
op.create_table('sequences', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "sequences",
sa.Column('source', sa.SmallInteger(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('remote_id', sa.Integer(), nullable=False), sa.Column("source", sa.SmallInteger(), nullable=False),
sa.Column('name', sa.String(length=256), nullable=False), sa.Column("remote_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['source'], ['sources.id'], name='fk_sequences_sources_id_source'), sa.Column("name", sa.String(length=256), nullable=False),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('source', 'remote_id', name='uc_sequences_source_remote_id') ["source"], ["sources.id"], name="fk_sequences_sources_id_source"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"source", "remote_id", name="uc_sequences_source_remote_id"
),
) )
op.create_table('author_annotations', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "author_annotations",
sa.Column('author', sa.Integer(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=256), nullable=False), sa.Column("author", sa.Integer(), nullable=False),
sa.Column('text', sa.Text(), nullable=False), sa.Column("title", sa.String(length=256), nullable=False),
sa.Column('file', sa.String(length=256), nullable=True), sa.Column("text", sa.Text(), nullable=False),
sa.ForeignKeyConstraint(['author'], ['authors.id'], name='fk_author_annotations_authors_id_author'), sa.Column("file", sa.String(length=256), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('author') ["author"], ["authors.id"], name="fk_author_annotations_authors_id_author"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("author"),
) )
op.create_table('book_annotations', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "book_annotations",
sa.Column('book', sa.Integer(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=256), nullable=False), sa.Column("book", sa.Integer(), nullable=False),
sa.Column('text', sa.Text(), nullable=False), sa.Column("title", sa.String(length=256), nullable=False),
sa.Column('file', sa.String(length=256), nullable=True), sa.Column("text", sa.Text(), nullable=False),
sa.ForeignKeyConstraint(['book'], ['books.id'], name='fk_book_annotations_books_id_book'), sa.Column("file", sa.String(length=256), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('book') ["book"], ["books.id"], name="fk_book_annotations_books_id_book"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("book"),
) )
op.create_table('book_authors', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "book_authors",
sa.Column('author', sa.Integer(), nullable=True), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('book', sa.Integer(), nullable=True), sa.Column("author", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['author'], ['authors.id'], name='fk_book_authors_authors_author_id', onupdate='CASCADE', ondelete='CASCADE'), sa.Column("book", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['book'], ['books.id'], name='fk_book_authors_books_book_id', onupdate='CASCADE', ondelete='CASCADE'), sa.ForeignKeyConstraint(
sa.PrimaryKeyConstraint('id'), ["author"],
sa.UniqueConstraint('book', 'author', name='uc_book_authors_book_author'), ["authors.id"],
name="fk_book_authors_authors_author_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["book"],
["books.id"],
name="fk_book_authors_books_book_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("book", "author", name="uc_book_authors_book_author"),
) )
op.create_table('book_genres', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "book_genres",
sa.Column('genre', sa.Integer(), nullable=True), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('book', sa.Integer(), nullable=True), sa.Column("genre", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['book'], ['books.id'], name='fk_book_genres_books_book_id', onupdate='CASCADE', ondelete='CASCADE'), sa.Column("book", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['genre'], ['genres.id'], name='fk_book_genres_genres_genre_id', onupdate='CASCADE', ondelete='CASCADE'), sa.ForeignKeyConstraint(
sa.PrimaryKeyConstraint('id'), ["book"],
sa.UniqueConstraint('book', 'genre', name='uc_book_genres_book_genre'), ["books.id"],
name="fk_book_genres_books_book_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["genre"],
["genres.id"],
name="fk_book_genres_genres_genre_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("book", "genre", name="uc_book_genres_book_genre"),
) )
op.create_table('book_sequences', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "book_sequences",
sa.Column('position', sa.SmallInteger(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('sequence', sa.Integer(), nullable=True), sa.Column("position", sa.SmallInteger(), nullable=False),
sa.Column('book', sa.Integer(), nullable=True), sa.Column("sequence", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['book'], ['books.id'], name='fk_book_sequences_books_book_id', onupdate='CASCADE', ondelete='CASCADE'), sa.Column("book", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['sequence'], ['sequences.id'], name='fk_book_sequences_sequences_sequence_id', onupdate='CASCADE', ondelete='CASCADE'), sa.ForeignKeyConstraint(
sa.PrimaryKeyConstraint('id'), ["book"],
sa.UniqueConstraint('book', 'sequence', name='uc_book_sequences_book_sequence'), ["books.id"],
name="fk_book_sequences_books_book_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["sequence"],
["sequences.id"],
name="fk_book_sequences_sequences_sequence_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("book", "sequence", name="uc_book_sequences_book_sequence"),
) )
op.create_table('translations', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "translations",
sa.Column('position', sa.SmallInteger(), nullable=False), sa.Column("id", sa.Integer(), nullable=False),
sa.Column('author', sa.Integer(), nullable=True), sa.Column("position", sa.SmallInteger(), nullable=False),
sa.Column('book', sa.Integer(), nullable=True), sa.Column("author", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['author'], ['authors.id'], name='fk_translations_authors_author_id', onupdate='CASCADE', ondelete='CASCADE'), sa.Column("book", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['book'], ['books.id'], name='fk_translations_books_book_id', onupdate='CASCADE', ondelete='CASCADE'), sa.ForeignKeyConstraint(
sa.PrimaryKeyConstraint('id'), ["author"],
sa.UniqueConstraint('book', 'author', name='uc_translations_book_author'), ["authors.id"],
name="fk_translations_authors_author_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["book"],
["books.id"],
name="fk_translations_books_book_id",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("book", "author", name="uc_translations_book_author"),
) )
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('translations') op.drop_table("translations")
op.drop_table('book_sequences') op.drop_table("book_sequences")
op.drop_table('book_genres') op.drop_table("book_genres")
op.drop_table('book_authors') op.drop_table("book_authors")
op.drop_table('book_annotations') op.drop_table("book_annotations")
op.drop_table('author_annotations') op.drop_table("author_annotations")
op.drop_table('sequences') op.drop_table("sequences")
op.drop_table('genres') op.drop_table("genres")
op.drop_table('books') op.drop_table("books")
op.drop_table('authors') op.drop_table("authors")
op.drop_table('sources') op.drop_table("sources")
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -6,4 +6,6 @@ from core.config import env_config
async def check_token(api_key: str = Security(default_security)): async def check_token(api_key: str = Security(default_security)):
if api_key != env_config.API_KEY: if api_key != env_config.API_KEY:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Wrong api key!") raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Wrong api key!"
)

View File

@@ -5,6 +5,6 @@ def get_book_filter(is_deleted: Optional[bool] = None) -> dict:
result = {} result = {}
if is_deleted is not None: if is_deleted is not None:
result['is_deleted'] = is_deleted result["is_deleted"] = is_deleted
return result return result

View File

@@ -64,9 +64,13 @@ class AuthorAnnotation(ormar.Model):
id = ormar.Integer(primary_key=True, nullable=False) id = ormar.Integer(primary_key=True, nullable=False)
author: Author = ormar.ForeignKey(Author, nullable=False, unique=True, related_name="annotations") author: Author = ormar.ForeignKey(
Author, nullable=False, unique=True, related_name="annotations"
)
title: str = ormar.String(max_length=256, nullable=False, default="") # type: ignore title: str = ormar.String(
max_length=256, nullable=False, default=""
) # type: ignore
text: str = ormar.Text(nullable=False, default="") # type: ignore text: str = ormar.Text(nullable=False, default="") # type: ignore
file: str = ormar.String(max_length=256, nullable=True) # type: ignore file: str = ormar.String(max_length=256, nullable=True) # type: ignore
@@ -103,7 +107,9 @@ class BookGenres(ormar.Model):
class BookSequences(ormar.Model): class BookSequences(ormar.Model):
class Meta(BaseMeta): class Meta(BaseMeta):
tablename = "book_sequences" tablename = "book_sequences"
orders_by = ["position", ] orders_by = [
"position",
]
id: int = ormar.Integer(primary_key=True, nullable=False) # type: ignore id: int = ormar.Integer(primary_key=True, nullable=False) # type: ignore
@@ -113,7 +119,9 @@ class BookSequences(ormar.Model):
class Translation(ormar.Model): class Translation(ormar.Model):
class Meta(BaseMeta): class Meta(BaseMeta):
tablename = "translations" tablename = "translations"
orders_by = ["position", ] orders_by = [
"position",
]
id: int = ormar.Integer(primary_key=True, nullable=False) # type: ignore id: int = ormar.Integer(primary_key=True, nullable=False) # type: ignore
@@ -132,21 +140,27 @@ class Book(ormar.Model):
source: Source = ormar.ForeignKey(Source, nullable=False) source: Source = ormar.ForeignKey(Source, nullable=False)
remote_id: int = ormar.Integer(minimum=0, nullable=False) # type: ignore remote_id: int = ormar.Integer(minimum=0, nullable=False) # type: ignore
title: str = ormar.String(max_length=256, nullable=False, index=True) # type: ignore title: str = ormar.String(
max_length=256, nullable=False, index=True
) # type: ignore
lang: str = ormar.String(max_length=3, nullable=False) # type: ignore lang: str = ormar.String(max_length=3, nullable=False) # type: ignore
file_type: str = ormar.String(max_length=4, nullable=False) # type: ignore file_type: str = ormar.String(max_length=4, nullable=False) # type: ignore
uploaded: date = ormar.Date() # type: ignore uploaded: date = ormar.Date() # type: ignore
is_deleted: bool = ormar.Boolean(default=False, server_default=text("false"), nullable=False) is_deleted: bool = ormar.Boolean(
default=False, server_default=text("false"), nullable=False
)
authors = ormar.ManyToMany(Author, through=BookAuthors) authors = ormar.ManyToMany(Author, through=BookAuthors)
translators = ormar.ManyToMany(Author, through=Translation, related_name="translated_books") translators = ormar.ManyToMany(
Author, through=Translation, related_name="translated_books"
)
genres = ormar.ManyToMany(Genre, through=BookGenres) genres = ormar.ManyToMany(Genre, through=BookGenres)
sequences = ormar.ManyToMany(Sequence, through=BookSequences) sequences = ormar.ManyToMany(Sequence, through=BookSequences)
@ormar.property_field @ormar.property_field
def available_types(self) -> list[str]: def available_types(self) -> list[str]:
if self.file_type == 'fb2' and self.source.name == 'flibusta': if self.file_type == "fb2" and self.source.name == "flibusta":
return ['fb2', 'fb2zip', 'epub', 'mobi'] return ["fb2", "fb2zip", "epub", "mobi"]
return [self.file_type] return [self.file_type]
@@ -161,8 +175,12 @@ class BookAnnotation(ormar.Model):
id = ormar.Integer(primary_key=True, nullable=False) id = ormar.Integer(primary_key=True, nullable=False)
book: Book = ormar.ForeignKey(Book, nullable=False, unique=True, related_name="annotations") book: Book = ormar.ForeignKey(
Book, nullable=False, unique=True, related_name="annotations"
)
title: str = ormar.String(max_length=256, nullable=False, default="") # type: ignore title: str = ormar.String(
max_length=256, nullable=False, default=""
) # type: ignore
text: str = ormar.Text(nullable=False, default="") # type: ignore text: str = ormar.Text(nullable=False, default="") # type: ignore
file: str = ormar.String(max_length=256, nullable=True) # type: ignore file: str = ormar.String(max_length=256, nullable=True) # type: ignore

View File

@@ -1,5 +1,5 @@
from typing import Optional
from datetime import date from datetime import date
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel

View File

@@ -1,5 +1,6 @@
from typing import Optional
from datetime import date from datetime import date
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from app.serializers.orjson_config import ORJSONConfig from app.serializers.orjson_config import ORJSONConfig

View File

@@ -1,5 +1,4 @@
from app.models import Author from app.models import Author
from app.services.common import TRGMSearchService, GetRandomService from app.services.common import TRGMSearchService, GetRandomService
@@ -9,7 +8,10 @@ SELECT ARRAY(
SELECT SELECT
id, id,
GREATEST( GREATEST(
similarity((last_name || ' ' || first_name || ' ' || middle_name), :query), similarity(
(last_name || ' ' || first_name || ' ' || middle_name),
:query
),
similarity((last_name || ' ' || first_name), :query), similarity((last_name || ' ' || first_name), :query),
similarity((last_name), :query) similarity((last_name), :query)
) as sml, ) as sml,

View File

@@ -2,10 +2,10 @@ from typing import Union
from fastapi import HTTPException, status from fastapi import HTTPException, status
from app.models import Book as BookDB, Author as AuthorDB from app.models import Author as AuthorDB
from app.models import Book as BookDB
from app.services.common import TRGMSearchService, GetRandomService
from app.serializers.book import CreateBook, CreateRemoteBook from app.serializers.book import CreateBook, CreateRemoteBook
from app.services.common import TRGMSearchService, GetRandomService
GET_OBJECT_IDS_QUERY = """ GET_OBJECT_IDS_QUERY = """
@@ -42,9 +42,7 @@ class BookCreator:
if len(author_ids) != len(authors): if len(author_ids) != len(authors):
cls._raise_bad_request() cls._raise_bad_request()
book = await BookDB.objects.create( book = await BookDB.objects.create(**data_dict)
**data_dict
)
for author in authors: for author in authors:
await book.authors.add(author) await book.authors.add(author)
@@ -56,14 +54,14 @@ class BookCreator:
data_dict = data.dict() data_dict = data.dict()
author_ids = data_dict.pop("remote_authors", []) author_ids = data_dict.pop("remote_authors", [])
authors = await AuthorDB.objects.filter(source__id=data.source, remote_id__in=author_ids).all() authors = await AuthorDB.objects.filter(
source__id=data.source, remote_id__in=author_ids
).all()
if len(author_ids) != len(authors): if len(author_ids) != len(authors):
cls._raise_bad_request() cls._raise_bad_request()
book = await BookDB.objects.create( book = await BookDB.objects.create(**data_dict)
**data_dict
)
for author in authors: for author in authors:
await book.authors.add(author) await book.authors.add(author)

View File

@@ -1,17 +1,17 @@
from typing import Optional, Generic, TypeVar, Union from typing import Optional, Generic, TypeVar, Union
from databases import Database
import aioredis
from databases import Database
from fastapi_pagination.api import resolve_params from fastapi_pagination.api import resolve_params
from fastapi_pagination.bases import AbstractParams, RawParams from fastapi_pagination.bases import AbstractParams, RawParams
from app.utils.pagination import Page, CustomPage
import aioredis
import orjson import orjson
from ormar import Model, QuerySet from ormar import Model, QuerySet
from sqlalchemy import Table from sqlalchemy import Table
from app.utils.pagination import Page, CustomPage
T = TypeVar('T', bound=Model)
T = TypeVar("T", bound=Model)
class TRGMSearchService(Generic[T]): class TRGMSearchService(Generic[T]):
@@ -48,7 +48,9 @@ class TRGMSearchService(Generic[T]):
@classmethod @classmethod
@property @property
def object_ids_query(cls) -> str: def object_ids_query(cls) -> str:
assert cls.GET_OBJECT_IDS_QUERY is not None, f"GET_OBJECT_IDS_QUERY in {cls.__name__} don't set!" assert (
cls.GET_OBJECT_IDS_QUERY is not None
), f"GET_OBJECT_IDS_QUERY in {cls.__name__} don't set!"
return cls.GET_OBJECT_IDS_QUERY return cls.GET_OBJECT_IDS_QUERY
@classmethod @classmethod
@@ -56,9 +58,9 @@ class TRGMSearchService(Generic[T]):
row = await cls.database.fetch_one(cls.object_ids_query, {"query": query_data}) row = await cls.database.fetch_one(cls.object_ids_query, {"query": query_data})
if row is None: if row is None:
raise ValueError('Something is wrong!') raise ValueError("Something is wrong!")
return row['array'] return row["array"]
@classmethod @classmethod
def get_cache_key(cls, query_data: str) -> str: def get_cache_key(cls, query_data: str) -> str:
@@ -66,7 +68,9 @@ class TRGMSearchService(Generic[T]):
return f"{model_class_name}_{query_data}" return f"{model_class_name}_{query_data}"
@classmethod @classmethod
async def get_cached_ids(cls, query_data: str, redis: aioredis.Redis) -> Optional[list[int]]: async def get_cached_ids(
cls, query_data: str, redis: aioredis.Redis
) -> Optional[list[int]]:
try: try:
key = cls.get_cache_key(query_data) key = cls.get_cache_key(query_data)
data = await redis.get(key) data = await redis.get(key)
@@ -80,7 +84,9 @@ class TRGMSearchService(Generic[T]):
return None return None
@classmethod @classmethod
async def cache_object_ids(cls, query_data: str, object_ids: list[int], redis: aioredis.Redis): async def cache_object_ids(
cls, query_data: str, object_ids: list[int], redis: aioredis.Redis
):
try: try:
key = cls.get_cache_key(query_data) key = cls.get_cache_key(query_data)
await redis.set(key, orjson.dumps(object_ids), ex=cls.CACHE_TTL) await redis.set(key, orjson.dumps(object_ids), ex=cls.CACHE_TTL)
@@ -88,7 +94,9 @@ class TRGMSearchService(Generic[T]):
print(e) print(e)
@classmethod @classmethod
async def get_objects(cls, query_data: str, redis: aioredis.Redis) -> tuple[int, list[T]]: async def get_objects(
cls, query_data: str, redis: aioredis.Redis
) -> tuple[int, list[T]]:
params = cls.get_raw_params() params = cls.get_raw_params()
cached_object_ids = await cls.get_cached_ids(query_data, redis) cached_object_ids = await cls.get_cached_ids(query_data, redis)
@@ -99,7 +107,7 @@ class TRGMSearchService(Generic[T]):
else: else:
object_ids = cached_object_ids object_ids = cached_object_ids
limited_object_ids = object_ids[params.offset:params.offset + params.limit] limited_object_ids = object_ids[params.offset : params.offset + params.limit]
queryset: QuerySet[T] = cls.model.objects queryset: QuerySet[T] = cls.model.objects
@@ -117,11 +125,7 @@ class TRGMSearchService(Generic[T]):
total, objects = await cls.get_objects(query, redis) total, objects = await cls.get_objects(query, redis)
return CustomPage.create( return CustomPage.create(items=objects, total=total, params=params)
items=objects,
total=total,
params=params
)
GET_RANDOM_OBJECT_ID_QUERY = """ GET_RANDOM_OBJECT_ID_QUERY = """

View File

@@ -1,5 +1,4 @@
from app.models import Sequence from app.models import Sequence
from app.services.common import TRGMSearchService, GetRandomService from app.services.common import TRGMSearchService, GetRandomService

View File

@@ -2,10 +2,12 @@ from typing import Union
from fastapi import HTTPException, status from fastapi import HTTPException, status
from app.models import Author as AuthorDB
from app.models import Book as BookDB
from app.models import Source as SourceDB
from app.models import Translation as TranslationDB
from app.serializers.translation import CreateTranslation, CreateRemoteTranslation from app.serializers.translation import CreateTranslation, CreateRemoteTranslation
from app.models import Translation as TranslationDB, Source as SourceDB, Book as BookDB, Author as AuthorDB
class TranslationCreator: class TranslationCreator:
@classmethod @classmethod
@@ -14,23 +16,27 @@ class TranslationCreator:
@classmethod @classmethod
async def _create_translation(cls, data: CreateTranslation) -> TranslationDB: async def _create_translation(cls, data: CreateTranslation) -> TranslationDB:
return await TranslationDB.objects.create( return await TranslationDB.objects.create(**data.dict())
**data.dict()
)
@classmethod @classmethod
async def _create_remote_translation(cls, data: CreateRemoteTranslation) -> TranslationDB: async def _create_remote_translation(
cls, data: CreateRemoteTranslation
) -> TranslationDB:
source = await SourceDB.objects.get_or_none(id=data.source) source = await SourceDB.objects.get_or_none(id=data.source)
if source is None: if source is None:
cls._raise_bad_request() cls._raise_bad_request()
book = await BookDB.objects.get_or_none(source__id=source.id, remote_id=data.remote_book) book = await BookDB.objects.get_or_none(
source__id=source.id, remote_id=data.remote_book
)
if book is None: if book is None:
cls._raise_bad_request() cls._raise_bad_request()
translator = await AuthorDB.objects.get_or_none(source__id=source.id, remote_id=data.remote_translator) translator = await AuthorDB.objects.get_or_none(
source__id=source.id, remote_id=data.remote_translator
)
if translator is None: if translator is None:
cls._raise_bad_request() cls._raise_bad_request()
@@ -42,7 +48,9 @@ class TranslationCreator:
) )
@classmethod @classmethod
async def create(cls, data: Union[CreateTranslation, CreateRemoteTranslation]) -> TranslationDB: async def create(
cls, data: Union[CreateTranslation, CreateRemoteTranslation]
) -> TranslationDB:
if isinstance(data, CreateTranslation): if isinstance(data, CreateTranslation):
return await cls._create_translation(data) return await cls._create_translation(data)
if isinstance(data, CreateRemoteTranslation): if isinstance(data, CreateRemoteTranslation):

View File

@@ -1,9 +1,8 @@
from typing import Protocol, TypeVar, Any, Generic, Sequence, runtime_checkable from typing import Protocol, TypeVar, Any, Generic, Sequence, runtime_checkable
from pydantic import conint
from fastapi_pagination import Page, Params from fastapi_pagination import Page, Params
from fastapi_pagination.bases import AbstractParams from fastapi_pagination.bases import AbstractParams
from pydantic import conint
@runtime_checkable @runtime_checkable
@@ -12,7 +11,7 @@ class ToDict(Protocol):
... ...
T = TypeVar('T', ToDict, Any) T = TypeVar("T", ToDict, Any)
class CustomPage(Page[T], Generic[T]): class CustomPage(Page[T], Generic[T]):

View File

@@ -1,14 +1,10 @@
from app.views.source import source_router
from app.views.author import author_router from app.views.author import author_router
from app.views.author_annotation import author_annotation_router from app.views.author_annotation import author_annotation_router
from app.views.book import book_router from app.views.book import book_router
from app.views.book_annotation import book_annotation_router from app.views.book_annotation import book_annotation_router
from app.views.translation import translation_router
from app.views.sequence import sequence_router from app.views.sequence import sequence_router
from app.views.source import source_router
from app.views.translation import translation_router
routers = [ routers = [

View File

@@ -1,16 +1,22 @@
from random import choice as random_choice
from fastapi import APIRouter, Depends, Request, HTTPException, status from fastapi import APIRouter, Depends, Request, HTTPException, status
from fastapi_pagination import Params from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.utils.pagination import CustomPage
from app.models import Author as AuthorDB, AuthorAnnotation as AuthorAnnotationDB, Book as BookDB from app.depends import check_token
from app.serializers.author import Author, CreateAuthor, UpdateAuthor, AuthorBook, TranslatedBook from app.models import Author as AuthorDB
from app.models import AuthorAnnotation as AuthorAnnotationDB
from app.models import Book as BookDB
from app.serializers.author import (
Author,
CreateAuthor,
UpdateAuthor,
AuthorBook,
TranslatedBook,
)
from app.serializers.author_annotation import AuthorAnnotation from app.serializers.author_annotation import AuthorAnnotation
from app.services.author import AuthorTGRMSearchService, GetRandomAuthorService from app.services.author import AuthorTGRMSearchService, GetRandomAuthorService
from app.depends import check_token from app.utils.pagination import CustomPage
author_router = APIRouter( author_router = APIRouter(
@@ -20,22 +26,19 @@ author_router = APIRouter(
) )
PREFETCH_RELATED = ["source", "annotations"] PREFETCH_RELATED = ["source", "annotations"]
@author_router.get("/", response_model=CustomPage[Author], dependencies=[Depends(Params)]) @author_router.get(
"/", response_model=CustomPage[Author], dependencies=[Depends(Params)]
)
async def get_authors(): async def get_authors():
return await paginate( return await paginate(AuthorDB.objects.prefetch_related(PREFETCH_RELATED))
AuthorDB.objects.prefetch_related(PREFETCH_RELATED)
)
@author_router.post("/", response_model=Author, dependencies=[Depends(Params)]) @author_router.post("/", response_model=Author, dependencies=[Depends(Params)])
async def create_author(data: CreateAuthor): async def create_author(data: CreateAuthor):
author = await AuthorDB.objects.create( author = await AuthorDB.objects.create(**data.dict())
**data.dict()
)
return await AuthorDB.objects.prefetch_related(PREFETCH_RELATED).get(id=author.id) return await AuthorDB.objects.prefetch_related(PREFETCH_RELATED).get(id=author.id)
@@ -49,7 +52,9 @@ async def get_random_author():
@author_router.get("/{id}", response_model=Author) @author_router.get("/{id}", response_model=Author)
async def get_author(id: int): async def get_author(id: int):
author = await AuthorDB.objects.prefetch_related(PREFETCH_RELATED).get_or_none(id=id) author = await AuthorDB.objects.prefetch_related(PREFETCH_RELATED).get_or_none(
id=id
)
if author is None: if author is None:
raise HTTPException(status.HTTP_404_NOT_FOUND) raise HTTPException(status.HTTP_404_NOT_FOUND)
@@ -79,20 +84,28 @@ async def get_author_annotation(id: int):
return annotation return annotation
@author_router.get("/{id}/books", response_model=CustomPage[AuthorBook], dependencies=[Depends(Params)]) @author_router.get(
"/{id}/books", response_model=CustomPage[AuthorBook], dependencies=[Depends(Params)]
)
async def get_author_books(id: int): async def get_author_books(id: int):
return await paginate( return await paginate(
BookDB.objects.select_related(["source", "annotations", "translators"]).filter(authors__id=id).order_by('title') BookDB.objects.select_related(["source", "annotations", "translators"])
.filter(authors__id=id)
.order_by("title")
) )
@author_router.get("/{id}/translated_books", response_model=CustomPage[TranslatedBook]) @author_router.get("/{id}/translated_books", response_model=CustomPage[TranslatedBook])
async def get_translated_books(id: int): async def get_translated_books(id: int):
return await paginate( return await paginate(
BookDB.objects.select_related(["source", "annotations", "translators"]).filter(translations__translator__id=id) BookDB.objects.select_related(["source", "annotations", "translators"]).filter(
translations__translator__id=id
)
) )
@author_router.get("/search/{query}", response_model=CustomPage[Author], dependencies=[Depends(Params)]) @author_router.get(
"/search/{query}", response_model=CustomPage[Author], dependencies=[Depends(Params)]
)
async def search_authors(query: str, request: Request): async def search_authors(query: str, request: Request):
return await AuthorTGRMSearchService.get(query, request.app.state.redis) return await AuthorTGRMSearchService.get(query, request.app.state.redis)

View File

@@ -3,9 +3,13 @@ from fastapi import APIRouter, Depends, HTTPException, status
from fastapi_pagination import Params, Page from fastapi_pagination import Params, Page
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.models import AuthorAnnotation as AuthorAnnotationDB
from app.serializers.author_annotation import AuthorAnnotation, CreateAuthorAnnotation, UpdateAuthorAnnotation
from app.depends import check_token from app.depends import check_token
from app.models import AuthorAnnotation as AuthorAnnotationDB
from app.serializers.author_annotation import (
AuthorAnnotation,
CreateAuthorAnnotation,
UpdateAuthorAnnotation,
)
author_annotation_router = APIRouter( author_annotation_router = APIRouter(
@@ -15,18 +19,16 @@ author_annotation_router = APIRouter(
) )
@author_annotation_router.get("/", response_model=Page[AuthorAnnotation], dependencies=[Depends(Params)]) @author_annotation_router.get(
"/", response_model=Page[AuthorAnnotation], dependencies=[Depends(Params)]
)
async def get_author_annotations(): async def get_author_annotations():
return await paginate( return await paginate(AuthorAnnotationDB.objects)
AuthorAnnotationDB.objects
)
@author_annotation_router.post("/", response_model=AuthorAnnotation) @author_annotation_router.post("/", response_model=AuthorAnnotation)
async def create_author_annotation(data: CreateAuthorAnnotation): async def create_author_annotation(data: CreateAuthorAnnotation):
return await AuthorAnnotationDB.objects.create( return await AuthorAnnotationDB.objects.create(**data.dict())
**data.dict()
)
@author_annotation_router.get("/{id}", response_model=AuthorAnnotation) @author_annotation_router.get("/{id}", response_model=AuthorAnnotation)

View File

@@ -1,18 +1,26 @@
from typing import Union from typing import Union
from random import choice as random_choice
from fastapi import APIRouter, Depends, Request, HTTPException, status from fastapi import APIRouter, Depends, Request, HTTPException, status
from fastapi_pagination import Params from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.utils.pagination import CustomPage
from app.models import Book as BookDB, Author as AuthorDB, BookAnnotation as BookAnnotationDB from app.depends import check_token
from app.serializers.book import Book, RemoteBook, BookDetail, CreateBook, UpdateBook, CreateRemoteBook from app.filters.book import get_book_filter
from app.models import Author as AuthorDB
from app.models import Book as BookDB
from app.models import BookAnnotation as BookAnnotationDB
from app.serializers.book import (
Book,
RemoteBook,
BookDetail,
CreateBook,
UpdateBook,
CreateRemoteBook,
)
from app.serializers.book_annotation import BookAnnotation from app.serializers.book_annotation import BookAnnotation
from app.services.book import BookTGRMSearchService, GetRandomBookService, BookCreator from app.services.book import BookTGRMSearchService, GetRandomBookService, BookCreator
from app.filters.book import get_book_filter from app.utils.pagination import CustomPage
from app.depends import check_token
book_router = APIRouter( book_router = APIRouter(
@@ -25,7 +33,9 @@ book_router = APIRouter(
SELECT_RELATED_FIELDS = ["source", "authors", "translators", "annotations"] SELECT_RELATED_FIELDS = ["source", "authors", "translators", "annotations"]
@book_router.get("/", response_model=CustomPage[RemoteBook], dependencies=[Depends(Params)]) @book_router.get(
"/", response_model=CustomPage[RemoteBook], dependencies=[Depends(Params)]
)
async def get_books(book_filter: dict = Depends(get_book_filter)): async def get_books(book_filter: dict = Depends(get_book_filter)):
return await paginate( return await paginate(
BookDB.objects.select_related(SELECT_RELATED_FIELDS).filter(**book_filter) BookDB.objects.select_related(SELECT_RELATED_FIELDS).filter(**book_filter)
@@ -59,8 +69,7 @@ async def get_book(id: int):
@book_router.get("/remote/{source_id}/{remote_id}", response_model=Book) @book_router.get("/remote/{source_id}/{remote_id}", response_model=Book)
async def get_remote_book(source_id: int, remote_id: int): async def get_remote_book(source_id: int, remote_id: int):
book = await BookDB.objects.select_related(SELECT_RELATED_FIELDS).get_or_none( book = await BookDB.objects.select_related(SELECT_RELATED_FIELDS).get_or_none(
source=source_id, source=source_id, remote_id=remote_id
remote_id=remote_id
) )
if book is None: if book is None:
@@ -84,9 +93,7 @@ async def update_book(id: int, data: UpdateBook):
author_ids = data_dict.pop("authors", []) author_ids = data_dict.pop("authors", [])
authors = await AuthorDB.objects.filter(id__in=author_ids).all() authors = await AuthorDB.objects.filter(id__in=author_ids).all()
book = await BookDB.objects.create( book = await BookDB.objects.create(**data_dict)
**data_dict
)
for author in authors: for author in authors:
await book.authors.add(author) await book.authors.add(author)
@@ -104,6 +111,8 @@ async def get_book_annotation(id: int):
return annotation return annotation
@book_router.get("/search/{query}", response_model=CustomPage[Book], dependencies=[Depends(Params)]) @book_router.get(
"/search/{query}", response_model=CustomPage[Book], dependencies=[Depends(Params)]
)
async def search_books(query: str, request: Request): async def search_books(query: str, request: Request):
return await BookTGRMSearchService.get(query, request.app.state.redis) return await BookTGRMSearchService.get(query, request.app.state.redis)

View File

@@ -3,30 +3,32 @@ from fastapi import APIRouter, Depends, HTTPException, status
from fastapi_pagination import Params, Page from fastapi_pagination import Params, Page
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.models import BookAnnotation as BookAnnotationDB
from app.serializers.book_annotation import BookAnnotation, CreateBookAnnotation, UpdateBookAnnotation
from app.depends import check_token from app.depends import check_token
from app.models import BookAnnotation as BookAnnotationDB
from app.serializers.book_annotation import (
BookAnnotation,
CreateBookAnnotation,
UpdateBookAnnotation,
)
book_annotation_router = APIRouter( book_annotation_router = APIRouter(
prefix="/api/v1/book_annotations", prefix="/api/v1/book_annotations",
tags=["book_annotation"], tags=["book_annotation"],
dependencies=[Depends(check_token)] dependencies=[Depends(check_token)],
) )
@book_annotation_router.get("/", response_model=Page[BookAnnotation], dependencies=[Depends(Params)]) @book_annotation_router.get(
"/", response_model=Page[BookAnnotation], dependencies=[Depends(Params)]
)
async def get_book_annotations(): async def get_book_annotations():
return await paginate( return await paginate(BookAnnotationDB.objects)
BookAnnotationDB.objects
)
@book_annotation_router.post("/", response_model=BookAnnotation) @book_annotation_router.post("/", response_model=BookAnnotation)
async def create_book_annotation(data: CreateBookAnnotation): async def create_book_annotation(data: CreateBookAnnotation):
return await BookAnnotationDB.objects.create( return await BookAnnotationDB.objects.create(**data.dict())
**data.dict()
)
@book_annotation_router.get("/{id}", response_model=BookAnnotation) @book_annotation_router.get("/{id}", response_model=BookAnnotation)

View File

@@ -1,15 +1,15 @@
from random import choice as random_choice
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from fastapi_pagination import Params from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.utils.pagination import CustomPage
from app.models import Sequence as SequenceDB, Book as BookDB, BookSequences as BookSequencesDB
from app.serializers.sequence import Sequence, CreateSequence, Book as SequenceBook
from app.services.sequence import SequenceTGRMSearchService, GetRandomSequenceService
from app.depends import check_token from app.depends import check_token
from app.models import Book as BookDB
from app.models import Sequence as SequenceDB
from app.serializers.sequence import Book as SequenceBook
from app.serializers.sequence import Sequence, CreateSequence
from app.services.sequence import SequenceTGRMSearchService, GetRandomSequenceService
from app.utils.pagination import CustomPage
sequence_router = APIRouter( sequence_router = APIRouter(
@@ -19,11 +19,11 @@ sequence_router = APIRouter(
) )
@sequence_router.get("/", response_model=CustomPage[Sequence], dependencies=[Depends(Params)]) @sequence_router.get(
"/", response_model=CustomPage[Sequence], dependencies=[Depends(Params)]
)
async def get_sequences(): async def get_sequences():
return await paginate( return await paginate(SequenceDB.objects)
SequenceDB.objects
)
@sequence_router.get("/random", response_model=Sequence) @sequence_router.get("/random", response_model=Sequence)
@@ -38,21 +38,30 @@ async def get_sequence(id: int):
return await SequenceDB.objects.get(id=id) return await SequenceDB.objects.get(id=id)
@sequence_router.get("/{id}/books", response_model=CustomPage[SequenceBook], dependencies=[Depends(Params)]) @sequence_router.get(
"/{id}/books",
response_model=CustomPage[SequenceBook],
dependencies=[Depends(Params)],
)
async def get_sequence_books(id: int): async def get_sequence_books(id: int):
return await paginate( return await paginate(
BookDB.objects.select_related(["source", "annotations", "authors", "translators"]) BookDB.objects.select_related(
.filter(sequences__id=id).order_by("sequences__booksequences__position") ["source", "annotations", "authors", "translators"]
)
.filter(sequences__id=id)
.order_by("sequences__booksequences__position")
) )
@sequence_router.post("/", response_model=Sequence) @sequence_router.post("/", response_model=Sequence)
async def create_sequence(data: CreateSequence): async def create_sequence(data: CreateSequence):
return await SequenceDB.objects.create( return await SequenceDB.objects.create(**data.dict())
**data.dict()
)
@sequence_router.get("/search/{query}", response_model=CustomPage[Sequence], dependencies=[Depends(Params)]) @sequence_router.get(
"/search/{query}",
response_model=CustomPage[Sequence],
dependencies=[Depends(Params)],
)
async def search_sequences(query: str, request: Request): async def search_sequences(query: str, request: Request):
return await SequenceTGRMSearchService.get(query, request.app.state.redis) return await SequenceTGRMSearchService.get(query, request.app.state.redis)

View File

@@ -3,9 +3,9 @@ from fastapi import APIRouter, Depends
from fastapi_pagination import Params, Page from fastapi_pagination import Params, Page
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.depends import check_token
from app.models import Source as SourceDB from app.models import Source as SourceDB
from app.serializers.source import Source, CreateSource from app.serializers.source import Source, CreateSource
from app.depends import check_token
source_router = APIRouter( source_router = APIRouter(
@@ -22,6 +22,4 @@ async def get_sources():
@source_router.post("", response_model=Source) @source_router.post("", response_model=Source)
async def create_source(data: CreateSource): async def create_source(data: CreateSource):
return await SourceDB.objects.create( return await SourceDB.objects.create(**data.dict())
**data.dict()
)

View File

@@ -4,12 +4,16 @@ from fastapi import APIRouter, Depends, HTTPException, status
from fastapi_pagination import Params from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate from fastapi_pagination.ext.ormar import paginate
from app.utils.pagination import CustomPage
from app.models import Translation as TranslationDB
from app.serializers.translation import Translation, CreateTranslation, CreateRemoteTranslation
from app.services.translation import TranslationCreator
from app.depends import check_token from app.depends import check_token
from app.models import Translation as TranslationDB
from app.serializers.translation import (
Translation,
CreateTranslation,
CreateRemoteTranslation,
)
from app.services.translation import TranslationCreator
from app.utils.pagination import CustomPage
translation_router = APIRouter( translation_router = APIRouter(
@@ -19,23 +23,27 @@ translation_router = APIRouter(
) )
@translation_router.get("/", response_model=CustomPage[Translation], dependencies=[Depends(Params)]) @translation_router.get(
"/", response_model=CustomPage[Translation], dependencies=[Depends(Params)]
)
async def get_translations(): async def get_translations():
return await paginate( return await paginate(TranslationDB.objects.prefetch_related(["book", "author"]))
TranslationDB.objects.prefetch_related(["book", "author"])
)
@translation_router.post("/", response_model=Translation) @translation_router.post("/", response_model=Translation)
async def create_translation(data: Union[CreateTranslation, CreateRemoteTranslation]): async def create_translation(data: Union[CreateTranslation, CreateRemoteTranslation]):
translation = await TranslationCreator.create(data) translation = await TranslationCreator.create(data)
return await TranslationDB.objects.prefetch_related(["book", "author"]).get(id=translation.id) return await TranslationDB.objects.prefetch_related(["book", "author"]).get(
id=translation.id
)
@translation_router.delete("/{id}", response_model=Translation) @translation_router.delete("/{id}", response_model=Translation)
async def delete_translation(id: int): async def delete_translation(id: int):
translation = await TranslationDB.objects.prefetch_related(["book", "author"]).get_or_none(id=id) translation = await TranslationDB.objects.prefetch_related(
["book", "author"]
).get_or_none(id=id)
if translation is None: if translation is None:
raise HTTPException(status.HTTP_404_NOT_FOUND) raise HTTPException(status.HTTP_404_NOT_FOUND)

View File

@@ -1,11 +1,11 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi_pagination import add_pagination
import aioredis
from core.db import database import aioredis
from core.config import env_config from fastapi_pagination import add_pagination
from app.views import routers from app.views import routers
from core.config import env_config
from core.db import database
def start_app() -> FastAPI: def start_app() -> FastAPI:
@@ -25,13 +25,13 @@ def start_app() -> FastAPI:
add_pagination(app) add_pagination(app)
@app.on_event('startup') @app.on_event("startup")
async def startup() -> None: async def startup() -> None:
database_ = app.state.database database_ = app.state.database
if not database_.is_connected: if not database_.is_connected:
await database_.connect() await database_.connect()
@app.on_event('shutdown') @app.on_event("shutdown")
async def shutdown() -> None: async def shutdown() -> None:
database_ = app.state.database database_ = app.state.database
if database_.is_connected: if database_.is_connected:

View File

@@ -18,8 +18,8 @@ class EnvConfig(BaseSettings):
REDIS_PASSWORD: Optional[str] REDIS_PASSWORD: Optional[str]
class Config: class Config:
env_file = '.env' env_file = ".env"
env_file_encoding = 'utf-8' env_file_encoding = "utf-8"
env_config = EnvConfig() env_config = EnvConfig()

View File

@@ -1,6 +1,6 @@
from urllib.parse import quote from urllib.parse import quote
from databases import Database
from databases import Database
from sqlalchemy import MetaData from sqlalchemy import MetaData
from core.config import env_config from core.config import env_config

View File

@@ -24,3 +24,43 @@ pytest = "^5.2"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.black]
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.vscode
| \venv
| alembic
)/
'''
[tool.flake8]
ignore = [
# Whitespace before ':' ( https://www.flake8rules.com/rules/E203.html )
"E203"
]
max-line-length=88
max-complexity = 15
select = "B,C,E,F,W,T4,B9"
exclude = [
# No need to traverse our git directory
".git",
# There's no value in checking cache directories
"__pycache__",
# The conf file is mostly autogenerated, ignore it
"fastapi_book_server/app/alembic/*",
# The old directory contains Flake8 2.0
]
[tool.isort]
profile = "black"
only_sections = true
force_sort_within_sections = true
lines_after_imports = 2
lexicographical = true
sections = ["FUTURE", "STDLIB", "BASEFRAMEWORK", "FRAMEWORKEXT", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
known_baseframework = ["fastapi",]
known_frameworkext = ["starlette",]
src_paths = ["fastapi_book_server"]

View File

@@ -2,4 +2,4 @@ from fastapi_book_server import __version__
def test_version(): def test_version():
assert __version__ == '0.1.0' assert __version__ == "0.1.0"