Aller au contenu

Sérialisation

Au-delà de l'accès aux attributs du modèle directement via leurs noms de champs (par exemple model.foobar ), les modèles peuvent être convertis, sauvegardés, sérialisés et exportés de plusieurs manières.

!!! astuce "Sérialiser ou vider" Pydantic utilise les termes "sérialiser" et "vider" de manière interchangeable. Les deux font référence au processus de conversion d’un modèle en dictionnaire ou en chaîne codée JSON.

Outside of Pydantic, the word "serialize" usually refers to converting in-memory data into a string or bytes.
However, in the context of Pydantic, there is a very close relationship between converting an object from a more
structured form — such as a Pydantic model, a dataclass, etc. — into a less structured form comprised of
Python built-ins such as dict.

While we could (and on occasion, do) distinguish between these scenarios by using the word "dump" when converting to
primitives and "serialize" when converting to string, for practical purposes, we frequently use the word "serialize"
to refer to both of these situations, even though it does not always imply conversion to a string or bytes.

model.model_dump(...)

??? API "Documentation API" pydantic.main.BaseModel.model_dump

Il s'agit du principal moyen de convertir un modèle en dictionnaire. Les sous-modèles seront convertis de manière récursive en dictionnaires.

!!! note La seule exception aux sous-modèles convertis en dictionnaires est que RootModel et ses sous-classes verront la valeur du champ root sauvegardée directement, sans dictionnaire d'encapsulage. Cela se fait également de manière récursive.

!!! note Vous pouvez utiliser des champs calculés pour inclure les données property et de cached_property dans la sortie model.model_dump(...) .

Exemple:

from typing import Any, List, Optional

from pydantic import BaseModel, Field, Json


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    banana: Optional[float] = 1.1
    foo: str = Field(serialization_alias='foo_alias')
    bar: BarModel


m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})

# returns a dictionary:
print(m.model_dump())
#> {'banana': 3.14, 'foo': 'hello', 'bar': {'whatever': 123}}
print(m.model_dump(include={'foo', 'bar'}))
#> {'foo': 'hello', 'bar': {'whatever': 123}}
print(m.model_dump(exclude={'foo', 'bar'}))
#> {'banana': 3.14}
print(m.model_dump(by_alias=True))
#> {'banana': 3.14, 'foo_alias': 'hello', 'bar': {'whatever': 123}}
print(
    FooBarModel(foo='hello', bar={'whatever': 123}).model_dump(
        exclude_unset=True
    )
)
#> {'foo': 'hello', 'bar': {'whatever': 123}}
print(
    FooBarModel(banana=1.1, foo='hello', bar={'whatever': 123}).model_dump(
        exclude_defaults=True
    )
)
#> {'foo': 'hello', 'bar': {'whatever': 123}}
print(
    FooBarModel(foo='hello', bar={'whatever': 123}).model_dump(
        exclude_defaults=True
    )
)
#> {'foo': 'hello', 'bar': {'whatever': 123}}
print(
    FooBarModel(banana=None, foo='hello', bar={'whatever': 123}).model_dump(
        exclude_none=True
    )
)
#> {'foo': 'hello', 'bar': {'whatever': 123}}


class Model(BaseModel):
    x: List[Json[Any]]


print(Model(x=['{"a": 1}', '[1, 2]']).model_dump())
#> {'x': [{'a': 1}, [1, 2]]}
print(Model(x=['{"a": 1}', '[1, 2]']).model_dump(round_trip=True))
#> {'x': ['{"a":1}', '[1,2]']}

model.model_dump_json(...)

??? API "Documentation API" pydantic.main.BaseModel.model_dump_json

La méthode .model_dump_json() sérialise un modèle directement en une chaîne codée en JSON équivalente au résultat produit par .model_dump() .

Voir arguments pour plus d'informations.

!!! note Pydantic peut sérialiser de nombreux types couramment utilisés en JSON qui seraient autrement incompatibles avec un simple json.dumps(foobar) (par exemple datetime , date ou UUID ) .

from datetime import datetime

from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    foo: datetime
    bar: BarModel


