Python pydantic. Валидация данных

05 Апр 2021 , 4556

В мире веб-разработки обработка и валидация данных играют ключевую роль. Изучая FastAPI  я познакомился с замечательной библиотекой Pydantic. В статье "Введение FastAPI" я описал использование Pydantic в связке с SQLAlchemy. А в данной статье мы рассмотрим все аспекты использования Pydantic, начиная от базовых концепций до более продвинутых техник валидации данных в Python.

Примечание: В данной статье рассматривается Pydantic 1

Что такое Pydantic?

Pydantic - это библиотека , для валидации и сериализации данных в Python. Pydantic предоставляет усовершенствованные возможности для валидации данных и управления их структурой с использованием аннотаций типов в Python. Эта библиотека обеспечивает подсказки типов на этапе выполнения и генерирует информативные сообщения об ошибках, когда данные не соответствуют заданным условиям.

Основные возможности Pydantic

Аннотации типов: Определение моделей данных с использованием аннотаций типов для атрибутов, что делает код более ясным и читаемым.

Валидация данных: автоматически проверяет данные на соответствие заданным типам и правилам валидации, что помогает предотвратить ошибки данных.

Сериализация данных: Легкость преобразования данных в формат JSON и обратно, что делает взаимодействие с API и хранение данных более удобным.

Поддержка дефолтных значений: Задание значений по умолчанию для атрибутов, что упрощает работу с необязательными данными.

Установим pydantic


pip install pydantic

Модели

В Pydantic основным инструментом для определения объектов данных являются модели. Модели в Pydantic представляют собой классы, которые наследуются от BaseModel. Эти модели описывают структуру данных, задавая типы и ограничения для их атрибутов.

Создадим первую Pydantic модель для представления поста(Post). Для этого нам нужно просто создать класс, который наследуется от BaseModel. Затем мы можем передать сырые данные в эту модель и посмотреть, как Pydantic c может преобразовать их в структурированный объект данных.


from datetime import datetime
from pydantic import BaseModel


#Наследуемся от BaseModel
class Post(BaseModel):
    id: int
    title: str
    text: str
    is_published: bool
    tags: list[str] = []
    published_at: datetime | None = None


if __name__ == '__main__':
    input_data = {
        'id': '1',
        'title': 'Python decorators',
        'text': 'Python decorators text',
        'is_published': True,
        'published_at': '2019-06-01 12:22',
        'tags': ['python', 'js', 3],
        'another_field': 'Test data',
    }
    post = Post(**input_data)
    print(post.id, type(post.id))
    #> 1 
    print(post.published_at, repr(post.published_at))
    #> 2019-06-01 12:22:00 datetime.datetime(2019, 6, 1, 12, 22)
    print("Выводим словарь", post.dict())
    #> Выводим словарь {'id': 1, 'title': 'Pyton decorators', 'text': 'Python decorators text',
    # 'is_published': True, 'tags': ['python', 'js', '3'], 'published_at': datetime.datetime(2019, 6, 1, 12, 22)}
    print("Выводим данные в json", post.json())
    #> Выводим данные в json {"id": 1, "title": "Python decorators", "text": "Python decorators text",
    # "is_published": true, "tags": ["python", "js", "3"], "published_at": "2019-06-01T12:22:00"}



Разберем поподробнее поля класса Post:

  • id - имеет целочисленный тип(int) и это поле является обязательным. Для этого поля мы передали строковое значение , но pydantic преобразовал его в целочисленный тип.

    Строки(str), байты(bytes) и числа плавающей запятой pydantic по возможности преобразует в целочисленный. В противном случае возбуждается исключение.

    
    from pydantic import BaseModel
    
    
    class Example(BaseModel):
        id: int
    
    
    print(Example(id='1')) # строковое значние будет преобразовано в 1
    print(Example(id=1.123)) # float преобразуется в 1 (отсекается дробовая часть)
    # Возникнет исключение валидации из-за невозможности преобразовать в целочисленный
    print(Example(id='this is str'))
    #> pydantic.error_wrappers.ValidationError: 1 validation error for Example
    # id value is not a valid integer (type=type_error.integer)
    
    
  • name - поле имеет строковый тип и является обязательным.

  • text - поле имеет строковый тип и является обязательным.

  • is_published - поле имеет булев тип и по умолчанию имеет значение True(Истинно).

  • is_published - поле имеет булев тип и по умолчанию имеет значение True(Истинно).

  • is_published - поле имеет булев тип и по умолчанию имеет значение

  • published_at - это поле даты и времени, которое не требуется (и принимает значение None, если оно не указано). Pydantic будет обрабатывать либо временную метку unix int (например, 1496498400), либо строку, представляющую дату и время.

  • tags- требует список строковых значений. По возможности входящие значения Pydanticбудет преобразоывать в строковые значения.

