- 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
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:
continue
-
- 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):
continue
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):
self.logger.info("%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):
sessionlib.register_attribute(
mapper.class_,
self.key,
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=[
Address(email='one'),
Address(email='two'),
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
sess.add(s1)
sess.flush()
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__":
import testenv; testenv.configure_for_tests()
from testlib import sa, testing
from testlib.sa import MetaData, Table, Column, Integer, String, ForeignKey
-from testlib.sa.orm import mapper, relation, backref, create_session
+from testlib.sa.orm import mapper, relation, backref, create_session, class_mapper
from testlib.sa.orm import defer, deferred, synonym, attributes
from testlib.testing import eq_
import pickleable
exclude_properties=('vendor_id',))
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',) )
+
@testing.resolve_artifact_names
def test_mapping_to_join(self):
"""Mapping to a join"""