Ga naar inhoud

JSON Schema

??? api "API-documentatie" pydantic.json_schema

Pydantic maakt het automatisch maken en aanpassen van JSON-schema's op basis van modellen mogelijk. De gegenereerde JSON-schema's voldoen aan de volgende specificaties:

JSON-schema genereren

Gebruik de volgende functies om JSON-schema te genereren:

!!! opmerking Deze methoden moeten niet worden verward met BaseModel.model_dump_json en TypeAdapter.dump_json, die respectievelijk instanties van het model of het aangepaste type serialiseren . Deze methoden retourneren JSON-tekenreeksen. Ter vergelijking: BaseModel.model_json_schema en TypeAdapter.json_schema retourneren een jsonable dictaat dat respectievelijk het JSON-schema van het model of het aangepaste type vertegenwoordigt.

!!! opmerking "over de "jsonable" aard van JSON-schema" Met betrekking tot de "jsonable" aard van de model_json_schema resultaten, aanroepen json.dumps(m.model_json_schema()) op sommige BaseModel retourneert m een geldige JSON-tekenreeks. Op dezelfde manier geldt voor TypeAdapter.json_schema het aanroepen van json.dumps(TypeAdapter(<some_type>).json_schema()) retourneert een geldige JSON-tekenreeks.

!!! tip Pydantic biedt ondersteuning voor zowel:

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 is een voorbeeld van het genereren van een JSON-schema op basis van een 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"
    }
  ]
}
"""

De JsonSchemaMode configureren

Geef de modus voor het genereren van JSON-schema's op via de mode in de methoden model_json_schema en TypeAdapter.json_schema. Standaard is de modus ingesteld op 'validation' , wat een JSON-schema produceert dat overeenkomt met het validatieschema van het model.

De JsonSchemaMode is een typealias die de beschikbare opties voor de mode vertegenwoordigt:

  • 'validation'
  • 'serialization'

Hier is een voorbeeld van hoe u de mode parameter opgeeft en hoe deze het gegenereerde JSON-schema beïnvloedt:

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-schema aanpassen

Het gegenereerde JSON-schema kan zowel op veldniveau als op modelniveau worden aangepast via:

  1. Aanpassing op veldniveau met de constructor Field.
  2. Aanpassing op modelniveau met model_config

Op zowel veld- als modelniveau kunt u de optie json_schema_extra gebruiken om extra informatie aan het JSON-schema toe te voegen. In het gedeelte json_schema_extra gebruiken hieronder vindt u meer informatie over deze optie.

Voor aangepaste typen biedt Pydantic andere tools voor het aanpassen van het genereren van JSON-schema's:

  1. WithJsonSchema annotatie
  2. SkipJsonSchema annotatie
  3. Implementatie van __get_pydantic_core_schema__
  4. Implementatie van __get_pydantic_json_schema__

Aanpassing op veldniveau

Optioneel kan de functie Field worden gebruikt om extra informatie over het veld en validaties te geven.

Sommige veldparameters worden uitsluitend gebruikt om het gegenereerde JSON-schema aan te passen:

  • title : De titel van het veld.
  • description : De beschrijving van het veld.
  • examples : De voorbeelden van het veld.
  • json_schema_extra : Extra JSON Schema-eigenschappen die aan het veld moeten worden toegevoegd.
  • field_title_generator : Een functie die programmatisch de titel van het veld instelt, op basis van de naam en info.

Hier is een voorbeeld:

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

Niet-afgedwongen Field

Als Pydantic beperkingen constateert die niet worden afgedwongen, zal er een fout optreden. Als u wilt dat de beperking in het schema verschijnt, ook al wordt deze niet gecontroleerd bij het parseren, kunt u variadische argumenten gebruiken voor Field met de onbewerkte schemakenmerknaam:

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

U kunt JSON-schemawijzigingen ook opgeven via de constructor Field via 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"
}
"""

Genereren van programmatische veldtitels

De parameter field_title_generator kan worden gebruikt om programmatisch de titel voor een veld te genereren op basis van de naam en info.

Zie het volgende voorbeeld:

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

Aanpassing op modelniveau

U kunt ook model config gebruiken om het genereren van JSON-schema's voor een model aan te passen. Concreet zijn de volgende configuratieopties relevant:

json_schema_extra gebruiken

De optie json_schema_extra kan worden gebruikt om extra informatie aan het JSON-schema toe te voegen, op veldniveau of op modelniveau . U kunt een dict of een Callable doorgeven aan json_schema_extra .

json_schema_extra gebruiken met een dict