m = FooBarModel(foo=datetime(2032, 6, 1, 12, 13, 14), bar={'whatever': 123})
print(m.model_dump_json())
#> {"foo":"2032-06-01T12:13:14","bar":{"whatever":123}}
print(m.model_dump_json(indent=2))
"""
{
  "foo": "2032-06-01T12:13:14",
  "bar": {
    "whatever": 123
  }
}
"""

dict(model) et itération

Les modèles pydantiques peuvent également être convertis en dictionnaires à l'aide de dict(model) , et vous pouvez également parcourir les champs d'un modèle à l'aide de for field_name, field_value in model: . Avec cette approche, les valeurs brutes des champs sont renvoyées, donc les sous-modèles ne seront pas convertis en dictionnaires.

Exemple:

from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    banana: float
    foo: str
    bar: BarModel


m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})

print(dict(m))
#> {'banana': 3.14, 'foo': 'hello', 'bar': BarModel(whatever=123)}
for name, value in m:
    print(f'{name}: {value}')
    #> banana: 3.14
    #> foo: hello
    #> bar: whatever=123

Notez également que RootModel est converti en dictionnaire avec la clé 'root' .

Sérialiseurs personnalisés

Pydantic fournit plusieurs [sérialiseurs fonctionnels][pydantic.function_serializers] pour personnaliser la façon dont un modèle est sérialisé dans un dictionnaire ou JSON.

  • [@field_serializer][pydantic.function_serializers.field_serializer]
  • [@model_serializer][pydantic.function_serializers.model_serializer]
  • [PlainSerializer][pydantic.function_serializers.PlainSerializer]
  • [WrapSerializer][pydantic.function_serializers.WrapSerializer]

La sérialisation peut être personnalisée sur un champ à l'aide du décorateur [@field_serializer][pydantic.function_serializers.field_serializer] et sur un modèle à l'aide du décorateur [@model_serializer][pydantic.function_serializers.model_serializer].

from datetime import datetime, timedelta, timezone
from typing import Any, Dict

from pydantic import BaseModel, ConfigDict, field_serializer, model_serializer


class WithCustomEncoders(BaseModel):
    model_config = ConfigDict(ser_json_timedelta='iso8601')

    dt: datetime
    diff: timedelta

    @field_serializer('dt')
    def serialize_dt(self, dt: datetime, _info):
        return dt.timestamp()


m = WithCustomEncoders(
    dt=datetime(2032, 6, 1, tzinfo=timezone.utc), diff=timedelta(hours=100)
)
print(m.model_dump_json())
#> {"dt":1969660800.0,"diff":"P4DT4H"}


class Model(BaseModel):
    x: str

    @model_serializer
    def ser_model(self) -> Dict[str, Any]:
        return {'x': f'serialized {self.x}'}


print(Model(x='test value').model_dump_json())
#> {"x":"serialized test value"}

!!! note Un seul sérialiseur peut également être appelé sur tous les champs en transmettant la valeur spéciale '*' au décorateur [@field_serializer][pydantic.function_serializers.field_serializer].

De plus, [PlainSerializer][pydantic.function_serializers.PlainSerializer] et [WrapSerializer][pydantic.function_serializers.WrapSerializer] vous permettent d'utiliser une fonction pour modifier la sortie de la sérialisation.

Les deux sérialiseurs acceptent des arguments facultatifs, notamment:

  • return_type spécifie le type de retour de la fonction. S'il est omis, il sera déduit de l'annotation de type.
  • when_used spécifie quand ce sérialiseur doit être utilisé. Accepte une chaîne avec les valeurs « toujours », « sauf-aucun », « json » et « json-sauf-aucun ». La valeur par défaut est «toujours».

PlainSerializer utilise une fonction simple pour modifier la sortie de la sérialisation.

from typing_extensions import Annotated

from pydantic import BaseModel
from pydantic.functional_serializers import PlainSerializer

FancyInt = Annotated[
    int, PlainSerializer(lambda x: f'{x:,}', return_type=str, when_used='json')
]


class MyModel(BaseModel):
    x: FancyInt


