Zum Inhalt

JSON Schema

??? API „API-Dokumentation“ pydantic.json_schema

Pydantic ermöglicht die automatische Erstellung und Anpassung von JSON-Schemas aus Modellen. Die generierten JSON-Schemas entsprechen den folgenden Spezifikationen:

Generieren eines JSON-Schemas

Verwenden Sie die folgenden Funktionen, um ein JSON-Schema zu generieren:

!!! Hinweis: Diese Methoden dürfen nicht mit BaseModel.model_dump_json und TypeAdapter.dump_json verwechselt werden, die Instanzen des Modells bzw. des angepassten Typs serialisieren . Diese Methoden geben JSON-Strings zurück. Im Vergleich dazu geben BaseModel.model_json_schema und TypeAdapter.json_schema ein jsonable-Dikt zurück, das das JSON-Schema des Modells bzw. des angepassten Typs darstellt.

!!! Hinweis „zur „jsonable“-Natur des JSON-Schemas“ In Bezug auf die „jsonable“-Natur der model_json_schema-Ergebnisse, Aufruf json.dumps(m.model_json_schema()) Bei einigen BaseModel gibt m eine gültige JSON-Zeichenfolge zurück. Ähnliches gilt für TypeAdapter.json_schema, Aufruf json.dumps(TypeAdapter(<some_type>).json_schema()) gibt eine gültige JSON-Zeichenfolge zurück.

!!! Tipp Pydantic bietet Unterstützung für beides:

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.

