This is a reference directory for Python projects that will live in other directories on disk. Machine-wide settings (uv config, shell guardrail, Claude permissions) live at user-scope, so they apply wherever uv init runs. Language conventions do not: they belong in each project's own CLAUDE.md, from the template in PROJECT.md, so that non-Python sessions are not made to read Python rules.
Fresh macOS (Apple Silicon) environment with no uv, no pyenv, and no Python tooling beyond whatever python3 the machine already ships. That might be Apple's Command Line Tools stub at /usr/bin/python3, a python.org build at /usr/local/bin/python3, or a Homebrew build at /opt/homebrew/bin/python3. Which one it is does not matter, because nothing below touches it. Goal: stand up a modern uv-centric Python dev machine where every project is isolated in a virtual environment, with guardrails strong enough that accidentally polluting the system/user site-packages becomes an error.
Decisions:
- Install uv via Homebrew
- Default Python for new projects: 3.14 (uv-managed)
- Strong venv enforcement:
PIP_REQUIRE_VIRTUALENV=1+ uv defaults - No globally-installed uv tools — add
ruff/pytest/etc. per project viauv add --dev
brew install uv
Verify: uv --version. uv installs to /opt/homebrew/bin/uv, already on PATH via the existing brew shellenv line in ~/.zprofile.
uv python install 3.14
uv python pin --global 3.14
uv python pin --global writes ~/.config/uv/.python-version, making 3.14 the default for any new project without its own .python-version. The system python3 is left untouched for ad-hoc use; uv-managed builds live under ~/.local/share/uv/python/, with a ~/.local/bin/python3.14 shim, and are only used by uv workflows.
uv downloads these builds from GitHub release assets, not from PyPI. On a TLS-inspecting network this is usually the first step that fails.
# Prefer uv-managed Pythons; don't silently use a random system Python
python-preference = "only-managed"
# Auto-download a pinned Python version if missing
python-downloads = "automatic"
# Compile .pyc files on install — faster first run, small disk cost
compile-bytecode = trueuv pip already refuses to install outside a venv by default (requires an explicit --system flag to override), so no extra config is needed for that path.
Append:
export PIP_REQUIRE_VIRTUALENV=1
Makes any plain pip install … (any non-uv pip on PATH) fail with "Could not find an activated virtualenv" unless a venv is active. Paired with step 3, both code paths refuse to install globally.
Edit ~/.claude/settings.json to merge a permissions.allow block, preserving every existing key (model, effortLevel, voiceEnabled, voice, enabledPlugins, statusLine, and so on) and every allow entry already listed:
{
"permissions": {
"allow": [
"Bash(uv --version)",
"Bash(uv init:*)",
"Bash(uv add:*)",
"Bash(uv remove:*)",
"Bash(uv sync:*)",
"Bash(uv lock:*)",
"Bash(uv run:*)",
"Bash(uv python:*)",
"Bash(uv tree:*)",
"Bash(uv venv:*)",
"Bash(uv tool list)"
]
}
}uv tool install / uv tool uninstall are deliberately not auto-allowed — they mutate global state and deserve a prompt.
Opinionated Python-stack extensions. Install from the terminal:
code --install-extension ms-python.python
code --install-extension ms-python.vscode-pylance
code --install-extension ms-python.debugpy
code --install-extension ms-python.vscode-python-envs
code --install-extension charliermarsh.ruff
code --install-extension astral-sh.ty
code --install-extension tamasfe.even-better-toml
code --install-extension ms-toolsai.jupyter
astral-sh.ty is Astral's preview type-checker extension — runs as a language server alongside Pylance.
Merge into ~/Library/Application Support/Code/User/settings.json. VS Code does not create this file until you change a setting through the UI, so on a fresh machine it will be missing; create it with these blocks wrapped in a top-level { … }. A running VS Code picks the change up live, no restart needed.
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
},
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml"
}source.fixAll.ruffapplies ruff autofixes on save.source.organizeImports.ruffsorts and prunes imports on save (replaces isort).
cd ~/dev/somewhere
uv init myproj # library-style layout; use `uv init --app myproj` for a CLI/app
cd myproj
uv add requests # runtime dep
uv add --dev ruff pytest # dev deps
uv run pytest # run tests
uv run python -m myproj # run the app
git clone <repo> && cd <repo>
uv sync # materializes .venv from uv.lock
uv run <cmd> # no manual `source .venv/bin/activate` needed
uv python pin 3.13
uv sync # rebuilds .venv against the new interpreter
uv run --with httpx python - <<'PY'
import httpx
print(httpx.get("https://example.com").status_code)
PY
uv lock --upgrade-package requests
uv sync
uv lock --upgrade
uv sync
uv tool install ruff # puts `ruff` on PATH at ~/.local/bin/ruff
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff
- New:
~/.config/uv/uv.toml - New:
/Users/etoews/dev/etoews/python/MAC.md(this file) - Edit:
~/.zprofile(append one export) - Edit:
~/.claude/settings.json(addpermissions.allow) - Edit:
~/Library/Application Support/Code/User/settings.json(add[python]and[toml]format-on-save blocks; create the file if absent) - Install: VS Code extensions listed in step 6
- Unchanged: the pre-existing system
python3,~/.claude/CLAUDE.md,/Users/etoews/dev/etoews/python/.claude/settings.local.json
~/.zprofile and ~/.claude/settings.json may each be a symlink into a dotfiles repo. Edit the symlink target, on a branch, so the machine setup stays version-controlled. Check with ls -la before writing.
Run from a fresh login shell so ~/.zprofile is re-sourced:
uv --version— prints a version.uv python list --only-installed— shows a uv-managed 3.14.x.- Venv guardrail:
pip3 install --dry-run requests, using whichever non-uvpip3is on PATH, should fail with "Could not find an activated virtualenv". - End-to-end smoke test in a throwaway dir outside this one:
should create
cd /tmp && uv init demo && cd demo uv add requests uv run python -c "import requests; print(requests.__version__)" rm -rf /tmp/demodemo/.venv/, writepyproject.toml+uv.lock, print a version. uv pip install --dry-run requestsrun outside any project dir, with no venv active. Should fail with "No virtual environment found; runuv venv…". That is uv's built-in default, not somethinguv.tomlconfigures, which is why step 3 needs no extra setting for this path.uv init demo && cd demo, then confirm a fresh Claude session picks up the conventions once you drop in theCLAUDE.mdtemplate from PROJECT.md. Nothing loads them automatically outside a project.