要求
您可以在 fastapi 请求主体参数中使用 ormar 模型而不是 pydantic 模型。
如果需要,您当然也可以将 ormar.Models 与 pydantic 混合使用。
请求中最常见的任务之一是排除您不希望包含在发送到 API 的负载中的某些字段。
在 ormar 中可以通过多种方式实现这一目标,因此您可以在下面查看您的选项并选择最适合您情况的一种。
排除请求中的字段
可选字段
请注意,每个可选字段都不是必需的,这意味着在响应和请求中都可以跳过可选字段。
如果(任何/许多/全部)出现以下情况,则不需要字段:
- 字段标记为 nullable=True
- 字段具有默认值或提供的功能,即 default="Test"
- 字段设置了 server_default 值
- Field 是一个 autoincrement=Trueprimary_key 字段(注意 ormar.Integerprimary_key 默认是 autoincrement)
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | base_ormar_config = ormar.OrmarConfig(
metadata=metadata
database=database
)
class User(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
email: str = ormar.String(max_length=255)
password: str = ormar.String(max_length=255)
first_name: str = ormar.String(max_length=255, nullable=True)
last_name: str = ormar.String(max_length=255)
category: str = ormar.String(max_length=255, default="User")
|
在上面的示例中,字段 id(是一个自动增量整数)、first_name(具有 nullable=True)和类别(具有默认值)是可选的,可以在响应中跳过,并且模型仍将验证。
如果该字段可为空,则不必在创建期间以及响应中将其包含在有效负载中,因此根据上面的示例,您可以:
!!!警告请注意,虽然您不必传递可选字段,但您仍然可以这样做。如果有人传递一个值,它将在以后使用,除非您采取措施阻止它。
| # note that app is an FastApi app
@app.post("/users/", response_model=User) # here we use ormar.Model in response
async def create_user(user: User): # here we use ormar.Model in request parameter
return await user.save()
|
这意味着如果您没有在请求中传递 ie first_name ,它将正确验证(因为字段是可选的), None 将被保存在数据库中。
从 ormar.Model 生成 pydantic 模型
由于排除字段的任务非常常见,ormar 有一种特殊的方法可以从现有的 ormar.Models 生成 pydantic 模型,而无需重新输入所有字段。
该方法是所有模型类都可用的 get_pydantic() 方法。
| # generate a tree of models without password on User and without priority on nested Category
RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
@app.post("/users3/", response_model=User) # here you can also use both ormar/pydantic
async def create_user3(user: RequestUser): # use the generated model here
# note how now user is pydantic and not ormar Model so you need to convert
return await User(**user.model_dump()).save()
|
!!!注意要查看更多示例并阅读更多内容,请访问文档的 get_pydantic 部分。
警告 get_pydantic 方法根据允许避免模型中循环的算法生成嵌套模型树中的所有模型(与 model_dump()、select_all() 等中使用的算法相同)
| That means that nested models won't have reference to parent model (by default ormar relation is bidirectional).
Note also that if given model exists in a tree more than once it will be doubled in pydantic models (each occurrence will have separate own model). That way you can exclude/include different fields on different leafs of the tree.
|
Mypy 和类型检查
请注意,将函数分配为 python 类型会在运行时通过(因为未检查),像 mypy 这样的静态类型检查器会抱怨。
尽管对于给定模型,函数调用的结果始终相同,但不允许使用动态创建的类型。
因此你有两个选择:
第一个是简单地添加 # type:ignore 来跳过类型检查
| RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
@app.post("/users3/", response_model=User)
async def create_user3(user: RequestUser): # type: ignore
# note how now user is not ormar Model so you need to convert
return await User(**user.model_dump()).save()
|
第二个有点更hacky,利用fastapi 提取函数参数的方式。
您可以覆盖给定参数的 __annotations__ 条目。
| RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
# do not use the app decorator
async def create_user3(user: User): # use ormar model here
return await User(**user.model_dump()).save()
# overwrite the function annotations entry for user param with generated model
create_user3.__annotations__["user"] = RequestUser
# manually call app functions (app.get, app.post etc.) and pass your function reference
app.post("/categories/", response_model=User)(create_user3)
|
请注意,这将导致 mypy “认为”用户是 ormar 模型,但由于在请求中它并不重要(无论如何你都会传递 jsonized dict 并且需要在保存之前进行转换)。
这仍然应该可以正常工作,因为生成的模型将是字段的子集,因此所有需要的字段都将验证,并且所有未使用的字段将在运行时失败。
独立的悬臂模型
最终的解决方案是手动创建单独的 pydantic 模型。它的工作原理与普通的 fastapi 应用程序完全相同,因此您可以使用不同的响应和请求模型等。
样本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | import pydantic
class UserCreate(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)
email: str
first_name: str
last_name: str
password: str
@app.post("/users3/", response_model=User) # use ormar model here (but of course you CAN use pydantic also here)
async def create_user3(user: UserCreate): # use pydantic model here
# note how now request param is a pydantic model and not the ormar one
# so you need to parse/convert it to ormar before you can use database
return await User(**user.model_dump()).save()
|