Hier ist ein Beispiel für die Generierung eines JSON-Schemas aus einem 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"
    }
  ]
}
"""

Konfigurieren des JsonSchemaMode

Geben Sie den Modus der JSON-Schemagenerierung über den mode in den Methoden model_json_schema und TypeAdapter.json_schema an. Standardmäßig ist der Modus auf 'validation' eingestellt, wodurch ein JSON-Schema erstellt wird, das dem Validierungsschema des Modells entspricht.

Der JsonSchemaMode ist ein Typalias, der die verfügbaren Optionen für den mode darstellt:

  • 'validation'
  • 'serialization'

Hier ist ein Beispiel dafür, wie der mode angegeben wird und wie er sich auf das generierte JSON-Schema auswirkt:

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

Anpassen des JSON-Schemas

Das generierte JSON-Schema kann sowohl auf Feldebene als auch auf Modellebene angepasst werden über:

  1. Anpassung auf Feldebene mit dem Konstruktor Field.
  2. Anpassung auf Modellebene mit model_config

Sowohl auf Feld- als auch auf Modellebene können Sie die Option json_schema_extra verwenden, um zusätzliche Informationen zum JSON-Schema hinzuzufügen. Der Abschnitt „Verwenden von json_schema_extra unten enthält weitere Details zu dieser Option.

Für benutzerdefinierte Typen bietet Pydantic weitere Tools zum Anpassen der JSON-Schema-Generierung:

  1. WithJsonSchema annotation
  2. SkipJsonSchema -Annotation
  3. Implementierung von __get_pydantic_core_schema__
  4. Implementierung von __get_pydantic_json_schema__

Anpassung auf Feldebene

Optional kann die Funktion Field verwendet werden, um zusätzliche Informationen über das Feld und Validierungen bereitzustellen.

Einige Feldparameter werden ausschließlich zum Anpassen des generierten JSON-Schemas verwendet:

  • title : Der Titel des Feldes.
  • description : Die Beschreibung des Feldes.
  • examples : Die Beispiele des Fachgebiets.
  • json_schema_extra : Zusätzliche JSON-Schema-Eigenschaften, die dem Feld hinzugefügt werden sollen.
  • field_title_generator : Eine Funktion, die den Titel des Felds basierend auf seinem Namen und seinen Informationen programmgesteuert festlegt.

Hier ist ein Beispiel:

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

Nicht erzwungene Field

Wenn Pydantic Einschränkungen findet, die nicht durchgesetzt werden, wird ein Fehler ausgegeben. Wenn Sie erzwingen möchten, dass die Einschränkung im Schema erscheint, auch wenn sie beim Parsen nicht überprüft wird, können Sie variadische Argumente für Field mit dem Rohschema-Attributnamen verwenden:

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

Sie können JSON-Schemaänderungen auch über den Konstruktor Field über typing.Annotated angeben:

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

Generierung programmatischer Feldtitel

Der Parameter field_title_generator kann verwendet werden, um den Titel für ein Feld basierend auf seinem Namen und seinen Informationen programmgesteuert zu generieren.

Sehen Sie sich das folgende Beispiel an:

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

Anpassung auf Modellebene

Sie können auch model config verwenden, um die JSON-Schemagenerierung für ein Modell anzupassen. Im Einzelnen sind die folgenden Konfigurationsoptionen relevant:

Verwendung von json_schema_extra

Mit der Option json_schema_extra können dem JSON-Schema zusätzliche Informationen hinzugefügt werden, entweder auf Feldebene oder auf Modellebene . Sie können ein dict oder ein Callable an json_schema_extra übergeben.

Verwendung von json_schema_extra mit einem dict

Sie können ein dict an json_schema_extra übergeben, um zusätzliche Informationen zum JSON-Schema hinzuzufügen:

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

Verwendung von json_schema_extra mit einem Callable

Sie können ein Callable an json_schema_extra übergeben, um das JSON-Schema mit einer Funktion zu ändern:

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 zusammenführen

Ab Version 2.9 führt Pydantic json_schema_extra -Wörterbücher aus annotierten Typen zusammen. Dieses Muster bietet einen additiveren Ansatz für die Zusammenführung im Vergleich zum vorherigen Überschreibungsverhalten. Dies kann sehr hilfreich sein, wenn zusätzliche JSON-Schemainformationen für mehrere Typen wiederverwendet werden.

Wir betrachteten diese Änderung größtenteils als Fehlerbehebung, da sie unbeabsichtigte Unterschiede im Zusammenführungsverhalten json_schema_extra zwischen BaseModel und TypeAdapter Instanzen behebt – weitere Einzelheiten finden Sie in diesem Problem .

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

Wenn Sie möchten, dass die letzte Ihrer json_schema_extra -Spezifikationen die vorherigen überschreibt, können Sie ein callable verwenden, um bedeutendere Änderungen vorzunehmen, einschließlich dem Hinzufügen oder Entfernen von Schlüsseln oder dem Ändern von Werten. Sie können dieses Muster verwenden, wenn Sie das Verhalten der json_schema_extra Überschreibungen nachahmen möchten, die in Pydantic v2.8 und früher vorhanden sind:

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-Dokumentation" pydantic.json_schema.WithJsonSchema

!!! Tipp Die Verwendung von WithJsonSchema] wird der Implementierung __get_pydantic_json_schema__ für benutzerdefinierte Typen vorgezogen, da dies einfacher und weniger fehleranfällig ist.

Die Annotation WithJsonSchema kann verwendet werden, um das generierte (Basis-)JSON-Schema für einen bestimmten Typ zu überschreiben, ohne dass __get_pydantic_core_schema__ oder __get_pydantic_json_schema__ für den Typ selbst implementiert werden muss.

Dies bietet eine Möglichkeit, ein JSON-Schema für Typen festzulegen, die andernfalls beim Erstellen eines JSON-Schemas Fehler auslösen würden, z. B. Callable , oder für Typen, die über ein is-instance-Kernschema verfügen.

Beispielsweise würde die Verwendung eines [PlainValidator][pydantic.Functional_validators.PlainValidator] im folgenden Beispiel andernfalls einen Fehler beim Erstellen eines JSON-Schemas auslösen, da der [PlainValidator][pydantic.Functional_validators.PlainValidator] ein Callable ist. Mithilfe der Annotation WithJsonSchema können wir jedoch das generierte JSON-Schema für den benutzerdefinierten MyInt Typ überschreiben:

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

!!! Hinweis Wie in dieser Ausgabe besprochen, ist es wahrscheinlich, dass Pydantic in Zukunft integrierte Unterstützung für die JSON-Schemagenerierung für Typen wie [PlainValidator][pydantic.Functional_validators.PlainValidator], aber die Annotation WithJsonSchema hinzufügen wird wird weiterhin für andere benutzerdefinierte Typen nützlich sein.

SkipJsonSchema -Annotation

??? API „API-Dokumentation“ pydantic.json_schema.SkipJsonSchema

Die Annotation SkipJsonSchema kann verwendet werden, um ein einschließendes Feld (oder einen Teil der Spezifikationen eines Felds) aus dem generierten JSON-Schema zu überspringen. Weitere Informationen finden Sie in den API-Dokumenten.

Implementierung von __get_pydantic_core_schema__

Benutzerdefinierte Typen (verwendet als field_name: TheType oder field_name: Annotated[TheType, ...] ) sowie Annotated Metadaten (verwendet als field_name: Annotated[int, SomeMetadata] ) kann das generierte Schema durch Implementierung __get_pydantic_core_schema__ ändern oder überschreiben. Diese Methode empfängt zwei Positionsargumente:

  1. Die Typanmerkung, die diesem Typ entspricht (im Fall von TheType[T][int] wäre es also TheType[int] ).
  2. Ein Handler/Rückruf zum Aufrufen des nächsten Implementierers von __get_pydantic_core_schema__ .

Das Handlersystem funktioniert genau wie mode='wrap' validators . In diesem Fall ist die Eingabe der Typ und die Ausgabe ein core_schema .

Hier ist ein Beispiel für einen benutzerdefinierten Typ, der das generierte core_schema überschreibt :

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

Da Pydantic nicht weiß, wie ein Schema für CompressedString generiert wird, erhalten Sie beim Aufrufen von handler(source) in seiner Methode __get_pydantic_core_schema__ ein pydantic.errors.PydanticSchemaGenerationError Fehler. Dies ist bei den meisten benutzerdefinierten Typen der Fall, sodass Sie handler für benutzerdefinierte Typen fast nie aufrufen möchten.

Der Prozess für Annotated Metadaten ist weitgehend derselbe, mit der Ausnahme, dass Sie im Allgemeinen handler aufrufen können, damit Pydantic das Schema generiert.

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

Bisher haben wir das Schema umschlossen, aber wenn Sie es nur ändern oder ignorieren möchten, können Sie das auch tun.

Um das Schema zu ändern, rufen Sie zuerst den Handler auf und ändern Sie dann das Ergebnis:

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

!!! Tipp Beachten Sie, dass Sie ein Schema zurückgeben müssen , auch wenn Sie es nur an Ort und Stelle mutieren.

Um das Schema vollständig zu überschreiben, rufen Sie den Handler nicht auf und geben Sie Ihr eigenes CoreSchema zurück:

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

Wie oben gezeigt, kann das Annotieren eines Felds mit einem BaseModel Typ verwendet werden, um das generierte JSON-Schema zu ändern oder zu überschreiben. Wenn Sie jedoch die Vorteile der Speicherung von Metadaten über Annotated nutzen, aber das generierte JSON-Schema nicht überschreiben möchten, können Sie den folgenden Ansatz mit einer No-Op-Version von __get_pydantic_core_schema__ verwenden, die in der Metadatenklasse implementiert ist:

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)],
    )
}
"""

