add input output port to view cart as example. organize usecases in interactors and interfaces
parent
821ef8a8d4
commit
0be0187062
|
|
@ -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]
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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))
|
||||||
|
|
@ -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
|
||||||
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
class BaseRepository:
|
||||||
|
pass
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
|
||||||
Loading…
Reference in New Issue