Skip to content
ocx
install

Use ocx with Python

This page shows the two ways Python and ocx meet. You can call ocx from Python code through the ocx-sdk package. You can also ship a Python application as an ocx package that runs with no pip, uv or virtual environment.

ocx has no ocx python command and does not read a pylock.toml itself. The lock translation lives in a library that ocx-mirror links, so the second route is a publisher task and not an everyday one.

ocx-sdk is a typed Python wrapper over the ocx CLI. Its methods mirror the commands one for one, and it parses only what ocx already validated. It needs Python 3.12 or later and has no runtime dependencies.

Terminal window
uv add ocx-sdk

A script needs three calls. bootstrap.ensure() downloads, verifies and caches a pinned ocx binary. Ocx(exe=...) wraps that binary. ocx.project(path) opens a directory that holds an ocx.toml, and its pull() and exec() methods run ocx pull and ocx exec there.

Use it where a Python test suite, a build script or a notebook needs a pinned tool. The SDK documentation has the full guides:

The SDK shells out to the CLI, so every setting in this section applies to it as well. Run ocx unattended lists the variables, and Read ocx output from a script describes the JSON it parses.

Run a locked Python application as an ocx package

Section titled “Run a locked Python application as an ocx package”

A Python application normally needs an interpreter, pip or uv and a virtual environment on every machine that runs it. A mirror can instead turn its lock into one ocx package that holds every wheel plus a private interpreter. The consumer then runs it like any other package:

Terminal window
ocx package exec dev.ocx.sh/ocx/black:26.5.1 -- black --version

Nothing on the consumer’s machine needs Python, and ocx package exec runs it in a clean environment. The package declares the interpreter as a private dependency, and each console script of the application becomes a package entrypoint.

The translation starts from a PEP 751 pylock.toml. The ocx_python crate runs five steps, and every writer of the shared registry namespace uses the same code so that the artifacts stay byte-compatible.

  1. It parses the lock.
  2. It selects one wheel per package for each target platform, checked against the interpreter version and ABI.
  3. It repacks each wheel into a layer.
  4. It checks that no two wheels write the same file.
  5. It composes the environment package: the layers, the interpreter dependency, the environment variables and the entrypoints.

Selection fails closed. A compiled wheel whose ABI tag does not match the target is an error. So is a lock that names a wheel the platform cannot use. A wheel layer is shared across the applications that use the same wheel.

The mirror tool is ocx-mirror, which has its own repository and documentation. Install it with ocx --global add ocx.sh/ocx/mirror, which provides the ocx-mirror binary. Start from a PEP 751 lock. With a uv project, uv export writes one:

Terminal window
uv export --format pylock.toml --output-file black.pylock.toml

A pylock source in the mirror spec black.yml then names the lock, the interpreter package and the target ABI:

name: black
target:
registry: dev.ocx.sh
repository: ocx/black
source:
type: pylock
path: black.pylock.toml
python:
version: "3.14.6"
abi: cp314
interpreter_package: "ocx.sh/cpython:3.14.6"
wheels:
"linux/amd64+libc.glibc": # empty entry keeps the default wheel filter
platforms:
linux/amd64:
runner: ubuntu-latest

Every wheels key needs a platforms entry for its base os/arch, which names the runner that tests it. The +libc.glibc suffix on a wheels key is not repeated in platforms: platforms keys stay plain.

Run ocx-mirror package check black.yml to see what a sync would push. It is a dry run and prints a table of version and platform pairs with what a real run would do. A total / pushed / skipped / failed summary follows.

Run ocx-mirror package sync black.yml to publish. It prints the same table and summary. A first run that publishes every version ends with a line like 12 total, 12 pushed, 0 skipped, 0 failed, with your own counts. A failed count of 0 means every version reached the registry.

Then run ocx package exec dev.ocx.sh/ocx/black:latest -- black --version to confirm the package runs. The spec may also list tests, as the mirror configuration reference shows.

A pypi source discovers versions from a PyPI-compatible index and derives one lock per version. The mirror configuration reference lists every field, including the wheel filters and the entrypoint selection.

This page does not cover publishing the result. A mirrored package is an ordinary package, so Publish packages and Publisher CI apply to it unchanged.