PyWhales
PyWhales is a typed Python wrapper around the external Docker CLI. It builds docker command lines, runs them as subprocesses, and returns the results as Python objects. It isn't an HTTP SDK and doesn't call the Docker Engine API.
Requirements
- Python 3.14.10 or newer, on Linux, macOS or Windows.
- The Docker CLI, installed separately. See the Docker CLI guide.
- A reachable Docker daemon for commands that talk to it.
- The Compose and Buildx plugins for the commands that use them (
docker.compose,docker.build).
Importing PyWhales or constructing a client doesn't check that the CLI, daemon or plugins are available. Problems show up when a command runs.
Install
uv pip install pywhales
Quickstart
from pywhales import docker
print(docker.run("hello-world", remove=True))
containers = docker.ps()
images = docker.image.list()
Building and Compose need local files: a Dockerfile for build (DockerClient.build is an alias for Buildx) and a Compose file for compose.
image = docker.build(".", tags="my-app:latest", load=True)
docker.compose.up(detach=True)
Common tasks
These examples use alpine, publish no ports, and clean up after themselves. The first run pulls the image if it isn't local. Commands are argv lists, not shell strings: a string raises TypeError.
Run a detached container safely
from pywhales import docker
with docker.run("alpine", ["sleep", "300"], detach=True, stop_timeout=1) as container:
state = container.state
if state is not None:
print(state.running) # True
detach=True returns a Container handle instead of output. Leaving the with block, normally or through an exception, stops the container if it's running and removes it along with its anonymous volumes. A container that's already gone isn't an error. stop_timeout=1 caps the shutdown grace period at one second, so cleanup doesn't wait on sleep. Nothing is published to the host unless you pass publish.
Execute a command
from pywhales import docker
with docker.run("alpine", ["sleep", "300"], detach=True, stop_timeout=1) as container:
print(container.execute(["echo", "hello"])) # hello
print(container.execute(["pwd"], workdir="/tmp")) # /tmp
execute() returns the command's stdout as a str with one trailing newline removed. Stderr isn't returned, but a failed command keeps it on the exception. docker.execute(container, [...]) does the same and also accepts a container name.
Stream output and close it
from pywhales import docker
with docker.run("alpine", ["sleep", "300"], detach=True, stop_timeout=1) as container:
with container.execute(
["sh", "-c", "echo out; echo err >&2"], stream=True
) as output:
for source, payload in output:
print(source, payload.decode(errors="replace"), end="")
# stdout out
# stderr err
With stream=True you get an iterator of (source, payload) pairs. source is "stdout" or "stderr", and payload is bytes: one line per record, newline included, so decode it yourself. The two sources can arrive in either order. The non-streaming call returns a decoded str instead.
The with block closes the local docker process on every exit path, including break and exceptions. A bare break doesn't close the stream. Closing affects only the local CLI: a command still running in the container isn't stopped, and the outer block removes the container.
Catch CLI failures
from pywhales import DockerException, docker
from pywhales.exceptions import DockerTimeout
with docker.run("alpine", ["sleep", "300"], detach=True, stop_timeout=1) as container:
try:
container.execute(["sh", "-c", "exit 3"], timeout=10)
except DockerTimeout:
print("The local docker CLI exceeded 10 seconds")
except DockerException as error:
print(f"{type(error).__name__}: exit code {error.return_code}")
# DockerException: exit code 3
A failing CLI call raises DockerException. Subclasses such as NoSuchContainer and DockerTimeout derive from it, so list them first. return_code is the exit code of the local docker process, which docker exec takes from the command itself. A stream raises the same exceptions when iteration reaches the end of a failed command.
The exception message shows the exit code but withholds the command and output. docker_command, stdout and stderr hold raw values that can include secrets, so log them only on purpose. timeout bounds only the local CLI. On expiry PyWhales kills and reaps it, but the command in the container may keep running. See Exceptions for the full list.
Custom configuration
docker is a shared default client. Create your own DockerClient to change its settings:
from pywhales import DockerClient
client = DockerClient(
context="my-context",
compose_files=["compose.yaml"],
default_timeout=30,
)
The Docker context must already exist. default_timeout is in seconds and bounds each local CLI launch. Expiry kills and reaps the local CLI process, but doesn't roll back work the daemon has already started.
Good to know
- Containers, images, volumes and other resources are typed handles. Reading a property may inspect the resource and cache the result briefly. Call
reload()when you want a fresh copy. - Streaming calls that own a process return iterators. Close them, or use them as context managers, if you stop reading early.
PYWHALES_DEBUG=1turns on execution diagnostics that withhold command arguments, environment values and captured output.DockerClient(raw_diagnostics=True)opts a single client into raw diagnostics, so use it only where its logs are safe to expose.- That redaction covers diagnostics only. Returned data, exception attributes and output PyWhales doesn't capture are not redacted.
More documentation
License
PyWhales is released under the MIT license. See LICENSE.
Metadata
Release files for pywhales 1.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pywhales-1.1.4.tar.gz | 395.7 kB | Without provenance | Details for pywhales-1.1.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pywhales-1.1.4-py3-none-any.whl | Python 3 | none | any | Details for pywhales-1.1.4-py3-none-any.whl |
Total release size: 721.3 kB
Release files / pywhales-1.1.4.tar.gz
| Download URL | pywhales-1.1.4.tar.gz |
|---|---|
| Size | 395.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0531d5e74a2664d1ea163550ab7db63826e52fc21269d4dafa697e252d42d040
|
|
BLAKE2b-256 checksum How to use checksums |
2ff687caef6c916c90bfd9805c0c404798fa6a5d539119869cbef8e2f98a44a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / pywhales-1.1.4-py3-none-any.whl
| Download URL | pywhales-1.1.4-py3-none-any.whl |
|---|---|
| Size | 325.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
711afde4d59e886fef4901516b8f2c9ba371bafcb78341742e688958d27b809d
|
|
BLAKE2b-256 checksum How to use checksums |
4886a2d49fb51050b9110685b2ea18d60c5996ed016b6a433e38918254014705
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for pywhales 1.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.