在ORM映射类上定义具有“混合”行为的属性。
“混合”是指属性在类级别和实例级别定义了不同的行为。
这个 hybrid
扩展提供了一种特殊形式的方法修饰器,大约有50行代码,几乎不依赖于其他的SQLAlchemy。理论上,它可以与任何基于描述符的表达式系统一起工作。
考虑映射 Interval
,表示整数 start
和 end
价值观。我们可以在生成类级SQL表达式的映射类上定义更高级别的函数,在实例级定义python表达式计算。下面,每个功能都用 hybrid_method
或 hybrid_property
可能收到 self
作为类的实例,或作为类本身:
from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, aliased
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
Base = declarative_base()
class Interval(Base):
__tablename__ = 'interval'
id = Column(Integer, primary_key=True)
start = Column(Integer, nullable=False)
end = Column(Integer, nullable=False)
def __init__(self, start, end):
self.start = start
self.end = end
@hybrid_property
def length(self):
return self.end - self.start
@hybrid_method
def contains(self, point):
return (self.start <= point) & (point <= self.end)
@hybrid_method
def intersects(self, other):
return self.contains(other.start) | self.contains(other.end)
上面, length
属性返回 end
和 start
属性。以…为例 Interval
,此减法在python中进行,使用普通的python描述符机制:
>>> i1 = Interval(5, 10)
>>> i1.length
5
当处理 Interval
类本身, hybrid_property
描述符根据给定的 Interval
类作为参数,当使用SqlAlchemy表达式机制进行计算时,该参数返回新的SQL表达式::
>>> print Interval.length
interval."end" - interval.start
>>> print Session().query(Interval).filter(Interval.length > 10)
SELECT interval.id AS interval_id, interval.start AS interval_start,
interval."end" AS interval_end
FROM interval
WHERE interval."end" - interval.start > :param_1
ORM方法,例如 filter_by()
普遍使用 getattr()
要定位属性,也可以与混合属性一起使用:
>>> print Session().query(Interval).filter_by(length=5)
SELECT interval.id AS interval_id, interval.start AS interval_start,
interval."end" AS interval_end
FROM interval
WHERE interval."end" - interval.start = :param_1
这个 Interval
类示例还演示了两种方法, contains()
和 intersects()
,装饰有 hybrid_method
. 这个修饰器将相同的思想应用于 hybrid_property
适用于属性。方法返回布尔值,并利用python |
和 &
按位运算符生成等效的实例级和SQL表达式级布尔行为:
>>> i1.contains(6)
True
>>> i1.contains(15)
False
>>> i1.intersects(Interval(7, 18))
True
>>> i1.intersects(Interval(25, 29))
False
>>> print Session().query(Interval).filter(Interval.contains(15))
SELECT interval.id AS interval_id, interval.start AS interval_start,
interval."end" AS interval_end
FROM interval
WHERE interval.start <= :start_1 AND interval."end" > :end_1
>>> ia = aliased(Interval)
>>> print Session().query(Interval, ia).filter(Interval.intersects(ia))
SELECT interval.id AS interval_id, interval.start AS interval_start,
interval."end" AS interval_end, interval_1.id AS interval_1_id,
interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
FROM interval, interval AS interval_1
WHERE interval.start <= interval_1.start
AND interval."end" > interval_1.start
OR interval.start <= interval_1."end"
AND interval."end" > interval_1."end"
我们使用的 &
和 |
上面的按位运算符很幸运,因为我们的函数对两个布尔值进行了运算,以返回一个新的布尔值。在许多情况下,in-python函数和sqlAlchemy SQL表达式的构造有足够的差异,因此应该定义两个单独的python表达式。这个 hybrid
装饰符定义 hybrid_property.expression()
用于此目的的修饰符。例如,我们将定义间隔的半径,这需要使用绝对值函数:
from sqlalchemy import func
class Interval(object):
# ...
@hybrid_property
def radius(self):
return abs(self.length) / 2
@radius.expression
def radius(cls):
return func.abs(cls.length) / 2
在python函数之上 abs()
用于实例级操作,SQL函数 ABS()
是通过 func
类级表达式的对象::
>>> i1.radius
2
>>> print Session().query(Interval).filter(Interval.radius > 5)
SELECT interval.id AS interval_id, interval.start AS interval_start,
interval."end" AS interval_end
FROM interval
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
注解
当为混合属性或方法定义表达式时,表达式方法 must 保留原始混合的名称,否则具有附加状态的新混合将附加到具有不匹配名称的类。要使用上面的示例:
class Interval(object):
# ...
@hybrid_property
def radius(self):
return abs(self.length) / 2
# WRONG - the non-matching name will cause this function to be
# ignored
@radius.expression
def radius_expression(cls):
return func.abs(cls.length) / 2
这对于其他的变异器方法也是如此,例如 hybrid_property.update_expression()
. 这和 @property
构造它是标准python的一部分。
混合属性还可以定义setter方法。如果我们想要 length
上面,设置后修改端点值:
class Interval(object):
# ...
@hybrid_property
def length(self):
return self.end - self.start
@length.setter
def length(self, value):
self.end = self.start + value
这个 length(self, value)
方法现在在set:上调用:
>>> i1 = Interval(5, 10)
>>> i1.length
5
>>> i1.length = 12
>>> i1.end
17
当使用 Query.update()
方法,允许在update的set子句中使用混合。
通常,当使用混合动力 Query.update()
,SQL表达式用作作为集合目标的列。如果我们 Interval
班上有一辆混合动力车 start_point
链接到 Interval.start
,可直接替换为:
session.query(Interval).update({Interval.start_point: 10})
但是,当使用类似 Interval.length
,此混合表示多个列。我们可以设置一个处理程序来容纳传递给 Query.update()
可能会影响这一点,使用 hybrid_property.update_expression()
装饰者。与setter类似的处理程序是:
class Interval(object):
# ...
@hybrid_property
def length(self):
return self.end - self.start
@length.setter
def length(self, value):
self.end = self.start + value
@length.update_expression
def length(cls, value):
return [
(cls.end, cls.start + value)
]
上面,如果我们使用 Interval.length
在更新表达式中作为:
session.query(Interval).update(
{Interval.length: 25}, synchronize_session='fetch')
我们将得到一个更新语句,其行如下:
UPDATE interval SET end=start + :value
在某些情况下,默认的“evaluate”策略不能在python中执行集合表达式;虽然支持上面使用的addition操作符,但是对于更复杂的集合表达式,通常需要使用“fetch”或错误的同步策略,如上图所示。
1.2 新版功能: 增加了对混合属性批量更新的支持。
与基于列的数据相比,创建与相关对象一起工作的混合对象没有本质的区别。对不同表达式的需求往往更大。我们将说明的两种变体是“依赖连接”的混合体和“相关子查询”的混合体。
考虑下面的声明性映射,它与 User
到A SavingsAccount
::
from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
Base = declarative_base()
class SavingsAccount(Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
balance = Column(Numeric(15, 5))
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
accounts = relationship("SavingsAccount", backref="owner")
@hybrid_property
def balance(self):
if self.accounts:
return self.accounts[0].balance
else:
return None
@balance.setter
def balance(self, value):
if not self.accounts:
account = Account(owner=self)
else:
account = self.accounts[0]
account.balance = value
@balance.expression
def balance(cls):
return SavingsAccount.balance
上述混合性质 balance
与第一个 SavingsAccount
此用户的帐户列表中的条目。在python中getter/setter方法可以处理 accounts
作为上提供的python列表 self
.
但是,在表达式级别,应该 User
类将在适当的上下文中使用,以便 SavingsAccount
将出席:
>>> print Session().query(User, User.balance).\
... join(User.accounts).filter(User.balance > 5000)
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
但是请注意,尽管实例级访问器需要担心 self.accounts
即使存在,这个问题在SQL表达式级别上也表现得不同,我们基本上会使用外部联接:
>>> from sqlalchemy import or_
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
Hybrid属性还包括一个助手,它允许构建自定义比较器。Comparator对象允许您分别自定义每个SQLAlchemy表达式运算符的行为。它们在创建自定义类型时非常有用,这些自定义类型在SQL端具有一些非常特殊的行为。
注解
这个 hybrid_property.comparator()
本节介绍的装饰器 替换 使用的 hybrid_property.expression()
装饰者。它们不能一起使用。
下面的示例类允许对名为 word_insensitive
::
from sqlalchemy.ext.hybrid import Comparator, hybrid_property
from sqlalchemy import func, Column, Integer, String
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class CaseInsensitiveComparator(Comparator):
def __eq__(self, other):
return func.lower(self.__clause_element__()) == func.lower(other)
class SearchWord(Base):
__tablename__ = 'searchword'
id = Column(Integer, primary_key=True)
word = Column(String(255), nullable=False)
@hybrid_property
def word_insensitive(self):
return self.word.lower()
@word_insensitive.comparator
def word_insensitive(cls):
return CaseInsensitiveComparator(cls.word)
上面的SQL表达式 word_insensitive
将应用 LOWER()
两边的SQL函数:
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
FROM searchword
WHERE lower(searchword.word) = lower(:lower_1)
这个 CaseInsensitiveComparator
上面实现了 ColumnOperators
接口。“强制”操作(如lowercasing)可应用于所有比较操作(即 eq
, lt
, gt
等)使用 Operators.operate()
::
class CaseInsensitiveComparator(Comparator):
def operate(self, op, other):
return op(func.lower(self.__clause_element__()), func.lower(other))
一个混合可以从一个超类中引用,以允许修改诸如 hybrid_property.getter()
, hybrid_property.setter()
用于在子类上重新定义这些方法。这类似于标准的python @property
对象作品:
class FirstNameOnly(Base):
# ...
first_name = Column(String)
@hybrid_property
def name(self):
return self.first_name
@name.setter
def name(self, value):
self.first_name = value
class FirstNameLastName(FirstNameOnly):
# ...
last_name = Column(String)
@FirstNameOnly.name.getter
def name(self):
return self.first_name + ' ' + self.last_name
@name.setter
def name(self, value):
self.first_name, self.last_name = value.split(' ', 1)
上面, FirstNameLastName
类是指来自 FirstNameOnly.name
为子类重新调整getter和setter的用途。
当超越 hybrid_property.expression()
和 hybrid_property.comparator()
作为对超类的第一个引用,这些名称与类级别上相同的命名访问器冲突 QueryableAttribute
对象在类级别返回。要在直接引用父类描述符时重写这些方法,请添加特殊限定符 hybrid_property.overrides
,它会将插入指令的属性反引用回混合对象::
class FirstNameLastName(FirstNameOnly):
# ...
last_name = Column(String)
@FirstNameOnly.name.overrides.expression
def name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
1.2 新版功能: 补充 hybrid_property.getter()
以及根据子类重新定义访问器的能力。
注意在前面的示例中,如果要比较 word_insensitive
的属性 SearchWord
实例到一个普通的python字符串,普通的python字符串不会被强制为小写- CaseInsensitiveComparator
我们构造,由 @word_insensitive.comparator
,仅适用于SQL端。
自定义比较器的一种更全面的形式是构造 混合值对象 . 此技术将目标值或表达式应用于值对象,然后由访问器在所有情况下返回该对象。Value对象允许控制对该值的所有操作,以及如何处理比较值,包括在SQL表达式端和Python值端。替换上一个 CaseInsensitiveComparator
用新的 CaseInsensitiveWord
班级:
class CaseInsensitiveWord(Comparator):
"Hybrid value representing a lower case representation of a word."
def __init__(self, word):
if isinstance(word, basestring):
self.word = word.lower()
elif isinstance(word, CaseInsensitiveWord):
self.word = word.word
else:
self.word = func.lower(word)
def operate(self, op, other):
if not isinstance(other, CaseInsensitiveWord):
other = CaseInsensitiveWord(other)
return op(self.word, other.word)
def __clause_element__(self):
return self.word
def __str__(self):
return self.word
key = 'word'
"Label to apply to Query tuple results"
上面, CaseInsensitiveWord
对象表示 self.word
,可以是SQL函数,也可以是Python本机函数。压倒一切 operate()
和 __clause_element__()
在…方面工作 self.word
,所有比较操作都将针对 word
,无论是SQL端还是Python端。我们的 SearchWord
现在可以交付 CaseInsensitiveWord
从单个混合调用中无条件获取对象::
class SearchWord(Base):
__tablename__ = 'searchword'
id = Column(Integer, primary_key=True)
word = Column(String(255), nullable=False)
@hybrid_property
def word_insensitive(self):
return CaseInsensitiveWord(self.word)
这个 word_insensitive
属性现在普遍具有不区分大小写的比较行为,包括SQL表达式与Python表达式(注意,这里的python值被转换为小写):
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
FROM searchword
WHERE lower(searchword.word) = :lower_1
SQL表达式与SQL表达式:
>>> sw1 = aliased(SearchWord)
>>> sw2 = aliased(SearchWord)
>>> print Session().query(
... sw1.word_insensitive,
... sw2.word_insensitive).\
... filter(
... sw1.word_insensitive > sw2.word_insensitive
... )
SELECT lower(searchword_1.word) AS lower_1,
lower(searchword_2.word) AS lower_2
FROM searchword AS searchword_1, searchword AS searchword_2
WHERE lower(searchword_1.word) > lower(searchword_2.word)
仅限python表达式::
>>> ws1 = SearchWord(word="SomeWord")
>>> ws1.word_insensitive == "sOmEwOrD"
True
>>> ws1.word_insensitive == "XOmEwOrX"
False
>>> print ws1.word_insensitive
someword
混合值模式对于任何可能具有多种表示形式的值都非常有用,例如时间戳、时间增量、度量单位、货币和加密密码。
参见
Hybrids and Value Agnostic Types -在techspot.zzzeek.org博客上
Value Agnostic Types, Part II -在techspot.zzzeek.org博客上
A transformer 是一个可以接收 Query
对象并返回新的。这个 Query
对象包含一个方法 with_transformation()
它返回一个新的 Query
由给定函数转换。
我们可以把这个和 Comparator
类来生成一种类型的方法,既可以设置查询的FROM子句,也可以指定筛选条件。
考虑映射类 Node
,它使用邻接列表组装成层次树模式:
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
parent = relationship("Node", remote_side=id)
假设我们想添加一个访问器 grandparent
. 这将返回 parent
属于 Node.parent
. 当我们有一个 Node
,这很简单:
from sqlalchemy.ext.hybrid import hybrid_property
class Node(Base):
# ...
@hybrid_property
def grandparent(self):
return self.parent.parent
在表达上,事情并不那么清楚。我们需要构造一个 Query
我们在哪里 join()
两次沿着 Node.parent
到达 grandparent
. 我们可以返回一个转换可调用文件,并将其与 Comparator
类接收任何 Query
对象,并返回一个新的 Node.parent
属性并根据给定条件进行筛选:
from sqlalchemy.ext.hybrid import Comparator
class GrandparentTransformer(Comparator):
def operate(self, op, other):
def transform(q):
cls = self.__clause_element__()
parent_alias = aliased(cls)
return q.join(parent_alias, cls.parent).\
filter(op(parent_alias.parent, other))
return transform
Base = declarative_base()
class Node(Base):
__tablename__ = 'node'
id =Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
parent = relationship("Node", remote_side=id)
@hybrid_property
def grandparent(self):
return self.parent.parent
@grandparent.comparator
def grandparent(cls):
return GrandparentTransformer(cls)
这个 GrandparentTransformer
超越核心 Operators.operate()
方法的基础 Comparator
返回查询转换可调用的层次结构,然后在特定上下文中运行给定的比较操作。例如,在上面的示例中, operate
方法被调用,给定 Operators.eq
可调用以及比较的右侧 Node(id=5)
. 函数 transform
然后返回,它将转换 Query
第一个加入 Node.parent
,然后比较 parent_alias
使用 Operators.eq
向左右两侧传球 Query.filter
:
>>> from sqlalchemy.orm import Session
>>> session = Session()
sql>>> session.query(Node).\
... with_transformation(Node.grandparent==Node(id=5)).\
... all()
SELECT node.id AS node_id, node.parent_id AS node_parent_id
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
WHERE :param_1 = node_1.parent_id
我们可以通过将“join”步骤与“filter”步骤分离,将模式修改为更详细但更灵活。这里棘手的部分是确保 GrandparentTransformer
使用相同的 AliasedClass
反对对象 Node
. 下面我们使用一种简单的记忆方法,将 GrandparentTransformer
每节课:
class Node(Base):
# ...
@grandparent.comparator
def grandparent(cls):
# memoize a GrandparentTransformer
# per class
if '_gp' not in cls.__dict__:
cls._gp = GrandparentTransformer(cls)
return cls._gp
class GrandparentTransformer(Comparator):
def __init__(self, cls):
self.parent_alias = aliased(cls)
@property
def join(self):
def go(q):
return q.join(self.parent_alias, Node.parent)
return go
def operate(self, op, other):
return op(self.parent_alias.parent, other)
sql>>> session.query(Node).\
... with_transformation(Node.grandparent.join).\
... filter(Node.grandparent==Node(id=5))
SELECT node.id AS node_id, node.parent_id AS node_parent_id
FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
WHERE :param_1 = node_1.parent_id
“Transformer”模式是一种实验模式,它开始使用一些函数式编程模式。虽然它只推荐给高级和/或病患开发人员,但可能有很多神奇的东西可以使用。
sqlalchemy.ext.hybrid.
hybrid_method
(func, expr=None)¶基地: sqlalchemy.orm.base.InspectionAttrInfo
一个允许定义具有实例级和类级行为的Python对象方法的修饰器。
__init__
(func, expr=None)¶创建新的 hybrid_method
.
通常通过装饰器使用:
from sqlalchemy.ext.hybrid import hybrid_method
class SomeClass(object):
@hybrid_method
def value(self, x, y):
return self._value + x + y
@value.expression
def value(self, x, y):
return func.some_function(self._value, x, y)
expression
(expr)¶提供定义SQL表达式生成方法的修改修饰符。
sqlalchemy.ext.hybrid.
hybrid_property
(fget, fset=None, fdel=None, expr=None, custom_comparator=None, update_expr=None)¶基地: sqlalchemy.orm.base.InspectionAttrInfo
允许定义具有实例级和类级行为的python描述符的修饰器。
__init__
(fget, fset=None, fdel=None, expr=None, custom_comparator=None, update_expr=None)¶创建新的 hybrid_property
.
通常通过装饰器使用:
from sqlalchemy.ext.hybrid import hybrid_property
class SomeClass(object):
@hybrid_property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
comparator
(comparator)¶提供定义自定义比较器生成方法的修改修饰器。
修饰方法的返回值应该是 Comparator
.
注解
这个 hybrid_property.comparator()
装饰者 替换 使用的 hybrid_property.expression()
装饰者。它们不能一起使用。
当在类级别调用混合时, Comparator
这里给出的对象被包装在一个专用的 QueryableAttribute
,这是ORM用来表示其他映射属性的同一类对象。这样做的原因是,可以在返回的结构中维护其他类级属性(如docstrings和对混合体本身的引用),而不必修改传入的原始comparator对象。
注解
当从拥有类(例如 SomeClass.some_hybrid
)的实例 QueryableAttribute
返回,将表达式或比较器对象表示为此混合对象。但是,该对象本身具有调用的访问器 expression
和 comparator
;因此,当尝试在子类上重写这些修饰符时,可能需要使用 hybrid_property.overrides
先修改。有关详细信息,请参阅该修饰符。
deleter
(fdel)¶提供定义删除方法的修改修饰符。
expression
(expr)¶提供定义SQL表达式生成方法的修改修饰符。
当在类级别调用混合时,此处给出的SQL表达式包装在专用的 QueryableAttribute
,这是ORM用来表示其他映射属性的同一类对象。这样做的原因是,可以在返回的结构中维护其他类级属性(如docstrings和对混合体本身的引用),而不修改传入的原始SQL表达式。
注解
当从拥有类(例如 SomeClass.some_hybrid
)的实例 QueryableAttribute
返回,表示表达式或比较器对象以及此混合对象。但是,该对象本身具有调用的访问器 expression
和 comparator
;因此,当尝试在子类上重写这些修饰符时,可能需要使用 hybrid_property.overrides
先修改。有关详细信息,请参阅该修饰符。
getter
(fget)¶提供定义getter方法的修改修饰符。
1.2 新版功能.
overrides
¶覆盖现有属性的方法的前缀。
这个 hybrid_property.overrides
访问器只返回这个混合对象,当从父类的类级别调用这个混合对象时,它将取消引用通常在此级别返回的“instructed attribute”,并允许修改类似 hybrid_property.expression()
和 hybrid_property.comparator()
在不与通常存在于 QueryableAttribute
::
class SuperClass(object):
# ...
@hybrid_property
def foobar(self):
return self._foobar
class SubClass(SuperClass):
# ...
@SuperClass.foobar.overrides.expression
def foobar(cls):
return func.subfoobar(self._foobar)
1.2 新版功能.
参见
setter
(fset)¶提供定义setter方法的修改修饰符。
update_expression
(meth)¶提供定义更新元组生成方法的修改修饰符。
方法接受单个值,该值是要呈现到UPDATE语句的SET子句中的值。然后,该方法应将该值处理为适合于ultimate set子句的单个列表达式,并将其作为2元组序列返回。每个元组都包含一个列表达式作为键和一个要呈现的值。
例如。::
class Person(Base):
# ...
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def fullname(self):
return first_name + " " + last_name
@fullname.update_expression
def fullname(cls, value):
fname, lname = value.split(" ", 1)
return [
(cls.first_name, fname),
(cls.last_name, lname)
]
1.2 新版功能.
sqlalchemy.ext.hybrid.
Comparator
(expression)¶基地: sqlalchemy.orm.interfaces.PropComparator
允许轻松构造自定义 PropComparator
用于混血儿的类。
sqlalchemy.ext.hybrid.
HYBRID_METHOD
= symbol('HYBRID_METHOD')¶符号表示 InspectionAttr
那是类型的 hybrid_method
.
分配给 InspectionAttr.extension_type
属性。
参见
Mapper.all_orm_attributes
sqlalchemy.ext.hybrid.
HYBRID_PROPERTY
= symbol('HYBRID_PROPERTY')¶InspectionAttr
那是类型的 hybrid_method
.
分配给 InspectionAttr.extension_type
属性。
参见
Mapper.all_orm_attributes