FastAPI简介

FastAPI简介

  • 一、FastAPI简介
  • 二、FastAPI安装
    • [2.1 使用pip安装FastAPI](#2.1 使用pip安装FastAPI)
    • [2.2 FastAPI的demo](#2.2 FastAPI的demo)
    • [2.3 FastAPI的程序结构](#2.3 FastAPI的程序结构)
  • 三、装饰器请求方法
  • 四、用户请求
    • [4.1 路径参数](#4.1 路径参数)
      • [4.1.1 单个路径参数](#4.1.1 单个路径参数)
      • [4.1.2 多个路径参数](#4.1.2 多个路径参数)
      • [4.1.3 固定路径和路径参数的冲突](#4.1.3 固定路径和路径参数的冲突)
    • [4.2 查询参数](#4.2 查询参数)
    • [4.3 默认参数](#4.3 默认参数)
    • [4.4 可选参数](#4.4 可选参数)
  • 五、请求体
    • [5.1 关于请求体](#5.1 关于请求体)
    • [5.2 实现请求体](#5.2 实现请求体)
    • [5.3 关于Pydantic的BaseModel类](#5.3 关于Pydantic的BaseModel类)
    • [5.4 关于Pydantic.Field](#5.4 关于Pydantic.Field)

一、FastAPI简介

FastAPI源码
FastAPI官方中文文档
FastAPI官方文档

Pydantic官方文档

二、FastAPI安装

2.1 使用pip安装FastAPI

  • 安装FastAPI
python 复制代码
pip install fastapi
  • 安装FastAPI依赖库
    FastAPI的依赖库包括 Uvicorn、Pydantic 和 Starlette 库
python 复制代码
pip install uvicorn[standard]
pip install pydantic
pip install starlette

可以安装所有依赖库

python 复制代码
pip3 install fastapi[all]
  • 验证安装成功
python 复制代码
import fastapi
print(fastapi.__version__)

2.2 FastAPI的demo

创建main.py的文件:

python 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

在命令行运行如下指令:

python 复制代码
uvicorn main:app --reload

可以看到如下输出:

python 复制代码
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的程序结构

python 复制代码
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: 导包
python 复制代码
 import uvicorn
from fastapi import FastAPI
  • Step2: 创建一个 FastAPI 实例,这个实例是应用程序的核心,所有的路由和配置都将围绕它进行
python 复制代码
app = FastAPI()
  • Step3: 定义一个路径操作函数 root,它被装饰器 @app.get("/") 标记为根路径(/)的处理器
    这意味着当用户访问应用程序的根 URL 时,FastAPI 将调用此函数来处理请求。
    • 返回一个 JSON 响应,包含消息 "Hello World"。由于 FastAPI 支持异步定义,
    • 使用 async def 可以更好地利用其异步特性,从而提高性能。
python 复制代码
@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 请求。
python 复制代码
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()
    用于从服务器获取信息。
python 复制代码
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()
    用于向指定资源提交数据,常用于创建新资源。
python 复制代码
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()
    用于删除指定资源。
python 复制代码
if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)
  • @app.put()
    用于更新整个资源。
python 复制代码
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 类似,但不返回消息体,只用来获取响应头。
python 复制代码
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()
    用于对资源进行部分更新。
python 复制代码
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()
    用于追踪路径,通常由客户端发送请求,服务器将请求作为实体返回。
python 复制代码
from fastapi import FastAPI

app = FastAPI()

@app.trace("/trace")
async def trace_endpoint():
    # 追踪请求
    pass

# 如果发送 TRACE 请求到 http://127.0.0.1:8000/trace
# 运行结果:
# 返回客户端发送的原始请求
  • @app.options()
    用于描述通信选项,即客户端可以对资源执行哪些方法。
python 复制代码
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路径参数搜索用户登录记录以进行删除:

python 复制代码
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,防止产生冲突和混淆。

python 复制代码
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之后依次实现。

以下服务就是一个示例:

python 复制代码
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"
# }

理论上,我们期望的输出如下:

python 复制代码
# 如果发送 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()之前声明。

修改后的代码:

python 复制代码
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服务可以管理一系列由&分割的查询参数。

python 复制代码
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类型的空字典
python 复制代码
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类型,然后使用它来设置参数。

它应该使用方括号[]包装参数的假定数据类型,并且如果需要可以具有任何默认值。

python 复制代码
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
python 复制代码
from pydantic import BaseModel
  • Setp2:创建它的子类以利用所有属性和行为
  • Setp3:将模型定义为参数

5.3 关于Pydantic的BaseModel类

什么是 Pydantic 的 BaseModel?
Pydantic 是一个 Python 库,它帮助开发者确保他们的应用程序接收到的数据是正确的。

它通过使用 Python 的类型提示(type hints)来自动验证传入的数据是否符合预期的格式和类型。
BaseModelPydantic 提供的一个类,所有的数据模型都是从BaseModel类继承下来的。

为什么需要Pydantic 的 BaseModel?

如何使用Pydantic 的 BaseModel?

下面的代码示例展示了如何使用 Pydantic 来定义一个数据模型,并通过该模型对输入的数据进行验证和解析

python 复制代码
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},
}
  1. 定义 User 模型
python 复制代码
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 会根据这些注解自动验证传入的数据是否符合预期格式。
  1. 创建 User 实例并验证数据
python 复制代码
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 异常,并给出详细的错误信息。
    在这个例子中,所有的数据都被成功地验证和转换了。
  1. "解包"(unpacking)操作符 **
    user = User(**external_data) 是一种非常常见的 Python 语法,它使用了所谓的"解包"(unpacking)操作符**
    这种用法允许我们将一个字典中的键值对作为关键字参数传递给函数或类的构造方法
    在这个例子中, User 是由 Pydantic 定义的一个数据模型类,而 external_data 是一个包含键值对的字典,这些键名与 User 类定义的字段名称相匹配。
  • external_data 字典

    这是一个包含用户信息的字典,其中键对应于 User 模型类中的字段名,值则是相应的数据。

  • ** 解包操作符

    通过在字典前加上双星号 **,Python 会将字典里的每个键值对展开成单独的关键字参数,并传递给 User 类的构造函数。

    因此,上面的例子等价于如下代码:

python 复制代码
user = User(id=123, signup_ts='2019-06-01 12:22', tastes={'wine': 9, 'cheese': 7, 'cabbage': '1'})
  1. 输出结果
python 复制代码
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 中的键也已经被统一成了字符串类型,且所有值都被转换为了正整数。
  1. 验证错误
python 复制代码
 # 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_parsing 的错误,表明输入不是有效的整数,并且无法将字符串解析为整数

  • 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 函数作为字段类型的默认值来应用这些额外的信息和约束。

    下面是一个简单的例子:

python 复制代码
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 框架中实现复杂数据验证逻辑不可或缺的一部分。

相关推荐
Python涛哥2 小时前
Python高性能web框架-FastApi教程:(1)创建一个简单的FastApi
python·fastapi
_.Switch5 小时前
FastAPI 应用安全性:多层防护
开发语言·前端·数据库·python·网络安全·fastapi
weixin_307779136 小时前
AWS云计算问答式知识库系统的实现
python·flask·云计算·fastapi·aws
sagima_sdu2 天前
Python 程序与 Java 系统集成:通过 FastAPI 实现 HTTP 接口
java·python·fastapi
老大白菜2 天前
快速上手Serverless架构与FastAPI结合实现自动化移动应用后端
架构·serverless·fastapi
m_merlon3 天前
Fastapi教程:使用 aioredis 连接池执行Redis 的高效异步操作
redis·fastapi
m_merlon3 天前
Fastapi教程:使用aioredis异步访问redis
redis·fastapi
_.Switch4 天前
高效的 Web 应用认证与授权:基于 FastAPI 和 JWT 的实现与优化
开发语言·前端·python·fastapi·命令模式
_.Switch5 天前
Python Web 开发:FastAPI 依赖注入与中间件应用
开发语言·前端·python·中间件·django·fastapi