A library for integrating Ansible roles into Python
Find a file
2026-02-02 11:03:39 -05:00
PyAnsible Fixes Exception Error 2026-02-02 11:03:39 -05:00
.gitignore Starts Project Tracking 2026-01-26 08:57:43 -05:00
pyproject.toml Starts Project Tracking 2026-01-26 08:57:43 -05:00
README.md Starts Project Tracking 2026-01-26 08:57:43 -05:00

PyAnsible

A library for integrating Ansible roles into Python

Author: August Knisley
Date: 15 December, 2025

Introduction

Architecture

PyAnsible is built around Ansible Runner, specifically the ansible_runner.run method. Which allows for running Ansible roles, playbooks, commands, etc. PyAnsible abstracts these features into a class based layout, which simplifies usages and importantly allows Ansible roles to be packaged as Python Wheels and imported as any other library.

The PyAnsible package is comprised of three components:

  • The Inventory class: models the Ansible inventory and stores Host objects, and manages global variables. It is used exclusively as the singleton object accessible as PyAnsible.inventory.

  • The Host class: models a host in Ansible inventory. Stores hostname and IP address as attributes, and manages hostvars available to the host. The class can be used as-is or as a base class to extend functionality for more specific host types

  • The RoleWrap class: A base-class that when extended wraps an single Ansible role allowing it to be used as a callable object within Python.

Basic Usage

Wrapping a Role

class MyWrappedRole(RoleWrap):
    '''
    A basic example of the use of the RoleWrap class
    '''
    def __init__(self):
        super().__init__('MyWrappedRole')

    def __call__(self, host: Host, var1, var2):
        '''
        Runs the wrapped `MyWrappedRole` Ansible role 
        '''
        ## Setup role inputs
        self.role_vars['ca_fqdn'] = var1
        self.role_vars['ca_fingerprint'] = var2

        ## Run role with Ansible on given host
        super().run(host.hostname)

        ## Return selected variables to caller
        ## NOTE: To be able to collect a variable, `cacheable: yes` must be
        ## added to the set_fact task
        return (
            self.output[host.hostname]['my_output_var1'],
            self.output[host.hostname]['my_output_var2'],
        )

Running a Role

import PyAnsible
from .MyWrappedRole import MyWrappedRole

## Create host
host1 = PyAnsible.Host('host1.example.com', '1.1.1.1')

## Add host to PyAnsible inventory
PyAnsible.inventory.add_host(host1)

## Create a role object
my_wrapped_role = MyWrappedRole()

## Run the role on host1, with inputs and collect outputs
foo, bar = my_wrapped_role(host1, 'baz', 'quux')

Packaging a Wrapped Role

There are currently two supported ways to package a wrapped Ansible role.

The first is where all the Ansible role folders are inside a roles directory, and the wrapper classes are within the the normal Python structure.

MyProject/            
├── __init__.py       
├── MyWrappedRole1.py
├── MyWrappedRole2.py
└── roles/
    ├── MyWrappedRole1/
    │   ├── tasks/      
    │   │   └── main.yml
    │   └── files/
    │       └── foo.conf
    └── MyWrappedRole2/
        └── tasks/      
            └── main.yml

This would only require adding the following to the pyproject.toml file:

[Rest of PyProject.toml file]
...
[tool.setuptools.package-data]
"MyProject.roles" = ["**"]

Or each role is a submodule of a parent class with something like the following directory structure:

MyProject/            
├── __init__.py       
├── MyWrappedRole1/
│   ├── __init__.py   
│   ├── tasks/      
│   │   └── main.yml
│   └── files/
│       └── foo.conf
└── MyWrappedRole2/
    ├── __init__.py   
    └── tasks/      
        └── main.yml
[Rest of PyProject.toml file]
...
[tool.setuptools.package-data]
"MyProject.MyWrappedRole1" = ["**"]
"MyProject.MyWrappedRole2" = ["**"]