From bf7807899c361929402d806cbf3e1cec2850aea9 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Sat, 30 Sep 2023 22:28:55 +0100 Subject: [PATCH 1/8] build: wip: joke example --- .../lib/api/v1/handlers/__init__.py | 3 ++- src/fastapi_app/lib/api/v1/handlers/joke.py | 22 ++++++++++++++++++ .../lib/api/v1/schemas/__init__.py | 5 ++-- src/fastapi_app/lib/api/v1/schemas/joke.py | 7 ++++++ src/fastapi_app/lib/app/app.py | 4 +++- src/fastapi_app/lib/joke/__init__.py | 3 +++ src/fastapi_app/lib/joke/services.py | 23 +++++++++++++++++++ src/fastapi_app/lib/models/joke.py | 10 ++++++++ 8 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/fastapi_app/lib/api/v1/handlers/joke.py create mode 100644 src/fastapi_app/lib/api/v1/schemas/joke.py create mode 100644 src/fastapi_app/lib/joke/__init__.py create mode 100644 src/fastapi_app/lib/joke/services.py create mode 100644 src/fastapi_app/lib/models/joke.py diff --git a/src/fastapi_app/lib/api/v1/handlers/__init__.py b/src/fastapi_app/lib/api/v1/handlers/__init__.py index e679678..cfea8ca 100644 --- a/src/fastapi_app/lib/api/v1/handlers/__init__.py +++ b/src/fastapi_app/lib/api/v1/handlers/__init__.py @@ -1,5 +1,6 @@ from .health import * +from .joke import get_joke, joke_router __all__ = [ - "basic_router", + "basic_router", "joke_router", "get_joke" ] diff --git a/src/fastapi_app/lib/api/v1/handlers/joke.py b/src/fastapi_app/lib/api/v1/handlers/joke.py new file mode 100644 index 0000000..50226a9 --- /dev/null +++ b/src/fastapi_app/lib/api/v1/handlers/joke.py @@ -0,0 +1,22 @@ +import fastapi + +import lib.api.v1.schemas as api_shemas +import lib.joke.services as services + +joke_router = fastapi.APIRouter() + + +@joke_router.get( + "/", + response_model=api_shemas.JokeResponse, + summary="Random joke", + description="Return a random joke from a free API.", +) +async def get_joke(joke_service: services.JokeService): + joke = await joke_service.get_joke() + if joke: + return api_shemas.JokeResponse( + joke=f"{joke.setup}\n{joke.punchline}", id=joke.id_field, category=joke.type_field + ) + + return api_shemas.JokeResponse(joke="No joke for you!", id=0, category="No category") diff --git a/src/fastapi_app/lib/api/v1/schemas/__init__.py b/src/fastapi_app/lib/api/v1/schemas/__init__.py index 3a43b09..d608846 100644 --- a/src/fastapi_app/lib/api/v1/schemas/__init__.py +++ b/src/fastapi_app/lib/api/v1/schemas/__init__.py @@ -1,5 +1,4 @@ from .base import HealthResponseModel +from .joke import JokeResponse -__all__ = [ - "HealthResponseModel", -] +__all__ = ["HealthResponseModel", "JokeResponse"] diff --git a/src/fastapi_app/lib/api/v1/schemas/joke.py b/src/fastapi_app/lib/api/v1/schemas/joke.py new file mode 100644 index 0000000..d146a9f --- /dev/null +++ b/src/fastapi_app/lib/api/v1/schemas/joke.py @@ -0,0 +1,7 @@ +import pydantic + + +class JokeResponse(pydantic.BaseModel): + id_field: int = pydantic.Field(alias="id") + joke: str + category: str diff --git a/src/fastapi_app/lib/app/app.py b/src/fastapi_app/lib/app/app.py index f20bb8d..c63283e 100644 --- a/src/fastapi_app/lib/app/app.py +++ b/src/fastapi_app/lib/app/app.py @@ -11,6 +11,7 @@ import lib.app.errors as app_errors import lib.app.settings as app_settings import lib.app.split_settings as app_split_settings import lib.clients as clients +import lib.joke.services as joke_services logger = logging.getLogger(__name__) @@ -69,12 +70,13 @@ class Application: # Services logger.info("Initializing services") + jk_serivces = joke_services.JokeService(http_client=http_client) # Handlers logger.info("Initializing handlers") liveness_probe_handler = api_v1_handlers.basic_router - + joke_handler = api_v1_handlers.get_joke(joke_service=jk_serivces) logger.info("Creating application") diff --git a/src/fastapi_app/lib/joke/__init__.py b/src/fastapi_app/lib/joke/__init__.py new file mode 100644 index 0000000..c55dd0a --- /dev/null +++ b/src/fastapi_app/lib/joke/__init__.py @@ -0,0 +1,3 @@ +from .services import JokeService + +__all__ = ["JokeService"] diff --git a/src/fastapi_app/lib/joke/services.py b/src/fastapi_app/lib/joke/services.py new file mode 100644 index 0000000..e6aaa66 --- /dev/null +++ b/src/fastapi_app/lib/joke/services.py @@ -0,0 +1,23 @@ +import logging + +import httpx +import pydantic + +import lib.models.joke as joke_models + + +class JokeService: + def __init__(self, http_client: httpx.AsyncClient): + self.http_client = http_client + self.logger = logging.getLogger(__name__) + + async def get_joke(self) -> joke_models.Joke | None: + try: + async with self.http_client as client: + response = await client.get("https://official-joke-api.appspot.com/random_joke") + content = response.json() + return joke_models.Joke(**content) + except pydantic.ValidationError as error: + self.logger.exception("Validation Error: %s", error) + except httpx.HTTPError as error: + self.logger.exception("HTTP Error: %s", error) diff --git a/src/fastapi_app/lib/models/joke.py b/src/fastapi_app/lib/models/joke.py new file mode 100644 index 0000000..c17abae --- /dev/null +++ b/src/fastapi_app/lib/models/joke.py @@ -0,0 +1,10 @@ +import pydantic + + +class Joke(pydantic.BaseModel): + """Joke model.""" + + id_field: int = pydantic.Field(alias="id") + type_field: str = pydantic.Field(alias="type") + setup: str + punchline: str From 741b84b875302bd130f4d976be6650a2ebfa001b Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Mon, 2 Oct 2023 20:06:13 +0100 Subject: [PATCH 2/8] chore: update pyright --- package-lock.json | 43 ++++++++++++++++++++++++++++++------- package.json | 2 +- src/fastapi_app/poetry.lock | 6 +++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 56659ee..3e394ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "husky": "^8.0.1", "lint-staged": "^13.0.3", "prettier": "2.8.3", - "pyright": "^1.1.299" + "pyright": "^1.1.329" } }, "node_modules/@babel/code-frame": { @@ -1063,6 +1063,20 @@ "node": ">=14.14" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2215,9 +2229,9 @@ } }, "node_modules/pyright": { - "version": "1.1.299", - "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.299.tgz", - "integrity": "sha512-37agqu0oNRsftFHNyGtaN3SBJJ+Qe2pembdgIpif9iL0VNqCU7j3wauSm8j0peXg1uNHFcS8UMrft4hT7w2XvQ==", + "version": "1.1.329", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.329.tgz", + "integrity": "sha512-5AT98Mi0OYcDiQ5lD1nPJ3cq8gX/HHaXrQ5WjJ/QZkaJtGqnEdrUp5Gq5wBPipWgOnv/l5e50YScaaNDMjoy9Q==", "dev": true, "bin": { "pyright": "index.js", @@ -2225,6 +2239,9 @@ }, "engines": { "node": ">=12.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, "node_modules/q": { @@ -3883,6 +3900,13 @@ "universalify": "^2.0.0" } }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4702,10 +4726,13 @@ "dev": true }, "pyright": { - "version": "1.1.299", - "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.299.tgz", - "integrity": "sha512-37agqu0oNRsftFHNyGtaN3SBJJ+Qe2pembdgIpif9iL0VNqCU7j3wauSm8j0peXg1uNHFcS8UMrft4hT7w2XvQ==", - "dev": true + "version": "1.1.329", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.329.tgz", + "integrity": "sha512-5AT98Mi0OYcDiQ5lD1nPJ3cq8gX/HHaXrQ5WjJ/QZkaJtGqnEdrUp5Gq5wBPipWgOnv/l5e50YScaaNDMjoy9Q==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } }, "q": { "version": "1.5.1", diff --git a/package.json b/package.json index 50cf231..6db49a1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "husky": "^8.0.1", "lint-staged": "^13.0.3", "prettier": "2.8.3", - "pyright": "^1.1.299" + "pyright": "^1.1.329" }, "hooks": { "commit-msg": "commitlint" diff --git a/src/fastapi_app/poetry.lock b/src/fastapi_app/poetry.lock index 8f5de35..cfa62cc 100644 --- a/src/fastapi_app/poetry.lock +++ b/src/fastapi_app/poetry.lock @@ -988,13 +988,13 @@ pytest = ">=4.6" [[package]] name = "pyright" -version = "1.1.327" +version = "1.1.329" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.327-py3-none-any.whl", hash = "sha256:3462cda239e9140276238bbdbd0b59d77406f1c2e14d8cb8c20c8e25639c6b3c"}, - {file = "pyright-1.1.327.tar.gz", hash = "sha256:ba74148ad64f22020dbbed6781c4bdb38ecb8a7ca90dc3c87a4f08d1c0e11592"}, + {file = "pyright-1.1.329-py3-none-any.whl", hash = "sha256:c16f88a7ac14ddd0513e62fec56d69c37e3c6b412161ad16aa23a9c7e3dabaf4"}, + {file = "pyright-1.1.329.tar.gz", hash = "sha256:5baf82ff5ecb8c8b3ac400e8536348efbde0b94a09d83d5b440c0d143fd151a8"}, ] [package.dependencies] From c0e35c4c3f40fac61a92569e08cab5d6e1981cbc Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Mon, 2 Oct 2023 20:07:19 +0100 Subject: [PATCH 3/8] build: add models --- src/fastapi_app/lib/models/__init__.py | 5 +-- src/fastapi_app/lib/models/base_sqlalchemy.py | 36 ------------------- src/fastapi_app/lib/models/orm/__init__.py | 4 +++ src/fastapi_app/lib/models/orm/base.py | 35 ++++++++++++++++++ src/fastapi_app/lib/models/orm/joke.py | 13 +++++++ 5 files changed, 55 insertions(+), 38 deletions(-) delete mode 100644 src/fastapi_app/lib/models/base_sqlalchemy.py create mode 100644 src/fastapi_app/lib/models/orm/__init__.py create mode 100644 src/fastapi_app/lib/models/orm/base.py create mode 100644 src/fastapi_app/lib/models/orm/joke.py diff --git a/src/fastapi_app/lib/models/__init__.py b/src/fastapi_app/lib/models/__init__.py index 33fb529..db9c34f 100644 --- a/src/fastapi_app/lib/models/__init__.py +++ b/src/fastapi_app/lib/models/__init__.py @@ -1,4 +1,5 @@ -from .base_sqlalchemy import Base, IdCreatedUpdatedBaseMixin +from .joke import Joke +from .orm import Base, IdCreatedUpdatedBaseMixin, JokeORM from .token import Token -__all__ = ["Base", "IdCreatedUpdatedBaseMixin", "Token"] +__all__ = ["Base", "IdCreatedUpdatedBaseMixin", "Joke", "JokeORM", "Token"] diff --git a/src/fastapi_app/lib/models/base_sqlalchemy.py b/src/fastapi_app/lib/models/base_sqlalchemy.py deleted file mode 100644 index 2023439..0000000 --- a/src/fastapi_app/lib/models/base_sqlalchemy.py +++ /dev/null @@ -1,36 +0,0 @@ -import uuid - -import sqlalchemy -import sqlalchemy.dialects.postgresql -import sqlalchemy.ext.declarative -import sqlalchemy.orm - - -class Base(sqlalchemy.orm.DeclarativeBase): - """Base class for all models.""" - - pass - - -class IdCreatedUpdatedBaseMixin(Base): - @sqlalchemy.ext.declarative.declared_attr - def uuid(cls): - return sqlalchemy.Column( - sqlalchemy.dialects.postgresql.UUID(as_uuid=True), - primary_key=True, - default=uuid.uuid4, - unique=True, - nullable=False, - ) - - @sqlalchemy.ext.declarative.declared_attr - def created_at(cls): - return sqlalchemy.Column(sqlalchemy.DateTime, server_default=sqlalchemy.sql.func.now()) - - @sqlalchemy.ext.declarative.declared_attr - def updated_at(cls): - return sqlalchemy.Column(sqlalchemy.DateTime, server_default=sqlalchemy.sql.func.now()) - - @sqlalchemy.ext.declarative.declared_attr.directive - def __tablename__(cls) -> str: - return cls.__name__.lower() diff --git a/src/fastapi_app/lib/models/orm/__init__.py b/src/fastapi_app/lib/models/orm/__init__.py new file mode 100644 index 0000000..f01a3d8 --- /dev/null +++ b/src/fastapi_app/lib/models/orm/__init__.py @@ -0,0 +1,4 @@ +from .base import Base, IdCreatedUpdatedBaseMixin +from .joke import JokeORM + +__all__ = ["Base", "IdCreatedUpdatedBaseMixin", "JokeORM"] diff --git a/src/fastapi_app/lib/models/orm/base.py b/src/fastapi_app/lib/models/orm/base.py new file mode 100644 index 0000000..3d1ace3 --- /dev/null +++ b/src/fastapi_app/lib/models/orm/base.py @@ -0,0 +1,35 @@ +import datetime +import uuid + +import sqlalchemy +import sqlalchemy.dialects.postgresql +import sqlalchemy.ext.declarative +import sqlalchemy.orm as sa_orm +import sqlalchemy.sql as sa_sql + + +class Base(sa_orm.DeclarativeBase): + """Base class for all models.""" + + @sqlalchemy.ext.declarative.declared_attr.directive + def __tablename__(cls): + return cls.__name__.lower() + + __mapper_args__ = {"eager_defaults": True} + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + + +class IdCreatedUpdatedBaseMixin: + # id: sa_orm.Mapped[int] = sa_orm.mapped_column(primary_key=True) + # id_field: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(name="uuid", primary_key=True, unique=True, default=uuid.uuid4, nullable=False) + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(server_default=sa_sql.func.now()) + updated: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + + # __mapper_args__ = {"eager_defaults": True} + + # @sqlalchemy.ext.declarative.declared_attr.directive + # def __tablename__(cls) -> str: + # return cls.__name__.lower() diff --git a/src/fastapi_app/lib/models/orm/joke.py b/src/fastapi_app/lib/models/orm/joke.py new file mode 100644 index 0000000..38aa64d --- /dev/null +++ b/src/fastapi_app/lib/models/orm/joke.py @@ -0,0 +1,13 @@ +import sqlalchemy.orm as sa_orm + +import lib.models.orm.base as base + +Base = base.Base + + +class JokeORM(Base): + __tablename__ = "joke" # type: ignore + + type_field: sa_orm.Mapped[str] = sa_orm.mapped_column(name="type", nullable=False) + setup: sa_orm.Mapped[str] = sa_orm.mapped_column() + punchline: sa_orm.Mapped[str] = sa_orm.mapped_column() From a7afc8fa7b431fdeee144c355ac80be63b5d41da Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Mon, 2 Oct 2023 20:08:07 +0100 Subject: [PATCH 4/8] build: joke repo & service --- src/fastapi_app/lib/joke/__init__.py | 3 ++- src/fastapi_app/lib/joke/repository.py | 30 ++++++++++++++++++++++++++ src/fastapi_app/lib/joke/services.py | 19 ++++++++++------ 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/fastapi_app/lib/joke/repository.py diff --git a/src/fastapi_app/lib/joke/__init__.py b/src/fastapi_app/lib/joke/__init__.py index c55dd0a..a26e7d9 100644 --- a/src/fastapi_app/lib/joke/__init__.py +++ b/src/fastapi_app/lib/joke/__init__.py @@ -1,3 +1,4 @@ +from .repository import JokeRepository from .services import JokeService -__all__ = ["JokeService"] +__all__ = ["JokeRepository", "JokeService"] diff --git a/src/fastapi_app/lib/joke/repository.py b/src/fastapi_app/lib/joke/repository.py new file mode 100644 index 0000000..dc11183 --- /dev/null +++ b/src/fastapi_app/lib/joke/repository.py @@ -0,0 +1,30 @@ +import logging + +import sqlalchemy.exc +import sqlalchemy.ext.asyncio as sa_asyncio + +import lib.models as models + + +class JokeRepository: + def __init__(self, async_session: sa_asyncio.async_sessionmaker[sa_asyncio.AsyncSession]): + self.async_session = async_session + self.logger = logging.getLogger(__name__) + + async def get_joke_by(self, id: int) -> models.JokeORM | None: + try: + async with self.async_session() as session: + joke = await session.get(models.JokeORM, id) + return joke + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) + + async def add_joke(self, joke: models.JokeORM) -> models.JokeORM | None: + try: + async with self.async_session() as session: + session.add(joke) + await session.commit() + await session.refresh(joke) + return joke + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) diff --git a/src/fastapi_app/lib/joke/services.py b/src/fastapi_app/lib/joke/services.py index e6aaa66..7569500 100644 --- a/src/fastapi_app/lib/joke/services.py +++ b/src/fastapi_app/lib/joke/services.py @@ -3,20 +3,27 @@ import logging import httpx import pydantic -import lib.models.joke as joke_models +import lib.joke.repository as joke_repository +import lib.models as models class JokeService: - def __init__(self, http_client: httpx.AsyncClient): - self.http_client = http_client + def __init__(self, repository: joke_repository.JokeRepository): + self.repository = repository self.logger = logging.getLogger(__name__) - async def get_joke(self) -> joke_models.Joke | None: + async def get_joke(self) -> models.Joke | None: try: - async with self.http_client as client: + async with httpx.AsyncClient() as client: response = await client.get("https://official-joke-api.appspot.com/random_joke") content = response.json() - return joke_models.Joke(**content) + self.logger.info("Joke retrieved from API") + formatted_joke = models.JokeORM( + type_field=content["type"], setup=content["setup"], punchline=content["punchline"] + ) + await self.repository.add_joke(formatted_joke) + self.logger.info("Joke added to database") + return models.Joke(**content) except pydantic.ValidationError as error: self.logger.exception("Validation Error: %s", error) except httpx.HTTPError as error: From 18abf8759874112425c2bf84d81459fee4fcd569 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Mon, 2 Oct 2023 20:08:31 +0100 Subject: [PATCH 5/8] build: joke handler & app --- .../lib/api/v1/handlers/__init__.py | 8 ++--- src/fastapi_app/lib/api/v1/handlers/joke.py | 35 +++++++++++-------- src/fastapi_app/lib/app/app.py | 8 +++-- src/fastapi_app/lib/clients/__init__.py | 3 +- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/fastapi_app/lib/api/v1/handlers/__init__.py b/src/fastapi_app/lib/api/v1/handlers/__init__.py index cfea8ca..0335d3f 100644 --- a/src/fastapi_app/lib/api/v1/handlers/__init__.py +++ b/src/fastapi_app/lib/api/v1/handlers/__init__.py @@ -1,6 +1,4 @@ -from .health import * -from .joke import get_joke, joke_router +from .health import basic_router +from .joke import JokeHandler -__all__ = [ - "basic_router", "joke_router", "get_joke" -] +__all__ = ["JokeHandler", "basic_router"] diff --git a/src/fastapi_app/lib/api/v1/handlers/joke.py b/src/fastapi_app/lib/api/v1/handlers/joke.py index 50226a9..066e642 100644 --- a/src/fastapi_app/lib/api/v1/handlers/joke.py +++ b/src/fastapi_app/lib/api/v1/handlers/joke.py @@ -1,22 +1,27 @@ import fastapi import lib.api.v1.schemas as api_shemas -import lib.joke.services as services - -joke_router = fastapi.APIRouter() +import lib.joke.services as joke_services -@joke_router.get( - "/", - response_model=api_shemas.JokeResponse, - summary="Random joke", - description="Return a random joke from a free API.", -) -async def get_joke(joke_service: services.JokeService): - joke = await joke_service.get_joke() - if joke: - return api_shemas.JokeResponse( - joke=f"{joke.setup}\n{joke.punchline}", id=joke.id_field, category=joke.type_field +class JokeHandler: + router = fastapi.APIRouter() + + def __init__(self, joke_service: joke_services.JokeService): + self.joke_service = joke_service + self.router = fastapi.APIRouter() + self.router.add_api_route( + "/", + self.get_joke, + methods=["GET"], + summary="Статус работоспособности", + description="Проверяет доступность сервиса FastAPI.", ) - return api_shemas.JokeResponse(joke="No joke for you!", id=0, category="No category") + async def get_joke(self): + joke = await self.joke_service.get_joke() + if joke: + return api_shemas.JokeResponse( + joke=f"{joke.setup}\n{joke.punchline}", id=joke.id_field, category=joke.type_field + ) + return api_shemas.JokeResponse(joke="No joke for you!", id=0, category="No category") diff --git a/src/fastapi_app/lib/app/app.py b/src/fastapi_app/lib/app/app.py index c63283e..1366d98 100644 --- a/src/fastapi_app/lib/app/app.py +++ b/src/fastapi_app/lib/app/app.py @@ -11,6 +11,7 @@ import lib.app.errors as app_errors import lib.app.settings as app_settings import lib.app.split_settings as app_split_settings import lib.clients as clients +import lib.joke.repository as joke_repository import lib.joke.services as joke_services logger = logging.getLogger(__name__) @@ -46,7 +47,6 @@ class Application: logger.info("Initializing global clients") postgres_client = clients.AsyncPostgresClient(settings=settings) - http_client = clients.get_async_http_session() disposable_resources.append( DisposableResource( @@ -62,6 +62,7 @@ class Application: # Repositories logger.info("Initializing repositories") + jk_repository = joke_repository.JokeRepository(async_session=postgres_client.get_async_session()) # Caches @@ -70,13 +71,13 @@ class Application: # Services logger.info("Initializing services") - jk_serivces = joke_services.JokeService(http_client=http_client) + jk_serivces = joke_services.JokeService(jk_repository) # Handlers logger.info("Initializing handlers") liveness_probe_handler = api_v1_handlers.basic_router - joke_handler = api_v1_handlers.get_joke(joke_service=jk_serivces) + joke_handler = api_v1_handlers.JokeHandler(joke_service=jk_serivces).router logger.info("Creating application") @@ -90,6 +91,7 @@ class Application: # Routes fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"]) + fastapi_app.include_router(joke_handler, prefix="/test", tags=["some"]) application = Application( settings=settings, diff --git a/src/fastapi_app/lib/clients/__init__.py b/src/fastapi_app/lib/clients/__init__.py index 40d0e2f..1fbe64c 100644 --- a/src/fastapi_app/lib/clients/__init__.py +++ b/src/fastapi_app/lib/clients/__init__.py @@ -1,4 +1,3 @@ -from .httpx import get_async_http_session from .postgres import AsyncPostgresClient -__all__ = ["AsyncPostgresClient", "get_async_http_session"] +__all__ = ["AsyncPostgresClient"] From 8722d79ee086b43566fee9bec3e14c447132f94c Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Mon, 2 Oct 2023 20:08:49 +0100 Subject: [PATCH 6/8] build: joke alembic migration --- ...-10-02_9749b063b095_added_initial_table.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/fastapi_app/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py diff --git a/src/fastapi_app/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py b/src/fastapi_app/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py new file mode 100644 index 0000000..e300471 --- /dev/null +++ b/src/fastapi_app/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py @@ -0,0 +1,37 @@ +"""Added initial table + +Revision ID: 9749b063b095 +Revises: +Create Date: 2023-10-02 19:46:05.078494 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "9749b063b095" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "joke", + sa.Column("type", sa.String(), nullable=False), + sa.Column("setup", sa.String(), nullable=False), + sa.Column("punchline", sa.String(), nullable=False), + sa.Column("id", sa.Uuid(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("joke") + # ### end Alembic commands ### From 721651e2e264ab3f7b4801d9db82abddc9fef518 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Mon, 2 Oct 2023 20:09:09 +0100 Subject: [PATCH 7/8] chore: updated pyproject & deleted httpx client --- src/fastapi_app/lib/clients/httpx.py | 16 ---------------- src/fastapi_app/pyproject.toml | 2 ++ 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 src/fastapi_app/lib/clients/httpx.py diff --git a/src/fastapi_app/lib/clients/httpx.py b/src/fastapi_app/lib/clients/httpx.py deleted file mode 100644 index 5570d2c..0000000 --- a/src/fastapi_app/lib/clients/httpx.py +++ /dev/null @@ -1,16 +0,0 @@ -import contextlib -import typing - -import httpx - - -@contextlib.asynccontextmanager -async def get_async_http_session( - settings: dict[str, typing.Any] | None = None -) -> typing.AsyncGenerator[httpx.AsyncClient, None]: - """Async http client.""" - if settings is None: - settings = {} - client = httpx.AsyncClient(**settings) # Insert your own settings here - async with client as ac: - yield ac diff --git a/src/fastapi_app/pyproject.toml b/src/fastapi_app/pyproject.toml index 441feb0..54d66d3 100644 --- a/src/fastapi_app/pyproject.toml +++ b/src/fastapi_app/pyproject.toml @@ -129,6 +129,8 @@ ignore = [ "D415", # Type-checkers interpret redundant `as` as exporting an item "PLC0414", + # Permit using alias for 'import' + "PLR0402", # Causes churn and awful looking import blocks for little gain "TCH" ] From 45be1307328f8c6a751f6b171d2ea066fc10d011 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Tue, 3 Oct 2023 15:40:21 +0100 Subject: [PATCH 8/8] chore: deleted redundant router --- src/fastapi_app/lib/api/v1/handlers/joke.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/fastapi_app/lib/api/v1/handlers/joke.py b/src/fastapi_app/lib/api/v1/handlers/joke.py index 066e642..0743aeb 100644 --- a/src/fastapi_app/lib/api/v1/handlers/joke.py +++ b/src/fastapi_app/lib/api/v1/handlers/joke.py @@ -5,8 +5,6 @@ import lib.joke.services as joke_services class JokeHandler: - router = fastapi.APIRouter() - def __init__(self, joke_service: joke_services.JokeService): self.joke_service = joke_service self.router = fastapi.APIRouter()