Mike Bayer  committed 5a0a1be

- An inheriting class can now override an attribute
inherited from the base class with a plain descriptor,
or exclude an inherited attribute via the
include_properties/exclude_properties collections.

  • Participants
  • Parent commits 874a649
  • Branches default

Comments (0)

Files changed (6)

     - A critical fix to dynamic relations allows the 
       "modified" history to be properly cleared after
       a flush().
+    - An inheriting class can now override an attribute
+      inherited from the base class with a plain descriptor, 
+      or exclude an inherited attribute via the 
+      include_properties/exclude_properties collections.
     - Added a new SessionExtension hook called after_attach().
       This is called at the point of attachment for objects

File lib/sqlalchemy/orm/

             o = o or bool(mapper.delete_orphans)
         return o
+    def has_property(self, key):
+        return key in self.__props
     def get_property(self, key, resolve_synonyms=False, raiseerr=True):
         """return a MapperProperty associated with the given key."""
             return getattr(getattr(cls, clskey), key)
+    def _should_exclude(self, name):
+        """determine whether a particular property should be implicitly present on the class.
+        This occurs when properties are propagated from an inherited class, or are 
+        applied from the columns present in the mapped table.
+        """
+        # check for an existing descriptor
+        if isinstance(
+            getattr(self.class_, name, None),
+            property):
+            return True
+        if (self.include_properties is not None and
+            name not in self.include_properties):
+            self.__log("not including property %s" % (name))
+            return True
+        if (self.exclude_properties is not None and
+            name in self.exclude_properties):
+            self.__log("excluding property %s" % (name))
+            return True
+        return False
     def __compile_properties(self):
         # object attribute names mapped to MapperProperty objects
         # pull properties from the inherited mapper if any.
         if self.inherits:
             for key, prop in self.inherits.__props.iteritems():
-                if key not in self.__props:
+                if key not in self.__props and not self._should_exclude(key):
                     self._adapt_inherited_property(key, prop)
         # create properties for each column in the mapped table,
         for column in self.mapped_table.columns:
             if column in self._columntoproperty:
-            if (self.include_properties is not None and
-                column.key not in self.include_properties):
-                self.__log("not including property %s" % (column.key))
-                continue
-            if (self.exclude_properties is not None and
-                column.key in self.exclude_properties):
-                self.__log("excluding property %s" % (column.key))
+            if self._should_exclude(column.key):
             column_key = (self.column_prefix or '') + column.key
         # in the 'with_polymorphic' selectable but we need it for the base mapper
         if self.polymorphic_on and self.polymorphic_on not in self._columntoproperty:
             col = self.mapped_table.corresponding_column(self.polymorphic_on) or self.polymorphic_on
+            if self._should_exclude(col.key):
+                raise sa_exc.InvalidRequestError("Cannot exclude or override the discriminator column %r" % col.key)
             self._compile_property(col.key, ColumnProperty(col), init=False, setparent=True)
     def _adapt_inherited_property(self, key, prop):

File lib/sqlalchemy/orm/
"%s register managed attribute" % self)
         for mapper in self.parent.polymorphic_iterator():
-            if mapper is self.parent or not mapper.concrete:
+            if (mapper is self.parent or not mapper.concrete) and mapper.has_property(self.key):

File test/ext/

         eq_(Address.__table__.c['id'].name, 'id')
         eq_(Address.__table__.c['_email'].name, 'email')
         eq_(Address.__table__.c['_user_id'].name, 'user_id')
         u1 = User(name='u1', addresses=[

File test/orm/inheritance/

         assert len(session.query(C).all()) == 1
 class OverrideColKeyTest(ORMTest):
-    """test overriding of column names with a common name from parent to child."""
+    """test overriding of column attributes."""
     def define_tables(self, metadata):
         global base, subtable
         assert sess.query(Sub).get(10) is s1
+    def test_plain_descriptor(self):
+        """test that descriptors prevent inheritance from propigating properties to subclasses."""
+        class Base(object):
+            pass
+        class Sub(Base):
+            @property
+            def data(self):
+                return "im the data"
+        mapper(Base, base)
+        mapper(Sub, subtable, inherits=Base)
+        s1 = Sub()
+        sess = create_session()
+        sess.add(s1)
+        sess.flush()
+        assert sess.query(Sub).one().data == "im the data"
 if __name__ == "__main__":

File test/orm/

 import testenv; testenv.configure_for_tests()
 from testlib import sa, testing
 from import MetaData, Table, Column, Integer, String, ForeignKey
-from import mapper, relation, backref, create_session
+from import mapper, relation, backref, create_session, class_mapper
 from import defer, deferred, synonym, attributes
 from testlib.testing import eq_
 import pickleable
         m_m = mapper(Manager, inherits=e_m, polymorphic_identity='manager',
-                     include_properties=())
+                     include_properties=('id', 'type'))
         v_m = mapper(Vendor, inherits=p_m, polymorphic_identity='vendor',
                      exclude_properties=('boss_id', 'employee_number'))
             want = set(want)
             eq_(have, want)
+        def assert_instrumented(cls, want):
+            have = set([p.key for p in class_mapper(cls).iterate_properties])
+            want = set(want)
+            eq_(have, want)
         assert_props(Person, ['id', 'name', 'type'])
+        assert_instrumented(Person, ['id', 'name', 'type'])
         assert_props(Employee, ['boss', 'boss_id', 'employee_number',
                                 'id', 'name', 'type'])
+        assert_instrumented(Employee,['boss', 'boss_id', 'employee_number',
+                                                        'id', 'name', 'type'])
         assert_props(Manager, ['boss', 'boss_id', 'employee_number', 'peon',
                                'id', 'name', 'type'])
+        # 'peon' and 'type' are both explicitly stated properties
+        assert_instrumented(Manager, ['peon', 'type', 'id'])
         assert_props(Vendor, ['vendor_id', 'id', 'name', 'type'])
         assert_props(Hoho, ['id', 'name', 'type'])
         assert_props(Lala, ['p_employee_number', 'p_id', 'p_name', 'p_type'])
+        # excluding the discriminator column is currently not allowed
+        class Foo(Person):
+            pass
+        self.assertRaises(sa.exc.InvalidRequestError, mapper, Foo, inherits=Person, polymorphic_identity='foo', exclude_properties=('type',) )
     def test_mapping_to_join(self):
         """Mapping to a join"""