版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

关联代理

associationproxy is used to create a read/write view of a target attribute across a relationship. 它基本上隐藏了两个端点之间的“中间”属性的用法,可用于从相关对象的集合中挑选字段或减少使用关联对象模式的详细程度。创造性地应用,关联代理允许构建几乎任何几何的复杂集合和字典视图,使用标准的,透明配置的关系模式持久化到数据库。

简化标量集合

考虑两个类(UserKeyword)之间的多对多映射。每个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中描述。关联代理对于在常规使用中保留“关联对象”非常有用。

Suppose our userkeywords table above had additional columns which we’d like to map explicitly, but in most cases we don’t require direct access to these attributes. 下面我们举例说明一个新的映射,该映射引入了映射到前面说明的userkeywords表的UserKeyword类。这个类增加了一个额外的列special_key,这是我们偶尔想要访问的一个值,但不是在通常的情况下。我们在User类中创建了一个名为keywords的关联代理,它将与Useruser_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')))

The UserKeyword association object has two attributes here which are populated; the .keyword attribute is populated directly as a result of passing the Keyword object as the first argument. 随后将UserKeyword对象附加到User.user_keywords集合,其中.user参数被分配,其中User.user_keywordsUserKeyword.user会导致UserKeyword.user属性的填充。上面的special_key参数的默认值是None

For those cases where we do want special_key to have a value, we create the UserKeyword object explicitly. 下面我们分配所有这三个属性,其中.user的作用被添加到User.user_keywords集合中:UserKeyword

>>> 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')}

复合关联代理

Given our previous examples of proxying from relationship to scalar attribute, proxying across an association object, and proxying dictionaries, we can combine all three techniques together to give User a keywords dictionary that deals strictly with the string value of special_key mapped to the string keyword. UserKeywordKeyword类都完全隐藏。这是通过在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 = keyword

User.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

查询关联代理

The AssociationProxy features simple SQL construction capabilities which relate down to the underlying relationship() in use as well as the target attribute. 例如,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上下文中。

attrAssociationProxy.local_attrAssociationProxy.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.

API文档

sqlalchemy.ext.associationproxy.association_proxy(target_collection, attr, **kw)

返回一个Python属性,实现引用目标成员属性的目标属性视图。

返回的值是AssociationProxy的一个实例。

将一个表示关系的Python属性实现为一个简单值或标量值的集合。被代理的属性将模仿目标(列表,字典或集合)的集合类型,或者在一对一关系的情况下,是一个简单的标量值。

参数:
  • target_collection - 我们将代理的属性的名称。该属性通常由relationship()映​​射以链接到目标集合,但也可以是多对一或非标量关系。
  • attr -

    关联的一个或多个实例上的属性,我们将代理它们。

    例如,给定[obj1,obj2]的目标集合,由此代理属性创建的列表看起来像[getattr(obj1,attr),getattr(obj2,attr t1 >)]

    如果关系是一对一的,或者使用list = False,那么简单地说:getattr(obj,attr

  • 创作者 -

    可选的。

    将新项目添加到此代理集合时,将创建由目标集合收集的类的新实例。对于列表和集合集合,目标类的构造函数将被调用新的实例的“值”。对于字典类型,传递了两个参数:键和值。

    如果要以不同的方式构造实例,请提供一个creator函数,该函数接受上面的参数并返回实例。

    对于标量关系,如果目标是None,creator()将被调用。如果目标存在,set操作被代理到关联对象上的setattr()。

    如果您有一个关联对象具有多个属性,则可以设置多个关联代理映射到不同的属性。请参阅单元测试的示例,以及有关如何使用creator()函数在此情况下按需构建标量关系的示例。

  • **kw – Passes along any other keyword arguments to AssociationProxy.
class 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

The association_proxy() function is provided as the usual entrypoint here, though AssociationProxy can be instantiated and/or subclassed directly.

参数:
  • target_collection – Name of the collection we’ll proxy to, usually created with relationship().
  • attr – Attribute on the collected instances we’ll proxy for. 例如,给定[obj1,obj2]的目标集合,由此代理属性创建的列表将看起来像[getattr(obj1,attr),getattr(obj2,attr)]
  • 创作者 -

    可选的。将新项目添加到此代理集合时,将创建由目标集合收集的类的新实例。对于列表和集合集合,目标类的构造函数将被调用新的实例的“值”。对于字典类型,传递了两个参数:键和值。

    如果你想以不同的方式构造实例,提供一个“creator”函数,该函数接受上面的参数并返回实例。

  • getset_factory -

    可选的。Proxied属性访问是由例程自动处理的,该例程根据此代理的attr参数获取和设置值。

    如果你想自定义这种行为,你可以提供一个getset_factory可调用,它产生一个gettersetter函数的元组。工厂被调用两个参数,底层集合的抽象类型和这个代理实例。

  • proxy_factory - 可选。要仿真的集合的类型是通过嗅探目标集合来确定的。如果您的集合类型不能通过鸭子打字确定,或者您想使用不同的集合实现,则可以提供工厂函数来生成这些集合。只适用于非标量关系。
  • proxy_bulk_set - 可选,与proxy_factory一起使用。有关详细信息,请参阅_set()方法。
  • info -

    如果存在,将被分配给AssociationProxy.info

    版本1.0.9中的新功能

任何 criterion = None** kwargs / T5>

使用EXISTS生成代理的“任何”表达式。

这个表达式将是一个使用底层代理属性的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.

也可以看看:

AssociationProxy.local_attr

AssociationProxy.remote_attr

包含 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>
inherited from the info attribute of InspectionAttrInfo

与对象关联的信息字典,允许用户定义的数据与这个InspectionAttr关联。

字典在第一次访问时生成。或者,它可以被指定为column_property()relationship()composite()函数的构造函数参数。

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引用的“本地”MapperProperty

New in version 0.7.3.

也可以看看:

AssociationProxy.attr

AssociationProxy.remote_attr

remote_attr T0> ¶ T1>

这个AssociationProxy引用的'remote'MapperProperty

New in version 0.7.3.

也可以看看:

AssociationProxy.attr

AssociationProxy.local_attr

标量 T0> ¶ T1>

如果AssociationProxy代表本地方的标量关系,则返回True

target_class T0> ¶ T1>

AssociationProxy处理的中介类。

截获的append / set / assignment事件将导致这个类的新实例的生成。

sqlalchemy.ext.associationproxy。 ASSOCIATION_PROXY =符号('ASSOCIATION_PROXY')