From 3954df86cb950aeb65444116c3e20f0a51647c10 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 22 Jan 2009 03:55:48 +0000 Subject: [PATCH] - Adjusted the attribute instrumentation change from 0.5.1 to fully establish instrumentation for subclasses where the mapper was created after the superclass had already been fully instrumented. [ticket:1292] --- CHANGES | 5 ++++ lib/sqlalchemy/orm/dynamic.py | 3 ++- lib/sqlalchemy/orm/interfaces.py | 40 +++++++++++++++++++++----------- lib/sqlalchemy/orm/mapper.py | 10 ++++++-- lib/sqlalchemy/orm/properties.py | 2 +- lib/sqlalchemy/orm/strategies.py | 31 +++++++++++++------------ test/orm/mapper.py | 10 +++++++- 7 files changed, 68 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index 81ef239da3..0328d44fc2 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,11 @@ CHANGES The relation will raise an error if multiple parent-association events occur within Python. + - Adjusted the attribute instrumentation change from 0.5.1 to + fully establish instrumentation for subclasses where the mapper + was created after the superclass had already been fully + instrumented. [ticket:1292] + - Fixed bug in delete-orphan cascade whereby two one-to-one relations from two different parent classes to the same target class would prematurely expunge the instance. diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index a46734dde8..0de5b98ff5 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -22,10 +22,11 @@ from sqlalchemy.orm.util import _state_has_identity, has_identity class DynaLoader(strategies.AbstractRelationLoader): - def init_class_attribute(self): + def init_class_attribute(self, mapper): self.is_class_level = True strategies._register_attribute(self, + mapper, useobject=True, impl_class=DynamicAttributeImpl, target_mapper=self.parent_property.mapper, diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 3e0fb94a40..6c3c3b1bae 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -395,23 +395,35 @@ class MapperProperty(object): def instrument_class(self, mapper): raise NotImplementedError() + _compile_started = False + _compile_finished = False + def init(self): - """Called after all mappers are compiled to assemble - relationships between mappers, establish instrumented class - attributes. + """Called after all mappers are created to assemble + relationships between mappers and perform other post-mapper-creation + initialization steps. + """ - - self._compiled = True + self._compile_started = True self.do_init() - + self._compile_finished = True + def do_init(self): - """Perform subclass-specific initialization steps. + """Perform subclass-specific initialization post-mapper-creation steps. This is a *template* method called by the - ``MapperProperty`` object's init() method.""" - + ``MapperProperty`` object's init() method. + + """ pass - + + def post_instrument_class(self, mapper): + """Perform instrumentation adjustments that need to occur + after init() has completed. + + """ + pass + def register_dependencies(self, *args, **kwargs): """Called by the ``Mapper`` in response to the UnitOfWork calling the ``Mapper``'s register_dependencies operation. @@ -573,9 +585,11 @@ class StrategizedProperty(MapperProperty): def do_init(self): self.__all_strategies = {} self.strategy = self.__init_strategy(self.strategy_class) - if self.is_primary(): - self.strategy.init_class_attribute() + def post_instrument_class(self, mapper): + if self.is_primary(): + self.strategy.init_class_attribute(mapper) + def build_path(entity, key, prev=None): if prev: return prev + (entity, key) @@ -810,7 +824,7 @@ class LoaderStrategy(object): def init(self): raise NotImplementedError("LoaderStrategy") - def init_class_attribute(self): + def init_class_attribute(self, mapper): pass def setup_query(self, context, entity, path, adapter, **kwargs): diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 6bcc89b3c2..7cc6bad125 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -620,6 +620,7 @@ class Mapper(object): if init: prop.init() + prop.post_instrument_class(self) def compile(self): @@ -684,9 +685,14 @@ class Mapper(object): self._log("_post_configure_properties() started") l = [(key, prop) for key, prop in self._props.iteritems()] for key, prop in l: - if not getattr(prop, '_compiled', False): - self._log("initialize prop " + key) + self._log("initialize prop " + key) + + if not prop._compile_started: prop.init() + + if prop._compile_finished: + prop.post_instrument_class(self) + self._log("_post_configure_properties() complete") self.compiled = True diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 22806e364a..f05613f5c0 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -711,6 +711,7 @@ class RelationProperty(StrategizedProperty): self._determine_direction() self._determine_local_remote_pairs() self._post_init() + super(RelationProperty, self).do_init() def _get_target(self): if not hasattr(self, 'mapper'): @@ -998,7 +999,6 @@ class RelationProperty(StrategizedProperty): "added to the primary mapper, i.e. the very first " "mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__)) - super(RelationProperty, self).do_init() def _refers_to_parent_table(self): return self.parent.mapped_table is self.target or self.parent.mapped_table is self.target diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 22ef7ded25..6edbd73d31 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -18,7 +18,7 @@ from sqlalchemy.orm.interfaces import ( from sqlalchemy.orm import session as sessionlib from sqlalchemy.orm import util as mapperutil -def _register_attribute(strategy, useobject, +def _register_attribute(strategy, mapper, useobject, compare_function=None, typecallable=None, copy_function=None, @@ -46,10 +46,10 @@ def _register_attribute(strategy, useobject, if useobject: attribute_ext.append(sessionlib.UOWEventHandler(prop.key)) - for mapper in prop.parent.polymorphic_iterator(): - if (mapper is prop.parent or not mapper.concrete) and mapper.has_property(prop.key): + for m in mapper.polymorphic_iterator(): + if (m is prop.parent or not m.concrete) and m.has_property(prop.key): attributes.register_attribute_impl( - mapper.class_, + m.class_, prop.key, parent_token=prop, mutable_scalars=mutable_scalars, @@ -98,12 +98,12 @@ class ColumnLoader(LoaderStrategy): c = adapter.columns[c] column_collection.append(c) - def init_class_attribute(self): + def init_class_attribute(self, mapper): self.is_class_level = True coltype = self.columns[0].type active_history = self.columns[0].primary_key # TODO: check all columns ? check for foreign Key as well? - _register_attribute(self, useobject=False, + _register_attribute(self, mapper, useobject=False, compare_function=coltype.compare_values, copy_function=coltype.copy_value, mutable_scalars=self.columns[0].type.is_mutable(), @@ -137,7 +137,7 @@ log.class_logger(ColumnLoader) class CompositeColumnLoader(ColumnLoader): """Strategize the loading of a composite column-based MapperProperty.""" - def init_class_attribute(self): + def init_class_attribute(self, mapper): self.is_class_level = True self.logger.info("%s register managed composite attribute" % self) @@ -158,7 +158,7 @@ class CompositeColumnLoader(ColumnLoader): else: return True - _register_attribute(self, useobject=False, + _register_attribute(self, mapper, useobject=False, compare_function=compare, copy_function=copy, mutable_scalars=True @@ -220,10 +220,10 @@ class DeferredColumnLoader(LoaderStrategy): self.columns = self.parent_property.columns self.group = self.parent_property.group - def init_class_attribute(self): + def init_class_attribute(self, mapper): self.is_class_level = True - _register_attribute(self, useobject=False, + _register_attribute(self, mapper, useobject=False, compare_function=self.columns[0].type.compare_values, copy_function=self.columns[0].type.copy_value, mutable_scalars=self.columns[0].type.is_mutable(), @@ -335,10 +335,10 @@ class AbstractRelationLoader(LoaderStrategy): class NoLoader(AbstractRelationLoader): """Strategize a relation() that doesn't load data automatically.""" - def init_class_attribute(self): + def init_class_attribute(self, mapper): self.is_class_level = True - _register_attribute(self, + _register_attribute(self, mapper, useobject=True, uselist=self.parent_property.uselist, typecallable = self.parent_property.collection_class, @@ -372,11 +372,12 @@ class LazyLoader(AbstractRelationLoader): if self.use_get: self.logger.info("%s will use query.get() to optimize instance loads" % self) - def init_class_attribute(self): + def init_class_attribute(self, mapper): self.is_class_level = True _register_attribute(self, + mapper, useobject=True, callable_=self.class_level_loader, uselist = self.parent_property.uselist, @@ -600,8 +601,8 @@ class EagerLoader(AbstractRelationLoader): super(EagerLoader, self).init() self.join_depth = self.parent_property.join_depth - def init_class_attribute(self): - self.parent_property._get_strategy(LazyLoader).init_class_attribute() + def init_class_attribute(self, mapper): + self.parent_property._get_strategy(LazyLoader).init_class_attribute(mapper) def setup_query(self, context, entity, path, adapter, column_collection=None, parentmapper=None, **kwargs): """Add a left outer join to the statement thats being constructed.""" diff --git a/test/orm/mapper.py b/test/orm/mapper.py index 72e417d26a..f1f5f4a472 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -210,6 +210,14 @@ class MapperTest(_fixtures.FixtureTest): mapper(Foo, addresses, inherits=User) assert getattr(Foo().__class__, 'name').impl is not None + @testing.resolve_artifact_names + def test_deferred_subclass_attribute_instrument(self): + class Foo(User):pass + mapper(User, users) + compile_mappers() + mapper(Foo, addresses, inherits=User) + assert getattr(Foo().__class__, 'name').impl is not None + @testing.resolve_artifact_names def test_compile_on_get_props_1(self): m =mapper(User, users) @@ -223,7 +231,7 @@ class MapperTest(_fixtures.FixtureTest): assert not m.compiled assert m.get_property('name') assert m.compiled - + @testing.resolve_artifact_names def test_add_property(self): assert_col = [] -- 2.47.2