Введение в Pydantic: Основы и продвинутые возможности

Введение в Pydantic: Основы и продвинутые возможности

Картинка к публикации: Введение в Pydantic: Основы и продвинутые возможности

Введение

Что такое Pydantic?

В современном программировании, особенно в разработке на Python, важно эффективно управлять данными. От качества обработки данных зависит стабильность и безопасность приложений. Здесь на сцену выходит Pydantic — библиотека, которая предлагает довольно простой способ валидации и сериализации данных.

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

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

1. Модели данных: Pydantic предоставляет удобный способ описания моделей данных через классы Python. Каждая модель представляет собой набор полей с определёнными типами данных и возможными значениями по умолчанию. Это позволяет явно описывать структуру данных и требования к ним.

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    age: int = 18

2. Валидация данных: Одна из ключевых возможностей Pydantic — автоматическая валидация данных. При создании экземпляра модели все переданные данные проверяются на соответствие типам, указанным в аннотациях. Если данные не соответствуют требованиям, Pydantic генерирует информативные сообщения об ошибках.

user = User(id='first', name='Alice', age=25)

3. Конвертация данных: Помимо валидации, Pydantic автоматически преобразует данные в нужные типы. Например, если поле ожидает значение типа int, а ему передана строка, представляющая число, Pydantic предпримет попытку конвертации.

user = User(id='1', name='Alice', age='25')
print(user.id)  # 1
print(user.age)  # 25

4. Работа с вложенными моделями: Pydantic поддерживает вложенные модели, что позволяет создавать сложные структуры данных. Это особенно полезно при работе с JSON и другими форматами данных, где часто встречаются вложенные объекты.

class Address(BaseModel):
    street: str
    city: str

class UserWithAddress(BaseModel):
    id: int
    name: str
    address: Address

address = Address(street='123 Main St', city='New York')
user = UserWithAddress(id=1, name='Alice', address=address)

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

from pydantic import validator

class UserWithValidation(BaseModel):
    id: int
    name: str
    age: int

    @validator('age')
    def check_age(cls, v):
        if v < 18:
            raise ValueError('Age must be at least 18')
        return v

user = UserWithValidation(id=1, name='Alice', age=25)

6. Интеграция с другими библиотеками и фреймворками: Pydantic легко интегрируется с популярными фреймворками, такими как FastAPI, что делает его отличным выбором для разработки веб-приложений. Например, в FastAPI модели Pydantic используются для валидации запросов и ответов, что существенно упрощает разработку и улучшает качество кода.

Краткая история создания Pydantic

Pydantic появился на свет благодаря стремлению разработчиков упростить и улучшить процесс валидации данных в Python. Основателем и главным разработчиком библиотеки является Сэмюэль Коллинсон (Samuel Colvin). Изначальная версия Pydantic была выпущена в 2018 году и быстро завоевала популярность среди разработчиков благодаря своей простоте и эффективности.

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

С момента своего появления, Pydantic прошёл через множество изменений и улучшений. Ниже приведены некоторые ключевые обновления:

Версия 1.0 (2018 год)

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

Версия 1.4 (2019 год)

  • Введение поддержки вложенных моделей и более сложных структур данных.
  • Улучшение производительности и расширение функциональности кастомных валидаторов.
  • Интеграция с FastAPI, что позволило использовать Pydantic для создания API с автоматической валидацией.

Версия 1.7 (2020 год)

  • Добавление новых типов данных, таких как UUID, Decimal и IPvAnyAddress.
  • Улучшение сообщений об ошибках валидации, что сделало их более информативными и понятными.
  • Оптимизация производительности и уменьшение времени обработки данных.

Версия 1.8 (2021 год)

  • Расширение возможностей для работы с конфигурационными файлами и настройками моделей.
  • Улучшение работы с datetime объектами и поддержка различных форматов дат и времени.
  • Введение поддержки динамического создания моделей с помощью функции create_model.

Версия 2.2 (2023 год)

  • Переписанный на Rust модуль pydantic-core для валидации, что значительно повысило производительность.
  • Поддержка Python 3.8 и выше.
  • Новый интерфейс для конфигурации моделей.
  • Улучшенная система сообщений об ошибках, что делает их более информативными​ ()​​ ()​.

