콘텐츠로 이동

JSON Schema

??? api "API 문서" pydantic.json_schema

Pydantic을 사용하면 모델에서 JSON 스키마를 자동으로 생성하고 사용자 정의할 수 있습니다. 생성된 JSON 스키마는 다음 사양을 준수합니다.

JSON 스키마 생성

다음 함수를 사용하여 JSON 스키마를 생성합니다.

!!! note 이러한 메서드를 모델 또는 적응형 유형의 인스턴스를 각각 직렬화하는 BaseModel.model_dump_jsonTypeAdapter.dump_json과 혼동하지 마십시오. . 이러한 메서드는 JSON 문자열을 반환합니다. 이에 비해 BaseModel.model_json_schemaTypeAdapter.json_schema는 각각 모델 또는 적응형 유형의 JSON 스키마를 나타내는 jsonable dict를 반환합니다.

!!! "JSON 스키마의 "jsonable" 특성에 대해" 참고하세요. model_json_schema 결과의 "jsonable" 특성과 관련하여 다음을 호출합니다. 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.

다음은 BaseModel 에서 JSON 스키마를 생성하는 예입니다.

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 구성

model_json_schemaTypeAdapter.json_schema 메서드의 mode 매개변수를 통해 JSON 스키마 생성 모드를 지정합니다. 기본적으로 모드는 모델의 유효성 검사 스키마에 해당하는 JSON 스키마를 생성하는 'validation' 으로 설정됩니다.

JsonSchemaModemode 매개변수에 사용 가능한 옵션을 나타내는 유형 별칭입니다.

  • '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 주석
  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',
}
"""

typing.Annotated를 통해 Field 생성자를 통해 JSON 스키마 수정 사항을 지정할 수도 있습니다.

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"
}
"""

모델 수준 사용자 정의

model config를 사용하여 모델에서 JSON 스키마 생성을 맞춤설정할 수도 있습니다. 특히 다음 구성 옵션이 관련됩니다.

json_schema_extra 사용

json_schema_extra 옵션을 사용하면 필드 수준 또는 모델 수준 에서 JSON 스키마에 추가 정보를 추가할 수 있습니다. json_schema_extradict 또는 Callable 을 전달할 수 있습니다.

json_schema_extra dict 와 함께 사용하기

json_schema_extradict 전달하여 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"
}
"""

Callable 과 함께 json_schema_extra 사용

Callablejson_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

v2.9부터 Pydantic은 주석이 달린 유형의 json_schema_extra 사전을 병합합니다. 이 패턴은 이전 재정의 동작보다 병합에 대한 추가 접근 방식을 제공합니다. 이는 여러 유형에 걸쳐 json 스키마 추가 정보를 재사용하는 경우에 매우 유용할 수 있습니다.

BaseModelTypeAdapter 인스턴스 간의 json_schema_extra 병합 동작에서 의도하지 않은 차이를 해결하므로 이 변경 사항을 주로 버그 수정으로 간주했습니다. 자세한 내용은 이 문제를 참조하세요.

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 사용하여 키 추가 또는 제거, 값 수정 등 더 중요한 변경을 수행할 수 있습니다. Pydantic v2.8 및 이전 버전에 있는 json_schema_extra 재정의 동작을 모방하려는 경우 이 패턴을 사용할 수 있습니다.

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

!!! 팁 사용자 정의 유형에 대해 __get_pydantic_json_schema__ 구현하는 것 보다 WithJsonSchema]를 사용하는 것이 더 간단하고 오류가 덜 발생하기 때문에 선호됩니다.

WithJsonSchema 주석을 사용하면 유형 자체에 __get_pydantic_core_schema__ 또는 __get_pydantic_json_schema__ 구현할 필요 없이 지정된 유형에 대해 생성된 (기본) JSON 스키마를 재정의할 수 있습니다.

이는 Callable 과 같은 JSON 스키마를 생성할 때 오류를 발생시키는 유형이나 is-instance 코어 스키마가 있는 유형에 대해 JSON 스키마를 설정하는 방법을 제공합니다.

예를 들어, 다음 예제에서 [PlainValidator][pydantic.function_validators.PlainValidator]를 사용하면 JSON 스키마를 생성할 때 [PlainValidator][pydantic.function_validators.PlainValidator]가 Callable 이기 때문에 오류가 발생합니다. 그러나 WithJsonSchema 주석을 사용하면 사용자 정의 MyInt 유형에 대해 생성된 JSON 스키마를 재정의할 수 있습니다.

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은 [PlainValidator][pydantic.function_validators.PlainValidator]와 같은 유형에 대한 JSON 스키마 생성에 대한 기본 지원을 추가할 가능성이 있지만, WithJsonSchema 주석 다른 사용자 정의 유형에는 여전히 유용합니다.

SkipJsonSchema 주석

??? 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 에 대한 스키마를 생성하는 방법을 모르기 때문에 __get_pydantic_core_schema__ 메소드에서 handler(source) 호출하면 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]
    """

!!! Tip 단순히 스키마를 변경하는 경우에도 스키마를 반환 해야 합니다 .

스키마를 완전히 재정의하려면 핸들러를 호출하지 말고 자체 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. 표준 format JSON 필드는 더 복잡한 string 하위 유형에 대한 Pydantic 확장을 정의하는 데 사용됩니다.

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='내 스키마' ) print(json.dumps(top_level_schema, indent=2)) """ { "$ defs": { "술집": { "속성": { "c": { "제목": "C", "유형": "정수" } }, "필수의": [ "기음" ], "title": "바", "유형": "객체" }, "푸": { "속성": { "a": { "기본값": null, "제목": "A", "유형": "문자열" } }, "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": { "$ 정의/Foo" } }, "필수의": [ "비" ], "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 은 클래스의 독스트링이나 Field 클래스에 대한 인수 description 에서 가져옵니다.
  • 스키마는 기본적으로 별칭을 키로 사용하여 생성되지만, 대신 model_json_schema() 또는 [model_dump_json()][pydantic.main.BaseModel을 호출하여 모델 속성 이름을 사용하여 생성할 수 있습니다. model_dump_json] by_alias=False 키워드 인수로 사용하세요.

本文总阅读量