Inheritance Configuration

Declarative supports all three forms of inheritance as intuitivelyas possible. The inherits mapper keyword argument is not neededas declarative will determine this from the class itself. The various“polymorphic” keyword arguments are specified using mapper_args.

See also

This section describes some specific details on how the Declarative systeminteracts with SQLAlchemy ORM inheritance configuration. SeeMapping Class Inheritance Hierarchies for a general introduction to inheritancemapping.

Joined Table Inheritance

Joined table inheritance is defined as a subclass that defines its owntable:

  1. class Person(Base):
  2. __tablename__ = 'people'
  3. id = Column(Integer, primary_key=True)
  4. discriminator = Column('type', String(50))
  5. __mapper_args__ = {'polymorphic_on': discriminator}
  6.  
  7. class Engineer(Person):
  8. __tablename__ = 'engineers'
  9. __mapper_args__ = {'polymorphic_identity': 'engineer'}
  10. id = Column(Integer, ForeignKey('people.id'), primary_key=True)
  11. primary_language = Column(String(50))

Note that above, the Engineer.id attribute, since it shares thesame attribute name as the Person.id attribute, will in factrepresent the people.id and engineers.id columns together,with the “Engineer.id” column taking precedence if queried directly.To provide the Engineer class with an attribute that representsonly the engineers.id column, give it a different attribute name:

  1. class Engineer(Person):
  2. __tablename__ = 'engineers'
  3. __mapper_args__ = {'polymorphic_identity': 'engineer'}
  4. engineer_id = Column('id', Integer, ForeignKey('people.id'),
  5. primary_key=True)
  6. primary_language = Column(String(50))

Single Table Inheritance

Single table inheritance is defined as a subclass that does not haveits own table; you just leave out the table and tablenameattributes:

  1. class Person(Base):
  2. __tablename__ = 'people'
  3. id = Column(Integer, primary_key=True)
  4. discriminator = Column('type', String(50))
  5. __mapper_args__ = {'polymorphic_on': discriminator}
  6.  
  7. class Engineer(Person):
  8. __mapper_args__ = {'polymorphic_identity': 'engineer'}
  9. primary_language = Column(String(50))

When the above mappers are configured, the Person class is mappedto the people table before the primarylanguage column isdefined, and this column will not be included in its own mapping.When Engineer then defines the primary_language column, thecolumn is added to the people table so that it is included in themapping for Engineer and is also part of the table’s full set ofcolumns. Columns which are not mapped to Person are also excludedfrom any other single or joined inheriting classes using theexclude_properties mapper argument. Below, Manager will haveall the attributes of Person and Manager but _not theprimary_language attribute of Engineer:

  1. class Manager(Person):
  2. __mapper_args__ = {'polymorphic_identity': 'manager'}
  3. golf_swing = Column(String(50))

The attribute exclusion logic is provided by theexcludeproperties mapper argument, and declarative’s defaultbehavior can be disabled by passing an explicit excludepropertiescollection (empty or otherwise) to the __mapper_args.

Resolving Column Conflicts

Note above that the primarylanguage and golfswing columnsare “moved up” to be applied to Person._table, as a result of theirdeclaration on a subclass that has no table of its own. A tricky casecomes up when two subclasses want to specify _the same column, as below:

  1. class Person(Base):
  2. __tablename__ = 'people'
  3. id = Column(Integer, primary_key=True)
  4. discriminator = Column('type', String(50))
  5. __mapper_args__ = {'polymorphic_on': discriminator}
  6.  
  7. class Engineer(Person):
  8. __mapper_args__ = {'polymorphic_identity': 'engineer'}
  9. start_date = Column(DateTime)
  10.  
  11. class Manager(Person):
  12. __mapper_args__ = {'polymorphic_identity': 'manager'}
  13. start_date = Column(DateTime)

Above, the start_date column declared on both Engineer and Managerwill result in an error:

  1. sqlalchemy.exc.ArgumentError: Column 'start_date' on class
  2. <class '__main__.Manager'> conflicts with existing
  3. column 'people.start_date'

