Static Data
Serving hard-coded snail records before introducing a real database
Concepts
- A Python dataclass (decorated with
@dataclass) groups related fields into a typed record- It automatically generates
__init__,__repr__, and__eq__ - What other libraries do the same thing as
dataclassand when should I use them instead?
- It automatically generates
- Type annotations in a dataclass (e.g.,
diameter: float) document what kind of value each field holds and enable runtime validation tools to check incoming data- What are the benefits and drawbacks of adding type annotations to Python?
float | Nonemeans a field may hold a number or may be absent- Code that reads such a field must handle both cases to avoid errors
- How do I safely display a dataclass field that might be
Nonewhen rendering it in htpy?
- Synthetic data allows you to develop and test code
before real data is collected or shared
- snailz generates consistent synthetic snail survey data
- How do I create a useful synthetic data generator?
- Iterating over a list inside an htpy element produces multiple sibling child elements,
one for each item
- This is the natural way to generate table rows from records
- When and how would I use a conditional in htpy?
Representing a record as a dataclass
- Python dataclasses for a snail measurement
- site, date, shell diameter, presence of mutation
- Write a Python dataclass for a snail field measurement and give an example of its use
- Type annotations and defaults
- Explain your choice of default values for the dataclass's fields
- Hard-code five records to work with during development
- Write five hardcoded snail measurement records as a Python list of dataclass instances for use during development and explain why you put them where you did
Rendering records as an HTML table
- A function that takes a list of dataclasses and returns an htpy table
- Write a Python function that takes a list of snail measurement dataclass instances and returns an htpy HTML table
- Handling missing values
- Modify the function so that users can easily change the way missing values are displayed
A route that serves the table
- Connecting the data and the renderer in a Litestar handler
- Write a Litestar route handler that serves the entire set of snail observations as a single table
- Checking the output in a browser and with curl
- Show and explain the curl command to check that the route handler is working
Check for Understanding
What does @dataclass do, and how is a dataclass different from a plain dictionary?
A plain dictionary is untyped and has no fixed structure;
you can add or remove keys freely.
A dataclass enforces structure at definition time and provides attribute-style access
(record.diameter instead of record["diameter"]).
The @dataclass decorator reads the field names and type annotations you declare
and generates an __init__ method
(so you can write Snail(site="north", diameter=12.3))
along with __repr__ and __eq__.
If a field in a dataclass has type float | None, what does that mean, and how would you handle it when rendering?
It means the field can hold a floating-point number or the value None (absent/unknown).
When rendering in htpy,
you must check before converting to a string:
for example, str(record.diameter) if record.diameter is not None else "—".
Passing None directly to htpy or formatting it without checking
would display the string "None",
which is confusing to readers.
Why might you prefer hard-coded sample data over querying a real database during early development?
Hard-coded data is always available, never changes between runs, and requires no setup. You can render and test the table layout before the database schema is finalized or data is loaded. It also makes bugs easier to isolate: if something is wrong, you know the data is not the cause. Of course, you must remember to replace it later.
If you add a new field to the dataclass, what else in the code do you need to update?
You need to update:
(1) any hard-coded sample data that creates instances of the dataclass
(the new field needs a value or a default),
(2) the HTML table renderer (add a column header and a cell for the new field),
(3) any SQL insert and select statements in later lessons that reference specific columns,
and (4) any tests that construct instances.
Exercises
Add a new field
Add a notes: str field with a default value of "" to the snail measurement dataclass.
Update the sample records and the table renderer to include it.
Prompt the LLM for help and check whether its output covers all the places that need updating;
do not just accept that it is complete.
Sort the table
Add a query parameter sort that controls which column the table is sorted by
(e.g., ?sort=diameter).
Write out what the sorting logic should do before prompting the LLM to implement it.
Verify that it handles the case where sort refers to a field that does not exist.
Color-code rows
Prompt the LLM to add a CSS class (e.g., mutated) to table rows
where the mutation field is True,
and to style those rows with a distinct background color.
Check that the class is applied only to the correct rows,
and that the color passes a basic contrast check against the text.
Export as CSV
Add a /data.csv route that returns the hard-coded sample records as a CSV file.
The response must set Content-Type: text/csv and include a header row.
Test it with curl -o records.csv http://localhost:8000/data.csv
and open the file in a spreadsheet application to verify the format.