Версия 2.7 (2024 год)

  • Введение опции serialize_as_any для сериализации объектов с произвольными полями.
  • Возможность передавать контекст в процессе сериализации для более гибкой обработки данных.
  • Улучшения производительности и новые функции, такие как расширенные возможности работы с аннотациями типов и оптимизация процесса валидации данных​.

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

  • Улучшенные механизмы валидации: Введение новых типов валидаторов и улучшение существующих механизмов валидации, что позволяет более точно и гибко проверять данные.
  • Оптимизация производительности: Значительное улучшение скорости обработки данных, особенно для больших и сложных моделей. Это позволяет использовать Pydantic в высоконагруженных приложениях и сервисах.
  • Расширенные возможности конфигурации: Добавлены новые опции для настройки поведения моделей и валидаторов, что даёт разработчикам больше контроля над процессом валидации и сериализации.
  • Новая документация и примеры: Обновлённая документация, включающая подробные примеры использования новых функций и возможностей. Это помогает разработчикам быстрее освоить библиотеку и начать её использовать в своих проектах.

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

Основы работы с Pydantic

Базовые типы данных

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

1. Числовые типы

  • int — Целые числа. Используется для представления целочисленных значений.
  • float — Числа с плавающей запятой. Используется для представления дробных значений.
  • Decimal — Десятичные числа высокой точности. Используется в финансовых приложениях, где важна точность вычислений.
from pydantic import BaseModel, Decimal

class Product(BaseModel):
    id: int
    price: float
    tax: Decimal

2. Строковые типы

  • str — Строки. Используется для представления текстовых данных.
class User(BaseModel):
    username: str
    first_name: str
    last_name: str

3. Булевы типы

  • bool — Логические значения (истина или ложь).
class FeatureFlag(BaseModel):
    is_enabled: bool

4. Списки и кортежи

  • list — Списки, содержащие элементы одного типа.
  • tuple — Кортежи, содержащие элементы одного или нескольких типов.
class Order(BaseModel):
    items: list[str]
    quantities: tuple[int, int, int]

5. Словари

  • dict — Словари, содержащие пары ключ-значение.
class Config(BaseModel):
    settings: dict[str, str]

6. Даты и время

  • datetime — Дата и время.
  • date — Дата.
  • time — Время.
  • timedelta — Разница между двумя моментами времени.
from datetime import datetime, timedelta

class Event(BaseModel):
    start_datetime: datetime
    end_datetime: datetime
    duration: timedelta

7. UUID

  • UUID — Универсально уникальный идентификатор.
from uuid import UUID

class Item(BaseModel):
    id: UUID
    name: str

8. EmailStr и AnyUrl

  • EmailStr — Валидация строк как email адресов.
  • AnyUrl — Валидация строк как URL.
from pydantic import EmailStr, AnyUrl

class Contact(BaseModel):
    email: EmailStr
    website: AnyUrl

Модели данных

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

Для создания модели данных в Pydantic необходимо унаследовать класс от BaseModel и определить поля модели с помощью аннотаций типов. Каждый атрибут модели представляет собой поле данных с указанием типа и, опционально, значения по умолчанию.

from pydantic import BaseModel, EmailStr
from datetime import datetime
from uuid import UUID

class User(BaseModel):
    id: UUID
    username: str
    email: EmailStr
    is_active: bool = True
    created_at: datetime = datetime.now()

В этом примере модель User включает несколько полей: id, username, email, is_active и created_at. Поля is_active и created_at имеют значения по умолчанию.

Создание экземпляра модели данных происходит путём передачи соответствующих значений в конструктор модели. Pydantic автоматически выполняет валидацию и преобразование данных согласно указанным типам.

from uuid import uuid4

user = User(
    id=uuid4(),
    username="alice",
    email="alice@example.com"
)
print(user)

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

try:
    user = User(
        id="not-a-uuid",
        username="alice",
        email="alice@example.com"
    )
except ValueError as e:
    print(e)

В этом примере Pydantic генерирует ошибку, так как значение поля id не является допустимым UUID.

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

user = User(
    id=uuid4(),
    username="alice",
    email="alice@example.com"
)
user_json = user.model_dump_json()
print(user_json)

Метод model_dump_json конвертирует данные модели в строку формата JSON, готовую для передачи или хранения.

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

class Address(BaseModel):
    street: str
    city: str
    country: str

class UserWithAddress(BaseModel):
    id: UUID
    username: str
    email: str
    address: Address

address = Address(street="123 Main St", city="New York", country="USA")

user = UserWithAddress(
    id=uuid4(),
    username="alice",
    email="alice@example.com",
    address=address
)

print(user)

