【FastAPI】简介

一、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" ,则表示安装成功。
在这里插入图片描述

2.3 FastAPI的启动方式

  1. uvicorn main:app --reload方式启动:

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 选项开启了热重载功能
  • 如果启用了 --reload-dir 参数,则只会监视该参数所指定的目录。
    • 例如,命令 uvicorn start:app --reload --reload-dir A 只会在 A 目录下的文件发生变更时触发重新加载
  • uvicorn 的 --reload 功能不仅限于简单的文件监控。
    • 它还支持通过设置环境变量 UvicornReloadDelay 来定义两次连续重载之间的延迟时间,以避免由于频繁保存导致的过度重启问题。
    • 而且,当使用 uvicorn.run() 函数从 Python 脚本内部启动服务器时,也可以传递类似的参数,如 reload=Truereload_dirs=['path/to/dir'],以便更细粒度地控制热重载行为。
      uvicorn 启动信息
  • unicorn启动信息:
    • 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 选项,因为这可能会导致不必要的资源消耗和稳定性问题。
    • 请注意,使用 --reload 选项启动热重载后,需要关注其监听的文件范围。
    • 请注意,Unicorn(针对 Ruby 应用)和 uvicorn(针对 Python 应用,特别是 FastAPI)是两个不同的项目。
      • uvicorn 是一个ASGI 服务器,针对 Python 应用,特别是 FastAPI。
      • Unicorn 是一款专为运行 Rack 应用程序设计的高性能 HTTP 服务器,服务于 Ruby 社区。
  1. 在代码文件中直接调用 uvicorn.run()
    另一种启动 FastAPI 应用的方法是在 Python 文件中直接调用 uvicorn.run() 函数·。
    这通常用于测试或简单的应用场景。
    可以在应用文件的底部添加如下代码:
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000, workers=5000)

这种方法允许你在同一个文件中定义和启动 FastAPI 应用,使得整个过程更加简洁。
需要注意的是,当你直接传递 app=appuvicorn.run() 函数时,实际上是在告诉 Uvicorn 使用当前脚本中已经创建的应用实例,这种方式避免了额外的导入步骤,从而可能导致更快的启动时间。

  1. 额外配置与选项
  • 无论是哪种启动方式,都可以对 Uvicorn 进行额外配置以适应不同的需求。
    例如,可以指定监听的地址和端口,或者设置工作进程的数量等。
    对于生产环境,建议移除 --reload 选项,因为它消耗更多资源并且更不稳定。
    此外,还可以使用其他 ASGI 服务器如 Hypercorn,后者支持 HTTP/2 和 Trio 协程库。

  • 对于多模态模型或其他复杂应用,如果遇到加载速度慢的问题,尝试调整启动方式可能会有所帮助。
    比如,将 uvicorn.run(main:app) 改为 uvicorn.run(app=app) 可能会减少启动时间。

  • 综上所述,FastAPI 提供了灵活且高效的启动机制,开发者可以根据具体的应用场景选择最适合自己的启动方式。
    无论是简单的本地调试还是复杂的生产部署,都有相应的解决方案来满足需求。

4.uvicorn.run()方法

