.. module:: sqlalchemy.ext.associationproxy
-``associationproxy`` is used to create a simplified, read/write view of a
-relationship. It can be used to cherry-pick fields from a collection of
-related objects or to greatly simplify access to associated objects in an
-association relationship.
+``associationproxy`` is used to create a read/write view of a
+target attribute across a relationship. It essentially conceals
+the usage of a "middle" attribute between two endpoints, and
+can be used to cherry-pick fields from a collection of
+related objects or to reduce the verbosity of using the association
+object pattern. Applied creatively, the association proxy allows
+the construction of sophisticated collections and dictionary
+views of virtually any geometry, persisted to the database using
+standard, transparently configured relational patterns.
+
+Simplifying Scalar Collections
+------------------------------
+
+Consider a many-to-many mapping between two classes, ``User`` and ``Keyword``.
+Each ``User`` can have any number of ``Keyword`` objects, and vice-versa
+(the many-to-many pattern is described at :ref:`relationships_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)
-Simplifying Relationships
--------------------------
-
-Consider this "association object" mapping::
+ def __init__(self, name):
+ self.name = name
- users_table = Table('users', metadata,
- Column('id', Integer, primary_key=True),
- Column('name', String(64)),
- )
+ class Keyword(Base):
+ __tablename__ = 'keyword'
+ id = Column(Integer, primary_key=True)
+ keyword = Column('keyword', String(64))
- keywords_table = Table('keywords', metadata,
- Column('id', Integer, primary_key=True),
- Column('keyword', String(64))
- )
+ def __init__(self, keyword):
+ self.keyword = keyword
- userkeywords_table = Table('userkeywords', metadata,
- Column('user_id', Integer, ForeignKey("users.id"),
+ userkeywords_table = Table('userkeywords', Base.metadata,
+ Column('user_id', Integer, ForeignKey("user.id"),
primary_key=True),
- Column('keyword_id', Integer, ForeignKey("keywords.id"),
+ Column('keyword_id', Integer, ForeignKey("keyword.id"),
primary_key=True)
)
- class User(object):
- def __init__(self, name):
- self.name = name
-
- class Keyword(object):
- def __init__(self, keyword):
- self.keyword = keyword
-
- mapper(User, users_table, properties={
- 'kw': relationship(Keyword, secondary=userkeywords_table)
- })
- mapper(Keyword, keywords_table)
-
-Above are three simple tables, modeling users, keywords and a many-to-many
-relationship between the two. These ``Keyword`` objects are little more
-than a container for a name, and accessing them via the relationship is
-awkward::
-
- user = User('jek')
- user.kw.append(Keyword('cheese inspector'))
- print user.kw
- # [<__main__.Keyword object at 0xb791ea0c>]
- print user.kw[0].keyword
- # 'cheese inspector'
- print [keyword.keyword for keyword in user.kw]
- # ['cheese inspector']
+We can append ``Keyword`` objects to a target ``User``, and access the
+``.keyword`` attribute of each ``Keyword`` in order to see the value, but
+the extra hop introduced by ``.kw`` can be awkward::
+
+ >>> 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']
With ``association_proxy`` you have a "view" of the relationship that contains
-just the ``.keyword`` of the related objects. The proxy is a Python
-property, and unlike the mapper relationship, is defined in your class::
+just the ``.keyword`` of the related objects. Below we illustrate
+how to bridge the gap between the ``kw`` collection and the ``keyword``
+attribute of each ``Keyword``::
from sqlalchemy.ext.associationproxy import association_proxy
- class User(object):
+ 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')
- # ...
- >>> user.kw
- [<__main__.Keyword object at 0xb791ea0c>]
+We can now reference the ``.keywords`` collection as a listing of strings,
+which is both readable and writeable::
+
+ >>> user = User('jek')
+ >>> user.keywords.append('cheese inspector')
>>> user.keywords
['cheese inspector']
>>> user.keywords.append('snack ninja')
- >>> user.keywords
- ['cheese inspector', 'snack ninja']
>>> user.kw
- [<__main__.Keyword object at 0x9272a4c>, <__main__.Keyword object at 0xb7b396ec>]
+ [<__main__.Keyword object at 0x12cdd30>, <__main__.Keyword object at 0x12cde30>]
+
+The association proxy is nothing more than a Python `descriptor <http://docs.python.org/howto/descriptor.html>`_,
+as opposed to a :class:`.MapperProperty`-based construct such as :func:`.relationship` or :func:`.column_property`.
+It is always defined in a declarative fashion along with its parent class, regardless of
+whether or not Declarative or classical mappings are used.
The proxy is read/write. New associated objects are created on demand when
values are added to the proxy, and modifying or removing an entry through
- To construct new instances, the type is called with the value being
assigned, or key and value for dicts.
- - A ````creator```` function can be used to create instances instead.
+ - A "creator" function can be used to create instances instead.
Above, the ``Keyword.__init__`` takes a single argument ``keyword``, which
maps conveniently to the value being set through the proxy. A ``creator``
-function could have been used instead if more flexibility was required.
+function can be used if more flexibility is required.
Because the proxies are backed by a regular relationship collection, all of the
usual hooks and patterns for using collections are still in effect. The
most convenient behavior is the automatic setting of "parent"-type
relationships on assignment. In the example above, nothing special had to
-be done to associate the Keyword to the User. Simply adding it to the
+be done to associate the ``Keyword`` to the ``User``. Simply adding it to the
collection is sufficient.
-Simplifying Association Object Relationships
---------------------------------------------
+Simplifying Association Proxies
+-------------------------------
-Association proxies are also useful for keeping ``association objects`` out
-the way during regular use. For example, the ``userkeywords`` table
-might have a bunch of auditing columns that need to get updated when changes
-are made- columns that are updated but seldom, if ever, accessed in your
-application. A proxy can provide a very natural access pattern for the
-relationship.
+Association proxies are useful for keeping "association objects" out
+the way during regular use. The "association object" pattern is an
+extended form of a many-to-many relationship, and is described at
+:ref:`association_pattern`.
-.. sourcecode:: python
+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. Below, we illustrate
+a new mapping which introduces the ``UserKeyword`` class, which
+is mapped to the ``userkeywords`` table illustrated earlier.
+This class adds an additional column ``special_key``. We
+create an association proxy on the ``User`` class called
+``keywords``, which will bridge the gap from the ``user_keywords``
+collection to the ``Keyword`` object referenced by each
+``UserKeyword``::
- from sqlalchemy.ext.associationproxy import association_proxy
-
- # users_table and keywords_table tables as above, then:
+ from sqlalchemy import Column, Integer, String, ForeignKey
+ from sqlalchemy.orm import relationship, backref
- def get_current_uid():
- """Return the uid of the current user."""
- return 1 # hardcoded for this example
+ from sqlalchemy.ext.associationproxy import association_proxy
+ from sqlalchemy.ext.declarative import declarative_base
- userkeywords_table = Table('userkeywords', metadata,
- Column('user_id', Integer, ForeignKey("users.id"), primary_key=True),
- Column('keyword_id', Integer, ForeignKey("keywords.id"), primary_key=True),
- # add some auditing columns
- Column('updated_at', DateTime, default=datetime.now),
- Column('updated_by', Integer, default=get_current_uid, onupdate=get_current_uid),
- )
+ Base = declarative_base()
- def _create_uk_by_keyword(keyword):
- """A creator function."""
- return UserKeyword(keyword=keyword)
+ class User(Base):
+ __tablename__ = 'user'
+ id = Column(Integer, primary_key=True)
+ name = Column(String(64))
+ keywords = association_proxy('user_keywords', 'keyword')
- class User(object):
def __init__(self, name):
self.name = name
- keywords = association_proxy('user_keywords', 'keyword', creator=_create_uk_by_keyword)
- class Keyword(object):
+ 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)
- class UserKeyword(object):
- def __init__(self, user=None, keyword=None):
+ 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))
+ user = relationship(User, backref=backref("user_keywords", cascade="all, delete-orphan"))
+ keyword = relationship(Keyword)
+
+ def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
+ self.special_key = special_key
+
+With the above mapping, we first illustrate usage of the ``UserKeyword`` class
+explicitly, creating a ``User``, ``Keyword``, and the association::
+
+ >>> user = User('log')
+ >>> kw1 = Keyword('new_from_blammo')
+
+ >>> # Creating a UserKeyword association object will add a Keyword.
+ ... # the "user" reference assignment in the UserKeyword() constructor
+ ... # populates "user_keywords" via backref.
+ ... UserKeyword(kw1, user)
+ <__main__.UserKeyword object at 0x12d9a50>
+
+ >>> # Accessing Keywords requires traversing UserKeywords
+ ... print user.user_keywords[0]
+ <__main__.UserKeyword object at 0x12d9a50>
+
+ >>> print user.user_keywords[0].keyword
+ Keyword('new_from_blammo')
+
+Next we illustrate using the association proxy, which is accessible via
+the ``keywords`` attribute on each ``User`` object. Using the proxy,
+the ``UserKeyword`` object is created for us automatically, passing in
+the given ``Keyword`` object as the first positional argument by default::
+
+ >>> for kw in (Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')):
+ ... user.keywords.append(kw)
+ ...
+ >>> print user.keywords
+ [Keyword('new_from_blammo'), Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')]
+
+In each call to ``keywords.append()``, the association proxy performs the
+operation as::
+
+ user.user_keywords.append(UserKeyword(kw))
+
+As each ``UserKeyword`` is added to the ``.user_keywords`` collection, the bidirectional
+relationship established between ``User.user_keywords`` and ``UserKeyword.user`` establishes
+the parent ``User`` as the current value of ``UserKeyword.user``, and the new ``UserKeyword``
+object is fully populated.
+
+Using the creator argument
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The above example featured usage of the default "creation" function for the association proxy,
+which is to call the constructor of the class mapped by the first attribute, in this case
+that of ``UserKeyword``. It is often necessary to supply a custom construction function
+specific to the context in which the association proxy is used. For example, if
+we wanted the ``special_key`` argument to be populated specifically when the
+association proxy collection were used. The ``creator`` argument is given a single-argument
+function to achieve this, often using a lambda for succinctness::
+
+ class User(Base):
+ # ...
+
+ keywords = association_proxy('user_keywords', 'keyword',
+ creator=lambda k:UserKeyword(keyword=k, special_key='special'))
+
+When the proxied collection is based on a Python mapping (e.g. a ``dict``-like object),
+the ``creator`` argument accepts a **two** argument callable, passing in the key and value
+used in the collection population. Below we map our ``UserKeyword`` association object
+to the ``User`` object using a dictionary interface, where the ``special_key`` attribute
+of ``UserKeyword`` is used as the key in this dictionary, and the ``UserKeyword`` as
+the value. The association proxy with ``creator`` can give us a dictionary of ``special_key``
+linked to ``Keyword`` objects::
+
+ 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
- mapper(User, users_table)
- mapper(Keyword, keywords_table)
- mapper(UserKeyword, userkeywords_table, properties={
- 'user': relationship(User, backref='user_keywords'),
- 'keyword': relationship(Keyword),
- })
-
- user = User('log')
- kw1 = Keyword('new_from_blammo')
-
- # Creating a UserKeyword association object will add a Keyword.
- # the "user" reference assignment in the UserKeyword() constructor
- # populates "user_keywords" via backref.
- UserKeyword(user, kw1)
+ Base = declarative_base()
- # Accessing Keywords requires traversing UserKeywords
- print user.user_keywords[0]
- # <__main__.UserKeyword object at 0xb79bbbec>
+ class User(Base):
+ __tablename__ = 'user'
+ id = Column(Integer, primary_key=True)
+ name = Column(String(64))
+ keywords = association_proxy('user_keywords', 'keyword',
+ creator=lambda k, v:UserKeyword(special_key=k, keyword=v)
+ )
- print user.user_keywords[0].keyword
- # Keyword('new_from_blammo')
+ def __init__(self, name):
+ self.name = name
- # Lots of work.
+ class Keyword(Base):
+ __tablename__ = 'keyword'
+ id = Column(Integer, primary_key=True)
+ keyword = Column('keyword', String(64))
- # It's much easier to go through the association proxy!
- for kw in (Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')):
- user.keywords.append(kw)
+ def __init__(self, keyword):
+ self.keyword = keyword
- print user.keywords
- # [Keyword('new_from_blammo'), Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')]
+ def __repr__(self):
+ return 'Keyword(%s)' % repr(self.keyword)
+ 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"
+ )
+ )
+ keyword = relationship(Keyword)
+
+ def __init__(self, keyword=None, user=None, special_key=None):
+ self.user = user
+ self.keyword = keyword
+ self.special_key = special_key
-Building Complex Views
-----------------------
+The ``.keywords`` collection is now a dictionary of string keys to ``Keyword``
+objects::
-.. sourcecode:: python
+ >>> user = User('log')
+ >>> kw1 = Keyword('new_from_blammo')
- stocks_table = Table("stocks", meta,
- Column('symbol', String(10), primary_key=True),
- Column('last_price', Numeric)
- )
+ >>> user.keywords['sk1'] = Keyword('kw1')
+ >>> user.keywords['sk2'] = Keyword('kw2')
- brokers_table = Table("brokers", meta,
- Column('id', Integer,primary_key=True),
- Column('name', String(100), nullable=False)
- )
+ >>> print user.keywords
+ {'sk1': Keyword('kw1'), 'sk2': Keyword('kw2')}
- holdings_table = Table("holdings", meta,
- Column('broker_id', Integer, ForeignKey('brokers.id'), primary_key=True),
- Column('symbol', String(10), ForeignKey('stocks.symbol'), primary_key=True),
- Column('shares', Integer)
- )
+Building Complex Views
+----------------------
-Above are three tables, modeling stocks, their brokers and the number of
-shares of a stock held by each broker. This situation is quite different
-from the association example above. ``shares`` is a *property of the
-relationship*, an important one that we need to use all the time.
+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``. Both the ``UserKeyword``
+and ``Keyword`` classes are entirely concealed. This is achieved by building
+an association proxy on ``User`` that refers to an association proxy
+present on ``UserKeyword``::
-For this example, it would be very convenient if ``Broker`` objects had a
-dictionary collection that mapped ``Stock`` instances to the shares held for
-each. That's easy::
+ 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
- def _create_holding(stock, shares):
- """A creator function, constructs Holdings from Stock and share quantity."""
- return Holding(stock=stock, shares=shares)
+ Base = declarative_base()
+
+ class User(Base):
+ __tablename__ = 'user'
+ id = Column(Integer, primary_key=True)
+ name = Column(String(64))
+ keywords = association_proxy(
+ 'user_keywords',
+ 'keyword',
+ creator=lambda k, v:UserKeyword(special_key=k, keyword=v)
+ )
- class Broker(object):
def __init__(self, name):
self.name = name
- holdings = association_proxy('by_stock', 'shares', creator=_create_holding)
-
- class Stock(object):
- def __init__(self, symbol):
- self.symbol = symbol
- self.last_price = 0
-
- class Holding(object):
- def __init__(self, broker=None, stock=None, shares=0):
- self.broker = broker
- self.stock = stock
- self.shares = shares
-
- mapper(Stock, stocks_table)
- mapper(Broker, brokers_table, properties={
- 'by_stock': relationship(Holding,
- collection_class=attribute_mapped_collection('stock'))
- })
- mapper(Holding, holdings_table, properties={
- 'stock': relationship(Stock),
- 'broker': relationship(Broker)
- })
-
-Above, we've set up the ``by_stock`` relationship collection to act as a
-dictionary, using the ``.stock`` property of each Holding as a key.
-
-Populating and accessing that dictionary manually is slightly inconvenient
-because of the complexity of the Holdings association object::
-
- stock = Stock('ZZK')
- broker = Broker('paj')
-
- broker.by_stock[stock] = Holding(broker, stock, 10)
- print broker.by_stock[stock].shares
- # 10
+ class Keyword(Base):
+ __tablename__ = 'keyword'
+ id = Column(Integer, primary_key=True)
+ keyword = Column('keyword', String(64))
-The ``holdings`` proxy we've added to the ``Broker`` class hides the details
-of the ``Holding`` while also giving access to ``.shares``::
-
- for stock in (Stock('JEK'), Stock('STPZ')):
- broker.holdings[stock] = 123
-
- for stock, shares in broker.holdings.items():
- print stock, shares
-
- session.add(broker)
- session.commit()
-
- # lets take a peek at that holdings_table after committing changes to the db
- print list(holdings_table.select().execute())
- # [(1, 'ZZK', 10), (1, 'JEK', 123), (1, 'STEPZ', 123)]
-
-Further examples can be found in the ``examples/`` directory in the
-SQLAlchemy distribution.
+ def __init__(self, keyword):
+ self.keyword = keyword
-API
----
+ 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"
+ )
+ )
+ kw = relationship(Keyword)
+ keyword = association_proxy('kw', 'keyword')
+
+``.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, persisted and loaded using the ORM::
+
+ >>> 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'}
+
+ >>> print user.user_keywords['sk3'].kw
+ <__main__.Keyword object at 0x12ceb90>
+
+Querying with Association Proxies
+---------------------------------
+
+The :class:`.AssociationProxy` features simple SQL construction capabilities
+which relate down to the underlying :func:`.relationship` in use as well
+as the target attribute. For example, the :meth:`.PropComparator.any`
+and :meth:`.PropComparator.has` operations are available for an association
+proxy that is specifically proxying two relationships, and will produce
+a "nested" EXISTS clause, such as in our basic association object example::
+
+ >>> 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)))
+
+For a proxy to a scalar attribute, ``__eq__()`` is supported::
+
+ >>> 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)
+
+and :meth:`.PropComparator.contains` for proxy to scalar collection::
+
+ >>> 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)
+
+:class:`.AssociationProxy` can be used with :meth:`.Query.join` somewhat manually
+using the :attr:`~.AssociationProxy.attr` attribute in a star-args context (new in 0.7.3)::
+
+ q = session.query(User).join(*User.keywords)
+
+:attr:`~.AssociationProxy.attr` is composed of :attr:`.AssociationProxy.local_attr` and :attr:`.AssociationProxy.remote_attr`,
+which are just synonyms for the actual proxied attributes, and can also
+be used for querying (also new in 0.7.3)::
+
+ uka = aliased(UserKeyword)
+ ka = aliased(Keyword)
+ q = session.query(User).\
+ join(uka, User.keywords.local_attr).\
+ join(ka, User.keywords.remote_attr)
+
+API Documentation
+-----------------
.. autofunction:: association_proxy
from sqlalchemy import exceptions
from sqlalchemy import orm
from sqlalchemy import util
-from sqlalchemy.orm import collections
+from sqlalchemy.orm import collections, ColumnProperty
from sqlalchemy.sql import not_
Unlike the list comprehension, the collection returned by the property is
always in sync with *target_collection*, and mutations made to either
collection will be reflected in both.
-
+
The association proxy also works with scalar attributes, which in
turn reference scalar attributes or collections.
the target (list, dict or set), or, in the case of a one to one relationship,
a simple scalar value.
- :param target_collection: Name of the relationship attribute we'll proxy to,
- usually created with :func:`~sqlalchemy.orm.relationship`.
+ :param target_collection: Name of the attribute we'll proxy to.
+ This attribute is typically mapped by
+ :func:`~sqlalchemy.orm.relationship` to link to a target collection, but
+ can also be a many-to-one or non-scalar relationship.
:param attr: Attribute on the associated instance or instances we'll proxy for.
def __init__(self, target_collection, attr, creator=None,
getset_factory=None, proxy_factory=None,
proxy_bulk_set=None):
- """Arguments are:
+ """Construct a new :class:`.AssociationProxy`.
- target_collection
- Name of the collection we'll proxy to, usually created with
- 'relationship()' in a mapper setup.
+ :param target_collection: Name of the collection we'll proxy to,
+ usually created with 'relationship()' in a mapper setup.
- attr
- Attribute on the collected instances we'll proxy for. For example,
+ :param attr: Attribute on the collected instances we'll proxy for. For example,
given a target collection of [obj1, obj2], a list created by this
proxy property would look like [getattr(obj1, attr), getattr(obj2,
attr)]
- creator
- Optional. When new items are added to this proxied collection, new
+ :param creator: Optional. When new items are added to this proxied collection, new
instances of the class collected by the target collection will be
created. For list and set collections, the target class constructor
will be called with the 'value' for the new instance. For dict
If you want to construct instances differently, supply a 'creator'
function that takes arguments as above and returns instances.
- getset_factory
- Optional. Proxied attribute access is automatically handled by
+ :param getset_factory: Optional. Proxied attribute access is automatically handled by
routines that get and set values based on the `attr` argument for
this proxy.
`setter` functions. The factory is called with two arguments, the
abstract type of the underlying collection and this proxy instance.
- proxy_factory
- Optional. The type of collection to emulate is determined by
+ :param proxy_factory: Optional. The type of collection to emulate is determined by
sniffing the target collection. If your collection type can't be
determined by duck typing or you'd like to use a different
collection implementation, you may supply a factory function to
produce those collections. Only applicable to non-scalar relationships.
- proxy_bulk_set
- Optional, use with proxy_factory. See the _set() method for
- details.
+ :param proxy_bulk_set: Optional, use with proxy_factory. See
+ the _set() method for details.
"""
self.target_collection = target_collection
type(self).__name__, target_collection, id(self))
self.collection_class = None
+ @property
+ def remote_attr(self):
+ """The 'remote' :class:`.MapperProperty` referenced by this
+ :class:`.AssociationProxy`.
+
+ New in 0.7.3.
+
+ See also:
+
+ :attr:`.AssociationProxy.attr`
+
+ :attr:`.AssociationProxy.local_attr`
+
+ """
+ return getattr(self.target_class, self.value_attr)
+
+ @property
+ def local_attr(self):
+ """The 'local' :class:`.MapperProperty` referenced by this
+ :class:`.AssociationProxy`.
+
+ New in 0.7.3.
+
+ See also:
+
+ :attr:`.AssociationProxy.attr`
+
+ :attr:`.AssociationProxy.remote_attr`
+
+ """
+ return getattr(self.owning_class, self.target_collection)
+
+ @property
+ def attr(self):
+ """Return a tuple of ``(local_attr, remote_attr)``.
+
+ This attribute is convenient when specifying a join
+ using :meth:`.Query.join` across two relationships::
+
+ sess.query(Parent).join(*Parent.proxied.attr)
+
+ New in 0.7.3.
+
+ See also:
+
+ :attr:`.AssociationProxy.local_attr`
+
+ :attr:`.AssociationProxy.remote_attr`
+
+ """
+ return (self.local_attr, self.remote_attr)
+
def _get_property(self):
return (orm.class_mapper(self.owning_class).
get_property(self.target_collection))
@util.memoized_property
def scalar(self):
+ """Return true if this :class:`.AssociationProxy` proxies a scalar
+ relationship on the local side."""
+
scalar = not self._get_property().uselist
if scalar:
self._initialize_scalar_accessors()
return self._get_property().comparator
def any(self, criterion=None, **kwargs):
+ """Produce a proxied 'any' expression using EXISTS."""
+
if self._value_is_scalar:
value_expr = getattr(self.target_class, self.value_attr).has(criterion, **kwargs)
else:
)
def has(self, criterion=None, **kwargs):
+ """Produce a proxied 'has' expression using EXISTS."""
+
return self._comparator.has(
getattr(self.target_class, self.value_attr).has(criterion, **kwargs)
)
def contains(self, obj):
+ """Produce a proxied 'contains' expression using EXISTS."""
+
if self.scalar and not self._value_is_scalar:
return self._comparator.has(
getattr(self.target_class, self.value_attr).contains(obj)
This corresponds to a parent-child or associative table relationship. The
constructed class is an instance of :class:`.RelationshipProperty`.
- A typical :func:`relationship`::
+ A typical :func:`.relationship`, used in a classical mapping::
mapper(Parent, properties={
- 'children': relationship(Children)
+ 'children': relationship(Child)
})
+ Some arguments accepted by :func:`.relationship` optionally accept a
+ callable function, which when called produces the desired value.
+ The callable is invoked by the parent :class:`.Mapper` at "mapper initialization"
+ time, which happens only when mappers are first used, and is assumed
+ to be after all mappings have been constructed. This can be used
+ to resolve order-of-declaration and other dependency issues, such as
+ if ``Child`` is declared below ``Parent`` in the same file::
+
+ mapper(Parent, properties={
+ "children":relationship(lambda: Child,
+ order_by=lambda: Child.id)
+ })
+
+ When using the :ref:`declarative_toplevel` extension, the Declarative
+ initializer allows string arguments to be passed to :func:`.relationship`.
+ These string arguments are converted into callables that evaluate
+ the string as Python code, using the Declarative
+ class-registry as a namespace. This allows the lookup of related
+ classes to be automatic via their string name, and removes the need to import
+ related classes at all into the local module space::
+
+ from sqlalchemy.ext.declarative import declarative_base
+
+ Base = declarative_base()
+
+ class Parent(Base):
+ __tablename__ = 'parent'
+ id = Column(Integer, primary_key=True)
+ children = relationship("Child", order_by="Child.id")
+
+ A full array of examples and reference documentation regarding
+ :func:`.relationship` is at :ref:`relationship_config_toplevel`.
+
:param argument:
- a class or :class:`.Mapper` instance, representing the target of
- the relationship.
+ a mapped class, or actual :class:`.Mapper` instance, representing the target of
+ the relationship.
+
+ ``argument`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
:param secondary:
for a many-to-many relationship, specifies the intermediary
- table. The *secondary* keyword argument should generally only
+ table, and is an instance of :class:`.Table`. The ``secondary`` keyword
+ argument should generally only
be used for a table that is not otherwise expressed in any class
- mapping. In particular, using the Association Object Pattern is
- generally mutually exclusive with the use of the *secondary*
- keyword argument.
+ mapping, unless this relationship is declared as view only, otherwise
+ conflicting persistence operations can occur.
+
+ ``secondary`` may
+ also be passed as a callable function which is evaluated at
+ mapper initialization time.
:param active_history=False:
When ``True``, indicates that the "previous" value for a
value in order to perform a flush. This flag is available
for applications that make use of
:func:`.attributes.get_history` which also need to know
- the "previous" value of the attribute. (New in 0.6.6)
+ the "previous" value of the attribute.
:param backref:
indicates the string name of a property to be placed on the related
rare and exotic composite foreign key setups where some columns
should artificially not be considered as foreign.
+ ``foreign_keys`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
:param innerjoin=False:
when ``True``, joined eager loads will use an inner join to join
against related tables instead of an outer join. The purpose
:param order_by:
indicates the ordering that should be applied when loading these
- items.
-
+ items. ``order_by`` is expected to refer to one of the :class:`.Column`
+ objects to which the target class is mapped, or
+ the attribute itself bound to the target class which refers
+ to the column.
+
+ ``order_by`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
:param passive_deletes=False:
Indicates loading behavior during delete operations.
use ``post_update`` to "break" the cycle.
:param primaryjoin:
- a ColumnElement (i.e. WHERE criterion) that will be used as the primary
+ a SQL expression that will be used as the primary
join of this child object against the parent object, or in a
many-to-many relationship the join of the primary object to the
association table. By default, this value is computed based on the
foreign key relationships of the parent and child tables (or association
table).
+ ``primaryjoin`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
:param remote_side:
used for self-referential relationships, indicates the column or
list of columns that form the "remote side" of the relationship.
+ ``remote_side`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
:param query_class:
a :class:`.Query` subclass that will be used as the base of the
"appender query" returned by a "dynamic" relationship, that
function.
:param secondaryjoin:
- a ColumnElement (i.e. WHERE criterion) that will be used as the join of
+ a SQL expression that will be used as the join of
an association table to the child object. By default, this value is
computed based on the foreign key relationships of the association and
child tables.
+ ``secondaryjoin`` may also be passed as a callable function
+ which is evaluated at mapper initialization time, and may be passed as a
+ Python-evaluable string when using Declarative.
+
:param single_parent=(True|False):
when True, installs a validator which will prevent objects
from being associated with more than one parent at a time.
This is used for many-to-one or many-to-many relationships that
should be treated either as one-to-one or one-to-many. Its
usage is optional unless delete-orphan cascade is also
- set on this relationship(), in which case its required (new in 0.5.2).
+ set on this relationship(), in which case its required.
:param uselist=(True|False):
a boolean that indicates if this property should be loaded as a