Skip to main content

ansible-host

CI License: Apache 2.0 Ruff

A thin Python wrapper over Ansible's executor. Lets you run Ansible modules from Python with structured results, batch execution, and a host-object-first API — without going through pytest-ansible or ansible-runner.

Status: beta (0.1.0). Built from a working internal implementation; API is stable in shape but may shift in details before 1.0.

Why this exists

Ansible has a great executor and a huge module ecosystem, but the existing Python access paths are awkward for in-test or in-tool use:

pytest-ansible ansible-runner ansible-host
Use case Pytest fixtures AWX-style managed jobs In-process programmatic
API surface ~20% of Ansible's runtime Subprocess + event stream Typed Python objects
Forking, custom callbacks Limited Yes (in subprocess) Yes (native)
Per-call overhead Low High (subprocess + JSON parsing) Low (in-process)
Returns Strings Event stream Structured Python dicts

ansible-host sits where the other two don't: in-process, low-overhead, structured-result execution that you can drop into any Python codebase that wants Ansible underneath.

Install

pip install ansible-host

Requires Python 3.10+ and ansible-core>=2.16,<2.22. Like Ansible itself, the library runs on POSIX systems (Linux, macOS, WSL) — it is not supported on native Windows.

Development

The recommended dev workflow uses uv — it manages the virtualenv directly, sidestepping the python3-venv split on Ubuntu and installing dependencies an order of magnitude faster than pip.

# One-time: install uv (https://docs.astral.sh/uv/getting-started/installation/)
curl -LsSf https://astral.sh/uv/install.sh | sh

# In the repo
uv venv                        # creates .venv with the default Python
uv pip install -e ".[dev]"     # editable install + dev extras
uv run pytest                  # run the test suite
uv run ruff check src tests    # lint

To target a specific Python or ansible-core version (matches the CI matrix):

uv venv --python 3.12
uv pip install -e ".[dev]"
uv pip install "ansible-core==2.19.*"
uv run pytest

pip and a manually-managed venv still work — uv is just the convenience.

Testing

The suite has two tiers:

  • Tier-1 (default) — runs modules in-process over Ansible's local connection. No SSH or external host required; this is what uv run pytest and CI's matrix run.
  • Tier-2 (ssh marker) — exercises Ansible's real SSH transport (connection options, remote module exec, become, multi-host fanout) against a throwaway containerized sshd in tests/ssh/. These tests auto-skip unless an SSH target is configured, so the default run stays green without any SSH setup.

Run the SSH tier locally (requires Docker):

# Start a throwaway SSH target with an ephemeral key authorized on it
ssh-keygen -t ed25519 -N '' -f /tmp/ah_ssh_key
export AH_SSH_PUBLIC_KEY="$(cat /tmp/ah_ssh_key.pub)"
docker compose -f tests/ssh/docker-compose.yml up -d --build

# Point the ssh-marked tests at it
AH_SSH_HOST=127.0.0.1 AH_SSH_PORT=2222 AH_SSH_USER=ansible AH_SSH_KEY=/tmp/ah_ssh_key \
    uv run pytest -m ssh

# Tear down
docker compose -f tests/ssh/docker-compose.yml down -v

CI runs Tier-1 across the full Python × ansible-core matrix, plus a dedicated test-ssh job that builds the container and runs the Tier-2 tests.

Quickstart

30-second try — no SSH, no inventory

AnsibleLocalhost runs modules in-process on the current machine via Ansible's local connection plugin. No inventory file, no SSH, no setup.

from ansible_host import AnsibleLocalhost

host = AnsibleLocalhost()
result = host.ping()
assert result["ping"] == "pong"

result = host.command("uname -a")
print(result["stdout"])

Running against a real host

Drop an inventory file alongside your script:

# inventory.ini
[switches]
sw-01 ansible_host=10.0.0.1 ansible_user=admin
from ansible_host import AnsibleHost

host = AnsibleHost(inventory="inventory.ini", pattern="sw-01")
result = host.shell("show version")
print(result["stdout"])

Multi-host fanout

from ansible_host import AnsibleHosts

hosts = AnsibleHosts(inventory="inventory.ini", pattern="switches")
results = hosts.ping()                       # parallelism via forks=
for hostname, r in results.items():
    print(hostname, r["ping"])