uvicorn.run()方法提供了多个参数以配置 FastAPI 应用的启动行为。
以下是 uvicorn.run() 支持的主要参数列表,这些参数可以帮助开发者更好地控制应用的行为:

  • app:这是必填参数,指定了要运行的应用实例。
    • 它可以是 Python 模块路径字符串(如 "main:app"),
    • 也可以直接传递一个 FastAPI 实例对象给 uvicorn.run() 函数。
  • host:指定服务器绑定的主机名,默认为 "127.0.0.1",即仅允许本地访问。
    如果你想让服务器对外网开放,则需要将其设置为 "0.0.0.0" 或者具体的 IP 地址。
  • port:指定服务器监听的端口号,默认为 8000。你可以根据实际情况调整此值,例如在开发环境中使用 8080 端口。
  • reload:布尔类型,默认为 False
    当设为 True 时,在代码发生更改后会自动重启服务器,这对于开发阶段非常有用。
  • debug:与 reload 类似,默认为 False
    开启后会在遇到错误时提供更多的调试信息。
    需要注意的是,并非所有版本都支持该参数,某些情况下它可能与 reload 参数效果相同。
  • workers:指定工作进程的数量,默认为 1
    对于生产环境而言,可以根据硬件资源适当增加这个数值来提升并发处理能力。
    但是请注意,当设置了 --reload 选项时,此参数将被忽略。
  • root_path:用于安装在特定 URL 路径下的应用程序设置 ASGI “根路径”
    这在部署到子路径时特别有用。
  • limit_concurrency:限制最大并发连接数或任务数,在达到限制之前不会返回 HTTP 503 响应。
    这对于防止服务器过载很有帮助。
  • log_level:设置日志级别,默认为 info。可选值包括 critical, error, warning, info, 和 debug,以便于根据需要调整输出的日志详细程度。
  • reload_dirs:如果启用了 reload 功能,可以通过此参数指定哪些目录下的文件修改会触发自动重启。
    它接受一个包含目录路径字符串的列表作为输入。
  • udsUnix Domain Socket 文件路径。如果设置了此选项,则会忽略 host port 参数,适用于特定的操作系统环境。
  • ssl_keyfile, ssl_certfile:分别用于指定 SSL 私钥和证书文件的位置,使 HTTPS 成为可能。
    这对于确保数据传输的安全性至关重要。
  • backlog:定义了等待连接的最大队列长度,默认为 2048。这有助于管理客户端请求排队的情况。
  • headers:可以用来添加响应头字段,接收一个由元组组成的列表,每个元组包含两个元素,分别是头部名称和对应的值。

上述就是 uvicorn.run() 中较为常用的几个参数,它们能够满足大多数场景下的需求。
当然,随着 Uvicorn 的发展,可能会有更多新特性加入,建议定期查阅官方文档获取最新信息。
通过合理配置这些参数,可以有效地优化 FastAPI 应用的表现并适应不同的部署条件。

2.4 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)来自动验证传入的数据是否符合预期的格式和类型。
BaseModelPydantic 提供的一个类,所有的数据模型都是从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},
}
  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 会根据这些注解自动验证传入的数据是否符合预期格式。
  1. 创建 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 异常,并给出详细的错误信息。
    在这个例子中,所有的数据都被成功地验证和转换了。
  1. ”解包”(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'})
  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 中的键也已经被统一成了字符串类型,且所有值都被转换为了正整数。
  1. 验证错误
 # 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 函数作为字段类型的默认值来应用这些额外的信息和约束。
    下面是一个简单的例子:

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

5.5. 处理Form参数

  • 定义一个接口
from fastapi import FastAPI, Form, HTTPException
from pydantic import BaseModel

app = FastAPI()

# 定义问答对
QA_para = {
    "你好": "你好呀,很高兴为你服务",
    "你是谁?": "我是大模型"
}

# 定义响应体的数据模型
class Answer(BaseModel):
    answer: str

@app.post("/QA/", response_model=Answer)
async def get_answer(question: str = Form(...)):
    # 尝试从 QA_para 中获取答案,如果找不到则返回默认信息
    answer = QA_para.get(question, "对不起,我不知道答案是什么。")
    
    # 返回一个 JSON 格式的回答,并使用 Answer 模型进行验证
    return Answer(answer=answer)
    
  • 'headers = { 'accept': 'application/json', 'Content-Type': 'application/json' }'发起请求
import requests
import json
import pprint

url = 'http://127.0.0.1:8000/QA/'
data = {'question': '你好'}
headers = {'Content-Type': 'application/json', 
           'accept': 'application/json'
    }

response = requests.post(url, headers=headers, data=json.dumps(data), stream = True)
pprint.pprint(response.json())
    

运行结果:

{'detail': [{'input': None,
             'loc': ['body', 'question'],
             'msg': 'Field required',
             'type': 'missing'}]}
  • 'accept: application/json':这个头部告诉服务器客户端期望接收的内容类型。
    客户端能够告知服务器其期望接收到的数据格式(Accept: application/json),从而保证服务器返回的数据易于客户端解析;
  • 'Content-Type: application/x-www-form-urlencoded':此头部定义了发送到服务器的数据的媒体类型。
    它将表单数据转换成键值对的形式,并且每个键值对之间用 & 分隔,这与 HTML 表单提交的方式一致。
    这种方式适合于简单的表单提交场景,特别是那些只包含文本字段的情况。
  • 'headers = { 'accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }'发起请求
