PythonIntermediate 16 min Lesson 30 of 30

Day 30 — Final Project and Best Practices

Ship a complete project and adopt the habits that separate working code from maintainable code — plus a clear path for what to learn next.

Python · Lesson 30 of 30
0/30 done(0%)

What is it? #

You now know enough Python to build real things. The last step is turning scattered knowledge into habits.

The final project brings the pieces together: an API over the expense tracker from Day 29, with validation, tests, configuration and a readme good enough for someone else to run it.

Alongside it, a short list of practices that hold up across every project — not style rules for their own sake, but the ones that reduce real pain later.

Then a map of where to go next, because Python is the tool, not the destination.

Think of it like this #

Passing a driving test does not make you a good driver. What follows is the ordinary practice: mirrors, indicators, keeping distance. Boring, repetitive, and exactly what keeps you safe in traffic. Good coding habits work the same way.

Simple example #

The final project is the Day 29 tracker exposed as an HTTP API, with a settings module, a test suite and a readme. Below it, the practices worth carrying into every project you write from here.

Code #

TEXT
Final project — expense tracker API

Requirements
1. GET  /expenses?category=food&limit=20   list with filters
2. POST /expenses                          create, validated, returns 201
3. GET  /expenses/summary                  totals by category
4. DELETE /expenses/{id}                   404 if missing, 204 if deleted
5. Config from environment (DATABASE_PATH, PAGE_SIZE)
6. Tests for the service layer, including failure cases
7. README with setup, run and test commands

Structure
src/tracker/{config.py, storage.py, service.py, routes.py, main.py}
tests/{test_service.py, test_routes.py}
.env.example  requirements.txt  README.md  .gitignore
PYTHON
# The practices, as code you can recognise

# 1. Names that say what they hold
days_until_expiry = 14            # not: d, tmp, x2

# 2. Early returns instead of nested ifs
def price_for(user, item):
    if item is None:
        return 0
    if not user.is_active:
        return item.list_price
    return item.member_price

# 3. Functions that return values rather than printing
def build_summary(rows):          # testable
    return {"count": len(rows), "total": sum(r.amount for r in rows)}

# 4. Errors that say what to do
raise ValidationError("category must be one of: food, travel, bills, other")
# not: raise Exception("bad input")

# 5. Configuration from the environment, never hardcoded
DB_PATH = os.environ.get("DATABASE_PATH", "expenses.db")

# 6. Log facts, not secrets
logger.info("created expense", extra={"expense_id": new_id})
# not: logger.info(f"payload={payload} token={token}")
BASH
# Tools worth adding to any project
pip install pytest mypy ruff
ruff check .          # linting and common mistakes
ruff format .         # consistent formatting, no arguments about style
mypy src/             # type checking
pytest -q             # tests

How it works #

The project brief is deliberately written as requirements rather than code. Working from requirements is the actual job; translating them into structure is the skill worth practising.

Each practice in the code block addresses a specific pain. Good names remove the need for comments explaining what a variable is. Early returns keep the indentation flat and the logic readable. Functions that return values can be tested; functions that print cannot.

Error messages that name the fix save support time. Compare "bad input" with "category must be one of: food, travel, bills, other" — the second one tells the user exactly what to do.

Configuration from the environment is what lets the same artefact run in three environments. Hardcoded paths are the reason "it works on my machine".

Logging facts and not secrets matters because logs are widely readable and retained for a long time. A token in a log line is a token that leaked.

The tool list is short on purpose. ruff handles linting and formatting quickly, mypy checks types, pytest runs tests. Wiring those three into CI — covered in the CI/CD track — means every push is checked automatically.

Real-world use #

The gap between a working script and maintainable software is mostly these habits. Nobody is impressed by clever code; teams value code they can change on a Friday without fear.

From here, three directions are worth considering, depending on what you want to build.

For backend work: go deeper on databases and system design, learn a framework properly, and learn to deploy — the System Design, VPS, Docker and CI/CD tracks here are the natural continuation.

For data work: NumPy, pandas and SQL at a deeper level, then visualisation and statistics.

For general engineering strength: data structures and algorithms, object-oriented design, SOLID and design patterns — which sharpen how you structure any code, in any language.

Whichever you pick, keep building. Small finished projects teach far more than large abandoned ones.

Common mistakes #

  • Learning more syntax instead of finishing a project. Depth comes from shipping.
  • Adding abstractions before there is a second use case for them.
  • Skipping the readme, so future you cannot run your own project.
  • Treating linting and type checking as optional extras rather than a five-minute setup.
  • Comparing your first projects with polished open-source code. Compare with your own work from last month.

Practice #

Build the final project from the brief above. When it runs, do these four things: write a readme someone else could follow, add ruff and mypy, get the service layer tests passing, and then delete one piece of code you no longer need. Shipping and pruning are both part of the job.

Quick quiz

  1. 1. Why prefer a function that returns a value over one that prints?

  2. 2. What is the problem with `raise Exception("bad input")`?

  3. 3. Why should configuration come from the environment?

  4. 4. What is the risk of logging a whole request payload?

  5. 5. What is the best way to keep improving after this track?

Summary

  • Build the final project from requirements — translating them is the real skill.
  • Good names, early returns and value-returning functions do most of the readability work.
  • Specific error messages and environment-based configuration save future pain.
  • Add ruff, mypy and pytest once; they pay off on every change after that.
  • Keep shipping small projects, then go deeper on design, databases or deployment.