Jednym ze sposobów przechowywania zorganizowanych danych w Pythonie są klasy. Można użyć normalnej klasy z metodą __init__, można też tworzyć modele za pomocą wbudowanych modułów Pythona, takich jak dataclasses. Jednak jedną z bardziej rozbudowanych i wygodniejszych opcji jest biblioteka Pydantic. Umożliwia ona modelowanie, walidację i modyfikowanie danych w bardzo wygodny sposób. Spójrzmy na praktyczny przykład - załóżmy, że chcemy stworzyć zwykłą to-do listę. Będziemy potrzebować modelu ListItem i modelu samej listy, nazwijmy go ToDoList. ListItem powinien mieć treść i flagę mówiącą, czy task jest skończony, czy nie. ToDoList powinna mieć nazwę i listę obiektów klasy ListItem jako membera. W vanilla Pythonie wygląda to tak:

class ListItem:

    def __init__(self, content: str, done: bool = False):
        self.content = content
        self.done = done
   
        
class ToDoList:
    
    def __init__(self, name: str, items: typing.List[ListItem]):
        self.name = name
        self.items = items

Problem pojawia się już na samym początku, mianowicie tu:

list_item1 = ListItem(123, False)

Jak możemy zabezpieczyć naszą klasę przed złym typem atrybutu, który może być zagrożeniem przy zapisie danych do bazy lub wprowadzaniu danych od strony użytkownika?

Mamy taką opcję:

def __init__(self, content: str, done: bool = False):
        if not isinstance(content, str):
            raise Exception("Wrong type of content")
            
        self.content = content
        self.done = done

Z naszego kodu w tym momencie zaczyna robić się niezłe spaghetti. Zabezpieczenie w ten sposób każdej klasy będzie niepraktyczne, pracochłonne i podatne na błędy. Jest jeszcze jeden problem:

print(list_item1)  # <__main__.ListItem object at 0x7f641ca7d4c0>

Nie mamy szybkiego dostępu do zawartości naszej klasy w razie potrzeby sprawdzenia jej printem podczas modyfikacji lub “testowania” naszego kodu. Zawsze możemy dodać metodę __repr__, która zmieni reprezentację obiektu, jednak są to kolejne linijki kodu do napisania. Te problemy rozwiązuje Pydantic. Zobaczmy jak wygląda nasz kod po konwersji na Pydantic:

from pydantic import BaseModel
import typing


class ListItem(BaseModel):
	content: str
	done: bool = False


class ToDoList(BaseModel):
	name: str
	items: typing.List[ListItem]

A teraz spójrzmy jak zachowuje się nasza klasa w kwestii walidacji i reprezentacji:

list_item1 = ListItem(content=123, done=True)
print(list_item)  # ListItem(content='123', done=True)

Pydantic przekonwertuje typ atrybutu, jeśli jest to możliwe, lub wyrzuci nam wyjątek, jeśli nie ma możliwości konwersji. Dodatkowo od razu otrzymujemy czytelną i pełną informacji reprezentację instancji. Dostajemy także wygodny sposób tworzenia słownika z danych naszej instancji poprzez metodę .dict(). A co jeśli chcemy dokonać bardziej złożonej walidacji? Załóżmy, że mamy klasę reprezentującą człowieka, która przechowuje numer PESEL. Dla uproszczenia przyjmijmy, że mamy funkcję sprawdzającą liczbę kontrolną numeru PESEL, zwracającą True lub False:

from pydantic import BaseModel, validator


class Human(BaseModel):
	pesel: int

	@validator('pesel')
	def validate_pesel(cls, v):
		if is_control_number_valid(v) is False:
			raise ValueError('Given PESEL number is invalid')
		return v

W argumencie  v  dostajemy wartość numeru PESEL już po konwersji typów (tworzenie modeli zachodzi od dołu do góry struktury danych), w razie nieprawidłowości wyrzucamy ValueError, który zostaje przekonwertowany przez Pydantic na pydantic.ValidationError. Możemy w tym miejscu sprawdzić długość stringa, lub znak liczby całkowitej. Można także modyfikować opcje walidacji, takie jak walidacja przy przypisaniu wartości czy zastosowanie walidacji dla każdego elementu, jeśli pole jest kolekcją. Dostęp do pozostałych wartości przekazanych do instancji, możemy otrzymać poprzez argument values funkcji walidującej.

Warto również wspomnieć, że Pydantic jest natywnie wspierany przez framework webowy FastAPI, co pozwala na łatwą walidację wartości przesyłanych do endpointów typu POST i PUT, oraz jasne i sztywne określenie struktury danych zwracanych przez endpointy typu GET. 
Podsumowując, Pydantic jest świetną biblioteką do modelowania klas, ułatwiającą pisanie czystego, prostego i przede wszystkim działającego kodu, która oferuje masę niezwykle przydatnych funkcjonalności.

Czy szukasz wykonawcy projektów IT ?
logo

Nasza oferta

Powiązane artykuły

Zobacz wszystkie artykuły powiązane z #Back-end