SqlAlchemy 2.0 中文文档(八)(2)https://developer.aliyun.com/article/1559847
在核心级别使用自定义数据类型
通过使用应用于映射的 Table
元数据的自定义数据类型,可以以适合在 Python 中的表示方式与在数据库中的表示方式之间转换数据的方式来影响列的值的非 ORM 方法。这在某些编码/解码样式在数据进入数据库和返回时都发生的情况下更为常见;在核心文档中阅读更多关于此的内容,参见扩充现有类型。
使用描述符和混合类型
产生修改后的属性行为的更全面的方法是使用描述符。在 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
属性除了在我们有EmailAddress
实例时提供 getter/setter 行为外,在类级别使用时也提供了一个 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() SELECT address.email AS address_email, address.id AS address_id FROM address WHERE address.email = ? ('address@example.com',) address.email = "otheraddress@example.com" session.commit() UPDATE address SET email=? WHERE address.id = ? ('otheraddress@example.com', 1) COMMIT
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)
以上,访问EmailAddress
实例的email
属性将返回_email
属性的值,从值中删除或添加主机名@example.com
。当我们针对email
属性进行查询时,将呈现出一个产生相同效果的 SQL 函数:
address = session.scalars( select(EmailAddress).where(EmailAddress.email == "address") ).one() SELECT address.email AS address_email, address.id AS address_id FROM address WHERE substr(address.email, ?, length(address.email) - ?) = ? (0, 12, 'address')
在混合属性中阅读更多内容。
同义词
同义词是一个映射级别的构造,允许类上的任何属性“镜像”另一个被映射的属性。
从最基本的意义上讲,同义词是一种简单的方式,通过额外的名称使某个属性可用:
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") my_table.job_status = :job_status_1 >>> print(MyClass.status == "some_status") my_table.job_status = :job_status_1
在实例级别上:
>>> 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()
还可以引用用户定义的描述符。我们可以用@property
来提供我们的status
同义词:
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)
在使用声明性时,可以使用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()
对于简单的镜像很有用,但是使用描述符增强属性行为的用例更好地在现代使用中使用混合属性特性来处理,后者更加面向 Python 描述符。从技术上讲,一个synonym()
可以做任何一个hybrid_property
能做的事情,因为它也支持注入自定义 SQL 功能,但是在更复杂的情况下混合属性更容易使用。
对象名称 | 描述 |
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: bool | None = None, descriptor: Any | None = None, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = 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 描述符,当访问此属性时将用作 getter(和可能的 setter)。map_column
–
仅适用于传统映射和对现有表对象的映射。如果为True
,则synonym()
构造将定位到在此同义词的属性名称通常与该同义词的属性名称相关联的映射表上的Column
对象,并生成一个新的ColumnProperty
,该属性将此Column
映射到作为同义词的“name”参数给定的替代名称;通过这种方式,重新定义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 Status: employed
- 当使用声明式时,为了与同义词结合使用提供描述符,请使用
sqlalchemy.ext.declarative.synonym_for()
助手。但是,请注意,通常应优选混合属性功能,特别是在重新定义属性行为时。 info
– 可选的数据字典,将填充到此对象的InspectionAttr.info
属性中。comparator_factory
–PropComparator
的子类,将在 SQL 表达式级别提供自定义比较行为。
注意
对于提供重新定义属性的 Python 级别和 SQL 表达式级别行为的用例,请参阅使用描述符和混合中介绍的混合属性,这是一种更有效的技术。
另请参阅
同义词 - 同义词概述
synonym_for()
- 一种面向声明式的辅助工具
使用描述符和混合 - 混合属性扩展提供了一种更新的方法,可以更灵活地增强属性行为,比同义词更有效。
运算符定制
SQLAlchemy ORM 和 Core 表达式语言使用的“运算符”是完全可定制的。例如,比较表达式 User.name == 'ed'
使用了 Python 本身内置的名为 operator.eq
的运算符 - SQLAlchemy 关联的实际 SQL 构造可以被修改。新的操作也可以与列表达式关联起来。最直接重新定义列表达式的运算符的方法是在类型级别进行 - 详细信息请参阅重新定义和创建新的运算符。
ORM 级别的函数如column_property()
、relationship()
和composite()
还提供了在 ORM 级别重新定义运算符的功能,方法是将PropComparator
子类传递给每个函数的comparator_factory
参数。在这个级别定制运算符的情况很少见。详细信息请参阅PropComparator
的文档概述。
SqlAlchemy 2.0 中文文档(八)(4)https://developer.aliyun.com/article/1559851