Routing
Kuí's routing is based on Radix Tree.
Basic Usage¶
Using Decorators¶
Similar to frameworks like Bottle/Flask, Kuí supports registering routes using decorators. In the example below, name is the route name, which is used for reverse route lookup.
from kui.asgi import Kui
app = Kui()
@app.router.http("/hello", name="hello")
async def hello():
...
@app.router.socket("/ws", name="echo")
async def echo():
...
Tip
If name is not specified, the registered callable object's __name__ attribute will be used as the route name by default.
Notice
If the name of a route is set to None, the route cannot be found using name.
Route Objects¶
In fact, the decorator route declaration is a shortcut for the following methods:
from kui.asgi import Kui, HttpRoute, SocketRoute
app = Kui()
async def hello():
...
async def echo():
...
app.router << HttpRoute("/hello", hello, name="hello")
app.router << SocketRoute("/ws", echo, name="echo")
The route objects in Kuí are as follows:
# Http
HttpRoute(path: str, endpoint: Any, name: Optional[str] = "")
# WebSocket
SocketRoute(path: str, endpoint: Any, name: Optional[str] = "")
-
pathspecifies the string that the route can match -
endpointspecifies the callable object corresponding to the route -
namespecifies the name of the route. WhennameisNone, the route will have no name; whennameis"", theendpoint.__name__will be automatically used as the route name.
Middleware¶
You can use decorators on route objects, which will be applied to the endpoint. However, unlike using decorators directly on the endpoint, it applies to the Kuí preprocessed endpoint.
Notice
In this document, the decorators registered in this way are called middleware. The term "middleware" is mainly used to be consistent with other frameworks.
You can register multiple decorators in the same way as registering regular decorators, and they will be executed from outer to inner.
Furthermore, you can register middleware when using decorators for route registration, as shown below. The execution order is also from right to left.
Restricting Request Methods¶
Notice
When specifying support for the GET method, HEAD will be automatically allowed.
Tip
When request methods are restricted, OPTIONS requests will be handled automatically. Otherwise, you need to handle OPTIONS requests manually.
When registering routes using decorators, you can directly restrict the request methods that the route can accept. Currently, only the following five HTTP methods are supported for restriction. If you don't specify any methods, all request methods are allowed by default.
from kui.asgi import Kui
app = Kui()
@app.router.http.get("/get")
async def need_get():
...
@app.router.http.post("/post")
async def need_post():
...
@app.router.http.put("/put")
async def need_put():
...
@app.router.http.patch("/patch")
async def need_patch():
...
@app.router.http.delete("/delete")
async def need_delete():
...
The above code internally uses the required_method decorator to achieve method restriction. You can also manually register the decorator, which allows for more types of requests. The code example is as follows:
from kui.asgi import Kui, required_method
app = Kui()
@app.router.http("/get", middlewares=[required_method("GET")])
async def need_get():
...
@app.router.http("/connect", middlewares=[required_method("CONNECT")])
async def need_connect():
...
List-Based Registration¶
Kuí also supports list-based registration similar to Django:
from kui.asgi import Kui, HttpRoute
async def hello():
return "hello world"
app = Kui(routes=[
HttpRoute("/hello", hello, name="hello"),
])
Path Parameters¶
You can use {name:type} to specify path parameters. Currently supported types are str, int, decimal, date, uuid, and any.
Tip
If the type of a path parameter is str, you can omit :str and directly use {name}.
Notice
str cannot match /. If you need to match /, use any.
Notice
any is a very special parameter type. It can only appear at the end of the path and can match any character.
from kui.asgi import Kui, request
app = Kui()
@app.router.http("/{username:str}")
async def what_is_your_name():
return request.path_params["username"]
Reverse Lookup¶
In some cases, you may need to generate the corresponding URL value based on the route name. You can use app.router.url_for for this purpose.
from kui.asgi import Kui, request
app = Kui()
@app.router.http("/hello", name="hello")
@app.router.http("/hello/{name}", name="hello-with-name")
async def hello():
return f"hello {request.path_params.get('name')}"
assert app.router.url_for("hello") == "/hello"
assert app.router.url_for("hello-with-name", {"name": "Aber"}) == "/hello/Aber"
Route Grouping¶
When you need to group certain routes together, you can use the Routes object.
The Routes object has an .http method that allows you to register routes using decorators, similar to app.router.
Routes also allows you to use route declaration similar to Django, as shown in the example below.
from kui.asgi import Routes, HttpRoute
async def hello(request):
return "hello world"
routes = Routes(
HttpRoute("/hello", hello),
)
You can register all routes in a Routes object to app.router using the << operator, and the result of this operation is app.router. This means you can chain the calls.
from .app1.urls import routes as app1_routes
from .app2.urls import routes as app2_routes
app.router << app1_routes << app2_routes
Alternatively, you can pass it directly when initializing the Kui object.
Route Combination¶
Routes can be easily combined with other Routes objects.
The result of << is the left-hand Routes object, which means you can chain it, as shown below.
from .app1.urls import routes as app1_routes
from .app2.urls import routes as app2_routes
Routes() << app1_routes << app2_routes
You can also merge two Routes objects into a new Routes object instead of merging one into the other.
from .app1.urls import routes as app1_routes
from .app2.urls import routes as app2_routes
new_routes = app1_routes + app2_routes
Namespace¶
You can set a namespace parameter for Routes, which will add the namespace: prefix to the name of each route (if any) in the Routes object. This helps avoid route name conflicts between different namespaces.
When using app.router.url_for, don't forget to add the namespace prefix of the route.
Middleware Registration¶
With Routes, you can register one or more middleware for the entire group of routes. Here is a simple example:
def one_http_middleware(endpoint):
async def wrapper():
return await endpoint()
return wrapper
def one_socket_middleware(endpoint):
async def wrapper():
await endpoint()
return wrapper
routes = Routes(
...,
http_middlewares=[one_http_middleware],
socket_middlewares=[one_socket_middleware]
)
Common Prefix¶
Sometimes you may want to group routes under the same prefix. The following two code snippets have the same result.
This operation creates a completely new route sequence and does not affect the original route sequence. Therefore, you can repeat the // operation on the same Routes object.
Note
When using routes = "prefix" // Routes(......), the subsequent routes registered using @routes.http will not automatically have the "prefix" prefix. You should perform the "prefix" // routes operation after all routes in a route group have been registered.
Other Route Grouping¶
By building a sequence of route objects, you can write your preferred route registration style, and they will all be merged into the Radix Tree.
MultimethodRoutes¶
MultimethodRoutes is a special route sequence that allows you to register routes using the following method, splitting different methods under the same PATH to multiple functions without explicitly using classes. Other than that, it is the same as Routes.