print(MyModel(x=1234).model_dump())
#> {'x': 1234}

print(MyModel(x=1234).model_dump(mode='json'))
#> {'x': '1,234'}

WrapSerializer reçoit les entrées brutes ainsi qu'une fonction de gestionnaire qui applique la logique de sérialisation standard et peut modifier la valeur résultante avant de la renvoyer comme sortie finale de la sérialisation.

from typing import Any

from typing_extensions import Annotated

from pydantic import BaseModel, SerializerFunctionWrapHandler
from pydantic.functional_serializers import WrapSerializer


def ser_wrap(v: Any, nxt: SerializerFunctionWrapHandler) -> str:
    return f'{nxt(v + 1):,}'


FancyInt = Annotated[int, WrapSerializer(ser_wrap, when_used='json')]


class MyModel(BaseModel):
    x: FancyInt


print(MyModel(x=1234).model_dump())
#> {'x': 1234}

print(MyModel(x=1234).model_dump(mode='json'))
#> {'x': '1,235'}

Remplacement du type de retour lors du dump d'un modèle

Alors que la valeur de retour de .model_dump() peut généralement être décrite comme dict[str, Any] , grâce à l'utilisation de @model_serializer vous pouvez en fait lui faire renvoyer une valeur qui ne correspond pas à cette signature:

from pydantic import BaseModel, model_serializer


class Model(BaseModel):
    x: str

    @model_serializer
    def ser_model(self) -> str:
        return self.x


print(Model(x='not a dict').model_dump())
#> not a dict

Si vous souhaitez faire cela tout en obtenant une vérification de type appropriée pour cette méthode, vous pouvez remplacer .model_dump() dans un bloc if TYPE_CHECKING::

from typing import TYPE_CHECKING, Any

from typing_extensions import Literal

from pydantic import BaseModel, model_serializer


class Model(BaseModel):
    x: str

    @model_serializer
    def ser_model(self) -> str:
        return self.x

    if TYPE_CHECKING:
        # Ensure type checkers see the correct return type
        def model_dump(
            self,
            *,
            mode: Literal['json', 'python'] | str = 'python',
            include: Any = None,
            exclude: Any = None,
            by_alias: bool = False,
            exclude_unset: bool = False,
            exclude_defaults: bool = False,
            exclude_none: bool = False,
            round_trip: bool = False,
            warnings: bool = True,
        ) -> str: ...

Cette astuce est en fait utilisée dans RootModel précisément dans ce but.

Sérialisation des sous-classes

Sous-classes de types standards

Les sous-classes des types standards sont automatiquement vidées comme leurs super-classes:

from datetime import date, timedelta
from typing import Any, Type

from pydantic_core import core_schema

from pydantic import BaseModel, GetCoreSchemaHandler


class DayThisYear(date):
    """
    Contrived example of a special type of date that
    takes an int and interprets it as a day in the current year
    """

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls.validate,
            core_schema.int_schema(),
            serialization=core_schema.format_ser_schema('%Y-%m-%d'),
        )

    @classmethod
    def validate(cls, v: int):
        return date(2023, 1, 1) + timedelta(days=v)


class FooModel(BaseModel):
    date: DayThisYear


m = FooModel(date=300)
print(m.model_dump_json())
#> {"date":"2023-10-28"}

Instances de sous-classe pour les champs de BaseModel , classes de données, TypedDict

Lors de l'utilisation de champs dont les annotations sont elles-mêmes des types de type struct (par exemple, sous-classes BaseModel , classes de données, etc.), le comportement par défaut est de sérialiser la valeur de l'attribut comme s'il s'agissait d'une instance du type annoté, même s'il s'agit d'une sous-classe. Plus précisément, seuls les champs du type annoté seront inclus dans l'objet dumpé:

from pydantic import BaseModel


class User(BaseModel):
    name: str


class UserLogin(User):
    password: str


class OuterModel(BaseModel):
    user: User


user = UserLogin(name='pydantic', password='hunter2')

m = OuterModel(user=user)
print(m)
#> user=UserLogin(name='pydantic', password='hunter2')
print(m.model_dump())  # note: the password field is not included
#> {'user': {'name': 'pydantic'}}

