Zum Inhalt

??? API „API-Dokumentation“ pydantic.main.BaseModel

Eine der wichtigsten Möglichkeiten, Schemata in Pydantic zu definieren, sind Modelle. Modelle sind einfach Klassen, die von pydantic.BaseModel erben und Felder als annotierte Attribute definieren.

Sie können sich Modelle ähnlich wie Strukturen in Sprachen wie C oder als Anforderungen eines einzelnen Endpunkts in einer API vorstellen.

Modelle haben viele Ähnlichkeiten mit den Datenklassen von Python, wurden jedoch mit einigen subtilen, aber wichtigen Unterschieden entworfen, die bestimmte Arbeitsabläufe im Zusammenhang mit Validierung, Serialisierung und JSON-Schemagenerierung rationalisieren. Weitere Informationen hierzu finden Sie im Abschnitt „Datenklassen“ der Dokumentation.

Nicht vertrauenswürdige Daten können an ein Modell übergeben werden und nach der Analyse und Validierung garantiert Pydantic, dass die Felder der resultierenden Modellinstanz den im Modell definierten Feldtypen entsprechen.

!!! Hinweis „Validierung – eine absichtliche Fehlbezeichnung“ ### TL;DR

We use the term "validation" to refer to the process of instantiating a model (or other type) that adheres to specified types and
constraints. This task, which Pydantic is well known for, is most widely recognized as "validation" in colloquial terms,
even though in other contexts the term "validation" may be more restrictive.

---

### The long version

The potential confusion around the term "validation" arises from the fact that, strictly speaking, Pydantic's
primary focus doesn't align precisely with the dictionary definition of "validation":

> ### validation
> _noun_
> the action of checking or proving the validity or accuracy of something.

In Pydantic, the term "validation" refers to the process of instantiating a model (or other type) that adheres to specified
types and constraints. Pydantic guarantees the types and constraints of the output, not the input data.
This distinction becomes apparent when considering that Pydantic's `ValidationError` is raised
when data cannot be successfully parsed into a model instance.

While this distinction may initially seem subtle, it holds practical significance.
In some cases, "validation" goes beyond just model creation, and can include the copying and coercion of data.
This can involve copying arguments passed to the constructor in order to perform coercion to a new type
without mutating the original input data. For a more in-depth understanding of the implications for your usage,
refer to the [Data Conversion](#data-conversion) and [Attribute Copies](#attribute-copies) sections below.

In essence, Pydantic's primary goal is to assure that the resulting structure post-processing (termed "validation")
precisely conforms to the applied type hints. Given the widespread adoption of "validation" as the colloquial term
for this process, we will consistently use it in our documentation.

While the terms "parse" and "validation" were previously used interchangeably, moving forward, we aim to exclusively employ "validate",
with "parse" reserved specifically for discussions related to [JSON parsing](../concepts/json.md).

Grundlegende Modellverwendung

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str = 'Jane Doe'

In diesem Beispiel ist User ein Modell mit zwei Feldern:

  • id , eine Ganzzahl, die erforderlich ist
  • name , eine Zeichenfolge, die nicht erforderlich ist (sie hat einen Standardwert).

user = Benutzer(id='123')

In diesem Beispiel ist user eine Instanz von User . Durch die Initialisierung des Objekts werden alle Parsing- und Validierungsvorgänge durchgeführt. Wenn kein ValidationError ausgelöst wird, wissen Sie, dass die resultierende Modellinstanz gültig ist.

assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123

Weitere Details zur Zwangslogik von Pydantic finden Sie unter Datenkonvertierung . Auf Felder eines Modells kann als normale Attribute des user zugegriffen werden. Die Zeichenfolge '123' wurde entsprechend dem Feldtyp in ein int umgewandelt.

assert user.name == 'Jane Doe'

name wurde bei der Initialisierung user nicht festgelegt und hat daher den Standardwert.

assert user.model_fields_set == {'id'}

Die Felder, die bei der Initialisierung des Benutzers bereitgestellt wurden.

assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}

Entweder .model_dump() oder dict(user) stellt ein Diktat von Feldern bereit, aber .model_dump() kann zahlreiche andere Argumente annehmen. (Beachten Sie, dass dict(user) verschachtelte Modelle nicht rekursiv in Diktate umwandelt, .model_dump() jedoch schon.)

user.id = 321
assert user.id == 321

Standardmäßig sind Modelle veränderbar und Feldwerte können durch Attributzuweisung geändert werden.

Modellmethoden und -eigenschaften

Das obige Beispiel zeigt nur die Spitze des Eisbergs dessen, was Modelle leisten können. Modelle verfügen über die folgenden Methoden und Attribute:

!!! Hinweis: Siehe BaseModel für die Klassendefinition einschließlich einer vollständigen Liste von Methoden und Attributen.

!!! Tipp Weitere Informationen zu Änderungen gegenüber Pydantic V1 finden Sie unter Änderungen an pydantic.BaseModel im Migrationsleitfaden .

Verschachtelte Modelle

Komplexere hierarchische Datenstrukturen können definiert werden, indem Modelle selbst als Typen in Annotationen verwendet werden.

from typing import List, Optional

from pydantic import BaseModel


class Foo(BaseModel):
    count: int
    size: Optional[float] = None


class Bar(BaseModel):
    apple: str = 'x'
    banana: str = 'y'


class Spam(BaseModel):
    foo: Foo
    bars: List[Bar]


m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
"""
foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
"""
print(m.model_dump())
"""
{
    'foo': {'count': 4, 'size': None},
    'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}],
}
"""

Informationen zu selbstreferenzierenden Modellen finden Sie unter verschobene Anmerkungen .

!!! Hinweis Achten Sie beim Definieren Ihrer Modelle auf Namenskonflikte zwischen Ihrem Feldnamen und seinem Typ, einem zuvor definierten Modell oder einer importierten Bibliothek.

For example, the following would yield a validation error:
```py
from typing import Optional

from pydantic import BaseModel


class Boo(BaseModel):
    int: Optional[int] = None


m = Boo(int=123)  # errors
```
An error occurs since the field  `int` is set to a default value of `None` and has the exact same name as its type, so both are interpreted to be `None`.

Modellschema neu erstellen

Das Modellschema kann mit model_rebuild() neu erstellt werden. Dies ist nützlich für die Erstellung rekursiver generischer Modelle.

