带有一个选项标记为已弃用的 Union 字段的 Pydantic 模型

问题描述 投票:0回答:1

我有一些 Pydantic 模型,其字段是不同模型的联合。 我正在寻找一种方法来在我的 fastapi 生成的文档中弃用一些联合模型。

我可以通过以下方式弃用整个字段:

Field(default=None, deprecated=True)

但是我发现没有办法在其中一个可能的值上做到这一点。例如在下面的示例中,是否可以将 SimpleUser 标记为已弃用 Log 模型并根据该标记生成文档。

from typing import Union

from pydantic import BaseModel, Field


class Admin(BaseModel):
    name: str


class SimpleUser(BaseModel):
    age: int


class Log(BaseModel):
    user: Union[Admin, SimpleUser, None] = Field(default=None)
python fastapi swagger-ui jsonschema pydantic
1个回答
1
投票

Pydantic 通过在其他模型中定义的属性中使用

$ref
键来创建嵌套模型的模式。默认情况下,架构将有一个单独的
definitions
对象,其中包含其中引用的架构。

在您的特定情况下,

Log
架构将如下所示:

{
  "title": "Log",
  "type": "object",
  "properties": {
    "user": {
      "title": "User",
      "anyOf": [
        {
          "$ref": "#/definitions/Admin"
        },
        {
          "$ref": "#/definitions/SimpleUser"
        }
      ]
    }
  },
  "definitions": {
    "Admin": {
      "title": "Admin",
      "type": "object",
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    },
    "SimpleUser": {
      "title": "SimpleUser",
      "type": "object",
      "properties": {
        "age": {
          "title": "Age",
          "type": "integer"
        }
      },
      "required": [
        "age"
      ]
    }
  }
}

联合反映在

anyOf
键中,数组中的每个对象都引用其中一个模型。

据我了解 JSON Schema 的当前规范,您可以在

$ref
旁边添加其他关键字。因此,我假设您可以通过简单地添加
SimpleUser
 关键字以及对 user
 定义的引用来传达 
deprecated
SimpleUser
的弃用选项。

Pydantic 允许您通过

schema_extra
配置参数最终控制 JSON 模式的构建方式。如果你将它定义为内部
Config
类的静态方法,你可以按照你喜欢的任何方式修改它。

所以实现该加法的方法如下所示:

from typing import Any

from pydantic import BaseModel, Field


class Admin(BaseModel):
    name: str


class SimpleUser(BaseModel):
    age: int


class Log(BaseModel):
    user: Admin | SimpleUser | None = Field(default=None)

    class Config:
        @staticmethod
        def schema_extra(schema: dict[str, Any]) -> None:
            """Marks the `SimpleUser` option for `user` as deprecated."""
            user_property = schema["properties"]["user"]
            for obj in user_property["anyOf"]:
                ref = obj.get("$ref")
                if isinstance(ref, str) and ref.endswith("/SimpleUser"):
                    obj["deprecated"] = True
                    break

print(Log.schema_json(indent=2))
现在的输出:

{
  "title": "Log",
  "type": "object",
  "properties": {
    "user": {
      "title": "User",
      "anyOf": [
        {
          "$ref": "#/definitions/Admin"
        },
        {
          "$ref": "#/definitions/SimpleUser",
          "deprecated": true
        }
      ]
    }
  },
  "definitions": {
    "Admin": {
      "title": "Admin",
      "type": "object",
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    },
    "SimpleUser": {
      "title": "SimpleUser",
      "type": "object",
      "properties": {
        "age": {
          "title": "Age",
          "type": "integer"
        }
      },
      "required": [
        "age"
      ]
    }
  }
}

与之前的唯一区别是添加

"deprecated": true
.

我不是 100% 确定客户是否应该按照您的预期方式理解此模式,因为我没有发现以这种方式使用它的示例,但我也没有发现相反的迹象,并且 IMO 符合规范。也许对 JSON Schema 有更多经验的人可以对此发表评论以(不)确认。


附:

如果

$ref
旁边的附加属性被客户端忽略,因为它坚持 OpenAPI 3.1 规范,您仍然可以在整个
SimpleUser
模型上设置已弃用的属性:

from pydantic import BaseModel, Field


class Admin(BaseModel):
    name: str


class SimpleUser(BaseModel):
    age: int

    class Config:
        schema_extra = {"deprecated": True}


class Log(BaseModel):
    user: Admin | SimpleUser | None = Field(default=None)
© www.soinside.com 2019 - 2024. All rights reserved.