ADD session deps, add usecase
parent
736cd533ec
commit
f73c722e01
|
@ -1,7 +1,13 @@
|
|||
import logging
|
||||
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -11,4 +17,10 @@ configure_logger(level=logging.INFO)
|
|||
def init_dependencies(app: FastAPI) -> None:
|
||||
"""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")
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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]
|
|
@ -3,6 +3,8 @@ from logging.config import fileConfig
|
|||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from fastfood_two.storage.pg_storage.models import Base
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
@ -16,7 +18,7 @@ if config.config_file_name is not None:
|
|||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = None
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
|
|
|
@ -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",
|
||||
]
|
|
@ -0,0 +1,5 @@
|
|||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
|
@ -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]
|
|
@ -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"))
|
|
@ -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",
|
||||
)
|
|
@ -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",
|
||||
)
|
|
@ -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: ...
|
Loading…
Reference in New Issue