In a situation like this, Declarative can’t be sureof the intent, especially if the startdate columns had, for example,different types. A situation like this can be resolved by usingdeclared_attr to define the Column conditionally, takingcare to return the existing column via the parent _table if italready exists:

  1. from sqlalchemy.ext.declarative import declared_attr
  2.  
  3. class Person(Base):
  4. __tablename__ = 'people'
  5. id = Column(Integer, primary_key=True)
  6. discriminator = Column('type', String(50))
  7. __mapper_args__ = {'polymorphic_on': discriminator}
  8.  
  9. class Engineer(Person):
  10. __mapper_args__ = {'polymorphic_identity': 'engineer'}
  11.  
  12. @declared_attr
  13. def start_date(cls):
  14. "Start date column, if not present already."
  15. return Person.__table__.c.get('start_date', Column(DateTime))
  16.  
  17. class Manager(Person):
  18. __mapper_args__ = {'polymorphic_identity': 'manager'}
  19.  
  20. @declared_attr
  21. def start_date(cls):
  22. "Start date column, if not present already."
  23. return Person.__table__.c.get('start_date', Column(DateTime))

Above, when Manager is mapped, the start_date column isalready present on the Person class. Declarative lets us returnthat Column as a result in this case, where it knows to skipre-assigning the same column. If the mapping is mis-configured suchthat the start_date column is accidentally re-assigned to adifferent table (such as, if we changed Manager to be joinedinheritance without fixing start_date), an error is raised whichindicates an existing Column is trying to be re-assigned toa different owning Table.

The same concept can be used with mixin classes (seeMixin and Custom Base Classes):

  1. class Person(Base):
  2. __tablename__ = 'people'
  3. id = Column(Integer, primary_key=True)
  4. discriminator = Column('type', String(50))
  5. __mapper_args__ = {'polymorphic_on': discriminator}
  6.  
  7. class HasStartDate(object):
  8. @declared_attr
  9. def start_date(cls):
  10. return cls.__table__.c.get('start_date', Column(DateTime))
  11.  
  12. class Engineer(HasStartDate, Person):
  13. __mapper_args__ = {'polymorphic_identity': 'engineer'}
  14.  
  15. class Manager(HasStartDate, Person):
  16. __mapper_args__ = {'polymorphic_identity': 'manager'}

The above mixin checks the local table attribute for the column.Because we’re using single table inheritance, we’re sure that in this case,cls.table refers to Person.table. If we were mixing joined-and single-table inheritance, we might want our mixin to check more carefullyif cls.table is really the Table we’re looking for.

Concrete Table Inheritance

Concrete is defined as a subclass which has its own table and sets theconcrete keyword argument to True:

  1. class Person(Base):
  2. __tablename__ = 'people'
  3. id = Column(Integer, primary_key=True)
  4. name = Column(String(50))
  5.  
  6. class Engineer(Person):
  7. __tablename__ = 'engineers'
  8. __mapper_args__ = {'concrete':True}
  9. id = Column(Integer, primary_key=True)
  10. primary_language = Column(String(50))
  11. name = Column(String(50))

Usage of an abstract base class is a little less straightforward as itrequires usage of polymorphic_union(),which needs to be created with the Table objectsbefore the class is built:

  1. engineers = Table('engineers', Base.metadata,
  2. Column('id', Integer, primary_key=True),
  3. Column('name', String(50)),
  4. Column('primary_language', String(50))
  5. )
  6. managers = Table('managers', Base.metadata,
  7. Column('id', Integer, primary_key=True),
  8. Column('name', String(50)),
  9. Column('golf_swing', String(50))
  10. )
  11.  
  12. punion = polymorphic_union({
  13. 'engineer':engineers,
  14. 'manager':managers
  15. }, 'type', 'punion')
  16.  
  17. class Person(Base):
  18. __table__ = punion
  19. __mapper_args__ = {'polymorphic_on':punion.c.type}
  20.  
  21. class Engineer(Person):
  22. __table__ = engineers
  23. __mapper_args__ = {'polymorphic_identity':'engineer', 'concrete':True}
  24.  
  25. class Manager(Person):
  26. __table__ = managers
  27. __mapper_args__ = {'polymorphic_identity':'manager', 'concrete':True}

The helper classes AbstractConcreteBase and ConcreteBaseprovide automation for the above system of creating a polymorphic union.See the documentation for these helpers as well as the main ORM documentationon concrete inheritance for details.

See also

Concrete Table Inheritance

inheritance_concrete_helpers