三周精通FastAPI:2 路径参数以及声明路径参数的类型
路径参数¶
FastAPI 支持使用 Python 字符串格式化语法声明路径参数(变量):
from fastapi import FastAPIapp = FastAPI()@app.get("/items/{item_id}")
async def read_item(item_id):return {"item_id": item_id}这段代码把路径参数 item_id 的值传递给路径函数的参数 item_id。
运行示例并访问 http://127.0.0.1:8000/items/foo,可获得如下响应:
{"item_id":"foo"} 
声明路径参数的类型¶
使用 Python 标准类型注解,声明路径操作函数中路径参数的类型。
from fastapi import FastAPIapp = FastAPI()@app.get("/items/{item_id}")
async def read_item(item_id: int):return {"item_id": item_id}类型声明将为函数提供错误检查、代码补全等编辑器支持。
数据转换¶
运行示例并访问 http://127.0.0.1:8000/items/3,返回的响应如下:
{"item_id":3} 注意,函数接收并返回的值是 3( int),不是 "3"(str)。
FastAPI 通过类型声明自动**解析**请求中的数据。
数据校验¶
通过浏览器访问 http://127.0.0.1:8000/items/foo,接收如下 HTTP 错误信息:
{"detail":[{"type":"int_parsing","loc":["path","item_id"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"foo"}]} 
这是因为路径参数 item_id 的值 ("foo")的类型不是 int。
值的类型不是 int 而是浮点数(float)时也会显示同样的错误,比如: http://127.0.0.1:8000/items/4.2。
FastAPI 使用 Python 类型声明实现了数据校验。
注意,上面的错误清晰地指出了未通过校验的具体原因。
这在开发调试与 API 交互的代码时非常有用。
查看文档¶
访问 http://127.0.0.1:8000/docs,查看自动生成的 API 文档:
还是使用 Python 类型声明,FastAPI 提供了(集成 Swagger UI 的)API 文档。
注意,路径参数的类型是整数。
基于标准的好处,备选文档¶
FastAPI 使用 OpenAPI 生成概图,所以能兼容很多工具。
因此,FastAPI 还内置了 ReDoc 生成的备选 API 文档,可在此查看 http://127.0.0.1:8000/redoc:
同样,还有很多兼容工具,包括多种语言的代码生成工具。
Pydantic¶
FastAPI 充分地利用了 Pydantic 的优势,用它在后台校验数据。众所周知,Pydantic 擅长的就是数据校验。
同样,str、float、bool 以及很多复合数据类型都可以使用类型声明。
下一章介绍详细内容。
顺序很重要¶
有时,*路径操作*中的路径是写死的。
比如要使用 /users/me 获取当前用户的数据。
然后还要使用 /users/{user_id},通过用户 ID 获取指定用户的数据。
由于*路径操作*是按顺序依次运行的,因此,一定要在 /users/{user_id} 之前声明 /users/me :
from fastapi import FastAPIapp = FastAPI()@app.get("/users/me")
async def read_user_me():return {"user_id": "the current user"}@app.get("/users/{user_id}")
async def read_user(user_id: str):return {"user_id": user_id}否则,/users/{user_id} 将匹配 /users/me,FastAPI 会**认为**正在接收值为 "me" 的 user_id 参数。
预设值¶
路径操作使用 Python 的 Enum (枚举)类型接收预设的*路径参数*。
创建 Enum 类¶
 
导入 Enum 并创建继承自 str 和 Enum 的子类。
通过从 str 继承,API 文档就能把值的类型定义为**字符串**,并且能正确渲染。
然后,创建包含固定值的类属性,这些固定值是可用的有效值:
from enum import Enumfrom fastapi import FastAPIclass ModelName(str, Enum):alexnet = "alexnet"resnet = "resnet"lenet = "lenet"app = FastAPI()@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):if model_name is ModelName.alexnet:return {"model_name": model_name, "message": "Deep Learning FTW!"}if model_name.value == "lenet":return {"model_name": model_name, "message": "LeCNN all the images"}return {"model_name": model_name, "message": "Have some residuals"}Python 3.4 及之后版本支持枚举(即 enums)。
"提示":AlexNet、ResNet、LeNet 是机器学习模型。
声明*路径参数*¶
使用 Enum 类(ModelName)创建使用类型注解的*路径参数*,就是这句:
async def get_model(model_name: ModelName):
查看文档¶
API 文档会显示预定义*路径参数*的可用值:
使用 Python 枚举类型¶
*路径参数*的值是枚举的元素。
比较*枚举元素*¶
枚举类 ModelName 中的*枚举元素*支持比较操作:
    if model_name is ModelName.alexnet:
获取*枚举值*¶
使用 model_name.value 或 your_enum_member.value 获取实际的值(本例中为**字符串**): 
    if model_name.value == "lenet":
"提示":使用 ModelName.lenet.value 也能获取值 "lenet"。
返回*枚举元素*¶
即使嵌套在 JSON 请求体里(例如, dict),也可以从*路径操作*返回*枚举元素*。返回给客户端之前,要把枚举元素转换为对应的值(本例中为字符串): 
        return {"model_name": model_name, "message": "Deep Learning FTW!"}
客户端中的 JSON 响应如下:
{ "model_name": "alexnet", "message": "Deep Learning FTW!" } 包含路径的路径参数¶
假设*路径操作*的路径为 /files/{file_path}。
但需要 file_path 中也包含*路径*,比如,home/johndoe/myfile.txt。
此时,该文件的 URL 是这样的:/files/home/johndoe/myfile.txt。
OpenAPI 支持¶
OpenAPI 不支持声明包含路径的*路径参数*,因为这会导致测试和定义更加困难。
不过,仍可使用 Starlette 内置工具在 FastAPI 中实现这一功能。
而且不影响文档正常运行,但是不会添加该参数包含路径的说明。
路径转换器¶
直接使用 Starlette 的选项声明包含*路径*的*路径参数*: 
/files/{file_path:path}本例中,参数名为 file_path,结尾部分的 :path 说明该参数应匹配*路径*。用法如下: 
from fastapi import FastAPIapp = FastAPI()@app.get("/files/{file_path:path}")
async def read_file(file_path: str):return {"file_path": file_path}"提示"
注意,包含 /home/johndoe/myfile.txt 的路径参数要以斜杠(/)开头。
本例中的 URL 是 /files//home/johndoe/myfile.txt。注意,files 和 home 之间要使用**双斜杠**(//)。
实践
本次实验的main.py文件:
from fastapi import FastAPI
from enum import Enumclass ModelName(str, Enum):alexnet = "alexnet"resnet = "resnet"lenet = "lenet"app = FastAPI()@app.get("/")
async def root():return {"message": "Hello World"}@app.get("/test")
async def test():return {"message": "This is a test"}@app.get("/items/{item_id}")
async def read_item(item_id: int):return {"item_id": item_id}@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):if model_name is ModelName.alexnet:return {"model_name": model_name, "message": "Deep Learning FTW!"}if model_name.value == "lenet":return {"model_name": model_name, "message": "LeCNN all the images"}return {"model_name": model_name, "message": "Have some residuals"}@app.get("/files/{file_path:path}")
async def read_file(file_path: str):return {"file_path": file_path}然后执行:
uvicorn main:app --host 0.0.0.0 --reload总结
本节主要学习了声明路径参数的类型,通过简短、直观的 Python 标准类型声明,FastAPI 可以获得:
- 编辑器支持:错误检查,代码自动补全等
- 数据**解析**
- 数据校验
- API 注解和 API 文档
只需要声明一次即可。
这可能是除了性能以外,FastAPI 与其它框架相比的主要优势。
总体感觉FastAPI的文档要比Pyramid的文档更易上手些。
