add input output port to view cart as example. organize usecases in interactors and interfaces

uebung_entities
michael 2025-01-31 11:41:18 +01:00
parent 821ef8a8d4
commit 0be0187062
26 changed files with 172 additions and 41 deletions

View File

@ -1,6 +1,6 @@
#python imports #python imports
from typing import List from typing import List
from dataclasses import dataclass from dataclasses import dataclass, field
#dependency imports #dependency imports
from .cart_item import CartItem from .cart_item import CartItem
@ -9,6 +9,7 @@ from .product import Product
@dataclass @dataclass
class Cart: class Cart:
items: List[CartItem] items: List[CartItem]
total_price: float = field(init=False, default=0.0)
def add_item(self, product: Product, quantity: int): def add_item(self, product: Product, quantity: int):
for item in self.items: for item in self.items:
@ -21,7 +22,7 @@ class Cart:
self.items = [item for item in self.items if item.product.id != product_id] self.items = [item for item in self.items if item.product.id != product_id]
def calculate_total_price(self) -> float: def calculate_total_price(self) -> float:
return sum(item.calculate_total_price() for item in self.items) self.total_price = sum(item.calculate_total_price() for item in self.items)
def list_items(self) -> List[str]: def list_items(self) -> List[str]:
return [f"{item.quantity} x {item.product.name} - {item.calculate_total_price()} EUR" for item in self.items] return [f"{item.quantity} x {item.product.name} - {item.calculate_total_price()} EUR" for item in self.items]

View File

@ -7,9 +7,12 @@ const Controller = {
}, },
async showCart() { async showCart() {
const userId = 1; // Hardcoded for simplicity const userId = "1"; // Hardcoded for simplicity
const cart = await Model.fetchCart(userId); const cartResponse = await Model.fetchCart(userId);
View.renderCart(cart); if(cartResponse.status=="success"){
const cart = cartResponse.data;
View.renderCart(cart);
}
}, },
async showProduct(productId) { async showProduct(productId) {

View File

@ -0,0 +1,9 @@
from use_cases.interfaces import BaseInputPort, BaseOutputPort
class BaseController:
def __init__(self, input_port: BaseInputPort) -> None:
self.input_port = input_port
def handle_request(self, request) -> dict:
"""Handles the incoming API request and delegates to the use case."""
return self.input_port.execute(request)

View File

@ -0,0 +1,28 @@
from typing import Type
from fastapi import APIRouter, HTTPException
from .base_controller import BaseController
from use_cases.interfaces import BaseOutputPort, BaseInputPort, BaseRepository
class Endpoint:
def __init__(self,
router: APIRouter,
controller_impl: Type[BaseController],
presenter_impl: Type[BaseOutputPort],
repository_impl: Type[BaseRepository],
use_case_impl: Type[BaseInputPort]
):
self.router = router
self.presenter = presenter_impl()
self.repository = repository_impl()
self.use_case = use_case_impl(repository=self.repository, output_port=self.presenter)
self.controller = controller_impl(input_port=self.use_case)
def create(self, route: str, request_model: Type, response_model: Type):
@self.router.post(route, response_model=response_model)
async def endpoint(request: request_model):
try:
response = self.controller.handle_request(request)
return response
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))

View File

@ -3,18 +3,63 @@ from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from pydantic import BaseModel
#dependency imports #dependency imports
from use_cases import ViewCart from use_cases.interactors import ViewCart
from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO from use_cases.interfaces import BaseInputPort, BaseOutputPort
from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO, CartDTO
from .base_controller import BaseController
#move to main
from .base_endpoint import Endpoint
from interface_adapters.repositories import SQLCartRepository from interface_adapters.repositories import SQLCartRepository
# Initialize components class ViewCartRequest(BaseModel):
cart_repository = SQLCartRepository() user_id: str
view_cart_use_case = ViewCart(cart_repository)
class ViewCartResponse(BaseModel):
status: str
data: CartDTO
class ViewCartController(BaseController):
def __init__(self, input_port: BaseInputPort) -> None:
self.input_port = input_port
def handle_request(self, request) -> dict:
"""Handles the incoming API request and delegates to the use case."""
return self.input_port.execute(request)
class ViewCartPresenter(BaseOutputPort):
def present(self, output) -> dict:
"""Format the response data for API."""
cart_items=[CartItemDTO(product_id=item.product_id, name=item.name, quantity=item.quantity, price=item.price) for item in output.items]
cart_data = CartDTO(items=cart_items,total_price=output.total_price)
response = ViewCartResponse(status="success",data=cart_data)
return response
router = APIRouter() router = APIRouter()
endpoint = Endpoint(router, ViewCartController, ViewCartPresenter, SQLCartRepository, ViewCart)
endpoint.create("/view_cart", ViewCartRequest, ViewCartResponse)
"""
# Initialize components
presenter = ViewCartPresenter()
repository = SQLCartRepository()
use_case = ViewCart(repository=repository, output_port=presenter)
controller = ViewCartController(input_port=use_case)
@router.post("/view_cart/")
async def view_cart(request: ViewCartRequest):
try:
result = controller.handle_request(request.user_id)
return result # Returns a dictionary which FastAPI will convert to JSON
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/view_cart", response_model=ViewCartResponseDTO) @router.post("/view_cart", response_model=ViewCartResponseDTO)
async def view_cart(request_dto: ViewCartRequestDTO): async def view_cart(request_dto: ViewCartRequestDTO):
@ -29,4 +74,4 @@ async def view_cart(request_dto: ViewCartRequestDTO):
) )
return response_dto return response_dto
"""

