FastAPI
pydantic
Pydantic 是一个 Python 库,用于数据解析和验证。它基于 Python 类型注解,提供了一个简单的方法来定义数据模型。这些数据模型可以用于读取和验证复杂的数据类型,如 JSON、环境变量或表单字段,确保它们符合预期的格式,并在数据不正确时提供详细的错误信息。
在 FastAPI 中,Pydantic 被广泛使用来定义请求和响应的数据模型,因为它可以自动处理数据的序列化和反序列化,以及数据验证。
https://docs.pydantic.dev/latest/concepts/dataclasses/
最佳实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API 服务,它基于标准 Python 类型提示。以下是使用 FastAPI 时的一些最佳实践:
-
使用 Pydantic 模型:
- 利用 Pydantic 创建数据模型来定义请求和响应的数据结构。
- 通过模型验证确保数据的有效性。
-
异步编程:
- 当需要进行 I/O 操作如访问数据库、文件系统或进行网络调用时,使用异步方法和函数。
- 使用
async和await关键字来编写异步代码。
-
依赖注入:
- 使用 FastAPI 的依赖注入系统来提供共享逻辑,如数据库会话管理、权限验证、配置设置等。
-
路由和 API 结构:
- 组织代码结构,将路由分离到不同的模块中,使用 APIRouter 进行子路由的管理。
- 使用路径操作装饰器(如
@app.get(),@app.post()等)来清晰地定义端点和 HTTP 方法。
-
中间件使用:
- 根据需要合理地使用中间件,例如处理 CORS、认证、日志记录等。
-
安全性:
- 利用 FastAPI 的安全和认证特性,如 OAuth2 密码流、Bearer 令牌等。
- 使用 HTTPS 和其他安全最佳实践来保护你的 API。
-
错误处理:
- 使用异常处理器来捕获并处理预期内的错误和异常。
-
性能优化:
- 使用 Starlette 的静态文件服务来提供静态内容。
- 考虑使用缓存来提升性能。
-
API 文档:
- 利用 FastAPI 自动生成的 Swagger UI 和 ReDoc 来创建和维护 API 文档。
-
测试:
- 编写测试用例并使用 Pytest 等工具来进行自动化测试。
- 利用 FastAPI 测试客户端来测试 API 端点。
-
环境管理:
- 使用环境变量来管理配置,例如数据库链接。
- 可以使用
python-dotenv或pydantic的BaseSettings类来加载和管理环境变量。
-
数据库操作:
- 使用 ORM 工具(如 SQLAlchemy 或 Tortoise ORM)来抽象化数据库操作。
- 确保在请求结束时关闭数据库会话。
-
项目结构:
- 按照功能模块组织代码,遵循一致的项目结构。
- 将业务逻辑、数据库模型、依赖项和路由分离到不同的文件或模块中。
-
部署:
- 使用 Uvicorn 或 Gunicorn 作为 ASGI 服务器来运行你的 FastAPI 应用。
- 考虑使用容器化技术(如 Docker)来简化部署和缩放。
-
持续集成/持续部署 (CI/CD):
- 设置 CI/CD 流程以自动化测试和部署过程。
遵循这些最佳实践有助于确保你的 FastAPI 应用是可维护的、可扩展的、安全的,并且能够提供高性能。
项目结构
设计一个良好的项目结构是确保可维护性和可扩展性的关键。以下是一个典型的 FastAPI 项目结构示例,它遵循了分层架构和模块化设计原则:
my_fastapi_project/
│
├── app/ # 应用主目录
│ ├── api/ # 与 API 相关的定义
│ │ ├── dependencies/ # 依赖项,如数据库会话、权限验证
│ │ ├── endpoints/ # 路由和视图
│ │ │ ├── __init__.py
│ │ │ ├── item.py # 与项目相关的路由
│ │ │ └── user.py # 与用户相关的路由
│ │ └── __init__.py
│ │
│ ├── core/ # 核心配置和设置
│ │ ├── config.py # 配置相关
│ │ └── security.py # 安全和认证相关
│ │
│ ├── crud/ # 数据库 CRUD 操作
│ │ ├── __init__.py
│ │ ├── crud_item.py # 项目相关的 CRUD 操作
│ │ └── crud_user.py # 用户相关的 CRUD 操作
│ │
│ ├── db/ # 数据库相关
│ │ ├── base.py # 数据库模型的基类
│ │ ├── init_db.py # 数据库初始化脚本
│ │ ├── models/ # 数据库模型定义
│ │ │ ├── __init__.py
│ │ │ ├── item.py # 项目模型
│ │ │ └── user.py # 用户模型
│ │ └── session.py # 数据库会话管理
│ │
│ ├── models/ # Pydantic 模型定义
│ │ ├── __init__.py
│ │ ├── item.py # 项目相关的 Pydantic 模型
│ │ └── user.py # 用户相关的 Pydantic 模型
│ │
│ ├── schemas/ # 数据验证和序列化模式
│ │ ├── __init__.py
│ │ ├── item.py
│ │ └── user.py
│ │
│ ├── main.py # FastAPI 应用实例和路由的入口点
│ │
│ └── utils/ # 工具和辅助函数
│ ├── __init__.py
│ └── some_util.py
│
├── tests/ # 测试目录
│ ├── api/ # API 测试
│ ├── crud/ # CRUD 操作测试
│ └── __init__.py
│
├── Dockerfile # Docker 容器配置文件
├── requirements.txt # 项目依赖文件
└── README.md # 项目文档和说明
这个结构清晰地分隔了不同的项目组件:
app/main.py是 FastAPI 应用的入口点,它创建和配置了应用实例。app/api/包含了所有的路由和视图,它们可能被进一步分组到不同的模块。app/models/包含了 Pydantic 模型,用于请求和响应的数据验证。app/crud/包含了与数据库交互的逻辑,通常是 CRUD(创建、读取、更新、删除)操作。app/db/包含了数据库模型(ORM 模型)和数据库会话管理。app/core/包含了核心配置和安全设置。app/utils/包含了项目中使用的辅助函数和工具。tests/包含了所有的测试代码,以确保应用的稳定性和可靠性。
这个项目结构是一个起点,根据你的项目需求,你可能需要调整和扩展这个结构。例如,对于大型项目,你可能需要进一步模块化你的 API,或者将业务逻辑从视图中分离出来。
github 项目 参考
是的,GitHub 上有很多优秀的 FastAPI 项目可以作为参考。这些项目不仅展示了如何使用 FastAPI 构建应用程序,还往往遵循了良好的项目结构和最佳实践。以下是一些你可以查看的开源项目:
-
FastAPI RealWorld Example App:
- GitHub: https://github.com/nsidnev/fastapi-realworld-example-app
- 描述: 这是一个符合 RealWorld 规范的后端实现,模拟了一个真实的博客平台的 API,展示了 FastAPI 的典型用法。
-
Full Stack FastAPI and PostgreSQL:
- GitHub: https://github.com/tiangolo/full-stack-fastapi-postgresql
- 描述: 由 FastAPI 的创建者 Sebastián Ramírez 创建,这个项目是一个完整的 FastAPI 应用程序,包括用户管理、身份验证等,并使用 PostgreSQL 作为数据库。
-
Awesome FastAPI:
- GitHub: https://github.com/mjhea0/awesome-fastapi
- 描述: 这是一个精选的 FastAPI 教程、项目和资源的列表,可以帮助你找到更多相关的项目和学习资料。
-
FastAPI Microservices:
- GitHub: https://github.com/nsidnev/fastapi-microservices
- 描述: 一个展示如何使用 FastAPI 和 Docker 构建微服务的项目。
-
FastAPI Example Project:
- GitHub: https://github.com/testdrivenio/fastapi-crud
- 描述: 一个简单的 CRUD(创建、读取、更新、删除)应用程序,用于演示 FastAPI 的基本用法。
在查看这些项目时,请注意它们的项目结构、代码组织、测试策略以及如何处理配置和环境变量。这些都是你在设计自己的 FastAPI 项目时需要考虑的关键要素。
请记住,开源项目可能随时会更新,而某些项目可能不再维护或不符合最新的最佳实践。因此,在使用它们作为参考时,一定要结合当前的 FastAPI 文档和社区的最新建议。
**
是的,Python 中的 ** 字典解包操作符和 ES6 (ECMAScript 2015) 中的扩展操作符 ... 在某种程度上是类似的。它们都用于展开一个集合的元素。
在 JavaScript 中,扩展操作符 ... 可以用于数组和对象:
- 数组: 将数组元素展开成单独的元素。
- 对象: 将对象的可枚举属性展开为单独的键值对。
例如,使用扩展操作符合并对象:
const item = { name: 'example', price: 10.99 };
const updatedItem = { ...item, description: 'An example item' };
// updatedItem 现在是 { name: "example", price: 10.99, description: "An example item" }
在 Python 中,** 操作符是用于字典的,并且有以下用途:
- 函数调用: 将字典的键值对作为命名参数传递给函数。
- 创建新字典: 将一个或多个字典的键值对合并成一个新的字典。
例如,使用 ** 合并字典:
item = {"name": "example", "price": 10.99}
updated_item = {**item, "description": "An example item"}
# updated_item 现在是 {'name': 'example', 'price': 10.99, 'description': 'An example item'}
尽管这两个操作符在不同的语言中使用不同的符号,它们的作用在高层次上是相似的:它们都用于将一个集合的元素或属性“展开”到另一个上下文中。
请求参数什么情况用哪种
函数参数将依次按如下规则进行识别:
- 如果在路径中也声明了该参数,它将被用作路径参数。
- 如果参数属于单一类型(比如 int、float、str、bool 等)它将被解释为查询参数。
- 如果参数的类型被声明为一个 Pydantic 模型,它将被解释为请求体。
函数中*号
在 Python 中,函数定义时的 * 符号用来指示之后的参数必须以关键字参数(keyword arguments)的形式传递
让我们通过一个简单的例子来说明这一点:
# 定义一个函数,其中 * 后面的参数必须以关键字形式传递
def greet(message, *, name):
print(f"{message}, {name}!")
# 正确的调用方式
greet("Hello", name="Alice") # 输出: Hello, Alice!
# 如果尝试使用位置参数传递 name,将会引发错误
try:
greet("Hello", "Alice") # 这会抛出异常
except TypeError as e:
print(e) # 输出: greet() takes 1 positional argument but 2 were given
在这个例子中,greet 函数定义了两个参数:message 和 name。* 放在 name 参数前面,这意味着 name 必须通过关键字来指定,即 name="Alice"。如果尝试像传递位置参数那样调用函数(即 greet("Hello", "Alice")),Python 解释器会抛出一个 TypeError,因为 name 参数必须以关键字参数的形式传递。
这个特性在函数有多个参数且某些参数有默认值时尤其有用,因为它可以防止调用者意外地将一个值赋给错误的参数。
查询参数/body 参数
@app.put("/items/{item_id}")
async def update_item(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()] #这里使用 Body()来标记是body参数
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
# 以下为路径-查询-body参数
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: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str | None = None, #没有body标记,也不是BaseModel,这里是查询参数
item: Item | None = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
if item:
results.update({"item": item})
return results
Path/Query/Body/Cookie/Header
## Path/Query/Body/Cookie/Header 这些都是函数符合函数的特点
## Path/Query/Body 有优先级
## async def functionName(字段:Annotated[类型,上面这些函数] = 默认值):
### Path
### Query
### Body
### Cookie
from typing import Annotated, Union
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
return {"ads_id": ads_id}
### Header
# HTTP headers 是大小写不敏感的,因此,因此可以使用标准Python样式(也称为 "snake_case")声明它们。
# 因此,您可以像通常在Python代码中那样使用 user_agent ,而不需要将首字母大写为 User_Agent 或类似的东西。
# 如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置Header的参数 convert_underscores 为 False:
# example1 你需要禁用下划线到连字符的自动转换,设置Header的参数 convert_underscores 为 False:
from typing import Annotated, Union
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
# strange_header 就是接受的字段
strange_header: Annotated[
Union[str, None], Header(convert_underscores=False)
] = None,
):
return {"strange_header": strange_header}
# example2 重复heder 声明为List
from typing import Annotated, List, Union
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None):
return {"X-Token values": x_token}
### Request
### Response
fastapi 返回剔除空值
在 FastAPI 中,当你定义了一个 Pydantic 模型并使用它作为请求体或响应模型时,Pydantic 会负责序列化和反序列化数据。默认情况下,Pydantic 的模型会输出所有字段,包括那些具有默认值的字段。如果你想要排除掉具有默认 值的字段,可以在 Pydantic 模型中使用 dict() 方法,并传递 exclude_unset=True 参数。
例如,如果你有以下模型:
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
在你的路由处理函数中,你可以这样返回一个不包含默认值的响应:
@app.get("/items/{item_id}")
async def read_item(item_id: int):
item = get_item_by_id(item_id) # 假设这个函数返回一个 Item 实例
return item.dict(exclude_unset=True)
在这个例子中,如果 description 字段没有被设置(即它是默认值 None),它将不会被包含在响应中。
对于 Go 语言的 Gin 框架,处理默认值的方式可能会有所不同。Gin 使用 Go 的标准库 encoding/json 来序列化和反序列化 JSON 数据。在 Go 中,如果你想要在序列化 JSON 时忽略零值或空值,你可以使用结构体标签。
例如,你可以定义一个 Go 结构体并使用 omitempty 标签:
type Item struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Price float64 `json:"price"`
Tax *float64 `json:"tax,omitempty"` // 使用指针来允许空值
}
在这个例子中,如果 Description 或 Tax 的值为它们类型的零值(对于 string 是空字符串,对于指针是 nil),则在序列化为 JSON 时这些字段将被忽略。
请注意,这种方式依赖于字段的零值。如果字段的零值是你想要传递的有效值,那么你可能需要使用指针或自定义的序列化逻辑来处理这种情况。
golang 剔除空值
在 Go 中,要剔除空值而非有效的零值,你可以使用指针类型来表示可选的字段。当一个指针字段为 nil 时,它可以被视为未设置或空值,并且可以在 JSON 序列化时被省略掉。这样做可以区分字段未被设置(nil)和字段设置为零值(比如 0、false、"" 等)的情况。
例如,下面是一个 Go 结构体,它使用指针类型来允许某些字段在序列化为 JSON 时被忽略:
package main
import (
"encoding/json"
"fmt"
)
type Item struct {
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Price float64 `json:"price"`
Tax *float64 `json:"tax,omitempty"`
}
func main() {
// 示例:Description 和 Tax 字段未被设置,将被忽略
noDescriptionAndTax := Item{
Name: "Gadget",
Price: 99.99,
}
// 示例:Description 被设置为空字符串,Tax 被设置为 0.0,将被序列化
emptyDescriptionAndZeroTax := Item{
Name: "Widget",
Description: new(string), // 空字符串
Price: 49.99,
Tax: new(float64), // 0.0
}
// 序列化时剔除空值
jsonNoDesc, _ := json.Marshal(noDescriptionAndTax)
fmt.Println(string(jsonNoDesc)) // 输出:{"name":"Gadget","price":99.99}
// 序列化时包含有效的零值
jsonEmptyDesc, _ := json.Marshal(emptyDescriptionAndZeroTax)
fmt.Println(string(jsonEmptyDesc)) // 输出:{"name":"Widget","description":"","price":49.99,"tax":0}
}
在这个例子中:
Description和Tax字段是指针类型。- 如果
Description和Tax字段为nil,它们将在 JSON 序列化时被忽略。 - 如果
Description和Tax被设置为它们的零值(空字符串和0.0),它们将被序列化并包含在 JSON 中,因为它们是指针指向的实际值。
omitempty 标签在 JSON 标签中用来指示,如果字段的值是空(nil),那么在序列化 JSON 时应该忽略该字段。这允许你区分字段未被设置和字段设置为零值的情况 。
response_model_include/response_model_exclude
在 FastAPI 中,response_model_include 和 response_model_exclude 参数用于控制响应模型中包含或排除的字段。这两个参数可以在路径操作装饰器中设置,以便在返回响应时,只包含或排除特定的字段。
response_model_include 参数允许你指定一个字段集合,只有这些字段会被包含在响应中。不在这个集合中的字段将不会被包含在响应数据中。
在你提供的代码示例中,有两个路由处理函数:
read_item_name:它使用response_model_include来指定只包含name和description字段在响应中。
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
return items[item_id]
当你访问这个路由时,例如 /items/foo/name,即使 Item 模型中定义了 name、description、price 和 tax 四个字段,返回的 JSON 响应体将只包含 name 和 description 字段。对于 foo 这个示例,由于 description 没有在字典中设置,所以最终响应将只包含 name 字段。
read_item_public_data:它使用response_model_exclude来指定排除tax字段在响应中。
@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
return items[item_id]
当你访问这个路由时,例如 /items/foo/public,返回的 JSON 响应体将包含 Item 模型中定义的除 tax 外的所有字段,即 name、description 和 price。
这种方式特别有用,因为它允许你根据不同的 API 端点需求,重用同一个 Pydantic 模型,同时控制响应数据的粒度。
from
在 FastAPI 中,要处理 HTML 表单数据,你可以使用 Form 类来定义表单字段。Form 是一个特殊的类,它会告诉 FastAPI 从表单的数据中获取信息,而不是从 JSON 中获取。这对于接收表单字段非常有用,比如用户登录或注册场景。
首先,要使用 Form,你需要从 fastapi 导入它:
from fastapi import FastAPI, Form
然后,你可以在路径操作函数中定义表单参数,如下所示:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
在上面的示例中,Form 的使用与 Query、Path 和 Body 类似,它们都是 FastAPI 的依赖注入系统的一部分。使用 Form(...) 表示该字段是必填的。如果你想要一个可选字段,可以使用 None 作为默认值,例如 Form(None)。
当你向这个 /login/ 端点发送一个 application/x-www-form-urlencoded 类型的 POST 请求时,FastAPI 会从表单数据中获取 username 和 password 字段的值,然后返回一个包含 username 的 JSON 响应。
为了使上述示例工作,你需要确保你的客户端发送的是表单数据,而不是 JSON。在 HTML 中,这通常意味着使用一个 <form> 标签,并且其 enctype 属性被设置为 application/x-www-form-urlencoded。
<form method="post" action="/login/" enctype="application/x-www-form-urlencoded">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="submit" />
</form>
如果你使用的是像 curl 这样的命令行工具,你需要确保数据以正确的格式发送:
curl -X POST "http://127.0.0.1:8000/login/" -H "Content-Type: application/x-www-form-urlencoded" -d "username=johndoe&password=secret"
这样,FastAPI 就可以正确处理表单数据了。
fastapi 保活
在 FastAPI 或任何其他 Python web 框架中,建立一个守护进程来确保应用程序持续运行通常不是应用程序本身的职责。相反,这通常是由外部的进程管理器或服务管理器来处理的。这些工具可以监控你的 FastAPI 应用程序,并在它崩溃或关闭时重新启动它。
以下是一些常用的进程管理器和服务管理器:
-
systemd(适用于 Linux 系统):
- 你可以将你的 FastAPI 应用程序配置为 systemd 服务。这样,systemd 就可以在应用程序崩溃时自动重启它,并且可以在系统启动时自动启动应用程序。
-
Supervisor:
- Supervisor 是一个流行的 Python 进程管理工具,它可以用来启动、重启和管理应用程序。你可以配置 supervisor 来监控你的 FastAPI 应用程序,并在必要时重启。
-
Docker:
- 如果你使用 Docker 容器来部署你的 FastAPI 应用程序,你可以利用 Docker 的重启策略来保持应用程序的运行。例如,你可以在 docker-compose 文件中设置
restart: always
- 如果你使用 Docker 容器来部署你的 FastAPI 应用程序,你可以利用 Docker 的重启策略来保持应用程序的运行。例如,你可以在 docker-compose 文件中设置