Aller au contenu

JSON Schema

??? API "Documentation API" pydantic.json_schema

Pydantic permet la création et la personnalisation automatiques de schémas JSON à partir de modèles. Les schémas JSON générés sont conformes aux spécifications suivantes:

Génération d'un schéma JSON

Utilisez les fonctions suivantes pour générer un schéma JSON:

!!! note Ces méthodes ne doivent pas être confondues avec BaseModel.model_dump_json et TypeAdapter.dump_json, qui sérialisent respectivement les instances du modèle ou du type adapté. . Ces méthodes renvoient des chaînes JSON. En comparaison, BaseModel.model_json_schema et TypeAdapter.json_schema renvoient respectivement un dict jsonable représentant le schéma JSON du modèle ou du type adapté.

!!! notez "sur la nature "jsonable" du schéma JSON" Concernant la nature "jsonable" des résultats model_json_schema, appelant json.dumps(m.model_json_schema()) sur certains BaseModel m renvoie une chaîne JSON valide. De même, pour TypeAdapter.json_schema, appelant json.dumps(TypeAdapter(<some_type>).json_schema()) renvoie une chaîne JSON valide.

!!! Astuce Pydantic offre un support pour:

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.

Voici un exemple de génération de schéma JSON à partir d'un 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"
    }
  ]
}
"""

Configuration du JsonSchemaMode

Spécifiez le mode de génération du schéma JSON via le paramètre mode dans les méthodes model_json_schema et TypeAdapter.json_schema. Par défaut, le mode est défini sur 'validation' , ce qui produit un schéma JSON correspondant au schéma de validation du modèle.

Le JsonSchemaMode est un alias de type qui représente les options disponibles pour le paramètre mode:

  • 'validation'
  • 'serialization'

Voici un exemple de la façon de spécifier le paramètre mode et de la manière dont il affecte le schéma JSON généré:

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

Personnalisation du schéma JSON

Le schéma JSON généré peut être personnalisé à la fois au niveau du champ et au niveau du modèle via:

  1. Personnalisation au niveau du champ avec le constructeur Field
  2. Personnalisation au niveau du modèle avec model_config

Au niveau du champ et du modèle, vous pouvez utiliser l'option json_schema_extra pour ajouter des informations supplémentaires au schéma JSON. La section Utilisation json_schema_extra ci-dessous fournit plus de détails sur cette option.

Pour les types personnalisés, Pydantic propose d'autres outils pour personnaliser la génération de schémas JSON:

  1. Avec l'annotation WithJsonSchema
  2. Annotation SkipJsonSchema
  3. Implémentation __get_pydantic_core_schema__
  4. Implémentation __get_pydantic_json_schema__

Personnalisation au niveau du champ

En option, la fonction Field peut être utilisée pour fournir des informations supplémentaires sur le champ et les validations.

Certains paramètres de champ sont utilisés exclusivement pour personnaliser le schéma JSON généré:

  • title : Le titre du champ.
  • description : La description du champ.
  • examples : Les exemples du domaine.
  • json_schema_extra: propriétés de schéma JSON supplémentaires à ajouter au champ.
  • field_title_generator : Une fonction qui définit par programme le titre du champ, en fonction de son nom et de ses informations.

Voici un exemple:

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

Contraintes Field non appliquées

Si Pydantic trouve des contraintes qui ne sont pas appliquées, une erreur sera générée. Si vous souhaitez forcer l'apparition de la contrainte dans le schéma, même si elle n'est pas vérifiée lors de l'analyse, vous pouvez utiliser des arguments variadiques pour Field avec le nom d'attribut brut du schéma:

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

Vous pouvez également spécifier des modifications de schéma JSON via le constructeur 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"
}
"""

Génération de titres de champs programmatiques

Le paramètre field_title_generator peut être utilisé pour générer par programme le titre d'un champ en fonction de son nom et de ses informations.

Voir l'exemple suivant:

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

Personnalisation au niveau du modèle

Vous pouvez également utiliser model config pour personnaliser la génération de schéma JSON sur un modèle. Plus précisément, les options de configuration suivantes sont pertinentes:

Utilisation de json_schema_extra

L'option json_schema_extra peut être utilisée pour ajouter des informations supplémentaires au schéma JSON, soit au niveau Field , soit au niveau Model . Vous pouvez transmettre un dict ou un Callable à json_schema_extra .

Utiliser json_schema_extra avec un dict

