??? api "Dokumentasi API" pydantic.main.BaseModel
Salah satu cara utama untuk mendefinisikan skema di Pydantic adalah melalui model. Model hanyalah kelas yang mewarisi dari pydantic.BaseModel
dan mendefinisikan bidang sebagai atribut beranotasi.
Anda dapat menganggap model mirip dengan struct dalam bahasa seperti C, atau sebagai persyaratan titik akhir tunggal dalam API.
Model memiliki banyak kesamaan dengan kelas data Python, namun telah dirancang dengan beberapa perbedaan halus namun penting yang menyederhanakan alur kerja tertentu terkait validasi, serialisasi, dan pembuatan skema JSON. Anda dapat menemukan diskusi lebih lanjut tentang ini di bagian Dataclasses pada dokumen.
Data yang tidak tepercaya dapat diteruskan ke model dan, setelah penguraian dan validasi, Pydantic menjamin bahwa bidang instance model yang dihasilkan akan sesuai dengan jenis bidang yang ditentukan pada model.
!!! catatan "Validasi — istilah yang keliru " ### 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).
Penggunaan model dasar¶
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = 'Jane Doe'
Dalam contoh ini, User
adalah model dengan dua bidang:
id
, yang merupakan bilangan bulat dan wajib diisi-
name
, yang merupakan string dan tidak diperlukan (memiliki nilai default).user = User(id='123')
Dalam contoh ini, user
adalah turunan dari User
. Inisialisasi objek akan melakukan semua parsing dan validasi. Jika tidak ada ValidationError
yang dimunculkan, Anda tahu bahwa instance model yang dihasilkan valid.
assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123
Detail lebih lanjut tentang logika paksaan pydantic dapat ditemukan di Konversi Data . Bidang model dapat diakses sebagai atribut normal dari objek user
. String '123'
telah diubah menjadi int sesuai jenis bidang.
assert user.name == 'Jane Doe'
name
tidak disetel saat user
diinisialisasi, sehingga memiliki nilai default.
assert user.model_fields_set == {'id'}
Bidang yang diberikan saat pengguna diinisialisasi.
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
Baik .model_dump()
atau dict(user)
akan memberikan dict bidang, namun .model_dump()
dapat mengambil banyak argumen lainnya. (Perhatikan bahwa dict(user)
tidak akan mengubah model bersarang menjadi dicts secara rekursif, tetapi .model_dump()
akan melakukannya.)
user.id = 321
assert user.id == 321
Secara default, model dapat berubah dan nilai bidang dapat diubah melalui penetapan atribut.
Metode dan properti model¶
Contoh di atas hanya menunjukkan puncak gunung es dari apa yang dapat dilakukan oleh model. Model memiliki metode dan atribut berikut:
model_computed_fields
: kamus bidang terhitung dari contoh model ini.model_construct()
: metode kelas untuk membuat model tanpa menjalankan validasi. Lihat Membuat model tanpa validasi .model_copy()
: mengembalikan salinan (secara default, salinan dangkal) model. Lihat Serialisasi .model_dump()
: mengembalikan kamus bidang dan nilai model. Lihat Serialisasi .model_dump_json()
: mengembalikan representasi string JSON darimodel_dump()
. Lihat Serialisasi .model_extra
: dapatkan set bidang tambahan selama validasi.model_fields_set
: kumpulan bidang yang disetel saat instance model diinisialisasi.model_json_schema()
: mengembalikan kamus jsonable yang mewakili model sebagai Skema JSON. Lihat Skema JSON .model_parametrized_name()
: menghitung nama kelas untuk parameterisasi kelas generik.model_post_init()
: melakukan inisialisasi tambahan setelah model diinisialisasi.model_rebuild()
: membangun kembali skema model, yang juga mendukung pembuatan model generik rekursif. Lihat Membangun kembali skema model .model_validate()
: utilitas untuk memuat objek apa pun ke dalam model. Lihat Fungsi pembantu .model_validate_json()
: utilitas untuk memvalidasi data JSON yang diberikan terhadap model Pydantic. Lihat Fungsi pembantu .
!!! catatan Lihat BaseModel
untuk definisi kelas termasuk daftar lengkap metode dan atribut.
!!! tip Lihat Perubahan pada pydantic.BaseModel
di Panduan Migrasi untuk detail tentang perubahan dari Pydantic V1.
Model bersarang¶
Struktur data hierarki yang lebih kompleks dapat ditentukan menggunakan model itu sendiri sebagai tipe dalam anotasi.
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'}],
}
"""
Untuk model referensi mandiri, lihat anotasi yang ditunda .
!!! catatan Saat mendefinisikan model Anda, hati-hati terhadap benturan penamaan antara nama bidang Anda dan tipenya, model yang ditentukan sebelumnya, atau pustaka yang diimpor.
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`.
Membangun kembali skema model¶
Skema model dapat dibangun kembali menggunakan model_rebuild()
. Ini berguna untuk membangun model generik rekursif.
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 mencoba menentukan kapan hal ini diperlukan secara otomatis dan membuat kesalahan jika tidak dilakukan, tetapi Anda mungkin ingin memanggil model_rebuild()
secara proaktif saat menangani model rekursif atau generik.
Di V2, model_rebuild()
menggantikan update_forward_refs()
dari V1. Ada sedikit perbedaan dengan perilaku baru ini. Perubahan terbesar adalah ketika memanggil model_rebuild()
pada model terluar, ia membangun skema inti yang digunakan untuk validasi keseluruhan model (model bersarang dan semuanya), jadi semua tipe sama sekali level harus siap sebelum model_rebuild()
dipanggil.
Contoh kelas sewenang-wenang¶
(Sebelumnya dikenal sebagai "Mode ORM"/ from_orm
.)
Model Pydantic juga dapat dibuat dari instance kelas arbitrer dengan membaca atribut instance yang sesuai dengan nama kolom model. Salah satu penerapan umum fungsi ini adalah integrasi dengan pemetaan relasional objek (ORM).
Untuk melakukan ini, atur atribut config model_config['from_attributes'] = True
. Lihat Model Config dan ConfigDict untuk informasi selengkapnya.
Contoh di sini menggunakan SQLAlchemy , tetapi pendekatan yang sama dapat digunakan untuk ORM apa pun.
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']
"""
Nama yang dipesan¶
Anda mungkin ingin memberi nama Column
setelah bidang SQLAlchemy yang dicadangkan. Dalam hal ini, alias Field
akan berguna:
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'}}
!!! catatan Contoh di atas berfungsi karena alias memiliki prioritas di atas nama bidang untuk populasi bidang. Mengakses atribut metadata
SQLModel
akan menyebabkan ValidationError
.
Atribut bersarang¶
Saat menggunakan atribut untuk mengurai model, instance model akan dibuat dari atribut tingkat atas dan atribut bertingkat lebih dalam jika diperlukan.
Berikut adalah contoh yang menunjukkan prinsip tersebut:
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')]
"""
Penanganan kesalahan¶
Pydantic akan memunculkan ValidationError
setiap kali menemukan kesalahan pada data yang divalidasinya.
Satu pengecualian bertipe ValidationError
akan dimunculkan berapapun jumlah kesalahan yang ditemukan, dan ValidationError
tersebut akan berisi informasi tentang semua kesalahan dan bagaimana kesalahan tersebut terjadi.
Lihat Penanganan Kesalahan untuk detail tentang kesalahan standar dan khusus.
Sebagai demonstrasi:
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]
"""
Fungsi pembantu¶
Pydantic menyediakan tiga fungsi pembantu classmethod
pada model untuk parsing data:
model_validate()
: ini sangat mirip dengan metode__init__
model, kecuali dibutuhkan dict atau objek daripada argumen kata kunci. Jika objek yang diteruskan tidak dapat divalidasi, atau jika objek tersebut bukan kamus atau instance model yang dimaksud,ValidationError
akan dimunculkan.model_validate_json()
: ini mengambil str atau byte dan menguraikannya sebagai json , lalu meneruskan hasilnya kemodel_validate()
.-
model_validate_strings()
: ini membutuhkan dict (dapat disarangkan) dengan kunci dan nilai string dan memvalidasi data dalam mode json sehingga string tersebut dapat dipaksa ke tipe yang benar.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] """
Jika Anda ingin memvalidasi data serial dalam format selain JSON, Anda harus memuat sendiri data tersebut ke dalam dict lalu meneruskannya ke model_validate
.
!!! catatan Tergantung pada jenis dan konfigurasi model yang terlibat, model_validate
dan model_validate_json
mungkin memiliki perilaku validasi yang berbeda. Jika Anda memiliki data yang berasal dari sumber non-JSON, tetapi menginginkan perilaku validasi dan kesalahan yang sama seperti yang Anda dapatkan dari model_validate_json
, rekomendasi kami untuk saat ini adalah menggunakan salah satu penggunaan model_validate_json(json.dumps(data))
, atau gunakan model_validate_strings
jika data berbentuk dict (berpotensi bersarang) dengan kunci dan nilai string.
!!! catatan Pelajari lebih lanjut tentang penguraian JSON di bagian JSON pada dokumen.
!!! catatan Jika Anda meneruskan instance model ke model_validate
, Anda sebaiknya mempertimbangkan untuk menyetel revalidate_instances
di konfigurasi model. Jika Anda tidak menetapkan nilai ini, validasi pada instans model akan dilewati. Lihat contoh di bawah ini:
\=== " revalidate_instances='never'
" ```py dari 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 dari impor pydantic 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]
"""
```
Membuat model tanpa validasi¶
Pydantic juga menyediakan metode model_construct()
, yang memungkinkan model dibuat tanpa validasi . Ini dapat berguna setidaknya dalam beberapa kasus:
- ketika bekerja dengan data kompleks yang sudah diketahui validnya (untuk alasan kinerja)
- ketika satu atau lebih fungsi validator tidak idempoten, atau
- ketika satu atau lebih fungsi validator memiliki efek samping yang tidak ingin Anda picu.
!!! catatan Di Pydantic V2, kesenjangan kinerja antara BaseModel.__init__
dan BaseModel.model_construct
telah dipersempit secara signifikan. Untuk model sederhana, memanggil BaseModel.__init__
mungkin lebih cepat. Jika Anda menggunakan model_construct()
karena alasan kinerja, Anda mungkin ingin membuat profil kasus penggunaan Anda sebelum berasumsi bahwa model_construct()
lebih cepat.
!!! peringatan model_construct()
tidak melakukan validasi apa pun, artinya dapat membuat model yang tidak valid. Anda sebaiknya hanya menggunakan metode model_construct()
dengan data yang telah divalidasi, atau yang benar-benar Anda percayai.
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')
Argumen kata kunci _fields_set
ke model_construct()
bersifat opsional, namun memungkinkan Anda untuk lebih tepat mengenai bidang mana yang awalnya disetel dan mana yang tidak. Jika dihilangkan model_fields_set
hanya akan menjadi kunci dari data yang disediakan.
Misalnya, dalam contoh di atas, jika _fields_set
tidak disediakan, new_user.model_fields_set
akan menjadi {'id', 'age', 'name'}
.
Perhatikan bahwa untuk subkelas RootModel
, nilai root dapat diteruskan ke model_construct()
secara posisi, alih-alih menggunakan argumen kata kunci.
Berikut adalah beberapa catatan tambahan tentang perilaku model_construct()
:
- Saat kami mengatakan "tidak ada validasi yang dilakukan" — ini termasuk mengubah dict menjadi instance model. Jadi jika Anda memiliki bidang dengan tipe
Model
, Anda perlu mengonversi sendiri dict bagian dalam menjadi model sebelum meneruskannya kemodel_construct()
.- Secara khusus, metode
model_construct()
tidak mendukung pembuatan model secara rekursif dari dicts.
- Secara khusus, metode
- Jika Anda tidak meneruskan argumen kata kunci untuk bidang dengan default, nilai default akan tetap digunakan.
- Untuk model dengan atribut privat, dict
__pydantic_private__
akan diinisialisasi sama seperti saat memanggil__init__
. - Saat membuat instance menggunakan
model_construct()
, tidak ada metode__init__
dari model atau kelas induknya yang akan dipanggil, bahkan ketika metode__init__
khusus ditentukan.
!!! catatan "Tentang perilaku extra
dengan model_construct
" * Untuk model dengan model_config['extra'] == 'allow'
, data yang tidak sesuai dengan bidang akan disimpan dengan benar di dict __pydantic_extra__
dan disimpan ke __dict__
model. * Untuk model dengan model_config['extra'] == 'ignore'
, data yang tidak sesuai dengan bidang akan diabaikan - yaitu, tidak disimpan di __pydantic_extra__
atau __dict__
pada instance. * Berbeda dengan panggilan ke __init__
, panggilan ke model_construct
dengan model_config['extra'] == 'forbid'
tidak menimbulkan kesalahan jika ada data yang tidak sesuai dengan bidang. Sebaliknya, data masukan tersebut diabaikan begitu saja.
Model umum¶
Pydantic mendukung pembuatan model generik untuk mempermudah penggunaan kembali struktur model umum.
Untuk mendeklarasikan model generik, Anda melakukan langkah-langkah berikut:
- Deklarasikan satu atau lebih instance
typing.TypeVar
yang akan digunakan untuk membuat parameter model Anda. - Deklarasikan model pydantic yang mewarisi dari
pydantic.BaseModel
dantyping.Generic
, tempat Anda meneruskan instanceTypeVar
sebagai parameter ketyping.Generic
. - Gunakan instance
TypeVar
sebagai anotasi di mana Anda ingin menggantinya dengan tipe lain atau model pydantic.
Berikut adalah contoh penggunaan subkelas BaseModel
generik untuk membuat pembungkus payload respons HTTP yang mudah digunakan kembali:
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]
"""
Jika Anda menyetel model_config
atau menggunakan @field_validator
atau dekorator Pydantic lainnya dalam definisi model generik Anda, model tersebut akan diterapkan ke subkelas berparametri dengan cara yang sama seperti saat mewarisi dari subkelas BaseModel
. Metode apa pun yang ditentukan di kelas generik Anda juga akan diwariskan.
Obat generik Pydantic juga terintegrasi dengan baik dengan pemeriksa tipe, sehingga Anda mendapatkan semua pemeriksaan tipe yang Anda harapkan jika Anda mendeklarasikan tipe berbeda untuk setiap parameterisasi.
!!! catatan Secara internal, Pydantic membuat subkelas BaseModel
saat runtime ketika model generik diparametrikan. Kelas-kelas ini di-cache, jadi harus ada overhead minimal yang ditimbulkan oleh penggunaan model generik.
Untuk mewarisi model generik dan mempertahankan fakta bahwa model tersebut generik, subkelas juga harus mewarisi dari typing.Generic
:
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
Anda juga dapat membuat subkelas generik dari BaseModel
yang menggantikan sebagian atau seluruh parameter tipe di superkelas:
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
Jika nama subkelas konkret itu penting, Anda juga dapat mengganti pembuatan nama default:
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')
Anda dapat menggunakan model generik berparametri sebagai tipe di model lain:
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)))
"""
!!! tip Saat menggunakan model generik berparametri sebagai tipe pada model lain (seperti product: ResponseModel[Product]
), pastikan untuk membuat parameter model generik tersebut saat Anda menginisialisasi instance model (seperti response = ResponseModel[Product](content=product)
). Jika tidak, ValidationError
akan dimunculkan, karena Pydantic tidak menyimpulkan tipe model generik berdasarkan data yang diteruskan ke model tersebut.
Menggunakan TypeVar
yang sama dalam model bersarang memungkinkan Anda menerapkan hubungan pengetikan di berbagai titik dalam model Anda:
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]]
"""
Saat menggunakan parameter tipe terikat, dan ketika parameter tipe tidak ditentukan, Pydantic memperlakukan model generik dengan cara yang sama seperti memperlakukan tipe generik bawaan seperti List
dan Dict
:
- Jika Anda tidak menentukan parameter sebelum membuat instance model generik, parameter tersebut akan divalidasi sebagai batasan
TypeVar
. - Jika
TypeVar
yang terlibat tidak memiliki batasan, maka akan diperlakukan sebagaiAny
.
Selain itu, seperti List
dan Dict
, parameter apa pun yang ditentukan menggunakan TypeVar
nantinya dapat diganti dengan tipe konkret:
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
!!! peringatan Meskipun ini mungkin tidak menimbulkan kesalahan, kami sangat menyarankan agar tidak menggunakan obat generik berparametri dalam pemeriksaan isinstance.
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)`.
Jika model Pydantic digunakan dalam batasan TypeVar
dan tipe generik tidak pernah diparametrikan, maka Pydantic akan menggunakan batasan tersebut untuk validasi tetapi memperlakukan nilainya sebagai Any
dalam hal serialisasi:
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',
},
}
Berikut contoh lain dari perilaku di atas, yang menyebutkan semua permutasi terkait spesifikasi terikat dan parameterisasi tipe generik:
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}}
Jika Anda menggunakan default=...
(tersedia dalam Python >= 3.13 atau melalui typing-extensions
) atau batasan ( TypeVar('T', str, int)
; perhatikan bahwa Anda jarang ingin menggunakan bentuk TypeVar
ini ) maka nilai default atau batasan akan digunakan untuk validasi dan serialisasi jika variabel tipe tidak diparametrikan. Anda dapat mengganti perilaku ini menggunakan pydantic.SerializeAsAny
:
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',
},
}
!!! catatan Catatan, Anda mungkin mengalami sedikit masalah jika Anda tidak membuat parameter generik ketika kasus validasi terhadap batasan generik dapat menyebabkan kehilangan data. Lihat contoh di bawah ini:
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}}
- Jika generik tidak diparametrikan, data masukan divalidasi berdasarkan batasan generik. Mengingat
ItemBase
tidak memiliki bidang, informasi bidangitem
akan hilang. - Dalam hal ini, informasi jenis runtime disediakan secara eksplisit melalui parameterisasi generik, sehingga data masukan divalidasi terhadap kelas
IntItem
dan keluaran serialisasi sesuai dengan yang diharapkan.
Pembuatan model dinamis¶
??? api "Dokumentasi API" pydantic.main.create_model
Ada beberapa kesempatan di mana diinginkan untuk membuat model menggunakan informasi runtime untuk menentukan kolom. Untuk ini Pydantic menyediakan fungsi create_model
untuk memungkinkan model dibuat dengan cepat:
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model(
'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
Di sini StaticFoobarModel
dan DynamicFoobarModel
identik.
Bidang ditentukan oleh salah satu bentuk tupel berikut:
(<type>, <default value>)
(<type>, Field(...))
typing.Annotated[<type>, Field(...)]
Menggunakan panggilan Field(...)
sebagai argumen kedua dalam tupel (nilai default) memungkinkan konfigurasi bidang lebih lanjut. Jadi, analoginya adalah sebagai berikut:
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')
Argumen kata kunci khusus __config__
dan __base__
dapat digunakan untuk menyesuaikan model baru. Ini termasuk memperluas model dasar dengan bidang tambahan.
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'])
Anda juga dapat menambahkan validator dengan meneruskan dict ke argumen __validators__
.
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]
"""
!!! catatan Untuk mengambil model yang dibuat secara dinamis:
- the model must be defined globally
- it must provide `__module__`
RootModel
dan tipe root khusus¶
??? api "Dokumentasi API" pydantic.root_model.RootModel
Model Pydantic dapat didefinisikan dengan "tipe root khusus" dengan membuat subkelas pydantic.RootModel
.
Tipe root dapat berupa tipe apa pun yang didukung oleh Pydantic, dan ditentukan oleh parameter generik ke RootModel
. Nilai root dapat diteruskan ke model __init__
atau model_validate
melalui argumen pertama dan satu-satunya.
Berikut ini contoh cara kerjanya:
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'}
Jika Anda ingin mengakses item di kolom root
secara langsung atau mengulangi item, Anda dapat mengimplementasikan fungsi __iter__
dan __getitem__
khusus, seperti yang ditunjukkan dalam contoh berikut.
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']
Anda juga dapat membuat subkelas dari model akar yang diparametrikan secara langsung:
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
Kekekalan palsu¶
Model dapat dikonfigurasi agar tidak dapat diubah melalui model_config['frozen'] = True
. Jika ini disetel, upaya mengubah nilai atribut instance akan menimbulkan kesalahan. Lihat referensi API untuk detail selengkapnya.
!!! catatan Perilaku ini dicapai di Pydantic V1 melalui pengaturan allow_mutation = False
. Bendera konfigurasi ini tidak digunakan lagi di Pydantic V2, dan telah diganti dengan frozen
.
!!! peringatan Di Python, kekekalan tidak diterapkan. Pengembang memiliki kemampuan untuk memodifikasi objek yang secara konvensional dianggap "tidak dapat diubah" jika mereka memilih untuk melakukannya.
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'}
Mencoba mengubah menyebabkan a
, dan a
tidak berubah. Namun, dict b
dapat diubah, dan kekekalan foobar
tidak menghentikan b
untuk diubah.
Kelas dasar abstrak¶
Model Pydantic dapat digunakan bersama dengan Kelas Dasar Abstrak (ABC) Python.
import abc
from pydantic import BaseModel
class FooBarModel(BaseModel, abc.ABC):
a: str
b: int
@abc.abstractmethod
def my_abstract_method(self):
pass
Pemesanan lapangan¶
Urutan bidang memengaruhi model dengan cara berikut:
- urutan bidang dipertahankan dalam skema model
- urutan bidang dipertahankan dalam kesalahan validasi
-
urutan bidang dipertahankan oleh
.model_dump()
dan.model_dump_json()
dll.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',)]¶
Bidang yang wajib diisi¶
Untuk mendeklarasikan suatu bidang sesuai kebutuhan, Anda dapat mendeklarasikannya menggunakan anotasi, atau anotasi yang dikombinasikan dengan spesifikasi Field
. Anda juga dapat menggunakan Ellipsis
/ ...
untuk menekankan bahwa suatu bidang diperlukan, terutama ketika menggunakan konstruktor Field
.
Fungsi Field
terutama digunakan untuk mengonfigurasi pengaturan seperti alias
atau description
untuk suatu atribut. Konstruktor mendukung Ellipsis
/ ...
sebagai satu-satunya argumen posisi. Ini digunakan sebagai cara untuk menunjukkan bahwa bidang tersebut wajib diisi, meskipun petunjuk jenislah yang menerapkan persyaratan ini.
from pydantic import BaseModel, Field
class Model(BaseModel):
a: int
b: int = ...
c: int = Field(..., alias='C')
Di sini a
, b
dan c
semuanya diperlukan. Namun, penggunaan b: int = ...
ini tidak berfungsi dengan baik mypy , dan pada v1.0 harus dihindari dalam banyak kasus.
!!! catatan Di Pydantic V1, bidang yang dianotasi dengan Optional
atau Any
akan diberikan default implisit None
meskipun tidak ada default yang ditentukan secara eksplisit. Perilaku ini telah berubah di Pydantic V2, dan tidak ada lagi anotasi tipe apa pun yang akan menghasilkan bidang yang memiliki nilai default implisit.
See [the migration guide](../migration.md#required-optional-and-nullable-fields) for more details on changes
to required and nullable fields.
Bidang dengan nilai default yang tidak dapat di-hash¶
Sumber bug yang umum di python adalah menggunakan objek yang bisa diubah sebagai nilai default untuk argumen fungsi atau metode, karena instance yang sama akhirnya digunakan kembali di setiap panggilan.
Modul dataclasses
sebenarnya memunculkan kesalahan dalam kasus ini, yang menunjukkan bahwa Anda harus menggunakan argumen default_factory
ke dataclasses.field
.
Pydantic juga mendukung penggunaan default_factory
untuk nilai default yang tidak dapat di-hash, tetapi ini tidak diperlukan. Jika nilai default tidak dapat di-hash, Pydantic akan menyalin nilai default secara mendalam saat membuat setiap instance model:
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)
#> [{}]
Bidang dengan nilai default dinamis¶
Ketika mendeklarasikan sebuah field dengan nilai default, Anda mungkin menginginkannya menjadi dinamis (yaitu berbeda untuk setiap model). Untuk melakukan ini, Anda mungkin ingin menggunakan default_factory
.
Berikut ini contohnya:
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
Anda dapat menemukan informasi lebih lanjut di dokumentasi fungsi Field
.
Atribut yang dikecualikan secara otomatis¶
Variasi kelas¶
Atribut yang dianotasi dengan typing.ClassVar
diperlakukan dengan benar oleh Pydantic sebagai variabel kelas, dan tidak akan menjadi bidang pada contoh model:
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
Atribut model pribadi¶
??? api "Dokumentasi API" pydantic.fields.PrivateAttr
Atribut yang namanya memiliki garis bawah di depan tidak diperlakukan sebagai bidang oleh Pydantic, dan tidak disertakan dalam skema model. Sebaliknya, ini diubah menjadi "atribut pribadi" yang tidak divalidasi atau bahkan disetel selama panggilan ke __init__
, model_validate
, dll.
!!! catatan Pada Pydantic v2.1.0, Anda akan menerima NameError jika mencoba menggunakan fungsi Field
dengan atribut pribadi. Karena atribut privat tidak diperlakukan sebagai kolom, fungsi Field() tidak dapat diterapkan.
Berikut ini contoh penggunaannya:
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
Nama atribut pribadi harus diawali dengan garis bawah untuk mencegah konflik dengan kolom model. Namun, nama-nama bodoh (seperti __attr__
) tidak didukung.
Konversi data¶
Pydantic dapat memasukkan data masukan untuk memaksanya menyesuaikan dengan jenis bidang model, dan dalam beberapa kasus hal ini dapat mengakibatkan hilangnya informasi. Misalnya:
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'}
Ini adalah keputusan Pydantic yang disengaja, dan seringkali merupakan pendekatan yang paling berguna. Lihat di sini untuk diskusi lebih panjang mengenai subjek ini.
Meskipun demikian, pemeriksaan tipe yang ketat juga didukung.
Tanda tangan model¶
Semua model Pydantic akan dibuat tanda tangannya berdasarkan bidangnya:
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
Tanda tangan yang akurat berguna untuk tujuan introspeksi dan perpustakaan seperti FastAPI
atau hypothesis
.
Tanda tangan yang dihasilkan juga akan mengikuti fungsi __init__
khusus:
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
Untuk disertakan dalam tanda tangan, alias atau nama bidang harus berupa pengidentifikasi Python yang valid. Pydantic akan memprioritaskan alias bidang di atas namanya saat membuat tanda tangan, tetapi dapat menggunakan nama bidang jika alias tersebut bukan pengidentifikasi Python yang valid.
Jika alias dan nama bidang keduanya bukan pengidentifikasi yang valid (yang mungkin terjadi melalui penggunaan create_model
), argumen **data
akan ditambahkan. Selain itu, argumen **data
akan selalu ada di tanda tangan jika model_config['extra'] == 'allow'
.
Pencocokan pola struktural¶
Pydantic mendukung pencocokan pola struktural untuk model, seperti yang diperkenalkan oleh PEP 636 dengan Python 3.10.
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')
!!! catatan Pernyataan kasus pencocokan mungkin tampak seolah-olah menciptakan model baru, tapi jangan tertipu; itu hanyalah gula sintaksis untuk mendapatkan atribut dan membandingkannya atau mendeklarasikan dan menginisialisasinya.
Salinan atribut¶
Dalam banyak kasus, argumen yang diteruskan ke konstruktor akan disalin untuk melakukan validasi dan, jika perlu, pemaksaan.
Dalam contoh ini, perhatikan bahwa ID daftar berubah setelah kelas dibuat karena telah disalin selama validasi:
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
!!! catatan Ada beberapa situasi di mana Pydantic tidak menyalin atribut, seperti saat meneruskan model — kami menggunakan model apa adanya. Anda dapat mengganti perilaku ini dengan menyetel model_config['revalidate_instances'] = 'always'
.
Bidang tambahan¶
Secara default, model Pydantic tidak akan error saat Anda memberikan data untuk kolom yang tidak dikenal, model tersebut hanya akan diabaikan:
from pydantic import BaseModel
class Model(BaseModel):
x: int
m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}
Jika Anda ingin ini menimbulkan kesalahan, Anda dapat melakukannya melalui model_config
:
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]
"""
Untuk mempertahankan data tambahan yang diberikan, Anda dapat mengatur extra='allow'
. Bidang tambahan kemudian akan disimpan di BaseModel.__pydantic_extra__
:
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'}
Secara default, tidak ada validasi yang akan diterapkan pada item tambahan ini, namun Anda dapat menetapkan tipe untuk nilai dengan mengganti anotasi tipe untuk __pydantic_extra__
:
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}
= Field(init=False)
tidak memiliki efek apa pun saat runtime, namun mencegah bidang__pydantic_extra__
diperlakukan sebagai argumen terhadap metode__init__
model oleh pemeriksa tipe.
Konfigurasi yang sama berlaku untuk TypedDict
dan dataclass
' kecuali konfigurasi dikontrol dengan menyetel atribut __pydantic_config__
kelas ke ConfigDict
yang valid.
本文总阅读量次