Modules¶
Cart¶
- class app.cart.CartManager[source]¶
Bases:
objectManage shopping carts using the Singleton design pattern.
- clear_cart(cart_id: str = 'default') None[source]¶
Clear the contents of a cart.
- Parameters:
cart_id – The identifier of the cart to clear. Default value to “default”.
- get_cart(cart_id: str = 'default') Cart[source]¶
Return an existing cart or create a new one.
- Parameters:
cart_id – The unique cart identifier. Default value to “default”.
- Returns:
The cart matching the given identifier.
- Return type:
Config¶
Configuration module for discount and shipping strategies.
This module allows to configure which discount and shipping strategies to use for order calculations. To change the behavior, edit the file config.env
The application must be restarted for changes to take effect.
Discount stategy¶
- class app.discount_strategy.DiscountStrategy[source]¶
Bases:
ABCAbstract base class for discount calculation strategies.
- class app.discount_strategy.NoDiscount[source]¶
Bases:
DiscountStrategyNo discount applied.
- class app.discount_strategy.PercentageDiscount(percentage: float)[source]¶
Bases:
DiscountStrategyApply a percentage discount.
- class app.discount_strategy.ThresholdFixedDiscount(threshold: float, amount: float)[source]¶
Bases:
DiscountStrategyApply a fixed discount if a threshold is reached.
Inventory¶
- class app.inventory.InventoryManager[source]¶
Bases:
objectManage the product inventory using the Singleton design pattern.
- decrement_stock(pid: int, qty: int) None[source]¶
Decrease the stock of a product.
- Parameters:
pid – The unique product identifier.
qty – The quantity to decrement.
- Raises:
ValueError – If the product is not found.
ValueError – If the stock is insufficient.
- static get_instance()[source]¶
Return the unique InventoryManager instance, creating it if needed.
- Returns:
The singleton instance.
- Return type:
- get_product(pid: int) Product | None[source]¶
Returns a product by its identifier.
- Parameters:
pid – The unique product identifier.
- Returns:
The matching product, or None if not found.
- Return type:
Product | None
- get_reorder_threshold(pid: int) int | None[source]¶
Returns the reorder threshold of the product whose id is pid.
- Parameters:
pid – the unique product identifier
- Returns:
the matching reorder threshold, or None if not found
- Return type:
int | None
- has_stock(pid: int, qty: int) bool[source]¶
Check whether a product has enough stock.
- Parameters:
pid – The unique product identifier.
qty – The required quantity.
- Returns:
True if the product exists and has sufficient stock, False otherwise.
- Return type:
bool
- Raises:
ValueError – If qty is less than 1.
- list_products() list[Product][source]¶
Return all products in the inventory.
- Returns:
The list of all products.
- Return type:
list[Product]
- register(event: str, observer: object, method: Callable[[Dict[str, int]], None])[source]¶
Registers the observer observer for the event event.
- Events description:
decrement: triggered when a product’s stock is decreased. Message: {“pid”: int, “amount”: int (>0)}
threshold: triggered when a product’s stock pass under its reorder threshold. Message: {“pid”: int}
- Parameters:
event – the concerned event
observer – the observer
method – the method to call when event happens. It should take one positional argument (message)
- Raises:
ValueError – if event is not in InventoryManager._events.
- class app.inventory.InventoryObserver[source]¶
Bases:
objectObserves and log the Inventory updates. Implemented as a Singleton.
- static get_instance() InventoryObserver[source]¶
Return the unique InventoryObserver instance, creating it if needed.
- Returns:
The singleton instance.
- Return type:
Orders¶
- app.orders.create_order(cart_id: str = 'default') Order[source]¶
Create an order from a shopping cart.
Validates stock availability for every item in the cart, decrements the inventory, computes the total price, and clears the cart.
- Parameters:
cart_id – The identifier of the cart to order from. Defaults to “default”.
- Returns:
The newly created order.
- Return type:
- Raises:
ValueError – If any product in the cart has insufficient stock.
- app.orders.get_machine(order_id: int) StateMachine[source]¶
Retrieves the state machine for an order by its ID. Raises ValueError if the order is not found.
- Parameters:
order_id – The unique identifier of the state machine to retrieve.
- Returns:
The StateMachine object corresponding to the given order_id.
- Raises:
ValueError – If the state machine for the specified order ID is not found in the system.
- app.orders.get_order(order_id: int) Order[source]¶
Retrieves the order by its ID. Raises ValueError if the order is not found.
- Parameters:
order_id – The unique identifier of the order to retrieve.
- Returns:
The Order object corresponding to the given order_id.
- Raises:
ValueError – If the order with the specified ID is not found in the system.
- app.orders.get_order_breakdown(order_id: int) dict[str, float][source]¶
Calculate the pricing breakdown for an order.
- Parameters:
order_id – The ID of the order
- Returns:
Breakdown with subtotal, discount, shipping, and total
- Return type:
dict
- app.orders.set_status(order_id: int, new_status: str) Order[source]¶
Updates the status of an order and triggers the state machine transition. Raises ValueError if the order is not found or if the status transition is invalid.
- Parameters:
order_id – The unique identifier of the order to update.
new_status – The new status to set for the order.
- Returns:
The updated Order object with the new status.
- Raises:
ValueError – If the order with the specified ID is not found or if the status
transition is invalid according to the state machine rules. –
Pydantic models¶
- class app.pydantic_models.Cart(*, id: str, items: dict[int, int]=<factory>)[source]¶
Bases:
BaseModel- id: str¶
- items: dict[int, int]¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class app.pydantic_models.CartItem(*, product_id: int, quantity: Annotated[int, Ge(ge=1)])[source]¶
Bases:
BaseModel- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- product_id: int¶
- quantity: int¶
- class app.pydantic_models.Order(*, id: int, cart_id: str, items: list[CartItem], total: float = 0.0, status: str = 'created')[source]¶
Bases:
BaseModel- cart_id: str¶
- id: int¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- status: str¶
- total: float¶
- class app.pydantic_models.Product(*, id: int, name: str, price: Annotated[float, Ge(ge=0)], stock: Annotated[int, Ge(ge=0)], reorder_threshold: Annotated[int, Ge(ge=0)] = 5, weight: Annotated[float, Ge(ge=0)] = 1.0)[source]¶
Bases:
BaseModel- id: int¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- name: str¶
- price: float¶
- reorder_threshold: int¶
- stock: int¶
- weight: float¶
Shipping strategy¶
- class app.shipping_strategy.FlatRate(amount: float)[source]¶
Bases:
ShippingStrategyApply a flat rate shipping cost.
- class app.shipping_strategy.FreeOver(threshold: float, flat_rate: float)[source]¶
Bases:
ShippingStrategyFree shipping if subtotal exceeds threshold, otherwise flat rate.
- class app.shipping_strategy.ShippingStrategy[source]¶
Bases:
ABCAbstract base class for shipping cost calculation strategies.
- class app.shipping_strategy.WeightBased(rate_per_kg: float)[source]¶
Bases:
ShippingStrategyCalculate shipping based on total weight from product data.
- calculate_shipping(subtotal: float, items: list[tuple[int, int]]) float[source]¶
Calculate shipping cost based on total weight of items.
- Parameters:
subtotal – The subtotal before shipping (not used in weight-based calc)
items – List of (product_id, quantity) tuples
- Returns:
The shipping cost
- Return type:
float
- Raises:
ValueError – If any product_id is not found
State machine¶
- class app.state_machine.State[source]¶
Bases:
ABCAbstract base class for all states in the state machine. Each state must implement the update method to handle transitions to other states.
- abstractmethod update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.
- class app.state_machine.StateCancelled[source]¶
Bases:
StateRepresents the ‘cancelled’ state of an order. In this state, the order has been cancelled and cannot be processed further. No further transitions are allowed from this state.
- update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.
- class app.state_machine.StateCreated[source]¶
Bases:
StateRepresents the ‘created’ state of an order. In this state, the order has been created but not yet paid for. The valid transition from this state is to ‘paid’.
- update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.
- class app.state_machine.StateDelivered[source]¶
Bases:
StateRepresents the ‘delivered’ state of an order. In this state, the order has been delivered to the customer. No further transitions are allowed from this state.
- update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.
- class app.state_machine.StateMachine[source]¶
Bases:
objectImplements a simple state machine for order processing. The states are: - created - paid - packed - shipped - delivered - cancelled
The valid transitions are: - created -> paid - paid -> packed - packed -> shipped - shipped -> delivered - paid -> cancelled - packed -> cancelled
Once an order is delivered or cancelled, no further transitions are allowed.
- class app.state_machine.StatePacked[source]¶
Bases:
StateRepresents the ‘packed’ state of an order. In this state, the order has been packed but not yet shipped. The valid transitions from this state are to ‘shipped’ or ‘cancelled’.
- update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.
- class app.state_machine.StatePaid[source]¶
Bases:
StateRepresents the ‘paid’ state of an order. In this state, the order has been paid for but not yet packed. The valid transitions from this state are to ‘packed’ or ‘cancelled’.
- update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.
- class app.state_machine.StateShipped[source]¶
Bases:
StateRepresents the ‘shipped’ state of an order. In this state, the order has been shipped but not yet delivered. The valid transition from this state is to ‘delivered’.
- update(machine: StateMachine, status: str)[source]¶
Handles the transition to the next state based on the new status. :param machine: The state machine instance to update. :param status: The new status that triggers the state transition.