extending the relationships api to ensure the intended uselist and back_populates match up

72 views
Skip to first unread message

Jonathan Vanasco

unread,
Feb 5, 2020, 4:22:02 PM2/5/20
to sqlalchemy
Does anyone know if there is an easy way to extend the relationships system so that I can specify the uselist I expect for back_populates relationships?

One of my projects has a complex model with a mix of one-to-one and one-to-many relationships that declare `uselist`.  I'd like to ensure I declare the correct `uselist` on each side of the relationship, and ensure an Exception is raised at runtime if one side is declared incorrectly.

as a use-case example, I'd like to do something like:



class Foo(Base):
    id
= Column(Integer, primary_key=True)
    bars
= relationship("Bar",
                        primaryjoin
="Foo.id==Bar.foo_id",
                        uselist
=True,
                        back_populates
="foo",
                        back_populates_uselist
=False,  # ensure the `uselist` on the `back_populates` relationship is False
                       
)


class Bar(Base):
    id
= Column(Integer, primary_key=True)
    foo_id
= Column(Integer)
    foo
= relationship("Foo",
                       primaryjoin
="Bar.foo_id==Foo.id",
                       uselist
=False,
                       back_populates
="bars",
                       back_populates_uselist
=True,  # ensure the `uselist` on the `back_populates` relationship is True
                       
)








Mike Bayer

unread,
Feb 5, 2020, 4:50:36 PM2/5/20
to noreply-spamdigest via sqlalchemy
what conditions would cause the "uselist" to be incorrect?
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

Jonathan Vanasco

unread,
Feb 5, 2020, 5:15:09 PM2/5/20
to sqlalchemy


On Wednesday, February 5, 2020 at 4:50:36 PM UTC-5, Mike Bayer wrote:
what conditions would cause the "uselist" to be incorrect?


If `Foo.bars` declares:
    uselist=True,
    back_populates_uselist=False

but `Bar.foo` declares:
    uselist=True,
    back_populates_uselist=True

I want to catch `Foo.bars` specified `back_populates_uselist=False`, but the back_populates relationship `Bar.foo` specified `uselist=True`.

i.e.. each side of the relationship agrees they are in a one-to-many or one-to-one relationship, instead of them independently defining only their side.



 

Mike Bayer

unread,
Feb 5, 2020, 5:58:30 PM2/5/20
to noreply-spamdigest via sqlalchemy
OK so you are explicitly worrying about a one-to-many relationship that should be one-to-one, or vice versa, that is, you aren't worried about FKs or remote_side setting up the entirely wrong M2O / O2M for a relationship.

However luckily, the only issue with any of this is having that argument available and fortunately it looks like I got around to adding info to relationship(), so you can check  / validate whatever you want, including if the "direction" got set up as expected or whatever other things you want to put into that info.  here's o2o:

from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import event
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session

Base = declarative_base()


class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)
    data = Column(String)
    b = relationship("B", back_populates="a", uselist=False)


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
    data = Column(String)
    a = relationship("A", back_populates="b", info={"one_to_one": True})


@event.listens_for(Base, "mapper_configured", propagate=True)
def _validate(mapper, cls):
    for rel in mapper.relationships:
        if "one_to_one" in rel.info:
            assert rel.mapper.attrs[rel.back_populates].uselist == (
                not rel.info["one_to_one"]
            )


e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)

s.add(A(b=B()))
s.commit()
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

Jonathan Vanasco

unread,
Feb 5, 2020, 6:45:25 PM2/5/20
to sqlalchemy


On Wednesday, February 5, 2020 at 5:58:30 PM UTC-5, Mike Bayer wrote:
OK so you are explicitly worrying about a one-to-many relationship that should be one-to-one, or vice versa, that is, you aren't worried about FKs or remote_side setting up the entirely wrong M2O / O2M for a relationship.

Exactly!

 
However luckily, the only issue with any of this is having that argument available and fortunately it looks like I got around to adding info to relationship(), so you can check  / validate whatever you want, including if the "direction" got set up as expected or whatever other things you want to put into that info.  here's o2o:

Awesome!  Thanks you so much, Mike! 

I had a feeling there was some way to leverage sqlalchemy's existing API and hooks/events to do this!  I just couldn't figure it out myself!
Reply all
Reply to author
Forward
0 new messages