How the AirRoute Class Customizes FastAPI Routing in Air

AirRoute is a thin wrapper around FastAPI's APIRoute that transforms raw requests into AirRequest objects, resolves postponed type annotations, and equips endpoints with reverse-URL helpers while maintaining full FastAPI compatibility.

The AirRoute class in the feldroy/air repository provides the foundation for Air's "FastAPI-compatible but Air-enhanced" routing layer. By subclassing FastAPI's APIRoute, it seamlessly integrates Air-specific request handling, automatic HTML tag rendering, and convenient URL generation without sacrificing any native FastAPI functionality.

What is AirRoute?

AirRoute is defined in src/air/routing.py as a specialized subclass of FastAPI's APIRoute class. Unlike standard FastAPI routes that work directly with Starlette requests, AirRoute acts as an interceptor that prepares the execution environment before your handler runs and processes the return value afterward.

When you create an application using air.Air() or air.AirRouter(), every route automatically uses AirRoute as the underlying route class. This happens because AirRouter.__init__ passes route_class=AirRoute to FastAPI's APIRouter constructor in src/air/routing.py (lines 452-461).

Core Customization Features

Extending APIRoute for Air-Specific Behavior

At its core, AirRoute inherits all standard FastAPI routing behavior while overriding specific hooks to inject Air functionality. The class definition at line 98 of src/air/routing.py shows the inheritance structure:

class AirRoute(APIRoute):
    # Custom initialization and handler logic

This inheritance ensures that existing FastAPI features like dependency injection, OpenAPI schema generation, and automatic validation continue to work exactly as expected.

Resolving Postponed Type Annotations (PEP 563)

One critical customization happens during route initialization. Modern Python uses postponed annotation evaluation (PEP 563), which can break runtime type inspection that Air relies on for proper request handling and response serialization.

In src/air/routing.py (lines 101-117), the __init__ method rebuilds the endpoint's signature with resolved annotations:


# Inside AirRoute.__init__

# Resolves 'AirRequest' and tag return types from string annotations

# to actual types at import time

This ensures that when you import Air modules with from __future__ import annotations, the framework still correctly identifies AirRequest parameter types and HTML tag return types for proper handling.

Wrapping Requests with AirRequest

The most visible customization occurs in the custom_route_handler method (lines 122-128 of src/air/routing.py). Before your endpoint function executes, AirRoute creates an AirRequest instance and passes it to your handler instead of the raw Starlette request:

async def custom_route_handler(self, request: Request) -> Response:
    # Wrap the incoming request

    air_request = AirRequest(request)
    # Forward to the original handler with the enhanced request object

    return await original_handler(air_request)

This wrapping provides convenient methods like await request.form() and other Air-specific request utilities defined in src/air/requests.py, giving you a richer API than standard FastAPI requests while maintaining backward compatibility.

Enabling Automatic AirResponse Rendering

While the actual wrapping logic for return values lives in RouterMixin._wrap_endpoint, AirRoute enables automatic HTML rendering by signaling to FastAPI that it should use this custom route class. When your endpoint returns Air tags (like air.H1 or air.Div), the framework automatically converts these to proper HTTP responses via AirResponse from src/air/responses.py.

This means you can write:

@app.get("/")
def home() -> air.H1:
    return air.H1("Welcome")

And receive properly formatted HTML without manually constructing Response objects.

Adding Reverse-URL Generation via url()

Every route created through AirRoute receives a .url() method for programmatic URL generation. In src/air/routing.py (lines 108-110), the _route method attaches this helper:

decorated.url = self._url_helper(...)

This allows you to generate URLs for any endpoint from within your code or templates:


# Generates "/hello/world" based on the route pattern

greet.url(name="world")

The actual URL construction logic resides in RouterMixin._url_helper, but AirRoute ensures every decorated function gets this convenient attribute.

How AirRoute Integrates with AirRouter

The AirRouter class (also in src/air/routing.py) serves as the main entry point that wires everything together. When you initialize an AirRouter, it automatically configures FastAPI to use AirRoute for all route registration:


# In AirRouter.__init__ (src/air/routing.py, lines 452-461)

super().__init__(
    route_class=AirRoute,  # This is the key integration point

    # ... other FastAPI APIRouter parameters

)

Developers never need to mention AirRoute explicitly in their code. By using air.Air() or air.AirRouter(), you automatically get Air's enhanced routing behavior throughout your entire application.

Practical Implementation Example

The following example from examples/src/simple_air_app.py demonstrates how AirRoute works in practice:

import air

app = air.Air()  # Creates FastAPI app with AirRoute as default

@app.get("/hello/{name}")
def greet(name: str) -> air.H1:
    """A basic route that returns an Air tag."""
    return air.H1(f"Hello, {name}!")

# The handler receives an AirRequest, not a raw Starlette request.

# The returned Air tag is automatically rendered as HTML via AirResponse.

# Reverse-URL generation:

print(greet.url(name="world"))  # Output: /hello/world

When you run this with Uvicorn and inspect the application routes, you'll find that app.routes[0].__class__ returns AirRoute, confirming that the customization is active. The request object passed to greet is actually an AirRequest instance, giving you access to Air's form handling and other request utilities.

Key Files Supporting AirRoute

Several modules work together to enable AirRoute functionality:

Summary

  • AirRoute subclasses FastAPI's APIRoute in src/air/routing.py to inherit standard routing while adding Air-specific enhancements.
  • Postponed annotations are resolved at initialization (lines 101-117) ensuring PEP 563 compatibility for runtime type checking.
  • Requests are automatically wrapped as AirRequest objects via custom_route_handler (lines 122-128), providing enhanced request utilities.
  • Return values automatically render as HTML through integration with AirResponse and RouterMixin._wrap_endpoint.
  • Every endpoint gets a .url() method (lines 108-110) for programmatic URL generation without manual route naming.
  • Zero configuration requiredAirRouter sets route_class=AirRoute by default, making the customization transparent to developers.

Frequently Asked Questions

What is the difference between AirRoute and FastAPI's APIRoute?

AirRoute extends APIRoute to provide Air-specific request handling. While APIRoute passes raw Starlette requests to your handlers, AirRoute wraps them as AirRequest objects and handles the resolution of postponed type annotations. It also attaches reverse-URL helpers to endpoints, features that standard FastAPI routes do not provide by default.

Do I need to manually specify AirRoute when creating routes?

No, AirRoute is automatically applied when you use Air's router classes. The AirRouter class in src/air/routing.py passes route_class=AirRoute to FastAPI's APIRouter constructor by default. Whether you use @app.get() on an Air() instance or create routes through AirRouter, the framework handles the AirRoute assignment transparently.

How does AirRoute handle type annotations with postponed evaluation?

AirRoute resolves string annotations during initialization. In the __init__ method (lines 101-117 of src/air/routing.py), it rebuilds the endpoint's signature to resolve postponed annotations (PEP 563) into actual types. This ensures that when you use from __future__ import annotations, Air can still correctly identify AirRequest parameters and HTML tag return types at runtime.

Can I use regular FastAPI dependencies and validation with AirRoute?

Yes, AirRoute maintains full FastAPI compatibility. Because AirRoute inherits from APIRoute, all standard FastAPI features including dependency injection, Depends(), query parameter validation, and OpenAPI schema generation continue to work exactly as they do in standard FastAPI applications. The customizations only add Air-specific functionality without removing any FastAPI capabilities.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →