Skip to content

Routing

Lilya has a simple but highly effective routing system capable of handling from simple routes to the most complex.

Using an enterprise application as example, the routing system surely will not be something simple with 20 or 40 direct routes, maybe it will have 200 or 300 routes where those are split by responsabilities, components and packages and imported also inside complex design systems. Lilya handles with those cases without any kind of issues at all.

Router

The Router is the main object that links the whole Lilya to the Path, WebSocketPath and Include.

Router class

The router class is composed by many attributes that are by default populated within the application but Lilya also allows to add extra custom routers as well but another way is to add a ChildLilya application.

from lilya.apps import Lilya
from lilya.requests import Request
from lilya.routing import Path


async def create(request: Request):
    await request.json()
    ...


app = Lilya(
    routes=[
        Path(
            "/create",
            handler=create,
            methods=["POST"],
        )
    ],
)

The main Router class is instantiated within the Lilya application with the given routes and the application starts.

Parameters

When creating a Path or WebSocketPath function handler, you have two ways of getting the path parameters.

  • Lilya auto discovers and injects them for you.
  • You get them from the request object.

Auto discovering the parameters

This is probably the easiest and simplest way.

from lilya.apps import Lilya
from lilya.requests import Request
from lilya.responses import JSONResponse
from lilya.routing import Include, Path


def update(item_id: int):
    return item_id


def another_update(request: Request):
    item_id = request.path_params["item_id"]
    return JSONResponse({"Success", {item_id}})


app = Lilya(
    routes=[
        Include(
            "/update",
            routes=[
                Path(
                    "/update/partial/{item_id:int}",
                    handler=update,
                    methods=["PATCH"],
                ),
                Path(
                    path="/last/{item_id:int}",
                    handler=another_update,
                    methods=["PATCH"],
                ),
            ],
        )
    ]
)

The customer_id declared in the path was also declared in the function handler allowing Lilya to inject the values found by order from the path parameters for you.

From the request path parameters.

from lilya.apps import Lilya
from lilya.requests import Request
from lilya.responses import JSONResponse
from lilya.routing import Path


def update(request: Request):
    item_id = request.path_params["item_id"]
    return JSONResponse({"Success", {item_id}})


app = Lilya(
    routes=[
        Path(
            "/update/{item_id:int}",
            handler=update,
            methods=["PUT"],
        ),
    ]
)

The customer_id declared in the path was obtained by accessing the request object.

Custom Router

Let's assume there are specific customer submodules inside a customers dedicated file. There are two way of separating the routes within the application, using Include, a ChildLilya or by creating another router. Let's focus on the latter.

/application/apps/routers/customers.py
from lilya.requests import Request
from lilya.responses import JSONResponse
from lilya.routing import Path, Router


async def create(request: Request):
    await request.json()
    return JSONResponse({"created": True})


async def get_customer(customer_id: int):
    return JSONResponse({"created": customer_id})


router = Router(
    path="/customers",
    routes=[
        Path(
            "/{customer_id:int}",
            handler=get_customer,
        ),
        Path(
            "/create",
            handler=create,
            methods=["POST"],
        ),
    ],
)

Above you create the /application/apps/routers/customers.py with all the information you need. It does not need to be in one file, you can have a entirely seperate package just to manage the customer, it is up to you.

Now you need to add the new custom router into the main application.

/application/app.py
from lilya.apps import Lilya
from lilya.routing import Include

app = Lilya(
    routes=[
        Include(
            "/",
            app=...,
        )
    ]
)

This simple and your router is added to the main Lilya application.

ChildLilya Application

What is this? We call it ChildLilya but in fact is simply Lilya but under a different name mostly for visualisation purposes and for the sake of organisation.

Check

Using ChildLilya or Lilya is exactly the same thing, it is only if you prefer to create a sub application and you prefer to use a different class instead of Lilya to make it more organised.

How does it work

Let's use the same example used in the custom routers with the customers specific routes and rules.

/application/apps/routers/customers.py
from lilya.apps import ChildLilya
from lilya.requests import Request
from lilya.responses import JSONResponse
from lilya.routing import Path


async def create(request: Request) -> JSONResponse:
    await request.json()
    return JSONResponse({"created": True})


async def get_customer() -> JSONResponse:
    return JSONResponse({"created": True})


router = ChildLilya(
    routes=[
        Path("/{customer_id:int}", handler=get_customer),
        Path("/create", handler=create, methods=["POST"]),
    ],
    include_in_schema=...,
)

Since the ChildLilya is a representation of a Lilya class, we can pass the otherwise limited parameters in the custom router and all the parameters available to Lilya.

You can add as many ChildLilya as you desire, there are no limits.

Now in the main application:

/application/app.py
from apps.routers.customers import router as customers_router

from lilya.apps import Lilya
from lilya.routing import Include

app = Lilya(
    routes=[
        Include("/customers", app=customers_router),
    ]
)

Adding nested applications

/application/app.py
from apps.routers.clients import router as clients_router
from apps.routers.customers import router as customers_router
from apps.routers.restrict import router as restrict_router

from lilya.apps import Lilya
from lilya.routing import Include

app = Lilya(
    routes=[
        Include("/customers", app=customers_router),
        Include(
            "/api/v1",
            routes=[
                Include("/clients", clients_router),
                Include("/restrict", routes=[Include("/access", restrict_router)]),
            ],
        ),
    ]
)

The example above, it is showing that you could even add the same application within nested includes and for each include you can add specific unique permissions and middlewares which are available on each instance of the Include. The options are endeless.

Note

In terms of organisation, ChildLilya has a clean approach to the isolation of responsabilities and allow treating every individual module separately and simply adding it in to the main application in the form of Include.

Tip

Treat the ChildLilya as an independent Lilya instance.

Check

When adding a ChildLilya or Lilya application, don't forget to add the unique path to the base Include, this way you can assure the routes are found properly.

Utils

The Router object has some available functionalities that can be useful.

add_route()

from lilya.apps import Lilya

app = Lilya()

app.add_route(
    path=...,
    methods=...,
    name=...,
    middleware=...,
    permissions=...,
    include_in_schema=...,
)

Parameters

  • path - The path for the child lilya.
  • name - Name of the route.
  • handler - The function handler.
  • methods - The available http verbs for the path.
  • include_in_schema - If route should be added to the OpenAPI Schema
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.

add_websocket_route()

from lilya.apps import Lilya

app = Lilya()

app.add_websocket_route(
    path=...,
    handler=...,
    name=...,
    middleware=...,
    permissions=...,
)

Parameters

  • path - The path for the child lilya.
  • name - Name of the route.
  • handler - The function handler.
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.

add_child_lilya()

from lilya.apps import ChildLilya, Lilya
from lilya.routing import Path


async def home() -> str:
    return "home"


child = ChildLilya(
    routes=[
        Path("/", handler=home, name="view"),
    ]
)

app = Lilya()

app.add_child_lilya(
    path="/child",
    child=child,
    name=...,
    middleware=...,
    permissions=...,
    include_in_schema=...,
    deprecated=...,
)

Parameters

  • path - The path for the child lilya.
  • child - The ChildLilya instance.
  • name - Name of the route.
  • handler - The function handler.
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.
  • include_in_schema - Boolean if this ChildLilya should be included in the schema.
  • deprecated - Boolean if this ChildLilya should be marked as deprecated.

Path

The object that connects and builds the application urls or paths. It maps the function handler with the application routing system

Parameters

  • path - The path for the child lilya.
  • name - Name of the route.
  • handler - The function handler.
  • methods - The available http verbs for the path.
  • include_in_schema - If route should be added to the OpenAPI Schema
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.
  • deprecated - Boolean if this ChildLilya should be marked as deprecated.
from lilya.apps import Lilya, Request
from lilya.routing import Path


async def homepage(request: Request) -> str:
    return "Hello, home!"


app = Lilya(
    routes=[
        Path(
            handler=homepage,
        )
    ]
)

WebSocketPath

Same principle as Path with one particularity. The websockets are async.

Parameters

  • path - The path for the child lilya.
  • name - Name of the route.
  • handler - The function handler.
  • include_in_schema - If route should be added to the OpenAPI Schema
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.
  • deprecated - Boolean if this ChildLilya should be marked as deprecated.
from lilya.apps import Lilya
from lilya.routing import WebSocketPath
from lilya.websockets import WebSocket


async def world_socket(websocket: WebSocket) -> None:
    await websocket.accept()
    msg = await websocket.receive_json()
    assert msg
    assert websocket
    await websocket.close()


app = Lilya(
    routes=[
        WebSocketPath(
            "/{path_param:str}",
            handler=world_socket,
        ),
    ]
)

Include

Includes are unique to Lilya, powerful and with more control and allows:

  1. Scalability without issues.
  2. Clean routing design.
  3. Separation of concerns.
  4. Separation of routes.
  5. Reduction of the level of imports needed through files.
  6. Less human lead bugs.

Warning

Includes DO NOT take path parameters. E.g.: Include('/include/{id:int}, routes=[...]).

Include and application

This is a very special object that allows the import of any routes from anywhere in the application. Include accepts the import via namespace or via routes list but not both.

When using a namespace, the Include will look for the default route_patterns list in the imported namespace (object) unless a different pattern is specified.

The patten only works if the imports are done via namespace and not via routes object.

Parameters

  • path - The path for the child lilya.
  • app - An application can be anything that is treated as an ASGI application. The app can be an ASGI related app of a string <dotted>.<module> location of the app.
  • routes - A global list of lilya routes. Those routes may vary and those can be Path, WebSocketPath or even another Include.
  • namespace - A string with a qualified namespace from where the URLs should be loaded.
  • pattern - A string pattern information from where the urls shall be read from.
  • name - Name of the Include.
  • permissions - A list of permissions to serve the application incoming requests (HTTP and Websockets).
  • middleware - A list of middleware to run for every request.
  • exception handlers - A dictionary of exception types (or custom exceptions) and the handler functions on an application top level. Exception handler callables should be of the form of handler(request, exc) -> response and may be be either standard functions, or async functions.
  • include_in_schema - If route should be added to the OpenAPI Schema
  • deprecated - Boolean if this Include should be marked as deprecated.
myapp/urls.py
from lilya.routing import Include

route_patterns = [
    Include("/", namespace="myapp.accounts.urls"),
]
src/myapp/urls.py
from myapp.accounts.urls import route_patterns

from lilya.routing import Include

route_patterns = [
    Include("/", routes=route_patterns),
]

This is an alternative of loading the app via string import instead of passing the object directly.

src/myapp/urls.py
from lilya.routing import Include

# There is an app in the location `myapp.asgi_or_wsgi.apps.child_lilya`

route_patterns = [
    Include(
        "/child",
        app="myapp.asgi_or_wsgi.apps.child_lilya",
    ),
]

Using a different pattern

src/myapp/accounts/controllers.py
from lilya.requests import Request
from lilya.responses import JSONResponse
from lilya.websockets import WebSocket


async def update_product(request: Request, product_id: int):
    data = await request.json()
    return {"product_id": product_id, "product_name": data["name"]}


async def home():
    return JSONResponse({"detail": "Hello world"})


async def another(request: Request):
    return {"detail": "Another world!"}


async def world_socket(websocket: WebSocket):
    await websocket.accept()
    msg = await websocket.receive_json()
    assert msg
    assert websocket
    await websocket.close()
src/myapp/accounts/urls.py
from lilya.routing import Path, WebSocketPath

from .views import another, home, update_product, world_socket

my_urls = [
    Path(
        "/product/{product_id}",
        handler=update_product,
        methods=["PUT"],
    ),
    Path("/", handler=home),
    Path("/another", handler=another),
    WebSocketPath(
        "/{path_param:str}",
        handler=world_socket,
    ),
]
src/myapp/urls.py
from lilya.routing import Include

route_patterns = [
    Include(
        "/",
        namespace="myapp.accounts.urls",
        pattern="my_urls",
    ),
]

Include and application instance

The Include can be very helpful mostly when the goal is to avoid a lot of imports and massive list of objects to be passed into one single object. This can be particularly useful to make a clean start Lilya object as well.

Example:

src/urls.py
from lilya.routing import Include

route_patterns = [
    Include(
        "/",
        namespace="myapp.accounts.urls",
        pattern="my_urls",
    )
]
src/app.py
from lilya.apps import Lilya
from lilya.routing import Include

app = Lilya(
    routes=[
        Include("/", namespace="src.urls"),
    ]
)

Nested Routes

When complexity increses and the level of routes increases as well, Include allows nested routes in a clean fashion.

from lilya.apps import Lilya
from lilya.routing import Include, Path


async def me() -> None: ...


app = Lilya(
    routes=[
        Include(
            "/",
            routes=[
                Path(
                    path="/me",
                    handler=me,
                )
            ],
        )
    ]
)
from lilya.apps import Lilya
from lilya.routing import Include, Path


async def me() -> None: ...


app = Lilya(
    routes=[
        Include(
            "/",
            routes=[
                Include(
                    "/another",
                    routes=[
                        Include(
                            "/multi",
                            routes=[
                                Include(
                                    "/nested",
                                    routes=[
                                        Include(
                                            "/routing",
                                            routes=[
                                                Path(path="/me", handler=me),
                                                Include(
                                                    path="/imported",
                                                    namespace="myapp.routes",
                                                ),
                                            ],
                                        )
                                    ],
                                )
                            ],
                        )
                    ],
                )
            ],
        )
    ]
)

Include supports as many nested routes with different paths and Includes as you desire to have. Once the application starts, the routes are assembled and it will immediatly available.

Nested routes also allows all the functionalities on each level, from middleware and permissions.

Application routes

Warning

Be very careful when using the Include directly in the Lilya(routes[]), importing without a path may incur in some routes not being properly mapped.

Only applied to the application routes:

If you decide to do this:

from lilya.apps import Lilya
from lilya.routing import Include

app = Lilya(
    routes=[
        Include(
            "/",
            namespace="src.urls",
            name="root",
        ),
        Include(
            "/accounts",
            namespace="accounts.v1.urls",
            name="accounts",
        ),
    ]
)

Host

If you aim to utilize distinct routes for the same path contingent on the Host header, Lilya provides a solution.

It's important to note that the port is disregarded from the Host header during matching. For instance, Host(host='example.com:8081', ...) will be processed regardless of whether the Host header contains a port different from 8081 (e.g., example.com:8083, example.org). Therefore, if the port is essential for path_for purposes, you can explicitly specify it.

There are multiple approaches to establish host-based routes for your application.

from lilya.apps import Lilya
from lilya.routing import Host, Router

internal = Router()
api = Router()
external = Router()

routes = [Host("api.example.com", api, name="api")]
app = Lilya(routes=routes)

app.host("www.example.com", internal, name="intenal_site")
external_host = Host("external.example.com", external)

app.router.routes.append(external)

URL lookups can encompass host parameters, similar to how path parameters are included.

from lilya.requests import Request
from lilya.routing import Host, Include, Path, Router


def user(): ...


def detail(username: str): ...


routes = [
    Host(
        "{subdomain}.example.com",
        name="sub",
        app=Router(
            routes=[
                Include(
                    "/users",
                    name="users",
                    routes=[
                        Path("/", user, name="user"),
                        Path("/{username}", detail, name="detail"),
                    ],
                )
            ]
        ),
    )
]

request = Request(...)

url = request.path_for("sub:users:user", username=..., subdomain=...)
url = request.path_for("sub:users:detail", subdomain=...)

Routes priority

The application routes in simple terms are simply prioritised. The incoming paths are matched agains each Path, WebSocketPath and Include in order.

In cases where more than one, let's say Path could match an incoming path, you should ensure that more specifc routes are listed before general cases.

Example:

from lilya.apps import Lilya
from lilya.routing import Path


async def user(): ...


async def active_user(): ...


# Don't do this: `/users/me`` will never match the incoming requests.
app = Lilya(
    routes=[
        Path(
            "/users/{username}",
            handler=user,
        ),
        Path(
            "/users/me",
            handler=active_user,
        ),
    ]
)

# Do this: `/users/me` is tested first and both cases will work.
app = Lilya(
    routes=[
        Path(
            "/users/me",
            handler=active_user,
        ),
        Path(
            "/users/{username}",
            handler=user,
        ),
    ]
)

Path parameters

Paths can use templating style for path components. The path params are only applied to Path and WebSocketPath and not applied to Include.

Remember that there are two ways of handling with the path parameters.

async def customer(customer_id: Union[int, str]) -> None:
    ...


async def floating_point(number: float) -> None:
    ...

Path("/customers/{customer_id}/example", handler=customer)
Path("/floating/{number:float}", handler=customer)

By default this will capture characters up to the end of the path of the next / and it will become /customers/{customer_id}/example.

Transformers can be used to modify what is being captured and the type of what is being captured. Lilya provides some default path transformers.

  • str returns a string, and is the default.
  • int returns a Python integer.
  • float returns a Python float.
  • uuid returns a Python uuid.UUID instance.
  • path returns the rest of the path, including any additional / characters.
  • datetime returns the datetime.

As per standard, the transformers are used by prefixing them with a colon:

Path('/customers/{customer_id:int}', handler=customer)
Path('/floating-point/{number:float}', handler=floating_point)
Path('/uploaded/{rest_of_path:path}', handler=uploaded)

Custom transformers

If a need for a different transformer that is not defined or available, you can also create your own.

from __future__ import annotations

import ipaddress

from lilya.transformers import Transformer, register_path_transformer


class IPTransformer(Transformer[str]):
    regex = r"((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\.(?!$)|$)){4}$"

    def transform(self, value: str) -> ipaddress.IPv4Address | ipaddress.IPv6Address:
        return ipaddress.ip_address(value)

    def normalise(self, value: ipaddress.IPv4Address | ipaddress.IPv6Address) -> str:
        return str(value)


register_path_transformer("ipaddress", IPTransformer())

With the custom transformer created you can now use it.

Path('/network/{address:ipaddress}', handler=network)

Middleware, exception Handlers and permissions

Examples

The following examples are applied to Path, WebSocketPath and Include.

We will be using Path for it can be replaced by any of the above as it is common among them.

Middleware

As specified before, the middleware of a Path are read from top down, from the parent to the very handler and the same is applied to exception handlers, and permissions.

from lilya.apps import Lilya
from lilya.middleware import DefineMiddleware
from lilya.protocols.middleware import MiddlewareProtocol
from lilya.routing import Path
from lilya.types import ASGIApp


class RequestLoggingMiddlewareProtocol(MiddlewareProtocol):
    def __init__(self, app: "ASGIApp", kwargs: str = "") -> None:
        self.app = app
        self.kwargs = kwargs


class ExampleMiddleware(MiddlewareProtocol):
    def __init__(self, app: "ASGIApp") -> None:
        self.app = app


async def homepage():
    return {"page": "ok"}


app = Lilya(
    routes=[
        Path(
            "/home",
            handler=homepage,
            middleware=[DefineMiddleware(ExampleMiddleware)],
        )
    ],
    middleware=[
        DefineMiddleware(RequestLoggingMiddlewareProtocol),
    ],
)

The above example illustrates the various levels where a middleware can be implemented and because it follows an parent order, the order is:

  1. Default application built-in middleware.
  2. RequestLoggingMiddlewareProtocol.
  3. ExampleMiddleware.

More than one middleware can be added to each list.

Exception Handlers

from lilya.apps import Lilya
from lilya.exceptions import InternalServerError, LilyaException, NotAuthorized
from lilya.requests import Request
from lilya.responses import JSONResponse
from lilya.routing import Path


async def http_lilya_handler(_: Request, exc: LilyaException):
    return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)


