Summary
Your project's behaviour should be a property of the repository, not of the machine it happens to be sitting on. Each tool in this module closes one gap between those two things.
uv manages the interpreter, the environment, and dependencies. pyproject.toml declares what the project needs and configures everything else. uv.lock records what was actually installed, with hashes, so an install is reproducible rather than merely successful. The src/ layout forces your tests to exercise the installed package rather than the source folder sitting next to them.
Environment variables keep secrets out of git, and a validated settings object turns a missing or malformed value into a clear startup failure instead of a confusing runtime one.
Ruff catches style problems and a useful class of bugs in under a second. mypy catches type errors without running the code. Pre-commit makes both automatic, and CI makes them unavoidable.
Tracebacks are read from the bottom up, looking for the innermost frame you own. Most apparent library bugs are your inputs, and six checks will tell you which.
Atomic commits are what make git's investigative tools work, and without them bisect and revert are unusable. Merge preserves history, rebase linearises it, and you never rebase what others have pulled. Bisect turns finding a regression from a day of reading into ten minutes of binary search.
Containers capture what a lockfile cannot: the operating system, system libraries, and drivers.
Key takeaways
- An install that succeeds is not the same as an environment that matches
- Specification and lockfile are different things. You write one, a tool generates the other, and both are committed
- Secrets never enter a tracked file, and if one does, rotate it before anything else
- Validate configuration at startup, not on first use
- Fast tools change behaviour, because slow tools do not get run
- Read tracebacks from the bottom and find the innermost frame you control
- Assume your bug before the library's, and verify in a fixed order
- Small commits are not tidiness, they are what makes regressions findable
Common mistakes to remember
- Committing
.venv,.env, or a large model file - Mixing
pip installinto a uv-managed project - Creating
src/__init__.py - Using
os.environ.get()for a required secret and gettingNonedownstream - Giving a secret a default value in a settings class
- Ignoring linter output until you stop reading it entirely
- Silencing mypy globally instead of per module
- Rebasing a branch someone else has pulled
- Committing a file that still contains conflict markers
- Copying source before dependencies in a Dockerfile
- Baking
.envinto a container image