Before we start today — I want you to think about everything you built in the last four weeks. Don't open any files. Just think. What comes up?
The Monday report. That's still my benchmark. And then I think about how every concept since then was something I was doing wrong in that report, or doing the slow way, or not doing at all because I didn't know it existed.
Good. Because today all of it comes back — not as isolated exercises, but as a single working system. You're going to build a multi-file inventory tool that uses tuples for data integrity, functions with defaults for clean APIs, match/case for command dispatch, and proper module structure so it doesn't turn into the 312-line tangle from the Week 4 intro.
Multi-file meaning this actually spans across modules? Like the real project structure we've been building toward since day twenty-three?
Exactly. But before I show you the architecture, design it yourself. You've been asking the right architectural questions all week. What would your split look like?
I'd put the data layer in one module — anything that touches the actual inventory dict. Then a separate module for the command dispatcher — that's pure routing logic. Then a main entry point that wires them together. Three files, clean separation.
Three files. Clean separation. You would not have said that on Day 2 of this track. You would have opened one file and started typing. That architectural instinct is the thing you can't fake — it comes from writing the 312-line tangle once and understanding why the seams matter.
I wrote that tangle. It was the category deduplication script. I never want to talk about it again.
We never have to. Here's the structure we're building:
# inventory_tool/
# __init__.py
# store.py # data layer: add, restock, search, lookup
# reports.py # output formatting: full report, low-stock filter
# cli.py # command dispatcher: match/case on command tuples
# main.py # entry point: __name__ == '__main__' guardstore.py is the data layer — correct. cli.py is the dispatcher. reports.py is separate because formatting logic has no business touching raw data. The __init__.py makes it a proper package.
The whole thing hangs together through one public function in cli.py — inventory_system. It takes a list of command tuples and returns a list of result strings. That's your API surface. Callers don't need to know about store.py or reports.py. They just pass commands and get results.
The commands are tuples — so I'm using match/case to dispatch on tuple structure, same pattern as Day 20. But now the dispatcher is organized as a proper module instead of floating in a script.
Right. Here's the command protocol:
("add", name, sku, price, stock, category) # add a new product
("restock", sku, qty) # increase stock for an existing SKU
("search", field, value) # find products by field match
("report",) # full inventory summary
("low_stock", threshold) # products with stock below thresholdThe add tuple has six elements. Will case ("add", name, sku, price, stock, category): handle that cleanly?
Python checks the length and first element in one shot, then binds all five remaining values. No indexing, no length check. You get name, sku, price, stock, and category as local variables the moment the case matches.
So store.py holds a dict keyed by SKU — inventory[sku] = {"name": ..., "price": ..., "stock": ..., "category": ...}. add inserts. restock updates the stock count. search filters by field. Then reports.py reads from that dict and formats output. And cli.py is pure routing — it calls the right store or report function based on the verb.
That is a correct system design. Now trace the edge cases. What happens when restock gets a SKU that doesn't exist?
Return an error string — "Error: {sku} not found" — instead of raising a KeyError. The function should never crash on bad input; it returns a description of what went wrong. Same for search returning "No matches for {field}={value}" when nothing matches.
That error contract — return a description, don't raise — is what separates a library from a script. A script crashes and you fix it. A library returns information and the caller decides what to do. You're writing a library.
The low_stock case is interesting. If I pass threshold=10 and nothing is below ten, I return "No low stock items" — one string. If there are low-stock items, I return one string per item. The results list accumulates all of them.
Right. The results list is a running log — every command appends its output. Stateful across the full command list. That's what lets you run multiple commands in sequence and get back a flat list of everything that happened.
For report — the unique category count. I'd use a set comprehension to deduplicate.
{p["category"] for p in inventory.values()} — a set comprehension. You've been building toward this since Day 4. The set deduplicates automatically; you just call len() on it.
Twenty-eight days of concepts and they all show up in this one function. Tuple unpacking in the match patterns. Dict-of-dicts for the inventory. Set comprehension for categories. Generator expression for the total stock sum. The for/else pattern is even in the low_stock case if I wanted to use it.
I was waiting for you to trace that. You didn't just learn Python syntax — you learned to recognize when each tool is the right tool. That's the thing that takes most developers years to develop.
I remember Day 3. I was confused about why you'd ever use a tuple when a list exists. Now I'm designing a system where the SKU is the canonical product identity and I'm reaching for a dict keyed by SKU as the natural data structure.
Twenty-eight days. And you got here not by memorizing syntax, but by applying each concept to something real and letting the next concept land on top of it. Each piece connected forward. Tuples → named tuples → immutability reasoning. Functions with defaults → keyword-only parameters → clean API design. Match/case → sequence patterns → command dispatch. Module structure → packages → import organization. Every lesson was building something.
After the capstone — the post-track Likert. I'm actually looking forward to seeing how my answers changed from Day 1.
You gave yourself a two or three on "I can structure a Python project." You literally wrote that you didn't know what a module was.
I just designed a four-file package architecture from scratch and it felt obvious. I think the number moved.
Go find out. Write inventory_system first. Docstring, then the dispatcher, then the full command protocol. All five cases, the wildcard, and the error contracts. When you're done, you'll have the capstone this track was building toward since Day 1.
I'm writing it. Diane's warehouse system is about to get a proper replacement.
This capstone integrates every major concept from the track into a single working system. The architecture separates concerns across modules:
store.py — data layer (inventory dict, add/restock/search operations)reports.py — output formatting (report generation, low-stock filter)cli.py — command dispatch (inventory_system function, match/case)main.py — entry point (if __name__ == '__main__': guard)__init__.py — marks the directory as a packageThe inventory_system function is the single public API surface. Callers pass command tuples and receive result strings — they never need to know about the internal module structure.
def inventory_system(commands: list[tuple]) -> list[str]:
inventory: dict[str, dict] = {}
results: list[str] = []
for cmd in commands:
match cmd:
case ("add", name, sku, price, stock, category):
inventory[sku] = {"name": name, "price": price,
"stock": stock, "category": category}
results.append(f"Added {name} ({sku})")
case ("restock", sku, quantity):
if sku in inventory:
old = inventory[sku]["stock"]
inventory[sku]["stock"] += quantity
results.append(f"Restocked {sku}: {old} -> {inventory[sku]['stock']}")
else:
results.append(f"Error: {sku} not found")
case ("report",):
count = len(inventory)
categories = len({p["category"] for p in inventory.values()})
total = sum(p["stock"] for p in inventory.values())
results.append(
f"Inventory: {count} products, {categories} categories, {total} total units"
)
case ("low_stock", threshold):
low = [p for p in inventory.values() if p["stock"] < threshold]
if low:
for p in low:
results.append(f"Low stock: {p['name']} ({p['sku']}): {p['stock']}")
else:
results.append("No low stock items")
case ("search", field, value):
matches = [p for p in inventory.values() if str(p.get(field, "")) == str(value)]
if matches:
for p in matches:
results.append(f"Found: {p['name']} ({p['sku']})")
else:
results.append(f"No matches for {field}={value}")
case _:
results.append(f"Unknown command: {cmd[0]}")
return resultsConcepts used in this single function:
Pitfall 1: State not persisted across commands. The inventory dict must be initialized once and mutated by each command. Reinitializing per-command resets the state.
Pitfall 2: search crashing on missing fields. product[field] raises KeyError if the field doesn't exist. Use product.get(field, "") to handle missing fields safely.
Pitfall 3: report on empty inventory. When inventory is empty, len() returns 0 and both comprehensions produce empty results — the function should still return the summary string: "Inventory: 0 products, 0 categories, 0 total units".
The single-function implementation works for the capstone. A real production system would split this into the four-module architecture described in the lesson: store.py owns state and mutation, reports.py owns formatting, cli.py owns dispatch, main.py owns the entry point. The seams matter when Diane asks you to add a supplier field six months from now — changing store.py shouldn't require touching reports.py or cli.py.
Sign up to write and run code in this lesson.
Before we start today — I want you to think about everything you built in the last four weeks. Don't open any files. Just think. What comes up?
The Monday report. That's still my benchmark. And then I think about how every concept since then was something I was doing wrong in that report, or doing the slow way, or not doing at all because I didn't know it existed.
Good. Because today all of it comes back — not as isolated exercises, but as a single working system. You're going to build a multi-file inventory tool that uses tuples for data integrity, functions with defaults for clean APIs, match/case for command dispatch, and proper module structure so it doesn't turn into the 312-line tangle from the Week 4 intro.
Multi-file meaning this actually spans across modules? Like the real project structure we've been building toward since day twenty-three?
Exactly. But before I show you the architecture, design it yourself. You've been asking the right architectural questions all week. What would your split look like?
I'd put the data layer in one module — anything that touches the actual inventory dict. Then a separate module for the command dispatcher — that's pure routing logic. Then a main entry point that wires them together. Three files, clean separation.
Three files. Clean separation. You would not have said that on Day 2 of this track. You would have opened one file and started typing. That architectural instinct is the thing you can't fake — it comes from writing the 312-line tangle once and understanding why the seams matter.
I wrote that tangle. It was the category deduplication script. I never want to talk about it again.
We never have to. Here's the structure we're building:
# inventory_tool/
# __init__.py
# store.py # data layer: add, restock, search, lookup
# reports.py # output formatting: full report, low-stock filter
# cli.py # command dispatcher: match/case on command tuples
# main.py # entry point: __name__ == '__main__' guardstore.py is the data layer — correct. cli.py is the dispatcher. reports.py is separate because formatting logic has no business touching raw data. The __init__.py makes it a proper package.
The whole thing hangs together through one public function in cli.py — inventory_system. It takes a list of command tuples and returns a list of result strings. That's your API surface. Callers don't need to know about store.py or reports.py. They just pass commands and get results.
The commands are tuples — so I'm using match/case to dispatch on tuple structure, same pattern as Day 20. But now the dispatcher is organized as a proper module instead of floating in a script.
Right. Here's the command protocol:
("add", name, sku, price, stock, category) # add a new product
("restock", sku, qty) # increase stock for an existing SKU
("search", field, value) # find products by field match
("report",) # full inventory summary
("low_stock", threshold) # products with stock below thresholdThe add tuple has six elements. Will case ("add", name, sku, price, stock, category): handle that cleanly?
Python checks the length and first element in one shot, then binds all five remaining values. No indexing, no length check. You get name, sku, price, stock, and category as local variables the moment the case matches.
So store.py holds a dict keyed by SKU — inventory[sku] = {"name": ..., "price": ..., "stock": ..., "category": ...}. add inserts. restock updates the stock count. search filters by field. Then reports.py reads from that dict and formats output. And cli.py is pure routing — it calls the right store or report function based on the verb.
That is a correct system design. Now trace the edge cases. What happens when restock gets a SKU that doesn't exist?
Return an error string — "Error: {sku} not found" — instead of raising a KeyError. The function should never crash on bad input; it returns a description of what went wrong. Same for search returning "No matches for {field}={value}" when nothing matches.
That error contract — return a description, don't raise — is what separates a library from a script. A script crashes and you fix it. A library returns information and the caller decides what to do. You're writing a library.
The low_stock case is interesting. If I pass threshold=10 and nothing is below ten, I return "No low stock items" — one string. If there are low-stock items, I return one string per item. The results list accumulates all of them.
Right. The results list is a running log — every command appends its output. Stateful across the full command list. That's what lets you run multiple commands in sequence and get back a flat list of everything that happened.
For report — the unique category count. I'd use a set comprehension to deduplicate.
{p["category"] for p in inventory.values()} — a set comprehension. You've been building toward this since Day 4. The set deduplicates automatically; you just call len() on it.
Twenty-eight days of concepts and they all show up in this one function. Tuple unpacking in the match patterns. Dict-of-dicts for the inventory. Set comprehension for categories. Generator expression for the total stock sum. The for/else pattern is even in the low_stock case if I wanted to use it.
I was waiting for you to trace that. You didn't just learn Python syntax — you learned to recognize when each tool is the right tool. That's the thing that takes most developers years to develop.
I remember Day 3. I was confused about why you'd ever use a tuple when a list exists. Now I'm designing a system where the SKU is the canonical product identity and I'm reaching for a dict keyed by SKU as the natural data structure.
Twenty-eight days. And you got here not by memorizing syntax, but by applying each concept to something real and letting the next concept land on top of it. Each piece connected forward. Tuples → named tuples → immutability reasoning. Functions with defaults → keyword-only parameters → clean API design. Match/case → sequence patterns → command dispatch. Module structure → packages → import organization. Every lesson was building something.
After the capstone — the post-track Likert. I'm actually looking forward to seeing how my answers changed from Day 1.
You gave yourself a two or three on "I can structure a Python project." You literally wrote that you didn't know what a module was.
I just designed a four-file package architecture from scratch and it felt obvious. I think the number moved.
Go find out. Write inventory_system first. Docstring, then the dispatcher, then the full command protocol. All five cases, the wildcard, and the error contracts. When you're done, you'll have the capstone this track was building toward since Day 1.
I'm writing it. Diane's warehouse system is about to get a proper replacement.
This capstone integrates every major concept from the track into a single working system. The architecture separates concerns across modules:
store.py — data layer (inventory dict, add/restock/search operations)reports.py — output formatting (report generation, low-stock filter)cli.py — command dispatch (inventory_system function, match/case)main.py — entry point (if __name__ == '__main__': guard)__init__.py — marks the directory as a packageThe inventory_system function is the single public API surface. Callers pass command tuples and receive result strings — they never need to know about the internal module structure.
def inventory_system(commands: list[tuple]) -> list[str]:
inventory: dict[str, dict] = {}
results: list[str] = []
for cmd in commands:
match cmd:
case ("add", name, sku, price, stock, category):
inventory[sku] = {"name": name, "price": price,
"stock": stock, "category": category}
results.append(f"Added {name} ({sku})")
case ("restock", sku, quantity):
if sku in inventory:
old = inventory[sku]["stock"]
inventory[sku]["stock"] += quantity
results.append(f"Restocked {sku}: {old} -> {inventory[sku]['stock']}")
else:
results.append(f"Error: {sku} not found")
case ("report",):
count = len(inventory)
categories = len({p["category"] for p in inventory.values()})
total = sum(p["stock"] for p in inventory.values())
results.append(
f"Inventory: {count} products, {categories} categories, {total} total units"
)
case ("low_stock", threshold):
low = [p for p in inventory.values() if p["stock"] < threshold]
if low:
for p in low:
results.append(f"Low stock: {p['name']} ({p['sku']}): {p['stock']}")
else:
results.append("No low stock items")
case ("search", field, value):
matches = [p for p in inventory.values() if str(p.get(field, "")) == str(value)]
if matches:
for p in matches:
results.append(f"Found: {p['name']} ({p['sku']})")
else:
results.append(f"No matches for {field}={value}")
case _:
results.append(f"Unknown command: {cmd[0]}")
return resultsConcepts used in this single function:
Pitfall 1: State not persisted across commands. The inventory dict must be initialized once and mutated by each command. Reinitializing per-command resets the state.
Pitfall 2: search crashing on missing fields. product[field] raises KeyError if the field doesn't exist. Use product.get(field, "") to handle missing fields safely.
Pitfall 3: report on empty inventory. When inventory is empty, len() returns 0 and both comprehensions produce empty results — the function should still return the summary string: "Inventory: 0 products, 0 categories, 0 total units".
The single-function implementation works for the capstone. A real production system would split this into the four-module architecture described in the lesson: store.py owns state and mutation, reports.py owns formatting, cli.py owns dispatch, main.py owns the entry point. The seams matter when Diane asks you to add a supplier field six months from now — changing store.py shouldn't require touching reports.py or cli.py.