associationproxy is used to create a read/write view of a target attribute across a relationship. 它本质上隐藏了两个端点之间的“中间”属性的用法,可用于从相关对象集合中挑选字段或减少使用关联对象模式的详细程度。创造性地应用,关联代理允许构建几乎任何几何图形的复杂集合和字典视图,并使用标准的,透明配置的关系模式持久化到数据库。
考虑两个类(User和Keyword)之间的多对多映射。每个User可以包含任意数量的Keyword对象,反之亦然(多对多模式在Many To Many中描述) :
from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
kw = relationship("Keyword", secondary=lambda: userkeywords_table)
def __init__(self, name):
self.name = name
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
userkeywords_table = Table('userkeywords', Base.metadata,
Column('user_id', Integer, ForeignKey("user.id"),
primary_key=True),
Column('keyword_id', Integer, ForeignKey("keyword.id"),
primary_key=True)
)读取和操作与User关联的“关键字”字符串的集合需要从每个集合元素遍历.keyword属性,这可能会很棘手:
>>> user = User('jek')
>>> user.kw.append(Keyword('cheese inspector'))
>>> print(user.kw)
[<__main__.Keyword object at 0x12bf830>]
>>> print(user.kw[0].keyword)
cheese inspector
>>> print([keyword.keyword for keyword in user.kw])
['cheese inspector']对User类应用association_proxy以产生kw关系的“视图”,该关系仅显示字符串值.keyword与每个Keyword对象关联:
from sqlalchemy.ext.associationproxy import association_proxy
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
kw = relationship("Keyword", secondary=lambda: userkeywords_table)
def __init__(self, name):
self.name = name
# proxy the 'keyword' attribute from the 'kw' relationship
keywords = association_proxy('kw', 'keyword')我们现在可以将.keywords集合引用为可读写的字符串列表。新的Keyword对象是透明地为我们创建的:
>>> user = User('jek')
>>> user.keywords.append('cheese inspector')
>>> user.keywords
['cheese inspector']
>>> user.keywords.append('snack ninja')
>>> user.kw
[<__main__.Keyword object at 0x12cdd30>, <__main__.Keyword object at 0x12cde30>]由association_proxy()函数产生的AssociationProxy对象是Python描述符的实例。无论使用通过mapper()函数的声明映射还是经典映射,都始终声明用户定义类正在映射。
代理通过对底层映射属性或集合进行操作来响应操作,并且通过代理进行的更改在映射属性中立即显现,反之亦然。底层属性保持完全可访问。
首次访问时,关联代理会对目标集合执行自检操作,以使其行为正确对应。例如,如果本地代理属性是集合(通常是)或标量引用,以及集合是否像集合,列表或字典一样起作用,则会考虑这些细节,以便代理应该像基础收集或属性确实。
当列表append()事件(或set add(),dictionary __setitem __()或标量赋值事件)被关联代理拦截时,它使用其构造函数实例化“中间”对象的新实例,作为单个论证给定的价值。在我们上面的例子中,一个操作如下:
user.keywords.append('cheese inspector')由协会代理翻译成操作:
user.kw.append(Keyword('cheese inspector'))这个例子在这里工作,因为我们设计了Keyword的构造函数来接受一个位置参数keyword。对于单参数构造函数不可行的情况,可以使用creator参数来定制关联代理的创建行为,该参数引用可调用的函数(即Python函数),该函数将产生一个新的对象实例给出单数论证。下面我们用典型的lambda来说明这一点:
class User(Base):
# ...
# use Keyword(keyword=kw) on append() events
keywords = association_proxy('kw', 'keyword',
creator=lambda kw: Keyword(keyword=kw))creator函数在基于列表或集合的集合或标量属性的情况下接受单个参数。在基于字典的集合中,它接受两个参数“key”和“value”。下面的例子是Proxying to Dictionary Based Collections。
“关联对象”模式是多对多关系的扩展形式,并在Association Object中进行了描述。关联代理对于在常规使用中保留“关联对象”非常有用。
假设我们上面的userkeywords表有额外的列,我们希望明确映射,但是在大多数情况下我们不需要直接访问这些属性。下面我们举例说明一个新的映射,它引入了映射到前面说明的userkeywords表的UserKeyword类。这个类增加了一个额外的列special_key,这是我们偶尔想要访问的值,但通常情况下不会。我们在User类中创建了一个名为keywords的关联代理,它将与User的user_keywords >到每个UserKeyword中存在的.keyword属性:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)通过上述配置,我们可以对每个User对象的.keywords集合进行操作,并且隐藏UserKeyword的用法:
>>> user = User('log')
>>> for kw in (Keyword('new_from_blammo'), Keyword('its_big')):
... user.keywords.append(kw)
...
>>> print(user.keywords)
[Keyword('new_from_blammo'), Keyword('its_big')]在上面,每个.keywords.append()操作相当于:
>>> user.user_keywords.append(UserKeyword(Keyword('its_heavy')))这个UserKeyword关联对象在这里有两个属性被填充;作为第一个参数传递Keyword对象的结果,直接填充.keyword属性。当UserKeyword对象附加到User.user_keywords集合时,.user参数被分配,其中User.user_keywords和UserKeyword.user会导致UserKeyword.user属性的填充。上面的special_key参数保留默认值None。
对于那些我们希望special_key具有值的情况,我们显式创建UserKeyword对象。下面我们分配所有三个属性,其中.user的赋值将UserKeyword的作用附加到User.user_keywords集合中:
>>> UserKeyword(Keyword('its_wood'), user, special_key='my special key')关联代理向我们返回由所有这些操作表示的Keyword对象的集合:
>>> user.keywords
[Keyword('new_from_blammo'), Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')]关联代理也可以代理基于字典的集合。SQLAlchemy映射通常使用attribute_mapped_collection()集合类型来创建字典集合,以及Custom Dictionary-Based Collections中描述的扩展技术。
关联代理在检测到基于字典的集合的使用时调整其行为。将新值添加到字典时,关联代理通过将两个参数传递给创建函数而不是一个参数(键和值)来实例化中间对象。与往常一样,此创建函数默认为中介类的构造函数,并可使用creator参数进行自定义。
下面,我们修改我们的UserKeyword示例,使得User.user_keywords集合现在将使用字典进行映射,其中UserKeyword.special_key参数将会被用作字典的关键字。然后,我们将creator参数应用于User.keywords代理,以便在将新元素添加到字典时适当地分配这些值:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.collections import attribute_mapped_collection
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# proxy to 'user_keywords', instantiating UserKeyword
# assigning the new key to 'special_key', values to
# 'keyword'.
keywords = association_proxy('user_keywords', 'keyword',
creator=lambda k, v:
UserKeyword(special_key=k, keyword=v)
)
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String)
# bidirectional user/user_keywords relationships, mapping
# user_keywords with a dictionary against "special_key" as key.
user = relationship(User, backref=backref(
"user_keywords",
collection_class=attribute_mapped_collection("special_key"),
cascade="all, delete-orphan"
)
)
keyword = relationship("Keyword")
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)我们将.keywords集合说明为字典,将UserKeyword.string_key值映射到Keyword对象:
>>> user = User('log')
>>> user.keywords['sk1'] = Keyword('kw1')
>>> user.keywords['sk2'] = Keyword('kw2')
>>> print(user.keywords)
{'sk1': Keyword('kw1'), 'sk2': Keyword('kw2')}考虑到我们之前从关系到标量属性,代理跨越关联对象以及代理字典的代理示例,我们可以将所有三种技术组合在一起以给出User a keywords字典严格处理映射到字符串keyword的special_key的字符串值。UserKeyword和Keyword类都完全隐藏。这是通过在User上建立一个关联代理实现的,该代理引用UserKeyword上存在的关联代理:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.collections import attribute_mapped_collection
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# the same 'user_keywords'->'keyword' proxy as in
# the basic dictionary example
keywords = association_proxy(
'user_keywords',
'keyword',
creator=lambda k, v:
UserKeyword(special_key=k, keyword=v)
)
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'),
primary_key=True)
special_key = Column(String)
user = relationship(User, backref=backref(
"user_keywords",
collection_class=attribute_mapped_collection("special_key"),
cascade="all, delete-orphan"
)
)
# the relationship to Keyword is now called
# 'kw'
kw = relationship("Keyword")
# 'keyword' is changed to be a proxy to the
# 'keyword' attribute of 'Keyword'
keyword = association_proxy('kw', 'keyword')
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keywordUser.keywords is now a dictionary of string to string, where UserKeyword and Keyword objects are created and removed for us transparently using the association proxy. 在下面的例子中,我们举例说明了赋值运算符的用法,也可以由关联代理进行适当处理,以便将字典值同时应用于集合:
>>> user = User('log')
>>> user.keywords = {
... 'sk1':'kw1',
... 'sk2':'kw2'
... }
>>> print(user.keywords)
{'sk1': 'kw1', 'sk2': 'kw2'}
>>> user.keywords['sk3'] = 'kw3'
>>> del user.keywords['sk2']
>>> print(user.keywords)
{'sk1': 'kw1', 'sk3': 'kw3'}
>>> # illustrate un-proxied usage
... print(user.user_keywords['sk3'].kw)
<__main__.Keyword object at 0x12ceb90>上面我们的示例的一个警告是,因为为每个字典集操作创建了Keyword对象,所以该示例未能保持其字符串名称上Keyword对象的唯一性,即这是一种标签场景的典型要求。对于这种用例,建议使用配方UniqueObject或类似的创建策略,这将对Keyword类的构造函数应用“首先查找,然后创建”策略,因此如果给定的名称已经存在,则返回已经存在的Keyword。
AssociationProxy具有简单的SQL构造功能,这些功能与使用中的基础relationship())以及目标属性相关。例如,RelationshipProperty.Comparator.any()和RelationshipProperty.Comparator.has()操作是可用的,并且会产生一个“嵌套的”EXISTS子句,如in我们的基本关联对象示例:
>>> print(session.query(User).filter(User.keywords.any(keyword='jek')))
SELECT user.id AS user_id, user.name AS user_name
FROM user
WHERE EXISTS (SELECT 1
FROM user_keyword
WHERE user.id = user_keyword.user_id AND (EXISTS (SELECT 1
FROM keyword
WHERE keyword.id = user_keyword.keyword_id AND keyword.keyword = :keyword_1)))对于标量属性的代理,支持__eq__():
>>> print(session.query(UserKeyword).filter(UserKeyword.keyword == 'jek'))
SELECT user_keyword.*
FROM user_keyword
WHERE EXISTS (SELECT 1
FROM keyword
WHERE keyword.id = user_keyword.keyword_id AND keyword.keyword = :keyword_1)和.contains()可用于标量集合的代理:
>>> print(session.query(User).filter(User.keywords.contains('jek')))
SELECT user.*
FROM user
WHERE EXISTS (SELECT 1
FROM userkeywords, keyword
WHERE user.id = userkeywords.user_id
AND keyword.id = userkeywords.keyword_id
AND keyword.keyword = :keyword_1)AssociationProxy can be used with Query.join() somewhat manually using the attr attribute in a star-args context:
q = session.query(User).join(*User.keywords.attr)版本0.7.3中的新增内容: attr属性在star-args上下文中。
attr由AssociationProxy.local_attr和AssociationProxy.remote_attr组成,它们只是实际代理属性的同义词,也可用于查询:
uka = aliased(UserKeyword)
ka = aliased(Keyword)
q = session.query(User).\
join(uka, User.keywords.local_attr).\
join(ka, User.keywords.remote_attr)New in version 0.7.3: AssociationProxy.local_attr and AssociationProxy.remote_attr, synonyms for the actual proxied attributes, and usable for querying.
sqlalchemy.ext.associationproxy.association_proxy(target_collection, attr, **kw)¶返回实现目标属性视图的Python属性,该属性引用目标成员上的属性。
返回的值是AssociationProxy的一个实例。
将一个表示关系的Python属性实现为一组简单值或一个标量值。被代理的属性将模仿目标(列表,字典或集合)的集合类型,或者在一对一关系的情况下,它是一个简单的标量值。
| 参数: |
|
|---|
sqlalchemy.ext.associationproxy.AssociationProxy(target_collection, attr, creator=None, getset_factory=None, proxy_factory=None, proxy_bulk_set=None, info=None)¶基础:sqlalchemy.orm.base.InspectionAttrInfo
呈现对象属性的读/写视图的描述符。
__init__(target_collection, attr, creator=None, getset_factory=None, proxy_factory=None, proxy_bulk_set=None, info=None)¶构建一个新的AssociationProxy。
association_proxy()函数在这里作为通常的入口点提供,尽管AssociationProxy可以直接实例化和/或子类化。
| 参数: |
|
|---|
任何 ( criterion = None,** kwargs ) / T5>使用EXISTS生成代理的“any”表达式。
该表达式将是一个使用基础代理属性的RelationshipProperty.Comparator.any()和/或RelationshipProperty.Comparator.has()运算符的组合产品。
ATTR T0> ¶ T1>返回(local_attr, remote_attr)的元组。
在跨两个关系使用Query.join()指定连接时,该属性很方便:
sess.query(Parent).join(*Parent.proxied.attr)New in version 0.7.3.
也可以看看:
包含 T0> ( T1> OBJ T2> ) T3> ¶ T4>使用EXISTS生成代理的“包含”表达式。
这个表达式将是一个使用RelationshipProperty.Comparator.any(),RelationshipProperty.Comparator.has()和/或RelationshipProperty.Comparator.contains()运算符的底层代理属性。
extension_type =符号('ASSOCIATION_PROXY') ¶有 ( criterion = None,** kwargs ) / T5>使用EXISTS生成代理“有”表达式。
该表达式将是一个使用基础代理属性的RelationshipProperty.Comparator.any()和/或RelationshipProperty.Comparator.has()运算符的组合产品。
信息 T0> ¶ T1>info attribute of InspectionAttrInfo信息字典与对象关联,允许用户定义的数据与这个InspectionAttr关联。
字典在第一次访问时生成。Alternatively, it can be specified as a constructor argument to the column_property(), relationship(), or composite() functions.
0.8版新增功能:增加了对所有MapperProperty子类的.info支持。
版本1.0.0更改: MapperProperty.info也可以通过InspectionAttrInfo.info属性在扩展类型上使用,以便它可以应用于更广泛的ORM和扩展结构。
is_aliased_class = False ¶is_attribute = False ¶is_clause_element = False ¶is_instance = False ¶is_mapper = False ¶is_property = False ¶is_selectable = False ¶ local_attr T0> ¶ T1>由AssociationProxy引用的'local'MapperProperty。
New in version 0.7.3.
也可以看看:
remote_attr T0> ¶ T1>这个AssociationProxy引用的'remote'MapperProperty。
New in version 0.7.3.
也可以看看:
标量 T0> ¶ T1>如果AssociationProxy代表本地方的标量关系,则返回True。
target_class T0> ¶ T1>由AssociationProxy处理的中介类。
截获的append / set / assignment事件将导致产生这个类的新实例。
sqlalchemy.ext.associationproxy。 ASSOCIATION_PROXY =符号('ASSOCIATION_PROXY') ¶