!!! avertissement "Avertissement de migration" Ce comportement est différent de la façon dont les choses fonctionnaient dans Pydantic V1, où nous incluions toujours tous les champs (sous-classe) lors du dumping récursif de modèles vers des dicts. La motivation derrière ce changement de comportement est qu'il permet de garantir que vous savez précisément quels champs peuvent être inclus lors de la sérialisation, même si les sous-classes sont transmises lors de l'instanciation de l'objet. En particulier, cela peut aider à éviter les surprises lors de l'ajout d'informations sensibles telles que des secrets en tant que champs de sous-classes.

Sérialisation avec duck-typing 🦆

Qu'est-ce que la sérialisation avec le typage canard ?

Duck-typing serialization is the behavior of serializing an object based on the fields present in the object itself, rather than the fields present in the schema of the object. This means that when an object is serialized, fields present in a subclass, but not in the original schema, will be included in the serialized output.

This behavior was the default in Pydantic V1, but was changed in V2 to help ensure that you know precisely which fields would be included when serializing, even if subclasses get passed when instantiating the object. This helps prevent security risks when serializing subclasses with sensitive information, for example.

Si vous souhaitez un comportement de sérialisation de type canard de style v1, vous pouvez utiliser un paramètre d'exécution ou annoter des types individuels.

  • Niveau champ/type: utiliser l'annotation SerializeAsAny
  • Niveau d'exécution: utilisez l'indicateur serialize_as_any lors de l'appel model_dump() ou model_dump_json()

Nous discutons de ces options ci-dessous plus en détail:

Annotation SerializeAsAny:

Si vous souhaitez un comportement de sérialisation par type de canard, cela peut être fait en utilisant l'annotation SerializeAsAny sur un type:

from pydantic import BaseModel, SerializeAsAny


class User(BaseModel):
    name: str


class UserLogin(User):
    password: str


class OuterModel(BaseModel):
    as_any: SerializeAsAny[User]
    as_user: User


user = UserLogin(name='pydantic', password='password')

print(OuterModel(as_any=user, as_user=user).model_dump())
"""
{
    'as_any': {'name': 'pydantic', 'password': 'password'},
    'as_user': {'name': 'pydantic'},
}
"""

Lorsqu'un champ est annoté comme SerializeAsAny[<SomeType>] , le comportement de validation sera le même que s'il était annoté comme <SomeType> , et les vérificateurs de type comme mypy traiteront également l'attribut comme ayant le type approprié. Mais lors de la sérialisation, le champ sera sérialisé comme si l'indication de type pour le champ était Any , d'où vient le nom.

paramètre d'exécution serialize_as_any

Le paramètre d'exécution serialize_as_any peut être utilisé pour sérialiser les données du modèle avec ou sans comportement de sérialisation de type canard. serialize_as_any peut être transmis comme argument de mot-clé aux méthodes model_dump() et model_dump_json des BaseModel et RootModel . Il peut également être transmis comme argument de mot-clé aux méthodes dump_python() et dump_json() des TypeAdapter .

Si serialize_as_any est défini sur True , le modèle sera sérialisé en utilisant le comportement de sérialisation de type canard, ce qui signifie que le modèle ignorera le schéma et demandera à la place à l'objet lui-même comment il doit être sérialisé. En particulier, cela signifie que lorsque les sous-classes du modèle sont sérialisées, les champs présents dans la sous-classe mais pas dans le schéma d'origine seront inclus.

Si serialize_as_any est défini sur False (ce qui est la valeur par défaut), le modèle sera sérialisé à l'aide du schéma, ce qui signifie que les champs présents dans une sous-classe mais pas dans le schéma d'origine seront ignorés.

!!! question «Pourquoi ce drapeau est-il utile?» Parfois, vous souhaitez vous assurer que quels que soient les champs ajoutés dans les sous-classes, l'objet sérialisé n'aura que les champs répertoriés dans la définition de type d'origine. Cela peut être utile si vous ajoutez quelque chose comme un champ password: str dans une sous-classe que vous ne souhaitez pas inclure accidentellement dans la sortie sérialisée.

