Dekorator Validasi
??? api "Dokumentasi API" pydantic.validate_call_decorator.validate_call
Dekorator @validate_call
memungkinkan argumen yang diteruskan ke suatu fungsi diurai dan divalidasi menggunakan anotasi fungsi sebelum fungsi tersebut dipanggil.
Meskipun pada dasarnya ini menggunakan pendekatan pembuatan dan inisialisasi model yang sama (lihat Validator untuk detail lebih lanjut), ini memberikan cara yang sangat mudah untuk menerapkan validasi ke kode Anda dengan boilerplate minimal.
Contoh penggunaan:
from pydantic import ValidationError, validate_call
@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
b = s.encode()
return separator.join(b for _ in range(count))
a = repeat('hello', 3)
print(a)
#> b'hellohellohello'
b = repeat('x', '4', separator=b' ')
print(b)
#> b'x x x x'
try:
c = repeat('hello', 'wrong')
except ValidationError as exc:
print(exc)
"""
1 validation error for repeat
1
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='wrong', input_type=str]
"""
Jenis argumen¶
Tipe argumen disimpulkan dari anotasi tipe pada fungsi, argumen tanpa dekorator tipe dianggap sebagai Any
. Semua tipe yang tercantum dalam tipe dapat divalidasi, termasuk model Pydantic dan tipe khusus . Seperti Pydantic lainnya, tipe dapat dipaksa oleh dekorator sebelum diteruskan ke fungsi sebenarnya:
# TODO replace find_file with something that isn't affected the filesystem
import os
from pathlib import Path
from typing import Optional, Pattern
from pydantic import DirectoryPath, validate_call
@validate_call
def find_file(path: DirectoryPath, regex: Pattern, max=None) -> Optional[Path]:
for i, f in enumerate(path.glob('**/*')):
if max and i > max:
return
if f.is_file() and regex.fullmatch(str(f.relative_to(path))):
return f
# note: this_dir is a string here
this_dir = os.path.dirname(__file__)
print(find_file(this_dir, '^validation.*'))
print(find_file(this_dir, '^foobar.*', max=3))
Beberapa catatan:
- Meskipun diteruskan sebagai string,
path
danregex
masing-masing dikonversi menjadi objekPath
dan regex oleh dekorator. max
tidak memiliki anotasi tipe, sehingga akan dianggap sebagaiAny
oleh dekorator.
Jenis paksaan seperti ini bisa sangat membantu, tetapi juga membingungkan atau tidak diinginkan. Lihat Pemaksaan dan ketegasan untuk diskusi tentang batasan @validate_call
dalam hal ini.
Tanda tangan fungsi¶
Dekorator @validate_call
dirancang untuk bekerja dengan fungsi menggunakan semua kemungkinan konfigurasi parameter dan semua kemungkinan kombinasi berikut ini:
- Argumen posisi atau kata kunci dengan atau tanpa default.
- Argumen posisi variabel ditentukan melalui
*
(seringkali*args
). - Argumen kata kunci variabel ditentukan melalui
**
(seringkali**kwargs
). - Argumen khusus kata kunci: argumen setelah
*,
. - Argumen khusus posisi: argumen sebelum
, /
(baru di Python 3.8).
Untuk mendemonstrasikan semua tipe parameter di atas:
from pydantic import validate_call
@validate_call
def pos_or_kw(a: int, b: int = 2) -> str:
return f'a={a} b={b}'
print(pos_or_kw(1))
#> a=1 b=2
print(pos_or_kw(a=1))
#> a=1 b=2
print(pos_or_kw(1, 3))
#> a=1 b=3
print(pos_or_kw(a=1, b=3))
#> a=1 b=3
@validate_call
def kw_only(*, a: int, b: int = 2) -> str:
return f'a={a} b={b}'
print(kw_only(a=1))
#> a=1 b=2
print(kw_only(a=1, b=3))
#> a=1 b=3
@validate_call
def pos_only(a: int, b: int = 2, /) -> str: # python 3.8 only
return f'a={a} b={b}'
print(pos_only(1))
#> a=1 b=2
print(pos_only(1, 2))
#> a=1 b=2
@validate_call
def var_args(*args: int) -> str:
return str(args)
print(var_args(1))
#> (1,)
print(var_args(1, 2))
#> (1, 2)
print(var_args(1, 2, 3))
#> (1, 2, 3)
@validate_call
def var_kwargs(**kwargs: int) -> str:
return str(kwargs)
print(var_kwargs(a=1))
#> {'a': 1}
print(var_kwargs(a=1, b=2))
#> {'a': 1, 'b': 2}
@validate_call
def armageddon(
a: int,
/, # python 3.8 only
b: int,
*c: int,
d: int,
e: int = None,
**f: int,
) -> str:
return f'a={a} b={b} c={c} d={d} e={e} f={f}'
print(armageddon(1, 2, d=3))
#> a=1 b=2 c=() d=3 e=None f={}
print(armageddon(1, 2, 3, 4, 5, 6, d=8, e=9, f=10, spam=11))
#> a=1 b=2 c=(3, 4, 5, 6) d=8 e=9 f={'f': 10, 'spam': 11}
Menggunakan Field untuk mendeskripsikan argumen fungsi¶
Bidang juga dapat digunakan dengan @validate_call
untuk memberikan informasi tambahan tentang bidang dan validasi. Secara umum ini harus digunakan dalam petunjuk tipe dengan Annotated , kecuali default_factory
ditentukan, dalam hal ini harus digunakan sebagai nilai default bidang:
from datetime import datetime
from typing_extensions import Annotated
from pydantic import Field, ValidationError, validate_call
@validate_call
def how_many(num: Annotated[int, Field(gt=10)]):
return num
try:
how_many(1)
except ValidationError as e:
print(e)
"""
1 validation error for how_many
0
Input should be greater than 10 [type=greater_than, input_value=1, input_type=int]
"""
@validate_call
def when(dt: datetime = Field(default_factory=datetime.now)):
return dt
print(type(when()))
#> <class 'datetime.datetime'>
alias
tersebut dapat digunakan dengan dekorator seperti biasa.
from typing_extensions import Annotated
from pydantic import Field, validate_call
@validate_call
def how_many(num: Annotated[int, Field(gt=10, alias='number')]):
return num
how_many(number=42)
Penggunaan dengan mypy¶
Dekorator validate_call
harus bekerja "di luar kotak" dengan mypy karena didefinisikan untuk mengembalikan fungsi dengan tanda tangan yang sama dengan fungsi yang didekorasi. Satu-satunya batasan adalah karena kita mengelabui mypy dengan berpikir bahwa fungsi yang dikembalikan oleh dekorator sama dengan fungsi yang didekorasi; akses ke fungsi mentah atau atribut lainnya memerlukan type: ignore
.
Fungsi mentah¶
Fungsi mentah yang didekorasi dapat diakses, ini berguna jika dalam beberapa skenario Anda memercayai argumen masukan Anda dan ingin memanggil fungsi tersebut dengan cara yang paling berkinerja (lihat catatan kinerja di bawah):
from pydantic import validate_call
@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
b = s.encode()
return separator.join(b for _ in range(count))
a = repeat('hello', 3)
print(a)
#> b'hellohellohello'
b = repeat.raw_function('good bye', 2, separator=b', ')
print(b)
#> b'good bye, good bye'
Fungsi asinkron¶
@validate_call
juga dapat digunakan pada fungsi async:
class Connection:
async def execute(self, sql, *args):
return 'testing@example.com'
conn = Connection()
# ignore-above
import asyncio
from pydantic import PositiveInt, ValidationError, validate_call
@validate_call
async def get_user_email(user_id: PositiveInt):
# `conn` is some fictional connection to a database
email = await conn.execute('select email from users where id=$1', user_id)
if email is None:
raise RuntimeError('user not found')
else:
return email
async def main():
email = await get_user_email(123)
print(email)
#> testing@example.com
try:
await get_user_email(-4)
except ValidationError as exc:
print(exc.errors())
"""
[
{
'type': 'greater_than',
'loc': (0,),
'msg': 'Input should be greater than 0',
'input': -4,
'ctx': {'gt': 0},
'url': 'https://errors.pydantic.dev/2/v/greater_than',
}
]
"""
asyncio.run(main())
# requires: `conn.execute()` that will return `'testing@example.com'`
Konfigurasi khusus¶
Model di belakang @validate_call
dapat dikustomisasi menggunakan pengaturan config
, yang setara dengan pengaturan subkelas ConfigDict
dalam model normal.
Konfigurasi diatur menggunakan argumen kata kunci config
ke dekorator, dapat berupa kelas konfigurasi atau dikt properti yang nantinya akan dikonversi ke kelas.
from pydantic import ValidationError, validate_call
class Foobar:
def __init__(self, v: str):
self.v = v
def __add__(self, other: 'Foobar') -> str:
return f'{self} + {other}'
def __str__(self) -> str:
return f'Foobar({self.v})'
@validate_call(config=dict(arbitrary_types_allowed=True))
def add_foobars(a: Foobar, b: Foobar):
return a + b
c = add_foobars(Foobar('a'), Foobar('b'))
print(c)
#> Foobar(a) + Foobar(b)
try:
add_foobars(1, 2)
except ValidationError as e:
print(e)
"""
2 validation errors for add_foobars
0
Input should be an instance of Foobar [type=is_instance_of, input_value=1, input_type=int]
1
Input should be an instance of Foobar [type=is_instance_of, input_value=2, input_type=int]
"""
Ekstensi — memvalidasi argumen sebelum memanggil suatu fungsi¶
Dalam beberapa kasus, mungkin berguna untuk memisahkan validasi argumen suatu fungsi dari pemanggilan fungsi itu sendiri. Ini mungkin berguna ketika fungsi tertentu mahal/memakan waktu.
Berikut ini contoh solusi yang dapat Anda gunakan untuk pola tersebut:
from pydantic import validate_call
@validate_call
def validate_foo(a: int, b: int):
def foo():
return a + b
return foo
foo = validate_foo(a=1, b=2)
print(foo())
#> 3
Keterbatasan¶
Pengecualian validasi¶
Saat ini setelah kegagalan validasi, Pydantic [ValidationError
] standar [pydantic_core.ValidationError] dimunculkan. Lihat penanganan kesalahan model untuk detailnya.
Hal ini berguna karena metode str()
memberikan detail berguna tentang kesalahan yang terjadi dan metode seperti .errors()
dan .json()
dapat berguna saat memaparkan kesalahan kepada pengguna akhir. Namun, ValidationError
mewarisi dari ValueError
bukan TypeError
, yang mungkin tidak terduga karena Python akan memunculkan TypeError
pada argumen yang tidak valid atau hilang. Hal ini dapat diatasi di masa mendatang dengan mengizinkan kesalahan khusus atau memunculkan pengecualian yang berbeda secara default, atau keduanya.
Pemaksaan dan ketegasan¶
Pydantic saat ini cenderung mencoba memaksakan tipe daripada memunculkan kesalahan jika suatu tipe salah, lihat konversi data model dan @validate_call
juga demikian.
Pertunjukan¶
Kami telah melakukan upaya besar untuk menjadikan Pydantic berkinerja sebaik mungkin dan pemeriksaan argumen serta pembuatan model hanya dilakukan satu kali ketika fungsi ditentukan, namun masih akan ada dampak kinerja menggunakan dekorator @validate_call
dibandingkan dengan memanggil fungsi mentah .
Dalam banyak situasi, hal ini hanya mempunyai sedikit atau tidak ada efek nyata, namun perlu diketahui bahwa @validate_call
bukanlah padanan atau alternatif untuk definisi fungsi dalam bahasa yang sangat diketik; itu tidak akan pernah terjadi.
Nilai kembalian¶
Nilai kembalian fungsi secara opsional dapat divalidasi berdasarkan anotasi tipe kembaliannya.
本文总阅读量次