К хорошему быстро привыкаешь, и при переходе от одного инструмента, если лишаешься, чего то привычного, то становится некомфортно. Именно с такой ситуацией я столкнулся при переходе от такого замечательного фреймворка как Django, на FastApi.
Как оказалось SqlAlcemy которую я решил использовать как ORM для своего проекта также не имеет встроенных средств, наподобие тех, что реализованы в Django.
Рванувшись в гугл с этим вопросом я наткнулся на решение которое многие советовали, а именно использовать библиотку sqlalchemy-utils и встроенный в нее тип ChoicesType.
Собственно я так и поступил, поставил библиотеку, установил в соответствующий тип нужные мне поля и столкнулся с тем, что Pydantic не может нормально валидировать ChoicesType данной библиотеки. После длительных мучений было найдено решение валидировать поле и самому возвращать нужное значение в валидаторе. Но все же я решил отказаться от использования данной библиотеки и все что нужно самому написать с нуля. Итак поехали.
ChoicesType строкового типа
В sqlalchemy определяем наши модели. У меня получился примерно следующий код.
import uuid, enum from sqlalchemy.dialects.postgresql import UUID from sqlalchemy import Boolean, Column, Integer, String, Date, ForeignKey, Enum from sqlalchemy.orm import relationship from app.models.mixins import Timestamp, generic_repr from app.db.base_class import Base class SpecializationDoctorEnum(enum.Enum): """Специализации врачей""" terapevt = 'Терапевт' ortodont = 'Ортодонт' ortoped ='Ортопед' parodontolog = 'Пародонтолог' detckii_stomatolog = 'Детский стоматолог' hirurg = 'Хирург' @generic_repr class User(Base, Timestamp): '''Модель пользователя''' id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) first_name = Column(String, index=True) last_name = Column(String, index=True) midle_name = Column(String, index=True) birthday = Column(Date) image = Column(String, nullable=True) email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=False) specialization = Column(Enum(SpecializationDoctorEnum), default=SpecializationDoctorEnum.terapevt, nullable=False) is_active = Column(Boolean(), default=True) is_superuser = Column(Boolean(), default=False) clinic_id = Column(Integer, ForeignKey('clinic.id')) clinic = relationship("Clinic", back_populates="users")
Ну и соответствующая Pydantic модель.
from typing import Optional from enum import Enum from uuid import UUID from pydantic import BaseModel, EmailStr, validator from datetime import date from app.schemas.clinic import Clinic from app.models.user import SpecializationDoctorEnum class Specialization(str, Enum): terapevt = 'Терапевт' ortodont = 'Ортодонт' ortoped ='Ортопед' parodontolog = 'Пародонтолог' detckii_stomatolog = 'Детский стоматолог' hirurg = 'Хирург' # Shared properties class UserBase(BaseModel): email: Optional[EmailStr] is_active: Optional[bool] = True is_superuser: bool = False first_name: Optional[str] last_name: Optional[str] midle_name: Optional[str] birthday: Optional[date] image: Optional[str] clinic: Optional[Clinic] specialization: Optional[SpecializationDoctorEnum] class Config: orm_mode = True
Ну собственно теперь мы сможем употреблять в коде наши значения примерно так
new_user.specialization = SpecializationDoctorEnum.terapevt
ChoicesType со значениями типа Integer
В данном случае у нас будет все то же самое только в определении моделей sqlAlchemy мы будем использовать enum.IntEnum.
class NumberJobsEnum(enum.IntEnum): none = 0 one = 1 two = 2 three = 3 four = 4 five = 5 class Person(db.Model): __tablename__ = 'persons' number_of_jobs = db.Column( IntEnum(NumberJobsEnum), default=NumberJobsEnum.none, nullable=False )
Заключение
Не всегда целесообразно для реализации простых вещей тянуть целые библиотеки, особенно если за ними не стоит большое активное сообщество. В моем случае при помощи встроенного Enum типа библиотеки sqlalchemy удалось реализовать весь необходимый функционал.