Выше мы создаем объект модели Post и передаем данные. Так как данные валидные , то создается новый объект и при этом как указали выше , некоторые входные данные преобразуются в тот тип , который указан в аннотациях полей.Во входных данных(input_data) у нас есть поле another_field, но такого поля нет в модели и поэтому значение этого поля игнорируется

Если входные данные будут неправильными с точки зрения описания модели , то будет вызываться ошибка валидации. Эти исключения мы можем обрабатывать следующим образом:


from pydantic import ValidationError

try:
    post = Post(**input_data)
except ValidationError as e:
    print(e.json())


Далее мы используем два метода , которые выводят данные в виде словаря(метод BaseModel.dict()) и виде json (метод BaseModel.json()). Есть еще другие методы и атрибуты модели , которые мы рассмотрим ниже.

Методы модели BaseModel

  • dict() - Возвращает словарь поля и значения модели

  • json() - Возвращает строковое представление JSON метода dict()

  • copy() - Возвращает копию модели(по умолчанию поверхностное копирование)

  • parse_obj()- метод для загрузки любого объекта в модель с обработкой ошибок, если объект не является словарем

  • parse_raw() - метод для загрузки строк различных форматов

  • parse_file() - похож на parse_raw() но для путей к файлам;

  • from_orm() - загружает данные в модель из произвольного класса; (режим ORM)


  • schema() - возвращает словарь, представляющий модель в виде схемы JSON.

    schema_json() - Возвращает строковое представление JSON метода schema()

    
    from datetime import datetime
    from pydantic import BaseModel
    
    
    class Post(BaseModel):
        id: int
        title: str
        text: str
        is_published: bool
        tags: list[str] = []
        published_at: datetime | None = None
    
    input_data = {
        'id': '1',
        'title': 'Python decorators',
        'text': 'Python decorators text',
        'is_published': True,
        'published_at': '2019-06-01 12:22',
        'tags': ['python', 'js', 3],
        'another_field': 'Test data',
    }
    
    post = Post(**input_data)
    print(post.schema()) 
    """
     {'title': 'Post', 'type': 'object', 'properties': {'id': {'title': 'Id', 'type': 'integer'}, 'title': {'title': 'Title', 'type': 'string'}, 'text': {'title': 'Text', 'type': 'string'}, 'is_published': {'title': 'Is Published', 'type': 'boolean'}, 'tags': {'title': 'Tags', 'default': [], 'type': 'array', 'items': {'type': 'string'}}, 'published_at': {'title': 'Published At', 'type': 'string', 'format': 'date-time'}}, 'required': ['id', 'title', 'text', 'is_published']}
    """
    print(post.schema_json()) 
    """
     {"title": "Post", "type": "object", "properties": {"id": {"title": "Id", "type": "integer"}, "title": {"title": "Title", "type": "string"}, "text": {"title": "Text", "type": "string"}, "is_published": {"title": "Is Published", "type": "boolean"}, "tags": {"title": "Tags", "default": [], "type": "array", "items": {"type": "string"}}, "published_at": {"title": "Published At", "type": "string", "format": "date-time"}}, "required": ["id", "title", "text", "is_published"]}
    """
    
    
  • construct() - метод класса для создания моделей без запуска проверки

    Это может быть полезно, когда данные уже проверены или получены из надежного источника, и вы хотите создать модель максимально эффективно (construct() обычно в 30 раз быстрее, чем создание модели с полной проверкой).

Рекурсивные(вложенные) модели

