Expert · Lesson 89 of 94

Reading real-world codebases

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

Concept

Professional programming is mostly reading. You join a project with tens of thousands of lines, a bug report, and a day. The skill is not understanding everything; it is finding the ten percent that matters for the task and trusting the rest until proven otherwise. That skill has a method, and it uses tools you already have: the project's own layout conventions, grep, the test suite, Git history, and a debugger.

Reading real-world codebases: README + pyproject: what is it, how does it run → entry points: scripts, main, app factory → structure: packages, one folder per concern → tests: the behavior spelled out → git log: why things are the way they are → trace one path with grep + breakpoint()
Reading real-world codebases · Figure 1 · Open for full size

Start outside. The README says what the project does and how to run it. pyproject.toml names the entry points, [project.scripts], the dependencies, which tell you the frameworks in play, and the tool settings, which tell you the team's standards. The folder layout of src tells you the concerns: api, models, services, cli. Run the tests before reading any code; a green suite tells you the environment works, and the test names are a table of contents of what the code promises. git log --oneline | head and git shortlog -sn tell you how active the project is and who knows it.

Question Where to look
what does it do, how do I run it? README, pyproject.toml scripts, Dockerfile, compose.yaml
where does execution start? [project.scripts], __main__.py, if __name__ == "__main__", app factories
how is it organized? src/<pkg>/ folder names, __init__.py exports
what does it promise? test names; run pytest -v
why is this line here? git blame, then git show on the commit; the PR discussion
who calls this function? grep -rn "name(" src tests or the editor's references
what does this object hold at runtime? breakpoint() in a test; vars(obj), type(obj).__mro__

Then go inside along one path. Pick a single behavior, one command or one request, find where it enters, and follow it with search: the function name, then the names it calls, reading only the bodies on the path and skipping every branch that does not fire for your case. When a call disappears into a framework or a decorator, put a breakpoint() in a test that exercises the path and inspect the live objects: vars(), type(), __mro__, inspect.getsource. The debugger answers "what is this really" faster than any amount of reading.

Knowing what to skip is the speed. Skip configuration boilerplate, skip generated code, skip tests that are not about your path, skip any function whose name and docstring you believe until something contradicts them. Read signatures and docstrings before bodies. Read the tests for a module before the module. Draw the three or four objects involved and the arrows between them on paper; if you cannot, you have not found the path yet.

The first change in a new codebase should be small, safe, and complete: a test that documents behavior you had to discover, a docstring where one was missing, a type hint that clarifies. It proves you can run, change, test, and submit in this project, it leaves a trace for the next reader, and it earns the trust for the larger change. Reading and contributing are the same skill at different depths, and both improve by doing them on real projects, which the closing lessons will point you toward.

Reading real-world codebases: pick one behavior → find the entry: grep the command or route → follow calls on the path only; skip the rest → breakpoint() in a test; inspect live objects → draw the objects and arrows → make one small safe change with a test
Reading real-world codebases · Figure 2 · Open for full size

Code

Orientation, automated: a script that reads a project's pyproject.toml, entry points, package layout, and test names, which is the first five minutes of any new codebase.

# orient.py
import tomllib
from pathlib import Path

root = Path("sample_project")
(root / "src/shop/api").mkdir(parents=True, exist_ok=True)
(root / "src/shop/models").mkdir(exist_ok=True)
(root / "tests").mkdir(exist_ok=True)
(root / "pyproject.toml").write_text('''
[project]
name = "shop"
version = "2.3.0"
dependencies = ["httpx>=0.28", "sqlalchemy>=2.0"]
[project.scripts]
shop = "shop.cli:main"
shop-worker = "shop.worker:run"
''')
(root / "src/shop/__init__.py").write_text("")
(root / "src/shop/cli.py").write_text("def main():\n    return 0\n")
(root / "src/shop/worker.py").write_text("def run():\n    return 0\n")
(root / "src/shop/api/routes.py").write_text("def get_order(order_id):\n    return {}\n")
(root / "src/shop/models/order.py").write_text("class Order:\n    pass\n")
(root / "tests/test_orders.py").write_text(
    "def test_order_total_includes_tax(): pass\ndef test_order_refund_restores_stock(): pass\n"
)
(root / "tests/test_cli.py").write_text("def test_cli_exit_code_zero(): pass\n")

meta = tomllib.loads((root / "pyproject.toml").read_text())["project"]
print("project:", meta["name"], meta["version"], "| depends on:", [d.split(">=")[0] for d in meta["dependencies"]])
print("entry points:", meta["scripts"])
print("concerns:", sorted(p.name for p in (root / "src/shop").iterdir() if p.is_dir()))
tests = [ln.split("def ")[1].split("(")[0]
         for f in (root / "tests").glob("test_*.py")
         for ln in f.read_text().splitlines() if ln.startswith("def test_")]
