Using the legacy ‘backref’ relationship parameter¶
Note
The relationship.backref
keyword should be considered
legacy, and use of relationship.back_populates
with explicit
relationship()
constructs should be preferred. Using
individual 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.
See also
For general information about bidirectional relationships, see the following sections:
Working with ORM Related Objects - in the SQLAlchemy 1.4 / 2.0 Tutorial,
presents an overview of bi-directional relationship configuration
and behaviors using relationship.back_populates
Controlling Cascade on Backrefs - notes on bi-directional relationship()
behavior regarding Session
cascade behaviors.
The relationship.backref
keyword argument on the
relationship()
construct allows the
automatic generation of a new relationship()
that will be automatically
be added to the ORM mapping for the related class. It will then be
placed into a relationship.back_populates
configuration
against the current relationship()
being configured, with both
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
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship("Address", backref="user")
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey("user.id"))
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. Using relationship.back_populates
it’s equivalent to the following:
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", 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", back_populates="addresses")
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 SQLAlchemy 1.4 / 2.0 Tutorial
at Working with ORM Related Objects.
Backref Default Arguments¶
Since relationship.backref
generates a whole new
relationship()
, the generation process by default
will attempt to include corresponding arguments in the new
relationship()
that correspond to the original arguments.
As an example, below is a relationship()
that includes a
custom join condition
which also includes the relationship.backref
keyword:
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'))"
),
backref="user",
)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey("user.id"))
When the “backref” is generated, the relationship.primaryjoin
condition is copied to the new relationship()
as well:
>>> print(User.addresses.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>
>>> print(Address.user.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>
Other arguments that are transferrable include the
relationship.secondary
parameter that refers to a
many-to-many association table, as well as the “join” arguments
relationship.primaryjoin
and
relationship.secondaryjoin
; “backref” is smart enough to know
that these two arguments should also be “reversed” when generating
the opposite side.
Specifying Backref Arguments¶
Lots of other arguments for a “backref” are not implicit, and
include arguments like
relationship.lazy
,
relationship.remote_side
,
relationship.cascade
and
relationship.cascade_backrefs
. For this case we use
the backref()
function in place of a string; this will store
a specific set of arguments that will be transferred to the new
relationship()
when generated:
# <other imports>
from sqlalchemy.orm import backref
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship(
"Address",
backref=backref("user", lazy="joined"),
)
Where above, we placed a lazy="joined"
directive only on the Address.user
side, indicating that when a query against Address
is made, a join to the User
entity should be made automatically which will populate the .user
attribute of each
returned Address
. The backref()
function formatted the arguments we gave
it into a form that is interpreted by the receiving relationship()
as additional
arguments to be applied to the new relationship it creates.