Использование аннотаций типов и автоматическая проверка данных делают код более надёжным и читаемым. Вложенные модели позволяют работать с комплексными структурами данных, что особенно полезно при разработке современных веб-приложений и API. Pydantic значительно упрощает работу с данными, обеспечивая высокую производительность и гибкость.

Поле модели и аннотации

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

Аннотации типов в Python используются для указания типов данных, которые ожидаются в полях модели. Pydantic использует эти аннотации для автоматической валидации и преобразования данных. Аннотации типов могут быть простыми (например, int, str) или более сложными (например, List[int], Dict[str, Any]).

Пример определения полей с аннотациями типов:

from pydantic import BaseModel
from typing import List, Dict
from uuid import UUID

class Product(BaseModel):
    id: UUID
    name: str
    price: float
    tags: List[str] = []
    metadata: Dict[str, str] = {}

В этом примере модель Product включает несколько полей: id, name, price, tags и metadata. Поля tags и metadata имеют значения по умолчанию, что делает их необязательными при создании экземпляра модели.

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

1. Значения по умолчанию

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

class User(BaseModel):
    username: str
    is_active: bool = True

2. Алиасы (псевдонимы)

Алиасы используются для задания альтернативных имён полей, что может быть полезно при работе с внешними API или системами, где используются другие наименования полей.

from pydantic import BaseModel, Field

class User(BaseModel):
    username: str
    email: str = Field(alias='user_email')

3. Валидаторы

Валидаторы позволяют задавать пользовательские правила валидации для полей. Они определяются с помощью декораторов и могут использоваться для проверки значений полей перед их сохранением в модель.

from pydantic import field_validator

class User(BaseModel):
    username: str
    age: int

    @field_validator('age')
    def check_age(cls, v):
        if v < 18:
            raise ValueError('Age must be at least 18')
        return v

4. Конфигурационные параметры

Конфигурационные параметры позволяют настраивать поведение всей модели или отдельных полей. Они определяются в атрибуте model_config, в который передаются настройки в виде словаря.

from pydantic import ConfigDict

class User(BaseModel):
    username: str
    email: str

    model_config = ConfigDict(
        str_strip_whitespace=True,
        str_min_length=1
    )

Примеры использования аннотаций типов и атрибутов полей

Пример 1: Модель с различными типами полей и атрибутами

from pydantic import BaseModel, Field
from uuid import UUID
from typing import List

class Item(BaseModel):
    id: UUID
    name: str
    description: str = None
    price: float
    tags: List[str] = Field(default_factory=list)

item = Item(
    id="123e4567-e89b-12d3-a456-426614174000",
    name="Laptop",
    price=999.99
)
print(item)

Пример 2: Модель с алиасами и валидаторами

from pydantic import BaseModel, Field, field_validator

class User(BaseModel):
    username: str
    email: str = Field(alias='user_email')
    age: int

    @field_validator('age')
    def check_age(cls, v):
        if v < 18:
            raise ValueError('Age must be at least 18')
        return v

user = User(
    username="alice",
    user_email="alice@example.com",
    age=25
)
print(user)

Поля моделей и аннотации типов в Pydantic обеспечивают гибкий механизм для описания структуры данных и их валидации. Аннотации типов позволяют точно определять ожидаемые типы данных, а атрибуты полей дают возможность задавать дополнительные параметры и правила валидации.

Валидация и обработка данных

Углубим и расширим понимание принципов и механизмов валидации данных в Pydantic.

Валидация входных данных

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

Основные принципы валидации данных:

1. Аннотации типов: Используются для определения ожидаемых типов данных для каждого поля модели. При создании экземпляра модели, Pydantic автоматически проверяет типы переданных данных, и если они не соответствуют аннотациям, генерируются ошибки валидации.

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    age: int

# Правильные данные
user = User(id=1, name="Alice", age=30)

# Неправильные данные
try:
    user = User(id="one", name="Alice", age="thirty")
except ValueError as e:
    print(e)

2. Встроенные валидаторы: Pydantic предоставляет ряд встроенных валидаторов, которые автоматически проверяют данные на соответствие типам и значениям. Например, валидаторы для чисел проверяют, что значения являются числами, строки проверяются на соответствие длине и наличию недопустимых символов.

from pydantic import BaseModel, EmailStr

class User(BaseModel):
    id: int
    email: EmailStr

# Правильный email
user = User(id=1, email="user@example.com")

# Неправильный email
try:
    user = User(id=2, email="not-an-email")
except ValueError as e:
    print(e)

