# Packaging, Dependencies and Environments

## 1. Virtual Environments

Every project gets its own isolated environment so dependency versions don't clash:

```bash
python -m venv .venv && source .venv/bin/activate    # built in
uv venv && uv pip install -r requirements.txt         # uv: very fast installer and resolver
```

Never 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).

## 2. pyproject.toml

The modern single source of project metadata (PEP 621):

```toml
[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.

## 3. Version 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.

## 4. Locking

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.

## 5. Tools 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 |

## 6. Building 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

1. **Installing into the global interpreter** or sharing one venv between projects.
2. **Unpinned application dependencies**: a transitive release breaks production.
3. **Pinning exact versions in libraries**: impossible to install alongside others.
4. **Committing the `.venv` directory**.
5. **Different Python versions** locally vs in CI/production.

## Quick Reference

```text
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
```
