PydanticMiddleTechnical

Как Pydantic обрабатывает типы datetime, UUID и HttpUrl из коробки?

Pydantic v2 из коробки парсит datetime (ISO 8601, unix timestamp), UUID (строки всех форматов) и HttpUrl (структурированный объект с валидацией схемы). Каждый тип имеет нюансы coercion и сериализации.

Встроенные типы Pydantic v2

Pydantic v2 поддерживает богатый набор стандартных Python-типов с автоматической валидацией и coercion. Три наиболее важных в backend-разработке: datetime, UUID и HttpUrl.

datetime: парсинг и timezone

from datetime import datetime
from pydantic import BaseModel

class Event(BaseModel):
    created_at: datetime
    scheduled_at: datetime

# Pydantic принимает ISO 8601 строку, unix timestamp (int/float), объект datetime
e = Event(
    created_at="2024-01-15T10:30:00Z",
    scheduled_at=1705312200,  # unix timestamp
)
print(e.created_at)    # 2024-01-15 10:30:00+00:00
print(e.scheduled_at)  # datetime объект

# Что не принимает:
# Event(created_at="15 января 2024")  -> ValidationError
# Event(created_at="01/15/2024")      -> ValidationError (не ISO 8601)

Важно: Pydantic v2 по умолчанию принимает datetime без timezone (naive). Чтобы требовать timezone-aware datetime, используйте datetime с AwareDatetime из pydantic:

from pydantic import AwareDatetime, BaseModel

class StrictEvent(BaseModel):
    created_at: AwareDatetime  # ValidationError если нет timezone

UUID: все форматы строк

from uuid import UUID
from pydantic import BaseModel

class Resource(BaseModel):
    id: UUID

# Принимает любой стандартный формат
r1 = Resource(id="550e8400-e29b-41d4-a716-446655440000")  # с дефисами
r2 = Resource(id="550e8400e29b41d4a716446655440000")        # без дефисов
r3 = Resource(id="{550e8400-e29b-41d4-a716-446655440000}")  # с фигурными скобками

# Объект UUID тоже принимается
from uuid import uuid4
r4 = Resource(id=uuid4())

print(type(r1.id))  # <class 'uuid.UUID'>
print(r1.id)        # UUID('550e8400-e29b-41d4-a716-446655440000')

# Сериализация: по умолчанию в строку
print(r1.model_dump())       # {'id': UUID('...')}
print(r1.model_dump(mode='json'))  # {'id': '550e8400-...'}

HttpUrl: структурированная валидация URL

from pydantic import BaseModel, HttpUrl

class Webhook(BaseModel):
    callback_url: HttpUrl

w = Webhook(callback_url="https://example.com/hook?token=abc")
print(w.callback_url)          # https://example.com/hook?token=abc
print(w.callback_url.host)     # example.com
print(w.callback_url.scheme)   # https
print(w.callback_url.path)     # /hook
print(w.callback_url.query)    # token=abc

# Что не принимает:
# Webhook(callback_url="ftp://example.com")  -> ValidationError (не http/https)
# Webhook(callback_url="not-a-url")          -> ValidationError
# Webhook(callback_url="http://")            -> ValidationError (нет хоста)

В Pydantic v2 HttpUrl — не строка, а объект типа Url. При сериализации через model_dump() возвращает объект Url, при model_dump(mode='json') — строку. Это частая причина ошибок при сравнении с ==.

Сериализация и совместимость

from datetime import datetime, timezone
from uuid import UUID
from pydantic import BaseModel, HttpUrl

class Entity(BaseModel):
    id: UUID
    created_at: datetime
    source: HttpUrl

e = Entity(
    id="550e8400-e29b-41d4-a716-446655440000",
    created_at="2024-01-15T10:30:00Z",
    source="https://api.example.com/v1",
)

# Python-объекты
d = e.model_dump()
print(type(d["id"]))          # <class 'uuid.UUID'>
print(type(d["created_at"]))  # <class 'datetime.datetime'>
print(type(d["source"]))      # <class 'pydantic_core._pydantic_core.Url'>

# JSON-совместимые типы
d_json = e.model_dump(mode="json")
print(type(d_json["id"]))          # <class 'str'>
print(type(d_json["created_at"]))  # <class 'str'>
print(type(d_json["source"]))      # <class 'str'>

Подводные камни

  • HttpUrl в Pydantic v2 — объект, а не строка. Код if url == "https://example.com" вернёт False — нужно str(url) == "https://example.com".
  • datetime без timezone (naive) принимается по умолчанию — если бизнес-логика требует timezone, используйте AwareDatetime, иначе данные из разных источников несравнимы.
  • Unix timestamp как int принимается для datetime, но в Pydantic v2 strict-режим (model_config = {"strict": True}) запрещает coercion из int — передавайте только строки ISO 8601.
  • model_dump() возвращает UUID-объект, а не строку — передача результата напрямую в json.dumps() вызовет TypeError. Используйте model_dump_json() или model_dump(mode="json").
  • HttpUrl нормализует URL: добавляет trailing slash к корневым URL (https://example.comhttps://example.com/) — это может ломать сравнения и тесты.
  • Для AnyUrl (в отличие от HttpUrl) схема не ограничена http/https — не используйте его там, где нужна строгая валидация.
  • При десериализации из БД (SQLAlchemy) datetime-объекты без timezone передаются как naive — при совместном использовании с AwareDatetime получите ValidationError.
  • В JSON Schema HttpUrl генерирует {"type": "string", "format": "uri"} — OpenAPI-клиенты не знают про ограничение http/https, документация может вводить в заблуждение.

Common mistakes

  • Описывать datetime uuid httpurl только как термин и не показывать механизм на минимальном примере.
  • Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
  • Не связывать поведение с официальным контрактом Pydantic и реальной эксплуатацией.

What the interviewer is testing

  • Объясняет datetime uuid httpurl через последовательность действий, а не через набор ключевых слов.
  • Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
  • Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.

Sources

Related topics