Par exemple:

from pydantic import BaseModel


class User(BaseModel):
    name: str


class UserLogin(User):
    password: str


class OuterModel(BaseModel):
    user1: User
    user2: User


user = UserLogin(name='pydantic', password='password')

outer_model = OuterModel(user1=user, user2=user)
print(outer_model.model_dump(serialize_as_any=True))  # (1)!
"""
{
    'user1': {'name': 'pydantic', 'password': 'password'},
    'user2': {'name': 'pydantic', 'password': 'password'},
}
"""

print(outer_model.model_dump(serialize_as_any=False))  # (2)!
#> {'user1': {'name': 'pydantic'}, 'user2': {'name': 'pydantic'}}
  1. Avec serialize_as_any défini sur True , le résultat correspond à celui de la V1.
  2. Avec serialize_as_any défini sur False (valeur par défaut de la V2), les champs présents sur la sous-classe, mais pas sur la classe de base, ne sont pas inclus dans la sérialisation.

Ce paramètre prend même effet avec les modèles imbriqués et récursifs. Par exemple:

from typing import List

from pydantic import BaseModel


class User(BaseModel):
    name: str
    friends: List['User']


class UserLogin(User):
    password: str


class OuterModel(BaseModel):
    user: User


user = UserLogin(
    name='samuel',
    password='pydantic-pw',
    friends=[UserLogin(name='sebastian', password='fastapi-pw', friends=[])],
)

print(OuterModel(user=user).model_dump(serialize_as_any=True))  # (1)!
"""
{
    'user': {
        'name': 'samuel',
        'friends': [
            {'name': 'sebastian', 'friends': [], 'password': 'fastapi-pw'}
        ],
        'password': 'pydantic-pw',
    }
}
"""

print(OuterModel(user=user).model_dump(serialize_as_any=False))  # (2)!
"""
{'user': {'name': 'samuel', 'friends': [{'name': 'sebastian', 'friends': []}]}}
"""
  1. Même les instances de modèle User imbriquées sont vidées avec des champs propres aux sous-classes User .
  2. Même les instances de modèle User imbriquées sont vidées sans champs propres aux sous-classes User .

!!! note Le comportement de l'indicateur d'exécution serialize_as_any est presque le même que celui de l'annotation SerializeAsAny . Il existe quelques différences nuancées que nous nous efforçons de résoudre, mais pour la plupart, vous pouvez vous attendre au même comportement de la part des deux. En savoir plus sur les différences dans ce numéro actif

Remplacement de la valeur par défaut serialize_as_any (False)

Vous pouvez remplacer le paramètre par défaut de serialize_as_any en configurant une sous-classe de BaseModel qui remplace la valeur par défaut de l'argument serialize_as_any en model_dump() et model_dump_json() , puis l'utiliser comme classe de base (au lieu de pydantic.BaseModel ) pour n'importe quel modèle que vous souhaitez avoir ce comportement par défaut.

Par exemple, vous pouvez procéder comme suit si vous souhaitez utiliser la sérialisation par type de canard par défaut:

from typing import Any, Dict

from pydantic import BaseModel, SecretStr


class MyBaseModel(BaseModel):
    def model_dump(self, **kwargs) -> Dict[str, Any]:
        return super().model_dump(serialize_as_any=True, **kwargs)

    def model_dump_json(self, **kwargs) -> str:
        return super().model_dump_json(serialize_as_any=True, **kwargs)


class User(MyBaseModel):
    name: str


class UserInfo(User):
    password: SecretStr


class OuterModel(MyBaseModel):
    user: User


u = OuterModel(user=UserInfo(name='John', password='secret_pw'))
print(u.model_dump_json())  # (1)!
#> {"user":{"name":"John","password":"**********"}}
  1. Par défaut, model_dump_json utilisera le comportement de sérialisation par type de canard, ce qui signifie que le champ password est inclus dans la sortie.

pickle.dumps(model)

Les modèles Pydantic prennent en charge un décapage et un décapage efficaces.

import pickle

from pydantic import BaseModel


