]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
move backref to "legacy"
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Jul 2022 17:05:37 +0000 (13:05 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Jul 2022 17:22:32 +0000 (13:22 -0400)
in the interests of consistency as well as new typing features,
backref should be considered legacy and is fully superseded
by back_populates.

this commit is for 2.0 /1.4, in 2.0 further updates will be
made for new ORM syntaxes.

Change-Id: Idd3b7a3b07843b73304df69e476dc4239c60b3f8
(cherry picked from commit d49dfa74e86778eb5c581470405131ed9f9d0206)

doc/build/orm/backref.rst
doc/build/orm/cascades.rst
doc/build/orm/relationships.rst
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/relationships.py

index f52b868f8d9e13ddcfdacf5cf4dca62a7837060f..edc87cd19ddc1ef0a21434b6e2b1bb59c02f84c8 100644 (file)
@@ -1,11 +1,43 @@
 .. _relationships_backref:
 
-Linking Relationships with Backref
-----------------------------------
+Using the legacy 'backref' relationship parameter
+--------------------------------------------------
+
+.. note:: The :paramref:`_orm.relationship.backref` keyword should be considered
+   legacy, and use of :paramref:`_orm.relationship.back_populates` with explicit
+   :func:`_orm.relationship` constructs should be preferred.  Using
+   individual :func:`_orm.relationship` constructs provides advantages
+   including that both ORM mapped classes will include their attributes
+   up front as the class is constructed, rather than as a deferred step,
+   and configuration is more straightforward as all arguments are explicit.
+   New :pep:`484` features in SQLAlchemy 2.0 also take advantage of
+   attributes being explicitly present in source code rather than
+   using dynamic attribute generation.
 
-The :paramref:`_orm.relationship.backref` keyword argument was first introduced in :ref:`ormtutorial_toplevel`, and has been
-mentioned throughout many of the examples here.   What does it actually do ?   Let's start
-with the canonical ``User`` and ``Address`` scenario::
+.. seealso::
+
+    For general information about bidirectional relationships, see the
+    following sections:
+
+    :ref:`tutorial_orm_related_objects` - in the :ref:`unified_tutorial`,
+    presents an overview of bi-directional relationship configuration
+    and behaviors using :paramref:`_orm.relationship.back_populates`
+
+    :ref:`back_populates_cascade` - notes on bi-directional :func:`_orm.relationship`
+    behavior regarding :class:`_orm.Session` cascade behaviors.
+
+    :paramref:`_orm.relationship.back_populates`
+
+
+The :paramref:`_orm.relationship.backref` keyword argument on the
+:func:`_orm.relationship` construct allows the
+automatic generation of a new :func:`_orm.relationship` that will be automatically
+be added to the ORM mapping for the related class.  It will then be
+placed into a :paramref:`_orm.relationship.back_populates` configuration
+against the current :func:`_orm.relationship` being configured, with both
+:func:`_orm.relationship` constructs referring to each other.
+
+Starting with the following example::
 
     from sqlalchemy import Column, ForeignKey, Integer, String
     from sqlalchemy.orm import declarative_base, relationship
@@ -29,12 +61,8 @@ with the canonical ``User`` and ``Address`` scenario::
 
 The above configuration establishes a collection of ``Address`` objects on ``User`` called
 ``User.addresses``.   It also establishes a ``.user`` attribute on ``Address`` which will
-refer to the parent ``User`` object.
-
-In fact, the :paramref:`_orm.relationship.backref` keyword is only a common shortcut for placing a second
-:func:`_orm.relationship` onto the ``Address`` mapping, including the establishment
-of an event listener on both sides which will mirror attribute operations
-in both directions.   The above configuration is equivalent to::
+refer to the parent ``User`` object.   Using :paramref:`_orm.relationship.back_populates`
+it's equivalent to the following::
 
     from sqlalchemy import Column, ForeignKey, Integer, String
     from sqlalchemy.orm import declarative_base, relationship
@@ -58,68 +86,23 @@ in both directions.   The above configuration is equivalent to::
 
         user = relationship("User", back_populates="addresses")
 
-Above, we add a ``.user`` relationship to ``Address`` explicitly.  On
-both relationships, the :paramref:`_orm.relationship.back_populates` directive tells each relationship
-about the other one, indicating that they should establish "bidirectional"
-behavior between each other.   The primary effect of this configuration
-is that the relationship adds event handlers to both attributes
-which have the behavior of "when an append or set event occurs here, set ourselves
-onto the incoming attribute using this particular attribute name".
-The behavior is illustrated as follows.   Start with a ``User`` and an ``Address``
-instance.  The ``.addresses`` collection is empty, and the ``.user`` attribute
-is ``None``::
-
-    >>> u1 = User()
-    >>> a1 = Address()
-    >>> u1.addresses
-    []
-    >>> print(a1.user)
-    None
-
-However, once the ``Address`` is appended to the ``u1.addresses`` collection,
-both the collection and the scalar attribute have been populated::
-
-    >>> u1.addresses.append(a1)
-    >>> u1.addresses
-    [<__main__.Address object at 0x12a6ed0>]
-    >>> a1.user
-    <__main__.User object at 0x12a6590>
-
-This behavior of course works in reverse for removal operations as well, as well
-as for equivalent operations on both sides.   Such as
-when ``.user`` is set again to ``None``, the ``Address`` object is removed
-from the reverse collection::
-
-    >>> a1.user = None
-    >>> u1.addresses
-    []
-
-The manipulation of the ``.addresses`` collection and the ``.user`` attribute
-occurs entirely in Python without any interaction with the SQL database.
-Without this behavior, the proper state would be apparent on both sides once the
-data has been flushed to the database, and later reloaded after a commit or
-expiration operation occurs.  The :paramref:`_orm.relationship.backref`/:paramref:`_orm.relationship.back_populates` behavior has the advantage
-that common bidirectional operations can reflect the correct state without requiring
-a database round trip.
-
-Remember, when the :paramref:`_orm.relationship.backref` keyword is used on a single relationship, it's
-exactly the same as if the above two relationships were created individually
-using :paramref:`_orm.relationship.back_populates` on each.
-
-Backref Arguments
-~~~~~~~~~~~~~~~~~
-
-We've established that the :paramref:`_orm.relationship.backref` keyword is merely a shortcut for building
-two individual :func:`_orm.relationship` constructs that refer to each other.  Part of
-the behavior of this shortcut is that certain configurational arguments applied to
-the :func:`_orm.relationship`
-will also be applied to the other direction - namely those arguments that describe
-the relationship at a schema level, and are unlikely to be different in the reverse
-direction.  The usual case
-here is a many-to-many :func:`_orm.relationship` that has a :paramref:`_orm.relationship.secondary` argument,
-or a one-to-many or many-to-one which has a :paramref:`_orm.relationship.primaryjoin` argument (the
-:paramref:`_orm.relationship.primaryjoin` argument is discussed in :ref:`relationship_primaryjoin`).  Such
-as if we limited the list of ``Address`` objects to those which start with "tony"::
+The behavior of the ``User.addresses`` and ``Address.user`` relationships
+is that they now behave in a **bi-directional** way, indicating that
+changes on one side of the relationship impact the other.   An example
+and discussion of this behavior is in the :ref:`unified_tutorial`
+at :ref:`tutorial_orm_related_objects`.
+
+
+Backref Default Arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Since :paramref:`_orm.relationship.backref` generates a whole new
+:func:`_orm.relationship`, the generation process by default
+will attempt to include corresponding arguments in the new
+:func:`_orm.relationship` that correspond to the original arguments.
+As an example, below is a :func:`_orm.relationship` that includes a
+:ref:`custom join condition <relationship_configure_joins>`
+which also includes the :paramref:`_orm.relationship.backref` keyword::
 
     from sqlalchemy import Column, ForeignKey, Integer, String
     from sqlalchemy.orm import declarative_base, relationship
@@ -147,8 +130,8 @@ as if we limited the list of ``Address`` objects to those which start with "tony
         email = Column(String)
         user_id = Column(Integer, ForeignKey("user.id"))
 
-We can observe, by inspecting the resulting property, that both sides
-of the relationship have this join condition applied::
+When the "backref" is generated, the :paramref:`_orm.relationship.primaryjoin`
+condition is copied to the new :func:`_orm.relationship` as well::
 
     >>> print(User.addresses.property.primaryjoin)
     "user".id = address.user_id AND address.email LIKE :email_1 || '%%'
@@ -157,22 +140,26 @@ of the relationship have this join condition applied::
     "user".id = address.user_id AND address.email LIKE :email_1 || '%%'
     >>>
 
-This reuse of arguments should pretty much do the "right thing" - it
-uses only arguments that are applicable, and in the case of a many-to-
-many relationship, will reverse the usage of
+Other arguments that are transferrable include the
+:paramref:`_orm.relationship.secondary` parameter that refers to a
+many-to-many association table, as well as the "join" arguments
 :paramref:`_orm.relationship.primaryjoin` and
-:paramref:`_orm.relationship.secondaryjoin` to correspond to the other
-direction (see the example in :ref:`self_referential_many_to_many` for
-this).
+:paramref:`_orm.relationship.secondaryjoin`; "backref" is smart enough to know
+that these two arguments should also be "reversed" when generating
+the opposite side.
+
+Specifying Backref Arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-It's very often the case however that we'd like to specify arguments
-that are specific to just the side where we happened to place the
-"backref". This includes :func:`_orm.relationship` arguments like
+Lots of other arguments for a "backref" are not implicit, and
+include arguments like
 :paramref:`_orm.relationship.lazy`,
 :paramref:`_orm.relationship.remote_side`,
 :paramref:`_orm.relationship.cascade` and
 :paramref:`_orm.relationship.cascade_backrefs`.   For this case we use
-the :func:`.backref` function in place of a string::
+the :func:`.backref` function in place of a string; this will store
+a specific set of arguments that will be transferred to the new
+:func:`_orm.relationship` when generated::
 
     # <other imports>
     from sqlalchemy.orm import backref
@@ -195,144 +182,3 @@ returned ``Address``.   The :func:`.backref` function formatted the arguments we
 it into a form that is interpreted by the receiving :func:`_orm.relationship` as additional
 arguments to be applied to the new relationship it creates.
 
-Setting cascade for backrefs
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A key behavior that occurs in the 1.x series of SQLAlchemy regarding backrefs
-is that :ref:`cascades <unitofwork_cascades>` will occur bidirectionally by
-default.   This basically means, if one starts with an ``User`` object
-that's been persisted in the :class:`.Session`::
-
-    user = session.query(User).filter(User.id == 1).first()
-
-The above ``User`` is :term:`persistent` in the :class:`.Session`.  It usually
-is intuitive that if we create an ``Address`` object and append to the
-``User.addresses`` collection, it is automatically added to the
-:class:`.Session` as in the example below::
-
-    user = session.query(User).filter(User.id == 1).first()
-    address = Address(email_address='foo')
-    user.addresses.append(address)
-
-The above behavior is known as the "save update cascade" and is described
-in the section :ref:`unitofwork_cascades`.
-
-However, if we instead created a new ``Address`` object, and associated the
-``User`` object with the ``Address`` as follows::
-
-    address = Address(email_address='foo', user=user)
-
-In the above example, it is **not** as intuitive that the ``Address`` would
-automatically be added to the :class:`.Session`.  However, the backref behavior
-of ``Address.user`` indicates that the ``Address`` object is also appended to
-the ``User.addresses`` collection.  This in turn initiates a **cascade**
-operation which indicates that this ``Address`` should be placed into the
-:class:`.Session` as a :term:`pending` object.
-
-Since this behavior has been identified as counter-intuitive to most people,
-it can be disabled by setting :paramref:`_orm.relationship.cascade_backrefs`
-to False, as in::
-
-
-    class User(Base):
-        # ...
-
-        addresses = relationship("Address", back_populates="user", cascade_backrefs=False)
-
-See the example in :ref:`backref_cascade` for further information.
-
-.. seealso::
-
-    :ref:`backref_cascade`.
-
-
-One Way Backrefs
-~~~~~~~~~~~~~~~~
-
-An unusual case is that of the "one way backref".   This is where the
-"back-populating" behavior of the backref is only desirable in one
-direction. An example of this is a collection which contains a
-filtering :paramref:`_orm.relationship.primaryjoin` condition.   We'd
-like to append items to this collection as needed, and have them
-populate the "parent" object on the incoming object. However, we'd
-also like to have items that are not part of the collection, but still
-have the same "parent" association - these items should never be in
-the collection.
-
-Taking our previous example, where we established a
-:paramref:`_orm.relationship.primaryjoin` that limited the collection
-only to ``Address`` objects whose email address started with the word
-``tony``, the usual backref behavior is that all items populate in
-both directions.   We wouldn't want this behavior for a case like the
-following::
-
-    >>> u1 = User()
-    >>> a1 = Address(email='mary')
-    >>> a1.user = u1
-    >>> u1.addresses
-    [<__main__.Address object at 0x1411910>]
-
-Above, the ``Address`` object that doesn't match the criterion of "starts with 'tony'"
-is present in the ``addresses`` collection of ``u1``.   After these objects are flushed,
-the transaction committed and their attributes expired for a re-load, the ``addresses``
-collection will hit the database on next access and no longer have this ``Address`` object
-present, due to the filtering condition.   But we can do away with this unwanted side
-of the "backref" behavior on the Python side by using two separate :func:`_orm.relationship` constructs,
-placing :paramref:`_orm.relationship.back_populates` only on one side::
-
-    from sqlalchemy import Column, ForeignKey, Integer, String
-    from sqlalchemy.orm import declarative_base, relationship
-
-    Base = declarative_base()
-
-
-    class User(Base):
-        __tablename__ = "user"
-        id = Column(Integer, primary_key=True)
-        name = Column(String)
-
-        addresses = relationship(
-            "Address",
-            primaryjoin="and_(User.id==Address.user_id, "
-            "Address.email.startswith('tony'))",
-            back_populates="user",
-        )
-
-
-    class Address(Base):
-        __tablename__ = "address"
-        id = Column(Integer, primary_key=True)
-        email = Column(String)
-        user_id = Column(Integer, ForeignKey("user.id"))
-
-        user = relationship("User")
-
-With the above scenario, appending an ``Address`` object to the ``.addresses``
-collection of a ``User`` will always establish the ``.user`` attribute on that
-``Address``::
-
-    >>> u1 = User()
-    >>> a1 = Address(email='tony')
-    >>> u1.addresses.append(a1)
-    >>> a1.user
-    <__main__.User object at 0x1411850>
-
-However, applying a ``User`` to the ``.user`` attribute of an ``Address``,
-will not append the ``Address`` object to the collection::
-
-    >>> a2 = Address(email='mary')
-    >>> a2.user = u1
-    >>> a2 in u1.addresses
-    False
-
-Of course, we've disabled some of the usefulness of
-:paramref:`_orm.relationship.backref` here, in that when we do append an
-``Address`` that corresponds to the criteria of
-``email.startswith('tony')``, it won't show up in the
-``User.addresses`` collection until the session is flushed, and the
-attributes reloaded after a commit or expire operation.   While we
-could consider an attribute event that checks this criterion in
-Python, this starts to cross the line of duplicating too much SQL
-behavior in Python.  The backref behavior itself is only a slight
-transgression of this philosophy - SQLAlchemy tries to keep these to a
-minimum overall.
index 466c1975cecdf80142b7bb418b5eba71e5fd5ff6..3c1180404c1f47c0e7cbd8a559c98637876cdad5 100644 (file)
@@ -570,6 +570,8 @@ expunge
 from the :class:`.Session` using :meth:`.Session.expunge`, the
 operation should be propagated down to referred objects.
 
+.. _back_populates_cascade:
+
 .. _backref_cascade:
 
 Controlling Cascade on Backrefs
index b9111741ccf25e7edf18e71e19d42e93a78323b8..0c12ba1a4b3db071c35df40744200bbb03885e8e 100644 (file)
@@ -14,9 +14,9 @@ of its usage.   For an introduction to relationships, start with the
 
     basic_relationships
     self_referential
-    backref
     join_conditions
     collections
     relationship_persistence
+    backref
     relationship_api
 
index 78650507ee6ce16e91cbaba9bba2f4e534a17402..6e0de05c6d3e4ed2a2f70066ecb277c0a29a0665 100644 (file)
@@ -172,18 +172,24 @@ composite = public_factory(CompositeProperty, ".orm.composite")
 
 
 def backref(name, **kwargs):
-    """Create a back reference with explicit keyword arguments, which are the
-    same arguments one can send to :func:`relationship`.
+    """When using the :paramref:`_orm.relationship.backref` parameter,
+    provides specific parameters to be used when the new
+    :func:`_orm.relationship` is generated.
 
-    Used with the ``backref`` keyword argument to :func:`relationship` in
-    place of a string argument, e.g.::
+    E.g.::
 
         'items':relationship(
             SomeItem, backref=backref('parent', lazy='subquery'))
 
+    The :paramref:`_orm.relationship.backref` parameter is generally
+    considered to be legacy; for modern applications, using
+    explicit :func:`_orm.relationship` constructs linked together using
+    the :paramref:`_orm.relationship.back_populates` parameter should be
+    preferred.
+
     .. seealso::
 
-        :ref:`relationships_backref`
+        :ref:`relationships_backref` - background on backrefs
 
     """
 
index f58277e32ea5c45f1d6956dd14032d0cc9dfbbf2..b51ea0e00977fca58402c762bdbd28f34c0e6c92 100644 (file)
@@ -311,41 +311,51 @@ class RelationshipProperty(StrategizedProperty):
           the "previous" value of the attribute.
 
         :param backref:
-          Indicates the string name of a property to be placed on the related
-          mapper's class that will handle this relationship in the other
-          direction. The other property will be created automatically
-          when the mappers are configured.  Can also be passed as a
-          :func:`.backref` object to control the configuration of the
-          new relationship.
+          A reference to a string relationship name, or a :func:`_orm.backref`
+          construct, which will be used to automatically generate a new
+          :func:`_orm.relationship` on the related class, which then refers to
+          this one using a bi-directional
+          :paramref:`_orm.relationship.back_populates` configuration.
+
+          In modern Python, explicit use of :func:`_orm.relationship` with
+          :paramref:`_orm.relationship.back_populates` should be preferred, as
+          it is more robust in terms of mapper configuration as well as more
+          conceptually straightforward. It also integrates with new :pep:`484`
+          typing features introduced in SQLAlchemy 2.0 which is not possible
+          with dynamically generated attributes.
 
           .. seealso::
 
-            :ref:`relationships_backref` - Introductory documentation and
-            examples.
+              :ref:`relationships_backref` - notes on using
+              :paramref:`_orm.relationship.backref`
 
-            :paramref:`_orm.relationship.back_populates` - alternative form
-            of backref specification.
+              :ref:`tutorial_orm_related_objects` - in the
+              :ref:`unified_tutorial`, presents an overview of bi-directional
+              relationship configuration and behaviors using
+              :paramref:`_orm.relationship.back_populates`
 
-            :func:`.backref` - allows control over :func:`_orm.relationship`
-            configuration when using :paramref:`_orm.relationship.backref`.
+              :func:`.backref` - allows control over :func:`_orm.relationship`
+              configuration when using :paramref:`_orm.relationship.backref`.
 
 
         :param back_populates:
-          Takes a string name and has the same meaning as
-          :paramref:`_orm.relationship.backref`, except the complementing
-          property is **not** created automatically, and instead must be
-          configured explicitly on the other mapper.  The complementing
-          property should also indicate
-          :paramref:`_orm.relationship.back_populates` to this relationship to
-          ensure proper functioning.
+          Indicates the name of a :func:`_orm.relationship` on the related
+          class that will be synchronized with this one.   It is usually
+          expected that the :func:`_orm.relationship` on the related class
+          also refer to this one.  This allows objects on both sides of
+          each :func:`_orm.relationship` to synchronize in-Python state
+          changes and also provides directives to the :term:`unit of work`
+          flush process how changes along these relationships should
+          be persisted.
 
           .. seealso::
 
-            :ref:`relationships_backref` - Introductory documentation and
-            examples.
+              :ref:`tutorial_orm_related_objects` - in the
+              :ref:`unified_tutorial`, presents an overview of bi-directional
+              relationship configuration and behaviors.
 
-            :paramref:`_orm.relationship.backref` - alternative form
-            of backref specification.
+              :ref:`relationship_patterns` - includes many examples of
+              :paramref:`_orm.relationship.back_populates`.
 
         :param overlaps:
            A string name or comma-delimited set of names of other relationships