]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- convert ORM tutorial and basic_relationships to favor
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Dec 2015 23:27:14 +0000 (18:27 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Dec 2015 23:27:14 +0000 (18:27 -0500)
back_populates while still maintaining great familiarity
w/ backref so as not to confuse people.  fixes #3390

doc/build/orm/basic_relationships.rst
doc/build/orm/tutorial.rst
lib/sqlalchemy/orm/__init__.py

index 9a7ad4fa229a709076137b7ccb3acda94c994395..acb2dba011d8f7b48bd1450eaa052ebd345fafee 100644 (file)
@@ -8,7 +8,7 @@ A quick walkthrough of the basic relational patterns.
 The imports used for each of the following sections is as follows::
 
     from sqlalchemy import Table, Column, Integer, ForeignKey
-    from sqlalchemy.orm import relationship, backref
+    from sqlalchemy.orm import relationship
     from sqlalchemy.ext.declarative import declarative_base
 
     Base = declarative_base()
@@ -32,20 +32,32 @@ a collection of items represented by the child::
         parent_id = Column(Integer, ForeignKey('parent.id'))
 
 To establish a bidirectional relationship in one-to-many, where the "reverse"
-side is a many to one, specify the :paramref:`~.relationship.backref` option::
+side is a many to one, specify an additional :func:`.relationship` and connect
+the two using the :paramref:`.relationship.back_populates` parameter::
 
     class Parent(Base):
         __tablename__ = 'parent'
         id = Column(Integer, primary_key=True)
-        children = relationship("Child", backref="parent")
+        children = relationship("Child", back_populates="parent")
 
     class Child(Base):
         __tablename__ = 'child'
         id = Column(Integer, primary_key=True)
         parent_id = Column(Integer, ForeignKey('parent.id'))
+        parent = relationship("Parent", back_populates="children")
 
 ``Child`` will get a ``parent`` attribute with many-to-one semantics.
 
+Alternatively, the :paramref:`~.relationship.backref` option may be used
+on a single :func:`.relationship` instead of using
+:paramref:`~.relationship.back_populates`::
+
+    class Parent(Base):
+        __tablename__ = 'parent'
+        id = Column(Integer, primary_key=True)
+        children = relationship("Child", backref="parent")
+
+
 Many To One
 ~~~~~~~~~~~~
 
@@ -63,9 +75,23 @@ attribute will be created::
         __tablename__ = 'child'
         id = Column(Integer, primary_key=True)
 
-Bidirectional behavior is achieved by setting
-:paramref:`~.relationship.backref` to the value ``"parents"``, which
-will place a one-to-many collection on the ``Child`` class::
+Bidirectional behavior is achieved by adding a second :func:`.relationship`
+and applying the :paramref:`.relationship.back_populates` parameter
+in both directions::
+
+    class Parent(Base):
+        __tablename__ = 'parent'
+        id = Column(Integer, primary_key=True)
+        child_id = Column(Integer, ForeignKey('child.id'))
+        child = relationship("Child", back_populates="parents")
+
+    class Child(Base):
+        __tablename__ = 'child'
+        id = Column(Integer, primary_key=True)
+        parents = relationship("Parent", back_populates="child")
+
+Alternatively, the :paramref:`~.relationship.backref` parameter
+may be applied to a single :func:`.relationship`, such as ``Parent.child``::
 
     class Parent(Base):
         __tablename__ = 'parent'
@@ -86,25 +112,39 @@ of the relationship. To convert one-to-many into one-to-one::
     class Parent(Base):
         __tablename__ = 'parent'
         id = Column(Integer, primary_key=True)
-        child = relationship("Child", uselist=False, backref="parent")
+        child = relationship("Child", uselist=False, back_populates="parent")
 
     class Child(Base):
         __tablename__ = 'child'
         id = Column(Integer, primary_key=True)
         parent_id = Column(Integer, ForeignKey('parent.id'))
+        parent = relationship("Child", back_populates="child")
 
-Or to turn a one-to-many backref into one-to-one, use the :func:`.backref` function
-to provide arguments for the reverse side::
+Or for many-to-one::
 
     class Parent(Base):
         __tablename__ = 'parent'
         id = Column(Integer, primary_key=True)
         child_id = Column(Integer, ForeignKey('child.id'))
-        child = relationship("Child", backref=backref("parent", uselist=False))
+        child = relationship("Child", back_populates="parent")
 
     class Child(Base):
         __tablename__ = 'child'
         id = Column(Integer, primary_key=True)
+        parent = relationship("Child", back_populates="child", uselist=False)
+
+As always, the :paramref:`.relationship.backref` and :func:`.backref` functions
+may be used in lieu of the :paramref:`.relationship.back_populates` approach;
+to specify ``uselist`` on a backref, use the :func:`.backref` function::
+
+    from sqlalchemy.orm import backref
+
+    class Parent(Base):
+        __tablename__ = 'parent'
+        id = Column(Integer, primary_key=True)
+        child_id = Column(Integer, ForeignKey('child.id'))
+        child = relationship("Child", backref=backref("parent", uselist=False))
+
 
 .. _relationships_many_to_many:
 
@@ -133,7 +173,32 @@ directives can locate the remote tables with which to link::
         id = Column(Integer, primary_key=True)
 
 For a bidirectional relationship, both sides of the relationship contain a
-collection.  The :paramref:`~.relationship.backref` keyword will automatically use
+collection.  Specify using :paramref:`.relationship.back_populates`, and
+for each :func:`.relationship` specify the common association table::
+
+    association_table = Table('association', Base.metadata,
+        Column('left_id', Integer, ForeignKey('left.id')),
+        Column('right_id', Integer, ForeignKey('right.id'))
+    )
+
+    class Parent(Base):
+        __tablename__ = 'left'
+        id = Column(Integer, primary_key=True)
+        children = relationship(
+            "Child",
+            secondary=association_table,
+            back_populates="parents")
+
+    class Child(Base):
+        __tablename__ = 'right'
+        id = Column(Integer, primary_key=True)
+        parents = relationship(
+            "Parent",
+            secondary=association_table,
+            back_populates="children")
+
+When using the :paramref:`~.relationship.backref` parameter instead of
+:paramref:`.relationship.back_populates`, the backref will automatically use
 the same :paramref:`~.relationship.secondary` argument for the reverse relationship::
 
     association_table = Table('association', Base.metadata,
@@ -259,23 +324,26 @@ is stored along with each association between ``Parent`` and
         __tablename__ = 'right'
         id = Column(Integer, primary_key=True)
 
-The bidirectional version adds backrefs to both relationships::
+As always, the bidirectional version make use of :paramref:`.relationship.back_populates`
+or :paramref:`.relationship.backref`::
 
     class Association(Base):
         __tablename__ = 'association'
         left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
         right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
         extra_data = Column(String(50))
-        child = relationship("Child", backref="parent_assocs")
+        child = relationship("Child", back_populates="parents")
+        parent = relationship("Parent", back_populates="children")
 
     class Parent(Base):
         __tablename__ = 'left'
         id = Column(Integer, primary_key=True)
-        children = relationship("Association", backref="parent")
+        children = relationship("Association", back_populates="parent")
 
     class Child(Base):
         __tablename__ = 'right'
         id = Column(Integer, primary_key=True)
+        parents = relationship("Association", back_populates="child")
 
 Working with the association pattern in its direct form requires that child
 objects are associated with an association instance before being appended to
index 607d3a8924bc8d4d6e4176799b737a20c66fa319..53f161003814a5d901e4bc4d1b586d043bf86a93 100644 (file)
@@ -1101,7 +1101,7 @@ declarative, we define this table along with its mapped class, ``Address``:
 .. sourcecode:: python+sql
 
     >>> from sqlalchemy import ForeignKey
-    >>> from sqlalchemy.orm import relationship, backref
+    >>> from sqlalchemy.orm import relationship
 
     >>> class Address(Base):
     ...     __tablename__ = 'addresses'
@@ -1109,11 +1109,14 @@ declarative, we define this table along with its mapped class, ``Address``:
     ...     email_address = Column(String, nullable=False)
     ...     user_id = Column(Integer, ForeignKey('users.id'))
     ...
-    ...     user = relationship("User", backref=backref('addresses', order_by=id))
+    ...     user = relationship("User", back_populates="addresses")
     ...
     ...     def __repr__(self):
     ...         return "<Address(email_address='%s')>" % self.email_address
 
+    >>> User.addresses = relationship(
+    ...     "Address", order_by=Address.id, back_populates="user")
+
 The above class introduces the :class:`.ForeignKey` construct, which is a
 directive applied to :class:`.Column` that indicates that values in this
 column should be :term:`constrained` to be values present in the named remote
@@ -1129,11 +1132,27 @@ to the ``User`` class, using the attribute ``Address.user``.
 :func:`.relationship` uses the foreign key
 relationships between the two tables to determine the nature of
 this linkage, determining that ``Address.user`` will be :term:`many to one`.
-A subdirective of :func:`.relationship` called :func:`.backref` is
-placed inside of :func:`.relationship`, providing details about
-the relationship as expressed in reverse, that of a collection of ``Address``
-objects on ``User`` referenced by ``User.addresses``.  The reverse
-side of a many-to-one relationship is always :term:`one to many`.
+An additional :func:`.relationship` directive is placed on the
+``User`` mapped class under the attribute ``User.addresses``.  In both
+:func:`.relationship` directives, the parameter
+:paramref:`.relationship.back_populates` is assigned to refer to the
+complementary attribute names; by doing so, each :func:`.relationship`
+can make intelligent decision about the same relationship as expressed
+in reverse;  on one side, ``Address.user`` refers to a ``User`` instance,
+and on the other side, ``User.addresses`` refers to a list of
+``Address`` instances.
+
+.. note::
+
+    The :paramref:`.relationship.back_populates` parameter is a newer
+    version of a very common SQLAlchemy feature called
+    :paramref:`.relationship.backref`.  The :paramref:`.relationship.backref`
+    parameter hasn't gone anywhere and will always remain available!
+    The :paramref:`.relationship.back_populates` is the same thing, except
+    a little more verbose and easier to manipulate.  For an overview
+    of the entire topic, see the section :ref:`relationships_backref`.
+
+The reverse side of a many-to-one relationship is always :term:`one to many`.
 A full catalog of available :func:`.relationship` configurations
 is at :ref:`relationship_patterns`.
 
@@ -1148,13 +1167,7 @@ use.   Once all mappings are complete, these strings are evaluated
 as Python expressions in order to produce the actual argument, in the
 above case the ``User`` class.   The names which are allowed during
 this evaluation include, among other things, the names of all classes
-which have been created in terms of the declared base.  Below we illustrate creation
-of the same "addresses/user" bidirectional relationship in terms of ``User`` instead of
-``Address``::
-
-    class User(Base):
-        # ....
-        addresses = relationship("Address", order_by="Address.id", backref="user")
+which have been created in terms of the declared base.
 
 See the docstring for :func:`.relationship` for more detail on argument style.
 
@@ -1839,7 +1852,7 @@ including the cascade configuration (we'll leave the constructor out too)::
     ...     fullname = Column(String)
     ...     password = Column(String)
     ...
-    ...     addresses = relationship("Address", backref='user',
+    ...     addresses = relationship("Address", back_populates='user',
     ...                     cascade="all, delete, delete-orphan")
     ...
     ...     def __repr__(self):
@@ -1854,6 +1867,7 @@ the ``Address.user`` relationship via the ``User`` class already::
     ...     id = Column(Integer, primary_key=True)
     ...     email_address = Column(String, nullable=False)
     ...     user_id = Column(Integer, ForeignKey('users.id'))
+    ...     user = relationship("User", back_populates="addresses")
     ...
     ...     def __repr__(self):
     ...         return "<Address(email_address='%s')>" % self.email_address
@@ -1969,8 +1983,9 @@ each individual :class:`.Column` argument is separated by a comma.  The
 :class:`.Column` object is also given its name explicitly, rather than it being
 taken from an assigned attribute name.
 
-Next we define ``BlogPost`` and ``Keyword``, with a :func:`.relationship` linked
-via the ``post_keywords`` table::
+Next we define ``BlogPost`` and ``Keyword``, using complementary
+:func:`.relationship` constructs, each referring to the ``post_keywords``
+table as an association table::
 
     >>> class BlogPost(Base):
     ...     __tablename__ = 'posts'
@@ -1981,7 +1996,9 @@ via the ``post_keywords`` table::
     ...     body = Column(Text)
     ...
     ...     # many to many BlogPost<->Keyword
-    ...     keywords = relationship('Keyword', secondary=post_keywords, backref='posts')
+    ...     keywords = relationship('Keyword',
+    ...                             secondary=post_keywords,
+    ...                             back_populates='posts')
     ...
     ...     def __init__(self, headline, body, author):
     ...         self.author = author
@@ -1997,6 +2014,9 @@ via the ``post_keywords`` table::
     ...
     ...     id = Column(Integer, primary_key=True)
     ...     keyword = Column(String(50), nullable=False, unique=True)
+    ...     posts = relationship('BlogPost',
+    ...                          secondary=post_keywords,
+    ...                          back_populates='keywords')
     ...
     ...     def __init__(self, keyword):
     ...         self.keyword = keyword
@@ -2021,15 +2041,12 @@ that a single user might have lots of blog posts. When we access
 ``User.posts``, we'd like to be able to filter results further so as not to
 load the entire collection. For this we use a setting accepted by
 :func:`~sqlalchemy.orm.relationship` called ``lazy='dynamic'``, which
-configures an alternate **loader strategy** on the attribute. To use it on the
-"reverse" side of a :func:`~sqlalchemy.orm.relationship`, we use the
-:func:`~sqlalchemy.orm.backref` function:
+configures an alternate **loader strategy** on the attribute::
 
 .. sourcecode:: python+sql
 
-    >>> from sqlalchemy.orm import backref
-    >>> # "dynamic" loading relationship to User
-    >>> BlogPost.author = relationship(User, backref=backref('posts', lazy='dynamic'))
+    >>> BlogPost.author = relationship(User, back_populates="posts")
+    >>> User.posts = relationship(BlogPost, back_populates="author", lazy="dynamic")
 
 Create new tables:
 
index e02a271e32647ebcb2a73b03f5f59e143565108c..d9910a0705c8133d624f48d7f9dc414bed9198b0 100644 (file)
@@ -149,7 +149,12 @@ def backref(name, **kwargs):
         'items':relationship(
             SomeItem, backref=backref('parent', lazy='subquery'))
 
+    .. seealso::
+
+        :ref:`relationships_backref`
+
     """
+
     return (name, kwargs)