3. Кастомные валидаторы: Позволяют разработчикам создавать свои собственные правила валидации для данных. Это достигается с помощью декораторов @validator, которые могут быть применены к полям модели.

from pydantic import BaseModel, field_validator

class User(BaseModel):
    id: int
    name: str
    age: int

    @field_validator('age')
    def check_age(cls, v):
        if v < 0:
            raise ValueError('Age must be a positive integer')
        return v

try:
    user = User(id=1, name="Alice", age=-1)
except ValueError as e:
    print(e)

Механизмы валидации данных:

1. Валидация при создании модели: Основной механизм валидации в Pydantic срабатывает при создании экземпляра модели. Все данные, переданные в конструктор модели, проверяются на соответствие аннотациям типов и правилам валидации, определённым в модели. Если данные не проходят валидацию, Pydantic генерирует подробные ошибки, указывая, какие поля не прошли проверку и по каким причинам.

2. Валидация вложенных моделей: Если модель содержит вложенные объекты, они также будут валидироваться согласно своим аннотациям типов и правилам.

from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class User(BaseModel):
    id: int
    name: str
    address: Address

address = Address(street="Main St", city="New York", zip_code="10001")
user = User(id=1, name="Alice", address=address)

3. Валидация сложных типов данных: Pydantic поддерживает валидацию сложных типов данных, таких как списки, словари и другие коллекции. Каждый элемент коллекции валидируется согласно аннотациям типов.

from typing import List
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    friends: List[int]

user = User(id=1, name="Alice", friends=[2, 3, 4])

try:
    user = User(id=1, name="Alice", friends=["two", "three"])
except ValueError as e:
    print(e)

Кастомные валидаторы

Кастомные валидаторы в Pydantic создаются с помощью декоратора @field_validator. Этот декоратор применяется к методу, который должен выполнять валидацию поля. В метод передаётся значение поля, которое необходимо проверить и, при необходимости, изменить. Если значение не проходит проверку, валидатор должен выбросить исключение ValueError с описанием ошибки.

from pydantic import BaseModel, field_validator

class User(BaseModel):
    username: str
    age: int

    @field_validator('age')
    def check_age(cls, value):
        if value < 18:
            raise ValueError('Age must be at least 18')
        return value

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

from pydantic import BaseModel, field_validator

class User(BaseModel):
    name: str
    age: int
    email: str

    @field_validator('email')
    def validate_email(cls, value, values):
        age = values.data.get('age')
        if age and age < 18:
            if not value.endswith('@example.com'):
                raise ValueError('Users under 18 must have an @example.com email')
        return value

try:
    user = User(name='Jose', age='15', email='jose@not_example.com')
except ValueError as e:
    print(e)

Кастомные валидаторы позволяют создавать комплексные правила валидации, включая проверки на основе регулярных выражений, доступа к базам данных, запросов к внешним API и других сложных логик.

import re
from pydantic import BaseModel, field_validator

class User(BaseModel):
    username: str

    @field_validator('username')
    def username_alphanumeric(cls, value):
        if not re.match(r'^[a-zA-Z0-9_]+$', value):
            raise ValueError('Username must be alphanumeric')
        return value

try:
    user = User(username='Jose@')
except ValueError as e:
    print(e)

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

from pydantic import BaseModel, field_validator

class Address(BaseModel):
    street: str
    city: str

class User(BaseModel):
    name: str
    address: Address

    @field_validator('address')
    def validate_address(cls, value):
        if not value.street or not value.city:
            raise ValueError('Both street and city must be provided')
        return value

try:
    user = User(name='Jose', address=Address(street='123 Main St', city=''))
except ValueError as e:
    print(e)

Кастомные валидаторы в Pydantic предоставляют разработчикам инструмент для реализации сложных правил валидации данных. С их помощью можно легко создавать проверки, которые выходят за рамки стандартных встроенных валидаторов, обеспечивая высокую степень гибкости и точности валидации.

Обработка ошибок

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

Основной тип исключений, используемый в Pydantic для обозначения ошибок валидации, — это ValidationError. Это исключение возникает, когда данные не соответствуют определённым в модели типам или правилам валидации.

from pydantic import BaseModel, ValidationError

class User(BaseModel):
    id: int
    name: str
    age: int

try:
    user = User(id='abc', name='Alice', age='twenty-five')
except ValidationError as e:
    print(e)

В этом примере ValidationError будет сгенерировано из-за несоответствия типов данных для полей id и age.

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

