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
from typing import List
from dataclasses import dataclass
from dataclasses import dataclass, field
#dependency imports
from .cart_item import CartItem
@ -9,6 +9,7 @@ from .product import Product
@dataclass
class Cart:
items: List[CartItem]
total_price: float = field(init=False, default=0.0)
def add_item(self, product: Product, quantity: int):
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]
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]:
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() {
const userId = 1; // Hardcoded for simplicity
const cart = await Model.fetchCart(userId);
View.renderCart(cart);
const userId = "1"; // Hardcoded for simplicity
const cartResponse = await Model.fetchCart(userId);
if(cartResponse.status=="success"){
const cart = cartResponse.data;
View.renderCart(cart);
}
},
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 import FastAPI, Request
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
#dependency imports
from use_cases import ViewCart
from interface_adapters.dtos import ViewCartRequestDTO, ViewCartResponseDTO, CartItemDTO
from use_cases.interactors import ViewCart
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
# Initialize components
cart_repository = SQLCartRepository()
view_cart_use_case = ViewCart(cart_repository)
class ViewCartRequest(BaseModel):
user_id: str
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()
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)
async def view_cart(request_dto: ViewCartRequestDTO):
@ -29,4 +74,4 @@ async def view_cart(request_dto: ViewCartRequestDTO):
)
return response_dto
"""

View File

@ -5,7 +5,7 @@ from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
#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.repositories import SQLProductRepository

View File

@ -6,4 +6,8 @@ class CartItemDTO(BaseModel):
product_id: int
name: str
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 typing import List, Optional
from .cart_item_dto import CartItemDTO
from .cart_dto import CartItemDTO
class ViewCartResponseDTO(BaseModel):

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ from typing import Optional, List
#dependency imports
from entities import Product
from .product_repository import IProductRepository
from use_cases.interfaces.product_repository import IProductRepository
class ViewAllProducts:
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
from entities import Product
from .product_repository import IProductRepository
from use_cases.interfaces.product_repository import IProductRepository
class ViewProduct:
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
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]:
"""
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