remove any cart stuff. add port interfaces for products

uebung_entities
michael 2025-02-02 02:42:54 +01:00
parent 5c84416fe0
commit d8ca863c8c
37 changed files with 144 additions and 537 deletions

View File

@ -1,5 +1,5 @@
# ENTITIES
Entities encapsulate enterprise-wide Critical Business Rules. An entity can be an
"Entities encapsulate enterprise-wide Critical Business Rules. An entity can be an
object with methods, or it can be a set of data structures and functions. It doesnt
matter so long as the entities can be used by many different applications in the
enterprise.
@ -8,4 +8,23 @@ entities are the business objects of the application. They encapsulate the most
and high-level rules. They are the least likely to change when something external
changes. For example, you would not expect these objects to be affected by a change
to page navigation or security. No operational change to any particular application
should affect the entity layer.
should affect the entity layer." -- Robert C. Martin, Clean Architecture
# Aufgabe
### Anforderungen:
#### Attribute:
- **name**: Der Name des Benutzers.
- **email**: Die E-Mail-Adresse des Benutzers.
- **password**: Das Passwort des Benutzers.
- **id**: Eine eindeutige ID, die den Benutzer identifiziert.
#### Getter und Setter Methoden:
- Implementiere für jedes Attribut (`name`, `email`, `password`, `id`) Getter- und Setter-Methoden. Diese Methoden sollten es ermöglichen, die Werte der Attribute abzurufen und zu ändern.
#### Datenvalidierung (optional):
- Validiere die Eingabedaten, z.B. sicherstellen, dass die E-Mail-Adresse ein gültiges Format hat und dass das Passwort eine Mindestlänge aufweist.
#### Konstruktor:
- Erstelle einen Konstruktor, der die Attribute `name`, `email`, `password` und `id` beim Erstellen eines Benutzerobjekts initialisiert.

View File

@ -1,69 +0,0 @@
#python imports
from typing import List
#dependency imports
from .cart_item import CartItem
from .product import Product
class Cart:
"""
Represents a shopping cart containing multiple cart items.
"""
def __init__(self):
"""
Initializes a new shopping cart.
"""
self.items: List[CartItem] = []
self.total_price: float = 0.0
def add_item(self, product: Product, quantity: int):
"""
Adds a product to the cart or updates the quantity if it already exists.
Args:
product (Product): The product to add to the cart.
quantity (int): The quantity of the product to add.
"""
for item in self.items:
if item.product_id == product.id:
item.quantity += quantity
return
self.items.append(CartItem(product_id=product.id, name=product.name, quantity=quantity, price=product.price))
def remove_item(self, product_id: str):
"""
Removes a product from the cart by its ID.
Args:
product_id (str): The unique identifier of the product to remove.
"""
self.items = [item for item in self.items if item.product_id != product_id]
def calculate_total_price(self) -> float:
"""
Calculates the total price of all items in the cart.
Returns:
float: The total price of all items in the cart.
"""
self.total_price = sum(item.calculate_total_price() for item in self.items)
return self.total_price
def list_items(self) -> List[str]:
"""
Lists all items in the cart with their quantities and total prices.
Returns:
List[str]: A list of strings representing the items in the cart.
"""
return [f"{item.quantity} x {item.name} - {item.calculate_total_price()} EUR" for item in self.items]
def __repr__(self):
"""
Returns a string representation of the cart.
Returns:
str: A string representation of the cart.
"""
return f"Cart(items={self.items}, total_price={self.total_price})"

View File

