Packaging, Dependencies and Environments
Manage Python dependencies like a professional: virtual environments, pyproject.toml, version specifiers, lock files for reproducible builds, and the modern tool landscape from pip-tools to Poetry and uv.
🎯 What you'll learn
- Virtual environments and why they matter
- pyproject.toml and project metadata
- Version specifiers and compatible releases
- Lock files, wheels and publishing
📚 Before you start
1Virtual Environments
Every project gets its own isolated environment so dependency versions don't clash:
python -m venv .venv && source .venv/bin/activate # built in
uv venv && uv pip install -r requirements.txt # uv: very fast installer and resolverNever install project dependencies into the system Python. In containers, the image is the environment (but a venv inside the image still helps with multi-stage builds).
2pyproject.toml
The modern single source of project metadata (PEP 621):
[project]
name = "orders-service"
version = "1.4.0"
requires-python = ">=3.11"
dependencies = ["fastapi>=0.110,<1", "sqlalchemy~=2.0", "httpx>=0.27"]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
[project.scripts]
orders = "orders.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"Tool configuration (ruff, mypy, pytest) also lives here.
3Version Specifiers
| Specifier | Meaning |
|---|---|
==2.1.3 | Exactly this version |
>=2.1,<3 | At least 2.1, below 3 |
~=2.1 | Compatible release: >=2.1, ==2.* |
~=2.1.3 | >=2.1.3, ==2.1.* |
!=2.1.5 | Exclude a broken release |
Libraries should use ranges (so they compose with other libraries); applications should lock exact versions for reproducible builds.
4Locking
A lock file pins the entire dependency tree (including transitive dependencies) with hashes: uv lock / uv.lock, poetry.lock, pip-compile → requirements.txt with == pins. CI and Docker install from the lock (uv sync --frozen, pip install --require-hashes -r requirements.txt). Update deliberately (Dependabot/Renovate PRs) and run tests.
5Tools at a Glance
| Tool | Role |
|---|---|
| pip + venv | Baseline installer and environments |
| pip-tools | Compile pinned requirements from loose ones |
| Poetry | Dependency management, locking, building, publishing |
| uv | Fast all-in-one: venvs, resolver, lock files, Python versions, tools |
pipx / uv tool | Install CLI apps in isolated environments |
pyenv / uv python | Manage multiple Python versions |
6Building and Publishing
python -m build produces a source distribution (sdist) and a wheel (a prebuilt zip installed without running setup code). Platform-specific wheels contain compiled extensions. Publish with twine upload or uv publish, ideally via trusted publishing from CI.
⚠️ Common Pitfalls
- Installing into the global interpreter or sharing one venv between projects.
- Unpinned application dependencies: a transitive release breaks production.
- Pinning exact versions in libraries: impossible to install alongside others.
- Committing the
.venvdirectory. - Different Python versions locally vs in CI/production.
📋 Quick Reference
one venv per project (python -m venv / uv venv) · pyproject.toml = metadata + deps + tool config
libraries: ranges (>=,<, ~=) · apps: lock everything (uv.lock / poetry.lock / pip-compile) with hashes
~=2.1 means >=2.1,==2.* · install from the lock in CI and Docker · wheels > sdists
uv / Poetry / pip-tools · pipx for CLIs · same Python version everywhere🧠 Key takeaways
- One virtual environment per project; never the system Python.
- Libraries declare ranges; applications lock the full tree with hashes.
~=2.1means>=2.1, ==2.*: a compatible release.- Install from the lock in CI and Docker; keep Python versions consistent.
🎤 Interview Q&A
Q1Why use virtual environments?core▾
To isolate each project's dependencies and Python version from others and from the system interpreter, preventing version conflicts and making environments reproducible.
- In Docker? The image isolates, but a venv still helps multi-stage builds.
Q2How do you make Python builds reproducible?core▾
Declare dependencies in pyproject.toml, generate a lock file of the full resolved tree with hashes (uv.lock, poetry.lock, pip-compile), install from it in CI/Docker with frozen/require-hashes options, and pin the Python version.
- How do you update? Automated PRs (Renovate/Dependabot) that regenerate the lock and run tests.
Q3Libraries vs applications: how should dependencies differ?core▾
Libraries specify compatible ranges so they can coexist with other libraries in users' environments; applications lock exact versions for deterministic deployments.
- Upper bounds in libraries? Debated; avoid overly tight caps.
Q4What does uv bring?senior▾
A very fast Rust-based tool replacing pip, pip-tools, virtualenv, pipx and pyenv functions: resolution, lock files, venv management, Python installs, and running tools.
- Compatible with pip?
uv pipoffers a pip-compatible interface.
Q5What's the difference between an sdist and a wheel?core▾
An sdist is source code that may need building (compiling extensions) at install time; a wheel is a prebuilt archive installed by unpacking, faster and without build tools. Platform wheels include compiled binaries.
- manylinux? Wheel tags for Linux binaries compatible across distributions.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.
~=2.1 allow?