From: Mike Bayer Date: Thu, 10 Dec 2015 23:27:14 +0000 (-0500) Subject: - convert ORM tutorial and basic_relationships to favor X-Git-Tag: rel_1_1_0b1~84^2~70^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=741b8af31bb436356b9e8950c045761a0e054fe0;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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 --- diff --git a/doc/build/orm/basic_relationships.rst b/doc/build/orm/basic_relationships.rst index 9a7ad4fa22..acb2dba011 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 607d3a8924..53f1610038 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 "" % 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 "" % 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 e02a271e32..d9910a0705 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)