How to Use the `.url()` Method for URL Generation in Air Routes

The .url() method is automatically attached to every route-decorated function in Air, enabling reverse URL lookup with optional query parameters via func.url(**path_params, query_params={}).

Air's routing layer provides a powerful URL generation helper that eliminates hardcoded paths in your web applications. When you register a function using route decorators like @app.get() or @app.post(), the framework automatically injects a .url() method for reverse URL resolution. This feature leverages FastAPI/Starlette's underlying path resolution while adding convenient query parameter support.

How the .url() Method Works

Route Decoration and Helper Injection

According to the source code in src/air/routing.py, the RouterMixin._route method attaches the URL helper at line 308:

decorated.url = self._url_helper(name or getattr(func, "__name__", "unknown"))

This assignment occurs immediately after the function is decorated, ensuring every route handler carries the .url() capability regardless of HTTP verb.

URL Building Implementation

The _url_helper method (lines 381-390) constructs the final URL by calling url_path_for() and conditionally appending query strings:

def helper_function(**params: Any) -> str:
    query_params = params.pop("query_params", None)
    path = self.url_path_for(name, **params)
    if query_params is None:
        return path
    query_string = urlencode(query_params, doseq=True)
    return f"{path}?{query_string}" if query_string else path

This implementation supports both path parameters (passed as keyword arguments) and query parameters (passed via the special query_params dictionary).

Generating URLs for GET Routes

Basic Reverse Lookup

To generate a URL for a simple GET endpoint, call .url() on the decorated function with the required path parameters:

import air

app = air.Air()

@app.get("/users/{user_id}")
def get_user(user_id: int):
    # URL to this same endpoint:

    return air.H1(f"URL: {get_user.url(user_id=42)}")   # → "/users/42"

As implemented in feldroy/air, this pattern works because @app.get triggers the helper injection in RouterMixin._route.

Cross-Route URL Generation

You can generate links to other routes within your application by calling .url() on the target function:

@app.get("/profile/{username}")
def profile_page(username: str):
    return air.H1(f"Profile page for {username}")

@app.get("/")
def index():
    # Generate a link to the profile page

    return air.H1(f"Link: {profile_page.url(username='johndoe')}")
    # → "/profile/johndoe"

This pattern is validated in the test suite under test_air_router_get_url_method_different_path (lines 75-82 of tests/test_routing.py).

Working with POST Routes and Form Actions

The .url() method works for any HTTP verb, including POST, PUT, PATCH, and DELETE. This is particularly useful for setting form actions dynamically:

@app.post("/submit")
async def submit_form():
    return air.H1("Submitted!")

@app.get("/form")
def form_page():
    # Use the POST route's URL as the form action

    return air.Form(..., method="post", action=submit_form.url())

The test test_air_router_post_with_url_method (lines 68-74 of tests/test_routing.py) confirms this behavior works identically across all HTTP methods.

Adding Query Parameters

To append query strings to generated URLs, pass a dictionary to the query_params argument:

@app.get("/items/{item_id}")
def get_item(item_id: int, page: int = 1):
    return air.P(f"Item {item_id}, page {page}")

# Build a URL with a query string:

url = get_item.url(item_id=5, query_params={"page": 2, "tags": ["a", "b"]})

# → "/items/5?page=2&tags=a&tags=b"

The urlencode function handles sequence values (like lists) using doseq=True, ensuring proper serialization of multi-value parameters.

Error Handling for Missing Parameters

If you omit required path parameters, the method raises NoMatchFound:

@app.get("/item/{item_id}")
def item_detail(item_id: int):
    ...

# Missing required path param raises NoMatchFound

try:
    bad_url = item_detail.url()
except air.router.NoMatchFound:
    print("Missing parameters!")

This validation is tested in test_air_router_get_with_url_method_throws_error (lines 52-60 of tests/test_routing.py).

Summary

  • Automatic injection: Every route decorator (@app.get, @app.post, etc.) attaches .url() via _url_helper() in src/air/routing.py (line 308).
  • Reverse lookup: Call function.url(**path_params) to generate the path string for any registered route.
  • Query support: Pass query_params={"key": "value"} to append URL-encoded query strings.
  • Universal compatibility: Works with all HTTP verbs and route types defined in the Air framework.
  • Error safety: Missing required parameters raise NoMatchFound exceptions for debugging.

Frequently Asked Questions

How do I access the .url() method on a route function?

The .url() method is available immediately after decoration. Simply reference the function name followed by .url():

@app.get("/users/{id}")
def user_detail(id: int): ...

# Access anywhere in your code

link = user_detail.url(id=42)

Can I use .url() with POST, PUT, and DELETE routes?

Yes. The method attaches to any function decorated with route handlers regardless of HTTP verb. This is particularly useful for generating form actions pointing to POST endpoints or links to DELETE confirmation pages.

How do I add query strings to URLs generated with .url()?

Pass a dictionary to the special query_params keyword argument:

url = my_route.url(id=1, query_params={"sort": "date", "order": "desc"})

The framework automatically URL-encodes the dictionary and appends it to the generated path.

What happens if I forget to provide required path parameters?

The method raises air.router.NoMatchFound. You should wrap URL generation in try-except blocks when parameters are dynamically constructed or potentially incomplete:

try:
    url = route.url()
except air.router.NoMatchFound:
    url = "/fallback-path"

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 →