View File

@ -5,7 +5,7 @@ from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
#dependency imports #dependency imports
from use_cases import ViewProduct, ViewAllProducts from use_cases.interactors import ViewProduct, ViewAllProducts
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO
from interface_adapters.repositories import SQLProductRepository from interface_adapters.repositories import SQLProductRepository

View File

@ -6,4 +6,8 @@ class CartItemDTO(BaseModel):
product_id: int product_id: int
name: str name: str
quantity: int quantity: int
price: float price: float
class CartDTO(BaseModel):
items: List[CartItemDTO]
total_price: float

View File

@ -2,7 +2,7 @@
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
from .cart_item_dto import CartItemDTO from .cart_dto import CartItemDTO
class ViewCartResponseDTO(BaseModel): class ViewCartResponseDTO(BaseModel):

View File

@ -3,7 +3,7 @@ from typing import Optional, List
import sqlite3 import sqlite3
#dependency imports #dependency imports
from use_cases import ICartRepository from use_cases.interfaces import ICartRepository
from entities import Cart, CartItem from entities import Cart, CartItem
class SQLCartRepository(ICartRepository): class SQLCartRepository(ICartRepository):

View File

@ -3,7 +3,7 @@ from typing import Optional, List
import sqlite3 import sqlite3
#dependency imports #dependency imports
from use_cases import IProductRepository from use_cases.interfaces import IProductRepository
from entities import Product from entities import Product
class SQLProductRepository(IProductRepository): class SQLProductRepository(IProductRepository):

View File

@ -3,7 +3,7 @@ from typing import Optional, List
import sqlite3 import sqlite3
#dependency imports #dependency imports
from use_cases import IUserRepository from use_cases.interfaces import IUserRepository
from entities import User from entities import User
class SQLUserRepository(IUserRepository): class SQLUserRepository(IUserRepository):

View File

@ -32,3 +32,4 @@ async def root():
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000) uvicorn.run(app, host="127.0.0.1", port=8000)

View File

@ -3,7 +3,7 @@ from typing import Optional, List
#dependency imports #dependency imports
from entities import Product from entities import Product
from .product_repository import IProductRepository from use_cases.interfaces.product_repository import IProductRepository
class ViewAllProducts: class ViewAllProducts:
def __init__(self, product_repository: IProductRepository): def __init__(self, product_repository: IProductRepository):

View File

@ -0,0 +1,34 @@
#python imports
from typing import Optional
#dependency imports
from entities import Cart, CartItem
from use_cases.interfaces.cart_repository import ICartRepository
from use_cases.interfaces.base_output_port import BaseOutputPort
from use_cases.interfaces.base_input_port import BaseInputPort
"""
class ViewCartInputPort():
def execute(self, name: str, email: str) -> None:
pass
class ViewCartOutputPort():
def present(self, user: dict) -> dict:
pass
"""
class ViewCart(BaseInputPort):
def __init__(self, repository: ICartRepository, output_port: BaseOutputPort):
self.repository = repository
self.output_port = output_port
def execute(self, input_data) -> Optional[Cart]:
"""
Fetches the cart data from the repository.
"""
if not hasattr(input_data, 'user_id'):
raise ValueError("Missing 'user_id' in input_data")
cart = self.repository.get_cart_by_user_id(input_data.user_id)
if not cart:
return None
cart.calculate_total_price()
return self.output_port.present(cart)

View File

@ -3,7 +3,7 @@ from typing import Optional
#dependency imports #dependency imports
from entities import Product from entities import Product
from .product_repository import IProductRepository from use_cases.interfaces.product_repository import IProductRepository
class ViewProduct: class ViewProduct:
def __init__(self, product_repository: IProductRepository): def __init__(self, product_repository: IProductRepository):

View File

@ -0,0 +1,11 @@
import importlib
import pkgutil
import inspect
__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
module = importlib.import_module(f".{module_name}", __name__)
for name, obj in inspect.getmembers(module, inspect.isclass):
if obj.__module__ == module.__name__:
globals()[name] = obj
__all__.append(name)

View File

@ -0,0 +1,6 @@
class BaseInputPort:
def execute(self, input_data):
"""
Abstract method to execute use case.
"""
raise NotImplementedError("execute must be implemented by a subclass")

View File

@ -0,0 +1,6 @@
class BaseOutputPort:
def present(self, output_data):
"""
Abstract method to present use case output.
"""
raise NotImplementedError("present must be implemented by a subclass")

View File

@ -0,0 +1,2 @@
class BaseRepository:
pass

View File

@ -2,8 +2,9 @@ from typing import Optional, List
#dependency imports #dependency imports
from entities import Cart from entities import Cart
from .base_repository import BaseRepository
class ICartRepository: class ICartRepository(BaseRepository):
def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]: def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
""" """
Abstract method to fetch cart data by user ID. Abstract method to fetch cart data by user ID.

View File

@ -1,20 +0,0 @@
#python imports
from typing import Optional
#dependency imports
from entities import Cart, CartItem
from .cart_repository import ICartRepository
class ViewCart:
def __init__(self, cart_repository: ICartRepository):
self.cart_repository = cart_repository
def execute(self, user_id: int) -> Optional[Cart]:
"""
Fetches the cart data from the repository.
"""
cart_data = self.cart_repository.get_cart_by_user_id(user_id)
if not cart_data:
return None
return cart_data