Перейти к содержанию

JSON Schema

??? API "Документация по API" pydantic.json_schema

Pydantic позволяет автоматически создавать и настраивать схемы JSON на основе моделей. Сгенерированные схемы JSON соответствуют следующим спецификациям:

Генерация схемы JSON

Используйте следующие функции для создания схемы JSON:

!!! Примечание. Эти методы не следует путать с BaseModel.model_dump_json и TypeAdapter.dump_json, которые сериализуют экземпляры модели или адаптированного типа соответственно. . Эти методы возвращают строки JSON. Для сравнения: BaseModel.model_json_schema и TypeAdapter.json_schema возвращают jsonable dict, представляющий схему JSON модели или адаптированного типа соответственно.

!!! примечание «о «jsonable» природе схемы JSON». Что касается «jsonable» природы результатов model_json_schema, вызов json.dumps(m.model_json_schema()) в некоторой BaseModel m возвращает действительную строку JSON. Аналогично, для TypeAdapter.json_schema вызов json.dumps(TypeAdapter(<some_type>).json_schema()) возвращает действительную строку JSON.

!!! Совет Pydantic предлагает поддержку обоих:

1. [Customizing JSON Schema](#customizing-json-schema)
2. [Customizing the JSON Schema Generation Process](#customizing-the-json-schema-generation-process)

The first approach generally has a more narrow scope, allowing for customization of the JSON schema for
more specific cases and types. The second approach generally has a more broad scope, allowing for customization
of the JSON schema generation process overall. The same effects can be achieved with either approach, but
depending on your use case, one approach might offer a more simple solution than the other.

Вот пример генерации схемы JSON из BaseModel :

import json
from enum import Enum
from typing import Union

from typing_extensions import Annotated

from pydantic import BaseModel, Field
from pydantic.config import ConfigDict


class FooBar(BaseModel):
    count: int
    size: Union[float, None] = None


class Gender(str, Enum):
    male = 'male'
    female = 'female'
    other = 'other'
    not_given = 'not_given'


class MainModel(BaseModel):
    """
    This is the description of the main model
    """

    model_config = ConfigDict(title='Main')

    foo_bar: FooBar
    gender: Annotated[Union[Gender, None], Field(alias='Gender')] = None
    snap: int = Field(
        42,
        title='The Snap',
        description='this is the value of snap',
        gt=30,
        lt=50,
    )


main_model_schema = MainModel.model_json_schema()  # (1)!
print(json.dumps(main_model_schema, indent=2))  # (2)!
"""
{
  "$defs": {
    "FooBar": {
      "properties": {
        "count": {
          "title": "Count",
          "type": "integer"
        },
        "size": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        }
      },
      "required": [
        "count"
      ],
      "title": "FooBar",
      "type": "object"
    },
    "Gender": {
      "enum": [
        "male",
        "female",
        "other",
        "not_given"
      ],
      "title": "Gender",
      "type": "string"
    }
  },
  "description": "This is the description of the main model",
  "properties": {
    "foo_bar": {
      "$ref": "#/$defs/FooBar"
    },
    "Gender": {
      "anyOf": [
        {
          "$ref": "#/$defs/Gender"
        },
        {
          "type": "null"
        }
      ],
      "default": null
    },
    "snap": {
      "default": 42,
      "description": "this is the value of snap",
      "exclusiveMaximum": 50,
      "exclusiveMinimum": 30,
      "title": "The Snap",
      "type": "integer"
    }
  },
  "required": [
    "foo_bar"
  ],
  "title": "Main",
  "type": "object"
}
"""
```

1. This produces a "jsonable" dict of `MainModel`'s schema.
2. Calling `json.dumps` on the schema dict produces a JSON string.

The [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] class lets you create an object with methods for validating, serializing,
and producing JSON schemas for arbitrary types. This serves as a complete replacement for `schema_of` in
Pydantic V1 (which is now deprecated).

Here's an example of generating JSON schema from a [`TypeAdapter`][pydantic.type_adapter.TypeAdapter]:

```py
from typing import List

from pydantic import TypeAdapter

adapter = TypeAdapter(List[int])
print(adapter.json_schema())
#> {'items': {'type': 'integer'}, 'type': 'array'}
```

You can also generate JSON schemas for combinations of [`BaseModel`s][pydantic.main.BaseModel]
and [`TypeAdapter`s][pydantic.type_adapter.TypeAdapter], as shown in this example:

```py
import json
from typing import Union

from pydantic import BaseModel, TypeAdapter


class Cat(BaseModel):
    name: str
    color: str


class Dog(BaseModel):
    name: str
    breed: str


ta = TypeAdapter(Union[Cat, Dog])
ta_schema = ta.json_schema()
print(json.dumps(ta_schema, indent=2))
"""
{
  "$defs": {
    "Cat": {
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "color": {
          "title": "Color",
          "type": "string"
        }
      },
      "required": [
        "name",
        "color"
      ],
      "title": "Cat",
      "type": "object"
    },
    "Dog": {
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "breed": {
          "title": "Breed",
          "type": "string"
        }
      },
      "required": [
        "name",
        "breed"
      ],
      "title": "Dog",
      "type": "object"
    }
  },
  "anyOf": [
    {
      "$ref": "#/$defs/Cat"
    },
    {
      "$ref": "#/$defs/Dog"
    }
  ]
}
"""

Настройка JsonSchemaMode

Укажите режим генерации схемы JSON с помощью параметра mode в методах model_json_schema и TypeAdapter.json_schema. По умолчанию для режима установлено значение 'validation' , при котором создается схема JSON, соответствующая схеме проверки модели.

JsonSchemaMode — это псевдоним типа, который представляет доступные параметры для параметра mode :

  • 'validation'
  • 'serialization'

Вот пример того, как указать параметр mode и как он влияет на сгенерированную схему JSON:

from decimal import Decimal

from pydantic import BaseModel


class Model(BaseModel):
    a: Decimal = Decimal('12.34')


print(Model.model_json_schema(mode='validation'))
"""
{
    'properties': {
        'a': {
            'anyOf': [{'type': 'number'}, {'type': 'string'}],
            'default': '12.34',
            'title': 'A',
        }
    },
    'title': 'Model',
    'type': 'object',
}
"""

print(Model.model_json_schema(mode='serialization'))
"""
{
    'properties': {'a': {'default': '12.34', 'title': 'A', 'type': 'string'}},
    'title': 'Model',
    'type': 'object',
}
"""

Настройка схемы JSON

Сгенерированную схему JSON можно настроить как на уровне поля, так и на уровне модели с помощью:

  1. Настройка на уровне поля с помощью конструктора Field
  2. Настройка на уровне модели с помощью model_config

Как на уровне поля, так и на уровне модели вы можете использовать параметр json_schema_extra , чтобы добавить дополнительную информацию в схему JSON. В разделе «Использование json_schema_extra ниже представлена более подробная информация об этой опции.

Для пользовательских типов Pydantic предлагает другие инструменты для настройки генерации схемы JSON:

  1. Аннотация WithJsonSchema
  2. SkipJsonSchema аннотациюJsonSchema
  3. Реализация __get_pydantic_core_schema__
  4. Реализация __get_pydantic_json_schema__

Настройка на уровне поля

При желании можно использовать функцию Field для предоставления дополнительной информации о поле и проверках.

Некоторые параметры полей используются исключительно для настройки сгенерированной схемы JSON:

  • title : Название поля.
  • description : описание поля.
  • examples : примеры поля.
  • json_schema_extra : дополнительные свойства схемы JSON, которые нужно добавить в поле.
  • field_title_generator : функция, которая программно устанавливает заголовок поля на основе его имени и информации.

Вот пример:

import json

from pydantic import BaseModel, EmailStr, Field, SecretStr


class User(BaseModel):
    age: int = Field(description='Age of the user')
    email: EmailStr = Field(examples=['marcelo@mail.com'])
    name: str = Field(title='Username')
    password: SecretStr = Field(
        json_schema_extra={
            'title': 'Password',
            'description': 'Password of the user',
            'examples': ['123456'],
        }
    )


print(json.dumps(User.model_json_schema(), indent=2))
"""
{
  "properties": {
    "age": {
      "description": "Age of the user",
      "title": "Age",
      "type": "integer"
    },
    "email": {
      "examples": [
        "marcelo@mail.com"
      ],
      "format": "email",
      "title": "Email",
      "type": "string"
    },
    "name": {
      "title": "Username",
      "type": "string"
    },
    "password": {
      "description": "Password of the user",
      "examples": [
        "123456"
      ],
      "format": "password",
      "title": "Password",
      "type": "string",
      "writeOnly": true
    }
  },
  "required": [
    "age",
    "email",
    "name",
    "password"
  ],
  "title": "User",
  "type": "object"
}
"""

Непринудительные ограничения Field

Если Pydantic обнаружит ограничения, которые не соблюдаются, возникнет ошибка. Если вы хотите принудительно отобразить ограничение в схеме, даже если оно не проверяется при анализе, вы можете использовать переменные аргументы для Field с именем необработанного атрибута схемы:

from pydantic import BaseModel, Field, PositiveInt

try:
    # this won't work since `PositiveInt` takes precedence over the
    # constraints defined in `Field`, meaning they're ignored
    class Model(BaseModel):
        foo: PositiveInt = Field(..., lt=10)

except ValueError as e:
    print(e)


# if you find yourself needing this, an alternative is to declare
# the constraints in `Field` (or you could use `conint()`)
# here both constraints will be enforced:
class ModelB(BaseModel):
    # Here both constraints will be applied and the schema
    # will be generated correctly
    foo: int = Field(..., gt=0, lt=10)


print(ModelB.model_json_schema())
"""
{
    'properties': {
        'foo': {
            'exclusiveMaximum': 10,
            'exclusiveMinimum': 0,
            'title': 'Foo',
            'type': 'integer',
        }
    },
    'required': ['foo'],
    'title': 'ModelB',
    'type': 'object',
}
"""

Вы можете указать изменения схемы JSON с помощью конструктора Field также с помощью typing.Annotated:

import json
from uuid import uuid4

from typing_extensions import Annotated

from pydantic import BaseModel, Field


class Foo(BaseModel):
    id: Annotated[str, Field(default_factory=lambda: uuid4().hex)]
    name: Annotated[str, Field(max_length=256)] = Field(
        'Bar', title='CustomName'
    )


print(json.dumps(Foo.model_json_schema(), indent=2))
"""
{
  "properties": {
    "id": {
      "title": "Id",
      "type": "string"
    },
    "name": {
      "default": "Bar",
      "maxLength": 256,
      "title": "CustomName",
      "type": "string"
    }
  },
  "title": "Foo",
  "type": "object"
}
"""

Автоматическое создание названия поля

Параметр field_title_generator можно использовать для программного создания заголовка поля на основе его имени и информации.

См. следующий пример:

import json

from pydantic import BaseModel, Field
from pydantic.fields import FieldInfo


def make_title(field_name: str, field_info: FieldInfo) -> str:
    return field_name.upper()


class Person(BaseModel):
    name: str = Field(field_title_generator=make_title)
    age: int = Field(field_title_generator=make_title)


print(json.dumps(Person.model_json_schema(), indent=2))
"""
{
  "properties": {
    "name": {
      "title": "NAME",
      "type": "string"
    },
    "age": {
      "title": "AGE",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}
"""

Настройка на уровне модели

Вы также можете использовать конфигурацию модели для настройки создания схемы JSON в модели. В частности, актуальны следующие параметры конфигурации:

Использование json_schema_extra

Опцию json_schema_extra можно использовать для добавления дополнительной информации в схему JSON либо на уровне поля , либо на уровне модели . Вы можете передать dict или Callable в json_schema_extra .

Использование json_schema_extra с dict

Вы можете передать dict json_schema_extra чтобы добавить дополнительную информацию в схему JSON:

import json

from pydantic import BaseModel, ConfigDict


class Model(BaseModel):
    a: str

    model_config = ConfigDict(json_schema_extra={'examples': [{'a': 'Foo'}]})


print(json.dumps(Model.model_json_schema(), indent=2))
"""
{
  "examples": [
    {
      "a": "Foo"
    }
  ],
  "properties": {
    "a": {
      "title": "A",
      "type": "string"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}
"""

Использование json_schema_extra с Callable объектом

Вы можете передать Callable в json_schema_extra , чтобы изменить схему JSON с помощью функции:

import json

from pydantic import BaseModel, Field


def pop_default(s):
    s.pop('default')


class Model(BaseModel):
    a: int = Field(default=1, json_schema_extra=pop_default)


print(json.dumps(Model.model_json_schema(), indent=2))
"""
{
  "properties": {
    "a": {
      "title": "A",
      "type": "integer"
    }
  },
  "title": "Model",
  "type": "object"
}
"""

Объединение json_schema_extra

Начиная с версии 2.9, Pydantic объединяет словари json_schema_extra из аннотированных типов. Этот шаблон предлагает более аддитивный подход к слиянию, чем предыдущий вариант переопределения. Это может быть весьма полезно в случаях повторного использования дополнительной информации схемы JSON для нескольких типов.

Мы рассматривали это изменение главным образом как исправление ошибки, поскольку оно устраняет непреднамеренные различия в поведении слияния json_schema_extra между экземплярами BaseModel и TypeAdapter — дополнительные сведения см . в этой проблеме .

import json

from typing_extensions import Annotated, TypeAlias

from pydantic import Field, TypeAdapter

ExternalType: TypeAlias = Annotated[
    int, Field(..., json_schema_extra={'key1': 'value1'})
]

ta = TypeAdapter(
    Annotated[ExternalType, Field(..., json_schema_extra={'key2': 'value2'})]
)
print(json.dumps(ta.json_schema(), indent=2))
"""
{
  "key1": "value1",
  "key2": "value2",
  "type": "integer"
}
"""

Если вы предпочитаете, чтобы последняя из ваших спецификаций json_schema_extra переопределяла предыдущие, вы можете использовать callable для внесения более существенных изменений, включая добавление или удаление ключей или изменение значений. Вы можете использовать этот шаблон, если хотите имитировать поведение переопределений json_schema_extra , присутствующих в Pydantic v2.8 и более ранних версиях:

import json

from typing_extensions import Annotated, TypeAlias

from pydantic import Field, TypeAdapter
from pydantic.json_schema import JsonDict

ExternalType: TypeAlias = Annotated[
    int, Field(..., json_schema_extra={'key1': 'value1', 'key2': 'value2'})
]


def finalize_schema(s: JsonDict) -> None:
    s.pop('key1')
    s['key2'] = s['key2'] + '-final'
    s['key3'] = 'value3-final'


ta = TypeAdapter(
    Annotated[ExternalType, Field(..., json_schema_extra=finalize_schema)]
)
print(json.dumps(ta.json_schema(), indent=2))
"""
{
  "key2": "value2-final",
  "key3": "value3-final",
  "type": "integer"
}
"""

Аннотация WithJsonSchema

??? API "Документация по API" pydantic.json_schema.WithJsonSchema

!!! Совет. Использование WithJsonSchema] предпочтительнее реализации __get_pydantic_json_schema__ для пользовательских типов, поскольку это более просто и менее подвержено ошибкам.

Аннотацию WithJsonSchema можно использовать для переопределения сгенерированной (базовой) схемы JSON для данного типа без необходимости реализации __get_pydantic_core_schema__ или __get_pydantic_json_schema__ для самого типа.

Это дает возможность установить схему JSON для типов, которые в противном случае вызвали бы ошибки при создании схемы JSON, например Callable , или типов, которые имеют базовую схему is-instance.

Например, использование PlainValidator в следующем примере в противном случае привело бы к ошибке при создании схемы JSON, поскольку PlainValidator является Callable . Однако, используя аннотацию WithJsonSchema, мы можем переопределить сгенерированную схему JSON для пользовательского типа MyInt :

import json

from typing_extensions import Annotated

from pydantic import BaseModel, PlainValidator, WithJsonSchema

MyInt = Annotated[
    int,
    PlainValidator(lambda v: int(v) + 1),
    WithJsonSchema({'type': 'integer', 'examples': [1, 0, -1]}),
]


class Model(BaseModel):
    a: MyInt


print(Model(a='1').a)
#> 2

print(json.dumps(Model.model_json_schema(), indent=2))
"""
{
  "properties": {
    "a": {
      "examples": [
        1,
        0,
        -1
      ],
      "title": "A",
      "type": "integer"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}
"""

!!! note Как обсуждалось в этом выпуске , в будущем Pydantic, вероятно, добавит встроенную поддержку генерации схемы JSON для таких типов, как PlainValidator, но аннотация WithJsonSchema будет по-прежнему полезен для других пользовательских типов.

SkipJsonSchema аннотациюJsonSchema

??? API "Документация по API" pydantic.json_schema.SkipJsonSchema

Аннотацию SkipJsonSchema можно использовать для пропуска включающего поля (или части спецификаций поля) из сгенерированной схемы JSON. Дополнительную информацию см. в документации API.

Реализация __get_pydantic_core_schema__

Пользовательские типы (используются как field_name: TheType или field_name: Annotated[TheType, ...] ), а также Annotated метаданные (используемые как field_name: Annotated[int, SomeMetadata] ) может изменить или переопределить сгенерированную схему, реализовав __get_pydantic_core_schema__ . Этот метод получает два позиционных аргумента:

  1. Аннотация типа, соответствующая этому типу (так что в случае TheType[T][int] это будет TheType[int] ).
  2. Обработчик/обратный вызов для вызова следующего реализатора __get_pydantic_core_schema__ .

Система обработчиков работает так же, как и валидаторы mode='wrap' . В этом случае входными данными является тип, а выходными — core_schema .

Вот пример пользовательского типа, который переопределяет сгенерированную core_schema :

from dataclasses import dataclass
from typing import Any, Dict, List, Type

from pydantic_core import core_schema

from pydantic import BaseModel, GetCoreSchemaHandler


@dataclass
class CompressedString:
    dictionary: Dict[int, str]
    text: List[int]

    def build(self) -> str:
        return ' '.join([self.dictionary[key] for key in self.text])

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        assert source is CompressedString
        return core_schema.no_info_after_validator_function(
            cls._validate,
            core_schema.str_schema(),
            serialization=core_schema.plain_serializer_function_ser_schema(
                cls._serialize,
                info_arg=False,
                return_schema=core_schema.str_schema(),
            ),
        )

    @staticmethod
    def _validate(value: str) -> 'CompressedString':
        inverse_dictionary: Dict[str, int] = {}
        text: List[int] = []
        for word in value.split(' '):
            if word not in inverse_dictionary:
                inverse_dictionary[word] = len(inverse_dictionary)
            text.append(inverse_dictionary[word])
        return CompressedString(
            {v: k for k, v in inverse_dictionary.items()}, text
        )

    @staticmethod
    def _serialize(value: 'CompressedString') -> str:
        return value.build()


class MyModel(BaseModel):
    value: CompressedString


print(MyModel.model_json_schema())
"""
{
    'properties': {'value': {'title': 'Value', 'type': 'string'}},
    'required': ['value'],
    'title': 'MyModel',
    'type': 'object',
}
"""
print(MyModel(value='fox fox fox dog fox'))
"""
value = CompressedString(dictionary={0: 'fox', 1: 'dog'}, text=[0, 0, 0, 1, 0])
"""

print(MyModel(value='fox fox fox dog fox').model_dump(mode='json'))
#> {'value': 'fox fox fox dog fox'}

Поскольку Pydantic не знает, как сгенерировать схему для CompressedString , если вы вызовете handler(source) в его методе __get_pydantic_core_schema__ вы получите pydantic.errors.PydanticSchemaGenerationError ошибка. Это справедливо для большинства пользовательских типов, поэтому вам почти никогда не понадобится вызывать handler для пользовательских типов.

Процесс для Annotated метаданных во многом такой же, за исключением того, что вы обычно можете вызвать handler , чтобы дескриптор Pydantic сгенерировал схему.

from dataclasses import dataclass
from typing import Any, Sequence, Type

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler, ValidationError


@dataclass
class RestrictCharacters:
    alphabet: Sequence[str]

    def __get_pydantic_core_schema__(
        self, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        if not self.alphabet:
            raise ValueError('Alphabet may not be empty')
        schema = handler(
            source
        )  # get the CoreSchema from the type / inner constraints
        if schema['type'] != 'str':
            raise TypeError('RestrictCharacters can only be applied to strings')
        return core_schema.no_info_after_validator_function(
            self.validate,
            schema,
        )

    def validate(self, value: str) -> str:
        if any(c not in self.alphabet for c in value):
            raise ValueError(
                f'{value!r} is not restricted to {self.alphabet!r}'
            )
        return value


class MyModel(BaseModel):
    value: Annotated[str, RestrictCharacters('ABC')]


print(MyModel.model_json_schema())
"""
{
    'properties': {'value': {'title': 'Value', 'type': 'string'}},
    'required': ['value'],
    'title': 'MyModel',
    'type': 'object',
}
"""
print(MyModel(value='CBA'))
#> value='CBA'

try:
    MyModel(value='XYZ')
except ValidationError as e:
    print(e)
    """
    1 validation error for MyModel
    value
      Value error, 'XYZ' is not restricted to 'ABC' [type=value_error, input_value='XYZ', input_type=str]
    """

До сих пор мы обертывали схему, но если вы просто хотите изменить ее или проигнорировать , вы тоже можете это сделать.

Чтобы изменить схему, сначала вызовите обработчик, а затем измените результат:

from typing import Any, Type

from pydantic_core import ValidationError, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


class SmallString:
    def __get_pydantic_core_schema__(
        self,
        source: Type[Any],
        handler: GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        schema = handler(source)
        assert schema['type'] == 'str'
        schema['max_length'] = 10  # modify in place
        return schema


class MyModel(BaseModel):
    value: Annotated[str, SmallString()]


try:
    MyModel(value='too long!!!!!')
except ValidationError as e:
    print(e)
    """
    1 validation error for MyModel
    value
      String should have at most 10 characters [type=string_too_long, input_value='too long!!!!!', input_type=str]
    """

!!! Совет Обратите внимание, что вы должны вернуть схему, даже если вы просто изменяете ее на месте.

Чтобы полностью переопределить схему, не вызывайте обработчик и не возвращайте собственную CoreSchema :

from typing import Any, Type

from pydantic_core import ValidationError, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


class AllowAnySubclass:
    def __get_pydantic_core_schema__(
        self, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        # we can't call handler since it will fail for arbitrary types
        def validate(value: Any) -> Any:
            if not isinstance(value, source):
                raise ValueError(
                    f'Expected an instance of {source}, got an instance of {type(value)}'
                )

        return core_schema.no_info_plain_validator_function(validate)


class Foo:
    pass


class Model(BaseModel):
    f: Annotated[Foo, AllowAnySubclass()]


print(Model(f=Foo()))
#> f=None


class NotFoo:
    pass


try:
    Model(f=NotFoo())
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    f
      Value error, Expected an instance of <class '__main__.Foo'>, got an instance of <class '__main__.NotFoo'> [type=value_error, input_value=<__main__.NotFoo object at 0x0123456789ab>, input_type=NotFoo]
    """

Как видно выше, аннотирование поля типом BaseModel можно использовать для изменения или переопределения сгенерированной схемы json. Однако если вы хотите воспользоваться преимуществом хранения метаданных через Annotated , но не хотите переопределять сгенерированную схему JSON, вы можете использовать следующий подход с неактивной версией __get_pydantic_core_schema__ , реализованной в классе метаданных:

from typing import Type

from pydantic_core import CoreSchema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


class Metadata(BaseModel):
    foo: str = 'metadata!'
    bar: int = 100

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Type[BaseModel], handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        if cls is not source_type:
            return handler(source_type)
        return super().__get_pydantic_core_schema__(source_type, handler)


class Model(BaseModel):
    state: Annotated[int, Metadata()]


m = Model.model_validate({'state': 2})
print(repr(m))
#> Model(state=2)
print(m.model_fields)
"""
{
    'state': FieldInfo(
        annotation=int,
        required=True,
        metadata=[Metadata(foo='metadata!', bar=100)],
    )
}
"""

Реализация __get_pydantic_json_schema__

Вы также можете реализовать __get_pydantic_json_schema__ чтобы изменить или переопределить сгенерированную схему json. Изменение этого метода влияет только на схему JSON — оно не влияет на базовую схему, которая используется для проверки и сериализации.

Вот пример изменения сгенерированной схемы JSON:

import json
from typing import Any

from pydantic_core import core_schema as cs

from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler, TypeAdapter
from pydantic.json_schema import JsonSchemaValue


class Person:
    name: str
    age: int

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> cs.CoreSchema:
        return cs.typed_dict_schema(
            {
                'name': cs.typed_dict_field(cs.str_schema()),
                'age': cs.typed_dict_field(cs.int_schema()),
            },
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, core_schema: cs.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        json_schema = handler(core_schema)
        json_schema = handler.resolve_ref_schema(json_schema)
        json_schema['examples'] = [
            {
                'name': 'John Doe',
                'age': 25,
            }
        ]
        json_schema['title'] = 'Person'
        return json_schema


print(json.dumps(TypeAdapter(Person).json_schema(), indent=2))
"""
{
  "examples": [
    {
      "age": 25,
      "name": "John Doe"
    }
  ],
  "properties": {
    "name": {
      "title": "Name",
      "type": "string"
    },
    "age": {
      "title": "Age",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}
"""

Использование field_title_generator

Параметр field_title_generator можно использовать для программного создания заголовка поля на основе его имени и информации. Это похоже на уровень поля field_title_generator , но опция ConfigDict будет применена ко всем полям класса.

См. следующий пример:

import json

from pydantic import BaseModel, ConfigDict


class Person(BaseModel):
    model_config = ConfigDict(
        field_title_generator=lambda field_name, field_info: field_name.upper()
    )
    name: str
    age: int


print(json.dumps(Person.model_json_schema(), indent=2))
"""
{
  "properties": {
    "name": {
      "title": "NAME",
      "type": "string"
    },
    "age": {
      "title": "AGE",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}
"""

Использование model_title_generator

Параметр конфигурации model_title_generator аналогичен параметру field_title_generator , но он применяется к заголовку самой модели и принимает класс модели в качестве входных данных.

См. следующий пример:

import json
from typing import Type

from pydantic import BaseModel, ConfigDict


def make_title(model: Type) -> str:
    return f'Title-{model.__name__}'


class Person(BaseModel):
    model_config = ConfigDict(model_title_generator=make_title)
    name: str
    age: int


print(json.dumps(Person.model_json_schema(), indent=2))
"""
{
  "properties": {
    "name": {
      "title": "Name",
      "type": "string"
    },
    "age": {
      "title": "Age",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Title-Person",
  "type": "object"
}
"""

Типы схем JSON

Типы, типы настраиваемых полей и ограничения (например, max_length ) сопоставляются с соответствующими форматами спецификаций в следующем порядке приоритета (при наличии эквивалента):

  1. Ядро схемы JSON
  2. Проверка схемы JSON
  3. Типы данных OpenAPI
  4. Поле JSON стандартного format используется для определения расширений Pydantic для более сложных подтипов string .

Сопоставление схемы полей из Python или Pydantic в схему JSON выполняется следующим образом:

Генерация схемы верхнего уровня

Вы также можете создать схему JSON верхнего уровня, которая включает только список моделей и связанных с ними данных. подмодели в его ` $defs`: ```py output="json" import json from pydantic import BaseModel from pydantic.json_schema import models_json_schema class Foo(BaseModel): a: str = None class Model(BaseModel): b: Класс Foo Bar(BaseModel): c: int _, top_level_schema = models_json_schema( [(Model, 'validation'), (Bar, 'validation')], title='My Schema' ) print(json.dumps(top_level_schema, indent=2)) """ { "$ defs": { "Бар": { "характеристики": { "с": { "титул": "С", "тип": "целое число" } }, "необходимый": [ "с" ], "title": "Бар", "тип": "объект" }, "Фу": { "характеристики": { "а": { «по умолчанию»: ноль, "титул": "А", "тип": "строка" } }, "title": "Фу", "тип": "объект" }, "Модель": { "характеристики": { "б": { " $defs": { "FooBar": { "properties": { "count": { "title": "Count", "type": "integer" }, "size": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Size" } }, "required": [ "count" ], "title": "FooBar", "type": "object" }, "Gender": { "enum": [ "male", "female", "other", "not_given" ], "title": "Gender", "type": "string" } }, "description": "This is the description of the main model", "properties": { "foo_bar": { "$ защита/Фу" } }, "необходимый": [ "б" ], "title": "Модель", "тип": "объект" } }, "title": "Моя схема" } """

## Customizing the JSON Schema Generation Process

??? api "API Documentation"
    [`pydantic.json_schema`][pydantic.json_schema.GenerateJsonSchema]<br>

If you need custom schema generation, you can use a `schema_generator`, modifying the
[`GenerateJsonSchema`][pydantic.json_schema.GenerateJsonSchema] class as necessary for your application.

The various methods that can be used to produce JSON schema accept a keyword argument `schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema`, and you can pass your custom subclass to these methods in order to use your own approach to generating JSON schema.

`GenerateJsonSchema` implements the translation of a type's `pydantic-core` schema into a JSON schema.
By design, this class breaks the JSON schema generation process into smaller methods that can be easily overridden in
subclasses to modify the "global" approach to generating JSON schema.

```

py from pydantic import BaseModel from pydantic.json_schema import GenerateJsonSchema

class MyGenerateJsonSchema(GenerateJsonSchema):
    def generate(self, schema, mode='validation'):
        json_schema = super().generate(schema, mode=mode)
        json_schema['title'] = 'Customize title'
        json_schema['$schema'] = self.schema_dialect
        return json_schema


class MyModel(BaseModel):
    x: int


print(MyModel.model_json_schema(schema_generator=MyGenerateJsonSchema))
"""
{
    'properties': {'x': {'title': 'X', 'type': 'integer'}},
    'required': ['x'],
    'title': 'Customize title',
    'type': 'object',
    '$schema': 'https://json-schema.org/draft/2020-12/schema',
}
"""

Ниже приведен подход, который вы можете использовать для исключения из схемы любых полей, которые не имеют допустимых схем JSON:

from typing import Callable

from pydantic_core import PydanticOmit, core_schema

from pydantic import BaseModel
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue


class MyGenerateJsonSchema(GenerateJsonSchema):
    def handle_invalid_for_json_schema(
        self, schema: core_schema.CoreSchema, error_info: str
    ) -> JsonSchemaValue:
        raise PydanticOmit


def example_callable():
    return 1


class Example(BaseModel):
    name: str = 'example'
    function: Callable = example_callable


instance_example = Example()

validation_schema = instance_example.model_json_schema(
    schema_generator=MyGenerateJsonSchema, mode='validation'
)
print(validation_schema)
"""
{
    'properties': {
        'name': {'default': 'example', 'title': 'Name', 'type': 'string'}
    },
    'title': 'Example',
    'type': 'object',
}
"""

Настройка $ref`s in JSON Schema The format of `$ref s можно изменить, вызвав model_json_schema()

или model_dump_json() с аргументом ключевого слова ref_template . Определения всегда хранятся под ключом $defs`, but a specified prefix can be used for the references. This is useful if you need to extend or modify the JSON schema default definitions location. For example, with OpenAPI: ```py output="json" import json from pydantic import BaseModel from pydantic.type_adapter import TypeAdapter class Foo(BaseModel): a: int class Model(BaseModel): a: Foo adapter = TypeAdapter(Model) print( json.dumps( adapter.json_schema(ref_template='#/components/schemas/{model}'), indent=2, ) ) """ { "$defs": { "Foo": { "properties": { "a": { "title": "A", "type": "integer" } }, "required": [ "a" ], "title": "Foo", "type": "object" } }, "properties": { "a": { "$ref": "#/components/schemas/Foo" } }, "required": [ "a" ], "title": "Model", "type": "object" } """ ``` ## Miscellaneous Notes on JSON Schema Generation * The JSON schema for `Optional` fields indicates that the value `null` is allowed. * The `Decimal` type is exposed in JSON schema (and serialized) as a string. * Since the `namedtuple` type doesn't exist in JSON, a model's JSON schema does not preserve `namedtuple`s as `namedtuple`s. * Sub-models used are added to the `$defs Атрибут JSON и ссылка на него согласно спецификации.

  • Подмодели с модификациями (через класс Field ), такими как настраиваемый заголовок, описание или значение по умолчанию, рекурсивно включаются, а не ссылаются на них.
  • description моделей берется либо из строки документации класса, либо description аргумента класса Field .
  • Схема создается по умолчанию с использованием псевдонимов в качестве ключей, но вместо этого ее можно создать с использованием имен свойств модели, вызвав model_json_schema() или [model_dump_json()][pydantic.main.BaseModel. model_dump_json] с аргументом ключевого слова by_alias=False .

本文总阅读量