From: Mike Bayer Date: Fri, 17 Dec 2010 02:08:26 +0000 (-0500) Subject: - remove the need to use LoadDeferredColumns, LoadLazyAttribute in most cases, X-Git-Tag: rel_0_7b1~157^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f7f6fbede5b407b954ce20f9b55f7a02c4152aa9;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - remove the need to use LoadDeferredColumns, LoadLazyAttribute in most cases, these are back to being part of LoaderStrategy - simplify attribute.get() - inline the dict get inside of attribute.__get__() - revert some memoized attrs from InstanceState which are called in almost all cases regardless - inlining --- diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 2ffffc0345..e9f3763b19 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -23,6 +23,7 @@ mapperutil = util.importlater("sqlalchemy.orm", "util") PASSIVE_NO_RESULT = util.symbol('PASSIVE_NO_RESULT') ATTR_WAS_SET = util.symbol('ATTR_WAS_SET') +ATTR_EMPTY = util.symbol('ATTR_EMPTY') NO_VALUE = util.symbol('NO_VALUE') NEVER_SET = util.symbol('NEVER_SET') @@ -59,7 +60,7 @@ class QueryableAttribute(interfaces.PropComparator): self.impl = impl self.comparator = comparator self.parententity = parententity - + manager = manager_of_class(class_) # manager is None in the case of AliasedClass if manager: @@ -72,6 +73,10 @@ class QueryableAttribute(interfaces.PropComparator): dispatch = event.dispatcher(events.AttributeEvents) dispatch.dispatch_cls.active_history = False + @util.memoized_property + def _supports_population(self): + return self.impl.supports_population + def get_history(self, instance, **kwargs): return self.impl.get_history(instance_state(instance), instance_dict(instance), **kwargs) @@ -127,8 +132,12 @@ class InstrumentedAttribute(QueryableAttribute): def __get__(self, instance, owner): if instance is None: return self - return self.impl.get(instance_state(instance), - instance_dict(instance)) + + dict_ = instance_dict(instance) + if self._supports_population and self.key in dict_: + return dict_[self.key] + else: + return self.impl.get(instance_state(instance),dict_) def create_proxied_attribute(descriptor): """Create an QueryableAttribute / user descriptor hybrid. @@ -324,31 +333,37 @@ class AttributeImpl(object): resulting value will be set as the new value for this attribute. """ - try: + if self.key in dict_: return dict_[self.key] - except KeyError: - # if no history, check for lazy callables, etc. - if state.committed_state.get(self.key, NEVER_SET) is NEVER_SET: + else: + # if history present, don't load + key = self.key + if key not in state.committed_state or \ + state.committed_state[key] is NEVER_SET: if passive is PASSIVE_NO_INITIALIZE: return PASSIVE_NO_RESULT - if self.key in state.callables: - callable_ = state.callables[self.key] - elif self.callable_ is not None: - callable_ = self.callable_(state) + if key in state.callables: + callable_ = state.callables[key] + value = callable_(passive) + elif self.callable_: + value = self.callable_(state, passive) else: - callable_ = None - - if callable_ is not None: - value = callable_(passive=passive) - if value is PASSIVE_NO_RESULT: - return value - elif value is not ATTR_WAS_SET: - return self.set_committed_value(state, dict_, value) - else: - if self.key not in dict_: - return self.get(state, dict_, passive=passive) - return dict_[self.key] + value = ATTR_EMPTY + + if value is PASSIVE_NO_RESULT: + return value + elif value is ATTR_WAS_SET: + try: + return dict_[key] + except KeyError: + # TODO: no test coverage here. + raise KeyError( + "Deferred loader for attribute " + "%r failed to populate " + "correctly" % key) + elif value is not ATTR_EMPTY: + return self.set_committed_value(state, dict_, value) # Return a new, empty value return self.initialize(state, dict_) diff --git a/lib/sqlalchemy/orm/identity.py b/lib/sqlalchemy/orm/identity.py index 8604c00084..f1400a8c6b 100644 --- a/lib/sqlalchemy/orm/identity.py +++ b/lib/sqlalchemy/orm/identity.py @@ -121,17 +121,28 @@ class WeakInstanceDict(IdentityMap): dict.__setitem__(self, state.key, state) self._manage_incoming_state(state) - + def add(self, state): - if state.key in self: - if dict.__getitem__(self, state.key) is not state: - raise AssertionError("A conflicting state is already " - "present in the identity map for key %r" - % (state.key, )) - else: - dict.__setitem__(self, state.key, state) - self._manage_incoming_state(state) - + key = state.key + # inline of self.__contains__ + if dict.__contains__(self, key): + try: + existing_state = dict.__getitem__(self, key) + if existing_state is not state: + o = existing_state.obj() + if o is None: + o = existing_state._is_really_none() + if o is not None: + raise AssertionError("A conflicting state is already " + "present in the identity map for key %r" + % (key, )) + else: + return + except KeyError: + pass + dict.__setitem__(self, key, state) + self._manage_incoming_state(state) + def remove_key(self, key): state = dict.__getitem__(self, key) self.remove(state) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 58c2246364..9acb3485fd 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1922,7 +1922,7 @@ class Query(object): # TODO: no coverage here return attributes.PASSIVE_NO_RESULT try: - state() + state(passive) except orm_exc.ObjectDeletedError: session._remove_newly_deleted(state) return None diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 48861085d3..392d83d948 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -36,10 +36,8 @@ class InstanceState(object): self.class_ = obj.__class__ self.manager = manager self.obj = weakref.ref(obj, self._cleanup) - - @util.memoized_property - def committed_state(self): - return {} + self.callables = {} + self.committed_state = {} @util.memoized_property def parents(self): @@ -49,10 +47,6 @@ class InstanceState(object): def pending(self): return {} - @util.memoized_property - def callables(self): - return {} - @property def has_identity(self): return bool(self.key) @@ -75,10 +69,18 @@ class InstanceState(object): instance_dict.remove(self) except AssertionError: pass + # remove possible cycles - self.__dict__.pop('callables', None) - self.dispose() - + self.callables.clear() + + # inlining of self.dispose() + if self.session_id: + try: + del self.session_id + except AttributeError: + pass + del self.obj + def obj(self): return None @@ -251,11 +253,8 @@ class InstanceState(object): else: filter_deferred = False - to_clear = ( - self.__dict__.get('pending', None), - self.__dict__.get('committed_state', None), - self.mutable_dict - ) + pending = self.__dict__.get('pending', None) + mutable_dict = self.mutable_dict for key in attribute_names: impl = self.manager[key].impl @@ -264,18 +263,20 @@ class InstanceState(object): self.callables[key] = self dict_.pop(key, None) - for d in to_clear: - if d is not None: - d.pop(key, None) + self.committed_state.pop(key, None) + if mutable_dict: + mutable_dict.pop(key, None) + if pending: + pending.pop(key, None) - def __call__(self, **kw): + def __call__(self, passive): """__call__ allows the InstanceState to act as a deferred callable for loading expired attributes, which is also serializable (picklable). """ - if kw.get('passive') is PASSIVE_NO_FETCH: + if passive is PASSIVE_NO_FETCH: return PASSIVE_NO_RESULT toload = self.expired_attributes.\ @@ -407,16 +408,15 @@ class InstanceState(object): if a value was not populated in state.dict. """ - - self.__dict__.pop('committed_state', None) - self.__dict__.pop('pending', None) - if 'callables' in self.__dict__: - callables = self.callables - for key in list(callables): - if key in dict_ and callables[key] is self: - del callables[key] + self.committed_state.clear() + self.__dict__.pop('pending', None) + callables = self.callables + for key in list(callables): + if key in dict_ and callables[key] is self: + del callables[key] + for key in self.manager.mutable_attributes: if key in dict_: self.committed_state[key] = self.manager[key].impl.copy(dict_[key]) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index ab7a181a77..c4619d3a72 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -230,7 +230,7 @@ class DeferredColumnLoader(LoaderStrategy): compare_function=self.columns[0].type.compare_values, copy_function=self.columns[0].type.copy_value, mutable_scalars=self.columns[0].type.is_mutable(), - callable_=self._class_level_loader, + callable_=self._load_for_state, expire_missing=False ) @@ -244,51 +244,26 @@ class DeferredColumnLoader(LoaderStrategy): setup_query(context, entity, path, adapter, **kwargs) - def _class_level_loader(self, state): + def _load_for_state(self, state, passive): if not state.key: - return None - - return LoadDeferredColumns(state, self.key) - - -log.class_logger(DeferredColumnLoader) - -class LoadDeferredColumns(object): - """serializable loader object used by DeferredColumnLoader""" + return attributes.ATTR_EMPTY - __slots__ = 'state', 'key' - - def __init__(self, state, key): - self.state = state - self.key = key - - def __getstate__(self): - return self.state, self.key - - def __setstate__(self, state): - self.state, self.key = state - - def __call__(self, passive=False): - state, key = self.state, self.key - if passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT - - localparent = mapper._state_mapper(state) + + prop = self.parent_property + localparent = state.manager.mapper - prop = localparent._props[key] - strategy = prop._get_strategy(DeferredColumnLoader) - - if strategy.group: + if self.group: toload = [ p.key for p in localparent.iterate_properties if isinstance(p, StrategizedProperty) and isinstance(p.strategy, DeferredColumnLoader) and - p.group==strategy.group + p.group==self.group ] else: - toload = [key] + toload = [self.key] # narrow the keys down to just those which have no history group = [k for k in toload if k in state.unmodified] @@ -298,13 +273,30 @@ class LoadDeferredColumns(object): raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session; " "deferred load operation of attribute '%s' cannot proceed" % - (mapperutil.state_str(state), key) + (mapperutil.state_str(state), self.key) ) query = session.query(localparent) query._load_on_ident(state.key, only_load_props=group, refresh_state=state) return attributes.ATTR_WAS_SET + +log.class_logger(DeferredColumnLoader) + +class LoadDeferredColumns(object): + """serializable loader object used by DeferredColumnLoader""" + + def __init__(self, state, key): + self.state = state + self.key = key + + def __call__(self, passive=False): + state, key = self.state, self.key + + localparent = state.manager.mapper + prop = localparent._props[key] + strategy = prop._strategies[DeferredColumnLoader] + return strategy._load_for_state(state, passive) class DeferredOption(StrategizedOption): propagate_to_loaders = True @@ -398,7 +390,7 @@ class LazyLoader(AbstractRelationshipLoader): _register_attribute(self, mapper, useobject=True, - callable_=self._class_level_loader, + callable_=self._load_for_state, uselist = self.parent_property.uselist, backref = self.parent_property.back_populates, typecallable = self.parent_property.collection_class, @@ -483,112 +475,20 @@ class LazyLoader(AbstractRelationshipLoader): criterion = adapt_source(criterion) return criterion - def _class_level_loader(self, state): + def _load_for_state(self, state, passive): if not state.key and \ (not self.parent_property.load_on_pending or not state.session_id): - return None - - return LoadLazyAttribute(state, self.key) - - def create_row_processor(self, selectcontext, path, mapper, row, adapter): - key = self.key - if not self.is_class_level: - def new_execute(state, dict_, row): - # we are not the primary manager for this attribute - # on this class - set up a - # per-instance lazyloader, which will override the - # class-level behavior. - # this currently only happens when using a - # "lazyload" option on a "no load" - # attribute - "eager" attributes always have a - # class-level lazyloader installed. - state.set_callable(dict_, key, LoadLazyAttribute(state, key)) - else: - def new_execute(state, dict_, row): - # we are the primary manager for this attribute on - # this class - reset its - # per-instance attribute state, so that the class-level - # lazy loader is - # executed when next referenced on this instance. - # this is needed in - # populate_existing() types of scenarios to reset - # any existing state. - state.reset(dict_, key) - - return new_execute, None, None - - @classmethod - def _create_lazy_clause(cls, prop, reverse_direction=False): - binds = util.column_dict() - lookup = util.column_dict() - equated_columns = util.column_dict() - - if reverse_direction and prop.secondaryjoin is None: - for l, r in prop.local_remote_pairs: - _list = lookup.setdefault(r, []) - _list.append((r, l)) - equated_columns[l] = r - else: - for l, r in prop.local_remote_pairs: - _list = lookup.setdefault(l, []) - _list.append((l, r)) - equated_columns[r] = l - - def col_to_bind(col): - if col in lookup: - for tobind, equated in lookup[col]: - if equated in binds: - return None - if col not in binds: - binds[col] = sql.bindparam(None, None, type_=col.type) - return binds[col] - return None + return attributes.ATTR_EMPTY - lazywhere = prop.primaryjoin - - if prop.secondaryjoin is None or not reverse_direction: - lazywhere = visitors.replacement_traverse( - lazywhere, {}, col_to_bind) - - if prop.secondaryjoin is not None: - secondaryjoin = prop.secondaryjoin - if reverse_direction: - secondaryjoin = visitors.replacement_traverse( - secondaryjoin, {}, col_to_bind) - lazywhere = sql.and_(lazywhere, secondaryjoin) - - bind_to_col = dict((binds[col].key, col) for col in binds) - - return lazywhere, bind_to_col, equated_columns - -log.class_logger(LazyLoader) - -class LoadLazyAttribute(object): - """serializable loader object used by LazyLoader""" - - __slots__ = 'state', 'key' - - def __init__(self, state, key): - self.state = state - self.key = key - - def __getstate__(self): - return self.state, self.key - - def __setstate__(self, state): - self.state, self.key = state - - def __call__(self, passive=False): - state, key = self.state, self.key instance_mapper = state.manager.mapper - prop = instance_mapper._props[key] - prop_mapper = prop.mapper - strategy = prop._strategies[LazyLoader] + prop = self.parent_property + key = self.key + prop_mapper = self.mapper pending = not state.key if ( passive is attributes.PASSIVE_NO_FETCH and - not strategy.use_get + not self.use_get ) or ( passive is attributes.PASSIVE_ONLY_PERSISTENT and pending @@ -605,7 +505,7 @@ class LoadLazyAttribute(object): # if we have a simple primary key load, check the # identity map without generating a Query at all - if strategy.use_get: + if self.use_get: if session._flushing: get_attr = instance_mapper._get_committed_state_attr_by_column else: @@ -616,7 +516,7 @@ class LoadLazyAttribute(object): get_attr( state, state.dict, - strategy._equated_columns[pk], + self._equated_columns[pk], passive=passive) for pk in prop_mapper.primary_key ] @@ -645,7 +545,7 @@ class LoadLazyAttribute(object): if state.load_options: q = q._conditional_options(*state.load_options) - if strategy.use_get: + if self.use_get: return q._load_on_ident(ident_key) if prop.order_by: @@ -659,7 +559,7 @@ class LoadLazyAttribute(object): not isinstance(rev.strategy, LazyLoader): q = q.options(EagerLazyOption((rev.key,), lazy='select')) - lazy_clause = strategy.lazy_clause(state) + lazy_clause = self.lazy_clause(state) if pending: bind_values = sql_util.bind_values(lazy_clause) @@ -669,7 +569,7 @@ class LoadLazyAttribute(object): q = q.filter(lazy_clause) result = q.all() - if strategy.uselist: + if self.uselist: return result else: l = len(result) @@ -684,6 +584,95 @@ class LoadLazyAttribute(object): else: return None + def create_row_processor(self, selectcontext, path, mapper, row, adapter): + key = self.key + if not self.is_class_level: + def new_execute(state, dict_, row): + # we are not the primary manager for this attribute + # on this class - set up a + # per-instance lazyloader, which will override the + # class-level behavior. + # this currently only happens when using a + # "lazyload" option on a "no load" + # attribute - "eager" attributes always have a + # class-level lazyloader installed. + state.set_callable(dict_, key, LoadLazyAttribute(state, key)) + else: + def new_execute(state, dict_, row): + # we are the primary manager for this attribute on + # this class - reset its + # per-instance attribute state, so that the class-level + # lazy loader is + # executed when next referenced on this instance. + # this is needed in + # populate_existing() types of scenarios to reset + # any existing state. + state.reset(dict_, key) + + return new_execute, None, None + + @classmethod + def _create_lazy_clause(cls, prop, reverse_direction=False): + binds = util.column_dict() + lookup = util.column_dict() + equated_columns = util.column_dict() + + if reverse_direction and prop.secondaryjoin is None: + for l, r in prop.local_remote_pairs: + _list = lookup.setdefault(r, []) + _list.append((r, l)) + equated_columns[l] = r + else: + for l, r in prop.local_remote_pairs: + _list = lookup.setdefault(l, []) + _list.append((l, r)) + equated_columns[r] = l + + def col_to_bind(col): + if col in lookup: + for tobind, equated in lookup[col]: + if equated in binds: + return None + if col not in binds: + binds[col] = sql.bindparam(None, None, type_=col.type) + return binds[col] + return None + + lazywhere = prop.primaryjoin + + if prop.secondaryjoin is None or not reverse_direction: + lazywhere = visitors.replacement_traverse( + lazywhere, {}, col_to_bind) + + if prop.secondaryjoin is not None: + secondaryjoin = prop.secondaryjoin + if reverse_direction: + secondaryjoin = visitors.replacement_traverse( + secondaryjoin, {}, col_to_bind) + lazywhere = sql.and_(lazywhere, secondaryjoin) + + bind_to_col = dict((binds[col].key, col) for col in binds) + + return lazywhere, bind_to_col, equated_columns + +log.class_logger(LazyLoader) + +class LoadLazyAttribute(object): + """serializable loader object used by LazyLoader""" + + def __init__(self, state, key): + self.state = state + self.key = key + + def __call__(self, passive=False): + state, key = self.state, self.key + instance_mapper = state.manager.mapper + prop = instance_mapper._props[key] + strategy = prop._strategies[LazyLoader] + + return strategy._load_for_state(state, passive) + + class ImmediateLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index 71a5153e6a..2995c35642 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -172,7 +172,7 @@ class LoadManyToOneFromIdentityTest(_base.MappedTest): parents = sess.query(Parent).all() children = sess.query(Child).all() - @profiling.function_call_count(23979, {'2.5':28974, '3':25978}) + @profiling.function_call_count(17987, {'3':20978}) def go(): for p in parents: p.child diff --git a/test/orm/test_attributes.py b/test/orm/test_attributes.py index b543e79a12..91f61c05c0 100644 --- a/test/orm/test_attributes.py +++ b/test/orm/test_attributes.py @@ -49,16 +49,28 @@ class AttributesTest(_base.ORMTest): def test_pickleness(self): instrumentation.register_class(MyTest) instrumentation.register_class(MyTest2) - attributes.register_attribute(MyTest, 'user_id', uselist=False, useobject=False) - attributes.register_attribute(MyTest, 'user_name', uselist=False, useobject=False) - attributes.register_attribute(MyTest, 'email_address', uselist=False, useobject=False) - attributes.register_attribute(MyTest, 'some_mutable_data', mutable_scalars=True, copy_function=list, compare_function=cmp, uselist=False, useobject=False) - attributes.register_attribute(MyTest2, 'a', uselist=False, useobject=False) - attributes.register_attribute(MyTest2, 'b', uselist=False, useobject=False) + attributes.register_attribute(MyTest, 'user_id', uselist=False, + useobject=False) + attributes.register_attribute(MyTest, 'user_name', + uselist=False, useobject=False) + attributes.register_attribute(MyTest, 'email_address', + uselist=False, useobject=False) + attributes.register_attribute(MyTest, 'some_mutable_data', + mutable_scalars=True, copy_function=list, + compare_function=cmp, uselist=False, useobject=False) + attributes.register_attribute(MyTest2, 'a', uselist=False, + useobject=False) + attributes.register_attribute(MyTest2, 'b', uselist=False, + useobject=False) + # shouldnt be pickling callables at the class level - def somecallable(*args, **kw): + + def somecallable(state, passive): return None - attributes.register_attribute(MyTest, "mt2", uselist = True, trackparent=True, callable_=somecallable, useobject=True) + + attributes.register_attribute(MyTest, 'mt2', uselist=True, + trackparent=True, callable_=somecallable, + useobject=True) o = MyTest() o.mt2.append(MyTest2()) @@ -259,26 +271,26 @@ class AttributesTest(_base.ORMTest): b1, b2, b3, b4 = Bar(id='b1'), Bar(id='b2'), Bar(id='b3'), Bar(id='b4') - def loadcollection(**kw): - if kw.get('passive') is attributes.PASSIVE_NO_FETCH: + def loadcollection(state, passive): + if passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT return [b1, b2] - def loadscalar(**kw): - if kw.get('passive') is attributes.PASSIVE_NO_FETCH: + def loadscalar(state, passive): + if passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT return b2 attributes.register_attribute(Foo, 'bars', uselist=True, useobject=True, - callable_=lambda o:loadcollection, + callable_=loadcollection, extension=[ReceiveEvents('bars')]) attributes.register_attribute(Foo, 'bar', uselist=False, useobject=True, - callable_=lambda o:loadscalar, + callable_=loadscalar, extension=[ReceiveEvents('bar')]) attributes.register_attribute(Foo, 'scalar', @@ -341,14 +353,17 @@ class AttributesTest(_base.ORMTest): instrumentation.register_class(Bar) bar1, bar2, bar3 = [Bar(id=1), Bar(id=2), Bar(id=3)] - def func1(**kw): - if kw.get('passive') is attributes.PASSIVE_NO_FETCH: + def func1(state, passive): + if passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT return [bar1, bar2, bar3] - attributes.register_attribute(Foo, 'bars', uselist=True, callable_=lambda o:func1, useobject=True, extension=[ReceiveEvents()]) - attributes.register_attribute(Bar, 'foos', uselist=True, useobject=True, backref="bars") + attributes.register_attribute(Foo, 'bars', uselist=True, + callable_=func1, useobject=True, + extension=[ReceiveEvents()]) + attributes.register_attribute(Bar, 'foos', uselist=True, + useobject=True, backref='bars') x = Foo() assert_raises(AssertionError, Bar(id=4).foos.append, x) @@ -423,9 +438,9 @@ class AttributesTest(_base.ORMTest): b = Blog() p1 = Post() attributes.instance_state(b).set_callable(attributes.instance_dict(b), - 'posts', lambda **kw:[p1]) + 'posts', lambda passive:[p1]) attributes.instance_state(p1).set_callable(attributes.instance_dict(p1), - 'blog', lambda **kw:b) + 'blog', lambda passive:b) p1, attributes.instance_state(b).commit_all(attributes.instance_dict(b)) # no orphans (called before the lazy loaders fire off) @@ -452,18 +467,18 @@ class AttributesTest(_base.ORMTest): instrumentation.register_class(Foo) instrumentation.register_class(Bar) - def func1(**kw): + def func1(state, passive): return "this is the foo attr" - def func2(**kw): + def func2(state, passive): return "this is the bar attr" - def func3(**kw): + def func3(state, passive): return "this is the shared attr" attributes.register_attribute(Foo, 'element', uselist=False, - callable_=lambda o:func1, useobject=True) + callable_=func1, useobject=True) attributes.register_attribute(Foo, 'element2', uselist=False, - callable_=lambda o:func3, useobject=True) + callable_=func3, useobject=True) attributes.register_attribute(Bar, 'element', uselist=False, - callable_=lambda o:func2, useobject=True) + callable_=func2, useobject=True) x = Foo() y = Bar() @@ -525,14 +540,17 @@ class AttributesTest(_base.ORMTest): instrumentation.register_class(Bar) bar1, bar2, bar3, bar4 = [Bar(id=1), Bar(id=2), Bar(id=3), Bar(id=4)] - def func1(**kw): + def func1(state, passive): return "this is func 1" - def func2(**kw): + def func2(state, passive): return [bar1, bar2, bar3] - attributes.register_attribute(Foo, 'col1', uselist=False, callable_=lambda o:func1, useobject=True) - attributes.register_attribute(Foo, 'col2', uselist=True, callable_=lambda o:func2, useobject=True) - attributes.register_attribute(Bar, 'id', uselist=False, useobject=True) + attributes.register_attribute(Foo, 'col1', uselist=False, + callable_=func1, useobject=True) + attributes.register_attribute(Foo, 'col2', uselist=True, + callable_=func2, useobject=True) + attributes.register_attribute(Bar, 'id', uselist=False, + useobject=True) x = Foo() attributes.instance_state(x).commit_all(attributes.instance_dict(x)) @@ -864,9 +882,6 @@ class BackrefTest(_base.ORMTest): # and this condition changes. assert c1 in p1.children - - - class PendingBackrefTest(_base.ORMTest): def setup(self): global Post, Blog, called, lazy_load @@ -888,19 +903,20 @@ class PendingBackrefTest(_base.ORMTest): called = [0] lazy_load = [] - def lazy_posts(instance): - def load(**kw): - if kw['passive'] is not attributes.PASSIVE_NO_FETCH: - called[0] += 1 - return lazy_load - else: - return attributes.PASSIVE_NO_RESULT - return load + def lazy_posts(state, passive): + if passive is not attributes.PASSIVE_NO_FETCH: + called[0] += 1 + return lazy_load + else: + return attributes.PASSIVE_NO_RESULT instrumentation.register_class(Post) instrumentation.register_class(Blog) - attributes.register_attribute(Post, 'blog', uselist=False, backref='posts', trackparent=True, useobject=True) - attributes.register_attribute(Blog, 'posts', uselist=True, backref='blog', callable_=lazy_posts, trackparent=True, useobject=True) + attributes.register_attribute(Post, 'blog', uselist=False, + backref='posts', trackparent=True, useobject=True) + attributes.register_attribute(Blog, 'posts', uselist=True, + backref='blog', callable_=lazy_posts, trackparent=True, + useobject=True) def test_lazy_add(self): global lazy_load @@ -1384,15 +1400,16 @@ class HistoryTest(_base.ORMTest): pass lazy_load = [] - def lazyload(instance): - def load(**kw): - return lazy_load - return load + def lazyload(state, passive): + return lazy_load instrumentation.register_class(Foo) instrumentation.register_class(Bar) - attributes.register_attribute(Foo, 'bars', uselist=True, backref='foo', trackparent=True, callable_=lazyload, useobject=True) - attributes.register_attribute(Bar, 'foo', uselist=False, backref='bars', trackparent=True, useobject=True) + attributes.register_attribute(Foo, 'bars', uselist=True, + backref='foo', trackparent=True, callable_=lazyload, + useobject=True) + attributes.register_attribute(Bar, 'foo', uselist=False, + backref='bars', trackparent=True, useobject=True) bar1, bar2, bar3, bar4 = [Bar(id=1), Bar(id=2), Bar(id=3), Bar(id=4)] lazy_load = [bar1, bar2, bar3] @@ -1419,14 +1436,13 @@ class HistoryTest(_base.ORMTest): pass lazy_load = [] - def lazyload(instance): - def load(**kw): - return lazy_load - return load + def lazyload(state, passive): + return lazy_load instrumentation.register_class(Foo) instrumentation.register_class(Bar) - attributes.register_attribute(Foo, 'bars', uselist=True, callable_=lazyload, trackparent=True, useobject=True) + attributes.register_attribute(Foo, 'bars', uselist=True, + callable_=lazyload, trackparent=True, useobject=True) bar1, bar2, bar3, bar4 = [Bar(id=1), Bar(id=2), Bar(id=3), Bar(id=4)] lazy_load = [bar1, bar2, bar3] @@ -1459,14 +1475,13 @@ class HistoryTest(_base.ORMTest): pass lazy_load = None - def lazyload(instance): - def load(**kw): - return lazy_load - return load + def lazyload(state, passive): + return lazy_load instrumentation.register_class(Foo) - attributes.register_attribute(Foo, 'bar', uselist=False, callable_=lazyload, useobject=False) - lazy_load = "hi" + attributes.register_attribute(Foo, 'bar', uselist=False, + callable_=lazyload, useobject=False) + lazy_load = 'hi' # with scalar non-object and active_history=False, the lazy callable is only executed on gets, not history # operations @@ -1497,14 +1512,14 @@ class HistoryTest(_base.ORMTest): pass lazy_load = None - def lazyload(instance): - def load(**kw): - return lazy_load - return load + def lazyload(state, passive): + return lazy_load instrumentation.register_class(Foo) - attributes.register_attribute(Foo, 'bar', uselist=False, callable_=lazyload, useobject=False, active_history=True) - lazy_load = "hi" + attributes.register_attribute(Foo, 'bar', uselist=False, + callable_=lazyload, useobject=False, + active_history=True) + lazy_load = 'hi' # active_history=True means the lazy callable is executed on set as well as get, # causing the old value to appear in the history @@ -1537,14 +1552,13 @@ class HistoryTest(_base.ORMTest): pass lazy_load = None - def lazyload(instance): - def load(**kw): - return lazy_load - return load + def lazyload(state, passive): + return lazy_load instrumentation.register_class(Foo) instrumentation.register_class(Bar) - attributes.register_attribute(Foo, 'bar', uselist=False, callable_=lazyload, trackparent=True, useobject=True) + attributes.register_attribute(Foo, 'bar', uselist=False, + callable_=lazyload, trackparent=True, useobject=True) bar1, bar2 = [Bar(id=1), Bar(id=2)] lazy_load = bar1 diff --git a/test/orm/test_extendedattr.py b/test/orm/test_extendedattr.py index ec7963c293..2eca1ac387 100644 --- a/test/orm/test_extendedattr.py +++ b/test/orm/test_extendedattr.py @@ -197,18 +197,21 @@ class UserDefinedExtensionTest(_base.ORMTest): instrumentation.register_class(Foo) instrumentation.register_class(Bar) - def func1(**kw): - print "func1" + def func1(state, passive): return "this is the foo attr" - def func2(**kw): - print "func2" + def func2(state, passive): return "this is the bar attr" - def func3(**kw): - print "func3" + def func3(state, passive): return "this is the shared attr" - attributes.register_attribute(Foo, 'element', uselist=False, callable_=lambda o:func1, useobject=True) - attributes.register_attribute(Foo, 'element2', uselist=False, callable_=lambda o:func3, useobject=True) - attributes.register_attribute(Bar, 'element', uselist=False, callable_=lambda o:func2, useobject=True) + attributes.register_attribute(Foo, 'element', + uselist=False, callable_=func1, + useobject=True) + attributes.register_attribute(Foo, 'element2', + uselist=False, callable_=func3, + useobject=True) + attributes.register_attribute(Bar, 'element', + uselist=False, callable_=func2, + useobject=True) x = Foo() y = Bar() @@ -224,8 +227,10 @@ class UserDefinedExtensionTest(_base.ORMTest): instrumentation.register_class(Post) instrumentation.register_class(Blog) - attributes.register_attribute(Post, 'blog', uselist=False, backref='posts', trackparent=True, useobject=True) - attributes.register_attribute(Blog, 'posts', uselist=True, backref='blog', trackparent=True, useobject=True) + attributes.register_attribute(Post, 'blog', uselist=False, + backref='posts', trackparent=True, useobject=True) + attributes.register_attribute(Blog, 'posts', uselist=True, + backref='blog', trackparent=True, useobject=True) b = Blog() (p1, p2, p3) = (Post(), Post(), Post()) b.posts.append(p1)