# Container API: index, iterate, len
print(len(hosts), hosts.hostnames)
first = hosts[0]                             # -> AnsibleHost
by_name = hosts["sw-01"]                     # -> AnsibleHost
for h in hosts:
    print(h.hostname)

Dynamic dispatch and task directives

Any Ansible module is callable as a method via __getattr__ (host.<module_name>(...)):

host.copy(src="/etc/hosts", dest="/tmp/hosts.bak")
host.command("rm /tmp/maybe-missing", task_directives={"ignore_errors": True})
host.shell("echo $TOKEN", task_directives={"no_log": True})

Common task_directives: ignore_errors, no_log, when, failed_when, changed_when, become.

Batch mode — queue tasks, run them in a single play

with host:
    host.shell("uptime")
    host.shell("df -h")
    host.shell("free -m")
results = host.results

Building a queue across functions

with host: is lexically scoped. If you need to assemble a batch across multiple functions, use the explicit form — same machinery, no scope limit:

host.load_module("ansible.builtin.command", args=["uptime"])
host.load_module("ansible.builtin.command", args=["df -h"])
results = host.run_loaded_modules()

Result shapes

single task batch (with block)
single host dict list[dict]
multi host {hostname: dict} {hostname: list[dict]}

Reading host variables

Inspect inventory variables resolved by Ansible — host vars, group vars, and runtime extra vars — without running a module:

host = AnsibleHost(inventory="inventory.ini", pattern="sw-01")

# Variables defined directly on the host (raw — Jinja2 templates not rendered):
host.get_host_var("ansible_user")          # -> "admin"
host.host_vars                             # -> dict of host-scoped vars

# Fully-resolved view a host can see (host + group + extra vars), with
# Jinja2 templates rendered:
host.get_visible_var("some_group_var")     # group var visible to the host
host.get_visible_var("missing", "default") # default when absent
host.visible_vars                          # -> dict of all resolved vars

# Runtime overrides (highest precedence):
host.update_extra_vars({"feature_flag": True})
host.extra_vars                            # -> current extra vars

AnsibleHosts exposes the host-keyed form: hosts.get_host_var("sw-01", "ansible_user") and hosts.get_visible_var("sw-01", "some_var").

Failures

A failing module raises AnsibleModuleFailed:

from ansible_host import AnsibleModuleFailed

try:
    host.command("false")
except AnsibleModuleFailed as e:
    print("module failed:", e)

# Or suppress and inspect the result:
result = host.command("false", task_directives={"ignore_errors": True})
assert result["failed"] is True

More examples

See the test suite for runnable examples:

Compatibility

This library uses Ansible's internal Python API (TaskQueueManager, InventoryManager, VariableManager, Play, DataLoader). Those APIs are not officially stable across ansible-core releases — expect occasional updates when ansible-core introduces breaking internal changes. The current support range is declared in pyproject.toml and in the matrix CI.

Concurrency

ansible-host is designed for sequential use at the instance level. For parallelism within a single call, use Ansible's native forking via the forks= option. Running multiple AnsibleHost / AnsibleHosts instances concurrently in different threads will race on ansible.context's process-global state.

Reference implementation

A real-world use of this pattern lives in sonic-mgmt's tbng branchansible-host is the cleaned-up, packaged version of that code.

License

Apache License 2.0. See LICENSE.

Release files for ansible-host 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ansible-host 0.1.0
File Size Uploaded
ansible_host-0.1.0.tar.gz 33.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ansible-host 0.1.0
File Interpreter ABI Platform
ansible_host-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 56.5 kB

Release files / ansible_host-0.1.0.tar.gz

Download URL ansible_host-0.1.0.tar.gz
Size 33.1 kB
Tags Source
SHA-256 checksum
How to use checksums
1b032444ff07587886cb9679093fff1bb848144414acd6ac53b3994970b83bcf
BLAKE2b-256 checksum
How to use checksums
1b29eb7076b1ae24eef2d19a676758dc132d9b1ee2ec410dfe85d67944d4c6e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release files / ansible_host-0.1.0-py3-none-any.whl

Download URL ansible_host-0.1.0-py3-none-any.whl
Size 23.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ead887c5668aaf4bd4afaa4ff0b573921713b4a124da8f99b50ac94e20a6b08d
BLAKE2b-256 checksum
How to use checksums
63866f9214d8221bca80e1fb075acc5e69ca33d5e342f15be67b11f4433e3c9d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release 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