Вывод будет содержать список ошибок с указанием полей, на которых произошли сбои, и сообщений о причинах ошибок:

2 validation errors for User
id
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/int_parsing
age
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='twenty-five', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/int_parsing

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

import logging
from pydantic import BaseModel, ValidationError

# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class User(BaseModel):
    id: int
    name: str
    age: int

try:
    user = User(id='abc', name='Alice', age='twenty-five')
except ValidationError as e:
    logger.error("Validation error: %s", e)
    # Дополнительные действия по обработке ошибки

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

from pydantic import BaseModel, field_validator

class CustomValidationError(Exception):
    def __init__(self, message: str):
        super().__init__(message)

class User(BaseModel):
    id: int
    name: str
    age: int

    @field_validator('age')
    def check_age(cls, value):
        if value < 0:
            raise CustomValidationError('Age must be a positive integer')
        return value

try:
    user = User(id=1, name='Alice', age=-5)
except CustomValidationError as e:
    print(e)
    
# Вывод: Age must be a positive integer

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

Продвинутые возможности

Подробнее о вложенности моделей

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

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

from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class User(BaseModel):
    id: int
    name: str
    address: Address

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

address_data = {
    "street": "123 Main St",
    "city": "New York",
    "zip_code": "10001"
}

user_data = {
    "id": 1,
    "name": "Alice",
    "address": address_data
}

user = User(**user_data)
print(user)

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

from pydantic import BaseModel, ValidationError

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class User(BaseModel):
    id: int
    name: str
    address: Address

invalid_user_data = {
    "id": 1,
    "name": "Alice",
    "address": {
        "street": "123 Main St",
        "city": "New York",
        "zip_code": 10001  # zip_code должен быть строкой
    }
}

try:
    user = User(**invalid_user_data)
except ValidationError as e:
    print(e.json())

Преимущества использования вложенных моделей

  1. Повышенная читаемость и поддерживаемость кода
    Вложенные модели помогают разделить сложные структуры данных на логически обоснованные части, что упрощает чтение и поддержку кода.
  2. Автоматическая валидация на всех уровнях
    Pydantic автоматически валидирует данные на всех уровнях вложенности, что снижает вероятность ошибок и обеспечивает целостность данных.
  3. Повторное использование моделей
    Вложенные модели можно повторно использовать в различных частях приложения, что способствует уменьшению дублирования кода и упрощает обновление моделей.

Примеры использования вложенных моделей:

Пример 1: Модель заказа с вложенными моделями для товаров и адреса доставки

from pydantic import BaseModel
from typing import List

class Item(BaseModel):
    name: str
    price: float

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class Order(BaseModel):
    order_id: int
    items: List[Item]
    shipping_address: Address

order_data = {
    "order_id": 123,
    "items": [
        {"name": "Laptop", "price": 999.99},
        {"name": "Mouse", "price": 25.75}
    ],
    "shipping_address": {
        "street": "456 Elm St",
        "city": "Los Angeles",
        "zip_code": "90001"
    }
}

order = Order(**order_data)
print(order)

Пример 2: Модель компании с вложенными моделями для сотрудников и адреса офиса

from pydantic import BaseModel
from typing import List

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class Employee(BaseModel):
    employee_id: int
    name: str
    position: str

class Company(BaseModel):
    name: str
    address: Address
    employees: List[Employee]

company_data = {
    "name": "Tech Corp",
    "address": {
        "street": "789 Maple Ave",
        "city": "San Francisco",
        "zip_code": "94107"
    },
    "employees": [
        {"employee_id": 1, "name": "John Doe", "position": "CEO"},
        {"employee_id": 2, "name": "Jane Smith", "position": "CTO"}
    ]
}

company = Company(**company_data)
print(company)

Динамическое создание моделей

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

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

from pydantic import BaseModel, create_model

# Динамическое создание модели
DynamicModel = create_model(
    'DynamicModel',  # Имя модели
    name=(str, ...),  # Поле name с типом str и обязательное
    age=(int, 30)  # Поле age с типом int и значением по умолчанию 30
)

# Создание экземпляра динамической модели
dynamic_instance = DynamicModel(name='Alice')
print(dynamic_instance)

