1. Introduction to Portfolio Capstone Project 2
Welcome to Lesson 38! In this capstone project lesson, we will synthesize the entire Object-Oriented Programming (OOP) Module (Lessons 32–37) alongside our foundational knowledge of persistent file I/O, error handling, logging, context managers, and functional tools.
Building enterprise-grade applications requires unifying multiple abstract design patterns into a cohesive, production-ready system architecture. In this project, we will design and engineer a complete Object-Oriented Enterprise Inventory Management Engine.
This portfolio project will showcase your mastery of modern Python software design—including Data Classes (`@dataclass`), Abstract Base Classes (`abc.ABC`), Composition ("Has-A" relationships), Encapsulated Managed Properties (`@property`), Custom Context Managers, File Persistence (JSON & CSV), and Structural Pattern Matching (`match-case`).
2. System Architecture & Requirements Specification
1. Architectural Layer Design Diagram
+-----------------------------------------------------------------------+
| CLI Command Dispatcher |
| (Tokenizes User Input & Routes via Match-Case Patterns) |
+-----------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------+
| Inventory Manager Composite Service |
| (Composes Storage Repository, Audit Logger, & Vault Lock) |
+-----------------------------------------------------------------------+
|
+-----------------------+-----------------------+
| |
v v
+-----------------------------------+ +-----------------------------------+
| Domain Model Layer | | Persistence Storage Layer |
| - BaseInventoryItem (ABC) | | - BaseStorageRepository (ABC) |
| - ElectronicsItem (@dataclass) | | - JSONStorageRepository |
| - PerishableItem (@dataclass) | | - CSVExporterUtility |
+-----------------------------------+ +-----------------------------------+
2. Technical Features Requirements Checklist
- Abstract Domain Hierarchy: An Abstract Base Class
BaseInventoryItem(ABC)defining abstract methods (calculate_discounted_value()) and managed properties for input validation. - Specialized Data Class Models: Concrete implementations using
@dataclass(e.g.,ElectronicsItemwith warranty metadata, andPerishableItemwith expiration date handling). - Composition Strategy: An
InventoryManagerservice class that composes a storage repository component ("Has-A" relationship) and delegates persistent load/save operations cleanly. - Context-Managed Persistence: Custom class-based context manager
FileVaultLockto guarantee file locks during JSON reading/writing operations. - Command Dispatcher Engine: A
match-casecontrol dispatcher that parses command-line arguments, validates inputs using static methods, and executes operations. - Observability & Fault Tolerance: Structured logging via Python's
loggingmodule alongside custom exceptions (InventoryError,StockDepletedError,ItemNotFoundError).
3. Domain Models & Abstract Interfaces Implementation
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
class BaseInventoryItem(ABC):
"""Abstract Base Class establishing common interface contracts for all inventory items."""
def __init__(self, item_id: str, title: str, price: float, quantity: int):
self.item_id = item_id
self.title = title
self.price = price # Invokes @price.setter
self.quantity = quantity # Invokes @quantity.setter
@property
def price(self) -> float:
return self._price
@price.setter
def price(self, value: float):
if value < 0.0:
raise ValueError("Price cannot be negative!")
self._price = float(value)
@property
def quantity(self) -> int:
return self._quantity
@quantity.setter
def quantity(self, value: int):
if value < 0:
raise ValueError("Quantity cannot be negative!")
self._quantity = int(value)
@abstractmethod
def calculate_discounted_value(self) -> float:
"""Abstract method enforcing custom discount calculation in subclasses."""
pass
def __repr__(self):
return f"{self.__class__.__name__}(id='{self.item_id}', title='{self.title}', price={self.price}, qty={self.quantity})"
@dataclass
class ElectronicsItem(BaseInventoryItem):
"""Concrete subclass representing electronic items with warranty metadata."""
item_id: str
title: str
price: float
quantity: int
warranty_months: int = 12
def __post_init__(self):
# Initialize BaseInventoryItem properties after dataclass field binding
super().__init__(self.item_id, self.title, self.price, self.quantity)
def calculate_discounted_value(self) -> float:
# Electronic items get 10% discount if quantity >= 10
total = self.price * self.quantity
return round(total * 0.90 if self.quantity >= 10 else total, 2)
@dataclass
class PerishableItem(BaseInventoryItem):
"""Concrete subclass representing perishable grocery items with expiration date."""
item_id: str
title: str
price: float
quantity: int
days_to_expire: int = 7
def __post_init__(self):
super().__init__(self.item_id, self.title, self.price, self.quantity)
def calculate_discounted_value(self) -> float:
# Perishable items get 30% markdown if expiring in <= 3 days!
total = self.price * self.quantity
return round(total * 0.70 if self.days_to_expire <= 3 else total, 2)
# Testing Domain Model Instantiation and Polymorphism
e_item = ElectronicsItem("E101", "Gaming Monitor", 300.0, 12, warranty_months=24)
p_item = PerishableItem("P201", "Fresh Organic Milk", 4.50, 20, days_to_expire=2)
print("=== Domain Model Polymorphic Discount Valuation ===")
print(f"Item: {e_item.title:<20} | Discounted Value: ${e_item.calculate_discounted_value():,.2f}")
print(f"Item: {p_item.title:<20} | Discounted Value: ${p_item.calculate_discounted_value():,.2f}")
=== Domain Model Polymorphic Discount Valuation === Item: Gaming Monitor | Discounted Value: $3,240.00 Item: Fresh Organic Milk | Discounted Value: $63.00
4. Persistence Storage Repository & Context Manager
import json
import csv
from abc import ABC, abstractmethod
from pathlib import Path
class BaseStorageRepository(ABC):
"""Abstract Base Class interface for storage persistence drivers."""
@abstractmethod
def save_data(self, file_path: Path, data: dict) -> bool:
pass
@abstractmethod
def load_data(self, file_path: Path) -> dict:
pass
class FileVaultLock:
"""Class-Based Context Manager ensuring safe resource handling during file operations."""
def __init__(self, file_path: Path):
self.file_path = file_path
def __enter__(self):
print(f"[VAULT LOCK] Securing access lock for: '{self.file_path.name}'")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"[VAULT UNLOCK] Releasing file lock for: '{self.file_path.name}'")
if exc_type:
print(f"[VAULT ALERT] Transaction aborted due to exception: {exc_val}")
return False # Propagate exception if present
class JSONStorageRepository(BaseStorageRepository):
"""Concrete JSON storage driver implementing BaseStorageRepository interface."""
def save_data(self, file_path: Path, data: dict) -> bool:
with FileVaultLock(file_path):
with open(file_path, mode="w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
return True
def load_data(self, file_path: Path) -> dict:
if not file_path.exists():
return {}
with FileVaultLock(file_path):
with open(file_path, mode="r", encoding="utf-8") as f:
return json.load(f)
print("Persistence Repository Driver Module Loaded Successfully.")
Persistence Repository Driver Module Loaded Successfully.
5. Master Capstone Implementation (`master_inventory_system.py`)
Below is the complete, modular single-file application integrating domain models, abstract repositories, composition services, logging, custom exceptions, and the interactive match-case CLI dispatcher.
import sys
import logging
from pathlib import Path
from datetime import datetime
# ==========================================
# 1. LOGGING & CUSTOM EXCEPTIONS
# ==========================================
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger("InventoryOS")
class InventoryError(Exception):
"""Base exception for inventory domain errors."""
pass
class ItemNotFoundError(InventoryError):
"""Raised when requested item ID does not exist."""
pass
class StockDepletedError(InventoryError):
"""Raised when withdrawal exceeds available stock."""
pass
# ==========================================
# 2. COMPOSITE INVENTORY MANAGER SERVICE
# ==========================================
class InventoryManager:
"""
Composite Service Class ("Has-A" Storage Repository).
Manages in-memory object registry and delegates persistent operations.
"""
def __init__(self, storage_repo: BaseStorageRepository, db_path: Path):
self.storage_repo = storage_repo
self.db_path = db_path
self._registry: dict[str, BaseInventoryItem] = {}
def add_item(self, item: BaseInventoryItem):
self._registry[item.item_id] = item
logger.info(f"Registered item #{item.item_id} ({item.title}) to inventory.")
def get_item(self, item_id: str) -> BaseInventoryItem:
if item_id not in self._registry:
raise ItemNotFoundError(f"Inventory item ID #{item_id} not found!")
return self._registry[item_id]
def process_sale(self, item_id: str, qty_sold: int) -> float:
item = self.get_item(item_id)
if qty_sold > item.quantity:
raise StockDepletedError(f"Cannot sell {qty_sold} units of '{item.title}'! Only {item.quantity} in stock.")
item.quantity -= qty_sold
sale_total = item.price * qty_sold
logger.info(f"Processed sale: {qty_sold}x '{item.title}' -> Total: ${sale_total:,.2f}")
return sale_total
def persist_to_disk(self):
# Convert objects to serializable dictionary maps using comprehensions
serialized_data = {
item_id: {
"type": item.__class__.__name__,
"title": item.title,
"price": item.price,
"quantity": item.quantity,
"extra": getattr(item, "warranty_months", getattr(item, "days_to_expire", 0))
}
for item_id, item in self._registry.items()
}
self.storage_repo.save_data(self.db_path, serialized_data)
logger.info(f"Persisted {len(serialized_data)} records to disk database.")
def get_all_items(self) -> list[BaseInventoryItem]:
return list(self._registry.values())
# ==========================================
# 3. MATCH-CASE CLI DISPATCHER
# ==========================================
def dispatch_cli_command(command_str: str, manager: InventoryManager) -> bool:
"""
Parses CLI commands using Structural Pattern Matching.
Returns False when exit command is received.
"""
tokens = command_str.strip().split()
match tokens:
case ["ADD", "ELEC", item_id, title, price_str, qty_str, warranty_str]:
try:
item = ElectronicsItem(
item_id=item_id,
title=title,
price=float(price_str),
quantity=int(qty_str),
warranty_months=int(warranty_str)
)
manager.add_item(item)
print(f"SUCCESS: Added Electronics Item '{title}' (#{item_id})")
except ValueError as val_err:
print(f"ERROR: Invalid numeric format -> {val_err}")
case ["ADD", "PERISH", item_id, title, price_str, qty_str, expire_str]:
try:
item = PerishableItem(
item_id=item_id,
title=title,
price=float(price_str),
quantity=int(qty_str),
days_to_expire=int(expire_str)
)
manager.add_item(item)
print(f"SUCCESS: Added Perishable Item '{title}' (#{item_id})")
except ValueError as val_err:
print(f"ERROR: Invalid numeric format -> {val_err}")
case ["SELL", item_id, qty_str] if qty_str.isdigit():
try:
total = manager.process_sale(item_id, int(qty_str))
print(f"SUCCESS: Sale completed! Total Charged: ${total:,.2f}")
except InventoryError as inv_err:
print(f"SALE_REJECTED: {inv_err}")
case ["LIST"]:
items = manager.get_all_items()
print("\n" + "="*65)
print(f"{'ID':<8} | {'Title':<20} | {'Price':<10} | {'Qty':<6} | {'Discounted Value':<12}")
print("="*65)
for item in sorted(items, key=lambda x: x.price, reverse=True):
disc_val = item.calculate_discounted_value()
print(f"{item.item_id:<8} | {item.title:<20} | ${item.price:<9.2f} | {item.quantity:<6} | ${disc_val:<11,.2f}")
print("="*65 + "\n")
case ["SAVE"]:
manager.persist_to_disk()
print("SUCCESS: Inventory database saved to persistent JSON store.")
case ["EXIT" | "QUIT"]:
manager.persist_to_disk()
print("Shutting down Inventory OS. Session persisted safely.")
return False
case _:
print(f"ERROR: Command '{command_str}' unrecognized.")
print("Commands: ADD ELEC | ADD PERISH ... | SELL | LIST | SAVE | EXIT")
return True
# ==========================================
# 4. ENTRY POINT GUARD
# ==========================================
if __name__ == "__main__":
db_file = Path.cwd() / "inventory_db.json"
json_repo = JSONStorageRepository()
inv_manager = InventoryManager(json_repo, db_file)
print("============================================================")
print(" ENTERPRISE OBJECT-ORIENTED INVENTORY SYSTEM (v1.0.0)")
print("============================================================")
# Simulating continuous interactive CLI command stream
cli_queue = [
"ADD ELEC E101 Wireless_Mouse 25.50 50 12",
"ADD ELEC E102 4K_Monitor 350.00 8 24",
"ADD PERISH P201 Organic_Milk 4.20 30 2",
"LIST",
"SELL E101 5",
"SELL P201 100", # Triggers StockDepletedError
"SAVE",
"EXIT"
]
for cmd in cli_queue:
print(f"\n> Executing Command: '{cmd}'")
should_continue = dispatch_cli_command(cmd, inv_manager)
if not should_continue:
break
============================================================ ENTERPRISE OBJECT-ORIENTED INVENTORY SYSTEM (v1.0.0) ============================================================ > Executing Command: 'ADD ELEC E101 Wireless_Mouse 25.50 50 12' 2026-07-26 19:40:00 [INFO] Registered item #E101 (Wireless_Mouse) to inventory. SUCCESS: Added Electronics Item 'Wireless_Mouse' (#E101) > Executing Command: 'ADD ELEC E102 4K_Monitor 350.00 8 24' 2026-07-26 19:40:00 [INFO] Registered item #E102 (4K_Monitor) to inventory. SUCCESS: Added Electronics Item '4K_Monitor' (#E102) > Executing Command: 'ADD PERISH P201 Organic_Milk 4.20 30 2' 2026-07-26 19:40:00 [INFO] Registered item #P201 (Organic_Milk) to inventory. SUCCESS: Added Perishable Item 'Organic_Milk' (#P201) > Executing Command: 'LIST' ================================================================= ID | Title | Price | Qty | Discounted Value ================================================================= E102 | 4K_Monitor | $350.00 | 8 | $2,800.00 E101 | Wireless_Mouse | $25.50 | 50 | $1,147.50 P201 | Organic_Milk | $4.20 | 30 | $88.20 ================================================================= > Executing Command: 'SELL E101 5' 2026-07-26 19:40:00 [INFO] Processed sale: 5x 'Wireless_Mouse' -> Total: $127.50 SUCCESS: Sale completed! Total Charged: $127.50 > Executing Command: 'SELL P201 100' SALE_REJECTED: Cannot sell 100 units of 'Organic_Milk'! Only 30 in stock. > Executing Command: 'SAVE' [VAULT LOCK] Securing access lock for: 'inventory_db.json' [VAULT UNLOCK] Releasing file lock for: 'inventory_db.json' 2026-07-26 19:40:00 [INFO] Persisted 3 records to disk database. SUCCESS: Inventory database saved to persistent JSON store. > Executing Command: 'EXIT' [VAULT LOCK] Securing access lock for: 'inventory_db.json' [VAULT UNLOCK] Releasing file lock for: 'inventory_db.json' 2026-07-26 19:40:00 [INFO] Persisted 3 records to disk database. Shutting down Inventory OS. Session persisted safely.
6. Frequently Asked Interview Questions with Answers
save_data() and load_data()), the InventoryManager class interacts strictly with abstract storage interfaces rather than concrete implementations. This allows swapping a JSONStorageRepository for an SQLDatabaseRepository without modifying any business logic inside InventoryManager.
@dataclass eliminates repetitive boilerplate code by generating __init__(), __repr__(), and value-based equality comparison methods (__eq__()) automatically, improving readability and data modeling clarity.
__enter__() and __exit__() allows wrapping file streams inside with FileVaultLock(path):, guaranteeing that access locks are released and streams are closed safely even if runtime I/O exceptions occur.
match-case matches input sequence structures, extracts variables automatically (e.g., case ["SELL", item_id, qty_str]), and evaluates conditional guards (e.g., if qty_str.isdigit()) in a single declarative expression, replacing verbose string splitting and nested conditionals.
ItemNotFoundError or StockDepletedError) allow catching specific business failures cleanly without inadvertently suppressing unexpected low-level system errors like KeyError or AttributeError.
7. Homework & Practical Assignments
Task 1: Adding CSV Audit Export Capability
In your master_inventory_system.py script inside the lesson_38 directory:
- Add a new class method or utility
export_to_csv(filepath, items)usingcsv.DictWriter. - Add a CLI command pattern:
case ["EXPORT", "CSV", filename]: - Export all registered items, including calculated discounted values, into a formatted CSV report.
Task 2: Restock Command with Input Guard Validation
Enhance the CLI dispatcher with a new pattern:
case ["RESTOCK", item_id, qty_str] if qty_str.isdigit()- Fetch the target item, increment its
quantityproperty, and handleItemNotFoundErrorgracefully. - Log the restock event using
logger.info()and output a confirmation statement using f-strings.
Task 3: Comprehensive Review Project — Production Enterprise Warehouse Management OS (`warehouse_pro_os.py`)
Create a script named warehouse_pro_os.py inside your lesson_38 folder. This assignment tests and integrates **ALL concepts learned across Lessons 1 through 38**.
Project Architectural Requirements Specification:
- Full OOP Architecture (Lessons 32-37):
- Define abstract class
BaseWarehouseItem(ABC)with abstract methodcalculate_storage_fee()and property validations. - Define concrete Data Classes
HazardousItemandStandardIteminheriting fromBaseWarehouseItem. - Define Abstract Base Storage Repository and concrete JSON/CSV drivers.
- Build composite
WarehouseManagerclass ("Has-A" Storage & Audit Logger).
- Define abstract class
- Observability & Fault Tolerance (Lessons 29-31):
- Set up multi-handler logging to console and
warehouse.logfile. - Incorporate developer assertions (
assert) verifying warehouse capacity invariants. - Define custom exception hierarchy (
WarehouseError,CapacityExceededError).
- Set up multi-handler logging to console and
- Context Managers & Persistence Layer (Lessons 27-30):
- Use custom context managers to lock files during persistence routines.
- Persist JSON records to
warehouse_db.jsonand export CSV audits towarehouse_audit.csvusingpathlib.Path.
- Nested Collections, Comprehensions & Data Structures (Lessons 21-26):
- Maintain an in-memory dictionary mapping item IDs to Data Class Instances.
- Use List and Dictionary Comprehensions to clean, filter, and map instance streams dynamically.
- Advanced Function Parameters & Scope (Lessons 15-17):
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
*args/**kwargslogging.
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
- Indefinite & Definite Loops (Lessons 13-14):
- Run the interactive CLI interface inside a continuous
while Truemenu loop. - Iterate through output reports using
forloops withenumerate()and.items().
- Run the interactive CLI interface inside a continuous
- Pattern Matching CLI Dispatcher (Lesson 12):
- Process user CLI command tokens inside a
match-caseblock:case ["ADD", "HAZARD", id_str, name, price_str, qty_str, risk_level]→ InstantiateHazardousItemand register.case ["SHIP", id_str, qty_str]→ Process stock removal with exception handling.case ["AUDIT", "FEES"]→ Perform polymorphic storage fee calculation across all items.case ["EXPORT", "CSV"]→ Export warehouse database to CSV.case ["EXIT" | "QUIT"]→ Terminate session safely using a sentinel flag.case _→ Output command error message vialogger.error().
- Process user CLI command tokens inside a
- Conditionals, Formatting & Foundations (Lessons 1-11):
- Sanitize all string inputs using
.strip()and.upper(). - Format tabular reports using f-strings with field width alignment specifiers (
:<15,:>10) and currency formatting (:,.2f).
- Sanitize all string inputs using
8. File & Workspace Directory Structure
Standard Course Workspace Layout:
Ensure all exercise files are stored inside their corresponding lesson directories:
python_mastery_course/
│
├── lesson_01/ ... lesson_37/
│
└── lesson_38/
├── inventory_domain_models.py
├── persistence_repository_module.py
├── master_inventory_system.py
├── inventory_db.json <-- (Generated JSON database file)
└── warehouse_pro_os.py <-- (Task 3: Master Review Capstone)
OnlineCBT