Implementierung von __get_pydantic_json_schema__

Sie können auch __get_pydantic_json_schema__ implementieren, um das generierte JSON-Schema zu ändern oder zu überschreiben. Das Ändern dieser Methode wirkt sich nur auf das JSON-Schema aus – nicht auf das Kernschema, das für die Validierung und Serialisierung verwendet wird.

Hier ist ein Beispiel für die Änderung des generierten JSON-Schemas:

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

Verwenden von field_title_generator

Der Parameter field_title_generator kann verwendet werden, um den Titel für ein Feld basierend auf seinem Namen und seinen Informationen programmgesteuert zu generieren. Dies ähnelt dem field_title_generator auf Feldebene, die Option ConfigDict wird jedoch auf alle Felder der Klasse angewendet.

Sehen Sie sich das folgende Beispiel an:

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

Verwendung von model_title_generator

Die Konfigurationsoption model_title_generator ähnelt der Option field_title_generator , gilt jedoch für den Titel des Modells selbst und akzeptiert die Modellklasse als Eingabe.

Sehen Sie sich das folgende Beispiel an:

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-Schematypen

Typen, benutzerdefinierte Feldtypen und Einschränkungen (wie max_length ) werden den entsprechenden Spezifikationsformaten in der folgenden Prioritätsreihenfolge zugeordnet (sofern ein Äquivalent verfügbar ist):

  1. JSON-Schemakern
  2. JSON-Schemavalidierung
  3. OpenAPI-Datentypen
  4. Das JSON-Feld format wird verwendet, um Pydantic-Erweiterungen für komplexere string -Untertypen zu definieren.

Die Feldschemazuordnung von Python oder Pydantic zum JSON-Schema erfolgt wie folgt:

Schemagenerierung auf oberster Ebene

Sie können auch ein JSON-Schema der obersten Ebene generieren, das nur eine Liste von Modellen und zugehörigen Modellen enthält Untermodelle in seinen ` $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-Klasse Bar(BaseModel): c: int _, top_level_schema = models_json_schema( [(Model, 'validation'), (Bar, 'validation')], title='Mein Schema' ) print(json.dumps(top_level_schema, indent=2)) """ { "$ defs": { "Bar": { "Eigenschaften": { "C": { „title“: „C“, „Typ“: „Ganzzahl“ } }, "erforderlich": [ "C" ], „title“: „Bar“, „Typ“: „Objekt“ }, „Foo“: { "Eigenschaften": { "A": { „Standard“: null, „Titel“: „A“, „Typ“: „Zeichenfolge“ } }, „title“: „Foo“, „Typ“: „Objekt“ }, "Modell": { "Eigenschaften": { "B": { " $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": { "$ defs/Foo" } }, "erforderlich": [ "B" ], „title“: „Modell“, „Typ“: „Objekt“ } }, „title“: „Mein Schema“ } „““

## 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',
}
"""

Im Folgenden finden Sie einen Ansatz, mit dem Sie alle Felder aus dem Schema ausschließen können, die keine gültigen JSON-Schemas haben:

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

Anpassen der $ref`s in JSON Schema The format of `$ref s können durch Aufrufen von model_json_schema() geändert werden.

oder model_dump_json() mit dem Schlüsselwortargument ref_template . Die Definitionen werden immer unter dem Schlüssel gespeichert $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-Attribut und referenziert gemäß der Spezifikation.

  • Untermodelle mit Änderungen (über die Field -Klasse) wie einem benutzerdefinierten Titel, einer Beschreibung oder einem Standardwert werden rekursiv eingebunden statt referenziert.
  • Die description für Modelle wird entweder der Dokumentzeichenfolge der Klasse oder der description der Field Klasse entnommen.
  • Das Schema wird standardmäßig mit Aliasen als Schlüssel generiert, kann aber stattdessen auch mit Modelleigenschaftennamen generiert werden, indem model_json_schema() oder [model_dump_json()][pydantic.main.BaseModel aufgerufen wird. model_dump_json] mit dem Schlüsselwortargument by_alias=False .

本文总阅读量