async def http_internal_server_error_handler(_: Request, exc: InternalServerError):
    return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)


async def http_not_authorized_handler(_: Request, exc: NotAuthorized):
    return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)


async def homepage() -> dict:
    return {"page": "ok"}


app = Lilya(
    routes=[
        Path(
            "/home",
            handler=homepage,
            exception_handlers={
                NotAuthorized: http_not_authorized_handler,
                InternalServerError: http_internal_server_error_handler,
            },
        )
    ],
    exception_handlers={LilyaException: http_lilya_handler},
)

The above example illustrates the various levels where the exception handlers can be implemented and follows a parent order where the order is:

  1. Default application built-in exception handlers.
  2. InternalServerError : http_internal_server_error_handler.
  3. NotAuthorized: http_not_authorized_handler.

More than one exception handler can be added to each mapping.

Permissions

Permissions are a must in every application. More on permissions and how to use them.

from lilya.apps import Lilya
from lilya.exceptions import PermissionDenied
from lilya.permissions import DefinePermission
from lilya.protocols.permissions import PermissionProtocol
from lilya.requests import Request
from lilya.routing import Include, Path
from lilya.types import ASGIApp, Receive, Scope, Send


class AllowAccess(PermissionProtocol):
    def __init__(self, app: ASGIApp, *args, **kwargs):
        super().__init__(app, *args, **kwargs)
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        request = Request(scope=scope, receive=receive, send=send)

        if "allow-access" in request.headers:
            await self.app(scope, receive, send)
            return
        raise PermissionDenied()


