primary_key=True)
)
-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::
+Reading and manipulating the collection of "keyword" strings associated
+with ``User`` requires traversal from each collection element to the ``.keyword``
+attribute, which can be awkward::
>>> user = User('jek')
>>> user.kw.append(Keyword('cheese inspector'))
- >>> print user.kw
+ >>> print(user.kw)
[<__main__.Keyword object at 0x12bf830>]
- >>> print user.kw[0].keyword
+ >>> print(user.kw[0].keyword)
cheese inspector
- >>> print [keyword.keyword for keyword in user.kw]
+ >>> 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. Below we illustrate
-how to bridge the gap between the ``kw`` collection and the ``keyword``
-attribute of each ``Keyword``::
+The ``association_proxy`` is applied to the ``User`` class to produce
+a "view" of the ``kw`` relationship, which only exposes the string
+value of ``.keyword`` associated with each ``Keyword`` object::
from sqlalchemy.ext.associationproxy import association_proxy
keywords = association_proxy('kw', 'keyword')
We can now reference the ``.keywords`` collection as a listing of strings,
-which is both readable and writeable::
+which is both readable and writable. New ``Keyword`` objects are created
+for us transparently::
>>> user = User('jek')
>>> user.keywords.append('cheese inspector')
>>> user.kw
[<__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 :class:`.AssociationProxy` object produced by the :func:`.association_proxy` function
+is an instance of a `Python descriptor <http://docs.python.org/howto/descriptor.html>`_.
+It is always declared with the user-defined class being mapped, regardless of
+whether Declarative or classical mappings via the :func:`.mapper` function 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
-the proxy also affects the underlying collection.
+The proxy functions by operating upon the underlying mapped attribute
+or collection in response to operations, and changes made via the proxy are immediately
+apparent in the mapped attribute, as well as vice versa. The underlying
+attribute remains fully accessible.
- - The association proxy property is backed by a mapper-defined relationship,
- either a collection or scalar.
+When first accessed, the association proxy performs introspection
+operations on the target collection so that its behavior corresponds correctly.
+Details such as if the locally proxied attribute is a collection (as is typical)
+or a scalar reference, as well as if the collection acts like a set, list,
+or dictionary is taken into account, so that the proxy should act just like
+the underlying collection or attribute does.
- - You can access and modify both the proxy and the backing
- relationship. Changes in one are immediate in the other.
+Creation of New Values
+-----------------------
- - The proxy acts like the type of the underlying collection. A list gets a
- list-like proxy, a dict a dict-like proxy, and so on.
+When a list append() event (or set add(), dictionary __setitem__(), or scalar
+assignment event) is intercepted by the association proxy, it instantiates a
+new instance of the "intermediary" object using its constructor, passing as a
+single argument the given value. In our example above, an operation like::
- - Multiple proxies for the same relationship are fine.
+ user.keywords.append('cheese inspector')
- - Proxies are lazy, and won't trigger a load of the backing relationship until
- they are accessed.
+Is translated by the association proxy into the operation::
- - The relationship is inspected to determine the type of the related objects.
+ user.kw.append(Keyword('cheese inspector'))
- - To construct new instances, the type is called with the value being
- assigned, or key and value for dicts.
+The example works here because we have designed the constructor for ``Keyword``
+to accept a single positional argument, ``keyword``. For those cases where a
+single-argument constructor isn't feasible, the association proxy's creational
+behavior can be customized using the ``creator`` argument, which references a
+callable (i.e. Python function) that will produce a new object instance given the
+singular argument. Below we illustrate this using a lambda as is typical::
- - A "creator" function can be used to create instances instead.
+ class User(Base):
+ # ...
-Above, the ``Keyword.__init__`` takes a single argument ``keyword``, which
-maps conveniently to the value being set through the proxy. A ``creator``
-function can be used if more flexibility is required.
+ # use Keyword(keyword=kw) on append() events
+ keywords = association_proxy('kw', 'keyword',
+ creator=lambda kw: Keyword(keyword=kw))
-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
-collection is sufficient.
+The ``creator`` function accepts a single argument in the case of a list-
+or set- based collection, or a scalar attribute. In the case of a dictionary-based
+collection, it accepts two arguments, "key" and "value". An example
+of this is below in :ref:`proxying_dictionaries`.
-Simplifying Association Proxies
+Simplifying Association Objects
-------------------------------
-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`.
+The "association object" pattern is an extended form of a many-to-many
+relationship, and is described at :ref:`association_pattern`. Association
+proxies are useful for keeping "association objects" out the way during
+regular use.
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
+This class adds an additional column ``special_key``, a value which
+we occasionally want to access, but not in the usual case. 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
+collection of ``User`` to the ``.keyword`` attribute present on each
``UserKeyword``::
from sqlalchemy import Column, Integer, String, ForeignKey
__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 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(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)
+
+ # 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
-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>
+ class Keyword(Base):
+ __tablename__ = 'keyword'
+ id = Column(Integer, primary_key=True)
+ keyword = Column('keyword', String(64))
- >>> # Accessing Keywords requires traversing UserKeywords
- ... print user.user_keywords[0]
- <__main__.UserKeyword object at 0x12d9a50>
+ def __init__(self, keyword):
+ self.keyword = keyword
- >>> print user.user_keywords[0].keyword
- Keyword('new_from_blammo')
+ def __repr__(self):
+ return 'Keyword(%s)' % repr(self.keyword)
-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::
+With the above configuration, we can operate upon the ``.keywords``
+collection of each ``User`` object, and the usage of ``UserKeyword``
+is concealed::
- >>> for kw in (Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')):
+ >>> 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'), Keyword('its_heavy'), Keyword('its_wood')]
+ >>> print(user.keywords)
+ [Keyword('new_from_blammo'), Keyword('its_big')]
-In each call to ``keywords.append()``, the association proxy performs the
-operation as::
+Where above, each ``.keywords.append()`` operation is equivalent to::
- user.user_keywords.append(UserKeyword(kw))
+ >>> user.user_keywords.append(UserKeyword(Keyword('its_heavy')))
-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.
+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. The ``.user`` argument is then
+assigned as the ``UserKeyword`` object is appended to the ``User.user_keywords``
+collection, where the bidirectional relationship configured between ``User.user_keywords``
+and ``UserKeyword.user`` results in a population of the ``UserKeyword.user`` attribute.
+The ``special_key`` argument above is left at its default value of ``None``.
-Using the creator argument
-^^^^^^^^^^^^^^^^^^^^^^^^^^
+For those cases where we do want ``special_key`` to have a value, we
+create the ``UserKeyword`` object explicitly. Below we assign all three
+attributes, where the assignment of ``.user`` has the effect of the ``UserKeyword``
+being appended to the ``User.user_keywords`` collection::
-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::
+ >>> UserKeyword(Keyword('its_wood'), user, special_key='my special key')
- class User(Base):
- # ...
+The association proxy returns to us a collection of ``Keyword`` objects represented
+by all these operations::
- keywords = association_proxy('user_keywords', 'keyword',
- creator=lambda k:UserKeyword(keyword=k, special_key='special'))
+ >>> user.keywords
+ [Keyword('new_from_blammo'), Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')]
+
+.. _proxying_dictionaries:
-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::
+Proxying to Dictionary Based Collections
+-----------------------------------------
+
+The association proxy can proxy to dictionary based collections as well. SQLAlchemy
+mappings usually use the :func:`.attribute_mapped_collection` collection type to
+create dictionary collections, as well as the extended techniques described in
+:ref:`dictionary_collections`.
+
+The association proxy adjusts its behavior when it detects the usage of a
+dictionary-based collection. When new values are added to the dictionary, the
+association proxy instantiates the intermediary object by passing two
+arguments to the creation function instead of one, the key and the value. As
+always, this creation function defaults to the constructor of the intermediary
+class, and can be customized using the ``creator`` argument.
+
+Below, we modify our ``UserKeyword`` example such that the ``User.user_keywords``
+collection will now be mapped using a dictionary, where the ``UserKeyword.special_key``
+argument will be used as the key for the dictionary. We then apply a ``creator``
+argument to the ``User.keywords`` proxy so that these values are assigned appropriately
+when new elements are added to the dictionary::
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
__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)
+ 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)
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
-
-The ``.keywords`` collection is now a dictionary of string keys to ``Keyword``
-objects::
+We illustrate the ``.keywords`` collection as a dictionary, mapping the
+``UserKeyword.string_key`` value to ``Keyword`` objects::
>>> user = User('log')
- >>> kw1 = Keyword('new_from_blammo')
>>> user.keywords['sk1'] = Keyword('kw1')
>>> user.keywords['sk2'] = Keyword('kw2')
- >>> print user.keywords
+ >>> print(user.keywords)
{'sk1': Keyword('kw1'), 'sk2': Keyword('kw2')}
-Building Complex Views
-----------------------
+Composite Association Proxies
+-----------------------------
Given our previous examples of proxying from relationship to scalar
attribute, proxying across an association object, and proxying dictionaries,
__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)
+ 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)
def __init__(self, keyword):
self.keyword = 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"
- )
- )
- 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.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. In the example below, we illustrate
+usage of the assignment operator, also appropriately handled by the
+association proxy, to apply a dictionary value to the collection at once::
>>> user = User('log')
>>> user.keywords = {
... 'sk1':'kw1',
... 'sk2':'kw2'
... }
- >>> print user.keywords
+ >>> print(user.keywords)
{'sk1': 'kw1', 'sk2': 'kw2'}
>>> user.keywords['sk3'] = 'kw3'
>>> del user.keywords['sk2']
- >>> print user.keywords
+ >>> print(user.keywords)
{'sk1': 'kw1', 'sk3': 'kw3'}
- >>> print user.user_keywords['sk3'].kw
+ >>> # illustrate un-proxied usage
+ ... print(user.user_keywords['sk3'].kw)
<__main__.Keyword object at 0x12ceb90>
+One caveat with our example above is that because ``Keyword`` objects are created
+for each dictionary set operation, the example fails to maintain uniqueness for
+the ``Keyword`` objects on their string name, which is a typical requirement for
+a tagging scenario such as this one. For this use case the recipe
+`UniqueObject <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/UniqueObject>`_, or
+a comparable creational strategy, is
+recommended, which will apply a "lookup first, then create" strategy to the constructor
+of the ``Keyword`` class, so that an already existing ``Keyword`` is returned if the
+given name is already present.
+
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
+as the target attribute. For example, the :meth:`.RelationshipProperty.Comparator.any`
+and :meth:`.RelationshipProperty.Comparator.has` operations are available, 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'))
+ >>> 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
For a proxy to a scalar attribute, ``__eq__()`` is supported::
- >>> print session.query(UserKeyword).filter(UserKeyword.keyword == 'jek')
+ >>> 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::
+and ``.contains()`` is available for a proxy to a scalar collection::
- >>> print session.query(User).filter(User.keywords.contains('jek'))
+ >>> print(session.query(User).filter(User.keywords.contains('jek')))
SELECT user.*
FROM user
WHERE EXISTS (SELECT 1