]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- second pass through association proxy docs, some more links on any()/has(), MapperP...
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 6 Aug 2011 16:43:09 +0000 (12:43 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 6 Aug 2011 16:43:09 +0000 (12:43 -0400)
CHANGES
doc/build/orm/collections.rst
doc/build/orm/extensions/associationproxy.rst
doc/build/orm/tutorial.rst
lib/sqlalchemy/ext/associationproxy.py
lib/sqlalchemy/orm/interfaces.py

diff --git a/CHANGES b/CHANGES
index 77f041c86efa669a89013f2122af5d4c105f8d74..9960e72dea62aa7021908427e3d822acc8af86f1 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -21,6 +21,12 @@ CHANGES
     the database regardless of whether C 
     extensions are in use or not.
 
+- ext
+  - Added local_attr, remote_attr, attr accessors
+    to AssociationProxy, providing quick access
+    to the proxied attributes at the class
+    level. [ticket:2236]
+
 0.7.2
 =====
 - orm
index 4d768e8c4a2433d6f41ee60c3198005d8570143b..fbe734e7c834aa3ffc6aedea923d212f8555ee8f 100644 (file)
@@ -284,6 +284,8 @@ interface marked for SQLAlchemy's use. Append and remove methods will be
 called with a mapped entity as the single argument, and iterator methods are
 called with no arguments and must return an iterator.
 
+.. _dictionary_collections:
+
 Dictionary-Based Collections
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -365,6 +367,9 @@ must decorate appender and remover methods, however- there are no compatible
 methods in the basic dictionary interface for SQLAlchemy to use by default.
 Iteration will go through ``itervalues()`` unless otherwise decorated.
 
+See also :ref:`proxying_dictionaries` for details on how to use association
+proxies to create flexible dictionary views.
+
 Instrumentation and Custom Types
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
index c78850cb7f0c4b6829f4ff8640bf1c41741cfcc3..3e0008009721acd359338035d2de9d2fc3512bc0 100644 (file)
@@ -52,23 +52,22 @@ Each ``User`` can have any number of ``Keyword`` objects, and vice-versa
                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
 
@@ -85,7 +84,8 @@ attribute of each ``Keyword``::
         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')
@@ -95,64 +95,74 @@ which is both readable and writeable::
     >>> 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
@@ -167,99 +177,103 @@ collection to the ``Keyword`` object referenced by each
         __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
@@ -273,13 +287,34 @@ linked to ``Keyword`` objects::
         __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)
@@ -291,38 +326,19 @@ linked to ``Keyword`` objects::
         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,
@@ -346,15 +362,40 @@ present on ``UserKeyword``::
         __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)
@@ -363,50 +404,50 @@ present on ``UserKeyword``::
         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 
@@ -417,16 +458,16 @@ a "nested" EXISTS clause, such as in our basic association object example::
 
 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 
index bb55fe60eb5357b6eb83818a308250958c9f36be..29525cc635e515ae843ef7d1ec4efd55cc730c3c 100644 (file)
@@ -1301,7 +1301,7 @@ There is an explicit EXISTS construct, which looks like this:
 
 The :class:`~sqlalchemy.orm.query.Query` features several operators which make
 usage of EXISTS automatically. Above, the statement can be expressed along the
-``User.addresses`` relationship using ``any()``:
+``User.addresses`` relationship using :meth:`~.RelationshipProperty.Comparator.any`:
 
 .. sourcecode:: python+sql
 
@@ -1315,7 +1315,7 @@ usage of EXISTS automatically. Above, the statement can be expressed along the
     ()
     {stop}jack
 
-``any()`` takes criterion as well, to limit the rows matched:
+:meth:`~.RelationshipProperty.Comparator.any` takes criterion as well, to limit the rows matched:
 
 .. sourcecode:: python+sql
 
@@ -1330,7 +1330,7 @@ usage of EXISTS automatically. Above, the statement can be expressed along the
     ('%google%',)
     {stop}jack
 
-``has()`` is the same operator as ``any()`` for many-to-one relationships (note the ``~`` operator here too, which means "NOT"):
+:meth:`~.RelationshipProperty.Comparator.has` is the same operator as :meth:`~.RelationshipProperty.Comparator.any` for many-to-one relationships (note the ``~`` operator here too, which means "NOT"):
 
 .. sourcecode:: python+sql
 
index 55237686c0143bc8f6762c66b87d2f670a365081..47b21ab5b096a94f0a9fd53806dfe9ea51171ead 100644 (file)
@@ -26,21 +26,9 @@ def association_proxy(target_collection, attr, **kw):
     """Return a Python property implementing a view of a target
     attribute which references an attribute on members of the 
     target.
-
-    Implements a read/write view over an instance's *target_collection*,
-    extracting *attr* from each member of the collection.  The property acts
-    somewhat like this list comprehension::
-
-      [getattr(member, *attr*)
-       for member in getattr(instance, *target_collection*)]
-
-    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 returned value is an instance of :class:`.AssociationProxy`.
+    
     Implements a Python property representing a relationship as a collection of
     simpler values, or a scalar value.  The proxied property will mimic the collection type of
     the target (list, dict or set), or, in the case of a one to one relationship,
@@ -95,9 +83,13 @@ class AssociationProxy(object):
                  getset_factory=None, proxy_factory=None, 
                  proxy_bulk_set=None):
         """Construct a new :class:`.AssociationProxy`.
