Six days of pieces. Today they become a program.
The project below is a habit tracker: it stores entries, saves them to a file, and reports on them. It is small enough to read in one sitting and real enough to be worth finishing.
The finished program
Read it first, then run it. Every construct in here comes from a lesson this
week — dataclasses from Day 6, comprehensions from Day 4, json and exception
handling from Day 5, dictionaries from Day 3.
Why it is split this way
Three modules, three jobs, dependencies pointing one way:
tracker.pyholds the rules and knows nothing about files or printing. Every method is testable with no setup at all.storage.pyis the only module that touches the filesystem. Swap it for a database module and nothing intracker.pychanges.main.pydecides what actually happens, behind a main guard.
That separation — rules, storage, entry point — is the smallest useful version of how most real applications are laid out.
Your turn
Take the program above and make it yours. In rough order of difficulty:
- Add
average_minutes(habit)and show it in the report. - Add
recordvalidation: reject adaythat is notYYYY-MM-DD.date.fromisoformatraisesValueErrorfor you — catch and re-raise with a clearer message. - Add a
remove(habit, day)method, and decide what should happen when there is no such entry. - Write tests for
streak— the empty case, a single day, a broken run, and two separate runs where the longer one comes first. - Add a command line: read commands from
input()in a loop —add read 2026-01-08 30,report,quit— parsing withsplit()and handling bad commands without crashing.
Start here: average minutes and a tested streak
Add average_minutes(habit) to Tracker, include it in report(), and write
assertions covering the four streak cases listed above.
Show one solution
def average_minutes(self, habit):
"""Mean minutes per recorded day, or 0 when there are none."""
days = self.days_for(habit)
if not days:
return 0
return round(self.total_minutes(habit) / len(days), 1)And the streak assertions:
empty = Tracker()
assert empty.streak("read") == 0
one = Tracker()
one.record("read", "2026-01-05", 10)
assert one.streak("read") == 1
broken = Tracker()
for day in ["2026-01-05", "2026-01-06", "2026-01-09"]:
broken.record("read", day, 10)
assert broken.streak("read") == 2
longest_first = Tracker()
for day in ["2026-01-01", "2026-01-02", "2026-01-03", "2026-01-09"]:
longest_first.record("read", day, 10)
assert longest_first.streak("read") == 3
print("all streak tests passed")The last case is the one that catches a real bug: an implementation that
returns current instead of longest passes the first three tests and fails
this one.
Where to go next
You know the language. What remains is choosing a direction and building things in it.
| If you want to… | Learn next |
|---|---|
| Analyse data | pandas, numpy, Jupyter notebooks |
| Build web APIs | FastAPI or Django, plus HTTP basics |
| Automate your machine | pathlib, subprocess, argparse, scheduling |
| Scrape and call APIs | requests, httpx, BeautifulSoup |
| Write better Python | pytest, ruff, mypy, and the itertools docs |
| Understand the language deeply | Fluent Python, and the Python source itself |
Three habits matter more than any of those choices:
Build something you actually want. Motivation beats curriculum. A script that renames your photos will teach you more than ten tutorials.
Read other people’s code. The standard library is on your machine and much
of it is readable. import random; print(random.__file__) will show you where.
Write it down when it breaks. Every error you understand is one you will never lose an hour to again.
The week, in one page
| Day | What you can now do |
|---|---|
| 1 | Variables, numbers, strings, f-strings, input and conversion |
| 2 | Conditions, while and for, break and continue |
| 3 | Lists, tuples, dictionaries, sets — and choosing between them |
| 4 | Functions, arguments, scope, lambdas, comprehensions |
| 5 | Exceptions, files, the standard library, your own environment |
| 6 | Classes, dunder methods, inheritance, dataclasses, type hints |
| 7 | Generators, packages, tests, and a program of your own |
That is the essential language. Everything else in Python is a library on top of what you have just learned.