Intermediate Python projects combine several skills into a program that reads real data, applies rules, saves results and can be run by someone else. The step up from beginner projects isn't harder syntax; it's structure: separate modules with clear responsibilities, persistent data with a defined format, automated tests and documented set-up.
def remaining_stock(stock, checked_out):
return {item: qty - checked_out.get(item, 0) for item, qty in stock.items()}
print(remaining_stock({"tripods": 8, "lamps": 6}, {"tripods": 3}))
# {'tripods': 5, 'lamps': 6}This page suggests three projects, an equipment loan report for a photography club, a validated donation importer and a logged API client, and covers the structure each one needs. It draws on file handling, CSV, pytest, logging and virtual environments.
Module boundaries
Split the program by responsibility, so each part can be understood, tested and changed on its own:
equipment_report/
loans/
__init__.py
parsing.py # read CSV files -> lists of dicts (no calculations)
stock.py # pure calculations on Python data (no files)
report.py # format results as text (no files, no calculations)
cli.py # glue: read arguments, call the others, write output
tests/
test_stock.py
test_parsing.py
pyproject.toml
README.mdThe calculation module, stock.py, takes plain dictionaries and returns plain dictionaries: it doesn't know whether data came from a CSV file, a database or a test. That makes it trivial to test and reuse. Parsing deals with messy input; reporting deals with presentation; the command-line module wires them together.
One large function that reads a file, calculates, formats and prints is hard to debug: when a total is wrong, you can't tell whether parsing, arithmetic or formatting is at fault. Separate modules localise failures.
Persistent data
Files outlive the program, so decide their format, encoding and failure policy up front:
- Format: CSV for tables people open in spreadsheets; JSON for nested settings and state; SQLite (
sqlite3) when you need queries or several record types. - Encoding: always
encoding="utf-8", withnewline=""for CSV. - Failure policy: what happens with a missing file, a malformed row or a crash halfway through writing?
Never write directly over your only copy of important data. Write to a temporary file and replace the original only when writing succeeded:
import json, os
from pathlib import Path
def save_json(path, data):
path = Path(path)
tmp = path.with_suffix(".tmp")
tmp.write_text(json.dumps(data, indent=2), encoding="utf-8")
os.replace(tmp, path) # atomic on the same file systemIf the program crashes while writing, the original file is untouched. Keep a sample data file in the repository, with fake data, so others can run the project immediately.
Testing collaborators
Test pure logic directly with ordinary values, and test file-handling code with temporary files that the test creates and cleans up:
# tests/test_stock.py
from loans.stock import remaining_stock
def test_items_not_checked_out_keep_full_stock():
assert remaining_stock({"lamps": 6}, {}) == {"lamps": 6}
def test_parse_skips_bad_rows(tmp_path):
file = tmp_path / "loans.csv"
file.write_text("item,qty\ntripods,3\nlamps,many\n", encoding="utf-8")
rows, errors = parse_loans(file)
assert rows == [{"item": "tripods", "qty": 3}] and len(errors) == 1pytest's tmp_path fixture provides a fresh folder for each test. Tests must never depend on files in your home folder or on data only you have; another developer, or continuous integration, must be able to run them on a clean machine.
Focus tests on the risky cases: missing equipment, items returned twice, empty files, malformed rows and quantities that would go negative. The mocking and fixtures lesson covers replacing network calls in the API client project.
Packaging and usage
A project is only finished when someone else can run it. Document and automate the set-up:
## Setup
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install -e ".[dev]"
## Run
python -m loans.cli data/sample_loans.csv
## Test
python -m pytestDeclare dependencies in pyproject.toml (see dependency management), add a .gitignore for .venv and generated output, and include sample data.
The real test is a clean checkout: clone the repository into a new folder, follow the README exactly, and check that tests pass and the report matches the documented example. Anything you had to do that isn't written down, such as installing a package by hand or creating a folder, is a missing step. A project that only works in your personal environment isn't finished.
Independent practice and a worked check
Task: build an equipment report, a validated donation importer or a logged API client with modules, persistence and tests. Suggested milestones for the donation importer:
parsing.py: read a CSV of donations, returning valid rows and a list of errors with line numbers.summary.py: total donations per month from clean rows (pure function).storage.py: save the summary to JSON with the temporary-file pattern.tests/: test parsing withtmp_path, the summary with plain data, and saving with a temporary folder.README.md: set-up, run and test commands, verified from a fresh clone.
def monthly_totals(rows):
totals = {}
for row in rows:
month = row["date"][:7] # "2026-03-14" -> "2026-03"
totals[month] = totals.get(month, 0) + row["amount"]
return totals
print(monthly_totals([{"date": "2026-03-14", "amount": 20}, {"date": "2026-03-20", "amount": 15},
{"date": "2026-04-02", "amount": 40}])) # {'2026-03': 35, '2026-04': 40}For the worked check, the responsibility that should be separated from stock arithmetic is external file parsing.
Reference and next practice
The Python Packaging User Guide's packaging tutorial explains project layout and pyproject.toml. Continue with advanced projects and revisit pytest in depth. Practise with the Python exercises and Python quiz.
Intermediate Python Projects Summary
- Module boundaries: Separate parsing, validation, business logic and presentation into understandable responsibilities.
- Persistent data: Files require a chosen schema, encoding and failure policy.
- Testing collaborators: Test pure logic directly and control external file dependencies with temporary data.
- Packaging and usage: Document the environment, dependencies and commands needed to run the project.
Intermediate Python Projects Examples
Each example shows the code, the output it produces and what each part does.
Intermediate Python Projects in a small program
def remaining_stock(stock, checked_out):
return {item: quantity - checked_out.get(item, 0) for item, quantity in stock.items()}
print(remaining_stock({"tripods": 8, "lamps": 6}, {"tripods": 3})){'tripods': 5, 'lamps': 6}Line-by-line explanation
- Lines 1 to 2A pure function: two dictionaries in, a new dictionary out, no files or printing.
- Line 2
checked_out.get(item, 0)treats items with no loans as fully in stock. - Line 4Three tripods are out, leaving five; lamps are untouched.
Parsing, logic and reporting kept separate
import csv, io
# parsing.py: text in, clean records and errors out
def parse_loans(text):
rows, errors = [], []
for line_no, row in enumerate(csv.DictReader(io.StringIO(text)), start=2):
try:
qty = int(row["qty"])
if qty <= 0:
raise ValueError("quantity must be positive")
rows.append({"item": row["item"].strip().lower(), "qty": qty})
except (ValueError, KeyError) as error:
errors.append(f"line {line_no}: {error}")
return rows, errors
# stock.py: pure calculation
def remaining_stock(stock, loans):
out = dict(stock)
for loan in loans:
out[loan["item"]] = out.get(loan["item"], 0) - loan["qty"]
return out
# report.py: formatting only
def format_report(remaining):
return "\n".join(f"{item:<9}{qty:>3}{' OVERDRAWN' if qty < 0 else ''}"
for item, qty in sorted(remaining.items()))
loans_csv = "item,qty\nTripods,3\nlamps,many\nlenses,2\ntripods,6\n"
loans, problems = parse_loans(loans_csv)
print(format_report(remaining_stock({"tripods": 8, "lamps": 6, "lenses": 4}, loans)))
print(problems)lamps 6
lenses 2
tripods -1 OVERDRAWN
["line 3: invalid literal for int() with base 10: 'many'"]Line-by-line explanation
- Lines 4 to 14Parsing normalises names, converts quantities and records bad rows with line numbers.
- Lines 17 to 21The calculation works on clean Python data and knows nothing about CSV.
- Lines 24 to 26Formatting flags negative stock rather than hiding it, which is a business rule worth testing.
Common Intermediate Python Projects Mistakes
Mixing file access into the calculation
def remaining_stock(path):
with open(path) as f: # parsing, calculation and output in one place
...
print(total)def remaining_stock(stock, loans): # pure: easy to test and reuse
...
return remainingOverwriting the only data file directly
with open("loans.json", "w") as f: # a crash here leaves an empty file
json.dump(data, f)tmp = Path("loans.tmp")
tmp.write_text(json.dumps(data), encoding="utf-8")
os.replace(tmp, "loans.json")Intermediate Python Projects in Real Projects
Intermediate projects and real work
This is the shape of most internal tools and automation scripts in organisations: import a spreadsheet export, apply business rules, produce a report or update another system, log what happened and run on a schedule. Projects structured this way are easy for colleagues to review and extend.
For a portfolio, one well-structured intermediate project with tests, a README and sample data is more convincing than several single-file scripts, because it shows the habits teams rely on: separation of concerns, safe data handling and reproducible set-up.
Intermediate Python Projects FAQ
What makes a Python project intermediate rather than beginner?
Structure rather than syntax: several modules with separate responsibilities, data stored in files or a database with a defined format, error handling for bad input, automated tests and documented set-up that works on another machine.
What are good intermediate Python project ideas?
An expense or inventory report from CSV files, a data importer with validation and error reports, a command-line tool for organising files, a small Flask API backed by SQLite, a web scraper that respects robots.txt and saves to JSON, or an API client with logging and retries.
How should I structure a Python project with several files?
Group code in a package folder with modules for parsing, core logic, storage and the command-line entry point, plus a tests/ folder, pyproject.toml, README.md and .gitignore. Keep core logic free of file and network access so it's easy to test.
Do intermediate projects need tests?
Yes. Tests are what let you change and extend a project confidently, and they show others that it works. Start with the pure logic, then add tests for parsing and storage using temporary files.
How do I check that someone else can run my project?
Clone it into a fresh folder, create a new virtual environment and follow your README exactly. If you have to do anything not written down, add it. Running the tests in continuous integration automates this check.
Intermediate Python Projects Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which responsibility should be separated from stock arithmetic?
Which review concern is valid for intermediate python projects?
Intermediate Python Projects Quiz
2 questions to check what you learned.
Which responsibility should be separated from stock arithmetic?
- The meaning of subtraction
- External file parsing
- Python ignores the distinction
Which statement correctly describes testing collaborators?
- Test pure logic directly and control external file dependencies with temporary data.
- Only the example output matters, not the rule
- Every operation has the same behavior