@declared_attr.cascading doesn't work when inheritance of multiple levels

79 views
Skip to first unread message

niuji...@gmail.com

unread,
Nov 28, 2021, 4:24:00 AM11/28/21
to sqlalchemy
I'm using the "joined table inheritance" model. I have three levels of inheritance.

class has_polymorphic_id(object):
    @declared_attr.cascading
    def record_id(cls):
        if has_inherited_table(cls):
            return Column(ForeignKey('employee.record_id'),
                          primary_key=True)
        else:
            return Column(Integer, primary_key=True)


class Employee(has_polymorphic_id, Base):
    __tablename__ = 'employee'
    name = Column(String(50))
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Engineer(Employee):
    __tablename__ = 'engineer'
    ....

class Programmer(Engineer):
    __tablename__ = 'programmer'
    ....

This only works for the second level, namely `Enginner` can inherits the foreignkey/primarykey from `Employee`'s mixin, but the next level, the `Programmer`, python gives me an error:
`sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'engineer' and 'programmer'.`

Is this designed this way? And if I manually set the foreignkey, should the third level reference to the base level or to its immediate parent level's primarykey?

Mike Bayer

unread,
Nov 28, 2021, 11:25:30 AM11/28/21
to noreply-spamdigest via sqlalchemy
The "cascading" attribute seems to be working correctly.  The error here is because you aren't providing any column that will allow for a JOIN between the "programmer" and "engineer" table.

you would want Programmer.record_id to be a foreign key to Engineer.record_id, not Employee.record_id.    When you load Programmer rows, the join would be "FROM employee JOIN engineer ON <onclause> JOIN programmer ON <onclause>".



Is this designed this way? And if I manually set the foreignkey, should the third level reference to the base level or to its immediate parent level's primarykey?

it has to be to the immediate parent.  that's what the error message here is talking about.




--
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.

niuji...@gmail.com

unread,
Nov 28, 2021, 4:22:28 PM11/28/21
to sqlalchemy
I've just manually put this line to the `Programmer` class definition, but it still gives me the same error, strangely:


class Programmer(Engineer):
    __tablename__ = 'programmer'
    record_id = Column(ForeignKey('engineer.record_id'),
                       primary_key=True)
    ....

Mike Bayer

unread,
Nov 28, 2021, 9:37:30 PM11/28/21
to noreply-spamdigest via sqlalchemy
this is addressed in the docs which discuss "cascading" here:


"The declared_attr.cascading feature currently does not allow for a subclass to override the attribute with a different function or value. This is a current limitation in the mechanics of how @declared_attr is resolved, and a warning is emitted if this condition is detected. This limitation does not exist for the special attribute names such as __tablename__, which resolve in a different way internally than that of declared_attr.cascading."


you will see a warning in your program's output:

SAWarning: Attribute 'record_id' on class <class '__main__.Programmer'> cannot be processed due to @declared_attr.cascading; skipping


as a workaround, you can look for the attribute in __dict__ and return it, though you still get the warning:

    class has_polymorphic_id(object):
        @declared_attr.cascading
        def record_id(cls):
            if "record_id" in cls.__dict__:
                return cls.__dict__['record_id']
            elif has_inherited_table(cls):
                return Column(ForeignKey("employee.record_id"), primary_key=True)
            else:
                return Column(Integer, primary_key=True)


otherwise you'd want to look at "cls" with inspect(cls).local_table etc. and figure out the correct column to include in the FK if you are doing things this way.

niuji...@gmail.com

unread,
Nov 28, 2021, 11:38:49 PM11/28/21
to sqlalchemy
Thanks for pointing this out. It did address this problem already.
I just solved this by manually adding all the primary/foreign keys to each classes. Not a big deal, actually it helps clear code!

Mike Bayer

unread,
Nov 29, 2021, 9:39:35 AM11/29/21
to noreply-spamdigest via sqlalchemy
yeah I am not a huge fan of declared_attr.cascading except for maybe a table name convention
Reply all
Reply to author
Forward
0 new messages