class AdminAccess(PermissionProtocol):
    def __init__(self, app: ASGIApp, *args, **kwargs):
        super().__init__(app, *args, **kwargs)
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        request = Request(scope=scope, receive=receive, send=send)

        if "allow-admin" in request.headers:
            await self.app(scope, receive, send)
            return
        raise PermissionDenied()


async def home():
    return "Hello world"


async def user(user: str):
    return f"Hello {user}"


# Via Path
app = Lilya(
    routes=[
        Path("/", handler=home),
        Path(
            "/{user}",
            handler=user,
            permissions=[
                DefinePermission(AdminAccess),
            ],
        ),
    ],
    permissions=[DefinePermission(AllowAccess)],
)


# Via Include
app = Lilya(
    routes=[
        Include(
            "/",
            routes=[
                Path("/", handler=home),
                Path(
                    "/{user}",
                    handler=user,
                    permissions=[
                        DefinePermission(AdminAccess),
                    ],
                ),
            ],
            permissions=[DefinePermission(AllowAccess)],
        )
    ]
)

More than one permission can be added to each list.

Reverse Path lookups

Frequently, there is a need to generate the URL for a specific route, especially in scenarios where a redirect response is required.

from lilya.apps import Lilya
from lilya.requests import Request
from lilya.routing import Path


