diff --git a/docs/environments.md b/docs/environments.md new file mode 100644 index 0000000..7206d92 --- /dev/null +++ b/docs/environments.md @@ -0,0 +1,145 @@ +# Virtual environments + +How we keep a project's Python isolated — from the system Python and from every +other project. The rule underneath all of it: **never install into, or upgrade, +the system Python.** The OS depends on it; a project must never touch it. + +!!! danger "Leave the system Python alone" + No `sudo pip install`, no upgrading the system interpreter for a project. If a + project needs a different version or a package, that goes in a **virtual + environment** — never the system one. Breaking system Python can break the OS. + +## Project-based isolation + +Every project runs against its own isolated environment. There are three ways that +happens, depending on where the project runs: + +=== "Local `.venv`" + + A virtualenv living in the repo. Simplest for day-to-day local work. + + ```bash + python -m venv .venv # create it (once) + source .venv/bin/activate # activate for this shell + pip install -r requirements.txt + ``` + + Keep `.venv/` **gitignored** — it's per-machine, never committed. + +=== "Makefile-driven" + + Wrap the venv in a `make` target so every dev (and CI) sets up identically — + no "did you activate it?" drift. + + ```makefile + VENV := .venv + PY := $(VENV)/bin/python + + $(VENV): requirements.txt + python -m venv $(VENV) + $(PY) -m pip install -r requirements.txt + + .PHONY: run + run: $(VENV) + $(PY) -m yourapp + ``` + + `make run` creates the venv if missing, installs deps, and runs — all against + the isolated interpreter, no manual activation. + +=== "Docker" + + The container **is** the isolation — its own filesystem, its own interpreter, + nothing shared with the host. You don't need a `.venv` inside an image; install + straight into the container's Python. + + ```dockerfile + FROM python:3.12-slim + COPY requirements.txt . + RUN pip install --no-cache-dir -r requirements.txt + COPY . . + ``` + + This is how things run in production — see the [Deploy guide](deploy.md) for the + full container standard (uid 1337, mounts, layer caching). + +!!! tip "Which one?" + **Local `.venv`** for quick iteration, **Makefile** when you want repeatable + setup across the team, **Docker** for anything that ships. They're not + exclusive — a project often has a `.venv` for local dev *and* a Dockerfile for + deploy. + +## Local dev with pyenv + +For local work you also need the right **Python version**, not just isolated deps. +We target **Python 3.10+**, and [pyenv](https://github.com/pyenv/pyenv) installs and +switches versions per-project without touching the system Python. + +- **Per-project pinning:** a `.python-version` file in a repo makes pyenv + auto-select that interpreter when you `cd` in — everyone on the project runs the + same one. +- **Pairs with venvs:** combined with `pyenv-virtualenv`, each project gets both an + isolated version *and* isolated deps. + +### Install + +Follow the [official pyenv installation](https://github.com/pyenv/pyenv#installation) +for the installer and build dependencies — no point reproducing it here. Then add +the shell init below to your `~/.zshrc` (or `~/.bashrc`) and restart your shell. + +### Shell init + +Without these lines pyenv's shims aren't on `PATH`, so `pyenv` and +auto-version-switching won't work: + +```bash +# pyenv — Python version management +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init --path)" # (1)! +eval "$(pyenv init -)" # (2)! +eval "$(pyenv virtualenv-init -)" # (3)! +``` + +1. Puts the **shims** dir on `PATH`, so `python` resolves to the pyenv-selected + version instead of the system one. +2. Shell integration — command rehashing and completion. +3. Auto-activates a project's virtualenv on `cd` — **only** if you use + `pyenv-virtualenv`. Drop this line if you don't. + +### Everyday use + +```bash +pyenv install 3.10.14 # install a version (one-time) +pyenv install --list # see available versions +cd +pyenv local 3.10.14 # writes .python-version -> auto-selects here +python --version # confirms the pinned version +``` + +- **`pyenv local `** per project — commit the `.python-version` so the team + matches. +- **`pyenv global `** for your default outside any project. + +## Shell quality-of-life extras + +A couple of optional lines worth having alongside pyenv: + +```bash +# flake8 with our shared config (max line 120) +alias flake8='flake8 --config ~/.config/flake8' + +# local bins on PATH (pip --user installs, npm globals) +export PATH="$HOME/.local/bin:$PATH" +export PATH="$HOME/.npm-global/bin:$PATH" +``` + +- The **flake8 alias** keeps everyone linting with the same config (our 120 + max-line, etc. — see [Standards](standards.md#files-and-style)). +- The **`.local/bin` / npm-global** PATH lines stop "command not found" after a + `pip install --user` or a global npm install. + +!!! note "More shell setup" + This is the Python-env slice. The fuller dev shell setup — the WSL/Windows + clipboard bridges, per-repo git-identity aliases, and the `gl` graph log — is on + the [Workflow](workflow.md#handy-shell-setup) page. diff --git a/docs/index.md b/docs/index.md index f2aa319..e971e65 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,6 +32,13 @@ out of it; examples use placeholders like ``, ``, and `/srv/...`. How we actually work day to day — our Gitea, git habits, and the plan-in-chat / build-in-Claude-Code flow, plus shell setup. +- :material-language-python: __[Virtual environments](environments.md)__ + + --- + + Project-based Python isolation — local `.venv`, Makefile, or Docker — and + local version management with pyenv. + - :material-rocket-launch: __[Deploy](deploy.md)__ --- diff --git a/docs/workflow.md b/docs/workflow.md index a0c1f35..f1076ae 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -171,8 +171,9 @@ works by running it** — don't accept "this should work." [Remote — WSL](https://code.visualstudio.com/docs/remote/wsl), and run [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) as your agent (terminal or the VS Code extension). -- **[pyenv](https://github.com/pyenv/pyenv)** for Python versions — per-project - versions, we target Python **3.10+**. +- **[pyenv](https://github.com/pyenv/pyenv)** for per-project Python versions + (we target **3.10+**) and isolated `.venv`s — full setup on the + [Virtual environments](environments.md) page. - **[flake8](https://flake8.pycqa.org/)** with a shared config — max line length **120** (see the alias below). diff --git a/mkdocs.yml b/mkdocs.yml index bd3507b..c633838 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,4 +54,5 @@ nav: - Libraries: libraries.md - Standards: standards.md - Workflow: workflow.md + - Virtual environments: environments.md - Deploy: deploy.md