class FooBarModel(BaseModel):
    a: str
    b: int


m = FooBarModel(a='hello', b=123)
print(m)
#> a='hello' b=123
data = pickle.dumps(m)
print(data[:20])
#> b'\x80\x04\x95\x95\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main_'
m2 = pickle.loads(data)
print(m2)
#> a='hello' b=123

Inclure et exclure avancés

Les méthodes model_dump et model_dump_json prennent en charge les arguments include et exclude qui peuvent être des ensembles ou des dictionnaires. Cela permet une sélection imbriquée des champs à exporter:

from pydantic import BaseModel, SecretStr


class User(BaseModel):
    id: int
    username: str
    password: SecretStr


class Transaction(BaseModel):
    id: str
    user: User
    value: int


t = Transaction(
    id='1234567890',
    user=User(id=42, username='JohnDoe', password='hashedpassword'),
    value=9876543210,
)

# using a set:
print(t.model_dump(exclude={'user', 'value'}))
#> {'id': '1234567890'}

# using a dict:
print(t.model_dump(exclude={'user': {'username', 'password'}, 'value': True}))
#> {'id': '1234567890', 'user': {'id': 42}}

print(t.model_dump(include={'id': True, 'user': {'id'}}))
#> {'id': '1234567890', 'user': {'id': 42}}

Le True indique que nous souhaitons exclure ou inclure une clé entière, comme si nous l'incluions dans un ensemble. Cela peut être fait à n’importe quel niveau de profondeur.

Des précautions particulières doivent être prises lors de l'inclusion ou de l'exclusion de champs d'une liste ou d'un tuple de sous-modèles ou de dictionnaires. Dans ce scénario, model_dump et les méthodes associées attendent des clés entières pour l'inclusion ou l'exclusion élément par élément. Pour exclure un champ de chaque membre d'une liste ou d'un tuple, la clé du dictionnaire '__all__' peut être utilisée, comme indiqué ici:

import datetime
from typing import List

from pydantic import BaseModel, SecretStr


class Country(BaseModel):
    name: str
    phone_code: int


class Address(BaseModel):
    post_code: int
    country: Country


class CardDetails(BaseModel):
    number: SecretStr
    expires: datetime.date


class Hobby(BaseModel):
    name: str
    info: str


class User(BaseModel):
    first_name: str
    second_name: str
    address: Address
    card_details: CardDetails
    hobbies: List[Hobby]


user = User(
    first_name='John',
    second_name='Doe',
    address=Address(
        post_code=123456, country=Country(name='USA', phone_code=1)
    ),
    card_details=CardDetails(
        number='4212934504460000', expires=datetime.date(2020, 5, 1)
    ),
    hobbies=[
        Hobby(name='Programming', info='Writing code and stuff'),
        Hobby(name='Gaming', info='Hell Yeah!!!'),
    ],
)

exclude_keys = {
    'second_name': True,
    'address': {'post_code': True, 'country': {'phone_code'}},
    'card_details': True,
    # You can exclude fields from specific members of a tuple/list by index:
    'hobbies': {-1: {'info'}},
}

include_keys = {
    'first_name': True,
    'address': {'country': {'name'}},
    'hobbies': {0: True, -1: {'name'}},
}

# would be the same as user.model_dump(exclude=exclude_keys) in this case:
print(user.model_dump(include=include_keys))
"""
{
    'first_name': 'John',
    'address': {'country': {'name': 'USA'}},
    'hobbies': [
        {'name': 'Programming', 'info': 'Writing code and stuff'},
        {'name': 'Gaming'},
    ],
}
"""

# To exclude a field from all members of a nested list or tuple, use "__all__":
print(user.model_dump(exclude={'hobbies': {'__all__': {'info'}}}))
"""
{
    'first_name': 'John',
    'second_name': 'Doe',
    'address': {
        'post_code': 123456,
        'country': {'name': 'USA', 'phone_code': 1},
    },
    'card_details': {
        'number': SecretStr('**********'),
        'expires': datetime.date(2020, 5, 1),
    },
    'hobbies': [{'name': 'Programming'}, {'name': 'Gaming'}],
}
"""