Функция create_model предоставляет широкие возможности для создания динамических моделей.

  • Определение полей: Поля модели задаются в виде именованных аргументов функции, где ключом является имя поля, а значением — кортеж, состоящий из типа поля и его значения по умолчанию. Если значение по умолчанию не указано, поле считается обязательным.
  • Использование валидаторов и настроек: Для создания динамической модели можно определить валидаторы, используя декораторы @field_validator. Настройки модели можно передать через класс конфигурации, который наследуется от созданной модели.
  • Наследование от существующих моделей: create_model позволяет создавать новые модели на основе существующих, добавляя или изменяя поля и атрибуты. При этом можно добавлять валидаторы и настройки конфигурации через наследование.
from pydantic import BaseModel, create_model, ValidationError, field_validator

# Создание базовой модели
class BaseUser(BaseModel):
    id: int

# Динамическое создание модели
DynamicUser = create_model(
    'DynamicUser',
    __base__=BaseUser,
    name=(str, ...),
    age=(int, ...)
)

# Добавление валидатора
class DynamicUserWithValidator(DynamicUser):
    @field_validator('age')
    def check_age(cls, v):
        if v < 18:
            raise ValueError('Age must be at least 18')
        return v

# Альтернативный способ добавления конфигурации через наследование
class ConfiguredUser(DynamicUserWithValidator):
    class Config:
        str_min_length = 1

# Создание экземпляра динамической модели
try:
    dynamic_user = ConfiguredUser(id=1, name='Bob', age=17)
except ValidationError as e:
    print(e)

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

import json
from pydantic import create_model

# Пример JSON-схемы
json_schema = '''
{
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"},
        "email": {"type": "string"}
    },
    "required": ["name", "email"]
}
'''

# Парсинг JSON-схемы
schema = json.loads(json_schema)

# Создание модели на основе JSON-схемы
fields = {
    key: (str if value['type'] == 'string' else int, ...)
    for key, value in schema['properties'].items()
}
DynamicModelFromJSON = create_model('DynamicModelFromJSON', **fields)

# Создание экземпляра динамической модели
instance = DynamicModelFromJSON(name='Alice', email='alice@example.com', age=25)
print(instance)

Преимущества использования create_model:

  1. Гибкость: Возможность динамически создавать модели позволяет адаптироваться к изменениям в данных и конфигурациях без необходимости заранее определять все возможные структуры.
  2. Удобство: create_model упрощает создание моделей для временных или одноразовых структур данных, что особенно полезно в сценариях тестирования и быстрого прототипирования.
  3. Интеграция с существующим кодом: Динамически созданные модели могут наследовать от существующих моделей и использовать их валидаторы и настройки, что позволяет легко интегрировать их в существующий код и инфраструктуру.

Валидация сложных типов данных

Валидация списков: Списки являются одной из самых распространённых структур данных, и Pydantic обеспечивает простые и гибкие способы для их валидации. Вы можете определить список, указав его тип с помощью аннотации типов List из модуля typing.

class User(BaseModel):
    id: int
    name: str
    tags: list[str]

user = User(id=1, name='Alice', tags=['admin', 'user'])
print(user)

Если данные не соответствуют указанному типу, Pydantic сгенерирует ошибку валидации.

try:
    user = User(id=1, name='Alice', tags='not-a-list')
except ValidationError as e:
    print(e

Валидация словарей: Словари используются для хранения данных в формате ключ-значение, и Pydantic поддерживает их валидацию с помощью аннотации типов Dict из модуля typing.

class Config(BaseModel):
    settings: dict[str, str]

config = Config(settings={"theme": "dark", "language": "en"})
print(config)

Если данные не соответствуют ожидаемой структуре словаря, Pydantic сгенерирует ошибку.

try:
    config = Config(settings="not-a-dict")
except ValidationError as e:
    print(e)

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

from pydantic import BaseModel, conint

class PositiveInt(BaseModel):
    value: conint(gt=0)

positive_value = PositiveInt(value=10)
print(positive_value)

try:
    negative_value = PositiveInt(value=-10)
except ValueError as e:
    print(e)

Вложенные сложные типы: Pydantic позволяет создавать сложные структуры данных, объединяя списки, словари и собственные типы. Это особенно полезно для валидации и обработки сложных и многослойных данных.

from pydantic import BaseModel
from typing import List, Dict

class Address(BaseModel):
    street: str
    city: str

class User(BaseModel):
    id: int
    name: str
    addresses: List[Address]
    preferences: Dict[str, str]

user_data = {
    "id": 1,
    "name": "Alice",
    "addresses": [
        {"street": "123 Main St", "city": "New York"},
        {"street": "456 Elm St", "city": "Los Angeles"}
    ],
    "preferences": {"theme": "dark", "language": "en"}
}

user = User(**user_data)
print(user)

Интеграция с другими библиотеками

FastAPI

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

FastAPI использует модели Pydantic для определения схем запросов и ответов. Это позволяет автоматизировать процесс валидации данных и улучшить качество кода за счёт использования типизированных моделей.

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class User(BaseModel):
    id: int
    name: str
    email: EmailStr

@app.post("/users/")
async def create_user(user: User):
    return user

В этом примере создаётся API, которое принимает данные пользователя в формате JSON, валидирует их с помощью модели Pydantic и возвращает данные обратно.

Одной из сильных сторон интеграции Pydantic с FastAPI является автоматическая валидация данных. FastAPI автоматически проверяет данные запросов на соответствие моделям Pydantic и возвращает понятные сообщения об ошибках, если данные не проходят валидацию.

{
    "id": "not-an-integer",
    "name": "Alice",
    "email": "alice@example.com"
}

FastAPI автоматически вернёт сообщение об ошибке:

{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "body",
                "id"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "not-an-integer"
        }
    ]
}

