This commit is contained in:
2023-05-06 00:12:30 +02:00
parent db14333f06
commit 179ac44d7d
24 changed files with 98 additions and 48 deletions

View File

@@ -2,18 +2,17 @@ exclude: 'docs|node_modules|migrations|.git|.tox'
repos:
- repo: https://github.com/ambv/black
rev: 22.12.0
rev: 23.3.0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.216'
rev: 'v0.0.265'
hooks:
- id: ruff
args: ["--force-exclude"]
- repo: https://github.com/crate-ci/typos
rev: v1.13.6
rev: typos-dict-v0.9.26
hooks:
- id: typos

View File

@@ -3,6 +3,7 @@ from typing import TypedDict
from app.models import Author
from app.services.common import GetRandomService, MeiliSearchService, TRGMSearchService
GET_OBJECT_IDS_QUERY = """
SELECT ARRAY(
WITH filtered_authors AS (

View File

@@ -8,6 +8,7 @@ from app.services.common import (
TRGMSearchService,
)
GET_OBJECT_IDS_QUERY = """
SELECT ARRAY(
WITH filtered_books AS (

View File

@@ -15,9 +15,10 @@ from redis import asyncio as aioredis
from sqlalchemy import Table
from app.utils.orjson_default import default as orjson_default
from app.utils.pagination import CustomPage, Page
from app.utils.pagination import Page
from core.config import env_config
MODEL = TypeVar("MODEL", bound=Model)
QUERY = TypeVar("QUERY", bound=TypedDict)
@@ -170,7 +171,7 @@ class BaseSearchService(Generic[MODEL, QUERY], BaseService[MODEL, QUERY]):
total, objects = await cls.get_limited_objects(query, redis, no_cache)
return CustomPage.create(items=objects, total=total, params=params)
return Page.create(items=objects, total=total, params=params)
class SearchQuery(TypedDict):

View File

@@ -3,6 +3,7 @@ from typing import TypedDict
from app.models import Sequence
from app.services.common import GetRandomService, MeiliSearchService, TRGMSearchService
GET_OBJECT_IDS_QUERY = """
SELECT ARRAY (
WITH filtered_sequences AS (

View File

@@ -1,6 +1,7 @@
from app.models import Author
from app.services.common import MeiliSearchService, TRGMSearchService
GET_OBJECT_IDS_QUERY = """
SELECT ARRAY(
WITH filtered_authors AS (

View File

@@ -1,5 +1,7 @@
from typing import Any
import orjson
def default(value: Any):
if isinstance(value, frozenset):
@@ -7,3 +9,7 @@ def default(value: Any):
return "-".join(sorted(list_value))
return value
def orjson_dumps(v, *, default) -> str:
return orjson.dumps(v, default=default).decode()

View File

@@ -1,8 +1,18 @@
from typing import Any, Generic, Protocol, Sequence, TypeVar, runtime_checkable
from math import ceil
from typing import (
Any,
Generic,
Protocol,
Sequence,
TypeVar,
runtime_checkable,
)
from fastapi_pagination import Page, Params
from fastapi_pagination.bases import AbstractParams
from pydantic import conint
from fastapi_pagination import Params
from fastapi_pagination.bases import AbstractParams, BasePage
from fastapi_pagination.types import GreaterEqualOne, GreaterEqualZero
import orjson
from utils.orjson_default import orjson_dumps
@runtime_checkable
@@ -14,23 +24,36 @@ class ToDict(Protocol):
T = TypeVar("T", ToDict, Any)
class CustomPage(Page[T], Generic[T]):
total_pages: conint(ge=0) # type: ignore
class Page(BasePage[T], Generic[T]):
page: GreaterEqualOne
size: GreaterEqualOne
total_pages: GreaterEqualZero
__params_type__ = Params
class Config:
json_loads = orjson.loads
json_dumps = orjson_dumps
@classmethod
def create(
cls,
items: Sequence[T],
total: int,
params: AbstractParams,
) -> Page[T]:
*,
total: int,
**kwargs: Any,
) -> "Page[T]":
if not isinstance(params, Params):
raise ValueError("Page should be used with Params")
pages = ceil(total / params.size)
return cls(
total=total,
items=[item.dict() for item in items],
items=items,
page=params.page,
size=params.size,
total_pages=(total + params.size - 1) // params.size,
total_pages=pages,
**kwargs,
)

View File

@@ -8,6 +8,7 @@ from app.views.sequence import sequence_router
from app.views.source import source_router
from app.views.translation import translation_router
routers = [
source_router,
author_router,

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate
@@ -10,7 +11,8 @@ from app.serializers.author import Author, AuthorBook, TranslatedBook
from app.serializers.author_annotation import AuthorAnnotation
from app.services.author import AuthorMeiliSearchService, GetRandomAuthorService
from app.services.translator import TranslatorMeiliSearchService
from app.utils.pagination import CustomPage
from app.utils.pagination import Page
author_router = APIRouter(
prefix="/api/v1/authors",
@@ -23,9 +25,7 @@ PREFETCH_RELATED_FIELDS = ["source"]
SELECT_RELATED_FIELDS = ["annotations"]
@author_router.get(
"/", response_model=CustomPage[Author], dependencies=[Depends(Params)]
)
@author_router.get("/", response_model=Page[Author], dependencies=[Depends(Params)])
async def get_authors():
return await paginate(
AuthorDB.objects.select_related(SELECT_RELATED_FIELDS).prefetch_related(
@@ -75,7 +75,7 @@ async def get_author_annotation(id: int):
@author_router.get(
"/{id}/books", response_model=CustomPage[AuthorBook], dependencies=[Depends(Params)]
"/{id}/books", response_model=Page[AuthorBook], dependencies=[Depends(Params)]
)
async def get_author_books(
id: int, allowed_langs: list[str] = Depends(get_allowed_langs)
@@ -89,7 +89,7 @@ async def get_author_books(
@author_router.get(
"/search/{query}", response_model=CustomPage[Author], dependencies=[Depends(Params)]
"/search/{query}", response_model=Page[Author], dependencies=[Depends(Params)]
)
async def search_authors(
query: str,
@@ -109,7 +109,7 @@ translator_router = APIRouter(
)
@translator_router.get("/{id}/books", response_model=CustomPage[TranslatedBook])
@translator_router.get("/{id}/books", response_model=Page[TranslatedBook])
async def get_translated_books(
id: int, allowed_langs: list[str] = Depends(get_allowed_langs)
):
@@ -125,7 +125,7 @@ async def get_translated_books(
@translator_router.get(
"/search/{query}", response_model=CustomPage[Author], dependencies=[Depends(Params)]
"/search/{query}", response_model=Page[Author], dependencies=[Depends(Params)]
)
async def search_translators(
query: str,

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi_pagination import Page, Params
from fastapi_pagination.ext.ormar import paginate
@@ -6,6 +7,7 @@ from app.depends import check_token
from app.models import AuthorAnnotation as AuthorAnnotationDB
from app.serializers.author_annotation import AuthorAnnotation
author_annotation_router = APIRouter(
prefix="/api/v1/author_annotations",
tags=["author_annotation"],

View File

@@ -1,6 +1,7 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi_pagination import Params
from app.depends import check_token, get_allowed_langs
@@ -15,7 +16,8 @@ from app.services.book import (
BookMeiliSearchService,
GetRandomBookService,
)
from app.utils.pagination import CustomPage
from app.utils.pagination import Page
book_router = APIRouter(
prefix="/api/v1/books",
@@ -29,9 +31,7 @@ SELECT_RELATED_FIELDS = ["authors", "translators", "annotations"]
DETAIL_SELECT_RELATED_FIELDS = ["sequences", "genres"]
@book_router.get(
"/", response_model=CustomPage[RemoteBook], dependencies=[Depends(Params)]
)
@book_router.get("/", response_model=Page[RemoteBook], dependencies=[Depends(Params)])
async def get_books(
request: Request,
book_filter: dict = Depends(get_book_filter),
@@ -40,7 +40,7 @@ async def get_books(
@book_router.get(
"/base/", response_model=CustomPage[BookBaseInfo], dependencies=[Depends(Params)]
"/base/", response_model=Page[BookBaseInfo], dependencies=[Depends(Params)]
)
async def get_base_books_info(
request: Request, book_filter: dict = Depends(get_book_filter)
@@ -116,7 +116,7 @@ async def get_book_annotation(id: int):
@book_router.get(
"/search/{query}", response_model=CustomPage[Book], dependencies=[Depends(Params)]
"/search/{query}", response_model=Page[Book], dependencies=[Depends(Params)]
)
async def search_books(
query: str,

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi_pagination import Page, Params
from fastapi_pagination.ext.ormar import paginate
@@ -6,6 +7,7 @@ from app.depends import check_token
from app.models import BookAnnotation as BookAnnotationDB
from app.serializers.book_annotation import BookAnnotation
book_annotation_router = APIRouter(
prefix="/api/v1/book_annotations",
tags=["book_annotation"],

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate
@@ -7,7 +8,8 @@ from app.filters.genre import get_genre_filter
from app.models import Genre as GenreDB
from app.serializers.genre import Genre
from app.services.genre import GenreMeiliSearchService
from app.utils.pagination import CustomPage
from app.utils.pagination import Page
genre_router = APIRouter(
prefix="/api/v1/genres", tags=["genres"], dependencies=[Depends(check_token)]
@@ -17,7 +19,7 @@ genre_router = APIRouter(
PREFETCH_RELATED_FIELDS = ["source"]
@genre_router.get("/", response_model=CustomPage[Genre], dependencies=[Depends(Params)])
@genre_router.get("/", response_model=Page[Genre], dependencies=[Depends(Params)])
async def get_genres(genre_filter: dict = Depends(get_genre_filter)):
return await paginate(
GenreDB.objects.prefetch_related(PREFETCH_RELATED_FIELDS)
@@ -46,7 +48,7 @@ async def get_genre(id: int):
@genre_router.get(
"/search/{query}", response_model=CustomPage[Genre], dependencies=[Depends(Params)]
"/search/{query}", response_model=Page[Genre], dependencies=[Depends(Params)]
)
async def search_genres(
query: str,

View File

@@ -1,5 +1,6 @@
from fastapi import APIRouter
healtcheck_router = APIRouter(tags=["healthcheck"])

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, Request
from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate
@@ -8,7 +9,8 @@ from app.models import Sequence as SequenceDB
from app.serializers.sequence import Book as SequenceBook
from app.serializers.sequence import Sequence
from app.services.sequence import GetRandomSequenceService, SequenceMeiliSearchService
from app.utils.pagination import CustomPage
from app.utils.pagination import Page
sequence_router = APIRouter(
prefix="/api/v1/sequences",
@@ -17,9 +19,7 @@ sequence_router = APIRouter(
)
@sequence_router.get(
"/", response_model=CustomPage[Sequence], dependencies=[Depends(Params)]
)
@sequence_router.get("/", response_model=Page[Sequence], dependencies=[Depends(Params)])
async def get_sequences():
return await paginate(SequenceDB.objects)
@@ -44,7 +44,7 @@ async def get_sequence(id: int):
@sequence_router.get(
"/{id}/books",
response_model=CustomPage[SequenceBook],
response_model=Page[SequenceBook],
dependencies=[Depends(Params)],
)
async def get_sequence_books(
@@ -60,7 +60,7 @@ async def get_sequence_books(
@sequence_router.get(
"/search/{query}",
response_model=CustomPage[Sequence],
response_model=Page[Sequence],
dependencies=[Depends(Params)],
)
async def search_sequences(

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends
from fastapi_pagination import Page, Params
from fastapi_pagination.ext.ormar import paginate
@@ -6,6 +7,7 @@ from app.depends import check_token
from app.models import Source as SourceDB
from app.serializers.source import Source
source_router = APIRouter(
prefix="/api/v1/sources",
tags=["source"],

View File

@@ -1,11 +1,13 @@
from fastapi import APIRouter, Depends
from fastapi_pagination import Params
from fastapi_pagination.ext.ormar import paginate
from app.depends import check_token
from app.models import Translation as TranslationDB
from app.serializers.translation import Translation
from app.utils.pagination import CustomPage
from app.utils.pagination import Page
translation_router = APIRouter(
prefix="/api/v1/translation",
@@ -15,7 +17,7 @@ translation_router = APIRouter(
@translation_router.get(
"/", response_model=CustomPage[Translation], dependencies=[Depends(Params)]
"/", response_model=Page[Translation], dependencies=[Depends(Params)]
)
async def get_translations():
return await paginate(TranslationDB.objects.select_related(["book", "author"]))

View File

@@ -1,5 +1,6 @@
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from fastapi_pagination import add_pagination
from prometheus_fastapi_instrumentator import Instrumentator
from redis import asyncio as aioredis
@@ -9,6 +10,7 @@ from app.views import routers
from core.config import env_config
from core.db import database
sentry_sdk.init(
env_config.SENTRY_SDN,
)

View File

@@ -1,3 +1,4 @@
from fastapi.security import APIKeyHeader
default_security = APIKeyHeader(name="Authorization")

View File

@@ -5,6 +5,7 @@ from sqlalchemy import MetaData
from core.config import env_config
DATABASE_URL = (
f"postgresql://{env_config.POSTGRES_USER}:{quote(env_config.POSTGRES_PASSWORD)}@"
f"{env_config.POSTGRES_HOST}:{env_config.POSTGRES_PORT}/{env_config.POSTGRES_DB}"

View File

@@ -1,3 +1,4 @@
from core.app import start_app
app = start_app()

View File

@@ -65,14 +65,13 @@ max-complexity = 15
[tool.ruff.isort]
known-first-party = ["core", "app"]
force-sort-within-sections = true
force-wrap-aliases = true
section-order = ["future", "standard-library", "base_framework", "framework_ext", "third-party", "first-party", "local-folder"]
lines-after-imports = 2
# 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",]
[tool.ruff.isort.sections]
base_framework = ["fastapi",]
framework_ext = ["starlette"]
[tool.ruff.pyupgrade]
keep-runtime-typing = true

View File

@@ -1,5 +1,6 @@
import httpx
response = httpx.get(
"http://localhost:8080/healthcheck",
)