Skip to main content

literegistry-podman-client

literegistry-podman-client is a small, standalone async Python client for running commands in rootless Podman containers through a LiteRegistry gateway. The person using it needs only a gateway URL; they do not need Redis, Podman, Docker, or the full literegistry package.

The same gateway may also expose a Docker Hub pull-through mirror. Mirror use is configured on the Podman servers by the operator, so client code still only passes a normal image such as docker.io/library/ubuntu:24.04.

Install

Install through the LiteRegistry extra:

pip install "literegistry[podman_client]"

The standalone distribution is also available directly:

pip install literegistry-podman-client

From this repository:

pip install ./literegistry_podman_client

The distribution name uses hyphens. Python imports use underscores:

from literegistry_podman_client import PodmanClient

Its runtime dependencies are aiohttp and Python Fire.

Give a deployment to someone

The operator gives the user one value:

export PODMAN_GATEWAY_URL=http://gateway.example:8080

The user can verify both gateway features:

curl -fsS "$PODMAN_GATEWAY_URL/health"
curl -fsS "$PODMAN_GATEWAY_URL/v2/"

/health checks the LiteRegistry gateway. /v2/ checks its Docker Registry V2 mirror route. No Redis URL or Podman replica address is exposed to users.

Small async example

This creates one container, writes a file, reads it in a separate command, and always deletes the container at the end:

Without async with

import asyncio

from literegistry_podman_client import PodmanClient


async def main() -> None:
    gateway_url = "http://gateway.example:8080"
    client = PodmanClient(gateway_url, workdir="/tmp")
    podman = None
    await client.open()
    try:
        podman = await client.handshake(
            image="docker.io/library/ubuntu:24.04",
            client_id="rollout-17",
        )
        print(podman.container_id)

        await podman.execute("echo ai2 hello > hello.txt", check=True)
        result = await podman.execute("cat hello.txt", check=True)
        print(result.stdout, end="")
    finally:
        try:
            if podman is not None:
                await podman.close()
        finally:
            await client.aclose()


asyncio.run(main())

With async with

import asyncio

from literegistry_podman_client import PodmanClient


async def main() -> None:
    async with PodmanClient(
        "http://gateway.example:8080",
        workdir="/tmp",
    ) as client:
        async with client.session(
            image="docker.io/library/ubuntu:24.04",
            client_id="rollout-17",
        ) as podman:
            print(podman.container_id)
            await podman.execute("echo ai2 hello > hello.txt", check=True)
            result = await podman.execute("cat hello.txt", check=True)
            print(result.stdout, end="")


asyncio.run(main())

The image pull happens on the selected Podman replica. If the operator wired those replicas to the gateway's mirror, the pull is transparently cached; the user does not change the image reference or client configuration.

The installed Fire CLI exposes the same smoke test:

literegistry-podman-client ai2-hello --gateway="$PODMAN_GATEWAY_URL"

The runnable version is examples/ai2_hello.py:

python literegistry_podman_client/examples/ai2_hello.py \
  --gateway "$PODMAN_GATEWAY_URL"

Explicit lifecycle

Handshake, execute, and close are all async. The affinity ID returned by the handshake keeps every command on the replica that owns its container.

client = PodmanClient(gateway_url, workdir="/home/user")
await client.open()
session = None
try:
    session = await client.handshake(image=container_image)
    first = await client.execute(
        session.affinity_id,
        "python -c 'print(6 * 7)'",
        timeout=60,
    )
    first.check_returncode()
finally:
    try:
        if session is not None:
            await session.close()
    finally:
        await client.aclose()
  • client.close(affinity_id) or session.close() deletes the container and its gateway affinity binding.
  • client.aclose() only closes the local HTTP connection pool. It cannot guess which concurrent sessions should be deleted.
  • check=True or result.check_returncode() raises PodmanCommandError for a non-zero command exit. Without it, stdout, stderr, and the exit code remain available on CommandResult.
  • result.stdout_truncated and result.stderr_truncated report whether the server discarded output beyond its configured capture limits. Always check these flags before treating captured output as complete.

Concurrent trajectories

One PodmanClient is intentionally shareable. It does not store a private "current container". Each handshake returns a separate session:

async def rollout(client: PodmanClient, number: int) -> str:
    podman = await client.handshake(client_id=f"rollout-{number}")
    try:
        result = await podman.execute("echo $((20 + 22))", check=True)
        return result.stdout.strip()
    finally:
        await podman.close()


client = PodmanClient(gateway_url)
await client.open()
try:
    outputs = await asyncio.gather(
        *(rollout(client, i) for i in range(128))
    )
finally:
    await client.aclose()

The HTTP pool is shared, while every request carries its explicit affinity ID. As soon as one trajectory completes, application code may start another.

Mirror behavior

There are two distinct flows behind the gateway URL:

Python client -> /affinity/* -> Podman replica -> rootless container
Podman pull   -> /v2/*       -> Docker mirror -> Docker Hub

The client exposes await client.mirror_health() as a convenience probe, but it does not configure the mirror. The deployment operator configures each Podman replica's containers-registries.conf to use the gateway URL. This is what ensures all users of the gateway benefit from the cache.

Build and publish

cd literegistry_podman_client
python -m build
python -m twine check dist/*
python -m twine upload dist/*

Publishing requires a PyPI account and token. Verify that the distribution name literegistry-podman-client is available before the first upload.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

literegistry_podman_client-0.1.2.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

literegistry_podman_client-0.1.2-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

File details

Details for the file literegistry_podman_client-0.1.2.tar.gz.

File metadata

File hashes

Hashes for literegistry_podman_client-0.1.2.tar.gz
Algorithm Hash digest
SHA256 166b30d8e5da988a1072ba04dad2cac3d26c632e75e3528220cff02fbaa5c455
MD5 e841358cf34bf66926126c1f4b64411b
BLAKE2b-256 dc36d0994633587046e28bb448432b02dd2f5142a2f4499ce2590ac5be9898ad

See more details on using hashes here.

File details

Details for the file literegistry_podman_client-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for literegistry_podman_client-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 38c02da9e127f0b16aa7a41f02b63e2e41ca3f0ebc914311671dae4f47ca2efd
MD5 00b04b6f6813dd2af45dc196d4ca2932
BLAKE2b-256 f8d112479fc3915f8008850d4c2dc1d59a645a1a82335303a0c5bc13e2ddc438

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.3

2 files

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page