summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-12-10 18:27:14 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2015-12-10 18:27:14 -0500
commit741b8af31bb436356b9e8950c045761a0e054fe0 (patch)
treef3f816cee909191edd1c2c7a67d643995eaef080
parentd533b8e9223b9c938655e5b666fc928e2d996cd3 (diff)
downloadsqlalchemy-741b8af31bb436356b9e8950c045761a0e054fe0.tar.gz
- convert ORM tutorial and basic_relationships to favor
back_populates while still maintaining great familiarity w/ backref so as not to confuse people. fixes #3390
-rw-r--r--doc/build/orm/basic_relationships.rst96
-rw-r--r--doc/build/orm/tutorial.rst65
-rw-r--r--lib/sqlalchemy/orm/__init__.py5
3 files changed, 128 insertions, 38 deletions
diff --git a/doc/build/orm/basic_relationships.rst b/doc/build/orm/basic_relationships.rst
index 9a7ad4fa2..acb2dba01 100644
--- a/doc/build/orm/basic_relationships.rst
+++ b/doc/build/orm/basic_relationships.rst
@@ -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
diff --git a/doc/build/orm/tutorial.rst b/doc/build/orm/tutorial.rst
index 607d3a892..53f161003 100644
--- a/doc/build/orm/tutorial.rst
+++ b/doc/build/orm/tutorial.rst
@@ -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:
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index e02a271e3..d9910a070 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -149,7 +149,12 @@ def backref(name, **kwargs):
'items':relationship(
SomeItem, backref=backref('parent', lazy='subquery'))
+ .. seealso::
+
+ :ref:`relationships_backref`
+
"""
+
return (name, kwargs)