Single table inheritance attributes are not hidden correctly when mixed with joined table inheritance

Issue #3895 resolved
Jack Zhou created an issue

When mixing single table inheritance and joined table inheritance like this:

class Person(Base):
    __tablename__ = "person"

    id = Column(Integer, primary_key=True)
    type = Column(String)

    __mapper_args__ = {
        "polymorphic_on": type,
    }


class Contractor(Person):
    contractor_field = Column(String)

    __mapper_args__ = {
        "polymorphic_identity": "contractor",
    }


class Employee(Person):
    __tablename__ = "employee"

    id = Column(Integer, ForeignKey(Person.id), primary_key=True)


class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

instances of Engineer has access to contractor_field:

try:
    Employee().contractor_field
except AttributeError:
    pass  # correctly raises
else:
    assert False
try:
    Engineer().contractor_field
except AttributeError:
    pass
else:
    assert False  # doesn't raise!

Comments (3)

  1. Mike Bayer repo owner

    it looks like this is handled in declarative

    diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
    index 16cb05e..7d97c1c 100644
    --- a/lib/sqlalchemy/ext/declarative/base.py
    +++ b/lib/sqlalchemy/ext/declarative/base.py
    @@ -492,8 +492,11 @@ class _MapperConfig(object):
    
                 if 'exclude_properties' not in mapper_args:
                     mapper_args['exclude_properties'] = exclude_properties = \
    -                    set([c.key for c in inherited_table.c
    -                         if c not in inherited_mapper._columntoproperty])
    +                    set(
    +                    [c.key for c in inherited_table.c
    +                     if c not in inherited_mapper._columntoproperty]
    +                ).union(inherited_mapper.exclude_properties or ())
    +
                     exclude_properties.difference_update(
                         [c.key for c in self.declared_columns])
    

    if mapping w/ classical mappings, the "contractor_field" will leak into Person if not added to exclude_properties explicitly because it needs to be in the Table that you map to Person, and there's no indication that contractor_field is only for Contractor and not Person otherwise, so the whole "properties are excluded automatically" thing is a declarative feature.

  2. Mike Bayer repo owner

    Union the exclude_properties of the inheriting mapper in declarative

    Fixed bug where the "automatic exclude" feature of declarative that ensures a column local to a single table inheritance subclass does not appear as an attribute on other derivations of the base would not take effect for multiple levels of subclassing from the base.

    Change-Id: Ibf67b631b4870dd1bd159f7d6085549d299fffe0 Fixes: #3895

    → <<cset 9a5943bf76cd>>

  3. Log in to comment