print("promises:", sorted(t.removeprefix("test_").replace("_", " ") for t in tests))
Output
project: shop 2.3.0 | depends on: ['httpx', 'sqlalchemy']
entry points: {'shop': 'shop.cli:main', 'shop-worker': 'shop.worker:run'}
concerns: ['api', 'models']
promises: ['cli exit code zero', 'order refund restores stock', 'order total includes tax']

Tracing a path with search. Starting from a command name, find the entry, then each callee, reading only what is on the path. A small grep helper stands in for your editor's "find references".

# trace.py
import re
from pathlib import Path

root = Path("trace_project")
root.mkdir(exist_ok=True)
(root / "cli.py").write_text("from service import refund\n\ndef main(argv):\n    return refund(int(argv[0]))\n")
(root / "service.py").write_text(
    "from repo import load_order, save_order\nfrom stock import restore\n\n"
    "def refund(order_id):\n    order = load_order(order_id)\n    restore(order.items)\n"
    "    order.status = 'refunded'\n    save_order(order)\n    return order\n"
)
(root / "repo.py").write_text("def load_order(order_id): ...\ndef save_order(order): ...\n")
(root / "stock.py").write_text("def restore(items): ...\n")
(root / "unrelated.py").write_text("def refund_policy_text(): return 'not the same refund'\n")


def definitions(name):
    return [f"{p.name}:{i}" for p in root.glob("*.py") for i, ln in enumerate(p.read_text().splitlines(), 1)
            if re.match(rf"^def {name}\(", ln)]


def callees(name):
    src = next(p for p in root.glob("*.py") if re.search(rf"^def {name}\(", p.read_text(), re.M))
    body = src.read_text().split(f"def {name}(")[1].split("\ndef ")[0]
    return sorted(set(re.findall(r"\b([a-z_]+)\(", body)) - {name, "int"})


print("entry:", definitions("main"))
path = ["main"]
frontier = ["refund"]
while frontier:
    fn = frontier.pop(0)
    path.append(f"{fn} -> {callees(fn)}")
    frontier += [c for c in callees(fn) if definitions(c) and callees(c)]
for step in path:
    print(" ", step)
print("skipped:", [p.name for p in root.glob("*.py")
                   if "refund" in p.read_text() and p.name not in ("cli.py", "service.py")])
Output
entry: ['cli.py:3']
  main
  refund -> ['load_order', 'restore', 'save_order']
skipped: ['unrelated.py']

Inspecting live objects when reading is not enough: what an object holds, what class it really is, where a method was defined, and the source of a function you did not write.

# introspect.py
import inspect
from dataclasses import dataclass, field


class Base:
    def describe(self):
        return f"{type(self).__name__} with {len(vars(self))} fields"


@dataclass
class Order(Base):
    id: int
    items: list = field(default_factory=list)
    status: str = "open"


order = Order(7, ["pen"])
print(vars(order))
print(type(order).__name__, [c.__name__ for c in type(order).__mro__])
print("describe defined in:", inspect.getsourcefile(Order.describe).split("/")[-1], "class", Order.describe.__qualname__.split(".")[0])
print(inspect.signature(Order))
print(inspect.getsource(Base.describe).strip().splitlines()[0])
Output
{'id': 7, 'items': ['pen'], 'status': 'open'}
Order ['Order', 'Base', 'object']
describe defined in: introspect.py class Base
(id: int, items: list = <factory>, status: str = 'open') -> None
def describe(self):

Git as a reading tool: who changed this, when, and why. A tiny repository with two commits shows blame and log -S answering "where did this line come from".

# git_archaeology.sh
rm -rf hist && mkdir hist && cd hist && git init -q -b main
git config user.name "Ada" && git config user.email "ada@example.com"
printf 'RATE = 0.20\n' > tax.py && git add tax.py && git commit -q -m "Add VAT rate"
printf 'RATE = 0.19  # reduced 2026-07, see ticket #412\n' > tax.py && git commit -q -am "Lower VAT to 19% per new regulation

Finance asked for this on 2026-07-01; ticket #412 has the source."
git blame -s tax.py | sed -E 's/^[0-9a-f]+ +/<hash> /'
echo "--- why:"
git log -1 --format=%B $(git blame -l -L 1,1 tax.py | cut -d' ' -f1) | head -1
echo "--- when RATE changed:"
git log --oneline -S"0.19" | sed -E 's/^[0-9a-f]+ /<hash> /'
Output
<hash> 1) RATE = 0.19  # reduced 2026-07, see ticket #412
--- why:
Lower VAT to 19% per new regulation
--- when RATE changed:
<hash> Lower VAT to 19% per new regulation

Try it yourself

Clone a small, active open-source Python project you use, such as a CLI tool or a library from this course's dependencies. Spend five minutes on orientation only: README, pyproject.toml, layout, pytest -v. Pick one command or function, trace it with your editor's search to the point where it does its real work, and put a breakpoint() in a test on that path to inspect the live objects. Write down the three or four objects involved. Then find one function without a docstring on that path and write one, as your first small change.

Common mistakes

What's next

You can enter any codebase. Next you will finish your own: the capstone that ships the expense library as a complete, production-ready package.

Course overview