Use workspace archives from Python#

Use conda_workspaces.archive.WorkspaceArchive when another Python tool needs archive operations without shelling out to conda workspace archive or importing CLI handlers.

Create an archive#

Create an archive from the current workspace:

from conda_workspaces.archive import WorkspaceArchive

archive = WorkspaceArchive.create(
    output="dist/my-project.tar.zst",
    receipt=True,
)

Pass workspace= to archive a different workspace root:

archive = WorkspaceArchive.create(
    workspace="/path/to/workspace",
    output="dist/my-project.tar.zst",
    receipt="dist/my-project.receipt.json",
)

Set lock=True to refresh conda.lock before writing the archive, and bundle=True to include resolved package archives from conda’s package cache:

archive = WorkspaceArchive.create(
    output="dist/my-project-offline.tar.zst",
    lock=True,
    bundle=True,
    receipt=True,
)

Inspect and verify an archive#

Inspect archive metadata without extracting files:

archive = WorkspaceArchive("dist/my-project.tar.zst", receipt=True)
info = archive.inspect()

if not info["has_manifest"]:
    raise RuntimeError("not a workspace archive")

Verify an archive against its receipt:

receipt = archive.verify()
print(receipt.workspace_paths)

Receipt verification, member inspection, and extraction use one private archive snapshot. Replacing the original path after snapshotting cannot change the verified bytes. A receipt still does not identify who created the archive or receipt.

Extract an archive#

Archive inspection and extraction reject more than 100,000 members or 100,000 distinct filesystem entries, member paths or link targets deeper than 256 components or longer than 4,096 UTF-8 bytes, more than 128 consecutive GNU or PAX extension headers, more than 100,000 PAX records, more than 64 MiB of expanded GNU and PAX metadata, and more than 100 GiB of declared regular-file data including possible link fallbacks. GNU sparse files, hardlinks, and unsupported tar members are rejected before their payloads are traversed. Extraction requires a supported Python patch release with tarfile.data_filter. Repack sparse or metadata-heavy inputs as ordinary files, split an archive, or exclude unneeded files when a trusted workspace exceeds one of these safety limits.

Extraction stops at 4 GiB of raw archive bytes or expanded regular files and possible link fallbacks by default. For an archive you trust, pass --allow-large-archive to conda workspace unarchive or allow_large_archive=True to WorkspaceArchive.extract(), WorkspaceArchive.install(), or extract_archive(). The 100 GiB hard limit still applies. Extraction checks free space before snapshotting the archive, writing expanded files, and copying bundled packages to a private installation cache. It reserves 16 KiB per extracted file or directory and checks available inodes where the filesystem reports them. Each space check preserves 64 MiB for other disk use. These checks cannot account for concurrent disk use.

When a receipt is configured, its archive digest is verified before member inspection. Archive creation also rejects a symbolic-link workspace manifest before reading its target. It refuses credentials embedded in the manifest or existing lockfile, then binds the approved manifest, lockfile, and package bytes to the bytes written into the archive. Remove and rotate embedded credentials, regenerate conda.lock, and configure replacement authentication through Conda outside the repository. Bundle and receipt lock inputs are read under the workspace publication guard, archive output is published atomically, and a receipt failure removes a new archive output but keeps a generated canonical lockfile that was already published successfully.

Extract into a target path that does not exist yet:

result = archive.extract(target="/tmp/restored", require_sha256=True)

print(result.target)
print(result.verified)

Extraction leaves bundled packages in the extracted workspace’s packages/ directory. It does not write records to a conda package cache. To install offline from a bundled archive, call WorkspaceArchive.install() with a receipt. It stages packages in a temporary cache for that installation:

archive.install(target="/tmp/restored")

Install from an archive#

Install all archived environments after extraction:

archive.install(target="/tmp/restored")

Install one environment to an explicit runtime prefix:

archive.install(
    target="/tmp/restored",
    environment="runtime",
    prefix="/opt/runtime",
)

Stage files under a filesystem root while preserving the requested runtime prefix:

result = archive.install(
    target="/tmp/restored",
    environment="runtime",
    prefix="/opt/runtime",
    dest="/tmp/rootfs",
)

if result.prefix_reference_matches:
    print("Some files still reference the staging prefix")

prefix_reference_matches reports files that still contain the physical staging prefix after installation. It is a warning signal for relocation workflows, not an automatic prefix-rewrite feature.

Customize installation#

Pass install_handler= to let conda-workspaces extract and verify an archive while your integration controls environment installation:

from pathlib import Path


def install_handler(
    workspace: Path,
    environment: str | None,
    prefix: Path | None,
    target_prefix_override: str | None,
) -> int:
    print(workspace, environment, prefix, target_prefix_override)
    return 0


archive.install(
    target="/tmp/restored",
    environment="runtime",
    prefix="/opt/runtime",
    dest="/tmp/rootfs",
    install_handler=install_handler,
)

The handler receives the extracted workspace path, the selected environment, the physical install prefix, and the runtime prefix override when staging under dest.

API reference#

See Archives for the API reference.