Изменение поведения атрибутов¶
В этом разделе будут рассмотрены возможности и техники, используемые для изменения поведения отображаемых атрибутов ORM, включая те, которые отображаются с помощью mapped_column()
, relationship()
и других.
Простые валидаторы¶
Быстрый способ добавить процедуру «валидации» к атрибуту - использовать декоратор validates()
. Валидатор атрибута может вызвать исключение, остановив процесс изменения значения атрибута, или изменить данное значение на другое. Валидаторы, как и все расширения атрибутов, вызываются только обычным пользовательским кодом; они не выдаются, когда ORM заполняет объект:
from sqlalchemy.orm import validates
class EmailAddress(Base):
__tablename__ = "address"
id = mapped_column(Integer, primary_key=True)
email = mapped_column(String)
@validates("email")
def validate_email(self, key, address):
if "@" not in address:
raise ValueError("failed simple email validation")
return address
Валидаторы также получают события добавления коллекции, когда элементы добавляются в коллекцию:
from sqlalchemy.orm import validates
class User(Base):
# ...
addresses = relationship("Address")
@validates("addresses")
def validate_address(self, key, address):
if "@" not in address.email:
raise ValueError("failed simplified email validation")
return address
Функция проверки по умолчанию не выдается для событий удаления коллекции, поскольку обычно ожидается, что отбрасываемое значение не требует проверки. Однако validates()
поддерживает прием этих событий, указывая include_removes=True
в декораторе. Когда этот флаг установлен, функция проверки должна получить дополнительный булев аргумент, который, если True
, указывает, что операция является удалением:
from sqlalchemy.orm import validates
class User(Base):
# ...
addresses = relationship("Address")
@validates("addresses", include_removes=True)
def validate_address(self, key, address, is_remove):
if is_remove:
raise ValueError("not allowed to remove items from the collection")
else:
if "@" not in address.email:
raise ValueError("failed simplified email validation")
return address
Случай, когда взаимозависимые валидаторы связаны обратной ссылкой, также может быть адаптирован с помощью опции include_backrefs=False
; эта опция, когда она установлена в False
, предотвращает выброс функции валидации, если событие происходит в результате обратной ссылки:
from sqlalchemy.orm import validates
class User(Base):
# ...
addresses = relationship("Address", backref="user")
@validates("addresses", include_backrefs=False)
def validate_address(self, key, address):
if "@" not in address:
raise ValueError("failed simplified email validation")
return address
Выше, если бы мы присвоили Address.user
, как в some_address.user = some_user
, функция validate_address()
не была бы испущена, даже если бы произошло добавление к some_user.addresses
- событие вызвано обратной ссылкой.
Обратите внимание, что декоратор validates()
является удобной функцией, построенной поверх событий атрибутов. Приложение, которому требуется больший контроль над конфигурацией поведения при изменении атрибутов, может воспользоваться этой системой, описанной в AttributeEvents
.
Object Name | Description |
---|---|
validates(*names, [include_removes, include_backrefs]) |
Украсьте метод как «валидатор» для одного или нескольких именованных свойств. |
- function sqlalchemy.orm.validates(*names: str, include_removes: bool = False, include_backrefs: bool = True) Callable[[_Fn], _Fn] ¶
Украсьте метод как «валидатор» для одного или нескольких именованных свойств.
Назначает метод как валидатор, метод, который получает имя атрибута, а также значение, которое должно быть присвоено, или, в случае коллекции, значение, которое должно быть добавлено в коллекцию. Затем функция может вызвать исключения валидации, чтобы остановить процесс от продолжения (где встроенные в Python исключения
ValueError
иAssertionError
являются разумным выбором), или может изменить или заменить значение перед продолжением. В противном случае функция должна вернуть заданное значение.Обратите внимание, что валидатор для коллекции не может выдать загрузку этой коллекции внутри процедуры валидации - это использование вызывает утверждение, чтобы избежать переполнения рекурсии. Это реентерабельное условие, которое не поддерживается.
- Параметры:
*names – список имен атрибутов, подлежащих проверке.
include_removes – если True, будут также отправляться события «удаления» - функция проверки должна принимать дополнительный аргумент «is_remove», который будет булевым значением.
include_backrefs – по умолчанию
True
; еслиFalse
, функция валидации не будет испускаться, если инициатором является событие атрибута, связанное через обратную ссылку. Это может использоваться для двунаправленного использованияvalidates()
, когда только один валидатор должен испускаться на каждую операцию с атрибутом. … versionchanged:: 2.0.16 Этот параметр по ошибке имел значение по умолчаниюFalse `` для релизов с 2.0.0 по 2.0.15. Его правильное значение по умолчанию ``True
восстановлено в версии 2.0.16.
См.также
Простые валидаторы - примеры использования для
validates()
Использование пользовательских типов данных на уровне ядра¶
Нестандартное средство воздействия на значение столбца таким образом, чтобы преобразовать данные между тем, как они представлены в Python, и тем, как они представлены в базе данных, может быть достигнуто путем использования пользовательского типа данных, который применяется к сопоставленным метаданным Table
. Это более распространено в случае некоторого стиля кодирования / декодирования, которое происходит как при передаче данных в базу данных, так и при их возврате; подробнее об этом можно прочитать в документации Core по адресу Дополнение существующих типов.
Использование дескрипторов и гибридов¶
Более полный способ создать модифицированное поведение для атрибута - использовать descriptors. В Python они обычно используются с помощью функции property()
. Стандартная техника SQLAlchemy для дескрипторов заключается в создании обычного дескриптора и его чтении/записи из сопоставленного атрибута с другим именем. Ниже мы проиллюстрируем это с помощью свойств в стиле Python 2.6:
class EmailAddress(Base):
__tablename__ = "email_address"
id = mapped_column(Integer, primary_key=True)
# name the attribute with an underscore,
# different from the column name
_email = mapped_column("email", String)
# then create an ".email" attribute
# to get/set "._email"
@property
def email(self):
return self._email
@email.setter
def email(self, email):
self._email = email
Приведенный выше подход будет работать, но мы можем добавить еще кое-что. Хотя наш объект EmailAddress
будет передавать значение через дескриптор email
и в сопоставленный атрибут _email
, атрибут уровня класса EmailAddress.email
не имеет обычной семантики выражения, используемой в Select
. Чтобы обеспечить их, мы используем расширение hybrid
следующим образом:
from sqlalchemy.ext.hybrid import hybrid_property
class EmailAddress(Base):
__tablename__ = "email_address"
id = mapped_column(Integer, primary_key=True)
_email = mapped_column("email", String)
@hybrid_property
def email(self):
return self._email
@email.setter
def email(self, email):
self._email = email
Атрибут .email
, помимо обеспечения поведения getter/setter, когда у нас есть экземпляр EmailAddress
, также обеспечивает SQL-выражение при использовании на уровне класса, то есть непосредственно из класса EmailAddress
:
from sqlalchemy.orm import Session
from sqlalchemy import select
session = Session()
address = session.scalars(
select(EmailAddress).where(EmailAddress.email == "address@example.com")
).one()
{execsql}SELECT address.email AS address_email, address.id AS address_id
FROM address
WHERE address.email = ?
('address@example.com',)
{stop}
address.email = "otheraddress@example.com"
session.commit()
{execsql}UPDATE address SET email=? WHERE address.id = ?
('otheraddress@example.com', 1)
COMMIT
{stop}
Модификатор hybrid_property
также позволяет нам изменить поведение атрибута, включая определение раздельного поведения при обращении к атрибуту на уровне экземпляра и на уровне класса/выражения, используя модификатор hybrid_property.expression()
. Например, если мы хотим автоматически добавлять имя хоста, мы можем определить два набора логики манипулирования строками:
class EmailAddress(Base):
__tablename__ = "email_address"
id = mapped_column(Integer, primary_key=True)
_email = mapped_column("email", String)
@hybrid_property
def email(self):
"""Return the value of _email up until the last twelve
characters."""
return self._email[:-12]
@email.setter
def email(self, email):
"""Set the value of _email, tacking on the twelve character
value @example.com."""
self._email = email + "@example.com"
@email.expression
def email(cls):
"""Produce a SQL expression that represents the value
of the _email column, minus the last twelve characters."""
return func.substr(cls._email, 0, func.length(cls._email) - 12)
Выше, обращение к свойству email
экземпляра EmailAddress
вернет значение атрибута _email
, удалив или добавив имя хоста @example.com
из значения. Когда мы делаем запрос к атрибуту email
, отображается SQL-функция, которая производит тот же эффект:
address = session.scalars(
select(EmailAddress).where(EmailAddress.email == "address")
).one()
{execsql}SELECT address.email AS address_email, address.id AS address_id
FROM address
WHERE substr(address.email, ?, length(address.email) - ?) = ?
(0, 12, 'address')
{stop}
Подробнее о гибридах читайте на Атрибуты гибрида.
Синонимы¶
Синонимы - это конструкция на уровне отображения, которая позволяет любому атрибуту класса «отражать» другой атрибут, который отображается.
В самом основном смысле синоним - это простой способ сделать определенный атрибут доступным по дополнительному имени:
from sqlalchemy.orm import synonym
class MyClass(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
job_status = mapped_column(String(50))
status = synonym("job_status")
Приведенный выше класс MyClass
имеет два атрибута, .job_status
и .status
, которые будут вести себя как один атрибут, оба на уровне выражения:
>>> print(MyClass.job_status == "some_status")
{printsql}my_table.job_status = :job_status_1{stop}
>>> print(MyClass.status == "some_status")
{printsql}my_table.job_status = :job_status_1{stop}
и на уровне экземпляра:
>>> m1 = MyClass(status="x")
>>> m1.status, m1.job_status
('x', 'x')
>>> m1.job_status = "y"
>>> m1.status, m1.job_status
('y', 'y')
synonym()
может использоваться для любого вида сопоставленных атрибутов, которые подклассы MapperProperty
, включая сопоставленные колонки и отношения, а также сами синонимы.
Помимо простого зеркала, synonym()
можно также сделать так, чтобы он ссылался на определенный пользователем descriptor. Мы можем снабдить наш синоним status
синонимом @property
:
class MyClass(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
status = mapped_column(String(50))
@property
def job_status(self):
return "Status: " + self.status
job_status = synonym("status", descriptor=job_status)
При использовании Declarative вышеприведенный шаблон может быть выражен более лаконично с помощью декоратора synonym_for()
:
from sqlalchemy.ext.declarative import synonym_for
class MyClass(Base):
__tablename__ = "my_table"
id = mapped_column(Integer, primary_key=True)
status = mapped_column(String(50))
@synonym_for("status")
@property
def job_status(self):
return "Status: " + self.status
Хотя synonym()
полезен для простого зеркалирования, в современном использовании для дополнения поведения атрибутов дескрипторами лучше использовать функцию hybrid attribute, которая больше ориентирована на дескрипторы Python. Технически, synonym()
может делать все то же, что и hybrid_property
, поскольку он также поддерживает внедрение пользовательских возможностей SQL, но гибрид более прост в использовании в более сложных ситуациях.
Object Name | Description |
---|---|
synonym(name, *, [map_column, descriptor, comparator_factory, init, repr, default, default_factory, compare, kw_only, info, doc]) |
Обозначьте имя атрибута как синоним сопоставленного свойства, в том смысле, что атрибут будет отражать значение и поведение выражения другого атрибута. |
- function sqlalchemy.orm.synonym(name: str, *, map_column: Optional[bool] = None, descriptor: Optional[Any] = None, comparator_factory: Optional[Type[PropComparator[_T]]] = None, init: Union[_NoArg, bool] = _NoArg.NO_ARG, repr: Union[_NoArg, bool] = _NoArg.NO_ARG, default: Union[_NoArg, _T] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None) Synonym[Any] ¶
Обозначьте имя атрибута как синоним сопоставленного свойства, в том смысле, что атрибут будет отражать значение и поведение выражения другого атрибута.
например:
class MyClass(Base): __tablename__ = 'my_table' id = Column(Integer, primary_key=True) job_status = Column(String(50)) status = synonym("job_status")
- Параметры:
name – имя существующего сопоставленного свойства. Это может относиться к строковому имени ORM-сопоставленного атрибута, настроенного на класс, включая связанные с колонками атрибуты и отношения.
descriptor – Python descriptor, который будет использоваться в качестве getter (и потенциально setter) при обращении к этому атрибуту на уровне экземпляра.
map_column – ** Только для классических отображений и отображений на существующий объект Table**. Если
True
, конструкцияsynonym()
найдет объектColumn
на отображенной таблице, который обычно ассоциируется с именем атрибута этого синонима, и создаст новыйColumnProperty
, который вместо этого отобразит этотColumn
на альтернативное имя, указанное в качестве аргумента «имя» синонима; таким образом, обычный шаг переопределения отображенияColumn
на другое имя становится ненужным. Обычно это используется, когдаColumn
нужно заменить атрибутом, который также использует дескриптор, то есть в сочетании с параметромsynonym.descriptor
:: my_table = Table( «my_table», metadata, Column(„id“, Integer, primary_key=True), Column(„job_status“, String(50)) ) class MyClass: @property def _job_status_descriptor(self): return «Status: %s» % self._job_status mapper( MyClass, my_table, properties={ «job_status»: synonym( «_job_status», map_column=True, descriptor=MyClass._job_status_descriptor) } ) Выше, атрибут с именем_job_status
автоматически отображается на колонкуjob_status
:: >>> j1 = MyClass() >>> j1._job_status = «employed» >>> j1.job_status Статус: employed При использовании Declarative, чтобы предоставить дескриптор в сочетании с синонимом, используйте помощникsqlalchemy.ext.declarative.synonym_for()
. Однако обратите внимание, что обычно предпочтение отдается функции hybrid properties, особенно при переопределенииinfo – Необязательный словарь данных, который будет заполнен в атрибут
InspectionAttr.info
этого объекта.comparator_factory – Подкласс
PropComparator
, обеспечивающий пользовательское поведение сравнения на уровне SQL-выражения. … примечание:: Для случая использования атрибута, который переопределяет поведение атрибута как на уровне Python, так и на уровне SQL-выражений, пожалуйста, обратитесь к атрибуту Hybrid, представленному в Использование дескрипторов и гибридов для более эффективной техники.
См.также
Синонимы - Обзор синонимов
synonym_for()
- помощник, ориентированный на декларативностьИспользование дескрипторов и гибридов - Расширение Hybrid Attribute обеспечивает обновленный подход к расширению поведения атрибутов более гибко, чем это может быть достигнуто с помощью синонимов.
Настройка оператора¶
Операторы», используемые SQLAlchemy ORM и языком выражений Core, являются полностью настраиваемыми. Например, выражение сравнения User.name == 'ed'
использует встроенный в Python оператор operator.eq
- фактическая конструкция SQL, которую SQLAlchemy ассоциирует с таким оператором, может быть изменена. Новые операции также могут быть связаны с выражениями столбцов. Операторы, которые имеют место для выражений столбцов, наиболее непосредственно переопределяются на уровне типов - описание см. в разделе Переопределение и создание новых операторов.
Функции уровня ORM, такие как column_property()
, relationship()
и composite()
, также обеспечивают переопределение оператора на уровне ORM, передавая подкласс PropComparator
в аргумент comparator_factory
каждой функции. Настройка операторов на этом уровне является редким случаем использования. Обзор см. в документации по адресу PropComparator
.