Validatie-decorator
??? api "API-documentatie" pydantic.validate_call_decorator.validate_call
Met de decorator @validate_call
kunnen de argumenten die aan een functie worden doorgegeven, worden geparseerd en gevalideerd met behulp van de annotaties van de functie voordat de functie wordt aangeroepen.
Hoewel dit onder de motorkap dezelfde aanpak voor het maken en initialiseren van modellen gebruikt (zie Validators voor meer details), biedt het een uiterst eenvoudige manier om validatie op uw code toe te passen met minimale standaardregels.
Voorbeeld van gebruik:
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]
"""
Argumenttypen¶
Argumenttypen worden afgeleid uit typeannotaties op de functie, argumenten zonder type-decorator worden beschouwd als Any
. Alle typen vermeld in typen kunnen worden gevalideerd, inclusief Pydantic-modellen en aangepaste typen . Net als bij de rest van Pydantic kunnen typen door de binnenhuisarchitect worden gedwongen voordat ze worden doorgegeven aan de daadwerkelijke functie:
# 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))
Een paar opmerkingen:
- Hoewel ze als tekenreeksen worden doorgegeven, worden
path
enregex
door de decorateur geconverteerd naar respectievelijk eenPath
object en een regex. max
heeft geen typeannotatie, dus wordt door de binnenhuisarchitect alsAny
beschouwd.
Dit soort dwang kan uiterst nuttig zijn, maar ook verwarrend of ongewenst. Zie Dwang en strengheid voor een bespreking van de beperkingen van @validate_call
in dit opzicht.
Functie handtekeningen¶
De @validate_call
decorateur is ontworpen om te werken met functies die alle mogelijke parameterconfiguraties en alle mogelijke combinaties hiervan gebruiken:
- Positionele of trefwoordargumenten met of zonder standaardwaarden.
- Variabele positionele argumenten gedefinieerd via
*
(vaak*args
). - Variabele trefwoordargumenten gedefinieerd via
**
(vaak**kwargs
). - Argumenten met alleen trefwoorden: argumenten na
*,
. - Alleen-positionele argumenten: argumenten vóór
, /
(nieuw in Python 3.8).
Om alle bovenstaande parametertypen te demonstreren:
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}
Veld gebruiken om functieargumenten te beschrijven¶
Veld kan ook worden gebruikt met @validate_call
om extra informatie over het veld en validaties te geven. Over het algemeen moet het worden gebruikt in een type hint met Annotated , tenzij default_factory
is opgegeven, in welk geval het moet worden gebruikt als de standaardwaarde van het veld:
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'>
De alias
kan normaal bij de binnenhuisarchitect worden gebruikt.
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)
Gebruik met mypy¶
De validate_call
decorateur zou "out of the box" moeten werken met mypy, omdat deze is gedefinieerd om een functie terug te geven met dezelfde handtekening als de functie die hij versiert. De enige beperking is dat, aangezien we Mypy laten denken dat de door de decorateur geretourneerde functie dezelfde is als de functie die wordt gedecoreerd; voor toegang tot de onbewerkte functie of andere attributen is type: ignore
.
Ruwe functie¶
De onbewerkte functie die is ingericht is toegankelijk, dit is handig als u in sommige scenario's uw invoerargumenten vertrouwt en de functie op de meest performante manier wilt aanroepen (zie opmerkingen over prestaties hieronder):
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'
Asynchrone functies¶
@validate_call
kan ook worden gebruikt voor asynchrone functies:
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'`
Aangepaste configuratie¶
Het model achter @validate_call
kan worden aangepast met behulp van een config
instelling, die gelijk is aan het instellen van de subklasse ConfigDict
in normale modellen.
De configuratie wordt ingesteld met behulp van het trefwoord config
voor de decorateur. Het kan een configuratieklasse zijn of een dictaat van eigenschappen die later naar een klasse worden geconverteerd.
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]
"""
Extensie — argumenten valideren voordat een functie wordt aangeroepen¶
In sommige gevallen kan het nuttig zijn om de validatie van de argumenten van een functie te scheiden van de functieaanroep zelf. Dit kan handig zijn als een bepaalde functie kostbaar/tijdrovend is.
Hier is een voorbeeld van een oplossing die u voor dat patroon kunt gebruiken:
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
Beperkingen¶
Validatie-uitzondering¶
Momenteel wordt bij een mislukte validatie een standaard Pydantic ValidationError
gegenereerd. Zie modelfoutafhandeling voor meer informatie.
Dit is handig omdat de str()
methode nuttige details biedt over de fout die is opgetreden en methoden als .errors()
en .json()
nuttig kunnen zijn bij het blootstellen van de fouten aan eindgebruikers. ValidationError
erft echter van ValueError
en niet van TypeError
, wat onverwacht kan zijn omdat Python een TypeError
zou genereren bij ongeldige of ontbrekende argumenten. Dit kan in de toekomst worden verholpen door een aangepaste fout toe te staan of standaard een andere uitzondering te genereren, of door beide.
Dwang en strengheid¶
Pydantic leunt momenteel aan de kant van het proberen typen af te dwingen in plaats van een fout te genereren als een type verkeerd is, zie modelgegevensconversie en @validate_call
is niet anders.
Prestatie¶
We hebben veel moeite gedaan om Pydantic zo performant mogelijk te maken en argumentinspectie en het maken van modellen worden slechts één keer uitgevoerd wanneer de functie is gedefinieerd, maar er zal nog steeds een prestatie-impact zijn bij het gebruik van de @validate_call
decorator in vergelijking met het aanroepen van de onbewerkte functie .
In veel situaties zal dit weinig of geen merkbaar effect hebben. Houd er echter rekening mee dat @validate_call
geen equivalent of alternatief is voor functiedefinities in sterk getypeerde talen; dat zal het nooit zijn.
Retourwaarde¶
De retourwaarde van de functie kan optioneel worden gevalideerd aan de hand van de annotatie van het retourtype.
本文总阅读量次