Dependency injection
先看一个最简单的例子,两个分页参数,首先通过 Type hint 标注它们都需要 int
类型,在给予它们 Query(...)
作为额外的类型描述。
Query
代表它们将会从 request.query_params
中读取值,...
作为第一个参数,意味着它没有默认值,也就是客户端请求该接口时必须传递值。譬如:?page_num=1&page_size=10
。
如果你使用 Query(10)
则意味着这个值可以不由前端传递,其默认值为 10
。
from typing_extensions import Annotated
from kui.asgi import Query
async def getlist(
page_num: Annotated[int, Query(...)],
page_size: Annotated[int, Query(...)],
):
...
也可以通过使用继承自 pydantic.BaseModel
的类作为类型注解来描述同一类型的全部参数,下例与上例是等价的。
from typing_extensions import Annotated
from kui.asgi import Query
from pydantic import BaseModel
class PageQuery(BaseModel):
page_num: int
page_size: int
async def getlist(query: Annotated[PageQuery, Query(exclusive=True)]):
...
同样的,你还可以使用其他对象来获取对应部分的参数,以下是对照:
Path
:request.path_params
Query
:request.query_params
Header
:request.headers
Cookie
:request.cookies
Body
:await request.data()
通过这样标注的请求参数,不仅会自动校验、转换类型,还能自动生成接口文档。在你需要接口文档的情况下,十分推荐这么使用。
Tip
路径参数(Path
)的校验错误是比较特别的,它会尝试调用用户自己注册的 404 异常处理方法或者默认的 404 异常处理方法返回 404 状态,就像没有找到路由一样,而不是像其他参数校验错误一样返回 422 状态。
依赖可调用对象¶
使用 Depends(func)
可以标注所依赖的可调用对象,在视图被调用前会调用并将返回值注入到视图的参数中。
Tip
你同样可以在这里进行参数标注,将会递归调用所有依赖的可调用对象以及需要注入的参数。
def get_name(name: Annotated[str, Query(...)]):
return name.lower()
async def hello(name: Annotated[str, Depends(get_name)]):
return f"hello {name}"
比较特殊的是,如果你使用 Depends(......)
标注的可调用对象是一个生成器函数,那么它将会被 contextlib
改造,yield
值被注入视图中,清理部分在视图函数退出后执行(无论视图函数是正常返回或是抛出异常,均会执行清理过程)。在获取某些需要清理的资源时,这特别有效。
async def get_db_connection():
connection = ... # get connection
try:
yield connection
finally:
await connection.close()
async def get_user(db: Annotated[Connection, Depends(get_db_connection)]):
...
在中间件中使用¶
在中间件中的使用方式并没有什么不同,直接在参数里描述即可。