import requests

url = 'http://127.0.0.1:8000/QA/'
headers = {
    'accept': 'application/json',
    'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
    'question': '你好'  # URL编码后的值 '%E4%BD%A0%E5%A5%BD' 对应的是中文 '你好'
}

response = requests.post(url, headers=headers, data=data)

print(response.json())       # 如果响应内容是JSON格式,则打印解析后的JSON对象
    

运行结果:

{'answer': '你好呀,很高兴为你服务'} 
  • 'headers = { 'accept': 'application/x-www-form-urlencoded'', 'Content-Type': 'application/x-www-form-urlencoded' }'发起请求
import requests

url = 'http://127.0.0.1:8000/QA/'
headers = {
    'accept': 'application/x-www-form-urlencoded',
    'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
    'question': '你好'  # URL编码后的值 '%E4%BD%A0%E5%A5%BD' 对应的是中文 '你好'
}

response = requests.post(url, headers=headers, data=data)

print(response.json())       # 如果响应内容是JSON格式,则打印解析后的JSON对象
    
    

运行结果:

{'answer': '你好呀,很高兴为你服务'} 

'accept': 'application/x-www-form-urlencoded''意味着告诉服务器客户端期望接收的内容类型。
但是服务器返回的结果仍然是json类型。
原因如下:

  • 自动序列化为 JSON
    Answer 是一个 Pydantic 模型,它定义了一个具有 answer 字段的对象。
    当返回 Answer(answer=answer) 时,FastAPI 知道这是一个需要被序列化为 JSON 的对象,并且根据 response_model 的定义来确保输出格式正确无误。
  • 内置支持与优化
    FastAPI 默认情况下会使用 jsonable_encoder 来处理各种复杂的数据类型(如日期时间、UUID 等),以确保它们可以被正确地序列化为 JSON 格式。

即使没有显式地指定 response_model,只要返回值是 Python 的基本数据类型(如字典或列表)或者是 Pydantic 模型,FastAPI 也会默认将其视为 JSON 兼容的数据,并进行相应的转换。

5.6. FastAPI处理Cookie

  • 设置Cookies
    FastAPI中设置Cookies可以通过两种主要方式实现:

    • 一种是在路径操作函数中声明Response类型的参数,并使用这个临时响应对象来设置Cookie
    • 另一种是直接创建并返回一个响应对象(例如JSONResponse),并在其上调用set_cookie方法。
  • 方法一:通过Response参数设置Cookie
    在响应中添加Cookie时,可以在路径操作函数中定义一个名为response的参数,其类型为fastapi.Response
    这允许您在这个临时响应对象中设置Cookie,同时还能保持原有响应数据结构不变。
    例如:

from fastapi import FastAPI, HTTPException, Response
from pydantic import BaseModel

app = FastAPI()

# 定义问答对
QA_para = {
    "Hello": "你好呀,很高兴为你服务",
    "你是谁?": "我是大模型"
}

# 定义请求体的数据模型
class Question(BaseModel):
    question: str

# 定义响应体的数据模型
class Answer(BaseModel):
    answer: str

@app.post("/QA/", response_model=Answer)
async def get_answer(question_data: Question, response: Response):
    # 尝试从 QA_para 中获取答案,如果找不到则返回默认信息
    answer = QA_para.get(question_data.question, "对不起,我不知道答案是什么。")
    
    # 设置一个名为 'question' 的 cookie,其值为用户的问题,有效时间为5分钟
    response.set_cookie(
        key="question", 
        value=question_data.question, 
        max_age=300, 
        path="/",
        secure=True,  # 如果你的应用只通过HTTPS提供,那么设置此选项
        httponly=True,  # 防止JavaScript访问这个cookie,提高安全性
        samesite='lax'  # 或者'strict'/'none',取决于你的跨站请求策略
    )

    # 返回一个 JSON 格式的回答,并使用 Answer 模型进行验证
    return Answer(answer=answer)

