From: Mike Bayer Date: Tue, 29 Jun 2021 23:22:20 +0000 (-0400) Subject: rewrite "one-to-one", again X-Git-Tag: rel_1_4_21~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=67edf8e376d4bf4baf5944b88dff1cb073e9ef72;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git rewrite "one-to-one", again Use the term "collection" instead of "many" as the "side" where "uselist=False" goes. Clarify that one-to-one is in many ways a convention. Fixes: #6692 Change-Id: I2bc7b24c9f57747306fdcf4b6376ac7eb3bff78b --- diff --git a/doc/build/orm/basic_relationships.rst b/doc/build/orm/basic_relationships.rst index 5dc494a1e8..1f6ad67a65 100644 --- a/doc/build/orm/basic_relationships.rst +++ b/doc/build/orm/basic_relationships.rst @@ -127,57 +127,100 @@ One To One ~~~~~~~~~~ One To One is essentially a bidirectional relationship with a scalar -attribute on both sides. To achieve this, the :paramref:`_orm.relationship.uselist` flag -set to a value of ``False`` indicates -the placement of a scalar attribute instead of a collection on the "many" side -of the relationship. +attribute on both sides. Within the ORM, "one-to-one" is considered as a +convention where the ORM expects that only one related row will exist +for any parent row. -For example, to convert a :ref:`one-to-many ` -relationship into one-to-one, we add ``uselist=False`` to what would normally -be the "many" side of the relationship, renaming ``Parent.children`` to -``Parent.child`` for clarity:: +The "one-to-one" convention is achieved by applying a value of +``False`` to the :paramref:`_orm.relationship.uselist` parameter of the +:func:`_orm.relationship` construct, or in some cases the :func:`_orm.backref` +construct, applying it on the "one-to-many" or "collection" side of a +relationship. + +In the example below we present a bidirectional relationship that includes +both :ref:`one-to-many ` (``Parent.children``) and +a :ref:`many-to-one ` (``Child.parent``) +relationships:: class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) - # this was previously Parent.children - child = relationship("Child", back_populates="parent", uselist=False) + # one-to-many collection + 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="child") -Similarly, to convert a :ref:`many-to-one ` -relationship into one-to-one, we again apply ``uselist=False`` to the -"many" side, in the below example renaming ``Child.parents`` to ``Child.parent``:: + # many-to-one scalar + parent = relationship("Parent", back_populates="children") + +Above, ``Parent.children`` is the "one-to-many" side referring to a collection, +and ``Child.parent`` is the "many-to-one" side referring to a single object. +To convert this to "one-to-one", the "one-to-many" or "collection" side +is converted into a scalar relationship using the ``uselist=False`` flag, +renaming ``Parent.children`` to ``Parent.child`` for clarity:: class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) - child_id = Column(Integer, ForeignKey('child.id')) - child = relationship("Child", back_populates="parent") + + # previously one-to-many Parent.children is now + # one-to-one Parent.child + child = relationship("Child", back_populates="parent", uselist=False) class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) + parent_id = Column(Integer, ForeignKey('parent.id')) - # this was previously Child.parents - parent = relationship("Parent", back_populates="child", uselist=False) + # many-to-one side remains, see tip below + parent = relationship("Parent", back_populates="child") -As always, the :paramref:`_orm.relationship.backref` and :func:`.backref` functions -may be used in lieu of the :paramref:`_orm.relationship.back_populates` approach; -to specify ``uselist`` on a backref, use the :func:`.backref` function:: +Above, when we load a ``Parent`` object, the ``Parent.child`` attribute +will refer to a single ``Child`` object rather than a collection. If we +replace the value of ``Parent.child`` with a new ``Child`` object, the ORM's +unit of work process will replace the previous ``Child`` row with the new one, +setting the previous ``child.parent_id`` column to NULL by default unless there +are specific :ref:`cascade ` behaviors set up. + +.. tip:: + + As mentioned previously, the ORM considers the "one-to-one" pattern as a + convention, where it makes the assumption that when it loads the + ``Parent.child`` attribute on a ``Parent`` object, it will get only one + row back. If more than one row is returned, the ORM will emit a warning. + + However, the ``Child.parent`` side of the above relationship remains as a + "many-to-one" relationship and is unchanged, and there is no intrinsic system + within the ORM itself that prevents more than one ``Child`` object to be + created against the same ``Parent`` during persistence. Instead, techniques + such as :ref:`unique constraints ` may be used in + the actual database schema to enforce this arrangement, where a unique + constraint on the ``Child.parent_id`` column would ensure that only + one ``Child`` row may refer to a particular ``Parent`` row at a time. + + +In the case where the :paramref:`_orm.relationship.backref` +parameter is used to define the "one-to-many" side, this can be converted +to the "one-to-one" convention using the :func:`_orm.backref` +function which allows the relationship generated by the +:paramref:`_orm.relationship.backref` parameter to receive custom parameters, +in this case the ``uselist`` parameter:: 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)) + + class Child(Base): + __tablename__ = 'child' + id = Column(Integer, primary_key=True) + parent_id = Column(Integer, ForeignKey('parent.id')) + parent = relationship("Parent", backref=backref("child", uselist=False)) .. _relationships_many_to_many: