Diversity w polskim IT
Ng Wai Foong
Ng Wai FoongAI Engineer @ YOOZOO GAMES

Przewodnik dla początkujących po Pydantic Pythona

Poznaj Pydantic, czyli pakiet Pythona do analizy i walidacji danych.
22.09.20217 min
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:

  1. Instalacja
  2. Implementacja
  3. 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ługuje Literalw przed wersją Pythona 3.8.
  • python-dotenv– Wsparcie dla pliku dotenv 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 modelu
  • json()– zwraca słownik, ale w formie JSON-a
  • copy()– zwraca dokładną kopię modelu
  • parse_obj()– ładuje dowolny obiekt do modelu z obsługą błędów, jeśli obiekt nie jest słownikiem
  • parse_raw()– narzędzie do wczytywania ciągów znaków o różnych formatach
  • parse_field()– analogicznie do parse_raw() ale przeznaczona dla plików
  • from_orm()– ładuje dane do modelu z dowolnej klasy
  • schema()– zwraca słownik reprezentujący model jako schemat JSON
  • schema_json()– zwraca ciąg znaków JSON dla funkcji schema()
  • 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.

<p>Loading...</p>