Skip to content

回复

您可以在 fastapi response_model 中使用 ormar 模型而不是 pydantic 模型。

如果需要,您当然也可以将 ormar.Models 与 pydantic 混合使用。

响应中最常见的任务之一是排除您不想包含在响应数据中的某些字段。

在 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)和类别(具有默认值)是可选的,可以在响应中跳过,并且模型仍将验证。

如果该字段可为空,则不必在创建期间以及响应中将其包含在有效负载中,因此根据上面的示例,您可以:

1
2
3
4
# 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 ,它将正确验证(因为字段是可选的),保存在数据库中并返回不带此字段的已保存记录(也将通过验证)。

!!!Note 请注意,虽然您没有传递字段值,但字段本身仍然存在于response_model 中,这意味着它将出现在响应数据中并设置为None。

1
If you want to fully exclude the field from the result read on.

FastApi响应_模型_排除

Fastapi 具有接受一组(或列表)字段名称的response_model_exclude。

这有其局限性,因为 ormar 和 pydantic 还接受字典,您可以在其中设置排除/包含列也可以在嵌套模型上(更多内容见下文)

警告注意,使用response_model 时不能排除必填字段,因为它会在验证过程中失败。

1
2
3
@app.post("/users/", response_model=User, response_model_exclude={"password"})
async def create_user(user: User):
    return await user.save()

上面的端点可以这样查询:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from starlette.testclient import TestClient

client = TestClient(app)

with client as client:
        # note there is no pk
        user = {
            "email": "test@domain.com",
            "password": "^*^%A*DA*IAAA",
            "first_name": "John",
            "last_name": "Doe",
        }
        response = client.post("/users/", json=user)
        # note that the excluded field is fully gone from response
        assert "password" not in response.json()
        # read the response and initialize model out of it
        created_user = User(**response.json())
        # note pk is populated by autoincrement
        assert created_user.pk is not None
        # note that password is missing in initialized model too
        assert created_user.password is None

!!!注意注意上面示例中密码字段如何从响应数据中完全消失。

1
Note that you can use this method only for non-required fields.

嵌套模型排除

尽管 fastapi 只允许传递一组字段名称,所以简单地排除,但在使用 response_model_exclude 时,ormar 更聪明。

在 ormar 中,您可以使用两种类型的符号排除嵌套模型。

第一个是具有表示模型树结构的嵌套字段的字典,第二个是双下划线分隔的字段名称路径。

假设我们的用户类别是一个单独的模型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
base_ormar_config = ormar.OrmarConfig(
    metadata=metadata
    database=database
)

class Category(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="categories")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=255)    
    priority: int = ormar.Integer(nullable=True)


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: Optional[Category] = ormar.ForeignKey(Category, related_name="categories")

如果您想从响应中的类别中排除优先级,您仍然可以使用 fastapi 参数。

1
2
3
@app.post("/users/", response_model=User, response_model_exclude={"category__priority"})
async def create_user(user: User):
    return await user.save()

请注意,您可以使用双下划线进入更深的模型,如果您想从嵌套模型中排除多个字段,则需要在它们前面加上完整路径。在例子中 response_model_exclude={"category__priority", "category__other_field", category__nested_model__nested_model_field} ETC。

!!!注意要阅读有关可能的排除以及如何构建排除字典或设置的更多信息,请访问文档的字段部分

注意注意,除了response_model_exclude参数之外,fastapi还支持从pydantic继承的其他参数。所有这些都也适用于 ormar,但可能有一些细微差别,因此最好阅读文档的 dict 部分。

在 Model.model_dump() 中排除

或者,您可以只从 ormar.Model 返回一个字典并使用 。

像这样,您还可以将排除/包含设置为字典,并排除嵌套模型上的字段。

!!!警告不使用response_model 将导致api 文档没有响应示例和模式,因为理论上响应可以具有任何格式。

1
2
3
4
5
@app.post("/users2/", response_model=User)
async def create_user2(user: User):
    user = await user.save()
    return user.model_dump(exclude={'password'})
    # could be also something like return user.model_dump(exclude={'category': {'priority'}}) to exclude category priority

!!!注意 请注意,即使您在请求中传递密码字段,上面的示例也会使密码字段无效,但该字段仍将存在,因为它是响应架构的一部分,该值将设置为 None。

如果您想用这种方法完全排除该字段,只需不要使用response_model并在模型的model_dump()中排除

或者,您可以只从 ormar 模型返回一个字典。像这样,您还可以将排除/包含设置为字典并排除嵌套模型上的字段。

!!!注意理论上,您在这里失去了响应验证,但由于您在 ormar.Models 上操作,响应数据在数据库查询后已经经过验证(因为 ormar 模型是 pydantic 模型)。

因此,如果您完全跳过response_model,您可以执行以下操作:

1
2
3
4
@app.post("/users4/") # note no response_model
async def create_user4(user: User):
    user = await user.save()
    return user.model_dump(exclude={'last_name'})

!!!注意 请注意,当您跳过response_model 时,您现在还可以排除必填字段,因为返回后不再验证响应。

1
The cost of this solution is that you loose also api documentation as response schema in unknown from fastapi perspective.

从 ormar.Model 生成 pydantic 模型

由于排除字段的任务非常常见,ormar 有一种特殊的方法可以从现有的 ormar.Models 生成 pydantic 模型,而无需重新输入所有字段。

该方法是所有模型类都可用的 get_pydantic() 方法。

1
2
3
4
5
# generate a tree of models without password on User and without priority on nested Category
ResponseUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
@app.post("/users3/", response_model=ResponseUser) # use the generated model here
async def create_user3(user: User):
    return await user.save()

!!!注意要查看更多示例并阅读更多内容,请访问文档的 get_pydantic 部分。

警告 get_pydantic 方法根据允许避免模型中循环的算法生成嵌套模型树中的所有模型(与 model_dump()、select_all() 等中使用的算法相同)

1
2
3
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.

独立的悬臂模型

最终的解决方案是手动创建单独的 pydantic 模型。它的工作原理与普通的 fastapi 应用程序完全相同,因此您可以使用不同的响应和请求模型等。

样本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pydantic

class UserBase(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(from_attributes=True)

    email: str
    first_name: str
    last_name: str


@app.post("/users3/", response_model=UserBase) # use pydantic model here
async def create_user3(user: User): #use ormar model here (but of course you CAN use pydantic also here)
    return await user.save()