
前几篇我们把接口的输入、输出、错误处理都过了一遍。这一篇来看另一个问题多个接口都要做同一件事比如都要校验请求头里的 token难道每个接口都复制粘贴一遍吗做完这篇后我们会有两个接口共用同一段校验逻辑接口函数本身只关心自己的业务公共逻辑被拿到外面去了。先看一个具体的问题假设现在有个需求所有接口都要在校验请求头里带一个x-token值必须是secret-token。如果不是返回 401。最直接的写法是这样的fromfastapiimportHeader,HTTPExceptionapp.get(/items/{item_id},response_modelItemPublic)defread_item(item_id:int,x_token:strHeader(...)):ifx_token!secret-token:raiseHTTPException(status_code401,detailInvalid token)ifitem_idnotinfake_items_db:raiseHTTPException(status_code404,detailItem not found)itemfake_items_db[item_id]return{id:item_id,**item}app.get(/users/{user_id})defread_user(user_id:int,x_token:strHeader(...)):ifx_token!secret-token:raiseHTTPException(status_code401,detailInvalid token)ifuser_idnotinfake_users_db:raiseHTTPException(status_code404,detailUser not found)return{id:user_id,**fake_users_db[user_id]}重点看if x_token ! secret-token这一段。它出现在两个接口里一模一样。如果再加十个接口就要复制粘贴十次。哪天 token 的校验规则变了比如要改成从数据库查就要改十个地方。这不是技术问题是维护问题。把公共逻辑拿出去FastAPI 的依赖注入就是为了解决这个问题。它不是什么玄学就是把一个函数的结果注入到另一个函数里。先创建一个dependencies.pyfromfastapiimportHeader,HTTPExceptiondefget_token_header(x_token:strHeader(...)):ifx_token!secret-token:raiseHTTPException(status_code401,detailInvalid token)returnx_token这个函数只做一件事检查x-token不通过就抛异常通过就把 token 返回。然后改写接口fromfastapiimportDependsfrom.dependenciesimportget_token_headerapp.get(/items/{item_id},response_modelItemPublic)defread_item(item_id:int,token:strDepends(get_token_header)):ifitem_idnotinfake_items_db:raiseHTTPException(status_code404,detailItem not found)itemfake_items_db[item_id]return{id:item_id,**item}app.get(/users/{user_id})defread_user(user_id:int,token:strDepends(get_token_header)):ifuser_idnotinfake_users_db:raiseHTTPException(status_code404,detailUser not found)return{id:user_id,**fake_users_db[user_id]}重点看token: str Depends(get_token_header)这一行。Depends(get_token_header)告诉 FastAPI在调用read_item之前先调用get_token_header把它的返回值赋给token参数。如果get_token_header抛了异常FastAPI 不会进read_item直接返回错误响应。在/docs里试一次启动服务.\.venv\Scripts\Activate.ps1$env:PYTHONIOENCODING utf-8$env:PYTHONUTF8 1fastapi dev app/main.py打开http://127.0.0.1:8000/docs点开GET /items/{item_id}会看到多了一个x-token的请求头输入框。这是 FastAPI 从get_token_header的参数定义里自动提取出来的不需要我们手动写文档。先不填x-token直接点Execute。应该能看到 401 响应{detail:Invalid token}填x-token为secret-token再点Execute。这次应该能看到正常的 200 响应。再试GET /users/{user_id}也是一样的行为。两个接口共用同一段校验逻辑但校验代码只写了一遍。FastAPI 在背后帮你做了什么Depends做的事情很简单在调用你的接口函数之前先调用你指定的依赖函数把依赖函数的返回值作为参数传给你的接口函数。用普通 Python 代码类比大概是这样的# FastAPI 帮你做的事情类似这样defhandle_request():# 1. 先调用依赖tokenget_token_header(x_tokenextract_header(x-token))# 2. 再把依赖的返回值传给接口函数responseread_item(item_id1,tokentoken)returnresponse只不过 FastAPI 是自动做的包括从请求里提取参数、处理校验错误、生成文档都是自动的。这也是为什么get_token_header的函数签名里可以直接写x_token: str Header(...)。FastAPI 在调用依赖函数时会用和调用接口函数一样的方式解析参数。依赖函数也可以不返回值前面的get_token_header返回了x_token但有些依赖只需要做校验不需要把结果传给接口函数。比如只需要校验 token 存在且合法接口函数不需要知道 token 的具体值fromfastapiimportHeader,HTTPExceptiondefverify_token(x_token:strHeader(...)):ifx_token!secret-token:raiseHTTPException(status_code401,detailInvalid token)# 不 return 任何东西app.get(/items/{item_id},response_modelItemPublic)defread_item(item_id:int,_:NoneDepends(verify_token)):ifitem_idnotinfake_items_db:raiseHTTPException(status_code404,detailItem not found)itemfake_items_db[item_id]return{id:item_id,**item}这里Depends(verify_token)的返回值是None接口函数用_接收。FastAPI 仍然会先调用verify_token只是接口函数里用不到它的返回值。真实项目里常见的依赖场景除了校验 token真实项目里依赖注入还常用来做这些事分页参数。很多列表接口都需要skip和limit每个接口都写一遍很烦fromtypingimportAnnotatedfromfastapiimportQuerydefget_pagination(skip:intQuery(0,ge0),limit:intQuery(10,le100)):return{skip:skip,limit:limit}app.get(/items)deflist_items(pagination:dictDepends(get_pagination)):skippagination[skip]limitpagination[limit]...数据库连接。每个需要查数据库的接口都先要拿到一个数据库 session用完后关闭defget_db():dbSessionLocal()try:yielddbfinally:db.close()app.get(/items/{item_id})defread_item(item_id:int,db:SessionDepends(get_db)):itemdb.query(Item).filter(Item.iditem_id).first()...注意这里用了yield而不是return。FastAPI 支持这种写法在yield之后的代码会在请求处理完后执行适合做清理工作。动手改一下新增一个get_common_query依赖统一读取q和limit两个查询参数让两个接口都复用它。先在dependencies.py里加fromtypingimportAnnotatedfromfastapiimportQuerydefget_common_query(q:str|NoneQuery(None,description搜索关键词),limit:intQuery(10,ge1,le100,description返回数量上限),):return{q:q,limit:limit}然后给GET /items和GET /users加上这个依赖如果还没有列表接口先加一个app.get(/items)deflist_items(queries:dictDepends(get_common_query)):qqueries[q]limitqueries[limit]# 简单模拟如果有 q只返回名字匹配的result[]foritem_id,iteminfake_items_db.items():ifqisNoneorq.lower()initem[name].lower():result.append({id:item_id,**item})returnresult[:limit]保存后在/docs里调用GET /items应该能看到q和limit两个查询参数仍然出现在文档里和直接写在接口函数参数里时一样。如果能在文档里看到这两个参数并且传不同的值能得到不同的过滤结果这篇就算真正学完。到这里这篇的目标已经完成我们知道了依赖注入是把公共逻辑从接口函数里拿出去的一种方式不是玄学。我们跑通了get_token_header被两个接口复用的效果。我们能在/docs里看到依赖函数的参数仍然正常出现在文档里。本文代码https://github.com/tanghaojin/fastapi-beginner-lab/tree/article-06-dependencies下一篇解决另一个问题接口多了main.py写不下时用APIRouter拆成多个文件。参考资料FastAPI Dependencies: https://fastapi.tiangolo.com/tutorial/dependencies/