Валидация данных в SQLAlchemy с Pydantic: пример и оптимизация

    При разработке backend-приложений на Python часто возникает задача валидации данных перед записью в базу данных. SQLAlchemy - мощный ORM, но он не предоставляет встроенных средств для кастомной проверки значений полей. Решение - использовать библиотеку Pydantic, которая позволяет задавать строгие правила для каждой модели данных. В этой статье разберём пример интеграции SQLAlchemy и Pydantic для модели студента, оценим корректность кода и предложим способы его оптимизации.

    Проблема: отсутствие валидации в SQLAlchemy

    Стандартные колонки SQLAlchemy (например, String(30)) ограничивают только длину хранимого значения, но не проверяют содержание. Например, имя студента не должно начинаться с цифры, а специальность должна входить в утверждённый список. Для таких сценариев нужна внешняя валидация.

    Решение: модель Pydantic для проверки данных

    Создадим класс StudentValidation на основе BaseModel из Pydantic. В нём определим поля и добавим кастомные валидаторы через декоратор @field_validator.

    Пример модели Pydantic

    class StudentValidation(BaseModel):
        student_first_name: str
        student_last_name: str | None
        specialty: str
        enrolment: str
    
        @field_validator('student_first_name')
        def validate_student_first_name(cls, value):
            if len(value) > 50:
                raise ValueError('Длина имени не должна превышать 50 символов.')
            if value[0].isdigit():
                raise ValueError(f'Имя не должно начинаться с цифры: {value}')
            return value
    
        @field_validator('student_last_name')
        def validate_student_last_name(cls, value):
            if len(value) > 50:
                raise ValueError('Длина фамилии не должна превышать 50 символов.')
            if value[0].isdigit():
                raise ValueError(f'Фамилия не должна начинаться с цифры: {value}')
            return value
    
        @field_validator('specialty')
        def validate_specialty(cls, value):
            if value not in specialties:
                raise ValueError(f'Приведенная специальность -> {value}, в то время как список допустимых -> {specialties}')
            return value

    Модель SQLAlchemy для таблицы students

    Определим класс Student с колонками, соответствующими полям Pydantic-модели. Обратите внимание: типы в SQLAlchemy (например, String(30)) могут отличаться от ограничений в Pydantic (50 символов). Рекомендуется синхронизировать эти значения.

    class Student(Base):
        __tablename__ = 'students'
        student_id: Mapped[int] = mapped_column(primary_key=True)
        student_first_name: Mapped[str] = mapped_column(String(30))
        student_last_name: Mapped[str | None] = mapped_column(String(30))
        specialty: Mapped[str]
        enrolment: Mapped[str]

    Функция добавления студента в БД

    Создадим функцию insert_student, которая принимает экземпляр StudentValidation, преобразует его в объект SQLAlchemy и сохраняет в базе данных.

    def insert_student(student: StudentValidation):
        student_db = Student(
            student_first_name=student.student_first_name,
            student_last_name=student.student_last_name,
            specialty=student.specialty,
            enrolment=student.enrolment
        )
        with Session(engine) as connection:
            connection.add(student_db)
            connection.commit()
    
    insert_student(StudentValidation(**data))

    Как улучшить код: советы по оптимизации

    Представленный код корректен, но его можно сделать компактнее и надёжнее. Вот несколько рекомендаций:

    • Синхронизируйте лимиты: если Pydantic проверяет длину до 50 символов, а колонка SQLAlchemy - до 30, данные пройдут валидацию, но не запишутся. Унифицируйте ограничения.
    • Используйте конфигурацию модели Pydantic: вместо повторяющихся валидаторов для имени и фамилии создайте один общий метод или используйте Annotated тип.
    • Добавьте обработку ошибок: оберните вызов insert_student в try-except, чтобы корректно реагировать на исключения валидации или базы данных.
    • Разделите логику: вынесите преобразование модели в отдельный метод to_orm() внутри StudentValidation.

    Заключение

    Интеграция SQLAlchemy и Pydantic - правильный и гибкий подход для валидации данных в Python-проектах. Ваш код работает, но его можно улучшить, следуя принципам DRY и синхронизируя ограничения. Используйте Pydantic для проверки бизнес-правил, а SQLAlchemy - для работы с базой данных. Это обеспечит чистоту и надёжность вашего backend-приложения.

    Часто задаваемые вопросы