+        
+        The :func:`.association_proxy` function is provided as the usual
+        entrypoint here, though :class:`.AssociationProxy` can be instantiated
+        and/or subclassed directly.
 
         :param target_collection: Name of the collection we'll proxy to, 
-          usually created with 'relationship()' in a mapper setup.
+          usually created with :func:`.relationship`.
 
         :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
@@ -202,12 +194,17 @@ class AssociationProxy(object):
 
     @util.memoized_property
     def target_class(self):
-        """The class the proxy is attached to."""
+        """The intermediary class handled by this :class:`.AssociationProxy`.
+        
+        Intercepted append/set/assignment events will result
+        in the generation of new instances of this class.
+        
+        """
         return self._get_property().mapper.class_
 
     @util.memoized_property
     def scalar(self):
-        """Return true if this :class:`.AssociationProxy` proxies a scalar
+        """Return ``True`` if this :class:`.AssociationProxy` proxies a scalar
         relationship on the local side."""
 
         scalar = not self._get_property().uselist
@@ -335,7 +332,14 @@ class AssociationProxy(object):
         return self._get_property().comparator
 
     def any(self, criterion=None, **kwargs):
-        """Produce a proxied 'any' expression using EXISTS."""
+        """Produce a proxied 'any' expression using EXISTS.
+        
+        This expression will be a composed product
+        using the :meth:`.RelationshipProperty.Comparator.any`
+        and/or :meth:`.RelationshipProperty.Comparator.has` 
+        operators of the underlying proxied attributes.
+
+        """
 
         if self._value_is_scalar:
             value_expr = getattr(self.target_class, self.value_attr).has(criterion, **kwargs)
@@ -355,14 +359,29 @@ class AssociationProxy(object):
                 )
 
     def has(self, criterion=None, **kwargs):
-        """Produce a proxied 'has' expression using EXISTS."""
+        """Produce a proxied 'has' expression using EXISTS.
+        
+        This expression will be a composed product
+        using the :meth:`.RelationshipProperty.Comparator.any`
+        and/or :meth:`.RelationshipProperty.Comparator.has` 
+        operators of the underlying proxied attributes.
+        
+        """
 
         return self._comparator.has(
-                    getattr(self.target_class, self.value_attr).has(criterion, **kwargs)
+                    getattr(self.target_class, self.value_attr).\
+                        has(criterion, **kwargs)
                 )
 
     def contains(self, obj):
-        """Produce a proxied 'contains' expression using EXISTS."""
+        """Produce a proxied 'contains' expression using EXISTS.
+        
+        This expression will be a composed product
+        using the :meth:`.RelationshipProperty.Comparator.any`
+        , :meth:`.RelationshipProperty.Comparator.has`,
+        and/or :meth:`.RelationshipProperty.Comparator.contains`
+        operators of the underlying proxied attributes.
+        """
 
         if self.scalar and not self._value_is_scalar:
             return self._comparator.has(
index 4f8e6dbfda95b1cf333d5937ad6bd2b614b5be83..24aebcc7bdc178f62027986cad9d4b22d0cba07a 100644 (file)
@@ -61,6 +61,13 @@ class MapperProperty(object):
     attribute, as well as that attribute as it appears on individual
     instances of the class, including attribute instrumentation,
     attribute access, loading behavior, and dependency calculations.
+    
+    The most common occurrences of :class:`.MapperProperty` are the
+    mapped :class:`.Column`, which is represented in a mapping as 
+    an instance of :class:`.ColumnProperty`,
+    and a reference to another class produced by :func:`.relationship`,
+    represented in the mapping as an instance of :class:`.RelationshipProperty`.
+    
     """
 
     cascade = ()
@@ -244,8 +251,7 @@ class PropComparator(operators.ColumnOperators):
             query.join(Company.employees.of_type(Engineer)).\\
                filter(Engineer.name=='foo')
 
-        \class_
-            a class or mapper indicating that criterion will be against
+        :param \class_: a class or mapper indicating that criterion will be against
             this specific subclass.
 
 
@@ -257,13 +263,16 @@ class PropComparator(operators.ColumnOperators):
         """Return true if this collection contains any member that meets the
         given criterion.
 
-        criterion
-          an optional ClauseElement formulated against the member class' table
-          or attributes.
+        The usual implementation of ``any()`` is 
+        :meth:`.RelationshipProperty.Comparator.any`.
+
+        :param criterion: an optional ClauseElement formulated against the 
+          member class' table or attributes.
+
+        :param \**kwargs: key/value pairs corresponding to member class attribute 
+          names which will be compared via equality to the corresponding
+          values.
 
-        \**kwargs
-          key/value pairs corresponding to member class attribute names which
-          will be compared via equality to the corresponding values.
         """
 
         return self.operate(PropComparator.any_op, criterion, **kwargs)
@@ -272,13 +281,16 @@ class PropComparator(operators.ColumnOperators):
         """Return true if this element references a member which meets the
         given criterion.
 
-        criterion
-          an optional ClauseElement formulated against the member class' table
-          or attributes.
+        The usual implementation of ``has()`` is 
+        :meth:`.RelationshipProperty.Comparator.has`.
+
+        :param criterion: an optional ClauseElement formulated against the 
+          member class' table or attributes.
+
+        :param \**kwargs: key/value pairs corresponding to member class attribute 
+          names which will be compared via equality to the corresponding
+          values.
 
-        \**kwargs
-          key/value pairs corresponding to member class attribute names which
-          will be compared via equality to the corresponding values.
         """
 
         return self.operate(PropComparator.has_op, criterion, **kwargs)