@ -1,50 +0,0 @@
#python imports
from typing import List
class CartItem:
"""
Represents an item in the cart with a product ID, name, quantity, and price.
"""
def __init__(self, product_id: int, name: str, quantity: int, price: float):
"""
Initializes a new cart item.
Args:
product_id (int): The unique identifier for the product.
name (str): The name of the product.
quantity (int): The quantity of the product in the cart.
price (float): The price of the product.
"""
self.product_id = product_id
self.name = name
self.quantity = quantity
self.price = price
self.post_init()
def post_init(self):
"""
Validates the cart item's quantity.
Raises:
ValueError: If the quantity is less than or equal to 0.
"""
if self.quantity <= 0:
raise ValueError("Quantity has to be at least 1")
def calculate_total_price(self) -> float:
"""
Calculates the total price of the cart item.
Returns:
float: The total price of the cart item.
"""
return self.quantity * self.price
def __repr__(self):
"""
Returns a string representation of the cart item.
Returns:
str: A string representation of the cart item.
"""
return f"CartItem(product_id={self.product_id}, name={self.name}, quantity={self.quantity}, price={self.price})"

View File

@ -33,7 +33,6 @@
<body>
<header>
<a id="products-tab" href="#">Products</a>
<a id="cart-tab" href="#">Cart</a>
<a id="user-tab" href="#">User</a>
</header>
<main id="main"></main>

View File