def user(): ...


app = Lilya(
    routes=[
        Path("/user", user, name="user"),
    ]
)

request = Request(...)

# Path lookup here
path = request.path_for("user")

The lookups also allow path parameters.

from lilya.apps import Lilya
from lilya.requests import Request
from lilya.routing import Include, Path


def detail(): ...


app = Lilya(
    routes=[
        Include(
            "/users",
            routes=[
                Path("/{username}", detail, name="detail"),
            ],
            name="users",
        )
    ]
)

request = Request(...)

# Path lookup here
path = request.path_for("users:detail", username=...)

If an Include includes a name, subsequent submounts should employ a {prefix}:{name} format for reverse Path lookups.

Using the reverse

This is an alternative for the reverse path lookup. It can be particularly useful if you want to reverse a path in testing or in isolation.

Parameters

  • name - The name given to the path.
  • app - An ASGI application containing the routes. Useful for reversing paths on specific applications and/or testing. (Optional).
  • path_params - A dictionary like object containing the parameters that should be passed in a given path. (Optional).

Using the reverse, if no app parameter is specified, it will automatically default to the application or application router, which under normal circunstances, besides testing, it is the expected behaviour.

from lilya.apps import Lilya
from lilya.compat import reverse
from lilya.routing import Path


def user(): ...


app = Lilya(
    routes=[
        Path("/user", user, name="user"),
    ]
)

# Path lookup here
path = reverse("user")

# Reverse with a specific app
# Path lookup here
path = reverse("user", app=app)

The reverse also allow path parameters.

from lilya.apps import Lilya
from lilya.compat import reverse
from lilya.requests import Request
from lilya.routing import Include, Path


def detail(): ...


app = Lilya(
    routes=[
        Include(
            "/users",
            routes=[
                Path("/{username}", detail, name="detail"),
            ],
            name="users",
        )
    ]
)

request = Request(...)

# Path lookup here
path = reverse(
    "users:detail",
    path_params={"username": ...},
)

# Reverse with a specific app
# Path lookup here
path = reverse(
    "users:detail",
    app=app,
    path_params={"username": ...},
)