from pydantic import BaseModel, PydanticUserError


class Foo(BaseModel):
    x: 'Bar'


try:
    Foo.model_json_schema()
except PydanticUserError as e:
    print(e)
    """
    `Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`.

    For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined
    """


class Bar(BaseModel):
    pass


Foo.model_rebuild()
print(Foo.model_json_schema())
"""
{
    '$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}},
    'properties': {'x': {'$ref': '#/$defs/Bar'}},
    'required': ['x'],
    'title': 'Foo',
    'type': 'object',
}
"""

Pydantic versucht automatisch zu ermitteln, wann dies erforderlich ist, und gibt einen Fehler aus, wenn dies nicht geschehen ist. Sie können jedoch model_rebuild() proaktiv aufrufen, wenn Sie mit rekursiven Modellen oder Generika arbeiten.

In V2 ersetzte model_rebuild() update_forward_refs() aus V1. Es gibt einige geringfügige Unterschiede zum neuen Verhalten. Die größte Änderung besteht darin, dass beim Aufruf von model_rebuild() für das äußerste Modell ein Kernschema erstellt wird, das zur Validierung des gesamten Modells (verschachtelte Modelle und alle), also aller Typen überhaupt, verwendet wird Ebenen müssen bereit sein, bevor model_rebuild() aufgerufen wird.

Beliebige Klasseninstanzen

(Früher bekannt als „ORM-Modus“/ from_orm .)

Pydantic-Modelle können auch aus beliebigen Klasseninstanzen erstellt werden, indem die Instanzattribute gelesen werden, die den Modellfeldnamen entsprechen. Eine häufige Anwendung dieser Funktionalität ist die Integration mit objektrelationalen Zuordnungen (ORMs).

Legen Sie dazu das Config-Attribut fest model_config['from_attributes'] = True . Weitere Informationen finden Sie unter Model Config und ConfigDict.

Das Beispiel hier verwendet SQLAlchemy , aber der gleiche Ansatz sollte für jedes ORM funktionieren.

from typing import List

from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated

from pydantic import BaseModel, ConfigDict, StringConstraints

Base = declarative_base()


class CompanyOrm(Base):
    __tablename__ = 'companies'

    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    public_key: Annotated[str, StringConstraints(max_length=20)]
    name: Annotated[str, StringConstraints(max_length=63)]
    domains: List[Annotated[str, StringConstraints(max_length=255)]]


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""

Reservierte Namen

Möglicherweise möchten Sie eine Column nach einem reservierten SQLAlchemy-Feld benennen. In diesem Fall sind Field praktisch:

import typing

import sqlalchemy as sa
from sqlalchemy.orm import declarative_base

from pydantic import BaseModel, ConfigDict, Field


class MyModel(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    metadata: typing.Dict[str, str] = Field(alias='metadata_')


Base = declarative_base()


class SQLModel(Base):
    __tablename__ = 'my_table'
    id = sa.Column('id', sa.Integer, primary_key=True)
    # 'metadata' is reserved by SQLAlchemy, hence the '_'
    metadata_ = sa.Column('metadata', sa.JSON)


sql_model = SQLModel(metadata_={'key': 'val'}, id=1)

pydantic_model = MyModel.model_validate(sql_model)

print(pydantic_model.model_dump())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.model_dump(by_alias=True))
#> {'metadata_': {'key': 'val'}}

!!! Hinweis: Das obige Beispiel funktioniert, weil Aliase bei der Feldauffüllung Vorrang vor Feldnamen haben. Der Zugriff auf das metadata von SQLModel würde zu einem ValidationError führen.

Verschachtelte Attribute

Bei der Verwendung von Attributen zum Analysieren von Modellen werden Modellinstanzen sowohl aus Attributen der obersten Ebene als auch aus tiefer verschachtelten Attributen erstellt.

Hier ist ein Beispiel, das das Prinzip demonstriert:

from typing import List

from pydantic import BaseModel, ConfigDict


class PetCls:
    def __init__(self, *, name: str, species: str):
        self.name = name
        self.species = species


class PersonCls:
    def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
        self.name = name
        self.age = age
        self.pets = pets