Vous pouvez transmettre un dict à json_schema_extra pour ajouter des informations supplémentaires au schéma 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"
}
"""

Utiliser json_schema_extra avec un Callable

Vous pouvez passer un Callable à json_schema_extra pour modifier le schéma JSON avec une fonction:

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

Fusion json_schema_extra

À partir de la v2.9, Pydantic fusionne les dictionnaires json_schema_extra des types annotés. Ce modèle offre une approche plus additive de la fusion plutôt que le comportement de remplacement précédent. Cela peut être très utile dans les cas de réutilisation d'informations supplémentaires du schéma JSON sur plusieurs types.

Nous avons considéré ce changement en grande partie comme une correction de bug, car il résout les différences involontaires dans le comportement de fusion json_schema_extra entre les instances BaseModel et TypeAdapter - voir ce problème pour plus de détails.

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

Si vous préférez que la dernière de vos spécifications json_schema_extra remplace les précédentes, vous pouvez utiliser un callable pour apporter des modifications plus importantes, notamment l'ajout ou la suppression de clés, ou la modification de valeurs. Vous pouvez utiliser ce modèle si vous souhaitez imiter le comportement des remplacements json_schema_extra présents dans Pydantic v2.8 et versions antérieures:

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

Avec l'annotation WithJsonSchema

??? API "Documentation API" pydantic.json_schema.WithJsonSchema

!!! astuce L'utilisation de WithJsonSchema] est préférable à l'implémentation __get_pydantic_json_schema__ pour les types personnalisés, car elle est plus simple et moins sujette aux erreurs.

L'annotation WithJsonSchema peut être utilisée pour remplacer le schéma JSON généré (de base) pour un type donné sans avoir besoin d'implémenter __get_pydantic_core_schema__ ou __get_pydantic_json_schema__ sur le type lui-même.

Cela fournit un moyen de définir un schéma JSON pour les types qui autrement généreraient des erreurs lors de la production d'un schéma JSON, tels que Callable , ou les types qui ont un schéma principal is-instance .

Par exemple, l'utilisation d'un [PlainValidator][pydantic.function_validators.PlainValidator] dans l'exemple suivant générerait autrement une erreur lors de la production d'un schéma JSON car le [PlainValidator][pydantic.function_validators.PlainValidator] est un Callable . Cependant, en utilisant l'annotation WithJsonSchema , nous pouvons remplacer le schéma JSON généré pour le type MyInt personnalisé:

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 Comme indiqué dans ce numéro , à l'avenir, il est probable que Pydantic ajoutera la prise en charge intégrée de la génération de schémas JSON pour des types tels que [PlainValidator][pydantic.function_validators.PlainValidator], mais l'annotation WithJsonSchema sera toujours utile pour d’autres types personnalisés.

Annotation SkipJsonSchema

??? API "Documentation API" pydantic.json_schema.SkipJsonSchema

L'annotation SkipJsonSchema peut être utilisée pour ignorer un champ inclusif (ou une partie des spécifications d'un champ) du schéma JSON généré. Consultez la documentation de l'API pour plus de détails.

Implémentation __get_pydantic_core_schema__

Types personnalisés (utilisés comme field_name: TheType ou field_name: Annotated[TheType, ...] ) ainsi que les métadonnées Annotated (utilisées comme field_name: Annotated[int, SomeMetadata] ) peut modifier ou remplacer le schéma généré en implémentant __get_pydantic_core_schema__ . Cette méthode reçoit deux arguments de position:

  1. L'annotation de type qui correspond à ce type (donc dans le cas de TheType[T][int] ce serait TheType[int] ).
  2. Un gestionnaire/rappel pour appeler le prochain implémenteur de __get_pydantic_core_schema__ .

Le système de gestion fonctionne exactement comme les validateurs mode='wrap' . Dans ce cas, l'entrée est le type et la sortie est un core_schema .

Voici un exemple de type personnalisé qui remplace le core_schema généré:

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

Puisque Pydantic ne saurait pas comment générer un schéma pour CompressedString , si vous appelez handler(source) dans sa méthode __get_pydantic_core_schema__ vous obtiendrez un pydantic.errors.PydanticSchemaGenerationError erreur. Ce sera le cas pour la plupart des types personnalisés, vous ne voudrez donc presque jamais appeler handler pour les types personnalisés.

Le processus pour les métadonnées Annotated est à peu près le même, sauf que vous pouvez généralement appeler handler pour que Pydantic gère la génération du schéma.

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

Jusqu'à présent, nous avons enveloppé le schéma, mais si vous souhaitez simplement le modifier ou l' ignorer , vous pouvez également le faire.

Pour modifier le schéma, appelez d'abord le gestionnaire, puis modifiez le résultat:

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

!!! astuce Notez que vous devez renvoyer un schéma, même si vous le faites simplement muter sur place.

Pour remplacer complètement le schéma, n'appelez pas le gestionnaire et renvoyez votre propre 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]
    """

Comme vu ci-dessus, l'annotation d'un champ avec un type BaseModel peut être utilisée pour modifier ou remplacer le schéma json généré. Cependant, si vous souhaitez profiter du stockage des métadonnées via Annotated , mais que vous ne souhaitez pas remplacer le schéma JSON généré, vous pouvez utiliser l'approche suivante avec une version sans opération de __get_pydantic_core_schema__ implémentée sur la classe de métadonnées:

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

Implémentation __get_pydantic_json_schema__

Vous pouvez également implémenter __get_pydantic_json_schema__ pour modifier ou remplacer le schéma json généré. La modification de cette méthode n'affecte que le schéma JSON ; elle n'affecte pas le schéma principal, qui est utilisé pour la validation et la sérialisation.

Voici un exemple de modification du schéma JSON généré:

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

Utilisation de field_title_generator

Le paramètre field_title_generator peut être utilisé pour générer par programme le titre d'un champ en fonction de son nom et de ses informations. Ceci est similaire au field_title_generator au niveau du champ, mais l'option ConfigDict sera appliquée à tous les champs de la classe.

Voir l'exemple suivant:

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

Utilisation de model_title_generator

L'option de configuration model_title_generator est similaire à l'option field_title_generator , mais elle s'applique au titre du modèle lui-même et accepte la classe de modèle en entrée.

Voir l'exemple suivant:

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

Types de schéma JSON

Les types, les types de champs personnalisés et les contraintes (comme max_length ) sont mappés aux formats de spécifications correspondants dans l'ordre de priorité suivant (lorsqu'un équivalent est disponible):

  1. Noyau de schéma JSON
  2. Validation du schéma JSON
  3. Types de données OpenAPI
  4. Le champ JSON format standard est utilisé pour définir des extensions Pydantic pour des sous-types string plus complexes.

Le mappage du schéma de champ de Python ou Pydantic au schéma JSON s'effectue comme suit:

Génération de schéma de niveau supérieur

Vous pouvez également générer un schéma JSON de niveau supérieur qui inclut uniquement une liste de modèles et d'éléments associés. sous-modèles dans ses ` $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 classe Bar(BaseModel) : c: int _, top_level_schema = models_json_schema( [(Modèle, 'validation'), (Bar, 'validation')], title='Mon schéma' ) print(json.dumps(top_level_schema, indent=2)) """ { "$ defs": { "Bar": { "propriétés": { "c": { "titre": "C", "type": "entier" } }, "requis": [ "c" ], "titre": "Barre", "type": "objet" }, "Fou": { "propriétés": { "un": { "par défaut": nul, "titre": "A", "type": "chaîne" } }, "titre": "Foo", "type": "objet" }, "Modèle": { "propriétés": { "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": { "$ défs/Foo" } }, "requis": [ "b" ], "title": "Modèle", "type": "objet" } }, "title": "Mon schéma" } """

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

Vous trouverez ci-dessous une approche que vous pouvez utiliser pour exclure du schéma tous les champs qui n'ont pas de schéma JSON valide:

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

Personnaliser le $ref`s in JSON Schema The format of `$ref s peuvent être modifiés en appelant model_json_schema()

ou model_dump_json() avec l'argument de mot-clé ref_template . Les définitions sont toujours stockées sous la clé $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 Attribut JSON et référencé, selon les spécifications.

  • Les sous-modèles avec des modifications (via la classe Field ) comme un titre personnalisé, une description ou une valeur par défaut sont inclus de manière récursive au lieu d'être référencés.
  • La description des modèles provient soit de la docstring de la classe, soit de la description de l'argument de la classe Field .
  • Le schéma est généré par défaut en utilisant des alias comme clés, mais il peut être généré à l'aide des noms de propriétés de modèle en appelant model_json_schema() ou [model_dump_json()][pydantic.main.BaseModel. model_dump_json] avec l'argument de mot-clé by_alias=False .

本文总阅读量