]> 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:05:37 +0000 (13:05 -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

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

index 24587003ea5d79478bbed915a9b4783128f84beb..edc87cd19ddc1ef0a21434b6e2b1bb59c02f84c8 100644 (file)
@@ -1,11 +1,43 @@
 .. _relationships_backref:
 
-Linking Relationships with Backref
-----------------------------------
+Using the legacy 'backref' relationship parameter
+--------------------------------------------------
 
-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::
+.. 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.
+
+.. 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,116 +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.
 
-
-Cascade behavior for backrefs
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-It's important to note that even though a bi-directional relationship
-may be manipulated from either direction, having the same end effect on the
-object structure produced, there is a significant difference in how the
-:ref:`save-update cascade <cascade_save_update>` behaves for two objects
-where one is attached and the other is unattached to a :class:`_orm.Session`,
-depending on the direction in which the relationships are manipulated.
-
-The ``save-update`` cascade will only take effect **uni-directionally**
-in the direction from a parent object that is already associated with a
-:class:`_orm.Session`, towards an object that is being associated with that
-parent directly via an attribute or collection on that parent.  It won't
-take effect if the parent object is instead assigned to an attribute or
-collection on the child, in which case the unattached parent object should be
-added to the :class:`_orm.Session` explicitly using :meth:`_orm.Session.add`.
-
-For a complete example of how this looks in practice, see the section
-: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 8d8e1f7763052aa565c4b9f0926bd44ff683796e..90d12b3cf60730a9d9756130d3edfe8e8c98a606 100644 (file)
@@ -122,6 +122,8 @@ for granted; it simplifies code by allowing a single call to
 that :class:`.Session` at once.   While it can be disabled, there
 is usually not a need to do so.
 
+.. _back_populates_cascade:
+
 .. _backref_cascade:
 
 Behavior of save-update cascade with bi-directional relationships
@@ -129,7 +131,8 @@ Behavior of save-update cascade with bi-directional relationships
 
 The ``save-update`` cascade takes place **uni-directionally** in the context of
 a bi-directional relationship, i.e. when using
-:ref:`backref / back_populates <relationships_backref>` to create two separate
+the :paramref:`_orm.relationship.back_populates` or :paramref:`_orm.relationship.backref`
+parameters to create two separate
 :func:`_orm.relationship` objects which refer to each other.
 
 An object that's not associated with a :class:`_orm.Session`, when assigned to
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 bafad09f221341bbca67eb44fa348064abf0572e..ea95f14202b6d0f14632b29030b40828a7e696f8 100644 (file)
@@ -978,41 +978,50 @@ def relationship(
       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`.
 
 
     :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
@@ -1876,18 +1885,24 @@ def dynamic_loader(
 
 
 def backref(name: str, **kwargs: Any) -> _ORMBackrefArgument:
-    """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
 
     """