Python

1. What's in the catalogue

Five published lines, 3.10.21 through 3.14.7, floating with security updates; pin with @3.10@3.14. As of catalogue 2026.35.22 (2026-08-25):

ToolCapsRequiresNotes
pythonrwno network by default; uv provider available for env builds
pipnet, rwpythonplain pip, single requirements.txt file
uvnet, rwpythonlockfile provider; also the default for env builds (see below)
flake8ro[email protected]style/logic checker, no writes
mypyrw[email protected]plus dmypy (daemon), stubgen, stubtest
pylintropythonplus the pyreverse binary
blackrw[email protected]formatter
banditrw[email protected]security linter; bandit-baseline, bandit-config-generator
pytestrwpythontest runner

Declared, not yet runnable: ruff, jupyter, ipython, poetry. Their images are in the signed payload but no tool is bound, so kapsl --info ruff reports unknown tool. Don't write recipes against them until they publish.

kapsl --info python
   LINES        RESOLVES TO
 ▸ @latest      3.14.7             in use
   @3.14        3.14.7
   @3.13        3.13.15
   @3.12        3.12.14
   @3.11        3.11.16
   @3.10        3.10.21
                lines float — kapsl carries security updates onto them
                pin one with python@<version> · a digest never moves

   CAPS          rw

2. The five-minute version

kapsl python --version
Python 3.14.7

No Python on the host, no install, nothing to uninstall. The first run pulls the image and scans it; warm runs are sub-second.

Run a script:

kapsl python script.py

The inline package form — python:pyyaml builds a one-shot environment with that package, scanned, then runs:

kapsl python:pyyaml -c "import yaml; print('pyyaml', yaml.__version__)"
pyyaml 6.0.3

The file-based form — a pinned requirements.txt next to the script:

kapsl -e @pip:requirements.txt python script.py
requests 2.34.2

Both environment forms are described in Always current; the -e file is content-hashed, so the built environment image is cached and reused unchanged across runs and machines until the file changes.

3. The environment story: why the default is uv, not pip

python is the only major ecosystem in the catalogue whose default package provider is not its own native manager. The inline form kapsl python:… and the bare -e dispatch route to uv; -e @pip:requirements.txt stays plain pip, exactly as asked.

The reason is lockfiles, not speed. Every other ecosystem records what it resolved, so an unpinned manifest is still reproducible. Plain pip produces no resolved set at all, and requirements.txt is a manifest, not a lock. uv can emit one, so routing the default through uv is what lets Python join the rest of the design instead of being permanently exempt from it. If you want pip's exact behaviour — no resolver changes, no lockfile — name it: kapsl -e @pip:requirements.txt python script.py works exactly as pip does, and -e @uv:pyproject.toml uses uv directly.

Two concrete consequences:

  • Package rules can grant capabilities. The catalogue carries package_rules entries for requests, flask, django, fastapi, … — when a built environment contains one of those packages, the run gets net automatically. A ro Python container that imports requests and then fails on the socket would be a confusing failure, so the grant is baked in by the catalogue. Your own internal packages can get the same treatment via a project's package_rules (see Project configuration).
  • The environment image is the unit of caching. kapsl -e @pip:requirements.txt builds kapsl-env-python-latest:pip-<hash>; change one pin and you get a new hash, a new image, a new scan. The base python image is untouched.

4. Tooling

The published linter set is the classic five: flake8, mypy, pylint, black, bandit, plus pytest. Each runs in its own signed image against your mounted working tree, so a linter upgrade never touches the runtime and the runtime never touches the linter.

kapsl flake8 app.py
kapsl mypy app.py
kapsl black --check app.py
kapsl bandit -r .
kapsl pytest

ruff — the consolidator that most projects have moved to — is declared, not published: its image is in the catalogue payload with no tool bound yet. Check kapsl --info ruff before relying on it.

The IDE and linters use case covers running these from an editor, the language-server shapes, and why ro linters are the safest tools to run unattended.

5. Project patterns

The examples corpus has the deepest Python coverage of any language. All are runnable as written; each README carries the captured transcript.

  • flask-quickstart — a real Flask app: requirements.txt, -e @pip, the trust prompt on first run, and the server bound to 0.0.0.0 behind a published port. Its README documents the failure modes too: what happens when you forget the port, and why --watch and .env interact badly in a container.
  • django-quickstart — the canonical subcommand_index: 1 case. args[0] is always the literal manage.py, so the overlay keys on the next argument and publishes port 8000 only for runservermigrate and test get no port.
  • fastapi-quickstart — uvicorn with --reload, the package_rules net grant for fastapi doing its job, and the reload-watcher caveats on macOS.
  • pip-python-file — the three levels of integration in one project: bare kapsl python, plus a .kapslrc that sets env_file: requirements.txt so a bare kapsl python behaves like an activated venv, plus a shim. Its README is the best write-up of why kapsl pip install evaporates (it installs into the tool's own ephemeral container, not your environment) and of the first-run trust review.
  • pip-python-lifecycle — the workflow for evolving dependencies: edit the file, re-run, watch the environment image rebuild and rescan.
  • uv-python-e @uv:pyproject.toml end to end, including the lockfile.

The .kapslrc from django-quickstart is the shortest complete example of a real project overlay:

tools:
  python:
    env_file: requirements.txt
    subcommand_index: 1
    subcommands:
      runserver:
        ports: ["8000"]

6. Known limitations

These are real, from the example transcripts:

  • kapsl pip install does not persist. It runs in the pip tool's own container; when the container exits, the install is gone. Persistent dependencies belong in an environment (-e @pip:requirements.txt or -e @uv:pyproject.toml). This trips up everyone coming from pip install muscle memory.
  • The CA bundle. The Python images don't carry the host's CA store. TLS to an internal endpoint signed by a private CA fails with CERTIFICATE_VERIFY_FAILED until you point REQUESTS_CA_BUNDLE/SSL_CERT_FILE at the bundle — the pip-python-file README walks through the exact env-var wiring.
  • --watch/--reload on macOS. File-watch extensions are unreliable on the bind mount; the Flask and FastAPI examples document the failure and the workaround (polling, or running the watcher outside the container). The same examples note that a --watch run and a published port don't compose — the watcher restarts the child outside the port's scope.
  • pip takes one file. The @pip provider installs a single requirements.txt-style file; multi-file or editable installs want the @uv provider.