ADD session deps, add usecase
parent
736cd533ec
commit
f73c722e01
|
@ -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")
|
||||||
|
|
|
@ -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 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:
|
||||||
|
|
|
@ -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