← Назад к вопросам
Как в SQLAlchemy используется модуль typing?
1.7 Middle🔥 151 комментариев
#Python Core#Архитектура и паттерны#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование typing в SQLAlchemy
Модуль typing в SQLAlchemy решает две важные задачи: аннотация типов для IDE и валидация данных. Начиная с SQLAlchemy 2.0, работа с типами стала полноценной и удобной.
1. Базовые аннотации
Декоратор @mapped_column
В SQLAlchemy 2.0+ используется декоратор @mapped_column с явной типизацией:
from typing import Optional
from sqlalchemy import Integer, String
from sqlalchemy.orm import declarative_base, Mapped, mapped_column
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
# Обязательное поле
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
email: Mapped[str]
# Опциональное поле (nullable)
phone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
age: Mapped[Optional[int]] = mapped_column(Integer, default=None)
Преимущества явной типизации:
- IDE получает полную информацию о типах (автодополнение)
- Статический анализатор (mypy, pyright) может проверить корректность кода
- Самодокументируемый код
- Меньше ошибок в runtime
2. Отношения между таблицами (Relationships)
Для связей используется Mapped с квадратными скобками:
from typing import List
from sqlalchemy.orm import Relationship
class Author(Base):
__tablename__ = 'authors'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
# One-to-Many: один автор — много книг
books: Mapped[List['Book']] = relationship('Book', back_populates='author')
class Book(Base):
__tablename__ = 'books'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
author_id: Mapped[int] = mapped_column(ForeignKey('authors.id'))
# Many-to-One: много книг — один автор
author: Mapped['Author'] = relationship('Author', back_populates='books')
Ключевые моменты:
Mapped[List['Book']]— коллекция, не загружается автоматически- Кавычки
'Book'используются для forward references back_populatesсинхронизирует две стороны отношения
3. Optional vs Required поля
Required поле (NOT NULL)
# Правильно: поле обязательно
name: Mapped[str] = mapped_column(String(100))
# При создании объекта name ТРЕБУЕТСЯ
user = User(name='Alice', email='alice@example.com')
Optional поле (NULLABLE)
# Правильно: поле опциональное
phone: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
# При создании объекта phone может быть None
user = User(name='Alice', email='alice@example.com', phone=None)
# Статический анализатор поймёт, что phone может быть None
if user.phone is not None:
print(user.phone.upper()) # OK
4. Генерики для коллекций
from typing import Set
class Post(Base):
__tablename__ = 'posts'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
# Many-to-Many
tags: Mapped[Set['Tag']] = relationship(
'Tag',
secondary='post_tags', # association table
)
class Tag(Base):
__tablename__ = 'tags'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(unique=True)
5. Собственные типы данных
У SQLAlchemy есть типы для специфических данных:
from sqlalchemy import JSON, UUID, DateTime, Boolean, Numeric
from datetime import datetime, timezone
import uuid
class Order(Base):
__tablename__ = 'orders'
id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc)
)
is_paid: Mapped[bool] = mapped_column(Boolean, default=False)
metadata: Mapped[dict] = mapped_column(JSON) # Store JSON
total_price: Mapped[Decimal] = mapped_column(Numeric(10, 2))
6. Валидация при создании объекта
TYPing помогает IDE ловить ошибки на этапе разработки:
from sqlalchemy.orm import Session
def create_user(session: Session, name: str, email: str) -> User:
user = User(name=name, email=email) # IDE проверит типы
session.add(user)
session.commit()
return user
# IDE предупредит о неправильном типе
create_user(session, name=123, email='test@example.com') # ❌ Ошибка
create_user(session, name='John', email='test@example.com') # ✅ OK
7. Практический пример с полной типизацией
from typing import Optional, List
from datetime import datetime, timezone
from uuid import uuid4
class User(Base):
__tablename__ = 'users'
id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid4()))
username: Mapped[str] = mapped_column(String(50), unique=True)
email: Mapped[str] = mapped_column(String(100), unique=True)
password_hash: Mapped[str]
is_active: Mapped[bool] = mapped_column(default=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc)
)
# Relationships
posts: Mapped[List['Post']] = relationship('Post', back_populates='author')
profile: Mapped[Optional['Profile']] = relationship('Profile', uselist=False)
class Post(Base):
__tablename__ = 'posts'
id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid4()))
title: Mapped[str] = mapped_column(String(200))
content: Mapped[str] = mapped_column(String(5000))
author_id: Mapped[str] = mapped_column(ForeignKey('users.id'))
author: Mapped[User] = relationship('User', back_populates='posts')
8. Проверка типов с mypy
# Проверить весь проект на ошибки типов
mypy src/
# mypy поймёт такие ошибки:
user = User(name='Alice', email=123) # ❌ Expected str, got int
phrase = user.phone.upper() # ❌ Optional[str] has no attribute 'upper'
Заключение
Типизация в SQLAlchemy 2.0+ — это не просто украшение кода, а инструмент для ловли ошибок на этапе разработки. Используй:
Mapped[T]для полей с явной типизациейMapped[Optional[T]]для nullable полейMapped[List['RelatedModel']]для relationships- IDE и mypy будут ловить ошибки до production