@ -7,23 +7,20 @@ const Controller = {
},
async showProducts() {
const products = await Model.fetchProducts();
View.renderProducts(products);
const productsResponse = await Model.fetchProducts();
if(productsResponse.status=="success"){
const products = productsResponse.data;
View.renderProducts(products);
}
Controller.addProductClickHandlers();
},
async showCart() {
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) {
const product = await Model.fetchProduct(productId);
View.renderProduct(product);
const productResponse = await Model.fetchProduct(productId);
if(productResponse.status=="success"){
const product = productResponse.data;
View.renderProduct(product);
}
document.getElementById('back-button').addEventListener('click', () => {
Controller.showProducts();
});
@ -45,11 +42,6 @@ const Controller = {
Controller.showProducts();
});
document.getElementById('cart-tab').addEventListener('click', (event) => {
event.preventDefault();
Controller.showCart();
});
document.getElementById('user-tab').addEventListener('click', (event) => {
event.preventDefault();
Controller.showUserRegister();

View File

@ -5,17 +5,6 @@ const Model = {
return response.json();
},
async fetchCart(userId) {
const response = await fetch('http://127.0.0.1:8000/view_cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ user_id: userId }),
});
return response.json();
},
async fetchProduct(productId) {
const response = await fetch(`http://127.0.0.1:8000/view_product/`, {
method: 'POST',

View File

@ -1,7 +1,6 @@
// View: Handles rendering
const View = {
renderProducts(products) {
var products = products.products;
const main = document.getElementById('main');
main.innerHTML = `
<h1>Products</h1>
@ -21,25 +20,7 @@ const View = {
`;
},
renderCart(cart) {
var cartItems = cart.items;
const main = document.getElementById('main');
main.innerHTML = `
<h1>Your Cart</h1>
<ul>
${cartItems
.map(
(item) => `
<li>${item.name} - $${item.price} (Quantity: ${item.quantity})</li>`
)
.join('')}
</ul>
<p>Total: $${cart.total_price}</p>
`;
},
renderProduct(product) {
var product = product.product;
const main = document.getElementById('main');
main.innerHTML = `
<h1>${product.name}</h1>

View File

@ -1,9 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,77 +0,0 @@
#python imports
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.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
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):
cart = view_cart_use_case.execute(request_dto.user_id)
if not cart:
raise HTTPException(status_code=404, detail="Cart not found")
response_dto = ViewCartResponseDTO(
items=[CartItemDTO(product_id=item.product_id, name=item.name, quantity=item.quantity, price=item.price) for item in cart.items],
total_price=sum(item.price * item.quantity for item in cart.items)
)
return response_dto
"""

View File

@ -1,46 +1,76 @@
#python imports
from typing import List
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 entities import Product
from use_cases.interactors import ViewProduct, ViewAllProducts
from interface_adapters.dtos import ViewProductRequestDTO, ViewProductResponseDTO, ViewAllProductsResponseDTO, ProductDTO
from use_cases.interfaces.ports import ViewProductInputPort, ViewAllProductsInputPort, ViewProductOutputPort, ViewAllProductsOutputPort
from interface_adapters.dtos import ProductDTO
from interface_adapters.repositories import SQLProductRepository
# Initialize components
class ViewProductRequest(BaseModel):
product_id: int
class ViewProductResponse(BaseModel):
status: str
data: ProductDTO
class ViewProductPresenter(ViewProductOutputPort):
def present(self, product:Product) -> ViewProductResponse:
"""Format the response data for API."""
product_dto=ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price)
response = ViewProductResponse(status="success",data=product_dto)
return response
class ViewProductController:
def __init__(self, input_port: ViewProductInputPort) -> None:
self.input_port = input_port
def handle_request(self, request:ViewProductRequest):
"""Handles the incoming API request and delegates to the use case."""
return self.input_port.execute(request.product_id)
product_repository = SQLProductRepository()
view_product_use_case = ViewProduct(product_repository)
view_all_products_use_case = ViewAllProducts(product_repository)
view_product_presenter = ViewProductPresenter()
view_product_use_case = ViewProduct(product_repository, view_product_presenter)
view_product_controller = ViewProductController(view_product_use_case)
class ViewAllProductsResponse(BaseModel):
status: str
data: List[ProductDTO]
class ViewAllProductsPresenter(ViewAllProductsOutputPort):
def present(self, products:List[Product]) -> ViewAllProductsResponse:
"""Format the response data for API."""
products_dto=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products]
response = ViewAllProductsResponse(status="success",data=products_dto)
return response
class ViewAllProductsController:
def __init__(self, input_port: ViewAllProductsInputPort) -> None:
self.input_port = input_port
def handle_request(self):
"""Handles the incoming API request and delegates to the use case."""
return self.input_port.execute()
view_all_products_presenter = ViewAllProductsPresenter()
view_all_products_use_case = ViewAllProducts(product_repository, view_all_products_presenter)
view_all_products_controller = ViewAllProductsController(view_all_products_use_case)
router = APIRouter()
@router.post("/view_product", response_model=ViewProductResponseDTO)
async def view_product(request_dto: ViewProductRequestDTO):
@router.post("/view_product", response_model=ViewProductResponse)
async def view_product(request: ViewProductRequest):
return view_product_controller.handle_request(request)
product = view_product_use_case.execute(request_dto.product_id)
if not product:
raise HTTPException(status_code=404, detail="Product not found")
response_dto = ViewProductResponseDTO(
product=ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price)
)
return response_dto
@router.get("/view_products", response_model=ViewAllProductsResponseDTO)
@router.get("/view_products", response_model=ViewAllProductsResponse)
async def view_products():
products = view_all_products_use_case.execute()
if not products:
raise HTTPException(status_code=404, detail="Product not found")
for product in products:
print(product)
response_dto = ViewAllProductsResponseDTO(
products=[ProductDTO(id=product.id, name=product.name, description=product.description, price=product.price) for product in products]
)
return response_dto
return view_all_products_controller.handle_request()

View File

@ -1,13 +0,0 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
class CartItemDTO(BaseModel):
product_id: int
name: str
quantity: int
price: float
class CartDTO(BaseModel):
items: List[CartItemDTO]
total_price: float

View File

@ -1,8 +0,0 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
from .product_dto import ProductDTO
class ViewAllProductsResponseDTO(BaseModel):
products: List[ProductDTO] = []

View File

@ -1,6 +0,0 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
class ViewCartRequestDTO(BaseModel):
user_id: int

View File

@ -1,10 +0,0 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
from .cart_dto import CartItemDTO
class ViewCartResponseDTO(BaseModel):
items: List[CartItemDTO] = []
total_price: float = 0.0

View File

@ -1,6 +0,0 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
class ViewProductRequestDTO(BaseModel):
product_id: int

View File

@ -1,8 +0,0 @@
#python imports
from pydantic import BaseModel
from typing import List, Optional
from .product_dto import ProductDTO
class ViewProductResponseDTO(BaseModel):
product: ProductDTO

View File

@ -1,88 +0,0 @@
#python imports
from typing import Optional, List
import sqlite3
#dependency imports
from use_cases.interfaces import ICartRepository
from entities import Cart, CartItem
class SQLCartRepository(ICartRepository):
def __init__(self, db_file="framework_driver/db/shop.db"):
"""Initialize the CartDatabase with a connection to the SQLite database."""
self.conn = sqlite3.connect(db_file)
self.conn.row_factory = sqlite3.Row
def add_to_cart(self, user_id, product_id, name, quantity, price):
"""Add a product to the user's cart."""
sql = "INSERT INTO cart (user_id, product_id, name, quantity, price) VALUES (?, ?, ?, ?, ?)"
try:
c = self.conn.cursor()
c.execute(sql, (user_id, product_id, name, quantity, price))
self.conn.commit()
return c.lastrowid
except sqlite3.Error as e:
print(e)
def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
"""Fetch cart items from the database for a given user ID."""
sql = "SELECT product_id, name, quantity, price FROM cart WHERE user_id = ?"
try:
c = self.conn.cursor()
c.execute(sql, (user_id,))
rows = c.fetchall()
return Cart([CartItem(**item) for item in rows])
except sqlite3.Error as e:
print(e)
return None
def update_cart_item(self, user_id, product_id, quantity):
"""Update the quantity of a specific cart item based on user_id and product_id."""
sql = "UPDATE cart SET quantity = ? WHERE user_id = ? AND product_id = ?"
try:
c = self.conn.cursor()
c.execute(sql, (quantity, user_id, product_id))
self.conn.commit()
except sqlite3.Error as e:
print(e)
def remove_from_cart(self, cart_id):
"""Remove a specific item from the cart."""
sql = "DELETE FROM cart WHERE id = ?"
try:
c = self.conn.cursor()
c.execute(sql, (cart_id,))
self.conn.commit()
except sqlite3.Error as e:
print(e)
def clear_cart(self, user_id):
"""Clear all items from a user's cart."""
sql = "DELETE FROM cart WHERE user_id = ?"
try:
c = self.conn.cursor()
c.execute(sql, (user_id,))
self.conn.commit()
except sqlite3.Error as e:
print(e)
def main():
database = "framework_driver/db/shop.db"
cart_db = SQLCartRepository(database)
ex_user_id = 1
# delete all cart items
cart_db.clear_cart(ex_user_id)
# user id, product id, name, quantity, price
cart_db.add_to_cart(ex_user_id, 1, "Unsichtbare Tinte", 2, 3.0)
cart_db.add_to_cart(ex_user_id, 3, "Geräuschloser Wecker", 5, 15.0)
print(f"Cart items for user {ex_user_id}:", cart_db.get_cart_by_user_id(ex_user_id))
# user id, product id, quantity
cart_db.update_cart_item(ex_user_id, 2, 3)
print(f"Cart items for user {ex_user_id}:", cart_db.get_cart_by_user_id(ex_user_id))
if __name__ == "__main__":
main()

View File

@ -3,7 +3,7 @@ from typing import Optional, List
import sqlite3
#dependency imports
from use_cases.interfaces import IProductRepository
from use_cases.interfaces.repositories 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.interfaces import IUserRepository
from use_cases.interfaces.repositories import IUserRepository
from entities import User
class SQLUserRepository(IUserRepository):

View File

@ -1,4 +1,3 @@
from interface_adapters.controllers.cart_controller import router as cart_router
from interface_adapters.controllers.product_controller import router as product_router
from interface_adapters.controllers.user_controller import router as user_router
from fastapi.middleware.cors import CORSMiddleware
@ -14,8 +13,8 @@ import os
app = FastAPI()
app.include_router(cart_router)
app.include_router(product_router)
app.include_router(user_router)
# Allow CORS for all origins (for simplicity)
app.add_middleware(

View File

@ -1,5 +1,5 @@
# USE CASES
The software in the use cases layer contains application-specific business rules. It
"The software in the use cases layer contains application-specific business rules. It
encapsulates and implements all of the use cases of the system. These use cases
orchestrate the flow of data to and from the entities, and direct those entities to use
their Critical Business Rules to achieve the goals of the use case.
@ -8,4 +8,6 @@ this layer to be affected by changes to externalities such as the database, the
any of the common frameworks. The use cases layer is isolated from such concerns.
We do, however, expect that changes to the operation of the application will affect
the use cases and, therefore, the software in this layer. If the details of a use case
change, then some code in this layer will certainly be affected.
change, then some code in this layer will certainly be affected." -- Robert C. Martin, Clean Architecture
# Aufgabe

View File

@ -3,11 +3,13 @@ from typing import Optional, List
#dependency imports
from entities import Product
from use_cases.interfaces.product_repository import IProductRepository
from use_cases.interfaces.repositories import IProductRepository
from use_cases.interfaces.ports import ViewAllProductsInputPort, ViewAllProductsOutputPort
class ViewAllProducts:
def __init__(self, product_repository: IProductRepository):
class ViewAllProducts(ViewAllProductsInputPort):
def __init__(self, product_repository: IProductRepository, output_port: ViewAllProductsOutputPort):
self.product_repository = product_repository
self.output_port = output_port
def execute(self) -> Optional[List[Product]]:
"""
@ -16,4 +18,4 @@ class ViewAllProducts:
product_data = self.product_repository.get_all_products()
if not product_data:
return None
return product_data
return self.output_port.present(product_data)

View File

@ -1,34 +0,0 @@
#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,11 +3,13 @@ from typing import Optional
#dependency imports
from entities import Product
from use_cases.interfaces.product_repository import IProductRepository
from use_cases.interfaces.repositories import IProductRepository
from use_cases.interfaces.ports import ViewProductInputPort, ViewProductOutputPort
class ViewProduct:
def __init__(self, product_repository: IProductRepository):
class ViewProduct(ViewProductInputPort):
def __init__(self, product_repository: IProductRepository, output_port: ViewProductOutputPort):
self.product_repository = product_repository
self.output_port = output_port
def execute(self, product_id: int) -> Optional[Product]:
"""
@ -17,4 +19,4 @@ class ViewProduct:
if not product_data:
return None
return product_data
return self.output_port.present(product_data)

View File

@ -1,14 +0,0 @@
#python imports
from typing import Optional
#dependency imports
#TODO: use correct dependencies
class ViewUser:
def __init__(self):
pass
#TODO: initialize required Interfaces
def execute(self):
pass
#TODO: implement

View File

@ -1,6 +0,0 @@
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

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

View File

@ -1,12 +0,0 @@
from typing import Optional, List
#dependency imports
from entities import Cart
from .base_repository import BaseRepository
class ICartRepository(BaseRepository):
def get_cart_by_user_id(self, user_id: int) -> Optional[Cart]:
"""
Abstract method to fetch cart data by user ID.
"""
raise NotImplementedError("get_cart_by_user_id must be implemented by a subclass")

View File

@ -1,5 +1,5 @@
class BaseInputPort:
def execute(self, input_data):
class ViewAllProductsInputPort:
def execute(self):
"""
Abstract method to execute use case.
"""

View File

@ -0,0 +1,8 @@
from entities import Product
from typing import List
class ViewAllProductsOutputPort:
def present(self, product_list:List[Product]):
"""
Abstract method to execute use case.
"""
raise NotImplementedError("execute must be implemented by a subclass")

View File

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

View File

@ -0,0 +1,7 @@
from entities import Product
class ViewProductOutputPort:
def present(self, product:Product):
"""
Abstract method to execute use case.
"""
raise NotImplementedError("execute must be implemented by a subclass")

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)