U kunt een dict doorgeven aan json_schema_extra om extra informatie aan het JSON-schema toe te voegen:

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 gebruiken met een Callable

U kunt een Callable doorgeven aan json_schema_extra om het JSON-schema te wijzigen met een functie:

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 samenvoegen

Vanaf v2.9 voegt Pydantic json_schema_extra woordenboeken van geannoteerde typen samen. Dit patroon biedt een meer additieve benadering van samenvoegen dan het eerdere overschrijfgedrag. Dit kan behoorlijk handig zijn als er sprake is van hergebruik van extra JSON-schema-informatie voor meerdere typen.

We hebben deze wijziging grotendeels gezien als een bugfix, omdat hiermee onbedoelde verschillen in het samenvoeggedrag json_schema_extra tussen BaseModel en TypeAdapter instanties worden opgelost. Zie dit probleem voor meer details.

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

Als u er de voorkeur aan geeft dat de laatste van uw json_schema_extra -specificaties de vorige overschrijft, kunt u een callable gebruiken om belangrijkere wijzigingen aan te brengen, waaronder het toevoegen of verwijderen van sleutels, of het wijzigen van waarden. U kunt dit patroon gebruiken als u het gedrag van de json_schema_extra overschrijvingen in Pydantic v2.8 en eerder wilt nabootsen:

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 annotatie

??? api "API-documentatie" pydantic.json_schema.WithJsonSchema

!!! tip Het gebruik van WithJsonSchema] heeft de voorkeur boven het implementeren van __get_pydantic_json_schema__ voor aangepaste typen, omdat het eenvoudiger is en minder foutgevoelig.

De annotatie WithJsonSchema kan worden gebruikt om het gegenereerde (basis) JSON-schema voor een bepaald type te overschrijven zonder de noodzaak om __get_pydantic_core_schema__ of __get_pydantic_json_schema__ op het type zelf te implementeren.

Dit biedt een manier om een JSON-schema in te stellen voor typen die anders fouten zouden veroorzaken bij het produceren van een JSON-schema, zoals Callable , of typen die een kernschema is-instance hebben.

Het gebruik van een PlainValidator in het volgende voorbeeld zou anders een fout opleveren bij het produceren van een JSON-schema, omdat de PlainValidator een Callable is. Door echter de annotatie WithJsonSchema te gebruiken, kunnen we het gegenereerde JSON-schema voor het aangepaste MyInt type overschrijven:

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 Zoals besproken in dit nummer is het waarschijnlijk dat Pydantic in de toekomst ingebouwde ondersteuning zal toevoegen voor het genereren van JSON-schema's voor typen als PlainValidator, maar de annotatie WithJsonSchema zal nog steeds nuttig zijn voor andere aangepaste typen.

SkipJsonSchema annotatie

??? api "API-documentatie" pydantic.json_schema.SkipJsonSchema

De annotatie SkipJsonSchema kan worden gebruikt om een inclusief veld (of een deel van de specificaties van een veld) uit het gegenereerde JSON-schema over te slaan. Zie de API-documentatie voor meer details.

Implementatie van __get_pydantic_core_schema__

Aangepaste typen (gebruikt als field_name: TheType of field_name: Annotated[TheType, ...] ) en Annotated metadata (gebruikt als field_name: Annotated[int, SomeMetadata] ) kan het gegenereerde schema wijzigen of overschrijven door __get_pydantic_core_schema__ te implementeren. Deze methode ontvangt twee positionele argumenten:

  1. De typeannotatie die overeenkomt met dit type (dus in het geval van TheType[T][int] zou dit TheType[int] zijn).
  2. Een handler/callback om de volgende implementator van __get_pydantic_core_schema__ aan te roepen.

Het handlersysteem werkt net als mode='wrap' validators . In dit geval is de invoer het type en de uitvoer een core_schema .

Hier is een voorbeeld van een aangepast type dat het gegenereerde core_schema overschrijft :

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

Omdat Pydantic niet zou weten hoe hij een schema voor CompressedString moest genereren, zou je, als je handler(source) aanroept in zijn __get_pydantic_core_schema__ methode, een pydantic.errors.PydanticSchemaGenerationError fout. Dit zal het geval zijn voor de meeste aangepaste typen, dus u wilt bijna nooit handler inschakelen voor aangepaste typen.

Het proces voor Annotated metagegevens is vrijwel hetzelfde, behalve dat u over het algemeen handler kunt aanroepen om Pydantic het schema te laten genereren.

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

Tot nu toe hebben we het schema ingepakt, maar als je het alleen maar wilt wijzigen of negeren , kun je dat ook doen.