class Pet(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    name: str
    species: str


class Person(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    name: str
    age: float = None
    pets: List[Pet]


bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
"""
name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'), Pet(name='Orion', species='cat')]
"""

Fehlerbehandlung

Pydantic löst ValidationError aus, wenn es einen Fehler in den zu validierenden Daten findet.

Unabhängig von der Anzahl der gefundenen Fehler wird eine einzelne Ausnahme vom Typ ValidationError ausgelöst, und dieser ValidationError enthält Informationen zu allen Fehlern und deren Entstehung.

Einzelheiten zu Standard- und benutzerdefinierten Fehlern finden Sie unter Fehlerbehandlung .

Als Demonstration:

from typing import List

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    list_of_ints: List[int]
    a_float: float


data = dict(
    list_of_ints=['1', 2, 'bad'],
    a_float='not a float',
)

try:
    Model(**data)
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    list_of_ints.2
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
    a_float
      Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
    """

Hilfsfunktionen

Pydantic bietet drei classmethod Hilfsfunktionen für Modelle zum Parsen von Daten:

  • model_validate(): Dies ist der __init__ Methode des Modells sehr ähnlich, außer dass sie ein Diktat oder ein Objekt anstelle von Schlüsselwortargumenten akzeptiert. Wenn das übergebene Objekt nicht validiert werden kann oder es sich nicht um ein Wörterbuch oder eine Instanz des betreffenden Modells handelt, wird ein ValidationError ausgelöst.
  • model_validate_json(): Dies nimmt einen String oder Bytes und analysiert ihn als json und übergibt das Ergebnis dann an model_validate().
  • model_validate_strings(): Dies nimmt ein Diktat (kann verschachtelt werden) mit Zeichenfolgenschlüsseln und -werten und validiert die Daten im JSON -Modus, sodass diese Zeichenfolgen in die richtigen Typen umgewandelt werden können.

    from datetime import datetime from typing import Optional

    from pydantic import BaseModel, ValidationError

    class User(BaseModel): id: int name: str = 'John Doe' signup_ts: Optional[datetime] = None

    m = User.model_validate({'id': 123, 'name': 'James'}) print(m)

    > id=123 name='James' signup_ts=None

    try: User.model_validate(['not', 'a', 'dict']) except ValidationError as e: print(e) """ 1 validation error for User Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list] """

    m = User.model_validate_json('{"id": 123, "name": "James"}') print(m)

    > id=123 name='James' signup_ts=None

    try: m = User.model_validate_json('{"id": 123, "name": 123}') except ValidationError as e: print(e) """ 1 validation error for User name Input should be a valid string [type=string_type, input_value=123, input_type=int] """

    try: m = User.model_validate_json('invalid JSON') except ValidationError as e: print(e) """ 1 validation error for User Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str] """

    m = User.model_validate_strings({'id': '123', 'name': 'James'}) print(m)

    > id=123 name='James' signup_ts=None

    m = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'} ) print(m)

    > id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)

    try: m = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True ) except ValidationError as e: print(e) """ 1 validation error for User signup_ts Input should be a valid datetime, invalid datetime separator, expected T, t, _ or space [type=datetime_parsing, input_value='2024-04-01', input_type=str] """

Wenn Sie serialisierte Daten in einem anderen Format als JSON validieren möchten, sollten Sie die Daten selbst in ein Diktat laden und es dann an model_validate übergeben.

!!! Hinweis Abhängig von den beteiligten Typen und Modellkonfigurationen können model_validate und model_validate_json ein unterschiedliches Validierungsverhalten aufweisen. Wenn Ihre Daten aus einer Nicht-JSON-Quelle stammen, Sie aber das gleiche Validierungsverhalten und die gleichen Fehler wünschen, die Sie von model_validate_json erhalten würden, empfehlen wir Ihnen vorerst, beide Verwendungszwecke zu verwenden model_validate_json(json.dumps(data)) , oder verwenden Sie model_validate_strings, wenn die Daten die Form eines (möglicherweise verschachtelten) Diktats mit Zeichenfolgenschlüsseln und -werten haben.

!!! Hinweis Weitere Informationen zum JSON-Parsing finden Sie im JSON -Abschnitt der Dokumentation.

!!! Hinweis Wenn Sie eine Instanz eines Modells an model_validate übergeben, sollten Sie erwägen, revalidate_instances in der Konfiguration des Modells festzulegen. Wenn Sie diesen Wert nicht festlegen, wird die Validierung für Modellinstanzen übersprungen. Sehen Sie sich das folgende Beispiel an:

\=== "❌ revalidate_instances='never' " ```py from pydantic import BaseModel

class Model(BaseModel):
    a: int


m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'

# doesn't raise a validation error even though m is invalid
m2 = Model.model_validate(m)
```

\=== "✅ revalidate_instances='always' " ```py from pydantic import BaseModel, ConfigDict, ValidationError

class Model(BaseModel):
    a: int

    model_config = ConfigDict(revalidate_instances='always')


m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'

try:
    m2 = Model.model_validate(m)
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    a
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str]
    """
