#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:
- SQLite не применяет строгое соблюдение типов столбцов, он распознает только «сходства» столбцов. Поэтому можно объявить столбец как
INT
, вставить в него строку (например, строковое представление UUID), и SQLite не обязательно выдаст ошибку. - SQLite принимает объявления ограничений внешнего ключа, но по умолчанию игнорирует их. Это может привести к путанице при работе с SQLite и ожиданию, что внешние ключи «просто будут работать».
Оба эти поведения сыграли роль в этой проблеме. Ошибка кодирования заключалась в вставке строк (UUID) в столбец, который должен был быть целым числом, и отсутствие соблюдения ограничений внешнего ключа привело к тому, что эта ошибка осталась незамеченной.
Мораль: используйте SQLite и цените его за то, что он может предложить, но также помните о его особой «индивидуальности» (как и любой другой диалект SQL).