SQLAlchemy не соответствует столбцу с запросом orm

#python #unit-testing #sqlalchemy #pytest

#python #модульное тестирование #sqlalchemy #pytest

Вопрос:

У меня есть простая взаимосвязь таблиц / моделей подписки и подписчиков в приложении Flask, в котором я использую SQLAlchemy для локального запроса базы данных SQLite во время разработки.

Подписка связана с подписчиком через subscriber_id поле, определенное следующим образом:

 subscriber_id = Column(db.Integer, ForeignKey('subscriber.id'))
 

Оба объекта имеют поле, определенное как String(36) , в которое я записываю строку UUID4, когда они создаются в базе данных. Они subscription_id и subscriber_id , соответственно, такие:

 @dataclass
class Subscription(db.Model):
    __tablename__ = 'subscription'

    subscription: str
    subscriber: str

    id = Column(db.Integer, primary_key=True)
    subscription_id = Column(
        db.String(36),
        default=generate_uuid,  # a function that returns a uuid4
        unique=True
    )
    subscriber_id = Column(db.Integer, ForeignKey('subscriber.id'))
 

Во время выполнения теста с использованием PyTest я сталкиваюсь со странным поведением, при котором subscriber_id экземпляр Subscription экземпляра возвращает не целое число, как определено в классе, а UUID.

Тест запускает метод, который запрашивает подписчика, а затем пытается получить его подписки:

 subscriber = Subscriber.query.filter(
    Subscriber.subscriber_id == subscriber_id
).first()

assert subscriber is not None

subscriptions = Subscription.query.filter(
    Subscription.subscriber_id == subscriber_id
).all()
 

subscriber_id происходит 1 во время выполнения, и подписчик извлекается из базы данных.

Однако второй запрос не работает, хотя subscriber_id является внешним ключом для подписчика id .

Если я заменю второй запрос другим без фильтра, это сработает. Но затем я вижу эту проблему, когда subscriber_id возвращаемое значение является не целым числом, а UUID:

 raise Exception(Subscription.query.first().subscriber_id)
 
         subscriptions = Subscription.query.filter(
            Subscription.subscriber_id == subscriber_id
        ).all()
    
>       raise Exception(Subscription.query.first().subscriber_id)
E       Exception: 59ebfdd6-1cbc-4748-94e1-955ef55c380c
 

Существует ли особое поведение SQLAlchemy, которое я здесь не учитываю? Возможно, соглашение об именовании таблиц / полей?

Комментарии:

1. Subscription.id уже является первичным ключом. Какова цель ключа-кандидата Subscription.subscription_id ?

2. кроме того, проверьте свою работу. subscriber_id не вернет uuid, но subscription_id вернет. Здесь нет соглашения об именовании.

3. @GordThompson цель состоит в том, чтобы не показывать фактический идентификатор в URI или теле запроса, поскольку это API. Это своего рода альтернативный идентификатор. Однако я планирую использовать фактический id во внутренних вызовах. Я обновлю исходное сообщение дополнительной информацией.

4. О, подождите, это SQLite. Вы явно включили поддержку внешнего ключа ?

5. @GordThompson это действительно странно. При явном включении поддержки внешнего ключа я получаю исключение IntegrityException.

Ответ №1:

Каким бы замечательным ни был SQLite, у него есть две особенности, которые делают его совершенно «отличным» от большинства других реализаций SQL:

  1. SQLite не применяет строгое соблюдение типов столбцов, он распознает только «сходства» столбцов. Поэтому можно объявить столбец как INT , вставить в него строку (например, строковое представление UUID), и SQLite не обязательно выдаст ошибку.
  2. SQLite принимает объявления ограничений внешнего ключа, но по умолчанию игнорирует их. Это может привести к путанице при работе с SQLite и ожиданию, что внешние ключи «просто будут работать».

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

Мораль: используйте SQLite и цените его за то, что он может предложить, но также помните о его особой «индивидуальности» (как и любой другой диалект SQL).