深入FastAPI:路径参数、查询参数及其检校
引言
大家好,我是GISer Liu😁,一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年11月学习赛的FastAPI学习总结文档;本文主要讲解路径参数、查询参数及其检校机制。💕💕😊
介绍
FastAPI 是一个现代、快速(高性能)的 Web 框架,基于 Python 3.7+ 标准类型提示。它特别适合构建 API,因为它支持异步编程、自动生成文档,并且具有强大的数据验证功能。本文将深入探讨 FastAPI 中的路径参数、查询参数及其检校机制,并通过实际代码示例帮助你更好地理解和应用这些概念。
一、FastAPI 基础
1. FastAPI 简介
FastAPI 是一个基于 Python 的 Web 框架,旨在提供高性能、易于使用和快速开发的特性。它利用 Python 的类型提示来进行数据验证和自动生成文档,使得开发 API 变得更加简单和高效。
2. 安装 FastAPI
首先,你需要安装 FastAPI 和 Uvicorn(一个 ASGI 服务器)。你可以通过以下命令进行安装:
pip install fastapi uvicorn
3. 创建第一个 FastAPI 应用
创建一个简单的 FastAPI 应用,启动一个基本的 Web 服务:
from fastapi import FastAPIapp = FastAPI()@app.get("/")
def read_root():return {"Hello": "World"}if __name__ == "__main__":import uvicornuvicorn.run(app, host="127.0.0.1", port=8000)
二、路径参数
1. 常见 HTTP 请求方法
在 FastAPI 中,常见的 HTTP 请求方法包括:
① GET:用于获取资源
- 幂等性:是
- 缓存:GET 请求可以被缓存,这意味着如果相同的请求多次发出,浏览器可能会使用缓存的响应而不是重新发送请求。
- 安全性:GET 请求是安全的,因为它只用于读取数据,不会对服务器上的资源进行修改。
- URL 参数:GET 请求的参数通常通过 URL 传递,这使得它们容易被记录在服务器日志、浏览器历史记录中,并且可以被收藏为书签。
- 使用场景:适用于获取数据、查询资源、获取资源列表等场景。
② POST:用于创建资源
- 幂等性:否
- 缓存:POST 请求不会被缓存,每次请求都会导致服务器处理请求并生成新的资源。
- 安全性:POST 请求不是安全的,因为它会修改服务器上的资源。
- 请求体:POST 请求的数据通常放在请求体中,这使得它可以传递更复杂的数据结构,如 JSON、XML 等。
- 使用场景:适用于提交表单数据、上传文件、创建新资源等场景。
③ PUT:用于更新资源
- 幂等性:是
- 缓存:PUT 请求不会被缓存。
- 安全性:PUT 请求不是安全的,因为它会修改服务器上的资源。
- 请求体:PUT 请求的数据通常放在请求体中,用于更新指定的资源。如果资源不存在,PUT 请求可以用于创建资源。
- 使用场景:适用于更新现有资源、替换资源内容等场景。
④ DELETE:用于删除资源
- 幂等性:是
- 缓存:DELETE 请求不会被缓存。
- 安全性:DELETE 请求不是安全的,因为它会删除服务器上的资源。
- 使用场景:适用于删除指定资源、清理资源等场景。
⑤ 补充知识
- 幂等性与安全性:幂等性和安全性是 HTTP 方法的两个重要特性。幂等性意味着多次执行相同的操作不会产生不同的结果,而安全性意味着操作不会修改服务器上的资源。
- URL 确定性:在使用 PUT 和 POST 时,URL 的确定性是一个关键因素。如果 URL 可以在客户端确定(例如,资源的唯一标识符已知),则使用 PUT;如果 URL 需要在服务端确定(例如,使用数据库自增主键),则使用 POST。
- 状态码:在 FastAPI 中,不同的 HTTP 方法通常会返回不同的状态码。例如,GET 请求成功时通常返回 200 OK,POST 请求成功时通常返回 201 Created,PUT 请求成功时通常返回 200 OK 或 204 No Content,DELETE 请求成功时通常返回 204 No Content。
2. 路径参数的基本概念
路径参数是 URL 中的一部分,用于标识特定的资源。它们通常用于动态地指定资源的位置或标识符。路径参数在 FastAPI 中非常重要,因为它们允许你根据 URL 中的不同部分来处理不同的资源。
假设我们有一个 API,用于管理用户和他们的订单。我们可以使用路径参数来标识特定的用户和订单。
示例 1:获取特定用户的信息
from fastapi import FastAPIapp = FastAPI()@app.get("/users/{user_id}")
def get_user(user_id: int):# 假设我们从数据库中获取用户信息user_info = {"user_id": user_id, "name": "John Doe", "email": "john.doe@example.com"}return user_info
在这个例子中,/users/{user_id}
中的 {user_id}
是一个路径参数。当客户端请求 /users/123
时,user_id
将被解析为 123
,并且 get_user
函数将返回用户 ID 为 123
的用户信息。如下图:
示例 2:获取特定用户的订单
@app.get("/users/{user_id}/orders/{order_id}")
def get_order(user_id: int, order_id: int):# 假设我们从数据库中获取订单信息order_info = {"user_id": user_id, "order_id": order_id, "product": "Laptop", "price": 1200}return order_info
在这个例子中,/users/{user_id}/orders/{order_id}
中的 {user_id}
和 {order_id}
是两个路径参数。当客户端请求 /users/123/orders/456
时,user_id
将被解析为 123
,order_id
将被解析为 456
,并且 get_order
函数将返回用户 ID 为 123
的订单 ID 为 456
的订单信息。
示例 3:更新特定用户的订单
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()@app.put("/users/{user_id}/orders/{order_id}")
def update_order(user_id: int, order_id: int, new_product: str, new_price: float):# 假设我们更新数据库中的订单信息if not order_exists(user_id, order_id):raise HTTPException(status_code=404, detail="Order not found")# 更新订单信息update_order_in_db(user_id, order_id, new_product, new_price)return {"message": "Order updated successfully"}def order_exists(user_id: int, order_id: int) -> bool:# 假设我们检查订单是否存在return True # 示例中假设订单总是存在def update_order_in_db(user_id: int, order_id: int, new_product: str, new_price: float):# 假设我们更新数据库中的订单信息pass
在这个例子中,/users/{user_id}/orders/{order_id}
中的 {user_id}
和 {order_id}
是两个路径参数。当客户端请求 /users/123/orders/456
并附带新的产品名称和价格时,update_order
函数将更新用户 ID 为 123
的订单 ID 为 456
的订单信息。
完整代码
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()# 示例 1:获取特定用户的信息
@app.get("/users/{user_id}")
def get_user(user_id: int):# 假设我们从数据库中获取用户信息user_info = {"user_id": user_id, "name": "John Doe", "email": "john.doe@example.com"}return user_info# 示例 2:获取特定用户的订单
@app.get("/users/{user_id}/orders/{order_id}")
def get_order(user_id: int, order_id: int):# 假设我们从数据库中获取订单信息order_info = {"user_id": user_id, "order_id": order_id, "product": "Laptop", "price": 1200}return order_info# 示例 3:更新特定用户的订单
@app.put("/users/{user_id}/orders/{order_id}")
def update_order(user_id: int, order_id: int, new_product: str, new_price: float):# 假设我们更新数据库中的订单信息if not order_exists(user_id, order_id):raise HTTPException(status_code=404, detail="Order not found")# 更新订单信息update_order_in_db(user_id, order_id, new_product, new_price)return {"message": "Order updated successfully"}def order_exists(user_id: int, order_id: int) -> bool:# 假设我们检查订单是否存在return True # 示例中假设订单总是存在def update_order_in_db(user_id: int, order_id: int, new_product: str, new_price: float):# 假设我们更新数据库中的订单信息pass# 启动 FastAPI 应用程序的指令
if __name__ == "__main__":import uvicornuvicorn.run(app, host="127.0.0.1", port=8000)
路径参数在 FastAPI 中用于动态地指定资源的位置或标识符。通过在 URL 中使用路径参数,你可以根据不同的参数值来处理不同的资源。路径参数的使用使得 API 更加灵活和强大,能够处理各种复杂的资源请求。
3. 构建一个 TodoList 的 CRUD 示例
我们以一个简单的 TodoList 应用为例,展示如何使用 FastAPI 进行增删改查操作:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Listapp = FastAPI()class Todo(BaseModel):id: inttask: strtodos = [{"id": 1,"task": "Learn FastAPI"
}]@app.post("/todos/", response_model=Todo)
def create_todo(todo: Todo):todos.append(todo)return todo@app.get("/todos/", response_model=List[Todo])
def read_todos():return todos@app.get("/todos/{todo_id}", response_model=Todo)
def read_todo(todo_id: int):for todo in todos:print(todo),"todo"if todo["id"] == todo_id:return todoreturn {"error": "Todo not found"}@app.put("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: int, updated_todo: Todo):for i, todo in enumerate(todos):if todo["id"] == todo_id:todos[i] = updated_todoreturn updated_todoreturn {"error": "Todo not found"}@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):for i, todo in enumerate(todos):if todo["id"]== todo_id:del todos[i]return {"message": "Todo deleted"}return {"error": "Todo not found"}# 启动 FastAPI 应用程序的指令
if __name__ == "__main__":import uvicornuvicorn.run(app, host="127.0.0.1", port=8000)
这里我们可以使用FastAPI自带的文档http://127.0.0.1:8000/docs进行查看:
- GET请求
- POST请求
- PUT请求
- DELETE请求
4. 路径参数的类型限制
在 FastAPI 中,路径参数可以指定类型,例如 int
、str
等。FastAPI 会自动进行类型转换和验证。
@app.get("/items/{item_id}")
def read_item(item_id: int):return {"item_id": item_id}
如果使用了不同的类型,则会报错
5. 数据检校与 Pydantic
Pydantic 是 FastAPI 中用于数据验证和解析的库。它通过 Python 的类型提示来定义数据模型,并自动进行验证。Pydantic 的主要作用是确保传入的数据符合预期的格式和类型,从而减少手动验证的复杂性和错误。
from pydantic import BaseModelclass Item(BaseModel):name: strprice: floatis_offer: bool = None
在这个案例中,我们定义了一个 Item
类,它继承自 BaseModel
。这个类有三个字段:
name
: 类型为str
,表示商品的名称。price
: 类型为float
,表示商品的价格。is_offer
: 类型为bool
,表示商品是否为特价商品。这个字段有一个默认值None
,表示它是可选的。
使用 Pydantic 进行数据检校的一般流程如下:
- 定义数据模型:首先,你需要定义一个继承自
BaseModel
的类,并在类中定义字段及其类型。 - 实例化数据模型:当你接收到数据时,将数据传递给数据模型的构造函数,Pydantic 会自动验证数据是否符合定义的类型和格式。
- 处理验证结果:如果数据验证通过,你可以继续处理数据;如果验证失败,Pydantic 会抛出一个
ValidationError
异常,并提供详细的错误信息。
下面案例代码中,展示了如何使用 Pydantic 进行数据验证和解析。
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, ValidationErrorapp = FastAPI()# 定义数据模型
class Item(BaseModel):name: strprice: floatis_offer: bool = None# 示例 1:创建新商品
@app.post("/items/")
def create_item(item: Item):# 数据已经通过 Pydantic 验证return {"message": "Item created successfully", "item": item}# 示例 2:更新商品信息
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):# 假设我们更新数据库中的商品信息if not item_exists(item_id):raise HTTPException(status_code=404, detail="Item not found")# 更新商品信息update_item_in_db(item_id, item)return {"message": "Item updated successfully", "item": item}def item_exists(item_id: int) -> bool:# 假设我们检查商品是否存在return True # 示例中假设商品总是存在def update_item_in_db(item_id: int, item: Item):# 假设我们更新数据库中的商品信息pass# 启动 FastAPI 应用程序的指令
if __name__ == "__main__":import uvicornuvicorn.run(app, host="127.0.0.1", port=8000)
- 如果传入的数据符合
Item
模型的定义,Pydantic 会自动解析并验证数据。 - 如果数据不符合定义,Pydantic 会抛出一个
ValidationError
异常,并返回详细的错误信息。
Pydantic 是 FastAPI 中用于数据验证和解析的强大工具。通过定义数据模型并使用 Pydantic 进行验证,开发者可以确保传入的数据符合预期的格式和类型,从而减少手动验证的复杂性和错误。Pydantic 还提供了详细的错误信息,辅助快速定位和修复问题。
6. 预设值与枚举值
你可以为路径参数设置预设值或使用枚举值进行限制:
from enum import Enumclass ModelName(str, Enum):alexnet = "alexnet"resnet = "resnet"lenet = "lenet"@app.get("/models/{model_name}")
def get_model(model_name: ModelName):if model_name == 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"}
7. 包含路径的路径参数
路径参数可以包含路径,例如 /files/{file_path:path}
,其中 file_path
可以是一个文件路径。
@app.get("/files/{file_path:path}")
def read_file(file_path: str):return {"file_path": file_path}
三、查询参数
1. 查询参数的基本概念
查询参数是 URL 中 ?
后面的部分,用于传递额外的信息。例如,/items/?skip=0&limit=10
中的 skip
和 limit
就是查询参数。
2. 默认查询参数
你可以为查询参数设置默认值:
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10):return {"skip": skip, "limit": limit}
这里的skip: int = 0就指代默认的skip参数是0,如果用户不指定该值,则会默认这个参数为0;
3. 必需查询参数
某些查询参数是必需的,如果没有提供,FastAPI 会返回错误:
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str):return {"item_id": item_id, "q": q}
这里我们没有为其设置默认值,如果不输入该值,其会报错
4. 可选查询参数
你可以将查询参数设置为可选的,并提供默认值:
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):return {"item_id": item_id, "q": q}
可有可没有的参数,如果有需要你就传入,如果不需要,函数也不会报错;注意区分可选查询参数和默认查询参数
5. 多个路径参数与多个查询参数
在 FastAPI 中,用户可以同时使用多个路径参数和多个查询参数:
@app.get("/items/{item_id}/users/{user_id}")
def read_item_user(item_id: int, user_id: int, q: str = None, short: bool = False):item = {"item_id": item_id, "user_id": user_id}if q:item.update({"q": q})if not short:item.update({"description": "This is an amazing item that has a long description"})return item
- 路径参数用以简单参数传递;
- 查询参数通过get或post请求中使用较多;
四、路径参数检校
在 FastAPI 中,路径参数校验是一个非常重要的功能,它允许开发者为路径参数添加类型检查、数值范围限制以及元数据声明。通过使用 Path
库可以轻松地实现这些功能。
1. 概念理解
① 导入 Path
首先,你需要从 fastapi
中导入 Path
:
from fastapi import FastAPI, Path
② 声明元数据
声明元数据是为了提供更多的信息,帮助开发者和其他使用者更好地理解 API 的设计意图。元数据可以包括标题、描述等。
from typing import Annotated
from fastapi import FastAPI, Pathapp = FastAPI()@app.get("/items/{item_id}")
async def read_items(item_id: Annotated[int, Path(title="The ID of the item to get")]
):results = {"item_id": item_id}return results
在这个例子中,item_id
是一个路径参数,并且我们为其添加了一个标题 "The ID of the item to get"
。这个标题可以帮助其他开发者理解这个参数的用途。
③ 数值校验
数值校验是为了确保路径参数的值在合理的范围内。FastAPI 提供了以下几种数值校验参数:
gt
: 大于(greater than)ge
: 大于等于(greater than or equal)lt
: 小于(less than)le
: 小于等于(less than or equal)
2.案例
① 大于等于校验
下面代码中展示了如何使用 Path
库进行数值校验:
import uvicorn
from typing import Annotated
from fastapi import FastAPI, Pathapp = FastAPI()@app.get("/items/{item_id}")
async def read_items(item_id: Annotated[int, Path(title="The ID of the item to get", ge=5)]
):results = {"item_id": item_id}return resultsif __name__ == '__main__':uvicorn.run(app, host='127.0.0.1', port=8009)
在这里,item_id
必须是一个大于或等于 5 的整数。如果请求的路径参数不满足这个条件,FastAPI 会返回一个 422 错误。
- 测试
我们在http//127.0.0.1:8009/docs进行测试:
- 访问
http://127.0.0.1:8009/items/6
,你会看到{"item_id": 6}
。
- 访问
http://127.0.0.1:8009/items/4
,你会看到一个错误信息,提示item_id
必须大于或等于 5。
② 浮点数校验
数值校验同样适用于浮点数。以下是一个示例,展示了如何对浮点数进行校验:
import uvicorn
from typing import Annotated
from fastapi import FastAPI, Pathapp = FastAPI()@app.get("/items-float/{item_id}")
async def read_items(item_id: Annotated[float, Path(title="The ID of the item to get", ge=5.5)]
):results = {"item_id": item_id}return resultsif __name__ == '__main__':uvicorn.run(app, host='127.0.0.1', port=8009)
在这个例子中,
item_id
必须是一个大于或等于 5.5 的浮点数。
- 访问
http://127.0.0.1:8009/items/5.6
,你会看到{"item_id": 5.6}
。
- 访问
http://127.0.0.1:8009/items/5.4
,你会看到一个错误信息,提示item_id
必须大于或等于 5.5。
这里你或许会尝试输入6来验证错误,但是在python中,整数是可以被自动转换为浮点型的,因此输入6并不会报类型错误;
五、查询参数检校
我们依旧在http://127.0.0.1:8000/docs#/中进行测试:
1. 字符串参数限制
你可以对查询参数进行字符串长度限制:
from fastapi import FastAPI, Queryapp = FastAPI()@app.get("/items/")
def read_items(q: str = Query(None, max_length=50)):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results
这里如果传入的字符串长度超过50,就会报错!
2. 增加额外的约束条件
你可以为查询参数增加更多的约束条件,例如最小长度、正则表达式等:
@app.get("/items_with_constraints/")
def read_items_with_constraints(q: str = Query(None, min_length=3, max_length=50, regex="^[a-z]+$")
):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results
这里要求我们最小长度为3,最大为50;q只能包含小写字幕并且至少有一个字符;这里如果我们填写不规范,都会报错422;
3. 最短字符长度与正则表达式
你可以使用 min_length
和 regex
来进一步限制查询参数:
@app.get("/items_with_regex/")
def read_items_with_regex(q: str = Query(None, min_length=3, max_length=50, regex="^[a-z]+$")
):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results
4. 声明必需参数与省略号
你可以使用 ...
来声明必需的查询参数:
@app.get("/items_required/")
def read_items_required(q: str = Query(..., min_length=3)):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results
使用
<font style="color:rgb(64, 64, 64);">...</font>
来声明必需的查询参数是一种明确的方式,表示该参数是必需的,不能省略
5. 声明更多元数据
你可以为查询参数声明更多的元数据,例如标题、描述等:
@app.get("/items_with_metadata/")
def read_items_with_metadata(q: str = Query(None,title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,)
):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results
声明详细的元数据可以帮助开发者更好的理解接口!
6.完整代码
代码如下,各位仔细测试就好!
from fastapi import FastAPI, Queryapp = FastAPI()# 1. 字符串参数限制
@app.get("/items/")
def read_items(q: str = Query(None, max_length=50)):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results# 2. 增加额外的约束条件
@app.get("/items_with_constraints/")
def read_items_with_constraints(q: str = Query(None, min_length=3, max_length=50, regex="^[a-z]+$")
):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results# 3. 最短字符长度与正则表达式
@app.get("/items_with_regex/")
def read_items_with_regex(q: str = Query(None, min_length=3, max_length=50, regex="^[a-z]+$")
):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results# 4. 声明必需参数与省略号
@app.get("/items_required/")
def read_items_required(q: str = Query(..., min_length=3)):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results# 5. 声明更多元数据
@app.get("/items_with_metadata/")
def read_items_with_metadata(q: str = Query(None,title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,)
):results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}if q:results.update({"q": q})return results# 运行 FastAPI 应用
if __name__ == "__main__":import uvicornuvicorn.run(app, host="127.0.0.1", port=8000)
总结
通过本文,我们深入探讨了 FastAPI 中的路径参数、查询参数及其检校机制。作者通过实际代码示例展示了如何使用 FastAPI 构建一个简单的 TodoList 应用,并详细介绍了路径参数和查询参数的各种用法和限制。希望本文能帮助你更好地理解和应用 FastAPI,提升各位入门者的 Python Web 开发技能。
OK!今天就学习到这里了!😎
相关链接
- 项目地址:FastAPI-CookBook
- 相关文档:专栏地址
- 作者主页:GISer Liu-CSDN博客
如果觉得我的文章对您有帮助,三连+关注便是对我创作的最大鼓励!或者一个star🌟也可以😂.