FastAPI и Pydantic поддерживают работу с вложенными моделями и сложными структурами данных, что позволяет создавать API для обработки комплексных данных.

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import List

app = FastAPI()

class Address(BaseModel):
    street: str
    city: str

class User(BaseModel):
    id: int
    name: str
    email: EmailStr
    addresses: List[Address]

@app.post("/users/")
async def create_user(user: User):
    return user

FastAPI автоматически генерирует документацию для API, включая схемы запросов и ответов на основе моделей Pydantic. Это делает API самодокументируемым и упрощает интеграцию с другими системами.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    id: int
    name: str
    email: str

users = {}

@app.post("/users/", response_model=User)
async def create_user(user: User):
    if user.id in users:
        raise HTTPException(status_code=400, detail="User already exists")
    users[user.id] = user
    return user

@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
    if user_id not in users:
        raise HTTPException(status_code=404, detail="User not found")
    return users[user_id]

FastAPI поддерживает асинхронные операции, что позволяет создавать высокопроизводительные и масштабируемые API. Интеграция с Pydantic остаётся такой же простой и эффективной даже при использовании асинхронного кода.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/")
async def create_item(item: Item):
    # Асинхронная обработка данных
    await some_async_function(item)
    return item

FastAPI и Pydantic используются в различных реальных проектах для создания надёжных и масштабируемых веб-сервисов. Примеры таких проектов включают RESTful API для e-commerce платформ, системы управления пользователями, и интеграционные шлюзы для микросервисов.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

app = FastAPI()

class Item(BaseModel):
    id: int
    name: str
    price: float

items = {}

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    if item.id in items:
        raise HTTPException(status_code=400, detail="Item already exists")
    items[item.id] = item
    return item

@app.get("/items/", response_model=List[Item])
async def list_items():
    return list(items.values())

@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return items[item_id]

SQLAlchemy

Основная идея интеграции Pydantic с SQLAlchemy заключается в использовании моделей Pydantic для валидации данных, которые сохраняются в базу данных или извлекаются из неё. Это позволяет объединить преимущества обеих библиотек: управление данными с помощью SQLAlchemy и строгую валидацию данных с помощью Pydantic.

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

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    email = Column(String, unique=True, index=True)

# Настройка подключения к базе данных
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Создание таблиц
Base.metadata.create_all(bind=engine)

После определения моделей SQLAlchemy необходимо создать соответствующие модели Pydantic для валидации данных. Модели Pydantic будут использоваться для приёма данных от клиентов и их валидации перед сохранением в базу данных.

from pydantic import BaseModel, EmailStr

class UserCreate(BaseModel):
    name: str
    email: EmailStr

class UserRead(BaseModel):
    id: int
    name: str
    email: EmailStr

    class Config:
        from_attributes = True

Модель UserCreate будет использоваться для валидации данных при создании нового пользователя, а модель UserRead — для сериализации данных, извлечённых из базы данных.

Рассмотрим, как использовать Pydantic и SQLAlchemy для создания и получения данных в API. Создадим функцию для обработки создания пользователя, которая будет принимать данные, валидировать их с помощью модели Pydantic и сохранять в базу данных с использованием SQLAlchemy.

from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.orm import Session

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/users/", response_model=UserRead)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    db_user = db.query(User).filter(User.email == user.email).first()
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    new_user = User(name=user.name, email=user.email)
    db.add(new_user)
    db.commit()
    db.refresh(new_user)
    return new_user

