CoursePython · Environment, Tooling, and Version Control · part 7 of 79
Part 7 · Environment, Tooling, and Version Control

Summary

6 min read·9 Sept 2026

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 install into a uv-managed project
  • Creating src/__init__.py
  • Using os.environ.get() for a required secret and getting None downstream
  • 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 .env into a container image


Knowledge check

1. A teammate's install succeeded, but the project crashes on startup with an error inside a library. Both of you installed from the same requirements.txt. What is the most likely cause, and what would have prevented it?

2. Why does the src/ layout catch packaging mistakes that a flat layout misses?

3. You see this in a traceback: "During handling of the above exception, another exception occurred". Which of the two tracebacks describes the problem you should fix first?

4. What must be true of every commit for git bisect to be useful, and why?

5. In a Dockerfile, why is COPY pyproject.toml uv.lock ./ placed before COPY src/ ./src/?

6. Your settings class declares api_key: str = "". What failure mode does that default create?

Answers

1. A transitive dependency resolved to a different version. A requirements.txt produced by pip freeze typically pins direct dependencies but does not guarantee the full graph, so a dependency of a dependency moved between the two installs. A lockfile recording every package with exact versions and hashes would have prevented it, and uv sync --locked in CI would have caught a stale lockfile.

2. Python puts the current directory on the import path, so in a flat layout import recipe_extractor finds the source folder whether or not the package is properly installed. The src/ layout removes that possibility, because the package is only importable once installed. Missing files, incomplete build configuration, and packaging errors therefore surface immediately rather than after release.

3. The first, meaning the upper one. That connector means a second exception occurred inside an except block while the first was being handled, which usually indicates a bug in your error handling that is now masking the real failure. Read the original error, then fix the handler.

4. Every commit must build and run, so that it can be judged good or bad. A commit in a broken intermediate state cannot be tested and must be skipped, and enough skips make the binary search unable to isolate the change. This is the practical reason for atomic commits.

5. Docker caches layers and reuses them when their inputs have not changed. Dependencies change rarely while source changes constantly, so installing dependencies in an earlier layer means an ordinary code edit rebuilds only the final layers rather than reinstalling every package. Reversing the order makes every build a full reinstall.

6. It converts a loud, immediate startup failure into a silent one. With no default, a missing API_KEY stops the program at startup with a precise message. With "", the service starts, appears healthy, and then fails on every API call with an authentication error that gives no hint about the actual cause.


Glossary

Atomic commit. A commit containing one logical change that leaves the codebase in a working state.

Container. A packaged, runnable environment including the operating system layer, system libraries, and application, producing identical behaviour across machines.

Dependency group. A named set of dependencies, such as dev, that can be installed or omitted independently of the runtime requirements.

Environment variable. A named value supplied to a process by the operating system, used to configure behaviour without changing code.

Fail fast. Validating inputs and configuration at startup so that problems surface immediately rather than mid-execution.

Frame. One entry in a traceback, representing a single function call in the chain that led to an error. The innermost frame is where the failure occurred.

Hunk. A contiguous block of changed lines in a diff, shown with surrounding context.

Layer. A cached step in a container image build. Unchanged layers are reused on rebuild.

Linter. A tool that inspects source code for errors and style problems without executing it.

Lockfile. A generated file recording the exact resolved version and file hash of every dependency, direct and transitive.

Rebase. Replaying commits onto a different base commit, producing new commit objects and a linear history.

Transitive dependency. A package required by one of your dependencies rather than by your project directly.

Type checker. A tool that verifies type annotations for consistency without running the program.

Virtual environment. An isolated directory containing one project's installed packages.


Where this leads

Everything that follows assumes this foundation. When the next module asks you to model data as typed records, mypy is what makes those types more than decoration. When later modules involve API keys and provider configuration, the settings pattern from Lesson 2 is what they use. When a pipeline crashes at record 800,000 overnight, the traceback skills from Lesson 4 decide whether you find the cause in five minutes or an afternoon.

The next module moves from tooling to problem solving: how to decompose a requirement before writing code, how to choose a data structure by the way you will read it rather than by habit, and how to turn loose dictionaries into typed records that stay trustworthy as they move through a system.

Before continuing, set up one real project with this structure. Reading about a lockfile teaches you very little. Deleting .venv, running uv sync, and watching the environment rebuild in seconds teaches you most of it.