Python-in-the-Shell
Virtual environments, uv, pipx, and running Python scripts on Linux - how operators and developers invoke Python without breaking system packages.
Recipe
cd billing-api
uv sync
uv run pytest -q
uv run python -m billing.cli --help
pipx install ruffWhen to reach for this:
- First clone setup on Linux dev machine or CI runner
- Running one-off management commands against staging DB
- Installing global CLIs (ruff, httpie, copier) safely
- Cron/systemd invoking application modules
Working Example
#!/usr/bin/env bash
set -euo pipefail
cd /opt/billing-api
export PATH="$HOME/.local/bin:$PATH"
# Deploy user uses locked env
uv sync --frozen --no-dev
uv run alembic upgrade head
uv run python -m billing.worker --once
# Global tool without project pollution
pipx ensurepath
pipx install 'httpie>=3.0'#!/usr/bin/env python3
"""Shebang script - prefer uv run in production."""
import sys
def main() -> int:
print("ok")
return 0
if __name__ == "__main__":
sys.exit(main())What this demonstrates:
uv sync --frozenmatches CI lockfile exactly-m modulerespects package importspipxisolates CLI tools- Portable shebang for scripts in
bin/
Deep Dive
How It Works
- venv - Isolated
site-packagesand interpreter symlinks. - uv - Fast resolver/installer;
uv runauto-uses project venv. - pipx - venv per CLI tool linked to
~/.local/bin. - system Python - Distro Python for
apttools only - not app deps.
Invocation Patterns
| Pattern | Use |
|---|---|
uv run pytest | Project-bound commands |
uv tool run ruff | Ephemeral tool without install |
pipx run cowsay | One-shot CLI |
python -m http.server | Stdlib quick server (dev only) |
Python Notes
# Which python am I using?
uv run python -c "import sys; print(sys.executable)"
# PEP 723 script deps (uv)
# /// script
# requires-python = ">=3.14"
# dependencies = ["httpx"]
# ///Gotchas
sudo pip install- Breaks system package manager. Fix: venv, uv, or pipx only.- Wrong venv activated in cron - Uses system python missing deps. Fix: absolute path to
uv runin crontab. - Shebang hardcoded /usr/bin/python3.11 - Breaks on 3.14 upgrade. Fix:
env python3+ documented version pin in README. - Mixing poetry and uv - Two lockfiles diverge. Fix: one toolchain per repo.
.venvnot in .gitignore - Accidental commit. Fix: gitignore + CI creates fresh venv.- pipx upgrade forgotten - Stale global ruff. Fix:
pipx upgrade-allquarterly.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
poetry run | Poetry-standardized repos | Team standardized on uv |
conda | Scientific stack with binary deps | Slim API containers |
docker run | Exact prod parity on laptop | Fast edit-test loop |
pyenv | Multiple Python versions locally | Container pins single version |
FAQs
uv or poetry for new repos?
This cookbook pins uv 0.6+ - use uv sync and uv.lock unless org ADR says otherwise.
Where does pipx install?
~/.local/pipx/venvs with shims in ~/.local/bin - ensure PATH in shell profile.
systemd ExecStart with uv?
ExecStart=/opt/billing-api/.venv/bin/uvicorn app.main:app or uv run wrapper script.
PEP 668 externally managed?
Debian/Ubuntu block system pip - use uv venv, expected on modern distros.
Run script without install?
uv run script.py with inline metadata deps (PEP 723).
Editable install?
uv sync with editable local package in pyproject.toml - see environments-packaging doc.
Multiple projects on server?
Separate /opt/<app> dirs each with own .venv - never shared site-packages.
python3.14 not on Ubuntu LTS?
Use uv python install 3.14 or official deadsnakes PPA per org policy.
Cron mail spam on error?
Redirect to logger: ... 2>&1 | logger -t billing-worker.
pipx override library version?
pipx inject package dependency==x.y when tool needs plugin.
Related
- uv - The Fast Toolchain - uv depth
- Managing Python Versions - version pins
- Environment Setup Checklist - onboarding
- Scheduling and Cron - cron patterns
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.