Создадим функцию для получения данных пользователя из базы данных и их сериализации с помощью модели Pydantic.

@app.get("/users/{user_id}", response_model=UserRead)
def read_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user

Добавим функции для обновления и удаления пользователей с валидацией данных.

class UserUpdate(BaseModel):
    name: str
    email: EmailStr

@app.put("/users/{user_id}", response_model=UserRead)
def update_user(user_id: int, user_update: UserUpdate, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    user.name = user_update.name
    user.email = user_update.email
    db.commit()
    db.refresh(user)
    return user
@app.delete("/users/{user_id}", response_model=UserRead)
def delete_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    db.delete(user)
    db.commit()
    return user

Преимущества интеграции Pydantic и SQLAlchemy

  1. Строгая валидация данных
    Использование Pydantic обеспечивает строгую валидацию данных перед их сохранением в базу данных, что уменьшает вероятность ошибок и улучшает целостность данных.
  2. Удобство работы с данными
    Модели Pydantic позволяют легко сериализовать и десериализовать данные, упрощая работу с JSON и другими форматами.
  3. Повышенная читаемость и поддерживаемость кода
    Разделение моделей данных на валидационные (Pydantic) и ORM (SQLAlchemy) помогает поддерживать чистоту и структурированность кода.

Django

Интеграция Pydantic с Django включает использование моделей Pydantic для валидации данных и сериализации, что позволяет повысить надёжность и читаемость кода. Основные шаги включают определение моделей Pydantic и использование их для валидации данных в Django представлениях и других компонентах.

Первый шаг в интеграции — создание моделей Pydantic, которые будут использоваться для валидации данных и сериализации. Рассмотрим пример модели пользователя:

from pydantic import BaseModel, EmailStr

class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

class UserRead(BaseModel):
    id: int
    username: str
    email: EmailStr

    class Config:
        from_attributes = True

Модели Pydantic могут использоваться в Django представлениях для валидации данных запросов. Рассмотрим пример представления для создания пользователя:

from django.http import JsonResponse
from django.views import View
from pydantic import ValidationError
from .models import User as DjangoUser
from .pydantic_models import UserCreate, UserRead

class CreateUserView(View):
    def post(self, request, *args, **kwargs):
        try:
            data = UserCreate.model_validate_json(request.body)
        except ValidationError as e:
            return JsonResponse(e.errors(), status=400, safe=False)

        user = DjangoUser.objects.create(
            username=data.username,
            email=data.email,
            password=data.password  # Здесь следует использовать хэширование паролей
        )
        user_data = UserRead.model_validate(user)
        return JsonResponse(user_data.model_dump())

В этом примере данные запроса валидируются с использованием модели UserCreate, а затем создаётся экземпляр модели Django User. Для ответа используется модель UserRead, которая преобразует данные в формат JSON.

Хотя Django предлагает собственные формы для валидации данных, Pydantic может быть использован для дополнительной проверки и преобразования данных. Рассмотрим пример формы, которая использует Pydantic для валидации:

from django import forms
from pydantic import BaseModel, EmailStr, ValidationError

class UserForm(forms.Form):
    username = forms.CharField(max_length=100)
    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        try:
            UserCreate(**cleaned_data)
        except ValidationError as e:
            raise forms.ValidationError(e.errors())

class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

В этом примере форма Django использует модель Pydantic UserCreate для валидации данных в методе clean.

Pydantic также может быть использован для сериализации данных, например, при возвращении JSON-ответов из представлений Django. Рассмотрим пример:

from django.http import JsonResponse
from django.views import View
from .models import User as DjangoUser
from .pydantic_models import UserRead

class ListUsersView(View):
    def get(self, request, *args, **kwargs):
        users = DjangoUser.objects.all()
        users_data = [UserRead.model_validate(user) for user in users]
        return JsonResponse([user.model_dump() for user in users_data], safe=False)

Преимущества использования Pydantic с Django

  1. Строгая валидация данных
    Использование Pydantic для валидации данных позволяет обеспечить более строгий контроль за входными данными, что снижает вероятность ошибок и повышает надёжность приложений.
  2. Удобство и ясность кода
    Модели Pydantic упрощают определение и использование структур данных, улучшая читаемость и поддерживаемость кода.
  3. Повторное использование моделей
    Модели Pydantic можно использовать повторно в различных частях приложения, включая представления, формы и сериализаторы, что снижает дублирование кода и упрощает его обновление.

Читайте также:

ChatGPT
Eva
💫 Eva assistant