Przewodnik dla początkujących po Pydantic Pythona
Dzisiejszy temat dotyczy walidacji danych i zarządzania ustawieniami przy użyciu podpowiadania typów (type hinting) w Pythonie. Będziemy używać pakietu Pythona o nazwie pydantic, który wymusza użycie podpowiedzi typów w czasie wykonywania. Zapewnia przyjazne dla użytkownika błędy, pozwalając na wychwycenie wszelkich niepoprawnych danych. Na podstawie oficjalnej dokumentacji Pydantic to:
(...) przede wszystkim biblioteka do parsowania, a nie walidacji. Walidacja jest środkiem do celu: zbudowania modelu, który jest zgodny z podanymi typami i ograniczeniami. Innymi słowy pydantic gwarantuje typy i ograniczenia w modelu wyjściowym, a nie danych wejściowych.
Poradnik ten podzieliłem na 3 sekcje:
- Instalacja
- Implementacja
- Wnioski
Przejdźmy do następnej sekcji i rozpocznijmy instalację niezbędnych modułów.
1. Instalacja
Przed przystąpieniem do instalacji zaleca się utworzenie środowiska wirtualnego.
Instalacja podstawowa
Otwórz terminal i uruchom następujące polecenie, aby zainstalować pydantic
pip install pydantic
Zaktualizuj istniejący pakiet
Jeśli posiadasz już istniejący pakiet i chciałbyś go zaktualizować, wykonaj następujące polecenie:
pip install -U pydantic
Anaconda
Użytkownicy Anacondy mogą go zainstalować w następujący sposób:
conda install pydantic -c conda-forge
Zależności opcjonalne
pydantic
jest dostarczany z następującymi opcjonalnymi zależnościami w zależności od potrzeb:
email-validator
– Wsparcie dla walidacji maili.typing-extensions
– ObsługujeLiteralw
przed wersją Pythona 3.8.python-dotenv
– Wsparcie dla plikudotenv
z ustawieniami.
Można je zainstalować ręcznie:
# install email-validator
pip install email-validator
# install typing-extensions
pip install typing_extensions
# install python-dotenv
pip install python-dotenv
lub wraz z pydantic
w następujący sposób:
# install email-validator
pip install pydantic[email]
# install typing-extensions
pip install pydantic[typing_extensions]
# install python-dotenv
pip install pydantic[dotenv]
# install all dependencies
pip install pydantic[email,typing_extensions,dotenv]
2. Implementacja
Teraz poznamy niektóre z przydatnych funkcji dostępnych w pydantic
.
Zdefiniowanie obiektu w pydantic
jest tak proste jak stworzenie nowej klasy, która dziedziczy po BaseModel
. Kiedy tworzysz nowy obiekt z tej klasy, pydantic
gwarantuje, że pola wynikowej instancji modelu będą zgodne z typami pól zdefiniowanymi w modelu.
Import
Dodaj następującą deklarację importu na górze swojego pliku Pythona.
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel
Klasa User
Zadeklaruj nową klasę, która dziedziczy po BaseModel
w następujący sposób:
class User(BaseModel):
id: int
username : str
password : str
confirm_password : str
alias = 'anonymous'
timestamp: Optional[datetime] = None
friends: List[int] = []
pydantic
używa wbudowanej składni podpowiadania typów do określenia typu danych każdej zmiennej. Zbadajmy po kolei, co dzieje się za kulisami.
id
– Zmienna typu integer reprezentuje identyfikator. Ponieważ nie podano wartości domyślnej, pole to jest wymagane i musi zostać określone podczas tworzenia obiektu. Ciągi znaków, bajty lub floaty zostaną zamienione na liczby całkowite, jeśli to możliwe. W przeciwnym razie zostanie zgłoszony wyjątek.username
– Ten ciąg znaków reprezentuje nazwę użytkownika i jest wymagana.password
– Zmienna będąca ciągiem znaków, reprezentuje hasło i jest wymagana.confirm_password
– Zmienna ciągu znaków reprezentuje hasło potwierdzające i jest wymagana. Zostanie to później użyte do walidacji danych.alias
– Zmienna ciągu znaków reprezentuje alias. Nie jest wymagana i zostanie ustawiony na ‘anonymous’, jeśli nie zostanie podany podczas tworzenia obiektu.timestamp
– Pole daty/czasu, które nie jest wymagane. Domyślnie None. pydantic przetworzy albo int z unix timestamp albo ciąg znaków reprezentujący datę/czas.friends
– Lista liczb całkowitych.
Inicjalizacja obiektu
Następnym krokiem jest stworzenie instancji nowego obiektu z klasy User
.
data = {'id': '1234', 'username': 'wai foong', 'password': 'Password123', 'confirm_password': 'Password123', 'timestamp': '2020-08-03 10:30', 'friends': [1, '2', b'3']}
user = User(**data)
Po wpisaniu zmiennej user
powinieneś otrzymać następujące dane wyjściowe. Możesz zauważyć, że id
zostało automatycznie przekonwertowane na liczbę całkowitą, mimo że w danych wejściowych był ciąg znaków. Podobnie bajty są automatycznie konwertowane na liczby całkowite, co pokazuje pole friends
.
id=1234 username='wai foong' password='Password123' confirm_password='Password123' timestamp=datetime.datetime(2020, 8, 3, 10, 30) friends=[1, 2, 3] alias='anonymous'
Metody i atrybuty w ramach BaseModel
Klasy, które dziedziczą po BaseModel
, będą miały następujące metody i atrybuty:
dict()
– zwraca słownik pól i wartości modelujson()
– zwraca słownik, ale w formie JSON-acopy()
– zwraca dokładną kopię modeluparse_obj()
– ładuje dowolny obiekt do modelu z obsługą błędów, jeśli obiekt nie jest słownikiemparse_raw()
– narzędzie do wczytywania ciągów znaków o różnych formatachparse_field()
– analogicznie doparse_raw()
ale przeznaczona dla plikówfrom_orm()
– ładuje dane do modelu z dowolnej klasyschema()
– zwraca słownik reprezentujący model jako schemat JSONschema_json()
– zwraca ciąg znaków JSON dla funkcjischema()
construct()
– metoda klasy do tworzenia modeli bez uruchamiania walidacji__fields_set__
– zbiór nazw pól, które zostały ustawione podczas inicjalizacji modelu__fields__
– słownik pól modelu__config__
– klasa konfiguracyjna dla modelu
Zmieńmy dane wejściowe dla id
na ciąg znaków w następujący sposób:
data = {'id': 'a random string', 'username': 'wai foong', 'password': 'Password123', 'confirm_password': 'Password123', 'timestamp': '2020-08-03 10:30', 'friends': [1, '2', b'3']}
user = User(**data)
Po uruchomieniu kodu powinieneś otrzymać następujący błąd.
value is not a valid integer (type=type_error.integer)
Błąd walidacji
Aby uzyskać więcej szczegółów na temat błędu, zaleca się owinięcie go wewnątrz bloku try-catch, jak poniżej:
from pydantic import BaseModel, ValidationError
# ... codes for User class
data = {'id': 'a random string', 'username': 'wai foong', 'password': 'Password123', 'confirm_password': 'Password123', 'timestamp': '2020-08-03 10:30', 'friends': [1, '2', b'3']}
try:
user = User(**data)
except ValidationError as e:
print(e.json())
Wygeneruje on następujący JSON, który wskazuje, że dane wejściowe dla id
nie są poprawną liczbą całkowitą.
[
{
"loc": [
"id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
Rodzaje pól
pydantic
zapewnia wsparcie dla większości popularnych typów z biblioteki standardowej Pythona. Lista ta obejmuje:
- bool
- int
- float
- str
- bytes
- list
- tuple
- dict
- set
- frozenset
- datetime.date
- datetime.time
- datetime.datetime
- datetime.timedelta
- typing.Any
- typing.TypeVar
- typing.Union
- typing.Optional
- typing.List
- typing.Tuple
- typing.Dict
- typing.Set
- typing.FrozenSet
- typing.Sequence
- typing.Iterable
- typing.Type
- typing.Callable
- typing.Pattern
- ipaddress.IPv4Address
- ipaddress.IPv4Interface
- ipaddress.IPv4Network
- ipaddress.IPv6Address
- ipaddress.IPv6Interface
- ipaddress.IPv6Network
- enum.Enum
- enum.IntEnum
- decimal.Decimal
- pathlib.Path
- uuid.UUID
- ByteSize
Constrained types
Możesz zastosować własne ograniczenia poprzez constrained types
. Przyjrzyjmy się następującemu przykładowi:
from pydantic import (
BaseModel,
NegativeInt,
PositiveInt,
conint,
conlist,
constr
)
class Model(BaseModel):
# minimum length of 2 and maximum length of 10
short_str: constr(min_length=2, max_length=10)
# regex
regex_str: constr(regex=r'^apple (pie|tart|sandwich)$')
# remove whitespace from string
strip_str: constr(strip_whitespace=True)
# value must be greater than 1000 and less than 1024
big_int: conint(gt=1000, lt=1024)
# value is multiple of 5
mod_int: conint(multiple_of=5)
# must be a positive integer
pos_int: PositiveInt
# must be a negative integer
neg_int: NegativeInt
# list of integers that contains 1 to 4 items
short_list: conlist(int, min_items=1, max_items=4)
Strict Types
Jeśli szukasz sztywnych ograniczeń, które przechodzą walidację wtedy i tylko wtedy, gdy walidowana wartość jest odpowiedniego typu lub jest podtypem tego typu, możesz użyć następujących ścisłych typów:
- StrictStr
- StrictInt
- StrictFloat
- StrictBool
Poniższy przykład ilustruje poprawny sposób wymuszenia StrictBool
w Twojej klasie dziedziczącej.
from pydantic import BaseModel, StrictBool,
class StrictBoolModel(BaseModel):
strict_bool: StrictBool
Ciąg znaków 'False'
spowoduje ValidationError, ponieważ akceptuje on tylko True
lub False
jako dane wejściowe.
Validator
Ponadto, możesz tworzyć własne walidatory używając dekoratora validator
wewnątrz dziedziczonej klasy. Przyjrzyjmy się następującemu przykładowi, który określa czy id
jest czterocyfrowe oraz czy confirm_password
pasuje do pola password
.
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ValidationError, validator
class User(BaseModel):
id: int
username : str
password : str
confirm_password : str
alias = 'anonymous'
timestamp: Optional[datetime] = None
friends: List[int] = []
@validator('id')
def id_must_be_4_digits(cls, v):
if len(str(v)) != 4:
raise ValueError('must be 4 digits')
return v
@validator('confirm_password')
def passwords_match(cls, v, values, **kwargs):
if 'password' in values and v != values['password']:
raise ValueError('passwords do not match')
return v
3. Wnioski
Podsumujmy, czego się dziś dowiedzieliśmy.
Zaczęliśmy od szczegółowego wyjaśnienia czym jest Pydantic, i powiedzieliśmy, że pomaga w parsowaniu i walidacji danych.
Następnie utworzyliśmy wirtualne środowisko i zainstalowaliśmy Pydantic za pomocą pip lub conda. Zawiera również wsparcie dla trzech dodatkowych zależności, które możemy użyć w razie potrzeby.
Po zakończeniu instalacji zbadaliśmy dogłębnie podstawowe funkcje oferowane przez pakiet. Podstawowym elementem jest stworzenie nowej klasy, która dziedziczy po BaseModel
.
Dowiedzieliśmy się, że Pydantic zapewnia wsparcie dla większości popularnych typów danych w ramach biblioteki standardowej Pythona. Przetestowaliśmy zarówno Constrained Types
, jak i Strict Types
, które pomagają egzekwować nasze własne ograniczenia.
Na koniec pobawiliśmy się trochę z dekoratorem validator
, aby umożliwić tylko cztery cyfry dla id
, i żeby confirm_password
pasowało do pola password
.
Dziękuję za przeczytanie tego artykułu. Mam nadzieję, że okaże się dla Ciebie przydatny.
Oryginał tekstu w języku angielskim przeczytasz tutaj.