```

Modelle ohne Validierung erstellen

Pydantic bietet außerdem die Methode model_construct(), mit der Modelle ohne Validierung erstellt werden können. Dies kann zumindest in einigen Fällen nützlich sein:

  • bei der Arbeit mit komplexen Daten, von denen bereits bekannt ist, dass sie gültig sind (aus Leistungsgründen)
  • wenn eine oder mehrere der Validatorfunktionen nicht idempotent sind, oder
  • wenn eine oder mehrere der Validierungsfunktionen Nebenwirkungen haben, die nicht ausgelöst werden sollen.

!!! Hinweis: In Pydantic V2 wurde die Leistungslücke zwischen BaseModel.__init__ und BaseModel.model_construct erheblich verringert. Bei einfachen Modellen ist der Aufruf von BaseModel.__init__ möglicherweise sogar schneller. Wenn Sie model_construct() aus Leistungsgründen verwenden, möchten Sie möglicherweise ein Profil Ihres Anwendungsfalls erstellen, bevor Sie davon ausgehen, dass model_construct() schneller ist.

!!! Warnung model_construct() führt keine Validierung durch, was bedeutet, dass ungültige Modelle erstellt werden können. Sie sollten die Methode model_construct() immer nur mit Daten verwenden, die bereits validiert wurden oder denen Sie definitiv vertrauen.

from pydantic import BaseModel


class User(BaseModel):
    id: int
    age: int
    name: str = 'John Doe'


original_user = User(id=123, age=32)

user_data = original_user.model_dump()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.model_fields_set
print(fields_set)
#> {'age', 'id'}

# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...

# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.model_construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.model_fields_set)
#> {'age', 'id'}

# construct can be dangerous, only use it with validated data!:
bad_user = User.model_construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')

Das Schlüsselwortargument _fields_set für model_construct() ist optional, ermöglicht Ihnen jedoch eine genauere Angabe, welche Felder ursprünglich festgelegt wurden und welche nicht. Wenn es weggelassen wird, sind model_fields_set nur die Schlüssel der bereitgestellten Daten.

Wenn beispielsweise im obigen Beispiel _fields_set nicht angegeben wurde, wäre new_user.model_fields_set {'id', 'age', 'name'} .

Beachten Sie, dass für Unterklassen von RootModel der Stammwert positionell an model_construct() übergeben werden kann, anstatt ein Schlüsselwortargument zu verwenden.

Hier sind einige zusätzliche Hinweise zum Verhalten von model_construct():

  • Wenn wir sagen „Es wird keine Validierung durchgeführt“, umfasst dies auch die Konvertierung von Diktaten in Modellinstanzen. Also, wenn Sie ein Feld haben Bei einem Model müssen Sie das innere Diktat selbst in ein Modell konvertieren, bevor Sie es an model_construct() übergeben.
    • Insbesondere unterstützt die Methode model_construct() nicht die rekursive Konstruktion von Modellen aus Diktaten.
  • Wenn Sie keine Schlüsselwortargumente für Felder mit Standardwerten übergeben, werden weiterhin die Standardwerte verwendet.
  • Bei Modellen mit privaten Attributen wird das Diktat __pydantic_private__ genauso initialisiert wie beim Aufruf von __init__ .
  • Beim Erstellen einer Instanz mit model_construct() wird keine __init__ Methode aus dem Modell oder einer seiner übergeordneten Klassen aufgerufen, selbst wenn eine benutzerdefinierte __init__ Methode definiert ist.

!!! Hinweis „Über extra Verhalten mit model_construct “ * Für Modelle mit model_config['extra'] == 'allow' , werden Daten, die nicht Feldern entsprechen, korrekt im Diktat __pydantic_extra__ und im __dict__ des Modells gespeichert. * Für Modelle mit model_config['extra'] == 'ignore' , werden Daten, die nicht den Feldern entsprechen, ignoriert, d. h. nicht in __pydantic_extra__ oder __dict__ auf der Instanz gespeichert. * Im Gegensatz zu einem Aufruf von __init__ ist ein Aufruf von model_construct mit model_config['extra'] == 'forbid' löst keinen Fehler aus, wenn Daten vorhanden sind, die nicht den Feldern entsprechen. Vielmehr werden diese Eingabedaten einfach ignoriert.

Generische Modelle

Pydantic unterstützt die Erstellung generischer Modelle, um die Wiederverwendung einer gemeinsamen Modellstruktur zu erleichtern.

Um ein generisches Modell zu deklarieren, führen Sie die folgenden Schritte aus:

  1. Deklarieren Sie eine oder mehrere typing.TypeVar -Instanzen zur Parametrisierung Ihres Modells.
  2. Deklarieren Sie ein pydantisches Modell, das von pydantic.BaseModel und typing.Generic erbt, wobei Sie die TypeVar Instanzen als Parameter an typing.Generic übergeben.
  3. Verwenden Sie die TypeVar Instanzen als Anmerkungen, wenn Sie sie durch andere Typen oder pydantische Modelle ersetzen möchten.

Hier ist ein Beispiel für die Verwendung einer generischen BaseModel Unterklasse zum Erstellen eines einfach wiederverwendbaren HTTP-Antwort-Nutzlast-Wrappers:

from typing import Generic, List, Optional, TypeVar

from pydantic import BaseModel, ValidationError

DataT = TypeVar('DataT')


class DataModel(BaseModel):
    numbers: List[int]
    people: List[str]


class Response(BaseModel, Generic[DataT]):
    data: Optional[DataT] = None


print(Response[int](data=1))
#> data=1
print(Response[str](data='value'))
#> data='value'
print(Response[str](data='value').model_dump())
#> {'data': 'value'}

data = DataModel(numbers=[1, 2, 3], people=[])
print(Response[DataModel](data=data).model_dump())
#> {'data': {'numbers': [1, 2, 3], 'people': []}}
try:
    Response[int](data='value')
except ValidationError as e:
    print(e)
    """
    1 validation error for Response[int]
    data
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str]
    """

Wenn Sie model_config festlegen oder @field_validator oder andere Pydantic-Dekoratoren in Ihrer generischen Modelldefinition verwenden, werden diese auf die gleiche Weise auf parametrisierte Unterklassen angewendet wie beim Erben von einer BaseModel Unterklasse. Alle in Ihrer generischen Klasse definierten Methoden werden ebenfalls vererbt.

Die Generika von Pydantic lassen sich auch problemlos in Typprüfer integrieren, sodass Sie alle Typprüfungen erhalten, die Sie erwarten würden, wenn Sie für jede Parametrisierung einen eigenen Typ deklarieren würden.

!!! Hinweis Intern erstellt Pydantic zur Laufzeit Unterklassen von BaseModel , wenn generische Modelle parametrisiert werden. Diese Klassen werden zwischengespeichert, sodass durch die Verwendung generischer Modelle nur ein minimaler Mehraufwand entstehen sollte.

Um von einem generischen Modell zu erben und die Tatsache zu bewahren, dass es generisch ist, muss die Unterklasse auch von typing.Generic erben:

from typing import Generic, TypeVar

from pydantic import BaseModel

TypeX = TypeVar('TypeX')


class BaseClass(BaseModel, Generic[TypeX]):
    X: TypeX


class ChildClass(BaseClass[TypeX], Generic[TypeX]):
    # Inherit from Generic[TypeX]
    pass


# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1

Sie können auch eine generische Unterklasse eines BaseModel erstellen, die die Typparameter in der Oberklasse teilweise oder vollständig ersetzt:

from typing import Generic, TypeVar

from pydantic import BaseModel

TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')


class BaseClass(BaseModel, Generic[TypeX, TypeY]):
    x: TypeX
    y: TypeY


class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
    z: TypeZ


# Replace TypeY by str
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3

Wenn der Name der konkreten Unterklassen wichtig ist, können Sie die Standardnamensgenerierung auch überschreiben:

from typing import Any, Generic, Tuple, Type, TypeVar

from pydantic import BaseModel

DataT = TypeVar('DataT')


class Response(BaseModel, Generic[DataT]):
    data: DataT

    @classmethod
    def model_parametrized_name(cls, params: Tuple[Type[Any], ...]) -> str:
        return f'{params[0].__name__.title()}Response'


print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')

Sie können parametrisierte generische Modelle als Typen in anderen Modellen verwenden:

from typing import Generic, TypeVar

from pydantic import BaseModel

T = TypeVar('T')


class ResponseModel(BaseModel, Generic[T]):
    content: T


class Product(BaseModel):
    name: str
    price: float


class Order(BaseModel):
    id: int
    product: ResponseModel[Product]


product = Product(name='Apple', price=0.5)
response = ResponseModel[Product](content=product)
order = Order(id=1, product=response)
print(repr(order))
"""
Order(id=1, product=ResponseModel[Product](content=Product(name='Apple', price=0.5)))
"""

!!! Tipp Wenn Sie ein parametrisiertes generisches Modell als Typ in einem anderen Modell verwenden (z product: ResponseModel[Product] ), stellen Sie sicher, dass Sie das generische Modell parametrisieren, wenn Sie die Modellinstanz initialisieren (wie response = ResponseModel[Product](content=product) ). Wenn Sie dies nicht tun, wird ein ValidationError ausgelöst, da Pydantic den Typ des generischen Modells nicht anhand der an es übergebenen Daten ableitet.

Durch die Verwendung derselben TypeVar in verschachtelten Modellen können Sie Typisierungsbeziehungen an verschiedenen Punkten in Ihrem Modell erzwingen:

from typing import Generic, TypeVar

from pydantic import BaseModel, ValidationError

T = TypeVar('T')


class InnerT(BaseModel, Generic[T]):
    inner: T


class OuterT(BaseModel, Generic[T]):
    outer: T
    nested: InnerT[T]


nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
    nested = InnerT[str](inner='a')
    print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
    print(e)
    """
    2 validation errors for OuterT[int]
    outer
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    nested
      Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]]
    """

Wenn gebundene Typparameter verwendet werden und Typparameter nicht angegeben werden, behandelt Pydantic generische Modelle ähnlich wie integrierte generische Typen wie List und Dict :

  • Wenn Sie vor der Instanziierung des generischen Modells keine Parameter angeben, werden diese als Grenze der TypeVar validiert.
  • Wenn die beteiligten TypeVar keine Grenzen haben, werden sie als Any behandelt.

Außerdem können, wie bei List und Dict , alle mit einer TypeVar angegebenen Parameter später durch konkrete Typen ersetzt werden:

from typing import Generic, TypeVar

from pydantic import BaseModel, ValidationError

AT = TypeVar('AT')
BT = TypeVar('BT')


class Model(BaseModel, Generic[AT, BT]):
    a: AT
    b: BT


print(Model(a='a', b='a'))
#> a='a' b='a'

IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
    typevar_model(a='a', b='a')
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for Model[int, TypeVar]
    a
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    b
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1

!!! Warnung Auch wenn dies möglicherweise keinen Fehler auslöst, raten wir dringend davon ab, parametrisierte Generika in Instanzprüfungen zu verwenden.

For example, you should not do `isinstance(my_model, MyGenericModel[int])`. However, it is fine to do `isinstance(my_model, MyGenericModel)`. (Note that, for standard generics, it would raise an error to do a subclass check with a parameterized generic.)

If you need to perform isinstance checks against parametrized generics, you can do this by subclassing the parametrized generic class. This looks like `class MyIntModel(MyGenericModel[int]): ...` and `isinstance(my_model, MyIntModel)`.

Wenn ein Pydantic-Modell in einer TypeVar Grenze verwendet wird und der generische Typ nie parametrisiert wird, verwendet Pydantic die Grenze zur Validierung, behandelt den Wert jedoch im Hinblick auf die Serialisierung als Any :

from typing import Generic, Optional, TypeVar

from pydantic import BaseModel


class ErrorDetails(BaseModel):
    foo: str


ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)


class Error(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[ErrorDataT]


class MyErrorDetails(ErrorDetails):
    bar: str


# serialized as Any
error = Error(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
        'bar': 'var2',
    },
}

# serialized using the concrete parametrization
# note that `'bar': 'var2'` is missing
error = Error[ErrorDetails](
    message='We just had an error',
    details=ErrorDetails(foo='var'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
    },
}

Hier ist ein weiteres Beispiel für das obige Verhalten, das alle Permutationen bezüglich der gebundenen Spezifikation und der generischen Typparametrisierung auflistet:

from typing import Generic

from typing_extensions import TypeVar

from pydantic import BaseModel

TBound = TypeVar('TBound', bound=BaseModel)
TNoBound = TypeVar('TNoBound')


class IntValue(BaseModel):
    value: int


class ItemBound(BaseModel, Generic[TBound]):
    item: TBound


class ItemNoBound(BaseModel, Generic[TNoBound]):
    item: TNoBound


item_bound_inferred = ItemBound(item=IntValue(value=3))
item_bound_explicit = ItemBound[IntValue](item=IntValue(value=3))
item_no_bound_inferred = ItemNoBound(item=IntValue(value=3))
item_no_bound_explicit = ItemNoBound[IntValue](item=IntValue(value=3))

# calling `print(x.model_dump())` on any of the above instances results in the following:
#> {'item': {'value': 3}}

Wenn Sie ein default=... (verfügbar in Python >= 3.13 oder über typing-extensions ) oder Einschränkungen ( TypeVar('T', str, int) ; beachten Sie, dass Sie diese Form einer TypeVar selten verwenden möchten), dann Der Standardwert oder die Standardeinschränkungen werden sowohl für die Validierung als auch für die Serialisierung verwendet, wenn die Typvariable nicht parametrisiert ist. Sie können dieses Verhalten mit pydantic.SerializeAsAny überschreiben:

from typing import Generic, Optional

from typing_extensions import TypeVar

from pydantic import BaseModel, SerializeAsAny


class ErrorDetails(BaseModel):
    foo: str


ErrorDataT = TypeVar('ErrorDataT', default=ErrorDetails)


class Error(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[ErrorDataT]


class MyErrorDetails(ErrorDetails):
    bar: str


# serialized using the default's serializer
error = Error(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
    },
}


class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
    message: str
    details: Optional[SerializeAsAny[ErrorDataT]]


# serialized as Any
error = SerializeAsAnyError(
    message='We just had an error',
    details=MyErrorDetails(foo='var', bar='baz'),
)
assert error.model_dump() == {
    'message': 'We just had an error',
    'details': {
        'foo': 'var',
        'bar': 'baz',
    },
}

!!! note Hinweis: Wenn Sie ein Generikum nicht parametrisieren, kann es zu Problemen kommen, da die Validierung anhand der Grenze des Generikums zu Datenverlust führen könnte. Siehe das Beispiel unten:

from typing import Generic

from typing_extensions import TypeVar

from pydantic import BaseModel

TItem = TypeVar('TItem', bound='ItemBase')


class ItemBase(BaseModel): ...


class IntItem(ItemBase):
    value: int


class ItemHolder(BaseModel, Generic[TItem]):
    item: TItem


loaded_data = {'item': {'value': 1}}


print(ItemHolder(**loaded_data).model_dump())  # (1)!
#> {'item': {}}

print(ItemHolder[IntItem](**loaded_data).model_dump())  # (2)!
#> {'item': {'value': 1}}
  1. Wenn das generische Element nicht parametrisiert ist, werden die Eingabedaten anhand der generischen Grenze validiert. Da ItemBase keine Felder hat, gehen die item verloren.
  2. In diesem Fall werden die Laufzeittypinformationen explizit über die generische Parametrisierung bereitgestellt, sodass die Eingabedaten anhand der IntItem -Klasse validiert werden und die Serialisierungsausgabe den Erwartungen entspricht.

Dynamische Modellerstellung

??? API „API-Dokumentation“ pydantic.main.create_model

In einigen Fällen ist es wünschenswert, ein Modell zu erstellen, bei dem Laufzeitinformationen zum Spezifizieren der Felder verwendet werden. Zu diesem Zweck stellt Pydantic die Funktion create_model zur Verfügung, mit der Modelle im Handumdrehen erstellt werden können:

from pydantic import BaseModel, create_model

DynamicFoobarModel = create_model(
    'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)


class StaticFoobarModel(BaseModel):
    foo: str
    bar: int = 123

Hier sind StaticFoobarModel und DynamicFoobarModel identisch.

Felder werden durch eine der folgenden Tupelformen definiert:

  • (<type>, <default value>)
  • (<type>, Field(...))
  • typing.Annotated[<type>, Field(...)]

Die Verwendung eines Field(...) -Aufrufs als zweites Argument im Tupel (der Standardwert) ermöglicht eine erweiterte Feldkonfiguration. Somit gilt Folgendes:

from pydantic import BaseModel, Field, create_model

DynamicModel = create_model(
    'DynamicModel',
    foo=(str, Field(..., description='foo description', alias='FOO')),
)


class StaticModel(BaseModel):
    foo: str = Field(..., description='foo description', alias='FOO')

Die speziellen Schlüsselwortargumente __config__ und __base__ können zum Anpassen des neuen Modells verwendet werden. Dazu gehört die Erweiterung eines Basismodells um zusätzliche Felder.

from pydantic import BaseModel, create_model


class FooModel(BaseModel):
    foo: str
    bar: int = 123


BarModel = create_model(
    'BarModel',
    apple=(str, 'russet'),
    banana=(str, 'yellow'),
    __base__=FooModel,
)
print(BarModel)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])

Sie können Validatoren auch hinzufügen, indem Sie ein Diktat an das Argument __validators__ übergeben.

from pydantic import ValidationError, create_model, field_validator


def username_alphanumeric(cls, v):
    assert v.isalnum(), 'must be alphanumeric'
    return v


validators = {
    'username_validator': field_validator('username')(username_alphanumeric)
}

UserModel = create_model(
    'UserModel', username=(str, ...), __validators__=validators
)

user = UserModel(username='scolvin')
print(user)
#> username='scolvin'

try:
    UserModel(username='scolvi%n')
except ValidationError as e:
    print(e)
    """
    1 validation error for UserModel
    username
      Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str]
    """

!!! Hinweis So wählen Sie ein dynamisch erstelltes Modell aus:

- the model must be defined globally
- it must provide `__module__`

RootModel und benutzerdefinierte Root-Typen

??? API „API-Dokumentation“ pydantic.root_model.RootModel

Pydantic-Modelle können mit einem „benutzerdefinierten Stammtyp“ definiert werden, indem eine Unterklasse von pydantic.RootModel gebildet wird.

Der Stammtyp kann jeder von Pydantic unterstützte Typ sein und wird durch den generischen Parameter für RootModel angegeben. Der Stammwert kann über das erste und einzige Argument an das Modell __init__ oder model_validate übergeben werden.

Hier ist ein Beispiel, wie das funktioniert:

from typing import Dict, List

from pydantic import RootModel

Pets = RootModel[List[str]]
PetsByName = RootModel[Dict[str, str]]


print(Pets(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets(['dog', 'cat']).model_dump_json())
#> ["dog","cat"]
print(Pets.model_validate(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets.model_json_schema())
"""
{'items': {'type': 'string'}, 'title': 'RootModel[List[str]]', 'type': 'array'}
"""

print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}).model_dump_json())
#> {"Otis":"dog","Milo":"cat"}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}

Wenn Sie direkt auf Elemente im root zugreifen oder über die Elemente iterieren möchten, können Sie benutzerdefinierte Funktionen __iter__ und __getitem__ implementieren, wie im folgenden Beispiel gezeigt.

from typing import List

from pydantic import RootModel


class Pets(RootModel):
    root: List[str]

    def __iter__(self):
        return iter(self.root)

    def __getitem__(self, item):
        return self.root[item]


pets = Pets.model_validate(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']

Sie können Unterklassen des parametrisierten Wurzelmodells auch direkt erstellen:

from typing import List

from pydantic import RootModel


class Pets(RootModel[List[str]]):
    def describe(self) -> str:
        return f'Pets: {", ".join(self.root)}'


my_pets = Pets.model_validate(['dog', 'cat'])

print(my_pets.describe())
#> Pets: dog, cat

Falsche Unveränderlichkeit

Modelle können über model_config['frozen'] = True so konfiguriert werden, dass sie unveränderlich sind. Wenn dies festgelegt ist, führt der Versuch, die Werte von Instanzattributen zu ändern, zu Fehlern. Weitere Einzelheiten finden Sie in der API-Referenz.

!!! Hinweis Dieses Verhalten wurde in Pydantic V1 über die Konfigurationseinstellung allow_mutation = False erreicht. Dieses Konfigurationsflag ist in Pydantic V2 veraltet und wurde durch frozen ersetzt.

!!! Warnung In Python wird Unveränderlichkeit nicht erzwungen. Entwickler haben die Möglichkeit, Objekte zu ändern, die herkömmlicherweise als „unveränderlich“ gelten, wenn sie dies wünschen.

from pydantic import BaseModel, ConfigDict, ValidationError


class FooBarModel(BaseModel):
    model_config = ConfigDict(frozen=True)

    a: str
    b: dict


foobar = FooBarModel(a='hello', b={'apple': 'pear'})

try:
    foobar.a = 'different'
except ValidationError as e:
    print(e)
    """
    1 validation error for FooBarModel
    a
      Instance is frozen [type=frozen_instance, input_value='different', input_type=str]
    """

print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}

Der Versuch, a zu ändern, verursachte einen Fehler und a bleibt unverändert. Das Diktat b ist jedoch veränderbar, und die Unveränderlichkeit von foobar verhindert nicht, dass b geändert wird.

Abstrakte Basisklassen

Pydantic-Modelle können zusammen mit den Abstract Base Classes (ABCs) von Python verwendet werden.

import abc

from pydantic import BaseModel


class FooBarModel(BaseModel, abc.ABC):
    a: str
    b: int

    @abc.abstractmethod
    def my_abstract_method(self):
        pass

Feldbestellung

Die Feldreihenfolge wirkt sich auf folgende Weise auf Modelle aus:

  • Die Feldreihenfolge bleibt im Modellschema erhalten
  • Die Feldreihenfolge bleibt bei Validierungsfehlern erhalten
  • Die Feldreihenfolge wird durch .model_dump() und .model_dump_json() usw. beibehalten.

    from pydantic import BaseModel, ValidationError

    class Model(BaseModel): a: int b: int = 2 c: int = 1 d: int = 0 e: float

    print(Model.model_fields.keys())

    > dict_keys(['a', 'b', 'c', 'd', 'e'])

    m = Model(e=2, a=1) print(m.model_dump())

    >

    try: Model(a='x', b='x', c='x', d='x', e='x') except ValidationError as err: error_locations = [e['loc'] for e in err.errors()]

    print(error_locations)

    > [('a',), ('b',), ('c',), ('d',), ('e',)]

Erforderliche Felder

Um ein Feld als erforderlich zu deklarieren, können Sie es mithilfe einer Annotation oder einer Annotation in Kombination mit einer Field deklarieren. Sie können Ellipsis / ... auch verwenden, um hervorzuheben, dass ein Feld erforderlich ist, insbesondere wenn Sie den Field Konstruktor verwenden.

Die Field wird hauptsächlich zum Konfigurieren von Einstellungen wie alias oder description für ein Attribut verwendet. Der Konstruktor unterstützt Ellipsis / ... als einziges Positionsargument. Dies wird verwendet, um anzugeben, dass das Feld obligatorisch ist, obwohl es der Typhinweis ist, der diese Anforderung erzwingt.

from pydantic import BaseModel, Field


class Model(BaseModel):
    a: int
    b: int = ...
    c: int = Field(..., alias='C')

Hier sind a , b und c alle erforderlich. Diese Verwendung von b: int = ... funktioniert jedoch mit mypy nicht ordnungsgemäß und sollte ab Version 1.0 in den meisten Fällen vermieden werden.

!!! Hinweis In Pydantic V1 erhielten Felder, die mit Optional oder Any versehen waren, implizit den Standardwert None , selbst wenn kein Standardwert explizit angegeben wurde. Dieses Verhalten hat sich in Pydantic V2 geändert und es gibt keine Typanmerkungen mehr, die dazu führen, dass ein Feld einen impliziten Standardwert hat.

See [the migration guide](../migration.md#required-optional-and-nullable-fields) for more details on changes
to required and nullable fields.

Felder mit nicht hashbaren Standardwerten

Eine häufige Fehlerquelle in Python ist die Verwendung eines veränderlichen Objekts als Standardwert für ein Funktions- oder Methodenargument, da bei jedem Aufruf dieselbe Instanz wiederverwendet wird.

Das Modul dataclasses löst in diesem Fall tatsächlich einen Fehler aus, der darauf hinweist, dass Sie das Argument default_factory für dataclasses.field verwenden sollten.

Pydantic unterstützt auch die Verwendung einer default_factory für nicht hashbare Standardwerte, dies ist jedoch nicht erforderlich. Für den Fall, dass der Standardwert nicht hashbar ist, kopiert Pydantic den Standardwert beim Erstellen jeder Instanz des Modells tief:

from typing import Dict, List

from pydantic import BaseModel


class Model(BaseModel):
    item_counts: List[Dict[str, int]] = [{}]


m1 = Model()
m1.item_counts[0]['a'] = 1
print(m1.item_counts)
#> [{'a': 1}]

m2 = Model()
print(m2.item_counts)
#> [{}]

Felder mit dynamischen Standardwerten

Wenn Sie ein Feld mit einem Standardwert deklarieren, möchten Sie möglicherweise, dass dieser dynamisch ist (dh für jedes Modell unterschiedlich). Zu diesem Zweck möchten Sie möglicherweise eine default_factory verwenden.

Hier ist ein Beispiel:

from datetime import datetime, timezone
from uuid import UUID, uuid4

from pydantic import BaseModel, Field


def datetime_now() -> datetime:
    return datetime.now(timezone.utc)


class Model(BaseModel):
    uid: UUID = Field(default_factory=uuid4)
    updated: datetime = Field(default_factory=datetime_now)


m1 = Model()
m2 = Model()
assert m1.uid != m2.uid

Weitere Informationen finden Sie in der Dokumentation der Field .

Automatisch ausgeschlossene Attribute

Klassenvars

Mit typing.ClassVar annotierte Attribute werden von Pydantic ordnungsgemäß als Klassenvariablen behandelt und werden nicht zu Feldern in Modellinstanzen:

from typing import ClassVar

from pydantic import BaseModel


class Model(BaseModel):
    x: int = 2
    y: ClassVar[int] = 1


m = Model()
print(m)
#> x=2
print(Model.y)
#> 1

Private Modellattribute

??? API „API-Dokumentation“ pydantic.fields.PrivateAttr

Attribute, deren Name einen führenden Unterstrich hat, werden von Pydantic nicht als Felder behandelt und sind nicht im Modellschema enthalten. Stattdessen werden diese in ein „privates Attribut“ umgewandelt, das bei Aufrufen von __init__ , model_validate usw. nicht validiert oder gar gesetzt wird.

!!! Hinweis Ab Pydantic v2.1.0 erhalten Sie einen NameError, wenn Sie versuchen, die Field Funktion mit einem privaten Attribut zu verwenden. Da private Attribute nicht als Felder behandelt werden, kann die Funktion Field() nicht angewendet werden.

Hier ist ein Anwendungsbeispiel:

from datetime import datetime
from random import randint

from pydantic import BaseModel, PrivateAttr


class TimeAwareModel(BaseModel):
    _processed_at: datetime = PrivateAttr(default_factory=datetime.now)
    _secret_value: str

    def __init__(self, **data):
        super().__init__(**data)
        # this could also be done with default_factory
        self._secret_value = randint(1, 5)


m = TimeAwareModel()
print(m._processed_at)
#> 2032-01-02 03:04:05.000006
print(m._secret_value)
#> 3

Private Attributnamen müssen mit einem Unterstrich beginnen, um Konflikte mit Modellfeldern zu vermeiden. Dunder-Namen (z. B. __attr__ ) werden jedoch nicht unterstützt.

Datenkonvertierung

Pydantic wandelt Eingabedaten möglicherweise um, um eine Anpassung an Modellfeldtypen zu erzwingen. In einigen Fällen kann dies zu einem Informationsverlust führen. Zum Beispiel:

from pydantic import BaseModel


class Model(BaseModel):
    a: int
    b: float
    c: str


print(Model(a=3.000, b='2.72', c=b'binary data').model_dump())
#> {'a': 3, 'b': 2.72, 'c': 'binary data'}

Dies ist eine bewusste Entscheidung von Pydantic und häufig der nützlichste Ansatz. Eine ausführlichere Diskussion zu diesem Thema finden Sie hier .

Dennoch wird auch eine strenge Typprüfung unterstützt.

Modellsignatur

Die Signatur aller Pydantic-Modelle wird basierend auf ihren Feldern generiert:

import inspect

from pydantic import BaseModel, Field


class FooModel(BaseModel):
    id: int
    name: str = None
    description: str = 'Foo'
    apple: int = Field(alias='pear')


print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None

Eine genaue Signatur ist für Selbstbeobachtungszwecke und Bibliotheken wie FastAPI oder hypothesis nützlich.

Die generierte Signatur berücksichtigt auch benutzerdefinierte __init__ Funktionen:

import inspect

from pydantic import BaseModel


class MyModel(BaseModel):
    id: int
    info: str = 'Foo'

    def __init__(self, id: int = 1, *, bar: str, **data) -> None:
        """My custom init!"""
        super().__init__(id=id, bar=bar, **data)


print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None

Um in die Signatur aufgenommen zu werden, muss der Alias oder Name eines Feldes ein gültiger Python-Bezeichner sein. Pydantic priorisiert beim Generieren der Signatur den Alias eines Felds vor seinem Namen, kann jedoch den Feldnamen verwenden, wenn der Alias kein gültiger Python-Bezeichner ist.

Wenn sowohl der Alias als auch der Name eines Felds keine gültigen Bezeichner sind (was durch die exotische Verwendung von create_model möglich sein kann), wird ein **data -Argument hinzugefügt. Darüber hinaus ist das Argument **data immer in der Signatur vorhanden, wenn model_config['extra'] == 'allow' .

Strukturmustervergleich

Pydantic unterstützt den strukturellen Mustervergleich für Modelle, wie durch PEP 636 in Python 3.10 eingeführt.

from pydantic import BaseModel


class Pet(BaseModel):
    name: str
    species: str


a = Pet(name='Bones', species='dog')

match a:
    # match `species` to 'dog', declare and initialize `dog_name`
    case Pet(species='dog', name=dog_name):
        print(f'{dog_name} is a dog')
#> Bones is a dog
    # default case
    case _:
        print('No dog matched')

!!! Hinweis Eine Match-Case-Anweisung mag so aussehen, als ob sie ein neues Modell erstellt, aber lassen Sie sich nicht täuschen; Es handelt sich lediglich um syntaktischen Zucker, um ein Attribut abzurufen und es entweder zu vergleichen oder zu deklarieren und zu initialisieren.

Attributkopien

In vielen Fällen werden an den Konstruktor übergebene Argumente kopiert, um eine Validierung und gegebenenfalls einen Zwang durchzuführen.

Beachten Sie in diesem Beispiel, dass sich die ID der Liste ändert, nachdem die Klasse erstellt wurde, da sie während der Validierung kopiert wurde:

from typing import List

from pydantic import BaseModel


class C1:
    arr = []

    def __init__(self, in_arr):
        self.arr = in_arr


class C2(BaseModel):
    arr: List[int]


arr_orig = [1, 9, 10, 3]


c1 = C1(arr_orig)
c2 = C2(arr=arr_orig)
print('id(c1.arr) == id(c2.arr):', id(c1.arr) == id(c2.arr))
#> id(c1.arr) == id(c2.arr): False

!!! Hinweis: Es gibt Situationen, in denen Pydantic keine Attribute kopiert, beispielsweise bei der Übergabe von Modellen – wir verwenden das Modell so, wie es ist. Sie können dieses Verhalten durch eine Einstellung außer Kraft setzen model_config['revalidate_instances'] = 'always' .

Zusätzliche Felder

Standardmäßig geben Pydantic-Modelle keine Fehler aus, wenn Sie Daten für nicht erkannte Felder bereitstellen. Sie werden einfach ignoriert:

from pydantic import BaseModel


class Model(BaseModel):
    x: int


m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}

Wenn Sie möchten, dass dies einen Fehler auslöst, können Sie dies über model_config erreichen:

from pydantic import BaseModel, ConfigDict, ValidationError


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='forbid')


try:
    Model(x=1, y='a')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    y
      Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str]
    """

Um stattdessen alle zusätzlichen bereitgestellten Daten beizubehalten, können Sie extra='allow' festlegen. Die zusätzlichen Felder werden dann in BaseModel.__pydantic_extra__ gespeichert:

from pydantic import BaseModel, ConfigDict


class Model(BaseModel):
    x: int

    model_config = ConfigDict(extra='allow')


m = Model(x=1, y='a')
assert m.__pydantic_extra__ == {'y': 'a'}

Standardmäßig wird auf diese zusätzlichen Elemente keine Validierung angewendet, Sie können jedoch einen Typ für die Werte festlegen, indem Sie die Typanmerkung für __pydantic_extra__ überschreiben:

from typing import Dict

from pydantic import BaseModel, ConfigDict, Field, ValidationError


class Model(BaseModel):
    __pydantic_extra__: Dict[str, int] = Field(init=False)  # (1)!

    x: int

    model_config = ConfigDict(extra='allow')


try:
    Model(x=1, y='a')
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model
    y
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

m = Model(x=1, y='2')
assert m.x == 1
assert m.y == 2
assert m.model_dump() == {'x': 1, 'y': 2}
assert m.__pydantic_extra__ == {'y': 2}
  1. Das = Field(init=False) hat zur Laufzeit keine Auswirkung, verhindert jedoch, dass das Feld __pydantic_extra__ von Typprüfern als Argument für die Methode __init__ des Modells behandelt wird.

Die gleichen Konfigurationen gelten für TypedDict und dataclass ', mit der Ausnahme, dass die Konfiguration durch Festlegen des Attributs __pydantic_config__ der Klasse auf ein gültiges ConfigDict gesteuert wird.


本文总阅读量