Add Domain entity
parent
cbcebdffb5
commit
fad06c45f8
|
@ -1,8 +0,0 @@
|
|||
from typing import Protocol
|
||||
|
||||
from fastfood_two.application.contracts.responses import MenuResponse
|
||||
|
||||
|
||||
class MenuGateway(Protocol):
|
||||
async def get_all_menus(self) -> list[MenuResponse]:
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,7 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AddMenuDTO:
|
||||
title: str
|
||||
description: str | None
|
|
@ -3,7 +3,7 @@ from uuid import UUID
|
|||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MenuResponse:
|
||||
class MenuDTO:
|
||||
id: UUID
|
||||
title: str
|
||||
description: str
|
||||
description: str | None
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from fastfood_two.application.abstractions.interactor import Interactor
|
||||
from fastfood_two.application.contracts.requests import AddMenuDTO
|
||||
from fastfood_two.application.contracts.responses import MenuDTO
|
||||
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||
from fastfood_two.domain.menu.menu_entity import Description, Menu, MenuId, Title
|
||||
|
||||
|
||||
class AddMenu(Interactor[AddMenuDTO, MenuDTO]):
|
||||
def __init__(self, gateway: MenuGateway) -> None:
|
||||
self._menu_gateway = gateway
|
||||
|
||||
async def __call__(self, request: AddMenuDTO) -> MenuDTO:
|
||||
|
||||
menu = await self._menu_gateway.insert_menu(
|
||||
menu=Menu(
|
||||
id=MenuId(uuid4()),
|
||||
title=Title(request.title),
|
||||
description=Description(request.description),
|
||||
)
|
||||
)
|
||||
return MenuDTO(
|
||||
id=menu.id.value,
|
||||
title=menu.title.value,
|
||||
description=menu.description.value,
|
||||
)
|
|
@ -1,12 +1,19 @@
|
|||
from fastfood_two.application.abstractions.interactor import Interactor
|
||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
||||
from fastfood_two.application.contracts.responses import MenuResponse
|
||||
from fastfood_two.application.contracts.responses import MenuDTO
|
||||
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||
|
||||
|
||||
class GetAllMenus(Interactor[None, list[MenuResponse]]):
|
||||
class GetAllMenus(Interactor[None, list[MenuDTO]]):
|
||||
def __init__(self, gateway: MenuGateway) -> None:
|
||||
self._menu_gateway = gateway
|
||||
|
||||
async def __call__(self, request=None) -> list[MenuResponse]:
|
||||
async def __call__(self, request=None) -> list[MenuDTO]:
|
||||
menus = await self._menu_gateway.get_all_menus()
|
||||
return menus
|
||||
return [
|
||||
MenuDTO(
|
||||
id=menu.id.value,
|
||||
title=menu.title.value,
|
||||
description=menu.description.value,
|
||||
)
|
||||
for menu in menus
|
||||
]
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from fastfood_two.domain.core.value_obj import DomainValueObject
|
||||
|
||||
EntityId = TypeVar("EntityId", bound=DomainValueObject)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DomainEntity(Generic[EntityId]):
|
||||
id: EntityId
|
|
@ -0,0 +1,8 @@
|
|||
class DomainError(Exception):
|
||||
def __init__(self, message: str, *args: object) -> None:
|
||||
self.message = message
|
||||
super().__init__(*args)
|
||||
|
||||
|
||||
class DomainValidationError(DomainError):
|
||||
pass
|
|
@ -0,0 +1,6 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainValueObject:
|
||||
pass
|
|
@ -0,0 +1,9 @@
|
|||
from fastfood_two.domain.core.error import DomainError
|
||||
|
||||
|
||||
class MenuNotFoundError(DomainError):
|
||||
pass
|
||||
|
||||
|
||||
class MenuDataValidationError(DomainError):
|
||||
pass
|
|
@ -0,0 +1,21 @@
|
|||
from typing import Protocol
|
||||
from uuid import UUID
|
||||
|
||||
from fastfood_two.domain.menu.menu_entity import Menu
|
||||
|
||||
|
||||
class MenuGateway(Protocol):
|
||||
async def get_menu_by_id(self, id: UUID) -> Menu | None:
|
||||
raise NotImplementedError
|
||||
|
||||
async def insert_menu(self, menu: Menu) -> Menu:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_all_menus(self) -> list[Menu]:
|
||||
raise NotImplementedError
|
||||
|
||||
async def update_menu(self, updated_menu: Menu) -> Menu | None:
|
||||
raise NotImplementedError
|
||||
|
||||
async def delete_menu(self, id: UUID) -> None:
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,34 @@
|
|||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
from fastfood_two.domain.core.entity import DomainEntity
|
||||
from fastfood_two.domain.core.value_obj import DomainValueObject
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MenuId(DomainValueObject):
|
||||
value: UUID
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Title(DomainValueObject):
|
||||
value: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Description(DomainValueObject):
|
||||
value: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Menu(DomainEntity[MenuId]):
|
||||
title: Title
|
||||
description: Description
|
||||
|
||||
@staticmethod
|
||||
def create(id: UUID, title: str, description: str | None) -> "Menu":
|
||||
return Menu(
|
||||
id=MenuId(id),
|
||||
title=Title(title),
|
||||
description=Description(description),
|
||||
)
|
|
@ -1,15 +1,27 @@
|
|||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from fastfood_two.application.contracts.responses import MenuResponse
|
||||
from fastfood_two.infrastructure.pg_storage.mappers.menu_mapper import entity_to_dto
|
||||
from fastfood_two.domain.menu.menu_entity import Menu
|
||||
from fastfood_two.infrastructure.pg_storage.mappers.menu_mapper import (
|
||||
db_entity_to_domain,
|
||||
)
|
||||
|
||||
|
||||
class MenuGatewayImpl:
|
||||
def __init__(self, session: AsyncSession) -> None:
|
||||
self._session = session
|
||||
|
||||
async def get_all_menus(self) -> list[MenuResponse]:
|
||||
async def get_all_menus(self) -> list[Menu]:
|
||||
query = text("SELECT * FROM menu;")
|
||||
menus = await self._session.execute(query)
|
||||
return [entity_to_dto(menu) for menu in menus.scalars().all()]
|
||||
return [db_entity_to_domain(menu) for menu in menus.scalars().all()]
|
||||
|
||||
async def get_menu_by_id(self, id: UUID) -> Menu | None: ...
|
||||
|
||||
async def insert_menu(self, menu: Menu) -> Menu: ...
|
||||
|
||||
async def update_menu(self, updated_menu: Menu) -> Menu | None: ...
|
||||
|
||||
async def delete_menu(self, id: UUID) -> None: ...
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastfood_two.application.contracts.responses import MenuResponse
|
||||
from fastfood_two.domain.menu.menu_entity import Menu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastfood_two.infrastructure.pg_storage.models import Menu
|
||||
from fastfood_two.infrastructure.pg_storage.models import SQLAMenu
|
||||
|
||||
|
||||
def entity_to_dto(menu: "Menu") -> MenuResponse:
|
||||
return MenuResponse(
|
||||
def db_entity_to_domain(menu: "SQLAMenu") -> Menu:
|
||||
return Menu(
|
||||
id=menu.id,
|
||||
title=menu.title,
|
||||
description=menu.description or "",
|
||||
description=menu.description,
|
||||
)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from .base import Base
|
||||
from .menu_model import Menu
|
||||
from .menu_model import SQLAMenu
|
||||
from .dish_model import Dish
|
||||
from .common_attrs import uuidpk, str_25
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"Menu",
|
||||
"SQLAMenu",
|
||||
"Dish",
|
||||
]
|
||||
|
|
|
@ -5,18 +5,18 @@ from .base import Base
|
|||
from .common_attrs import str_25, uuidpk
|
||||
|
||||
|
||||
class Menu(Base):
|
||||
__tablename__ = "menu"
|
||||
class SQLAMenu(Base):
|
||||
__tablename__ = "menus"
|
||||
|
||||
id: Mapped[uuidpk]
|
||||
title: Mapped[str_25]
|
||||
description: Mapped[str | None]
|
||||
|
||||
parent: Mapped[uuidpk | None] = mapped_column(ForeignKey("menu.id", ondelete="CASCADE"), nullable=True)
|
||||
parent: Mapped[uuidpk | None] = mapped_column(ForeignKey("menus.id", ondelete="CASCADE"), nullable=True)
|
||||
|
||||
submenus: Mapped[list["Menu"]] = relationship(
|
||||
"Menu",
|
||||
backref="menu",
|
||||
submenus: Mapped[list["SQLAMenu"]] = relationship(
|
||||
"SQLAMenu",
|
||||
backref="menus",
|
||||
lazy="selectin",
|
||||
cascade="all, delete",
|
||||
)
|
||||
|
|
|
@ -5,8 +5,8 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
|||
|
||||
from fastfood_two.application.common.logger import configure_logger
|
||||
from fastfood_two.application.config import Config
|
||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
||||
from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus
|
||||
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||
from fastfood_two.presentation.fastapi_backend.depends.config import get_settings
|
||||
from fastfood_two.presentation.fastapi_backend.depends.gateways import get_menu_gateway
|
||||
from fastfood_two.presentation.fastapi_backend.depends.session import (
|
||||
|
|
|
@ -3,7 +3,7 @@ from typing import Annotated
|
|||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
||||
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||
from fastfood_two.infrastructure.menu_gateway import MenuGatewayImpl
|
||||
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
||||
|
||||
|
|
|
@ -2,10 +2,15 @@ from typing import Annotated
|
|||
|
||||
from fastapi import Depends
|
||||
|
||||
from fastfood_two.application.contracts.gateways import MenuGateway
|
||||
from fastfood_two.application.usecases.menu.add_menu import AddMenu
|
||||
from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus
|
||||
from fastfood_two.domain.menu.gateway import MenuGateway
|
||||
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
||||
|
||||
|
||||
def get_all_menus_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> GetAllMenus:
|
||||
return GetAllMenus(gateway=gateway)
|
||||
|
||||
|
||||
def add_menu_usecase(gateway: Annotated[MenuGateway, Depends(Stub(MenuGateway))]) -> AddMenu:
|
||||
return AddMenu(gateway=gateway)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AddMenuPDModel(BaseModel):
|
||||
title: str
|
||||
description: str | None
|
|
@ -2,20 +2,36 @@ from typing import Annotated
|
|||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from fastfood_two.application.contracts.responses import MenuResponse
|
||||
from fastfood_two.application.contracts.requests import AddMenuDTO
|
||||
from fastfood_two.application.contracts.responses import MenuDTO
|
||||
from fastfood_two.application.usecases.menu.add_menu import AddMenu
|
||||
from fastfood_two.application.usecases.menu.get_all_menus import GetAllMenus
|
||||
from fastfood_two.presentation.fastapi_backend.depends.stub import Stub
|
||||
from fastfood_two.presentation.fastapi_backend.pdmodels.menu import AddMenuPDModel
|
||||
|
||||
router = APIRouter(prefix="/menu", tags=["Menu"])
|
||||
|
||||
|
||||
@router.get("/", response_model=list[MenuResponse])
|
||||
@router.get("/", response_model=list[MenuDTO])
|
||||
async def get_all_menus(
|
||||
usecase: Annotated[GetAllMenus, Depends(Stub(GetAllMenus))],
|
||||
) -> list[MenuResponse]:
|
||||
) -> list[MenuDTO]:
|
||||
"""Get all menus.
|
||||
|
||||
Endpoint returns list of all available food menus
|
||||
"""
|
||||
menus = await usecase()
|
||||
return menus
|
||||
|
||||
|
||||
@router.post("/", response_model=MenuDTO)
|
||||
async def add_menu(
|
||||
request: AddMenuPDModel,
|
||||
usecase: Annotated[AddMenu, Depends(Stub(AddMenu))],
|
||||
) -> MenuDTO:
|
||||
"""Get all menus.
|
||||
|
||||
Endpoint returns list of all available food menus
|
||||
"""
|
||||
menus = await usecase(request=AddMenuDTO(title=request.title, description=request.description))
|
||||
return menus
|
||||
|
|
Loading…
Reference in New Issue