abstract class inheritors not behaving properly

Issue #3636 closed
Daniel Kassen created an issue

Below is a snippet of code that defines a Parent class and a Child class which is abstract and used for inheritance. Classes that inherit from Child do not automatically populate if the entry exists in the database. By that, I mean the proper query is not performed to automatically pull that object out from the database even though it exists, causing duplicate entry errors when commit()'ing the session.

class Parent(Base):
    __tablename__ = 'parent'

    id = Column(Integer, primary_key=true)
    name = Column(String(100, collation='utf8_bin'),
                  nullable=False, unique=True)


class Child(Base):
        __abstract__ = True

        @classmethod
        def _classname(cls):
            return cls.__name__

        @delcared_attr
        def __tablename__(self):
            return self._classname().lower()

        @delcared_attr
        def id(self):
            return Column(Integer, ForeignKey(Parent.id), primary_key=True)

        @declared_attr
        def parent(self):
            return relationship(Parent)

Creating a factory that declares a class with the same attributes as are declared above in the Child class actually works, which is why this isn't very high priority, but it does make my code much less pythonic. Classes created from the factory below auto-populate as they should.

def create_child(name):
    class Child(Base):

        __tablename__ = name.lower()

        id = Column(Serial, ForeignKey(Parent.id), primary_key=True)

        parent = relationship(Parent)

    Child.__name__ = name
    return Child

Comments (3)

  1. Mike Bayer repo owner

    hi there -

    this issue does not make clear what the actual problem is. The phrase "the proper query is not performed to automatically pull that object out from the database even though it exists" is very vague and can refer to any number of different situations.

    Below illustrates your mapping (with typos corrected) , a sample "concrete" class extending from your abstract base, and two forms of a "query performed to automatically pull the object", a straight query() and then a refresh from an expiry, both of which succeed without issue.

    Please include exact code illustrating the specific use you're invoking as well as what unexpected results are occurring; see http://stackoverflow.com/help/mcve for guidance on what's needed - thanks!

    from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
    from sqlalchemy.orm import relationship, Session
    from sqlalchemy.ext.declarative import declarative_base, declared_attr
    
    Base = declarative_base()
    
    
    class Parent(Base):
        __tablename__ = 'parent'
    
        id = Column(Integer, primary_key=True)
        name = Column(String(100),  nullable=False, unique=True)
    
    
    class Child(Base):
            __abstract__ = True
    
            @classmethod
            def _classname(cls):
                return cls.__name__
    
            @declared_attr
            def __tablename__(self):
                return self._classname().lower()
    
            @declared_attr
            def id(self):
                return Column(Integer, ForeignKey(Parent.id), primary_key=True)
    
            @declared_attr
            def parent(self):
                return relationship(Parent)
    
    
    class ConcreteChild(Child):
        pass
    
    e = create_engine("sqlite://", echo=True)
    Base.metadata.create_all(e)
    
    s = Session(e)
    
    s.add(ConcreteChild(parent=Parent(name='some parent')))
    s.commit()
    s.close()
    
    s = Session(e)
    cc = s.query(ConcreteChild).first()
    assert cc.parent.name == 'some parent'
    
    s.expire_all()
    # two more SELECts emitted here
    assert cc.parent.name == 'some parent'
    
  2. Log in to comment