Есть ли способ прозрачно выполнить проверку на объектах SQLAlchemy?
Есть ли способ выполнить проверку на объект после (или как) свойств, установленных перед сеансом?
Например, у меня есть модель домена Device
, которая имеет свойство mac
. Я хотел бы убедиться, что свойство mac
содержит действительное и дезинфицированное значение mac перед его добавлением или обновлением в базе данных.
Похоже, что подход Pythonic должен делать большинство вещей как свойства (включая SQLAlchemy). Если бы я закодировал это на PHP или Java, я бы, вероятно, решил создать методы getter/setter для защиты данных и дать мне возможность справиться с этим в самой модели домена.
public function mac() { return $this->mac; }
public function setMac($mac) {
return $this->mac = $this->sanitizeAndValidateMac($mac);
}
public function sanitizeAndValidateMac($mac) {
if ( ! preg_match(self::$VALID_MAC_REGEX) ) {
throw new InvalidMacException($mac);
}
return strtolower($mac);
}
Что такое Pythonic способ справиться с этим типом ситуации с помощью SQLAlchemy?
(Хотя я знаю, что валидация и должна быть обработана в другом месте (т.е. веб-фреймворк), я хотел бы выяснить, как обрабатывать некоторые из этих правил проверки подлинности в домене, поскольку они неизбежно возникают часто.)
UPDATE
Я знаю, что я мог бы использовать property, чтобы сделать это при обычных обстоятельствах. Ключевой частью является то, что я использую SQLAlchemy с этими классами. Я не понимаю, как именно SQLAlchemy выполняет свою магию, но я подозреваю, что создание и переопределение этих свойств может привести к нестабильным и/или непредсказуемым результатам.
Ответы
Ответ 1
Вы можете добавить проверку данных внутри своих классов SQLAlchemy с помощью декоратора @validates()
.
Из документов - Простые валидаторы:
Валидатор атрибутов может вызывать исключение, останавливать процесс изменения значения атрибутов или изменять данное значение во что-то другое.
from sqlalchemy.orm import validates
class EmailAddress(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
@validates('email')
def validate_email(self, key, address):
# you can use assertions, such as
# assert '@' in address
# or raise an exception:
if '@' not in address:
raise ValueError('Email address must contain an @ sign.')
return address
Ответ 2
Да. Это можно сделать красиво, используя MapperExtension.
# uses sqlalchemy hooks to data model class specific validators before update and insert
class ValidationExtension( sqlalchemy.orm.interfaces.MapperExtension ):
def before_update(self, mapper, connection, instance):
"""not every instance here is actually updated to the db, see http://www.sqlalchemy.org/docs/reference/orm/interfaces.html?highlight=mapperextension#sqlalchemy.orm.interfaces.MapperExtension.before_update"""
instance.validate()
return sqlalchemy.orm.interfaces.MapperExtension.before_update(self, mapper, connection, instance)
def before_insert(self, mapper, connection, instance):
instance.validate()
return sqlalchemy.orm.interfaces.MapperExtension.before_insert(self, mapper, connection, instance)
sqlalchemy.orm.mapper( model, table, extension = ValidationExtension(), **mapper_args )
Вы можете проверить before_update, потому что не каждый экземпляр здесь фактически обновляется до db.
Ответ 3
"Похоже, что подход Pythonic - это делать большинство вещей как свойства"
Он меняется, но это близко.
"Если бы я закодировал это на PHP или Java, я бы, вероятно, решил создать методы getter/setter..."
Хорошо. Это достаточно Pythonic. Ваши функции getter и setter связаны с свойством; это очень хорошо.
Какой вопрос?
Вы спрашиваете, как записать property?
Тем не менее, "прозрачная проверка" - если я правильно прочитал ваш пример кода - может быть, не совсем такая хорошая идея.
Ваша модель и ваша проверка, вероятно, должны храниться отдельно. Общепринято иметь несколько валидаций для одной модели. Для некоторых пользователей поля являются необязательными, фиксированными или не используются; это приводит к нескольким проверкам.
Вы будете счастливее, следуя шаблону проектирования Django, используя Form для проверки, отдельно отформатируйте модель.