调用接口

import requests
import json
import pprint

url = 'http://127.0.0.1:8000/QA/'
data = {'question': 'Hello'}
headers = {'Content-Type': 'application/json', 
           'accept': 'application/json'
    }

response = requests.post(url, headers=headers, data=json.dumps(data), stream = True)
print(f"\n > > > > > >  response: \n{response}")
print(f"\n > > > > > >  response.cookies: \n{response.cookies}")
    
for name, value in response.cookies.items():
    print(f"\n > > > > > >  {name}: {value}")

运行结果:

 > > > > > >  response: 
<Response [200]>

 > > > > > >  response.cookies: 
<RequestsCookieJar[<Cookie question=Hello for 127.0.0.1/>]>

 > > > > > >  question: Hello

set_cookie 函数或方法在不同的Web框架中可能具有略微不同的参数列表,但通常它们都遵循HTTP协议规定的标准。
在 FastAPI 中,set_cookie 方法是通过 Response 对象提供的,用于向客户端发送 HTTP 响应时设置 Cookie。
FastAPI 使用 Starlette 库来处理底层的 HTTP 请求和响应,因此 set_cookie 的实现细节实际上是来自 Starlette 。

以下是 set_cookie 在 FastAPI 中可以使用的参数列表及其描述:

  • key (必需): Cookie 的键名,类型为字符串 (str)。这是用来标识存储在用户浏览器中的特定信息片段的关键字。
  • value (必需): Cookie 的值,类型为字符串 (str)。这是与指定键关联的数据,通常会包含一些识别信息或者状态标志
  • max_age (可选): Cookie 的生命周期,以秒为单位,类型为整数 (int)。如果设为负数或 0,则表示立即丢弃该 Cookie。此参数决定了 Cookie 在客户端保存的时间长度。
  • expires (可选): Cookie 的过期时间,类型为整数 (int) 或者 Python 的 datetime 对象。它定义了具体的日期和时间点之后 Cookie 将不再有效。注意,max_ageexpires 可以选择性地使用其中一个;如果两者都提供了,max_age 优先级更高。
  • path (可选): Cookie 应用的路径,默认为根路径 (/),类型为字符串 (str)。这指定了哪些 URL 路径下的请求能够携带这个 Cookie。
  • domain (可选): Cookie 有效的域名,类型为字符串 (str)。它可以确保只有来自特定域名的请求才会附带此 Cookie,有助于跨子域共享 Cookie。
  • secure (可选): 类型为布尔值 (bool)。当设置为 True 时,意味着只有在 HTTPS 协议下才会发送该 Cookie 给服务器端,增强了安全性。
  • httponly (可选): 类型为布尔值 (bool)。若设为 True,则 JavaScript 无法通过 Document.cookieXMLHttpRequest 或其他请求 API 访问该 Cookie,从而降低了 XSS 攻击的风险。
  • samesite (可选): 类型为字符串 (str),取值可以是 "lax"(默认)、"strict""none"。此参数用于指定 Cookie 的相同站点策略,影响浏览器如何处理跨站请求中的 Cookie,特别是为了防止 CSRF 攻击而设计。

这些参数可以帮助开发者更精细地控制 Cookie 的行为,确保它们按照预期的方式工作,并且考虑到安全性和隐私保护的需求。例如,在构建需要用户认证的应用程序时,合理配置这些参数对于维持会话的安全至关重要。

此外,值得注意的是,FastAPI 允许你在路径操作函数中声明一个 Response 参数,并通过它调用 set_cookie 方法来设置 Cookie。你也可以在依赖项中声明 Response 参数并在其中设置 Cookie、headers 等。这样做的好处是可以保持代码的整洁,同时利用 FastAPI 的依赖注入系统来管理复杂的逻辑。

六、相关链接

【FastAPI】日志
【FastAPI】中间件
【FastAPI】简介
【FastAPI】BaseModel类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值