Dynamic Routes in aiohttp: Flexible URL Handling

Dynamic routes in aiohttp allow you to create flexible URL patterns that can handle variable inputs.

In this tutorial, you’ll learn how to implement dynamic routes, from basic usage to advanced methods.

 

 

Basic Dynamic Routes

To create a dynamic route with a variable segment, use curly braces {} in your URL pattern:

from aiohttp import web
async def greet(request):
    name = request.match_info['name']
    return web.Response(text=f"Hello, {name}!")
app = web.Application()
app.router.add_get('/{name}', greet)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

When you access http://localhost:8080/Adam, you’ll see “Hello, Adam!” in your browser.

The {name} segment in the URL pattern captures any value provided in that position and makes it available in the request.match_info dictionary.

 

Regular Expression Routes

You can use regular expressions to define more complex URL patterns:

import re
from aiohttp import web
async def handle_product(request):
    product_code = request.match_info['code']
    return web.Response(text=f"Product code: {product_code}")
app = web.Application()
app.router.add_get(r'/product/{code:[A-Z]{2}\d{4}}', handle_product)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

This route will match URLs like http://localhost:8080/product/AB1234.

The regular expression [A-Z]{2}\d{4} allows only valid product codes where the product code consists of two uppercase letters followed by four digits.

 

Wildcard Routes

To match any number of path segments, use the {tail:.*} syntax:

from aiohttp import web
async def catch_all(request):
    path = request.match_info['tail']
    return web.Response(text=f"Caught path: /{path}")
app = web.Application()
app.router.add_get('/{tail:.*}', catch_all)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

This route will match any URL path, capturing the entire path after the domain.

For example, http://localhost:8080/any/path/here will display “Caught path: /any/path/here”.

 

Route Prioritization and Order

In aiohttp, the order in which routes are added to the application matters because the router processes them sequentially.

This means that more specific routes should be added before more general ones to ensure they are matched correctly.

from aiohttp import web
async def handle_root(request):
  return web.Response(text="This is the root handler")
async def handle_user(request):
  user_id = request.match_info.get('user_id', "Anonymous")
  return web.Response(text=f"User ID: {user_id}")
async def handle_wildcard(request):
  return web.Response(text="This is the wildcard handler")
app = web.Application()

# Add the more specific route first
app.router.add_get('/user/{user_id}', handle_user)

# Add a more general route later
app.router.add_get('/user/{tail:.*}', handle_wildcard)
app.router.add_get('/', handle_root)
web.run_app(app)

Specific Route: The route /user/{user_id} is added first. This route is more specific because it matches a URL pattern with a specific structure, expecting a user_id.

Wildcard Route: The route /user/{tail:.*} is added after the specific route.

This route uses a wildcard to match any path that starts with /user/ and capture the rest of the path in the tail variable.

If this route were added before the specific route, it would match all /user/... requests and will prevent you from accessing the specific route.

 

URL Query Parameters with Dynamic Routes

Combine path variables and query parameters

You can use both path variables and query parameters in your routes:

from aiohttp import web
async def search_products(request):
    category = request.match_info['category']
    query = request.query.get('q', '')
    return web.Response(text=f"Search in category '{category}' for '{query}'")
app = web.Application()
app.router.add_get('/{category}/search', search_products)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

This route will handle URLs like http://localhost:8080/electronics/search?q=laptop, where “electronics” is the category from the path, and “laptop” is the search query from the query parameter.

You can handle both optional and required query parameters:

from aiohttp import web
async def filter_products(request):
    category = request.match_info['category']
    min_price = request.query.get('min_price')
    max_price = request.query.get('max_price')
    if min_price is None or max_price is None:
        return web.Response(text="Both min_price and max_price are required", status=400)

    return web.Response(text=f"Filtering {category} products between ${min_price} and ${max_price}")
app = web.Application()
app.router.add_get('/{category}/filter', filter_products)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

This route requires both min_price and max_price query parameters.

A request to http://localhost:8080/electronics/filter?min_price=100&max_price=500 will work, but if you remove any parameter, it will result in a 400 Bad Request response.

 

Internationalization with Dynamic Routes

Localizing URL patterns

You can create localized URL patterns for different languages:

from aiohttp import web
async def welcome(request):
    lang = request.match_info['lang']
    name = request.match_info['name']
    greetings = {
        'en': 'Hello',
        'es': 'Hola',
        'fr': 'Bonjour'
    }
    greeting = greetings.get(lang, greetings['en'])
    return web.Response(text=f"{greeting}, {name}!")
app = web.Application()
app.router.add_get('/{lang:(en|es|fr)}/{name}', welcome)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

This route supports localized greetings for English, Spanish, and French.

If you access http://localhost:8080/es/Maria, it will return “Hola, Maria!”, while http://localhost:8080/fr/Pierre will return “Bonjour, Pierre!”.

Language-specific Routing

You can create separate routes for different languages:

from aiohttp import web
async def welcome_en(request):
    name = request.match_info['name']
    return web.Response(text=f"Welcome, {name}!")
async def welcome_es(request):
    name = request.match_info['name']
    return web.Response(text=f"Bienvenido, {name}!")
app = web.Application()
app.router.add_get('/en/{name}', welcome_en)
app.router.add_get('/es/{name}', welcome_es)
web.run_app(app)

Output:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

This method uses separate handlers for each language.

If you access http://localhost:8080/en/John, it will return “Welcome, John!”, while http://localhost:8080/es/Juan will return “Bienvenido, Juan!”.

Leave a Reply

Your email address will not be published. Required fields are marked *