summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-02-27 13:15:10 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2018-09-27 17:09:28 -0400
commit6446e0dfd3e3bb60754bad81c4d52345733d94e3 (patch)
tree217e2183a984170c15db799baa3f79dda0da379d /doc
parentd945ee87a1934cb689066e4683ba9531d803f9ad (diff)
downloadsqlalchemy-6446e0dfd3e3bb60754bad81c4d52345733d94e3.tar.gz
Break association proxy into a descriptor + per-class accessor
Reworked :class:`.AssociationProxy` to store state that's specific to a parent class in a separate object, so that a single :class:`.AssocationProxy` can serve for multiple parent classes, as is intrinsic to inheritance, without any ambiguity in the state returned by it. A new method :meth:`.AssociationProxy.for_class` is added to allow inspection of class-specific state. Change-Id: I634f88aae6306ac5c5237a0e1acbe07d0481d6b6 Fixes: #3423
Diffstat (limited to 'doc')
-rw-r--r--doc/build/changelog/migration_13.rst62
-rw-r--r--doc/build/changelog/unreleased_13/3423.rst15
-rw-r--r--doc/build/orm/extensions/associationproxy.rst5
3 files changed, 82 insertions, 0 deletions
diff --git a/doc/build/changelog/migration_13.rst b/doc/build/changelog/migration_13.rst
index f742eb667..500062686 100644
--- a/doc/build/changelog/migration_13.rst
+++ b/doc/build/changelog/migration_13.rst
@@ -76,6 +76,68 @@ to ``None``::
:ticket:`4308`
+.. _change_3423:
+
+AssociationProxy stores class-specific state in a separate container
+--------------------------------------------------------------------
+
+The :class:`.AssociationProxy` object makes lots of decisions based on the
+parent mapped class it is associated with. While the
+:class:`.AssociationProxy` historically began as a relatively simple "getter",
+it became apparent early on that it also needed to make decisions about what
+kind of attribute it is referring towards, e.g. scalar or collection, mapped
+object or simple value, and similar. To achieve this, it needs to inspect the
+mapped attribute or other descriptor or attribute that it refers towards, as
+referenced from its parent class. However in Python descriptor mechanics, a
+descriptor only learns about its "parent" class when it is accessed in the
+context of that class, such as calling ``MyClass.some_descriptor``, which calls
+the ``__get__()`` method which passes in the class. The
+:class:`.AssociationProxy` object would therefore store state that is specific
+to that class, but only once this method were called; trying to inspect this
+state ahead of time without first accessing the :class:`.AssociationProxy`
+as a descriptor would raise an error. Additionally, it would assume that
+the first class to be seen by ``__get__()`` would be the only parent class it
+needed to know about. This is despite the fact that if a particular class
+has inheriting subclasses, the association proxy is really working
+on behalf of more than one parent class even though it was not explicitly
+re-used. While even with this shortcoming, the association proxy would
+still get pretty far with its current behavior, it still leaves shortcomings
+in some cases as well as the complex problem of determining the best "owner"
+class.
+
+These problems are now solved in that :class:`.AssociationProxy` no longer
+modifies its own internal state when ``__get__()`` is called; instead, a new
+object is generated per-class known as :class:`.AssociationProxyInstance` which
+handles all the state specific to a particular mapped parent class (when the
+parent class is not mapped, no :class:`.AssociationProxyInstance` is generated).
+The concept of a single "owning class" for the association proxy, which was
+nonetheless improved in 1.1, has essentially been replaced with an approach
+where the AP now can treat any number of "owning" classes equally.
+
+To accommodate for applications that want to inspect this state for an
+:class:`.AssociationProxy` without necessarily calling ``__get__()``, a new
+method :meth:`.AssociationProxy.for_class` is added that provides direct access
+to a class-specific :class:`.AssociationProxyInstance`, demonstrated as::
+
+ class User(Base):
+ # ...
+
+ keywords = association_proxy('kws', 'keyword')
+
+
+ proxy_state = inspect(User).all_orm_descriptors["keywords"].for_class(User)
+
+Once we have the :class:`.AssociationProxyInstance` object, in the above
+example stored in the ``proxy_state`` variable, we can look at attributes
+specific to the ``User.keywords`` proxy, such as ``target_class``::
+
+
+ >>> proxy_state.target_class
+ Keyword
+
+
+:ticket:`3423`
+
.. _change_4246:
FOR UPDATE clause is rendered within the joined eager load subquery as well as outside
diff --git a/doc/build/changelog/unreleased_13/3423.rst b/doc/build/changelog/unreleased_13/3423.rst
new file mode 100644
index 000000000..63317ae85
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/3423.rst
@@ -0,0 +1,15 @@
+.. change::
+ :tags: bug, ext
+ :tickets: 3423
+
+ Reworked :class:`.AssociationProxy` to store state that's specific to a
+ parent class in a separate object, so that a single
+ :class:`.AssocationProxy` can serve for multiple parent classes, as is
+ intrinsic to inheritance, without any ambiguity in the state returned by it.
+ A new method :meth:`.AssociationProxy.for_class` is added to allow
+ inspection of class-specific state.
+
+ .. seealso::
+
+ :ref:`change_3423`
+
diff --git a/doc/build/orm/extensions/associationproxy.rst b/doc/build/orm/extensions/associationproxy.rst
index 18803c75f..62b24c928 100644
--- a/doc/build/orm/extensions/associationproxy.rst
+++ b/doc/build/orm/extensions/associationproxy.rst
@@ -511,4 +511,9 @@ API Documentation
:undoc-members:
:inherited-members:
+.. autoclass:: AssociationProxyInstance
+ :members:
+ :undoc-members:
+ :inherited-members:
+
.. autodata:: ASSOCIATION_PROXY