Более сложные иерархические структуры данных можно определить, используя сами модели в качестве типов в аннотациях.

Давайте создадим модель Author со своими полями и эту модель мы будем использовать для указания аннотации для поля authors в модели Post


from datetime import datetime
from pydantic import BaseModel, EmailStr


class Author(BaseModel):
    name: str
    last_name: str
    username: str
    email: EmailStr #EmailStr - это тип данных из Pydantic, который валидирует, что строка имеет правильный формат электронной почты.
    date_of_birth: str
    avatar: str


class Post(BaseModel):
    id: int
    title: str
    text: str
    is_published: bool
    tags: list[str] = []
    published_at: datetime | None = None
    authors: list[Author] #Список авторов поста. Это список объектов Author, связанных с данным постом.

# Создание экземпляра Author
author = Author(
    name="John",
    last_name="Doe",
    username="johndoe",
    email="johndoe@example.com",
    date_of_birth="1990-01-01",
    avatar="https://example.com/avatar.jpg"
)

# Создание экземпляра Post с автором 
post = Post(
    id=1,
    title="Sample Post",
    text="This is the content of the post",
    is_published=True,
    tags=["python", "programming"],
    published_at=datetime.now(),
    authors=[author]
)


Валидаторы в Pydantic

В Pydantic, помимо базовых аннотаций типов, вы также можете использовать валидаторы для более тонкой настройки валидации данных в ваших моделях. Валидаторы - это функции, которые выполняют проверку и трансформацию данных, прежде чем они будут присвоены атрибутам модели

Чтобы добавить валидатор к атрибуту модели, вы можете использовать декоратор @validator. Давайте рассмотрим примеры использования валидаторов:

Допустим, мы хотим добавить валидатор для поля date_of_birth, чтобы убедиться, что дата рождения автора находится в прошлом.


from pydantic import BaseModel, EmailStr, validator
from datetime import date

class Author(BaseModel):
    name: str
    last_name: str
    username: str
    email: EmailStr
    date_of_birth: str
    avatar: str

    @validator("date_of_birth")
    def check_date_of_birth(cls, value):
        # Преобразуем строку с датой в объект date
        date_of_birth = date.fromisoformat(value)
        today = date.today()
        if date_of_birth >= today:
            raise ValueError("Дата рождения должна быть в прошлом")
        return value

Создадим комбинированный валидатор для модели Post, который будет проверять несколько условий одновременно.

Например, мы можем создать валидатор, который проверяет, что текст поста не пустой, а если пост опубликован (is_published установлен в True), то также проверяет наличие тегов (tags)


from pydantic import BaseModel, validator

class Post(BaseModel):
    id: int
    title: str
    text: str
    is_published: bool
    tags: list[str] = []

    @validator("text")
    def check_text_not_empty(cls, value, values):
        if not value:
            raise ValueError("Текст поста не может быть пустым")
        return value

    @validator("tags")
    def check_tags_if_published(cls, value, values):
        is_published = values.get("is_published")
        if is_published and not value:
            raise ValueError("Если пост опубликован, необходимо указать хотя бы один тег")
        return value

В этом примере мы создали два валидатора:

  • check_text_not_empty: Этот валидатор проверяет, что текст поста не пустой. Он использует аргумент value для доступа к значению поля text.

  • check_tags_if_published: Этот валидатор проверяет наличие тегов только если пост опубликован (is_published равен True). Он использует аргументы value и values для доступа к значению поля tags и is_published.

Таким образом, комбинированный валидатор может учитывать несколько полей и их значения при валидации данных в модели. Это позволяет определять сложные правила валидации, которые зависят от разных атрибутов модели.

Заключение

В этой статье мы показали, как создавать модели , как объявлять поля с типами аннотаций, показали как pydantic пытается преобразовать входные данные

Особое внимание было уделено методам моделей, которые делают работу с данными более удобной. Рассмотрели вложенные модели, а также показали примеры использования валидаторов

Pydantic предоставляет множество мощных возможностей для работы с данными в Python, и планируется выход второй версии , где будет много изменений и классных улучшений.

comments powered by Disqus

Подписка

Подпишитесь на наш список рассылки, чтобы получать обновления из блога

Рубрики

Теги