Twenty lessons of primitives. Today's exercise composes six of them — no new concepts, just careful arrangement.
Build a tiny in-memory library mini that contains:
@dataclass Item with type-hinted fields name: str and value: int@logged that records the call name into a module-level list@logged-decorated function make_item(name, value) -> Itemfrom_pairs(pairs) that yields Item(...) for each (name, value) pairThen write a unittest.TestCase with a parametrised test method that creates four items via make_item and asserts is_high() matches the expected boolean.
That's a lot.
It's six concepts you've already used individually. The work is holding them all in your head at once and arranging them so the data flows cleanly.
Is there a north-star shape I should aim at?
Yes — given any list of (name, value) pairs, a one-line pipeline produces a list of Items with a logged audit trail and a passing test suite. Same six primitives, used together, give you that.
| Primitive | From | Used for |
|---|---|---|
@dataclass with type hints | L4-L5 | declaring Item(name: str, value: int) cleanly |
| Decorator | L9 | @logged records each call |
| Generator | L11 | from_pairs yields items lazily |
unittest.TestCase | L16 | one test class for the library |
subTest parametrisation | L17 | four input pairs in one method |
| Methods on a class | L3 | is_high() predicate on Item |
No new concepts — just arranging the ones you have.
from dataclasses import dataclass
import unittest
@dataclass
class Item:
name: str
value: int
def is_high(self) -> bool:
return self.value > 10
calls = []
def logged(fn):
def wrapper(*args, **kwargs):
calls.append(fn.__name__)
return fn(*args, **kwargs)
return wrapper
@logged
def make_item(name: str, value: int) -> Item:
return Item(name, value)
def from_pairs(pairs):
for name, value in pairs:
yield make_item(name, value)class TestMini(unittest.TestCase):
def test_pairs(self):
cases = [
("a", 5, False),
("b", 12, True),
("c", 3, False),
("d", 100, True),
]
for name, value, expected_high in cases:
with self.subTest(name=name, value=value):
item = make_item(name, value)
self.assertEqual(item.is_high(), expected_high)Four parametrised cases, one method, all four should pass.
models.py, iterators.py, tests/test_models.py. In this in-browser runtime everything lives in one module — but the concepts are the same.__init__.py. Same reason.pytest.parametrize. Not available; we use subTest instead.The shape — composing dataclass + decorator + generator + tests — is what real codebases look like, regardless of how the file split goes.
Twenty lessons of primitives. Today's exercise composes six of them — no new concepts, just careful arrangement.
Build a tiny in-memory library mini that contains:
@dataclass Item with type-hinted fields name: str and value: int@logged that records the call name into a module-level list@logged-decorated function make_item(name, value) -> Itemfrom_pairs(pairs) that yields Item(...) for each (name, value) pairThen write a unittest.TestCase with a parametrised test method that creates four items via make_item and asserts is_high() matches the expected boolean.
That's a lot.
It's six concepts you've already used individually. The work is holding them all in your head at once and arranging them so the data flows cleanly.
Is there a north-star shape I should aim at?
Yes — given any list of (name, value) pairs, a one-line pipeline produces a list of Items with a logged audit trail and a passing test suite. Same six primitives, used together, give you that.
| Primitive | From | Used for |
|---|---|---|
@dataclass with type hints | L4-L5 | declaring Item(name: str, value: int) cleanly |
| Decorator | L9 | @logged records each call |
| Generator | L11 | from_pairs yields items lazily |
unittest.TestCase | L16 | one test class for the library |
subTest parametrisation | L17 | four input pairs in one method |
| Methods on a class | L3 | is_high() predicate on Item |
No new concepts — just arranging the ones you have.
from dataclasses import dataclass
import unittest
@dataclass
class Item:
name: str
value: int
def is_high(self) -> bool:
return self.value > 10
calls = []
def logged(fn):
def wrapper(*args, **kwargs):
calls.append(fn.__name__)
return fn(*args, **kwargs)
return wrapper
@logged
def make_item(name: str, value: int) -> Item:
return Item(name, value)
def from_pairs(pairs):
for name, value in pairs:
yield make_item(name, value)class TestMini(unittest.TestCase):
def test_pairs(self):
cases = [
("a", 5, False),
("b", 12, True),
("c", 3, False),
("d", 100, True),
]
for name, value, expected_high in cases:
with self.subTest(name=name, value=value):
item = make_item(name, value)
self.assertEqual(item.is_high(), expected_high)Four parametrised cases, one method, all four should pass.
models.py, iterators.py, tests/test_models.py. In this in-browser runtime everything lives in one module — but the concepts are the same.__init__.py. Same reason.pytest.parametrize. Not available; we use subTest instead.The shape — composing dataclass + decorator + generator + tests — is what real codebases look like, regardless of how the file split goes.
Create a free account to get started. Paid plans unlock all tracks.