FastAPI简介
- 一、FastAPI简介
- 二、FastAPI安装
- 2.1 使用pip安装FastAPI
- 2.2 FastAPI的demo
- 2.3 FastAPI的程序结构
- 三、装饰器请求方法
- 四、用户请求
- 4.1 路径参数
- 4.1.1 单个路径参数
- 4.1.2 多个路径参数
- 4.1.3 固定路径和路径参数的冲突
- 4.2 查询参数
- 4.3 默认参数
- 4.4 可选参数
- 五、请求体
- 5.1 关于请求体
- 5.2 实现请求体
- 5.3 关于Pydantic的BaseModel类
- 5.4 关于Pydantic.Field
一、FastAPI简介
FastAPI源码
FastAPI官方中文文档
FastAPI官方文档
Pydantic官方文档
二、FastAPI安装
2.1 使用pip安装FastAPI
- 安装FastAPI
pip install fastapi
- 安装FastAPI依赖库
FastAPI的依赖库包括 Uvicorn、Pydantic 和 Starlette 库
pip install uvicorn[standard]
pip install pydantic
pip install starlette
可以安装所有依赖库
pip3 install fastapi[all]
- 验证安装成功
import fastapi
print(fastapi.__version__)
2.2 FastAPI的demo
创建main.py的文件:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
在命令行运行如下指令:
uvicorn main:app --reload
可以看到如下输出:
INFO: Will watch for changes in these directories: ['/root']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [3531390] using WatchFiles
INFO: Started server process [3531392]
INFO: Waiting for application startup.
INFO: Application startup complete.
在本机浏览器中输入: http://127.0.0.1:8000
。
打印出:"message": "Hello World"
,则表示安装成功。
Uvicorn 启动信息:
INFO: Will watch for changes in these directories: ['/root']
:
表示 Uvicorn 将监视 /root 目录下的文件变化以触发自动重载。
这意味着如果在这个目录下对 Python 文件进行了任何改动,Uvicorn 将会重启应用。INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
:
告知用户 Uvicorn 已经启动并在本地地址 http://127.0.0.1:8000 上监听请求。
这意味着您可以访问此 URL 来测试您的 API。INFO: Started reloader process [3531227] using WatchFiles
:
说明 Uvicorn 使用 WatchFiles 方法启动了一个后台进程来监控文件系统的变化,以便能够及时响应文件更新并触发应用的重新加载。INFO: Started server process [3531230]
:
显示 Uvicorn 已经启动了一个新的子进程来处理 HTTP 请求。INFO: Waiting for application startup
. 和INFO: Application startup complete
.:
这两条日志表明 FastAPI 应用正在启动,并且已经成功完成了启动过程,现在可以接收来自客户端的请求。- 综上所述,已经正确设置了开发环境,并且 FastAPI 应用程序正在正常运行。
开发时,只要保持终端窗口打开并且不要关闭 Uvicorn 进程,每次对 main.py 或者其他被监控的文件做出更改,Uvicorn 都会自动重新加载应用,使得最新的代码变更立即生效。
如果要停止服务,只需按 CTRL+C 即可终止 Uvicorn 进程。
此外,请注意,在生产环境中部署时应避免使用 --reload 选项,因为这可能会导致不必要的资源消耗和稳定性问题9。
Uvicorn 命令详解:
uvicorn main:app --reload
是一个用于启动 FastAPI 应用程序的命令行语句,它结合了 Uvicorn 服务器与 FastAPI 框架的功能。
作用就是使用 Uvicorn 启动位于 main.py 文件中的 FastAPI 应用,并且开启了热重载模式以便于开发过程中快速迭代和测试。
这条命令中的各个部分具有特定的意义:
uvicorn
:这是调用 Uvicorn ASGI(异步网关接口)服务器的命令。main:app
:这部分指定了要运行的应用程序。
main
表示 Python 模块的名字,通常是文件名为 main.py 的模块(即 main.py 文件去掉扩展名);app
则是指在这个模块中定义的一个 FastAPI 实例对象的名字。
例如,在 main.py 文件中有如下代码 app = FastAPI(),那么这里的 app 就是指这个 FastAPI 应用实例。--reload
:这是一个选项参数,表示开启热重载功能。
- 当启用此选项时,Uvicorn 会在检测到代码发生更改后自动重启服务器。
- 这对于开发环境非常有用,因为它可以确保开发者在修改代码后无需手动重启服务器即可看到最新的更改效果。
- 值得注意的是,在生产环境中不应该使用 --reload 选项,因为这会消耗更多资源并且可能不如预期稳定。
- 在非默认端口上使用热重载功能时,可以通过配置启动命令或修改配置文件来实现
- 命令:uvicorn main:app --host 127.0.0.1 --port 9000 --reload
- –host 参数指定了监听的 IP 地址
- –port 参数则定义了监听的端口号
- –reload 选项开启了热重载功能
2.3 FastAPI的程序结构
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
- Step1: 导包
import uvicorn
from fastapi import FastAPI
- Step2: 创建一个 FastAPI 实例,这个实例是应用程序的核心,所有的路由和配置都将围绕它进行
app = FastAPI()
- Step3: 定义一个路径操作函数
root
,它被装饰器@app.get("/")
标记为根路径(/
)的处理器
这意味着当用户访问应用程序的根 URL 时,FastAPI 将调用此函数来处理请求。- 返回一个 JSON 响应,包含消息 “Hello World”。由于 FastAPI 支持异步定义,
- 使用
async def
可以更好地利用其异步特性,从而提高性能。
@app.get("/")
async def root():
return {"message": "Hello World"}
- Step4: 检查当前模块是否作为主程序运行。如果是,则执行以下代码块。
调用uvicorn.run()
函数启动 Uvicorn 服务器,提供必要的参数如应用对象 (app
)、主机地址 (host
) 和端口 (port
)- 参数
host="127.0.0.1"
表示服务器只接受来自本地计算机的连接; - 参数
port=8000
表示服务器将在端口 8000 上监听 HTTP 请求。
- 参数
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
- Step5: 查看运行结果
在本机浏览器中输入:http://127.0.0.1:8000
。
打印出:"message": "Hello World"
,则表示安装成功。
装饰器函数的返回值:
- 返回 Python 数据类型
当路由函数返回一个简单的 Python 数据类型时,例如字符串、整数或布尔值,FastAPI 会自动将它们转换成相应的 JSON 格式,并设置正确的 Content-Type 头信息为 application/json。
这意味着如果返回的是字符串 “hello fastapi”,它将被当作 JSON 字符串返回给客户端。- 返回字典或列表
返回字典或列表时,FastAPI 同样会将其序列化为 JSON。
- 对于字典而言,键必须是字符串,而值可以是任何能被 JSON 序列化的数据类型;
- 对于列表,则其元素也需满足同样的条件。
- 返回 Pydantic 模型
Pydantic 是一个用于数据验证及设定的库,它允许定义具有类型注解的数据模型。
当从 FastAPI 路由中返回 Pydantic 模型实例时,框架会自动调用 jsonable_encoder 函数来处理复杂的数据类型(如日期时间、UUID 等),确保它们能够正确地被 JSON 序列化。
这不仅简化了开发者的代码编写过程,还保证了 API 输出的一致性和准确性。- 使用自定义 JSONResponse
有时候,开发者可能希望对响应进行更细粒度的控制,比如添加自定义头部或者设置不同的状态码。
这时就可以使用 JSONResponse 类来构建响应对象。JSONResponse 继承自 Response 类,允许指定额外的参数如 status_code、headers 和 media_type 来定制响应行为
三、装饰器请求方法
装饰器包含8中HTTP请求方法:
@app.get()
用于从服务器获取信息。
from fastapi import FastAPI
app = 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/42
# 运行结果:
# {
# "item_id": 42
# }
@app.post()
用于向指定资源提交数据,常用于创建新资源。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.post("/items/")
async def create_item(item: Item):
return item
# 如果发送 POST 请求到 http://127.0.0.1:8000/items/
# 并且请求体是 {"name": "Foo", "description": "A very nice Item", "price": 35.4}
# 运行结果:
# {
# "name": "Foo",
# "description": "A very nice Item",
# "price": 35.4,
# "tax": null
# }
@app.delete()
用于删除指定资源。
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
@app.put()
用于更新整个资源。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, **item.dict()}
# 如果发送 PUT 请求到 http://127.0.0.1:8000/items/789
# 并且请求体是 {"name": "Updated Foo", "price": 35.4}
# 运行结果:
# {
# "item_id": 789,
# "name": "Updated Foo",
# "description": null,
# "price": 35.4,
# "tax": null
# }
@app.head()
与 GET 类似,但不返回消息体,只用来获取响应头。
from fastapi import FastAPI
app = FastAPI()
@app.head("/items/")
async def head_items():
# 只返回头部,没有主体内容
pass
# 如果发送 HEAD 请求到 http://127.0.0.1:8000/items/
# 运行结果:
# HTTP响应头,例如:
# Content-Length: 0
# Content-Type: application/json
# Date: Sat, 14 Dec 2024 17:54:00 GMT
# Server: uvicorn
@app.patch()
用于对资源进行部分更新。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemUpdate(BaseModel):
name: str | None = None
description: str | None = None
price: float | None = None
tax: float | None = None
@app.patch("/items/{item_id}")
async def patch_item(item_id: int, item_update: ItemUpdate):
return {"item_id": item_id, **item_update.dict(exclude_unset=True)}
# 如果发送 PATCH 请求到 http://127.0.0.1:8000/items/567
# 并且请求体是 {"name": "Patched Foo"}
# 运行结果:
# {
# "item_id": 567,
# "name": "Patched Foo"
# }
@app.trace()
用于追踪路径,通常由客户端发送请求,服务器将请求作为实体返回。
from fastapi import FastAPI
app = FastAPI()
@app.trace("/trace")
async def trace_endpoint():
# 追踪请求
pass
# 如果发送 TRACE 请求到 http://127.0.0.1:8000/trace
# 运行结果:
# 返回客户端发送的原始请求
@app.options()
用于描述通信选项,即客户端可以对资源执行哪些方法。
from fastapi import FastAPI
app = FastAPI()
@app.options("/items/")
async def options_items():
return {"Allow": "GET, POST, PUT, DELETE, PATCH, OPTIONS"}
# 如果发送 OPTIONS 请求到 http://127.0.0.1:8000/items/
# 运行结果:
# {
# "Allow": "GET, POST, PUT, DELETE, PATCH, OPTIONS"
# }
四、用户请求
FastAPI允许开发人员通过路径参数或路径变量从API的端点URL中获取请求数据。
(路径参数或路径变量使URL呈现一些动态变化。)
路径参数包含一个值,该值成为由花括号{}
指示的URL的一部分。
在URL中设置这些路径参数后,FastAPI要求通过应用类型提示来声明这些参数。
4.1 路径参数
4.1.1 单个路径参数
以下delete_username()
服务是一个DELETE API
方法,它使用username
路径参数搜索用户登录记录以进行删除:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
@app.delete("/login/remove/{username}")
async def delete_username(username: str):
if username == None:
return {"message": "invalid username"}
else:
return {"message": "deleted username"}
# 如果发送 DELETE 请求到 http://127.0.0.1:8000/login/remove/Jack
# 运行结果:
# {
# "message": "deleted username"
# }
4.1.2 多个路径参数
如果最左边的变量比最右边的变量更有可能填充值,则可以接受多个路径参数。
换句话说,最左边的变量的重要性将使得其处理比最右边的路径变量更相关,也更正确。
应用此标准是为了确保端点URL不会看起来像其他URL,防止产生冲突和混淆。
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/login/{username}/{password}")
async def combine_parameters(username: str, password: int):
# 定义有效的用户名和密码
valid_username = "Jack"
valid_password = 123456
# 检查提供的用户名和密码是否匹配预定义的有效值
if username == valid_username and password == valid_password:
combined_result = f"username is {username}, and password is {password}"
return {"message": combined_result}
else:
# 如果提供的用户名或密码不正确,则抛出一个HTTP异常
raise HTTPException(status_code=401, detail="Invalid username or password")
# 如果发送 DELETE 请求到 http://127.0.0.1:8000/login/Jack/123456
# 运行结果:
# {
# "message": "username is Jack, and password is 123456"
# }
4.1.3 固定路径和路径参数的冲突
FastAPI对属于基本路径或具有不同子目录的顶级域路径的端点URL不友好。
当我们动态URL模式在分配特定路径变量时,看起来与其他固定端点URL相同时,就会发生这种情况。
这些固定URL在这些动态URL之后依次实现。
以下服务就是一个示例:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/login/{username}/{password}")
async def combine_parameters(username: str, password: str):
# 定义有效的用户名和密码
valid_username = "Jack"
valid_password = "123456"
# 检查提供的用户名和密码是否匹配预定义的有效值
if username == valid_username and password == valid_password:
combined_result = f"username is {username}, and password is {password}"
return {"message": combined_result}
else:
# 如果提供的用户名或密码不正确,则抛出一个HTTP异常
raise HTTPException(status_code=401, detail="Invalid username or password")
@app.get("/login/detail/info")
async def login_info():
return {"message": "username and password are needed"}
# 如果发送 DELETE 请求到 http://127.0.0.1:8000/login/detail/info
# 运行结果:
# {
# "message": "username and password are needed"
# }
理论上,我们期望的输出如下:
# 如果发送 DELETE 请求到 http://127.0.0.1:8000/login/detail/info
# 运行结果:
# {
# "message": "username and password are needed"
# }
实际得到的输出:
分析原因:
访问的url是http://127.0.0.1:8000/login/detail/info
实际访问的url是http://127.0.0.1:8000/login/{detail}/{info}
也就是说,固定路径的details和info路径目录,被视为username和password参数值。
解决办法:
首先应声明所有固定路径,然后再声明带有路径参数的动态端点URL。
上述示例中的login_info()服务应该在login_with_token()之前声明。
修改后的代码:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/login/detail/info")
async def login_info():
return {"message": "username and password are needed"}
@app.get("/login/{username}/{password}")
async def combine_parameters(username: str, password: str):
# 定义有效的用户名和密码
valid_username = "Jack"
valid_password = "123456"
# 检查提供的用户名和密码是否匹配预定义的有效值
if username == valid_username and password == valid_password:
combined_result = f"username is {username}, and password is {password}"
return {"message": combined_result}
else:
# 如果提供的用户名或密码不正确,则抛出一个HTTP异常
raise HTTPException(status_code=401, detail="Invalid username or password"
运行结果和预期一致:
4.2 查询参数
查询参数是在端点URL结束后提供的键值对(key-value pair
), 用问号?
表示。
就像路径参数一样,它也保存请求数据,所有查询参数也在服务方法中声明。
API服务可以管理一系列由&
分割的查询参数。
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/login/")
async def login(username: str, password: str):
# 定义有效的用户名和密码
valid_username = "Jack"
valid_password = "123456"
# 检查提供的用户名和密码是否匹配预定义的有效值
if username == valid_username and password == valid_password:
combined_result = f"username is {username}, and password is {password}"
return {"message": combined_result}
else:
return {"message": error}
访问url:http://127.0.0.1:8000/login/?username=Jack&password=123456
该login服务使用username和password作为str类型的查询参数。
4.3 默认参数
API服务的查看参数和路径参数,都不是固定参数,可以为它们指定默认值,避免出现验证错误消息。
根据要求,分配的默认值通常是
- 数字类型的0
- 布尔类型的False
- 字符串类型的空字符串
- List类型的空列表
- Dict类型的空字典
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/login/")
async def login(username: str="Jack", password: str="123456"):
# 定义有效的用户名和密码
valid_username = "Jack"
valid_password = "123456"
# 检查提供的用户名和密码是否匹配预定义的有效值
if username == valid_username and password == valid_password:
combined_result = f"username is {username}, and password is {password}"
return {"message": combined_result}
else:
return {"message": error}
# 如果发送 DELETE 请求到 http://127.0.0.1:8000/login
# 由于路径参数username和password有默认值,所以运行结果:
# {
# "message": "username is Jack, and password is 123456"
# }
4.4 可选参数
如果API服务的路径不一定需要由用户提供,可以将这些路径或参数设置为可选参数。
要声明可选参数,需要从typeing模块中导入Optional类型,然后使用它来设置参数。
它应该使用方括号[]
包装参数的假定数据类型,并且如果需要可以具有任何默认值。
from fastapi import FastAPI, HTTPException
from typing import Optional
app = FastAPI()
@app.get("/login/")
async def login(username: str, password: Optional[str] = None):
# 定义有效的用户名
valid_username = "Jack"
# 检查提供的用户名
if username == valid_username:
combined_result = f"username is {username}"
return {"message": combined_result}
else:
return {"message": error}
# 如果发送 GET 请求到 http://127.0.0.1:8000/login/?username=Jack
# 由于路径参数username和password有默认值,所以运行结果:
# {
# "message": "username is Jack"
# }
五、请求体
5.1 关于请求体
将数据从客户端(例如浏览器)发送到API时,可以将其作为 “请求体” 发送。
- 请求体是客户端发送到的API服务的数据。
- 响应体是API服务发送给客户端的数据。
API几乎总是必须发送一个响应体,但是客户端并不需要一直发送请求体。
定义请求体,需要使用 Pydantic 模型。注意以下几点
- 不能通过GET请求发送请求体
- 发送请求体数据,必须使用以下几种方法之一:POST(最常见)、PUT、DELETE、PATCH
5.2 实现请求体
- Setp1:从pydantic中导入BaseModel
from pydantic import BaseModel
- Setp2:创建它的子类以利用所有属性和行为
- Setp3:将模型定义为参数
5.3 关于Pydantic的BaseModel类
什么是 Pydantic 的 BaseModel?
Pydantic
是一个 Python 库,它帮助开发者确保他们的应用程序接收到的数据是正确的。
它通过使用 Python 的类型提示(type hints)来自动验证传入的数据是否符合预期的格式和类型。
BaseModel
是 Pydantic
提供的一个类,所有的数据模型都是从BaseModel类继承下来的。
为什么需要Pydantic 的 BaseModel?
如何使用Pydantic 的 BaseModel?
下面的代码示例展示了如何使用 Pydantic 来定义一个数据模型,并通过该模型对输入的数据进行验证和解析。
from datetime import datetime
from pydantic import BaseModel, PositiveInt
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
external_data = {
'id': 123,
'signup_ts': '2019-06-01 12:22',
'tastes': {
'wine': 9,
b'cheese': 7,
'cabbage': '1',
},
}
user = User(**external_data)
print(user.id)
#> 123
print(user.model_dump())
"""
{
'id': 123,
'name': 'John Doe',
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1},
}
- 定义 User 模型
from datetime import datetime
from pydantic import BaseModel, PositiveInt
class User(BaseModel):
id: int # 声明变量id是一个整型,必选字段
name: str = 'John Doe' # 声明变量name是str类型,默认值是'John Doe',必选字段
signup_ts: datetime | None # 声明变量signup_ts是一个时间类型,可选字段
tastes: dict[str, PositiveInt] # 声明变量tastes是字典类型,键是字符串类型的正整数,必选字段
- 字段类型声明:在 User 类中,我们为每个字段指定了类型。
- id 是必需的整数;
- name 是字符串,默认值为 ‘John Doe’,意味着如果创建实例时没有提供这个字段,则会自动设置为默认值;
- signup_ts 可以是 datetime 对象或 None,表示该字段是可选的;
- tastes 是一个字典,键为字符串,值为正整数(PositiveInt),确保用户兴趣爱好评分不会为负数。
- 类型注解与验证:
- Pydantic 使用 Python 的类型注解来指定字段的数据类型。
- 当创建 User 实例时,Pydantic 会根据这些注解自动验证传入的数据是否符合预期格式。
- 创建 User 实例并验证数据
external_data = {
'id': 123,
'signup_ts': '2019-06-01 12:22',
'tastes': {
'wine': 9,
b'cheese': 7,
'cabbage': '1',
},
}
user = User(**external_data)
- 数据转换:即使某些字段的值不是严格意义上的正确类型
例如,b'cheese'
是字节串而不是字符串,'1'
是字符串而不是整数),Pydantic 也会尝试将它们转换成正确的类型。
对于signup_ts
字段,Pydantic 能够识别ISO8601
格式的日期时间字符串,并将其转换为 datetime 对象。 - 错误处理:如果数据不符合预期类型或者违反了约束条件(如非正整数),Pydantic 将抛出 ValidationError 异常,并给出详细的错误信息。
在这个例子中,所有的数据都被成功地验证和转换了。
- ”解包”(unpacking)操作符
**
user = User(**external_data)
是一种非常常见的 Python 语法,它使用了所谓的“解包”(unpacking)操作符**
。
这种用法允许我们将一个字典中的键值对作为关键字参数传递给函数或类的构造方法。
在这个例子中,User
是由 Pydantic 定义的一个数据模型类,而external_data
是一个包含键值对的字典,这些键名与User
类定义的字段名称相匹配。
-
external_data 字典:
这是一个包含用户信息的字典,其中键对应于 User 模型类中的字段名,值则是相应的数据。 -
**
解包操作符:
通过在字典前加上双星号**
,Python 会将字典里的每个键值对展开成单独的关键字参数,并传递给 User 类的构造函数。
因此,上面的例子等价于如下代码:
user = User(id=123, signup_ts='2019-06-01 12:22', tastes={'wine': 9, 'cheese': 7, 'cabbage': '1'})
- 输出结果
print(user.id)
#> 123
print(user.model_dump())
"""
{
'id': 123,
'name': 'John Doe',
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1},
}
"""
- 访问属性:一旦创建了 User 实例,就可以像普通 Python 对象一样访问其属性。
这里打印了用户的 ID 和整个模型的字典表示形式。
注意,在最终输出的结果中,signup_ts 已经被正确解析为了 datetime 对象,而 tastes 中的键也已经被统一成了字符串类型,且所有值都被转换为了正整数。
- 验证错误
# continuing the above example...
from datetime import datetime
from pydantic import BaseModel, PositiveInt, ValidationError
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
external_data = {'id': 'not an int', 'tastes': {}}
try:
User(**external_data)
except ValidationError as e:
print(e.errors())
"""
[
{
'type': 'int_parsing',
'loc': ('id',),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not an int',
'url': 'https://errors.pydantic.dev/2/v/int_parsing',
},
{
'type': 'missing',
'loc': ('signup_ts',),
'msg': 'Field required',
'input': {'id': 'not an int', 'tastes': {}},
'url': 'https://errors.pydantic.dev/2/v/missing',
},
]
"""
如果验证失败,Pydantic 将引发错误并详细说明错误原因:
external_data 包含了两个可能导致验证失败的问题:
-
id
字段的值'not an int'
不是有效的整数。
id
字段要求的是一个整数值,但是给定的是一个无法解析成整数的字符串'not an int'
。
因此,Pydantic 抛出了一个类型为int_parsin
g 的错误,表明输入不是有效的整数,并且无法将字符串解析为整数。 -
signup_ts
字段没有提供,默认情况下它是必需的,除非你在模型配置中指定了它可以为 None 或者设置了默认值。
signup_ts
字段是必需的,但在external_data
中并没有提供相应的值。
由于它既不是一个有效的 `datetime`` 对象也不是 None(假设允许为空),所以 Pydantic 认为此字段缺失,从而抛出了 missing 类型的错误。
5.4 关于Pydantic.Field
-
什么是 Pydantic.Field?
Pydantic
的Field
是一个非常强大的工具,它允许开发者为数据模型中的字段添加额外的验证和元数据信息。
通过使用Field
,我们可以更精细地控制数据模型的行为,确保数据的有效性和一致性。 -
为什么需要 Pydantic.Field?
在定义数据模型时,有时仅靠类型注解并不能完全表达我们对字段的所有要求。
例如,我们可能希望限制字符串的最大长度、指定数值的取值范围或为字段提供描述性文本。
此时,Field
就派上了用场。
它可以用来设置字段的默认值、定义必填项与可选项、设定最大最小值等约束条件,以及为字段添加描述信息,这些都对于生成文档和支持API开发非常重要。 -
如何使用 Pydantic.Field?
要使用 Field,首先需要从 pydantic 中导入它。
然后,在定义模型类时,可以通过将 Field 函数作为字段类型的默认值来应用这些额外的信息和约束。
下面是一个简单的例子:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
description: str = Field(None, title="The description of the item", max_length=10)
price: float = Field(..., gt=0, description="The price must be greater than zero")
tax: float = None
在这个例子中:
- description
字段被标记为可选,并且设置了最大长度为 10。
- price
字段是必填项(用省略号 … 表示),并且指定了它的值必须大于零。
- 当创建 Item
实例时,如果提供了超出规定的 description
长度或者不符合 price 约束的数据,则会触发验证错误。
- Field 的参数说明?
Field
提供了一系列参数用于定制字段行为,包括但不限于以下几种:default
: 定义字段的默认值;如果未提供该值,默认为 None。alias
: 定义字段的别名,这在处理不符合 Python 变量命名规则的字段名时特别有用。title
: 为字段定义标题,这对生成文档很有帮助。description
: 提供字段的描述信息,同样有助于生成详细的 API 文档。min_length
和max_length
: 对于字符串类型字段,可以定义其最小和最大长度。gt, ge, lt, le
: 分别表示数值类型的字段应大于、大于等于、小于、小于等于某个特定值。- 其他如
regex
正则表达式验证、allow_mutation 是否允许修改字段值等高级选项也都可以通过 Field 来配置。 - 此外,
Field
还支持传递任意关键字参数到 JSON Schema 中,这意味着你可以根据需要灵活地扩展字段的功能。
例如,examples
参数可以直接影响生成的OpenAPI
文档中的示例值。
总之, Field
不仅仅是用来替代简单类型注解的一种方式,它更是 Pydantic
框架中实现复杂数据验证逻辑不可或缺的一部分。