From cbba30f2af0393562d1608e0208383c2d0b71b9d Mon Sep 17 00:00:00 2001 From: Kurbanov Bulat Date: Sat, 1 Jan 2022 20:54:59 +0300 Subject: [PATCH] Add linters configs --- .github/workflows/linters.yaml | 35 +++ .pre-commit-config.yaml | 20 ++ fastapi_book_server/__init__.py | 2 +- fastapi_book_server/app/alembic/env.py | 8 +- .../app/alembic/versions/08193b547a80_.py | 82 +++-- .../app/alembic/versions/b44117a41998_.py | 293 +++++++++++------- fastapi_book_server/app/depends.py | 4 +- fastapi_book_server/app/filters/book.py | 2 +- fastapi_book_server/app/models.py | 42 ++- fastapi_book_server/app/serializers/author.py | 2 +- .../app/serializers/sequence.py | 3 +- fastapi_book_server/app/services/author.py | 12 +- fastapi_book_server/app/services/book.py | 20 +- fastapi_book_server/app/services/common.py | 38 ++- fastapi_book_server/app/services/sequence.py | 5 +- .../app/services/translation.py | 26 +- fastapi_book_server/app/utils/pagination.py | 5 +- fastapi_book_server/app/views/__init__.py | 8 +- fastapi_book_server/app/views/author.py | 53 ++-- .../app/views/author_annotation.py | 20 +- fastapi_book_server/app/views/book.py | 39 ++- .../app/views/book_annotation.py | 22 +- fastapi_book_server/app/views/sequence.py | 43 ++- fastapi_book_server/app/views/source.py | 6 +- fastapi_book_server/app/views/translation.py | 28 +- fastapi_book_server/core/app.py | 12 +- fastapi_book_server/core/config.py | 4 +- fastapi_book_server/core/db.py | 2 +- pyproject.toml | 40 +++ tests/test_fastapi_book_server.py | 2 +- 30 files changed, 580 insertions(+), 298 deletions(-) create mode 100644 .github/workflows/linters.yaml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml new file mode 100644 index 0000000..3b3fb20 --- /dev/null +++ b/.github/workflows/linters.yaml @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5cf5615 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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', + ] diff --git a/fastapi_book_server/__init__.py b/fastapi_book_server/__init__.py index b794fd4..3dc1f76 100644 --- a/fastapi_book_server/__init__.py +++ b/fastapi_book_server/__init__.py @@ -1 +1 @@ -__version__ = '0.1.0' +__version__ = "0.1.0" diff --git a/fastapi_book_server/app/alembic/env.py b/fastapi_book_server/app/alembic/env.py index cd89223..dad7229 100644 --- a/fastapi_book_server/app/alembic/env.py +++ b/fastapi_book_server/app/alembic/env.py @@ -1,20 +1,22 @@ from logging.config import fileConfig +import os +import sys from alembic import context -import sys, os - from sqlalchemy.engine import create_engine from core.db import DATABASE_URL myPath = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, myPath + '/../../') +sys.path.insert(0, myPath + "/../../") config = context.config from app.models import BaseMeta + + target_metadata = BaseMeta.metadata diff --git a/fastapi_book_server/app/alembic/versions/08193b547a80_.py b/fastapi_book_server/app/alembic/versions/08193b547a80_.py index 962287a..175a026 100644 --- a/fastapi_book_server/app/alembic/versions/08193b547a80_.py +++ b/fastapi_book_server/app/alembic/versions/08193b547a80_.py @@ -10,35 +10,77 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '08193b547a80' -down_revision = 'b44117a41998' +revision = "08193b547a80" +down_revision = "b44117a41998" branch_labels = None depends_on = None def upgrade(): # ### 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_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.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') + 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("tgrm_books_title"), + "books", + ["title"], + postgresql_using="gin", + postgresql_ops={"description": "gin_trgm_ops"}, + ) + 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 ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - 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('tgrm_books_title'), 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_lf'), 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_author'), table_name='book_authors') + 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("tgrm_books_title"), 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_lf"), 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_author"), table_name="book_authors") # ### end Alembic commands ### diff --git a/fastapi_book_server/app/alembic/versions/b44117a41998_.py b/fastapi_book_server/app/alembic/versions/b44117a41998_.py index 6805c18..cf5cb60 100644 --- a/fastapi_book_server/app/alembic/versions/b44117a41998_.py +++ b/fastapi_book_server/app/alembic/versions/b44117a41998_.py @@ -11,7 +11,7 @@ from sqlalchemy.sql.schema import UniqueConstraint # revision identifiers, used by Alembic. -revision = 'b44117a41998' +revision = "b44117a41998" down_revision = None branch_labels = None depends_on = None @@ -19,128 +19,203 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('sources', - sa.Column('id', sa.SmallInteger(), nullable=False), - sa.Column('name', sa.String(length=32), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') + op.create_table( + "sources", + sa.Column("id", sa.SmallInteger(), nullable=False), + sa.Column("name", sa.String(length=32), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), ) - op.create_table('authors', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('source', sa.SmallInteger(), nullable=False), - sa.Column('remote_id', sa.Integer(), nullable=False), - sa.Column('first_name', sa.String(length=256), nullable=False), - sa.Column('last_name', sa.String(length=256), nullable=False), - sa.Column('middle_name', sa.String(length=256), nullable=True), - sa.ForeignKeyConstraint(['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( + "authors", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("source", sa.SmallInteger(), nullable=False), + sa.Column("remote_id", sa.Integer(), nullable=False), + sa.Column("first_name", sa.String(length=256), nullable=False), + sa.Column("last_name", sa.String(length=256), nullable=False), + sa.Column("middle_name", sa.String(length=256), nullable=True), + sa.ForeignKeyConstraint( + ["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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('source', sa.SmallInteger(), nullable=False), - sa.Column('remote_id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=256), nullable=False), - sa.Column('lang', sa.String(length=3), nullable=False), - sa.Column('file_type', sa.String(length=4), nullable=False), - sa.Column('uploaded', sa.Date(), nullable=False), - sa.Column('is_deleted', sa.Boolean(), server_default=sa.text('false'), nullable=False), - 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( + "books", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("source", sa.SmallInteger(), nullable=False), + sa.Column("remote_id", sa.Integer(), nullable=False), + sa.Column("title", sa.String(length=256), nullable=False), + sa.Column("lang", sa.String(length=3), nullable=False), + sa.Column("file_type", sa.String(length=4), nullable=False), + sa.Column("uploaded", sa.Date(), nullable=False), + sa.Column( + "is_deleted", sa.Boolean(), server_default=sa.text("false"), nullable=False + ), + 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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('source', sa.SmallInteger(), nullable=False), - sa.Column('remote_id', sa.Integer(), nullable=False), - sa.Column('code', sa.String(length=45), nullable=False), - sa.Column('description', sa.String(length=99), nullable=False), - sa.Column('meta', sa.String(length=45), nullable=False), - sa.ForeignKeyConstraint(['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( + "genres", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("source", sa.SmallInteger(), nullable=False), + sa.Column("remote_id", sa.Integer(), nullable=False), + sa.Column("code", sa.String(length=45), nullable=False), + sa.Column("description", sa.String(length=99), nullable=False), + sa.Column("meta", sa.String(length=45), nullable=False), + sa.ForeignKeyConstraint( + ["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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('source', sa.SmallInteger(), nullable=False), - sa.Column('remote_id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=256), nullable=False), - sa.ForeignKeyConstraint(['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( + "sequences", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("source", sa.SmallInteger(), nullable=False), + sa.Column("remote_id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=256), nullable=False), + sa.ForeignKeyConstraint( + ["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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('author', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=256), nullable=False), - sa.Column('text', sa.Text(), nullable=False), - sa.Column('file', sa.String(length=256), nullable=True), - sa.ForeignKeyConstraint(['author'], ['authors.id'], name='fk_author_annotations_authors_id_author'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('author') + op.create_table( + "author_annotations", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("author", sa.Integer(), nullable=False), + sa.Column("title", sa.String(length=256), nullable=False), + sa.Column("text", sa.Text(), nullable=False), + sa.Column("file", sa.String(length=256), nullable=True), + sa.ForeignKeyConstraint( + ["author"], ["authors.id"], name="fk_author_annotations_authors_id_author" + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("author"), ) - op.create_table('book_annotations', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('book', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=256), nullable=False), - sa.Column('text', sa.Text(), nullable=False), - sa.Column('file', sa.String(length=256), nullable=True), - sa.ForeignKeyConstraint(['book'], ['books.id'], name='fk_book_annotations_books_id_book'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('book') + op.create_table( + "book_annotations", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("book", sa.Integer(), nullable=False), + sa.Column("title", sa.String(length=256), nullable=False), + sa.Column("text", sa.Text(), nullable=False), + sa.Column("file", sa.String(length=256), nullable=True), + sa.ForeignKeyConstraint( + ["book"], ["books.id"], name="fk_book_annotations_books_id_book" + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("book"), ) - op.create_table('book_authors', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('author', sa.Integer(), nullable=True), - sa.Column('book', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['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_authors", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("author", sa.Integer(), nullable=True), + sa.Column("book", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('genre', sa.Integer(), nullable=True), - sa.Column('book', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['book'], ['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_genres", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("genre", sa.Integer(), nullable=True), + sa.Column("book", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["book"], + ["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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('position', sa.SmallInteger(), nullable=False), - sa.Column('sequence', sa.Integer(), nullable=True), - sa.Column('book', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['book'], ['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( + "book_sequences", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("position", sa.SmallInteger(), nullable=False), + sa.Column("sequence", sa.Integer(), nullable=True), + sa.Column("book", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["book"], + ["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', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('position', sa.SmallInteger(), nullable=False), - sa.Column('author', sa.Integer(), nullable=True), - sa.Column('book', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['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'), + op.create_table( + "translations", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("position", sa.SmallInteger(), nullable=False), + sa.Column("author", sa.Integer(), nullable=True), + sa.Column("book", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["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 ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('translations') - op.drop_table('book_sequences') - op.drop_table('book_genres') - op.drop_table('book_authors') - op.drop_table('book_annotations') - op.drop_table('author_annotations') - op.drop_table('sequences') - op.drop_table('genres') - op.drop_table('books') - op.drop_table('authors') - op.drop_table('sources') + op.drop_table("translations") + op.drop_table("book_sequences") + op.drop_table("book_genres") + op.drop_table("book_authors") + op.drop_table("book_annotations") + op.drop_table("author_annotations") + op.drop_table("sequences") + op.drop_table("genres") + op.drop_table("books") + op.drop_table("authors") + op.drop_table("sources") # ### end Alembic commands ### diff --git a/fastapi_book_server/app/depends.py b/fastapi_book_server/app/depends.py index b99768e..39e7e32 100644 --- a/fastapi_book_server/app/depends.py +++ b/fastapi_book_server/app/depends.py @@ -6,4 +6,6 @@ from core.config import env_config async def check_token(api_key: str = Security(default_security)): 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!" + ) diff --git a/fastapi_book_server/app/filters/book.py b/fastapi_book_server/app/filters/book.py index cca1b91..71cdf29 100644 --- a/fastapi_book_server/app/filters/book.py +++ b/fastapi_book_server/app/filters/book.py @@ -5,6 +5,6 @@ def get_book_filter(is_deleted: Optional[bool] = None) -> dict: result = {} if is_deleted is not None: - result['is_deleted'] = is_deleted + result["is_deleted"] = is_deleted return result diff --git a/fastapi_book_server/app/models.py b/fastapi_book_server/app/models.py index ca63178..6658bc7 100644 --- a/fastapi_book_server/app/models.py +++ b/fastapi_book_server/app/models.py @@ -64,9 +64,13 @@ class AuthorAnnotation(ormar.Model): 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 file: str = ormar.String(max_length=256, nullable=True) # type: ignore @@ -89,7 +93,7 @@ class Sequence(ormar.Model): class BookAuthors(ormar.Model): class Meta(BaseMeta): tablename = "book_authors" - + id: int = ormar.Integer(primary_key=True, nullable=False) # type: ignore @@ -103,7 +107,9 @@ class BookGenres(ormar.Model): class BookSequences(ormar.Model): class Meta(BaseMeta): tablename = "book_sequences" - orders_by = ["position", ] + orders_by = [ + "position", + ] id: int = ormar.Integer(primary_key=True, nullable=False) # type: ignore @@ -113,7 +119,9 @@ class BookSequences(ormar.Model): class Translation(ormar.Model): class Meta(BaseMeta): tablename = "translations" - orders_by = ["position", ] + orders_by = [ + "position", + ] 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) 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 file_type: str = ormar.String(max_length=4, nullable=False) # 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) - 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) sequences = ormar.ManyToMany(Sequence, through=BookSequences) @ormar.property_field def available_types(self) -> list[str]: - if self.file_type == 'fb2' and self.source.name == 'flibusta': - return ['fb2', 'fb2zip', 'epub', 'mobi'] + if self.file_type == "fb2" and self.source.name == "flibusta": + return ["fb2", "fb2zip", "epub", "mobi"] return [self.file_type] @@ -161,8 +175,12 @@ class BookAnnotation(ormar.Model): 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 file: str = ormar.String(max_length=256, nullable=True) # type: ignore diff --git a/fastapi_book_server/app/serializers/author.py b/fastapi_book_server/app/serializers/author.py index fd96856..c627951 100644 --- a/fastapi_book_server/app/serializers/author.py +++ b/fastapi_book_server/app/serializers/author.py @@ -1,5 +1,5 @@ -from typing import Optional from datetime import date +from typing import Optional from pydantic import BaseModel diff --git a/fastapi_book_server/app/serializers/sequence.py b/fastapi_book_server/app/serializers/sequence.py index 6aa5d1d..51858f5 100644 --- a/fastapi_book_server/app/serializers/sequence.py +++ b/fastapi_book_server/app/serializers/sequence.py @@ -1,5 +1,6 @@ -from typing import Optional from datetime import date +from typing import Optional + from pydantic import BaseModel from app.serializers.orjson_config import ORJSONConfig diff --git a/fastapi_book_server/app/services/author.py b/fastapi_book_server/app/services/author.py index 7acb907..f009a86 100644 --- a/fastapi_book_server/app/services/author.py +++ b/fastapi_book_server/app/services/author.py @@ -1,15 +1,17 @@ from app.models import Author - from app.services.common import TRGMSearchService, GetRandomService GET_OBJECT_IDS_QUERY = """ SELECT ARRAY( - WITH filtered_authors AS ( - SELECT + WITH filtered_authors AS ( + SELECT id, 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), :query) ) as sml, @@ -27,7 +29,7 @@ SELECT ARRAY( EXISTS ( SELECT * FROM book_authors LEFT JOIN books ON (books.id = book AND books.is_deleted = 'f') - WHERE author = authors.id + WHERE author = authors.id ) ) SELECT fauthors.id FROM filtered_authors as fauthors diff --git a/fastapi_book_server/app/services/book.py b/fastapi_book_server/app/services/book.py index 43f9507..473a0a0 100644 --- a/fastapi_book_server/app/services/book.py +++ b/fastapi_book_server/app/services/book.py @@ -2,15 +2,15 @@ from typing import Union from fastapi import HTTPException, status -from app.models import Book as BookDB, Author as AuthorDB - -from app.services.common import TRGMSearchService, GetRandomService +from app.models import Author as AuthorDB +from app.models import Book as BookDB from app.serializers.book import CreateBook, CreateRemoteBook +from app.services.common import TRGMSearchService, GetRandomService GET_OBJECT_IDS_QUERY = """ SELECT ARRAY( - WITH filtered_books AS ( + WITH filtered_books AS ( SELECT id, similarity(title, :query) as sml FROM books WHERE books.title % :query AND books.is_deleted = 'f' ) @@ -42,9 +42,7 @@ class BookCreator: if len(author_ids) != len(authors): cls._raise_bad_request() - book = await BookDB.objects.create( - **data_dict - ) + book = await BookDB.objects.create(**data_dict) for author in authors: await book.authors.add(author) @@ -56,14 +54,14 @@ class BookCreator: data_dict = data.dict() 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): cls._raise_bad_request() - book = await BookDB.objects.create( - **data_dict - ) + book = await BookDB.objects.create(**data_dict) for author in authors: await book.authors.add(author) diff --git a/fastapi_book_server/app/services/common.py b/fastapi_book_server/app/services/common.py index 2dafdde..d136efe 100644 --- a/fastapi_book_server/app/services/common.py +++ b/fastapi_book_server/app/services/common.py @@ -1,17 +1,17 @@ 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.bases import AbstractParams, RawParams -from app.utils.pagination import Page, CustomPage -import aioredis import orjson - from ormar import Model, QuerySet 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]): @@ -48,7 +48,9 @@ class TRGMSearchService(Generic[T]): @classmethod @property 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 @classmethod @@ -56,9 +58,9 @@ class TRGMSearchService(Generic[T]): row = await cls.database.fetch_one(cls.object_ids_query, {"query": query_data}) if row is None: - raise ValueError('Something is wrong!') + raise ValueError("Something is wrong!") - return row['array'] + return row["array"] @classmethod def get_cache_key(cls, query_data: str) -> str: @@ -66,7 +68,9 @@ class TRGMSearchService(Generic[T]): return f"{model_class_name}_{query_data}" @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: key = cls.get_cache_key(query_data) data = await redis.get(key) @@ -80,7 +84,9 @@ class TRGMSearchService(Generic[T]): return None @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: key = cls.get_cache_key(query_data) await redis.set(key, orjson.dumps(object_ids), ex=cls.CACHE_TTL) @@ -88,7 +94,9 @@ class TRGMSearchService(Generic[T]): print(e) @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() cached_object_ids = await cls.get_cached_ids(query_data, redis) @@ -99,7 +107,7 @@ class TRGMSearchService(Generic[T]): else: 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 @@ -117,11 +125,7 @@ class TRGMSearchService(Generic[T]): total, objects = await cls.get_objects(query, redis) - return CustomPage.create( - items=objects, - total=total, - params=params - ) + return CustomPage.create(items=objects, total=total, params=params) GET_RANDOM_OBJECT_ID_QUERY = """ diff --git a/fastapi_book_server/app/services/sequence.py b/fastapi_book_server/app/services/sequence.py index de89151..2e8a7a0 100644 --- a/fastapi_book_server/app/services/sequence.py +++ b/fastapi_book_server/app/services/sequence.py @@ -1,18 +1,17 @@ from app.models import Sequence - from app.services.common import TRGMSearchService, GetRandomService GET_OBJECT_IDS_QUERY = """ SELECT ARRAY ( WITH filtered_sequences AS ( - SELECT + SELECT id, similarity(name, :query) as sml, ( SELECT count(*) FROM book_sequences LEFT JOIN books ON (books.id = book AND books.is_deleted = 'f') - WHERE sequence = sequences.id + WHERE sequence = sequences.id ) as books_count FROM sequences WHERE name % :query AND diff --git a/fastapi_book_server/app/services/translation.py b/fastapi_book_server/app/services/translation.py index 249cb24..92f471c 100644 --- a/fastapi_book_server/app/services/translation.py +++ b/fastapi_book_server/app/services/translation.py @@ -2,10 +2,12 @@ from typing import Union 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.models import Translation as TranslationDB, Source as SourceDB, Book as BookDB, Author as AuthorDB - class TranslationCreator: @classmethod @@ -14,23 +16,27 @@ class TranslationCreator: @classmethod async def _create_translation(cls, data: CreateTranslation) -> TranslationDB: - return await TranslationDB.objects.create( - **data.dict() - ) + return await TranslationDB.objects.create(**data.dict()) @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) if source is None: 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: 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: cls._raise_bad_request() @@ -42,7 +48,9 @@ class TranslationCreator: ) @classmethod - async def create(cls, data: Union[CreateTranslation, CreateRemoteTranslation]) -> TranslationDB: + async def create( + cls, data: Union[CreateTranslation, CreateRemoteTranslation] + ) -> TranslationDB: if isinstance(data, CreateTranslation): return await cls._create_translation(data) if isinstance(data, CreateRemoteTranslation): diff --git a/fastapi_book_server/app/utils/pagination.py b/fastapi_book_server/app/utils/pagination.py index 63dbfb2..c6a6b0f 100644 --- a/fastapi_book_server/app/utils/pagination.py +++ b/fastapi_book_server/app/utils/pagination.py @@ -1,9 +1,8 @@ from typing import Protocol, TypeVar, Any, Generic, Sequence, runtime_checkable -from pydantic import conint - from fastapi_pagination import Page, Params from fastapi_pagination.bases import AbstractParams +from pydantic import conint @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]): diff --git a/fastapi_book_server/app/views/__init__.py b/fastapi_book_server/app/views/__init__.py index 260c086..1e28172 100644 --- a/fastapi_book_server/app/views/__init__.py +++ b/fastapi_book_server/app/views/__init__.py @@ -1,14 +1,10 @@ -from app.views.source import source_router - from app.views.author import author_router from app.views.author_annotation import author_annotation_router - from app.views.book import book_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.source import source_router +from app.views.translation import translation_router routers = [ diff --git a/fastapi_book_server/app/views/author.py b/fastapi_book_server/app/views/author.py index 8bbb999..66e220c 100644 --- a/fastapi_book_server/app/views/author.py +++ b/fastapi_book_server/app/views/author.py @@ -1,16 +1,22 @@ -from random import choice as random_choice - from fastapi import APIRouter, Depends, Request, HTTPException, status from fastapi_pagination import Params 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.serializers.author import Author, CreateAuthor, UpdateAuthor, AuthorBook, TranslatedBook +from app.depends import check_token +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.services.author import AuthorTGRMSearchService, GetRandomAuthorService -from app.depends import check_token +from app.utils.pagination import CustomPage author_router = APIRouter( @@ -20,22 +26,19 @@ author_router = APIRouter( ) - 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(): - return await paginate( - AuthorDB.objects.prefetch_related(PREFETCH_RELATED) - ) + return await paginate(AuthorDB.objects.prefetch_related(PREFETCH_RELATED)) @author_router.post("/", response_model=Author, dependencies=[Depends(Params)]) async def create_author(data: CreateAuthor): - author = await AuthorDB.objects.create( - **data.dict() - ) + author = await AuthorDB.objects.create(**data.dict()) 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) 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: raise HTTPException(status.HTTP_404_NOT_FOUND) @@ -75,24 +80,32 @@ async def get_author_annotation(id: int): if annotation is None: raise HTTPException(status.HTTP_404_NOT_FOUND) - + 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): 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]) async def get_translated_books(id: int): 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): return await AuthorTGRMSearchService.get(query, request.app.state.redis) diff --git a/fastapi_book_server/app/views/author_annotation.py b/fastapi_book_server/app/views/author_annotation.py index 00f5d5b..b2df4be 100644 --- a/fastapi_book_server/app/views/author_annotation.py +++ b/fastapi_book_server/app/views/author_annotation.py @@ -3,9 +3,13 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi_pagination import Params, Page 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.models import AuthorAnnotation as AuthorAnnotationDB +from app.serializers.author_annotation import ( + AuthorAnnotation, + CreateAuthorAnnotation, + UpdateAuthorAnnotation, +) 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(): - return await paginate( - AuthorAnnotationDB.objects - ) + return await paginate(AuthorAnnotationDB.objects) @author_annotation_router.post("/", response_model=AuthorAnnotation) async def create_author_annotation(data: CreateAuthorAnnotation): - return await AuthorAnnotationDB.objects.create( - **data.dict() - ) + return await AuthorAnnotationDB.objects.create(**data.dict()) @author_annotation_router.get("/{id}", response_model=AuthorAnnotation) diff --git a/fastapi_book_server/app/views/book.py b/fastapi_book_server/app/views/book.py index ab8273e..e058da8 100644 --- a/fastapi_book_server/app/views/book.py +++ b/fastapi_book_server/app/views/book.py @@ -1,18 +1,26 @@ from typing import Union -from random import choice as random_choice from fastapi import APIRouter, Depends, Request, HTTPException, status from fastapi_pagination import Params 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.serializers.book import Book, RemoteBook, BookDetail, CreateBook, UpdateBook, CreateRemoteBook +from app.depends import check_token +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.services.book import BookTGRMSearchService, GetRandomBookService, BookCreator -from app.filters.book import get_book_filter -from app.depends import check_token +from app.utils.pagination import CustomPage book_router = APIRouter( @@ -22,10 +30,12 @@ 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)): return await paginate( BookDB.objects.select_related(SELECT_RELATED_FIELDS).filter(**book_filter) @@ -49,7 +59,7 @@ async def get_random_book(): @book_router.get("/{id}", response_model=BookDetail) async def get_book(id: int): book = await BookDB.objects.select_related(SELECT_RELATED_FIELDS).get_or_none(id=id) - + if book is None: raise HTTPException(status.HTTP_404_NOT_FOUND) @@ -59,8 +69,7 @@ async def get_book(id: int): @book_router.get("/remote/{source_id}/{remote_id}", response_model=Book) async def get_remote_book(source_id: int, remote_id: int): book = await BookDB.objects.select_related(SELECT_RELATED_FIELDS).get_or_none( - source=source_id, - remote_id=remote_id + source=source_id, remote_id=remote_id ) if book is None: @@ -84,9 +93,7 @@ async def update_book(id: int, data: UpdateBook): author_ids = data_dict.pop("authors", []) authors = await AuthorDB.objects.filter(id__in=author_ids).all() - book = await BookDB.objects.create( - **data_dict - ) + book = await BookDB.objects.create(**data_dict) for author in authors: await book.authors.add(author) @@ -104,6 +111,8 @@ async def get_book_annotation(id: int): 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): return await BookTGRMSearchService.get(query, request.app.state.redis) diff --git a/fastapi_book_server/app/views/book_annotation.py b/fastapi_book_server/app/views/book_annotation.py index 3ff3b3f..2f71496 100644 --- a/fastapi_book_server/app/views/book_annotation.py +++ b/fastapi_book_server/app/views/book_annotation.py @@ -3,30 +3,32 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi_pagination import Params, Page 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.models import BookAnnotation as BookAnnotationDB +from app.serializers.book_annotation import ( + BookAnnotation, + CreateBookAnnotation, + UpdateBookAnnotation, +) book_annotation_router = APIRouter( prefix="/api/v1/book_annotations", 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(): - return await paginate( - BookAnnotationDB.objects - ) + return await paginate(BookAnnotationDB.objects) @book_annotation_router.post("/", response_model=BookAnnotation) async def create_book_annotation(data: CreateBookAnnotation): - return await BookAnnotationDB.objects.create( - **data.dict() - ) + return await BookAnnotationDB.objects.create(**data.dict()) @book_annotation_router.get("/{id}", response_model=BookAnnotation) diff --git a/fastapi_book_server/app/views/sequence.py b/fastapi_book_server/app/views/sequence.py index eac7abb..4a55453 100644 --- a/fastapi_book_server/app/views/sequence.py +++ b/fastapi_book_server/app/views/sequence.py @@ -1,15 +1,15 @@ -from random import choice as random_choice - from fastapi import APIRouter, Depends, Request from fastapi_pagination import Params 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.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( @@ -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(): - return await paginate( - SequenceDB.objects - ) + return await paginate(SequenceDB.objects) @sequence_router.get("/random", response_model=Sequence) @@ -38,21 +38,30 @@ async def get_sequence(id: int): 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): return await paginate( - BookDB.objects.select_related(["source", "annotations", "authors", "translators"]) - .filter(sequences__id=id).order_by("sequences__booksequences__position") + BookDB.objects.select_related( + ["source", "annotations", "authors", "translators"] + ) + .filter(sequences__id=id) + .order_by("sequences__booksequences__position") ) @sequence_router.post("/", response_model=Sequence) async def create_sequence(data: CreateSequence): - return await SequenceDB.objects.create( - **data.dict() - ) + return await SequenceDB.objects.create(**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): return await SequenceTGRMSearchService.get(query, request.app.state.redis) diff --git a/fastapi_book_server/app/views/source.py b/fastapi_book_server/app/views/source.py index aa4637c..0c1fe93 100644 --- a/fastapi_book_server/app/views/source.py +++ b/fastapi_book_server/app/views/source.py @@ -3,9 +3,9 @@ from fastapi import APIRouter, Depends from fastapi_pagination import Params, Page from fastapi_pagination.ext.ormar import paginate +from app.depends import check_token from app.models import Source as SourceDB from app.serializers.source import Source, CreateSource -from app.depends import check_token source_router = APIRouter( @@ -22,6 +22,4 @@ async def get_sources(): @source_router.post("", response_model=Source) async def create_source(data: CreateSource): - return await SourceDB.objects.create( - **data.dict() - ) + return await SourceDB.objects.create(**data.dict()) diff --git a/fastapi_book_server/app/views/translation.py b/fastapi_book_server/app/views/translation.py index 3850346..6202aa8 100644 --- a/fastapi_book_server/app/views/translation.py +++ b/fastapi_book_server/app/views/translation.py @@ -4,12 +4,16 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi_pagination import Params 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.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( @@ -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(): - return await paginate( - TranslationDB.objects.prefetch_related(["book", "author"]) - ) + return await paginate(TranslationDB.objects.prefetch_related(["book", "author"])) @translation_router.post("/", response_model=Translation) async def create_translation(data: Union[CreateTranslation, CreateRemoteTranslation]): 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) 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: raise HTTPException(status.HTTP_404_NOT_FOUND) diff --git a/fastapi_book_server/core/app.py b/fastapi_book_server/core/app.py index 6f05d57..642beb7 100644 --- a/fastapi_book_server/core/app.py +++ b/fastapi_book_server/core/app.py @@ -1,11 +1,11 @@ from fastapi import FastAPI -from fastapi_pagination import add_pagination -import aioredis -from core.db import database -from core.config import env_config +import aioredis +from fastapi_pagination import add_pagination from app.views import routers +from core.config import env_config +from core.db import database def start_app() -> FastAPI: @@ -25,13 +25,13 @@ def start_app() -> FastAPI: add_pagination(app) - @app.on_event('startup') + @app.on_event("startup") async def startup() -> None: database_ = app.state.database if not database_.is_connected: await database_.connect() - @app.on_event('shutdown') + @app.on_event("shutdown") async def shutdown() -> None: database_ = app.state.database if database_.is_connected: diff --git a/fastapi_book_server/core/config.py b/fastapi_book_server/core/config.py index b2c88c3..9016a9e 100644 --- a/fastapi_book_server/core/config.py +++ b/fastapi_book_server/core/config.py @@ -18,8 +18,8 @@ class EnvConfig(BaseSettings): REDIS_PASSWORD: Optional[str] class Config: - env_file = '.env' - env_file_encoding = 'utf-8' + env_file = ".env" + env_file_encoding = "utf-8" env_config = EnvConfig() diff --git a/fastapi_book_server/core/db.py b/fastapi_book_server/core/db.py index 6037ba3..0879e69 100644 --- a/fastapi_book_server/core/db.py +++ b/fastapi_book_server/core/db.py @@ -1,6 +1,6 @@ from urllib.parse import quote -from databases import Database +from databases import Database from sqlalchemy import MetaData from core.config import env_config diff --git a/pyproject.toml b/pyproject.toml index f50e4df..c59c616 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,3 +24,43 @@ pytest = "^5.2" [build-system] requires = ["poetry-core>=1.0.0"] 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"] diff --git a/tests/test_fastapi_book_server.py b/tests/test_fastapi_book_server.py index e1f39f1..14793ce 100644 --- a/tests/test_fastapi_book_server.py +++ b/tests/test_fastapi_book_server.py @@ -2,4 +2,4 @@ from fastapi_book_server import __version__ def test_version(): - assert __version__ == '0.1.0' + assert __version__ == "0.1.0"