Il en va de même pour la méthode model_dump_json .

Inclure et exclure au niveau du modèle et du champ

En plus des arguments explicites exclude et include passés aux méthodes model_dump et model_dump_json , nous pouvons également transmettre les arguments exclude: bool directement au constructeur Field :

La définition exclude sur le constructeur de champ ( Field(..., exclude=True) ) est prioritaire sur l' exclude / include sur model_dump et model_dump_json :

from pydantic import BaseModel, Field, SecretStr


class User(BaseModel):
    id: int
    username: str
    password: SecretStr = Field(..., exclude=True)


class Transaction(BaseModel):
    id: str
    value: int = Field(exclude=True)


t = Transaction(
    id='1234567890',
    value=9876543210,
)

print(t.model_dump())
#> {'id': '1234567890'}
print(t.model_dump(include={'id': True, 'value': True}))  # (1)!
#> {'id': '1234567890'}
  1. value exclue de la sortie car elle a été exclue dans Field .

Cela étant dit, la définition de exclude sur le constructeur de champ ( Field(..., exclude=True) ) n'a pas la priorité sur les paramètres exclude_unset , exclude_none et exclude_default sur model_dump et model_dump_json :

from typing import Optional

from pydantic import BaseModel, Field


class Person(BaseModel):
    name: str
    age: Optional[int] = Field(None, exclude=False)


person = Person(name='Jeremy')

print(person.model_dump())
#> {'name': 'Jeremy', 'age': None}
print(person.model_dump(exclude_none=True))  # (1)!
#> {'name': 'Jeremy'}
print(person.model_dump(exclude_unset=True))  # (2)!
#> {'name': 'Jeremy'}
print(person.model_dump(exclude_defaults=True))  # (3)!
#> {'name': 'Jeremy'}
  1. age exclu de la sortie car exclude_none a été défini sur True et age est None .
  2. age exclu de la sortie car exclude_unset était défini sur True et age n'était pas défini dans le constructeur Person.
  3. age exclu de la sortie car exclude_defaults a été défini sur True et age prend la valeur par défaut de None .

Contexte de sérialisation

Vous pouvez transmettre un objet contextuel aux méthodes de sérialisation accessibles depuis l'argument info vers les fonctions de sérialisation décorées. Ceci est utile lorsque vous devez mettre à jour dynamiquement le comportement de sérialisation pendant l'exécution. Par exemple, si vous souhaitez qu'un champ soit vidé en fonction d'un ensemble de valeurs autorisées contrôlables dynamiquement, cela peut être fait en transmettant les valeurs autorisées par contexte:

from pydantic import BaseModel, SerializationInfo, field_serializer


class Model(BaseModel):
    text: str

    @field_serializer('text')
    def remove_stopwords(self, v: str, info: SerializationInfo):
        context = info.context
        if context:
            stopwords = context.get('stopwords', set())
            v = ' '.join(w for w in v.split() if w.lower() not in stopwords)
        return v


model = Model.model_construct(**{'text': 'This is an example document'})
print(model.model_dump())  # no context
#> {'text': 'This is an example document'}
print(model.model_dump(context={'stopwords': ['this', 'is', 'an']}))
#> {'text': 'example document'}
print(model.model_dump(context={'stopwords': ['document']}))
#> {'text': 'This is an example'}

De même, vous pouvez utiliser un contexte pour la validation .

model_copy(...)

??? API "Documentation API" pydantic.main.BaseModel.model_copy

model_copy() permet de dupliquer les modèles (avec des mises à jour facultatives), ce qui est particulièrement utile lorsque l'on travaille avec des modèles gelés.

Exemple:

from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    banana: float
    foo: str
    bar: BarModel


m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})

print(m.model_copy(update={'banana': 0}))
#> banana=0 foo='hello' bar=BarModel(whatever=123)
print(id(m.bar) == id(m.model_copy().bar))
#> True
# normal copy gives the same object reference for bar
print(id(m.bar) == id(m.model_copy(deep=True).bar))
#> False
# deep copy gives a new object reference for `bar`

本文总阅读量