FastAPI
简介
FastAPI,一个用于构建API的现代、快速(高性能)的web框架。
FsastAPI是建立在Starlette和Pydantic的基础上的。
- Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的苦。
- Starlette是一种轻量级的ASGI(Asynchronous Server Gateway Interface)框架/工具包,是构建高性能
Asyncio
服务的理性选择。
依赖:Python3.6及更高的版本。- 我选择使用FastAPI的理由是,这个框架还支持
WebSocket
,而Flask和Django不支持
- 我选择使用FastAPI的理由是,这个框架还支持
预备知识点
HTTP协议
在这小节,我们来彻底弄清楚一下HTTP协议,带着下面这四个问题来看:
- 什么是请求头,请求体、响应头,响应体
- URL地址包括什么
- GET请求和POST请求到底是什么,有什么区别
- Content-Type是什么
把这些彻底弄清楚之后,学习其他语言的Web框架就会容易很多
简介
虽然上学时期都听腻了HTTP协议是
Hyper Text Transfer Protocol
(超文本传输协议)的缩写,是用于万维网(WWW:World Wide Web)服务器与本地浏览器之间传输超文本的传输协议。HTTP是一个属于应用层的面向对象的协议(为什么是应用层?建议回顾一下OSI七层模型或者TCP/IP四层模型)。TCP/IP四层模型
层级 | 功能 | 示例协议 |
---|---|---|
应用层 | 提供应用程序的接口,定义数据的语义。 | HTTP, FTP, SMTP, DNS |
传输层 | 提供端到端通信的可靠性和数据流管理。 | TCP, UDP |
网络层 | 负责路由和逻辑地址(IP 地址)的管理。 | IP, ICMP |
链路层 | 处理数据帧的传输及硬件地址(MAC 地址)。 | Ethernet, Wi-Fi |
- OSI七层模型
层级 | 功能 | 示例协议 |
---|---|---|
应用层 | 提供应用程序接口,支持网络应用程序的通信。 | HTTP, FTP, SMTP, DNS, Telnet |
表示层 | 负责数据的格式化、加密、解密及压缩处理。 | JPEG, GIF, SSL/TLS, ASCII |
会话层 | 管理应用程序之间的会话,包括建立、管理和终止。 | NetBIOS, PPTP, RPC |
传输层 | 提供端到端的通信,负责可靠性和数据流控制。 | TCP, UDP |
网络层 | 负责路由和逻辑地址管理,实现跨网络通信。 | IP, ICMP, ARP, RIP, OSPF |
数据链路层 | 处理数据帧的传输及物理地址(MAC地址)。 | Ethernet, PPP, Wi-Fi, Frame Relay |
物理层 | 负责比特流的传输,定义硬件电气及机械特性。 | RS-232, DSL, ISDN, IEEE 802.11 |
- 由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用和发展,得到不断的完善和扩展,HTTP协议工作于客户端-服务端架构上。浏览器作为HTTP客户端,通过URL向HTTP服务端即Web服务器发送所有请求。Web服务器根据接收到的请求,向客户端发送响应信息。
HTTP协议特性
基于TCP/IP协议
什么是基于TCP/IP
HTTP
依赖于TCP
协议 来传输数据:TCP
是传输层协议,提供可靠的、面向连接的通信。HTTP
的每个请求和响应(例如GET
请求)都通过TCP
连接发送。TCP
使用IP
协议(网络层)来确定数据的目的地:IP
负责将数据包路由到目标主机。TCP
和IP
协作完成数据的分发和传输。IP
数据包通过 链路层协议(如Ethernet
或Wi-Fi
)在实际的物理网络中传输。
具体的工作流程
- 应用层(HTTP):用户在浏览器中输入网址(如www.baidu.com),浏览器通过HTTP发起请求。HTTP构建请求数据,调用传输层。
- 传输层(TCP):HTTP请求糖果TCP建立一个连接(例如TCP三次握手)。TCP将数据分割为小的“段”,保证这些段可靠的传输。
- 网络层(IP):TCP将数据包封装为IP数据包,通过IP协议将数据包通过路由器发送到目标主机。
- 链路层(Ethernet):IP数据包通过链路层协议(如Ethernet或Wi-Fi)发送到目标主机。
- 返回响应:目标服务器接受请求并通过相同的层次返回响应,直至用户浏览器完成解析。
总结
- TCP 提供可靠的数据传输。
- IP 负责数据路由。
- HTTP 构建在这些基础之上,专注于实现 Web 通信的语义(如请求、响应、状态码等)。
基于请求-响应模式
- HTTP协议规定,请求从客户端发出,最后服务器响应该请求并返回。换句话说,肯定是先从客户端开始建立通信的,服务端在没有接收到请求之前不会发送响应数据。
- 请求和响应是HTTP协议的核心组成部分。
- 请求(Request):客户端发送的请求数据,包括请求方法、URL、请求头、请求体等。
- 响应(Response):服务器返回的响应数据,包括状态码、响应头、响应体等。
无状态保存
在HTTP协议中,每次发送一个新的请求时,服务器都会杀横撑一个对应的新响应。这种请求-响应模式的设计中,协议不会记录之前的任何请求或响应内容。设计成无状态的主要目的是简化协议的实现,减少服务器的资源占用,从而能够快速处理大量事务。这种特性极大地提高了协议的可伸缩性,使其能够支持高并发和复杂的互联网应用场景。
由于HTTP自身无法保存状态,因此需要借助其他技术(如Cookie和Session)来实现状态管理和数据持久化,从而满足实际应用中的需求。
- 补充说明:
- Cookie是一种由服务器生成并存储在客户端浏览器中的小型数据,用于保存用户状态信息。例如登录会话或偏好设置,当用户再次访问同一网站时,浏览器会携带相应的Cookie信息,从而使浏览器能够识别用户身份或恢复之前的会话状态。
- Session是一种由服务器端维护的状态管理机制,通过在服务器端为每个用户创建唯一的会话标识符(Session ID),来保存用户的状态信息。与Cookie不同,Session ID通常存储在服务器端,而不是客户端。
短连接
- HTTP1.0默认使用的是短连接。浏览器和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
- 但是这种方式存在性能为问题,因为每次请求都需要进行三次握手、数据传输以及连接断开,这样会消耗额外的时间和资源。
- HTTP/1.1引入了长连接(即复用TCP连接)。在默认情况下,HTTP/1.1会保持连接打开,允许在同一TCP连接上发送多个请求和响应。
- Connection: keep-alive是HTTP/1.1中的一个请求/响应头,表示链接不关闭。但在实际使用中,HTTP/1.1的keep-alive连接默认会保持连接打开,即使没有显示指定keep-alive
- 长连接的优势在于减少了重复建立和关闭连接的开销,提升了性能。
- SSE和WebSocket
- 上面说的
长连接
需要和SSE和WebSocket的持久连接
区分一下。 - SSE是基于HTTP协议的单向通信技术,通常用于服务器主动推送实时数据到客户端(如新闻推送、实时通知等)。SSE是一种持久的HTTP连接,服务器可以不断的向客户端发送数据。
- WebSocket是一种更为灵活的双向通信协议(所以ws连接的url是
ws://
,而http连接的url是http://
),客户端和服务器通过WebSocket建立持久连接后,双方可以随时互相发送数据,这种连接比SSE延迟更低,且实时性更强。WebSocket是独立于HTTP的,但是最初的握手是通过HTTP协议完成的。
- 上面说的
HTTP请求协议与响应协议
- HTTP协议包含有浏览器发送数据到服务器需要遵循的请求协议和服务器发送数据到浏览器需要遵循的请求协议。
- 用户HTTP协议交互的信息被称为HTTP报文。请求端(客户端)的HTTP报文被称为请求报文,响应端(服务器)的HTTP报文被称为响应报文。
- HTTP报文本身是有多行数据构成的文字文本,每行数据都是以
\r\n
结尾。
- 一个完整的URL包括:协议、ip、端口、路径、参数
- 例如:
http://www.baidu.com:80/s?wd=python
,其中http
是协议,www.baidu.com
是ip,443
是端口,/s
是路径,wd=python
是参数。 - 例如:
ws://localhost:8000/ws
,其中ws
是协议,localhost
是ip,8000
是端口,/ws
是路径。
- 例如:
- GET和POST请求的区别
- GET请求提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连。而POST请求则是把提交的数据放在HTTP请求报文的请求体中。
- GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST请求则没有此限制。
测试HTTP协议格式
- 在这里,我们通过手写一个原生的
socket
程序来测试HTTP
协议格式。常见的 Web 框架其实都是基于socket
实现的,只不过框架已经封装了大部分细节,我们只需按照框架规则编写代码即可。
1 | import socket |
- 当我们用浏览器访问
http://127.0.0.1:9571/
时,浏览器会发送一个GET请求,此时打印的结果是1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22接收到客户端发送的请求信息:b'GET / HTTP/1.1\r\nHost: localhost:9571\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nsec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"\r\nsec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: "Windows"\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nSec-Fetch-Site: none\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\nSec-Fetch-Dest: document\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nAccept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en;q=0.7\r\nCookie: Hm_lvt_d54c3018e5f620d2cda0f5ca52ff80cb=1732428640,1734018506,1734100966,1734138834; HMACCOUNT=6EC0BD1C42ED9962; Hm_lpvt_d54c3018e5f620d2cda0f5ca52ff80cb=1734152185\r\n\r\n'
====================
GET / HTTP/1.1
Host: localhost:9571
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en;q=0.7
Cookie: Hm_lvt_d54c3018e5f620d2cda0f5ca52ff80cb=1732428640,1734018506,1734100966,1734138834; HMACCOUNT=6EC0BD1C42ED9962; Hm_lpvt_d54c3018e5f620d2cda0f5ca52ff80cb=1734152185
==================== - 分析结果:
- 请求首行:如 GET / HTTP/1.1,表示请求方法是 GET,路径是 /,协议版本是 HTTP/1.1。
- 请求头:每行以 key: value 形式存在,例如 Host: localhost:9571 表示目标主机和端口。
- 空行:空行标识请求头结束。
- 请求体:如果有请求体,空行后会跟随内容,此处因无请求体而为空。
在浏览器中没有显示任何内容的原因是:服务器未返回任何数据。即便返回数据,也必须严格按照 HTTP 协议格式。
那我们现在来修改一下代码,使其有响应数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import socket
sock = socket.socket()
sock.bind(("127.0.0.1", 9571))
sock.listen(5)
while True:
conn, addr = sock.accept()
data = conn.recv(1024)
decode_data = data.decode('utf-8')
print(f"接收到客户端发送的请求信息:{data}")
lines = decode_data.split('\r\n')
print('=' * 20)
for line in lines:
print(line)
print('=' * 20)
conn.send(b'HTTP/1.1 200 OK\r\n\r\nHello World')- 浏览器收到响应后,正常显示 Hello World,因为我们遵循了 HTTP 协议格式,返回了以下内容:
- 响应首行:HTTP/1.1 200 OK,表示状态码为 200,状态为 OK。
- 空行:标识响应头结束。
- 响应体:Hello World。
测试Content-Type
- 在响应体中我们可以返回各种数据,但是浏览器怎么知道返回的数据是什么类型的呢?这就需要用到
Content-Type
。 Content-Type
是HTTP协议中用于描述响应体数据类型的头部字段。例如:Content-Type: text/html
,表示响应体是HTML格式的数据。Content-Type: application/json
,表示响应体是JSON格式的数据。Content-Type: text/plain
,表示响应体是纯文本格式的数据。Content-Type: image/jpeg
,表示响应体是JPEG格式的图片。Content-Type: application/octet-stream
,表示响应体是二进制流数据。
请求时的Content-Type
- 以下是用 Apifox 工具分别发送不同类型请求时的表现:
- GET 请求
- 示例 URL:http://localhost:9571?name=kyle&age=21
- 参数放在请求首行:
1
2
3
4
5
6
7
8
9
10
11接收到客户端发送的请求信息:b'GET /?name=kyle&age=21 HTTP/1.1\r\nUser-Agent: Apifox/1.0.0 (https://apifox.com)\r\nAccept: */*\r\nHost: localhost:9571\r\nAccept-Encoding: gzip, deflate, br\r\nConnection: keep-alive\r\n\r\n'
====================
GET /?name=kyle&age=21 HTTP/1.1
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: localhost:9571
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
====================
- POST 请求(application/x-www-form-urlencoded)
- 参数以 key=value 格式放在请求体中:
1
2
3
4
5
6
7
8
9
10
11
12
13接收到客户端发送的请求信息:b'POST / HTTP/1.1\r\nUser-Agent: Apifox/1.0.0 (https://apifox.com)\r\nAccept: */*\r\nHost: localhost:9571\r\nAccept-Encoding: gzip, deflate, br\r\nConnection: keep-alive\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 16\r\n\r\nname=kyle&age=21'
====================
POST / HTTP/1.1
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: localhost:9571
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 16
name=kyle&age=21
====================
- 参数以 key=value 格式放在请求体中:
- POST 请求(application/json)
- 参数为 JSON 格式:
1
2
3
4
5
6
7
8
9
10
11
12
13接收到客户端发送的请求信息:b'POST / HTTP/1.1\r\nUser-Agent: Apifox/1.0.0 (https://apifox.com)\r\nContent-Type: application/json\r\nAccept: */*\r\nHost: localhost:9571\r\nAccept-Encoding: gzip, deflate, br\r\nConnection: keep-alive\r\nContent-Length: 27\r\n\r\n{"name": "kyle", "age": 21}'
====================
POST / HTTP/1.1
User-Agent: Apifox/1.0.0 (https://apifox.com)
Content-Type: application/json
Accept: */*
Host: localhost:9571
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 27
{"name": "kyle", "age": 21}
====================
- 参数为 JSON 格式:
- GET 请求
响应时的Content-Type
- 如果服务器返回的 Content-Type 不正确,浏览器可能无法正确解析响应体。例如:
- 错误的 Content-Type,以下代码返回了 HTML 内容,但设置了 Content-Type: text/plain:浏览器中惠当做纯文本显示
1
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n<h1>Hello World</h1>')
1
<h1>Hello World</h1>
- 正确的
Content-Type
,将Content-Type
改为text/html
:浏览器中会正确显示 HTML 内容:1
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Hello World</h1>')
1
Hello World
- 错误的 Content-Type,以下代码返回了 HTML 内容,但设置了 Content-Type: text/plain:
快速开始
- 安装fastapi和uvicorn库
1
2pip install fastapi
pip install uvicorn - 创建一个main.py文件,并编写以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13from fastapi import FastAPI
import uvicorn
app = FastAPI()
def first_api():
return {}
if __name__ == '__main__':
uvicorn.run('main:app', host='127.0.0.1', port=9421, reload=True) - fastapi还内置了Swagger UI,可以通过http://127.0.0.1:9421/docs 访问。
路径操作
- fastapi支持各种请求方式,但是实际使用中,建议只使用get和post方式。
1
2
3
4
5
6
7
8
路径操作做装饰器参数
- 这部分和Java里的Swagger注解类似,可以用来描述API的元数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32from typing import Optional
from fastapi import FastAPI
import uvicorn
from pydantic import BaseModel
from starlette import status
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
)
def first_api():
return {}
if __name__ == '__main__':
uvicorn.run('main:app', host='127.0.0.1', port=9421, reload=True) - 效果如下:
路由分发include_router
- 在实际开发中,我们可能需要将不同的功能模块拆分到不同的文件中,此时就需要用到路由分发。
- 路由分发需要使用
include_router
方法,该方法接收一个APIRouter
对象,并将其添加到FastAPI应用中。- 定义订单相关的路由
1
2
3
4
5
6
7
8
9
10from fastapi import APIRouter
order_route = APIRouter()
def get_order_info():
return {
'order_id': 123
}
定义用户相关的路由
1
2
3
4
5
6
7
8
9
10
11from fastapi import APIRouter
user_route = APIRouter()
def get_user_info():
return {
'user_id': 123,
'username': 'Lucy'
}- 在main.py中引入路由分发,其中
prefix
参数用于指定路由的前缀,tags
参数用于指定路由的标签,将在Swagger UI中显示。1
2
3
4
5
6
7
8
9
10
11
12from fastapi import FastAPI
import uvicorn
from order.order_service import order_route
from user.user_service import user_route
app = FastAPI()
app.include_router(user_route, prefix='/user', tags=['用户相关接口'])
app.include_router(order_route, prefix='/order', tags=['订单相关接口'])
if __name__ == '__main__':
uvicorn.run('main:app', host='127.0.0.1', port=9421, reload=True) 其中目录结构如图
效果如下:
- 定义订单相关的路由
请求与响应
路径参数
基本使用
- 路径参数是指在URL中定义的参数,例如:
/get_user_info/{user_id}
,其中{user_id}
就是路径参数,同时路径参数会作为函数的参数传入。1
2
3
4
5
6
7
8
9
10
11from fastapi import APIRouter
user_route = APIRouter()
def get_user_info(user_id):
return {
'user_id': user_id,
'username': 'Lucy'
} - 当我们访问
http://127.0.0.1:9421/get_user_info/123
时,会返回{'user_id': 123, 'username': 'Lucy'}
。
类型注解
- 在刚刚的代码中,路径参数user_id没有指定类型注解,所以甚至可以传入字符串作为参数。当我们访问
http://127.0.0.1:9421/user/get_user_info/qwe
时,会返回{'user_id': 'qwe', 'username': 'Lucy'}
。 - 在实际开发中,我们大部分情况想回希望路径参数的类型是固定的,所以需要指定类型注解,例如:
1
2
3
4
5
6
7
8
9
10
11from fastapi import APIRouter
user_route = APIRouter()
def get_user_info(user_id: int):
return {
'user_id': user_id,
'username': 'Lucy'
} - 此时当我们访问
http://127.0.0.1:9421/user/get_user_info/qwe
时,就会报错1
2
3
4
5
6
7
8
9
10
11
12
13{
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"user_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "qwe"
}
]
}
查询参数(Query Parameters)
- 查询参数是指在URL中定义的参数,例如:
/get_user_info?user_id=123
,其中user_id
就是查询参数,同时查询参数会作为函数的参数传入。1
2
3
4
5
6
7
8
9
10
11from fastapi import APIRouter
user_route = APIRouter()
def get_user_info(user_id: int):
return {
'user_id': user_id,
'username': 'Lucy'
} 当我们访问
http://127.0.0.1:9421/user/get_user_info?user_id=123
时,会返回{'user_id': 123, 'username': 'Lucy'}
。自python3.5开始,PEP484引入了类型注解(Type Hints),用于在函数参数和返回值中指定类型。作用主要有:
- 提高代码的可读性和可维护性。
- 在运行时进行类型检查,避免运行时错误。
- 在IDE中提供代码补全和错误检查功能。
- 在静态类型检查工具中进行类型检查,避免运行时错误。
- 在代码文档中提供类型信息,方便其他开发者理解代码。
Union
是当参数类型不确定时,可以使用Union
类型注解,例如:1
2
3
4
5
6
7from typing import Union
def get_user_info(user_id: Union[int, str]):
return {
'user_id': user_id,
'username': 'Lucy'
}Optional
是Union
的一个简化,表示参数可以是None
或指定类型,不需要显示的指定None
。Optional[int]
相当于Union[int, None]
。1
2
3
4
5
6
7from typing import Optional
def get_user_info(user_id: Optional[int] = None):
return {
'user_id': user_id,
'username': 'Lucy'
}
请求体数据
- 当你需要将数据从客户端发送给服务器时,就需要使用请求体数据。
- FastAPI基于
pydantic
库,而pydantic
库主要用来做类型强制检查,对于API服务,支持类型检查非常有用,会让服务更加健壮,也会加快开发速度,因为不用随时随地都要检查一下数据类型是否符合业务需求。
1 | from fastapi import APIRouter |
- POST方式请求url:
http://127.0.0.1:9421/user/get_user_info
,请求体数据:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29{
"user_list": [
{
"name": "Alice",
"age": 30,
"birth": "1994-05-20",
"friends": [
1,
2
],
"addr": {
"city": "Alicetown",
"street": "Tea Party Lane"
}
},
{
"name": "David",
"age": 40,
"birth": "1984-01-01",
"friends": [
1
],
"addr": {
"city": "Davidsville",
"street": "Invisible Cloak Road"
}
}
]
} - 返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27[
{
"name": "Alice",
"age": 30,
"birth": "1994-05-20",
"friends": [
1,
2
],
"addr": {
"city": "Alicetown",
"street": "Tea Party Lane"
}
},
{
"name": "David",
"age": 40,
"birth": "1984-01-01",
"friends": [
1
],
"addr": {
"city": "Davidsville",
"street": "Invisible Cloak Road"
}
}
]
Field
- 在
Pydantic
中,Field
方法还可以指定额外的验证规则和默认值,例如1
2
3
4
5
6class User(BaseModel):
name: str = None,
age: int = Field(default=0, gt=0, lt=100),
birth: Optional[date] = None,
friends: List[int] = None,
addr: Addr = None - 关于
Field
中的参数:default=0
: 设置了age
字段的默认值为 0。如果创建User
实例时没有提供age
的值,则它将被设置为 0。lt=100
:lt
表示less than
(小于),这意味着age
的值必须小于 100。gt=0
:gt
表示greater than
(大于),这意味着age
的值必须大于 0。
- 当我们输出一个不符合范围的age时,就会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32{
"detail": [
{
"type": "less_than",
"loc": [
"body",
"user_list",
0,
"age"
],
"msg": "Input should be less than 100",
"input": 200,
"ctx": {
"lt": 100
}
},
{
"type": "greater_than",
"loc": [
"body",
"user_list",
1,
"age"
],
"msg": "Input should be greater than 0",
"input": -1,
"ctx": {
"gt": 0
}
}
]
}
自定义验证器
Pydantic
还支持自定义验证器来处理特定字段或者整个模型的数据验证,允许执行更复杂的逻辑检查,而不仅仅是简单的类型检查和范围限制。Pydantic
提供了@validator
装饰器和@root_validator
装饰器来处理自定义验证逻辑。1
2
3
4
5
6
7
8
9
10
11
12
13class User(BaseModel):
name: str = None
age: int = Field(default=0, lt=100, gt=0)
birth: Optional[date] = None
friends: List[int] = None
addr: Addr = None
def check_name(cls, v):
if v is not None and v: # 检查是否为非空字符串
if not v[0].isupper(): # 正确的方法名
raise ValueError('姓名必须是大写字母开头')
return v- 当我们输出一个不符合条件的name时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"user_list": [
{
"name": "alice",
"age": 80,
"birth": "1994-05-20",
"friends": [
1,
2
],
"addr": {
"city": "Alicetown",
"street": "Tea Party Lane"
}
}
]
} - 结果会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
"detail": [
{
"type": "value_error",
"loc": [
"body",
"user_list",
0,
"name"
],
"msg": "Value error, 姓名必须是大写字母开头",
"input": "alice",
"ctx": {
"error": {}
}
}
]
}