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:
src/air/routing.py– ContainsAirRoute,RouterMixin, andAirRouterdefinitionssrc/air/requests.py– ImplementsAirRequestused by the custom route handlersrc/air/responses.py– ProvidesAirResponsefor automatic tag renderingsrc/air/utils.py– Suppliescached_signature,cached_unwrap, anddefault_generate_unique_idhelpers for signature fixing and unique ID generationsrc/air/exception_handlers.py– Default 404 handler referenced byAirRouter
Summary
- AirRoute subclasses FastAPI's APIRoute in
src/air/routing.pyto 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
AirResponseandRouterMixin._wrap_endpoint. - Every endpoint gets a
.url()method (lines 108-110) for programmatic URL generation without manual route naming. - Zero configuration required –
AirRoutersetsroute_class=AirRouteby 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →