ADD session deps, add usecase

main
Сергей Ванюшкин 2024-08-20 22:35:48 +00:00
parent 736cd533ec
commit f73c722e01
16 changed files with 216 additions and 1 deletions

View File

@ -1,7 +1,13 @@
import logging import logging
from fastapi import FastAPI from fastapi import FastAPI
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
from fastfood_two.app.depends.session import (
create_engine,
create_session_maker,
get_session,
)
from fastfood_two.common.logger import configure_logger from fastfood_two.common.logger import configure_logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,4 +17,10 @@ configure_logger(level=logging.INFO)
def init_dependencies(app: FastAPI) -> None: def init_dependencies(app: FastAPI) -> None:
"""Initialize FastAPI dependencies.""" """Initialize FastAPI dependencies."""
# app.dependency_overrides[Settings] = get_settings
app.dependency_overrides[AsyncEngine] = create_engine
app.dependency_overrides[async_sessionmaker[AsyncSession]] = create_session_maker
app.dependency_overrides[AsyncSession] = get_session
logger.info("Dependencies initialized") logger.info("Dependencies initialized")

View File

@ -0,0 +1,37 @@
from collections.abc import AsyncIterable
from functools import lru_cache
from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from fastfood_two.common.stub import Stub
from fastfood_two.config import Config
@lru_cache()
def create_engine(
config: Annotated[Config, Depends(Stub(Config))],
) -> AsyncEngine:
return create_async_engine(config.db.url)
def create_session_maker(
engine: Annotated[AsyncEngine, Depends(Stub(AsyncEngine))],
) -> async_sessionmaker[AsyncSession]:
return async_sessionmaker(engine, expire_on_commit=False)
async def get_session(
session_maker: Annotated[
async_sessionmaker[AsyncSession],
Depends(Stub(async_sessionmaker[AsyncSession])),
],
) -> AsyncIterable[AsyncSession]:
async with session_maker() as session:
yield session

View File

@ -0,0 +1,9 @@
from typing import Generic, Protocol, TypeVar
Request = TypeVar("Request")
Response = TypeVar("Response")
class Interactor(Generic[Request, Response], Protocol): # type: ignore
async def __call__(self, request: Request) -> Response:
raise NotImplementedError

View File

@ -0,0 +1,41 @@
from collections.abc import Callable
class Stub:
"""
This class is used to prevent fastapi from digging into
real dependencies attributes detecting them as request data
So instead of
`interactor: Annotated[Interactor, Depends()]`
Write
`interactor: Annotated[Interactor, Depends(Stub(Interactor))]`
And then you can declare how to create it:
`app.dependency_overrids[Interactor] = some_real_factory`
"""
def __init__(self, dependency: Callable, **kwargs):
self._dependency = dependency
self._kwargs = kwargs
def __call__(self):
raise NotImplementedError
def __eq__(self, other) -> bool:
if isinstance(other, Stub):
return self._dependency == other._dependency and self._kwargs == other._kwargs
else:
if not self._kwargs:
return self._dependency == other
return False
def __hash__(self):
if not self._kwargs:
return hash(self._dependency)
serial = (
self._dependency,
*self._kwargs.items(),
)
return hash(serial)

View File

View File

@ -0,0 +1,14 @@
from dataclasses import dataclass
from uuid import UUID
@dataclass(frozen=True)
class MenuResponse:
id: UUID
name: str
description: str
@dataclass(frozen=True)
class MenusResponse:
menus: list[MenuResponse]

View File

@ -3,6 +3,8 @@ from logging.config import fileConfig
from alembic import context from alembic import context
from sqlalchemy import engine_from_config, pool from sqlalchemy import engine_from_config, pool
from fastfood_two.storage.pg_storage.models import Base
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
config = context.config config = context.config
@ -16,7 +18,7 @@ if config.config_file_name is not None:
# for 'autogenerate' support # for 'autogenerate' support
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
target_metadata = None target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,
# can be acquired: # can be acquired:

View File

@ -0,0 +1,14 @@
from .base import Base
from .menu_model import Menu
from .submenu_model import SubMenu
from .dish_model import Dish
from .common_attrs import uuidpk, str_25
__all__ = [
"Base",
"Menu",
"SubMenu",
"Dish",
"uuidpk",
"str_25",
]

View File

@ -0,0 +1,5 @@
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass

View File

@ -0,0 +1,16 @@
import uuid
from typing import Annotated
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import mapped_column
uuidpk = Annotated[
uuid.UUID,
mapped_column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
),
]
str_25 = Annotated[str, 25]

View File

@ -0,0 +1,17 @@
import uuid
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from . import Base, str_25, uuidpk
class Dish(Base):
__tablename__ = "dish"
id: Mapped[uuidpk]
title: Mapped[str_25]
description: Mapped[str | None]
price: Mapped[float]
parent_submenu: Mapped[uuid.UUID] = mapped_column(ForeignKey("submenu.id", ondelete="CASCADE"))

View File

@ -0,0 +1,18 @@
from sqlalchemy.orm import Mapped, relationship
from . import Base, str_25, uuidpk
class Menu(Base):
__tablename__ = "menu"
id: Mapped[uuidpk]
title: Mapped[str_25]
description: Mapped[str | None]
submenus: Mapped[list["SubMenu"]] = relationship(
"SubMenu",
backref="menu",
lazy="selectin",
cascade="all, delete",
)

View File

@ -0,0 +1,22 @@
import uuid
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.properties import ForeignKey
from . import Base, str_25, uuidpk
class SubMenu(Base):
__tablename__ = "submenu"
id: Mapped[uuidpk]
title: Mapped[str_25]
description: Mapped[str | None]
parent_menu: Mapped[uuid.UUID] = mapped_column(ForeignKey("menu.id", ondelete="CASCADE"))
dishes: Mapped[list["Dish"]] = relationship(
"Dish",
backref="submenu",
lazy="selectin",
cascade="all, delete",
)

View File

View File

@ -0,0 +1,8 @@
from fastfood_two.common.interactor import Interactor
from fastfood_two.contracts.menu_contracts import MenusResponse
class GetAllMenus(Interactor[None, MenusResponse]):
def __init__(self) -> None: ...
async def __call__(self, request=None) -> MenusResponse: ...