Part 4: Async Programming and FastAPI

Introduction

The ansible-inspec server runs compliance jobs in the background while still serving API requests. Without async, every long-running job would block the HTTP server entirely. Python's asyncio plus FastAPI is the combination I landed on β€” FastAPI handles routing and validation, asyncio manages concurrent I/O, and BackgroundTasks dispatches jobs without blocking the caller.

This part covers async programming from first principles up to a realistic FastAPI server pattern.


Sync vs Async β€” The Core Difference

import time
import asyncio

# Synchronous β€” each sleep blocks the entire thread
def sync_check(host: str, delay: float = 1.0) -> str:
    time.sleep(delay)        # blocks everything
    return f"{host}: ok"

# Asynchronous β€” sleep yields control back to the event loop
async def async_check(host: str, delay: float = 1.0) -> str:
    await asyncio.sleep(delay)   # yields; other coroutines can run
    return f"{host}: ok"

Running three sync checks takes 3 seconds (sequential). Running three async checks with asyncio.gather takes ~1 second (concurrent).


async/await Fundamentals

Coroutine vs function

await β€” yield until ready

asyncio.gather β€” run concurrently

Error handling in gather

Timeout with asyncio.wait_for

asyncio.Queue β€” worker pool pattern

The job executor in ansible-inspec uses a queue of pending jobs and a pool of worker coroutines:


CPU-bound Work in Async Code

asyncio is single-threaded β€” it can't parallelise CPU-bound work. For that, use asyncio.to_thread (3.9+) or ProcessPoolExecutor:


FastAPI Server

FastAPI is an async-first web framework that auto-generates OpenAPI docs from Pydantic models and type annotations. ansible-inspec uses it for the REST API at localhost:8080.

Minimal server

Run with:

Job templates endpoint

Background job execution

ansible-inspec runs compliance jobs in BackgroundTasks so the API returns immediately:

Dependency injection with Depends

FastAPI's Depends wires shared resources (DB sessions, auth, config) into route handlers:

Lifespan β€” startup and shutdown hooks

Router organisation

For larger projects, split routes into separate files:


Error Handling


Summary

Concept
Key point

async/await

await yields control; doesn't block the event loop

asyncio.gather

Run multiple coroutines concurrently

asyncio.wait_for

Timeout wrapper for any coroutine

asyncio.Queue

Worker pool for job queues

asyncio.to_thread

Run blocking sync code without blocking the loop

FastAPI routing

Type-annotated + Pydantic = auto OpenAPI docs

BackgroundTasks

Non-blocking job dispatch

Depends

Dependency injection for auth, DB, config

lifespan

Async startup/shutdown hooks


What's Next

Part 5 covers pytest, async test fixtures, and how to build a robust CI-checked test suite β€” the same approach used in ansible-inspec's tests/ directory.

Last updated