Om het schema te wijzigen, roept u eerst de handler aan en muteert u vervolgens het resultaat:

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 Houd er rekening mee dat u een schema moet retourneren, zelfs als u het alleen op zijn plaats muteert.

Om het schema volledig te overschrijven, roept u de handler niet aan en retourneert u uw eigen 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]
    """

Zoals hierboven te zien is, kan het annoteren van een veld met een BaseModel type worden gebruikt om het gegenereerde JSON-schema te wijzigen of te overschrijven. Als u echter wilt profiteren van het opslaan van metagegevens via Annotated , maar u het gegenereerde JSON-schema niet wilt overschrijven, kunt u de volgende aanpak gebruiken met een no-op-versie van __get_pydantic_core_schema__ geïmplementeerd in de metadataklasse:

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

Implementatie van __get_pydantic_json_schema__

U kunt ook __get_pydantic_json_schema__ implementeren om het gegenereerde json-schema te wijzigen of te overschrijven. Het wijzigen van deze methode heeft alleen invloed op het JSON-schema. Het heeft geen invloed op het kernschema, dat wordt gebruikt voor validatie en serialisatie.

Hier is een voorbeeld van het wijzigen van het gegenereerde JSON-schema:

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 gebruiken

De parameter field_title_generator kan worden gebruikt om programmatisch de titel voor een veld te genereren op basis van de naam en info. Dit is vergelijkbaar met het veldniveau field_title_generator , maar de optie ConfigDict wordt toegepast op alle velden van de klasse.

Zie het volgende voorbeeld:

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 gebruiken

De configuratieoptie model_title_generator is vergelijkbaar met de optie field_title_generator , maar is van toepassing op de titel van het model zelf en accepteert de modelklasse als invoer.

Zie het volgende voorbeeld:

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, aangepaste veldtypen en beperkingen (zoals max_length ) worden in de volgende prioriteitsvolgorde toegewezen aan de overeenkomstige specificatie-indelingen (als er een equivalent beschikbaar is):

  1. JSON-schemakern
  2. JSON-schemavalidatie
  3. OpenAPI-gegevenstypen
  4. Het JSON-veld format wordt gebruikt om Pydantic-extensies te definiëren voor complexere string .

De veldschematoewijzing van Python of Pydantic naar JSON-schema gebeurt als volgt:

Schema genereren op het hoogste niveau

U kunt ook een JSON-schema op het hoogste niveau genereren dat alleen een lijst met modellen en gerelateerde modellen bevat submodellen in zijn ` $defs`: ```py output="json" importeer json van pydantic importeer BaseModel van pydantic.json_schema import models_json_schema klasse Foo(BaseModel): a: str = Geen klasse Model(BaseModel): b: Foo class Bar(BaseModel): c: int _, top_level_schema = models_json_schema( [(Model, 'validatie'), (Bar, 'validatie')], title='Mijn schema' ) print(json.dumps(top_level_schema, indent=2)) """ { "$ defs": { "Bar": { "eigenschappen": { "C": { "titel": "C", "type": "geheel getal" } }, "vereist": [ "C" ], "title": "Bar", "type": "object" }, "Foe": { "eigenschappen": { "A": { "standaard": nul, "titel": "A", "type": "tekenreeks" } }, "title": "Foe", "type": "object" }, "Model": { "eigenschappen": { "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" } }, "vereist": [ "B" ], "titel": "Model", "type": "object" } }, "title": "Mijn 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',
}
"""

Hieronder vindt u een aanpak die u kunt gebruiken om velden uit het schema uit te sluiten die geen geldige json-schema's hebben:

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

Het aanpassen van de $ref`s in JSON Schema The format of `$ref s kunnen worden gewijzigd door model_json_schema() aan te roepen

of model_dump_json() met het trefwoordargument ref_template . De definities worden altijd onder de sleutel opgeslagen $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-kenmerk en waarnaar wordt verwezen, volgens de specificatie.

  • Submodellen met wijzigingen (via de klasse Field ), zoals een aangepaste titel, beschrijving of standaardwaarde, worden recursief opgenomen in plaats van dat er naar wordt verwezen.
  • De description voor modellen wordt overgenomen uit de docstring van de klasse of uit de description naar de klasse Field .
  • Het schema wordt standaard gegenereerd met aliassen als sleutels, maar kan in plaats daarvan worden gegenereerd met namen van modeleigenschappen door model_json_schema() of [model_dump_json()][pydantic.main.BaseModel aan te roepen. model_dump_json] met het by_alias=False trefwoordargument.

本文总阅读量