FastAPI 基础入门
fastapi第三章
文章目录
- [FastAPI 基础入门](#FastAPI 基础入门)
- [3.1 app应用配置参数详解](#3.1 app应用配置参数详解)
-
- [3.1.1 开启debug模式](#3.1.1 开启debug模式)
- [3.1.2 关于API交互式文档参数](#3.1.2 关于API交互式文档参数)
- [3.1.3 关闭交互式文档访问](#3.1.3 关闭交互式文档访问)
- [3.1.4 全局routes参数说明](#3.1.4 全局routes参数说明)
- [3.1.5 全局异常/错误捕获](#3.1.5 全局异常/错误捕获)
- [3.2 API端口路由注册和匹配](#3.2 API端口路由注册和匹配)
-
- [3.2.1 路由节点元数据](#3.2.1 路由节点元数据)
- [3.2.2 路由URL匹配](#3.2.2 路由URL匹配)
- [3.2.3 基于APIRouter实例的路由注册](#3.2.3 基于APIRouter实例的路由注册)
- 在这里插入图片描述
- [3.3 异步和同步API端点路由](#3.3 异步和同步API端点路由)
-
- [3.3.1 同步API端点路由](#3.3.1 同步API端点路由)
- [3.3.2 异步API端点路由](#3.3.2 异步API端点路由)
- [3.4 多应用挂载](#3.4 多应用挂载)
-
- [3.4.1 主从应用挂载](#3.4.1 主从应用挂载)
- [3.4.2 挂载其他wsgi应用](#3.4.2 挂载其他wsgi应用)
- [3.5 自定义配置swagger_ui](#3.5 自定义配置swagger_ui)
- [3.6 应用配置信息读取](#3.6 应用配置信息读取)
-
- [3.6.1 基于文件读取配置参数](#3.6.1 基于文件读取配置参数)
- [3.6.2 基于pydantic和.env环境变量读取配置参数](#3.6.2 基于pydantic和.env环境变量读取配置参数)
- [3.6.3 给配置读取加上缓存](#3.6.3 给配置读取加上缓存)
- [3.7 API端点路由函数参数](#3.7 API端点路由函数参数)
-
- [3.7.1 路径操作及路径函数](#3.7.1 路径操作及路径函数)
- [3.7.2 path 参数](#3.7.2 path 参数)
- [3.7.3 Query参数](#3.7.3 Query参数)
- [3.7.4 Body参数](#3.7.4 Body参数)
- [3.7.5 Form数据和文件处理](#3.7.5 Form数据和文件处理)
3.1 app应用配置参数详解
3.1.1 开启debug模式
from fastapi import FastAPI
from fastapi.response import PlainTextResponse
app = FastAPI(debug=True)
@app.get('/')
def index():
1988/0
return PlainTextResponse('欢迎学习FastAPI框架!')
在上述代码中,实例化app对象时设置了debug参数值为True;并且创建一个API,在接口函数设置了一个"1988/0"的错误"ZeroDivision-Error"。启动服务,此时页面http://127.0.0.1:8000/会显示出详细的错误堆栈异常信息。
通过堆栈异常信息,可以快速定位到错误问题点的具体位置。
3.1.2 关于API交互式文档参数
调试接口的过程中,常用的是基于Swagger UI模式生成文档。
#!/usr/bin/evn python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
app = FastAPI(
title="文档的标题",
description='关于该API文档一些描述信息补充说明',
version='v1.0.0',
openapi_prefix='',
swagger_ui_oauth2_redirect_url="/docs/oauth2-redirect",
swagger_ui_init_oauth=None,
docs_url='/docs',
redoc_url='/redoc',
openapi_url="/openapi/openapi_json.json",
terms_of_service="https://terms/团队的官网网站/",
deprecated=True,
contact={
"name": "邮件接收者信息",
"url": "https://xxx.cc",
"email": "[email protected]",
},
license_info={
"name": "版权信息说明 License v3.0",
"url": "https://xxxxxxx.com",
},
openapi_tags=[
{
"name": "接口分组",
"description": "接口分组信息说明",
},
],
# 配置服务请求地址相关的参数信息
servers=[
{"url": "/", "description": "本地调试环境"},
{"url": "https://xx.xx.com", "description": "线上测试环境"},
{"url": "https://xx2.xx2.com", "description": "线上生产环境"},
]
)
@app.get(path="/index")
async def index():
return {"index": "index"}
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

参数项说明:
字段 | 说明 |
---|---|
title | 表示整个API文档的标题和文档网站标题,默认fastapi |
description | 表示该API文档描述的补充说明,支持使用markdown格式来编写 |
version | 表示API文档版本号信息 |
openapi_prefix | 配置访问openapi_json.json文件路径的前缀,默认空字符串。 |
swagger_ui_init_oauth2_redirect_url | 配置在swagger_ui,使用oauth2进行身份认证时的授权URL重定向地址。 |
swagger_ui_init_oauth | 自定义oauth认证信息配置,默认是none |
docs_url | 自定义swagger_ui交互式文档访问请求地址,默认为docs。 |
redoc_url | 自定义redoc_ui交互式文档访问请求地址,默认为redoc。 |
openapi_url | 配置访问openapi_json.json文件路径 |
terms_of_service | 团队服务条款的URL;如果提供,则该值必须是一个URL;该参数为非必要参数 |
deprecated | 是否全局标注所有的API为废弃标识,默认为none |
openapi_tags | 默认的接口分组列表信息,可以定义相关api的分组tag名称 |
servers | 默认的接口分组列表信息,可以定义相关API的分组tag名称 |
3.1.3 关闭交互式文档访问
#!/usr/bin/evn python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
app = FastAPI(
docs_url=None,
redoc_url=None,
# 或者直接设置openapi_url=None
openapi_url=None,
)
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

设置了docs_url=None和redoc_url=None。访问http:/127.0.0.1:8000/docs的请求都会返回"not found"的提示,表示找不到该路由的访问地址。
3.1.4 全局routes参数说明
from fastapi import FastAPI,Request
from fastapi.responses import JSONResponse
from fastapi.routing import APIRoute
async def fastapi_index():
return JSONResponse({'index':'fastapi_index'})
async def fastapi_about():
return JSONResponse({'about':'fastapi_about'})
# 定义一个routes列表,保存着已经实例化的APIRoute对象实例。
routes = [
APIRoute(path='/fastapi/index',endpoint=fastapi_index,methods=['GET',"POST"]),
APIRoute(path='/fastapi/about',endpoint=fastapi_about,methods=["POST"]),
]
app = FastAPI(routes=routes)
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

3.1.5 全局异常/错误捕获
exception_handlers参数主要用于捕获在执行业务逻辑处理过程中抛出的各种异常。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import HTTPException
# 自定义异常处理程序
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "error": "没有定义这个请求地址"},
media_type="application/json; charset=utf-8"
)
app = FastAPI(exception_handlers={404: http_exception_handler})
@app.get("/")
async def read_root():
return {"message": "Hello World"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000, reload=True)

3.2 API端口路由注册和匹配
在FastAPI框架中,所有注册路由都会统一保存到app.routes中。
3.2.1 路由节点元数据
元数据 :一组用于描述数据的数据,主要用于组织、查找和理解数据(通常指描述数据属性的信息)。
与api可视化文档显示有关的字段信息说明如下:
参数 | 说明 |
---|---|
tags | 设置API文档中接口所属组别的标签名,可以理解为分组名称,支持设定多个所属分组 |
summary | 设置API文档中该路由接口名称,默认值为当前被装饰的函数(视图函数)名称 |
description | 设置API文档中对该路由功能的详细描述 |
response_description | 设置API文档中对该路由响应保温信息结果的描述 |
deprecated | 设置API文档中是否该路由标记为废弃接口 |
operation_id | 自定义设置路径操作中使用的openapi的 operation_id名称 |
name | 设置API文档中该路由姐哭的名称。其功能和summary类似,但是name主要是供用户反向查询使用。两者同时存在会优先显示summary值 |
openapi_extra | 用于自定义或扩展API文档中对应的openapi_extra字段的功能 |
include_in_schema | 表示该路由接口相关信息是否在API文档中显示 |
与响应报文处理有关的字段信息说明如下:
参数 | 说明 |
---|---|
path | 定义路由访问的URL地址 |
response_model | 定义函数处理结果中返回的json的模型类,这里会把输出数据转换为对应的response_model中声明的数据模型 |
status_code | 定义响应报文状态码 |
response_class | 设置响应保温使用的Response类,默认返回JSONReponse |
responses | 设定不同响应报文状态码下不同的响应模型 |
response_model_include | 设置响应模型的json信息中包含哪些字段,参数格式为集合{字段名,字段名,...} |
response_model_exclude | 设定响应模型的json信息中需要过滤哪些字段 |
response_model_include_unset | 设定不返回响应模型的json信息中没有值的字段 |
response_model_include_defaults | 设定不返回响应模型的json信息中有默认值的字段 |
response_model_exclude_none | 设定不返回响应模型的json信息中值为none的字段 |
3.2.2 路由URL匹配
URL地址是绑定路径装饰器和对应视图函数(同步函数或协程函数)的纽带。
1️⃣多重URL地址绑定函数
RESTful API常用的http方法主要有如下几个:
❑ GET:读取数据库信息
❑ POST:创建新数据
❑ PUT:更新已有数据
❑ PATCH:更新数据,通常仅更新部分数据
❑ DELETE:删除数据信息
在项目开发过程中,一个试图函数需要同时支持多个请求地址的访问,则需要使用多个装饰器(多个URL地址)来装饰绑定视图函数。
# 使用app实例对象来装饰实现路由注册
@app.get('/',response_class = JSONResponse)
@app.get('/index',response_class = JSONResponse)
@app.get('/index',response_class = JSONResponse)
@app.get('/app/hello',tags = ['app实例对象注册接口-示例'])
def index():
return {"Hello","app api"}
2️⃣同一个URL下的动态和静态路由优先级
从访问URL的可变性角度,可以把路由划分为静态路由(固定请求URL)和动态路由(可变请求URL)。如果动态和静态路由URL同时存在,那么请求访问的优先级是怎么样的?
from fastapi imort FastAPI
app = FastAPI()
# 动态路由
@app.get('/user/{userid}')
async def login(userid:str):
return {"Hello":"dynamic"}
# 静态路由
@app.get('/user/{userid}')
async def login():
return {"Hello":"static"}
上述代码,定义了2个路由,他们URL地址几乎一样,区别在于URL地址中的useid参数,一个是动态路由中的动态参数,另一个是静态路由中的路径参数的一部分。启动服务后,同时访问http://127.0.0.1:8000/user/userid,则此时的路由访问原则是:谁先注册就优先访问谁。最终结果: {"Hello":"dynamic"}。调换注册顺序的话,此时会显示:{"Hello":"static"}。
3️⃣一个URL配置多个http请求地址
#!/usr/bin/evn python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
from fastapi import APIRouter
from starlette.responses import JSONResponse
app = FastAPI(routes=None)
# ============一个URL配置多个HTTP请求方法==========
# =========================================
@app.api_route(path="/index", methods=["GET", "POST"])
async def index():
return {"index": "index"}
async def index2():
return JSONResponse({"index": "index"})
app.add_api_route(path="/index2", endpoint=index2, methods=["GET", "POST"])
router_uesr = APIRouter(prefix="/user", tags=["用户模块"])
@router_uesr.get("/user/login")
def user_login():
return {"ok": "登入成功!"}
@router_uesr.api_route("/user/api/login", methods=['GET', 'POST'])
def user_api_route_login():
return {"ok": "登入成功!"}
def add_user_api_route_login():
return {"ok": "登入成功!"}
router_uesr.add_api_route("/user/add/api/login", methods=['GET', 'POST'], endpoint=add_user_api_route_login)
app.include_router(router_uesr)
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

3.2.3 基于APIRouter实例的路由注册
API端点路由注册方式三种:
❑ 基于app实例对象提供的装饰器或函数进行注册
❑ 基于FastAPI提供的APIRouter类的实例对象提供的装饰器或函数进行注册
❑ 通过直接实例化APIRoute对象且添加的方式进行注册
⚠️
这里的APIRouter和APIRoute是不一样。APIRouter主要用于定义路由组,可以理解为一个路由组的根路由,而APIRoute则表示具体路由节点对象。APIRouter可以实现的功能类似flask框架中提供的一个蓝图模式的加载。
1️⃣路由注册方式
基于APIRouter的实例对象实现路由注册,本质上是向路由中添加子路由(也就是所谓的蓝图模式)
from fastapi import FastAPI, APIRouter
app = FastAPI()
# 创建路由分组
router_user = APIRouter(prefix='/user', tags=['用户模块'])
router_pay = APIRouter(prefix='/pay', tags=['支付模块'])
# 定义路由
@router_user.get("/login")
def user_login():
return {"ok": "登陆成功!"}
@router_pay.get("/order")
def pay_order():
return {"ok": "订单支付成功!"}
# 添加路由分组
app.include_router(router_user)
app.include_router(router_pay)
if __name__ == "__main__":
import uvicorn
import os
app_model_name = os.path.basename(__file__).replace(".py", "")
print(app_model_name)
uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)
参数说明:
❑ prefix:当前整个全局路由对象请求URL地址前缀
❑ tags:API分组归属标签

2️⃣给路由配置多个http请求方法
from fastapi import FastAPI, APIRouter
app = FastAPI()
# 创建路由分组
router_user = APIRouter(prefix='/user', tags=['用户模块'])
router_pay = APIRouter(prefix='/pay', tags=['支付模块'])
# 定义路由
@router_user.get("/login")
def user_login():
return {"ok": "登陆成功!"}
@router_user.api_route("/user/api/login",methods=['GET','POST'])
def user_api_route_login():
return {"ok": "登陆成功!"}
def add_user_api_router_login():
return {"ok": "登陆成功!"}
router_user.add_api_route("/user/add/api/login",methods=['GET','POST'],endpoint=add_user_api_router_login)
@router_pay.get("/order")
def pay_order():
return {"ok": "订单支付成功!"}
# 添加路由分组
app.include_router(router_user)
app.include_router(router_pay)
if __name__ == "__main__":
import uvicorn
import os
app_model_name = os.path.basename(__file__).replace(".py", "")
print(app_model_name)
uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)
3.3 异步和同步API端点路由
3.3.1 同步API端点路由
同步路由:当使用URL地址绑定的关联视图函数是一个同步函数(使用def定义的函数)时,就把这个绑定过程理解为一个同步路由注册的过程。
#!/usr/bin/evn python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
import threading
import time
import asyncio
app = FastAPI(routes=None)
@app.get(path="/sync")
def syncdef():
time.sleep(10)
print("当前普通函数运行的线程ID:",threading.current_thread().ident)
return {"index": "sync"}
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

从上面的输出结果看到,当发起多个请求时,每个请求的执行所执行所产生的线程ID都是不一样的。同步路由的并发处理机制是基于多线程方式来实现的。
3.3.2 异步API端点路由
异步路由:当使用URL地址绑定的视图函数是一个协程函数(使用async def定义的函数),就把这个绑定过程理解为一个异步路由注册的过程
#!/usr/bin/evn python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
import threading
import time
import asyncio
app = FastAPI(routes=None)
@app.get(path="/async")
async def asyncdef():
await asyncio.sleep(10)
print("当前协程运行的线程ID:", threading.current_thread().ident)
return {"index": "async"}
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)

从输出结果可以看出:当发起多个访问请求时,每个请求的执行都运行在同一个线程内。所有可以理解每个请求本质上都是运行在同一个线程的循环事件中,所有他们的线程id是一样的,也说明了异步路由的并发处理机制是基于单线程方式运行的。
⚠️
不建议在异步操作使用同步函数,因为在一个线程中执行同步函数时肯定会引发阻塞。比如一般不会在异步协程函数中使用time.sleep(),而是使用await asyncio.sleep()。
3.4 多应用挂载
3.4.1 主从应用挂载
主从应用挂载子应用:app独立管理;个字所属API交互式文档是独立的,可以分开访问。具体挂载步骤:
#!/usr/bin/evn python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
from fastapi.responses import JSONResponse
# 1️⃣创建主app应用对象实例,注册所属的路由信息
app = FastAPI(title='主应用',description="我是主应用文档的描述",version="v1.0.0")
@app.get('/index',summary='首页')
async def index():
return JSONResponse({"index": "我是属于主应用的接口!"})
# 2️⃣创建子app应用对象实例,注册所属的路由信息
subapp = FastAPI(title='子应用',description="我是子应用文档的描述",version="v1.0.0")
@subapp.get('/index',summary='首页')
async def index():
return JSONResponse({"index": "我是属于子应用的接口!"})
# 3️⃣通过调用app.mout(subapp)进行主应用挂载子应用关联,设置子应用请求URL地址为/subapp
app.mount(path='/subapp',app=subapp,name='subapp')
if __name__ == "__main__":
import uvicorn
import os
app_modeel_name = os.path.basename(__file__).replace(".py", "")
print(app_modeel_name)
uvicorn.run(f"{app_modeel_name}:app", host='127.0.0.1', reload=True)
4️⃣启动服务,分别查看主应用和子应用的独立文档
❑ 主应用交互式文档:http://127.0.0.1:8000/docs
❑ 子应用交互式文档:http://127.0.0.1:8000/subapp/docs
3.4.2 挂载其他wsgi应用
如果已经开发了wsgi应用程序(如flask或者Django),也可以通过FastAPI无缝进行挂载关联,这样就可以通过FastAPI部署来启动WSGI应用程序。FastAPI提供一个名叫WSGI Middlerware中间件,通过他可以挂载WSGI应用程序。
1️⃣ > pip install flask
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from starlette.middleware.wsgi import WSGIMiddleware
# 2️⃣创建FastAPI主应用,注册所属的路由信息
app = FastAPI(title='主应用', description="我是主应用文档的描述", version="v1.0.0")
@app.get('/index', summary='首页')
async def index():
return JSONResponse({"index": "我是属于FastAPI应用的接口!"}, media_type="application/json; charset=utf-8")
# 3️⃣创建Flask子应用,注册所属的路由信息
from flask import Flask, Response # 使用 Response 来返回原始 JSON 字符串
flask_app = Flask(__name__)
@flask_app.route('/index')
def flask_main():
# 返回原始 JSON 字符串,并设置 Content-Type 头
return Response('{"index": "我是属于flask_app应用的接口!"}', mimetype='application/json; charset=utf-8')
# 4️⃣进行挂载关联,设置子应用请求URL地址为/flaskapp
app.mount(path='/flaskapp', app=WSGIMiddleware(flask_app), name='flask_app')
if __name__ == "__main__":
import uvicorn
import os
app_model_name = os.path.basename(__file__).replace(".py", "")
print(app_model_name)
uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)
5️⃣启动服务,分别访问不同的路由地址,最终不同的请求会分发到不同路由上,且会由不同的框架进行处理并返回最终数据。
访问:http://127.0.0.1:8000/index
访问:http://127.0.0.1:8000/flaskapp/index
3.5 自定义配置swagger_ui
swagger-ui.css和swagger-ui-bundle.js资源是从第三方的cdn服务商上加载的,第三方服务cdn服务出现问题会导致无法加载可视化API文档界面。为避免这种情况,可以自定义(或改造)并渲染html模版中的一些变量,让swagger-ui.css和swagger-ui-bundle.js等静态资源从本地进行加载。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pathlib
from fastapi import FastAPI
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles
app = FastAPI(docs_url=None)
# 确保静态文件目录存在
static_dir = pathlib.Path(__file__).parent / "static"
if not static_dir.exists():
static_dir.mkdir()
app.mount("/localswagger/static", StaticFiles(directory=str(static_dir)), name="static")
@app.get('/docs', include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - Swagger UI",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
swagger_js_url="/localswagger/static/swagger-ui-bundle.js",
swagger_css_url="/localswagger/static/swagger-ui.css",
swagger_favicon_url="https://fastapi.tiangolo.com/img/favicon.png"
)
if __name__ == "__main__":
import uvicorn
import os
app_model_name = os.path.basename(__file__).replace(".py", "")
print(app_model_name)
uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)
3.6 应用配置信息读取
3.6.1 基于文件读取配置参数
1️⃣定义配置文件con.ini的内容
[fastapi_config]
debug = True
title = "FastAPI"
description = "FastAPI文档明细描述"
version = v1.0.0
[redis]
ip = 127.0.0.1
port = 6379
password = 123456
2️⃣导入configparser模块解析配置文件,读取制定配置参数信息
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from fastapi import FastAPI
import configparser
# 获取当前脚本的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(current_dir, 'conf.ini')
# 检查配置文件是否存在
if not os.path.exists(config_path):
raise FileNotFoundError(f"配置文件 {config_path} 不存在")
# 加载配置文件
config = configparser.ConfigParser()
config.read(config_path, encoding='utf-8')
# 检查配置文件中是否存在 [fastapi_config] 部分
if not config.has_section('fastapi_config'):
raise configparser.NoSectionError("配置文件中不存在 [fastapi_config] 部分")
app = FastAPI(
debug=config.getboolean('fastapi_config', 'debug'),
title=config.get('fastapi_config', 'title'),
description=config.get('fastapi_config', 'description'),
version=config.get('fastapi_config', 'version'),
)
if __name__ == "__main__":
import uvicorn
import os
app_model_name = os.path.basename(__file__).replace(".py", "")
print(app_model_name)
uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)
3.6.2 基于pydantic和.env环境变量读取配置参数
1️⃣安装读取环境变量的依赖包
pip install python-dotenv
2️⃣定义配置文件.env内容
DEBUG=true
TITLE="FastAPI"
DESCRIPTION="FastAPI文档明细描述"
vERSION="v1.0.0"
3️⃣定义继承于BaseSettings模型的Settings子类
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from typing import Optional
from fastapi import FastAPI
from pydantic_settings import BaseSettings
from pydantic import field_validator
from functools import lru_cache
class Settings(BaseSettings):
debug: bool = False
title: str
description: str
version: str
class Config:
env_file = ".env"
env_file_encoding = 'utf-8'
@field_validator("version", mode='before')
def version_len_check(cls, v: str) -> Optional[str]:
if v and len(v) == 0:
return None
return v
@lru_cache()
def get_settings():
return Settings()
# 4️⃣创建Settings实例对象,完整.env文件解析
settings = get_settings()
print(settings.debug)
print(settings.title)
print(settings.description)
print(settings.version)
app = FastAPI(
debug=settings.debug,
title=settings.title,
description=settings.description,
version=settings.version,
)
if __name__ == "__main__":
import uvicorn
import os
app_model_name = os.path.basename(__file__).replace(".py", "")
print(app_model_name)
uvicorn.run(f"{app_model_name}:app", host='127.0.0.1', reload=True)

3.6.3 给配置读取加上缓存
系统中用到的配置参数只会读取一次,如果每次调用参数都要进行一次Settings对象实例化,则可能引发性能问题(当然也可以进行单例的实现)。对于非单例模式的实例对象,可以通过添加缓存的方式来避免多次实例化,进而提高整体性能
from functools import lru_cache
@lru_cache()
def get_settings():
return Settings()
app = FastAPI(
debug=settings.debug,
title=settings.title,
description=settings.description,
version=settings.version,
)
通过导入functools模块的lru_cache装饰器,完成了Settings对象缓存处理。
3.7 API端点路由函数参数
项目开发过程中,用户一般需要通过提交参数来访问API,但是对于API来说,用户输入的信息永远是不可靠的,所以需要对用户输入的参数进行校验,比如字段参数类型是否一致、字段的长度不能超过多少等。参数的校验库比较多,如以下常用的:
参数 | 说明 |
---|---|
WTForms | 支持多个Web框架的Form组件,主要用于对用户请求数据进行验证 |
valideer | 轻量级、可扩展的数据验证和适配库 |
validators | 验证库 |
cerberus | 用于python的轻量级和可扩展的数据验证库 |
colander | 用于对xml、json、html以及其他同样简单的序列化数据进行校验和反序列化的库 |
jsonschema | 用来标记和校验json数据,可在自动化测试中验证json的整体结构和字段类型 |
schematics | 一个python库,用于将类型组合到结构中并验证他们,然后根据简单的描述转换数据的形状 |
voluptuous | 主要用于验证以json、yaml等形式传入python的数据 |
3.7.1 路径操作及路径函数
from typing import List
from fastapi import FastAPI, Query, Path
from starlette import status
from enum import Enum
app = FastAPI()
@app.post("/parameter/", summary='我是路径参数', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
async def parameter(q: List[str] = Query(["test1", "test2"])):
return {
'message': q,
}
@app.post() 表示API端点的路径操作,也就是API装饰器 ,他里面的传输参数则可以理解为路径操作函数 ;async def parameter()表示视图函数,该视图函数可以是同步函数,也可以是协程函数,其中需要传入参数表示路径函数参数,这里叫做视图函数参数
3.7.2 path 参数
1️⃣路径参数变量的声明和获取
路径参数通常是指URL地址中可变的参数
@app.get("/user/{user_id}/article/{article_id}")
async def callback(user_id: int, article_id: str):
return {
'user_id': user_id,
'article_id': article_id
}
user_id和article_id两个路径参数 变量,FastAPI会自动把这两个参数传递到视图函数上。在视图函数中,对这两个路径参数的要求如下:
❑ user_id:int 类型,是必填项。如果传递过程中没有这个参数,则会抛出请求参数校验异常。
❑ article_id:字符串类型,是必填项。如果传递过程中没有这个参数,则会抛出请求参数校验异常。

2️⃣带"/"的关键子路径参数
传入路径参数是一种文件类型的路径,且要求把该变量值传入URL路径中,那么URL会识别出多重路径。
@app.get("/uls/{file_path}")
async def callback_file_path(file_path: str):
return {
'file_path': file_path
}
http://127.0.0.1:8000/user/s1234/article/xiaoxiao
如果此时提交file_path变量值为file/1.txt/,如访问http://127.0.0.1:8000/uls//file/1.txt,则会出现404错误,无法匹配到正确的路由地址。此时只有把路径变量定义为Path类型,才能进行正确处理。
@app.get("/uls/{file_path:path}")
async def callback_file_path_2(file_path: str):
return {
'file_path': file_path
}
上面的代码,路径参数为file_path:path。其中path表示该参数应匹配的任意路径,也可以理解为一种对路径的转换机制。此时访问http://127.0.0.1:8000/uls//file/1.txt,就能正常返回最终的处理结果
3️⃣枚举预设路径参数值
from fastapi import FastAPI, Query, Path
from enum import Enum
class ModelName(str, Enum):
name1 = "name1"
name2 = "name2"
name3 = "name3"
@app.get("/model/{model_name}")
async def get_model(model_name: ModelName):
if model_name == ModelName.name1:
return {"model_name": model_name, "message": "ok!"}
if model_name.value == "name2":
return {"model_name": model_name, "message": "name2 ok!"}
return {"model_name": model_name, "message": "fail!"}

4️⃣路径参数的更多条件校验
需要对单独的一个参数进行多维度校验,则单纯采用上文的声明无法完成。
@app.get("/pay/{user_id}/article/{article_id}")
async def callback(user_id: int = Path(..., title="用户ID", description='用户ID信息', ge=10000),
article_id: str = Path(..., title="文章ID", description='用户所属文章ID信息', min_length=1,
max_length=50)):
return {
'user_id': user_id,
'article_id': article_id
}
在上面的代码中,在视图函数中声明了两个path参数。
❑ user_id:int类型参数
❑ ...:表示user_id是一个必填项
❑ title:表示参数显示在API交互式文档中的标题名称
❑ description:表示参数显示在API交互式文档中的详细描述
❑ ge=10000:表示参数传值需要大于或等于10000。如不满足,则会抛出请求参数校验异常
❑ article_id:字符串类型参数
❑ min_length=1:表示参数传值需要字符串长度大于1。如不满足,则会抛出请求参数校验异常
❑ max_length=50:表示参数传值需要字符串长度小于50。如不满足,则会抛出请求参数校验异常

5️⃣路径参数必填值说明
⚠️路径参数的定义------任何路径参数值都应该声明为必填项;若将路径参数声明为非必填项,如user_id:int=Path(default=None),则这个声明不会起作用,因为它还是一个路径参数。
6️⃣参数声明顺序问题
在视图函数中,声明的参数氛围默认值和无默认值两种。如果有默认值参数放在无默认值参数的前面,那么ide会提示告警,并且无法启动服务。坚持吧有默认参数放在最前面,则需要在第一个参数前面加一个*。
@app.get("/items/{item_id}")
async def callback(*,item_id: int = Path(...,), q: str):
return 'OK'
此时,FastAPI框架会将*之后的参数自动识别为关键字参数(键值对),也就是**kwargs,并且他不会在意之后对参数是否有默认值。
3.7.3 Query参数
1️⃣查询参数必选和可选
@app.get('/query')
async def read_item(user_id:int,user_name:Optional[str]=None,user_token:str = 'token'):
return{
'user_id':user_id,
'user_name':user_name,
"user_token":user_token
}
在视图函数中声明查询的参数说明如下:
❑ user_id:int类型参数,是必填项,如果没有这个参数的传递,则会抛出请求参数校验异常。
❑ user_name:字符串类型参数,是可选项,如果没有这个参数,则不会出发任何校验异常。Optional的主要作用是参数值类型提示,方便IDE识别提示。Optional[str]=None表示该参数要么是字符串类型,要么是None类型
❑ user_token:字符串类型参数,它的默认值为"token"
2️⃣bool类型参数转换
❑ isbool=true/false
❑ isbool=1/0(1表示true,0表示false)
❑ isbool=on/off(on表示true,off表示false)
当请求地址为以下几种时得到的结果是一样的:
❑ http://127.0.0.1:8000/query/bool/?isbool=true
❑ http://127.0.0.1:8000/query/bool/?isbool=1
❑ http://127.0.0.1:8000/query/bool/?isbool=on

3.7.4 Body参数
Body(请求体)参数表示在HTTP中提交请求体的数据,它既可以是某种文档类型的数据,也可以是文件类型或是表单类的数据。常见的请求体参数传递都是JSON格式的,如果是JSON格式的数据,则通常要求提交请求头字段Content-Type的格式为如下形式。
FastAPI框架提供了如下3种方式来接收Body参数,并自动把JSON格式的字符串转换为dict格式。
❑ 引入Pydantic模型来声明请求体并进行绑定。
❑ 直接通过Request对象获取Body的函数。
❑ 使用Body类来定义
1️⃣pydantic模型声明请求体
# 步骤1:创建对应的body数据模型
from pydantic import BaseModel
from typing import Optional
class Item(BaseModel):
user_id: str
token: str
timestamp: str
article_id: Optional[str] = None
# 步骤2:把模型绑定到视图函数中
@app.post("/action/")
def callback(item: Item):
return {
'user_id': item.user_id,
'article_id': item.article_id,
'token': item.token,
'timestamp': item.timestamp
}
完成以上两个步骤后,完成上面两个步骤之后,FastAPI框架会自动处理以下几种情况。
❑ 将请求体参数识别为JSON格式字符串,并自动将字段转换为相应的数据类型。
❑ 自动进行参数规则的校验。如果校验失败,则响应报文内容会自动返回一个错误,并准确指出错误数据的位置和信息。
❑ 为模型生成JSON Schema定义,并显示在API交互式文档中,Schema会成为OpenAPI Schema的一部分。
❑ 在函数内部,可以直接访问模型对象的所有属性。
2️⃣单值Request Body字段参数定义
@app.post("/action/body")
def callbackbody(
token: str = Body(...),
user_id: int = Body(..., gt=10),
timestamp: str = Body(...),
article_id: str = Body(default=None),
):
return {
'user_id': user_id,
'article_id': article_id,
'token': token,
'timestamp': timestamp
}
❑ token、user_id、timestamp都是必填的字段。
❑ user_id参数值需要大于或等于10。
❑ article_id是可选的字段。

可以将所有body参数都设置为可选
@app.post("/action/body2")
def callbackbody(
token: str = Body(default=None),
user_id: int = Body(default=None, gt=10),
timestamp: str = Body(default=None),
article_id: str = Body(default=None),
):
return {
'user_id': user_id,
'article_id': article_id,
'token': token,
'timestamp': timestamp
}
此时body参数为非必填项。
3️⃣Request Body中的embed参数
class Itement(BaseModel):
user_id: int = Body(..., gt=10, embed=True)
token: str
timestamp: str
article_id: Optional[str] = None
@app.post("/action/body3")
def callbackbody(item: Itement = Body(default=None, embed=False)):
return {
'body': item
}
❑ default=None表示这个模型作为请求体提交时是一个可选的字段,此时不会校验这个模型是否存在,尽管模"型类中的user_id、token、timestamp等字段都是必填项,但还是会忽略对模型类中必填参数的校验。
❑ embed=False 表示item参数字段名不会成为请求体的一部分,如下图
embed=True 表示item参数字段名会成为请求体的一部分,如下图

4️⃣多个Request Body参数
复杂请求体数据,有可能有嵌套要求,请求体格式要求如下:
{
"item":{
"name":"苹果",
"description":"苹果手机"
"price":4212.0,
"tax":3.2
},
"user":{
"username":"xiaoxiao",
"full_name":"xiaozhong tongxue"
}
}
class ItemUser(BaseModel):
name: str
description: str = None
price: float
tax: float = None
class User(BaseModel):
username: str
full_name: str = None
@app.put("/items/")
async def update_item1111(item: ItemUser, user: User):
results = {"item": item, "user": user}
return results
上面多个模型声明在API交互式文档中显示结果:

5️⃣多个模型类和单个Request Body
请求体不仅存在多个Request Body,而且存在单一Request Body的字段信息,
{
"item":{
"name":"Foo",
"description":"The pretender"
"price":42.0,
"tax":3.2
},
"user":{
"username":"dave",
"full_name":"Dave "
},
"importance":5
}
@app.put("/items/")
async def update_item1111(item: ItemUser, user: User):
results = {"item": item, "user": user}
return results

3.7.5 Form数据和文件处理
表单数据默认使用POST的方式提交到API上,由于表单数据的传输过程使用一种"特殊"编码,所以与JSON不同,设置Content-Type格式的对应要求也不同。对于表单数据的传输,通常要求提交请求头字段Content-Type的方式是application/x-www-form-urlencoded,这也是默认的方式。下面介绍如何在FastAPI框架中对Form参数进行解析读取。
FastAPI中提供的Form类是基于Body模块扩展而来的,所以它的用法和Body是一样的。
rom fastapi import FastAPI, Query, Path, Body, Form, File, UploadFile
from pydantic.main import BaseModel
from starlette import status
from enum import Enum
from fastapi import Depends
app = FastAPI()
@app.post("/demo/login/")
async def login(username: str = Form(...,title="用户名",description="用户名字段描述", max_length=5),
password: str = Form(...,title="用户密码",description="用户密码字段描述", max_length=20)):
return {"username": username, "password": password}

File bytes 方式接受文件上传
@app.post("/sync_file",summary='File形式的-单文件上传')
def sync_file(file: bytes = File(...)):
'''
基于使用File类 文件内容会以bytes的形式读入内存通常主要用于上传小的文件
:param file:
:return:
'''
with open('./data.bat', 'wb') as f:
f.write(file)
return {'file_size': len(file)}
@app.post("/async_file",summary='File形式的-单文件上传')
async def async_file(file: bytes = File(...)):
'''
基于使用File类 使用异步的方式进行文件接收处理
:param file:
:return:
'''
# 异步方式执行with操作,修改为 async with
async with aiofiles.open("./data.bat", "wb") as fp:
await fp.write(file)
return {'file_size': len(file)}
可以看到定义的两个视图函数,一个是同步类型的sync_file()视图函数,另一个是异步类型的async_file()视图函数,与它们对应的视图函数都用file: bytes=File参数声明具体要接收的文件。需要注意,File参数接收到的内容是文件字节,它会将整个内容读取并存储到内存中,所以它主要用在上传小文件的场景中。
@app.post("/uploadfiles",summary='UploadFile形式的-单文件上传')
async def uploadfiles(file: UploadFile = File(...)):
result = {
"filename": file.filename,
"content-type": file.content_type,
}
content = await file.read()
with open(f"./{file.filename}", 'wb') as f:
f.write(content)
return result
与bytes对比,UploadFile具有以下几点优势。
❑ 使用UploadFile进行文件读取时,所获得的数据存储在内存中,当占用的内存达到阈值后将被保存在磁盘中。这种读取方式更适用于大图片、视频等大文件的上传处理。
❑ UploadFile对象包含很多文件元数据,如文件名、文件类型等。
❑ 有文件对象的异步接口,可以对文件对象进行write()、read()、seek()和close()等操作。