translocate
translocate moves an installed Python package from where it lives now
to somewhere else. The two destinations it knows about are a wheel file
(for transporting the package around) and another interpreter's
site-packages (for projecting the package into a different environment
without going through the index).
It recovers the package's logical contents from the install's RECORD,
classifies each file by the install scheme it came from, and then
either serializes the result back into a wheel archive or projects it
through a target interpreter's scheme using hardlinks, symlinks, or
copies. The output advertises itself accurately: a regenerated wheel
reïnstalls cleanly but is not bit-identical to the upstream artifact,
and a cloned prefix carries a regenerated RECORD describing the files
at their new locations so pip and uv see the cloned distribution
as a first-class install.
Clone installed system packages into a uv venv
uv venv ~/envs/inference
translocate clone torch numpy Jinja2 \
--target ~/envs/inference \
--link-mode hardlink \
--include-deps \
--smoke-test-imports
Pass one or more distribution names. Each becomes a root of the clone; the union deduplicates by PEP 503 canonical name, so naming a package that also turns up as a dep of another root materializes it once.
--link-mode hardlink shares inodes with the source install, so the
clone takes near-zero extra disk and survives the source being
uninstalled. --link-mode symlink is appropriate when the source is a
stable shared store and the clone is read-mostly; --link-mode copy
is the universal fallback.
--include-deps walks the source environment's installed dependency
closure and clones each member with the same link mode. Members that
cannot be translocated (editable installs, legacy egg-info layouts)
are skipped and reported.
--smoke-test-imports in this example runs import torch (and the
import for each other root) through the target interpreter once the
structural checks pass, then reports the outcome separately. A failed
import after the structural checks succeed usually points at a runtime
gap, like an untranslocatable dep.
Pair a translocated root with its deps from an index
--deps-file=<path> (or --deps-file=- for stdout) writes a
name==version list of each translocated root and that root's direct
Requires-Dist, every entry pinned to the source-environment version:
translocate clone torch \
--target ~/envs/inference \
--deps-file ~/envs/inference/requirements.txt
The resulting file feeds uv pip install -r to satisfy the direct
deps from an index:
uv pip install -r ~/envs/inference/requirements.txt \
--python ~/envs/inference/bin/python
Capture the full closure as a lockfile
With --include-deps, the file expands from direct deps to the full
transitive closure, every entry still pinned:
translocate clone torch \
--target ~/envs/inference \
--include-deps \
--deps-file ~/envs/inference/requirements.txt
The result is a pip-style lockfile of every package in the cloned venv, suitable for rebuilding the same set elsewhere.
Repackage installed packages as wheels
translocate repackage torch numpy --out ./wheels --include-deps
pip install ./wheels/torch-*.whl ./wheels/numpy-*.whl
--out specifies a directory. Wheels are emitted into that directory with
filenames generated per PEP 427 from the source distribution's metadata
({name}-{version}-{tag}.whl). The CLI prints each produced
filename and ends with a summary listing every wheel written to the
--out directory.
The output wheels reïnstall faithfully but are not bit-for-bit reproductions of the upstream artifacts. Any file the source install rewrote in place (script shebangs, post-install patches) and the RECORD file itself will differ from the original. The wheels are suitable for reïnstall and redistribution; they are not suitable for hash-pinning against an upstream hash.
Lock uv projects against translocated packages
A uv.lock can pin the exact build of a translocated package, even a
version that no index carries, such as a special torch 2.10.0+cu130
build compiled into a container base image. To do this, use uv's
flat index feature, which allows you to treat a directory of wheels
as an index source. Point uv lock at a flat directory generated via
translocate repackage and it records each package at its exact
built version. During uv sync --frozen, a translocated-in package
installation that already matches the locked version will be left
as-is, and everything else resolves normally from PyPI or
other indices.
translocate repackage --uv-config=- prints the pyproject.toml
snippet that configures uv to treat its --out directory
as a flat index:
[[tool.uv.index]]
name = "translocated"
url = "file:///abs/path/to/wheels"
format = "flat"
explicit = true
[tool.uv.sources]
torch = { index = "translocated" }
explicit = true confines the index to the packages that name it
under [tool.uv.sources]. Every other dependency resolves from PyPI
as usual. Use --uv-config=PATH instead of - to write the snippet
to a file. The emitted url is the absolute path of --out.
uv also accepts a path relative to the project root, like
url = "./wheels", which may travel better when the snippet is
committed, depending on your development setup.
Two ways to populate the index:
Real wheels
translocate repackage torch --out ./wheels --mkdir --uv-config=-
Run this where the package is installed. For a base image, that
means inside a container. Carry ./wheels to wherever you develop,
merge the snippet into pyproject.toml, and run uv lock. The
index holds installable wheels, so uv sync can populate a fresh
venv from it directly.
Metadata-only stubs
translocate repackage torch --out ./stubs --mkdir --metadata-only --uv-config=-
--metadata-only writes stub wheels holding just dist-info, which typically
makes for a few KiB per wheel. uv lock reads dependency metadata from them
exactly as it would from real wheels. Installing one would yield an empty
package, so they're mainly useful when paired with translocate clone:
the stub gives the resolver something to lock against, the clone puts the
real files in the venv, and uv sync --frozen accepts the pair as the
locked version already installed.
Building a container against the lockfile
With pyproject.toml and uv.lock in the build context, the image
build is:
uv venv /app/.venv
translocate clone my-package --target /app/.venv --link-mode symlink
uv sync --frozen
Symlink mode keeps the image small: the links resolve into the base
image's site-packages, which is always present at runtime. Clone
before sync: uv sync --frozen skips a package only when the venv
already holds the locked version. A frozen sync never consults the
index directory, so it does not need to exist in the image.
Regenerate it only when you need to run uv lock or uv add again,
in an environment that has the packages.
Common flags
| flag | clone | repackage |
|---|---|---|
--include-deps |
yes | yes |
--strict-deps |
yes | yes |
--deps-file <path|-> |
yes | yes |
--dry-run |
yes | yes |
--target <prefix> |
yes | no |
--link-mode <mode> |
yes | no |
--smoke-test-imports |
yes | no |
--out <dir> |
no | yes |
--metadata-only |
no | yes |
--uv-config <path|-> |
no | yes |
--strict-deps upgrades the default skip-and-warn for an
untranslocatable dep into a hard error, for callers who want
all-or-nothing.
--dry-run resolves packages, runs the up-front checks, and reports
what the command would write or clone. Side outputs requested by
flag, like --deps-file and --uv-config, are still written.
Exit codes
| code | meaning |
|---|---|
| 0 | Operation completed and verification passed |
| 1 | Unexpected error |
| 2 | Invalid CLI invocation |
| 10 | Distribution not found in source environment |
| 11 | Distribution is editable or otherwise untranslocatable |
| 12 | Target is not a Python install, or source and target are the same interpreter |
| 13 | Target ABI is incompatible with the source |
| 20 | Verification failed |
Metadata
Release files for translocate 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| translocate-0.1.0.tar.gz | 72.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| translocate-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 132.4 kB
Release files / translocate-0.1.0.tar.gz
| Download URL | translocate-0.1.0.tar.gz |
|---|---|
| Size | 72.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
70b8ce372f12b4bd273003f9970f3dce4a244221f3af3765071372d31a02cae5
|
|
BLAKE2b-256 checksum How to use checksums |
362f7ce523adef17bb0fbab1709c6de25fb6325b7847cff5411516c4235e43bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|
Release files / translocate-0.1.0-py3-none-any.whl
| Download URL | translocate-0.1.0-py3-none-any.whl |
|---|---|
| Size | 60.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c40d115d598b178e347bc07afa86154a2bb43e0c0bdc9e728c2ca5b20204264a
|
|
BLAKE2b-256 checksum How to use checksums |
74a399c863d7450db6abb4b6abd01d2a591e4add82877550c7ea5caea34d3229
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|