📄 attributes.py
字号:
Only handles collections of instrumented objects. InstrumentedCollectionAttribute holds an arbitrary, user-specified container object (defaulting to a list) and brokers access to the CollectionAdapter, a "view" onto that object that presents consistent bag semantics to the orm layer independent of the user data implementation. """ accepts_scalar_loader = False def __init__(self, class_, key, callable_, typecallable=None, trackparent=False, extension=None, copy_function=None, compare_function=None, **kwargs): super(CollectionAttributeImpl, self).__init__(class_, key, callable_, trackparent=trackparent, extension=extension, compare_function=compare_function, **kwargs) if copy_function is None: copy_function = self.__copy self.copy = copy_function if typecallable is None: typecallable = list self.collection_factory = \ collections._prepare_instrumentation(typecallable) # may be removed in 0.5: self.collection_interface = \ util.duck_type_collection(self.collection_factory()) def __copy(self, item): return [y for y in list(collections.collection_adapter(item))] def get_history(self, state, passive=False): if self.key in state.dict: return _create_history(self, state, state.dict[self.key]) else: current = self.get(state, passive=passive) if current is PASSIVE_NORESULT: return (None, None, None) else: return _create_history(self, state, current) def fire_append_event(self, state, value, initiator): if self.key not in state.committed_state and self.key in state.dict: state.committed_state[self.key] = self.copy(state.dict[self.key]) state.modified = True if self.trackparent and value is not None: self.sethasparent(value._state, True) instance = state.obj() for ext in self.extensions: ext.append(instance, value, initiator or self) def fire_pre_remove_event(self, state, initiator): if self.key not in state.committed_state and self.key in state.dict: state.committed_state[self.key] = self.copy(state.dict[self.key]) def fire_remove_event(self, state, value, initiator): if self.key not in state.committed_state and self.key in state.dict: state.committed_state[self.key] = self.copy(state.dict[self.key]) state.modified = True if self.trackparent and value is not None: self.sethasparent(value._state, False) instance = state.obj() for ext in self.extensions: ext.remove(instance, value, initiator or self) def get_history(self, state, passive=False): current = self.get(state, passive=passive) if current is PASSIVE_NORESULT: return (None, None, None) else: return _create_history(self, state, current) def delete(self, state): if self.key not in state.dict: return state.modified = True collection = self.get_collection(state) collection.clear_with_event() # TODO: catch key errors, convert to attributeerror? del state.dict[self.key] def initialize(self, state): """Initialize this attribute on the given object instance with an empty collection.""" _, user_data = self._build_collection(state) state.dict[self.key] = user_data return user_data def append(self, state, value, initiator, passive=False): if initiator is self: return collection = self.get_collection(state, passive=passive) if collection is PASSIVE_NORESULT: state.get_pending(self.key).append(value) self.fire_append_event(state, value, initiator) else: collection.append_with_event(value, initiator) def remove(self, state, value, initiator, passive=False): if initiator is self: return collection = self.get_collection(state, passive=passive) if collection is PASSIVE_NORESULT: state.get_pending(self.key).remove(value) self.fire_remove_event(state, value, initiator) else: collection.remove_with_event(value, initiator) def set(self, state, value, initiator): """Set a value on the given object. `initiator` is the ``InstrumentedAttribute`` that initiated the ``set()` operation and is used to control the depth of a circular setter operation. """ if initiator is self: return # we need a CollectionAdapter to adapt the incoming value to an # assignable iterable. pulling a new collection first so that # an adaptation exception does not trigger a lazy load of the # old collection. new_collection, user_data = self._build_collection(state) new_values = list(new_collection.adapt_like_to_iterable(value)) old = self.get(state) # ignore re-assignment of the current collection, as happens # implicitly with in-place operators (foo.collection |= other) if old is value: return if self.key not in state.committed_state: state.committed_state[self.key] = self.copy(old) old_collection = self.get_collection(state, old) idset = util.IdentitySet constants = idset(old_collection or []).intersection(new_values or []) additions = idset(new_values or []).difference(constants) removals = idset(old_collection or []).difference(constants) for member in new_values or (): if member in additions: new_collection.append_with_event(member) elif member in constants: new_collection.append_without_event(member) state.dict[self.key] = user_data state.modified = True # mark all the orphaned elements as detached from the parent if old_collection: for member in removals: old_collection.remove_with_event(member) old_collection.unlink(old) def set_committed_value(self, state, value): """Set an attribute value on the given instance and 'commit' it. Loads the existing collection from lazy callables in all cases. """ collection, user_data = self._build_collection(state) if value: for item in value: collection.append_without_event(item) state.callables.pop(self.key, None) state.dict[self.key] = user_data if self.key in state.pending: # pending items. commit loaded data, add/remove new data state.committed_state[self.key] = list(value or []) added = state.pending[self.key].added_items removed = state.pending[self.key].deleted_items for item in added: collection.append_without_event(item) for item in removed: collection.remove_without_event(item) del state.pending[self.key] elif self.key in state.committed_state: # no pending items. remove committed state if any. # (this can occur with an expired attribute) del state.committed_state[self.key] return user_data def _build_collection(self, state): """build a new, blank collection and return it wrapped in a CollectionAdapter.""" user_data = self.collection_factory() collection = collections.CollectionAdapter(self, state, user_data) return collection, user_data def get_collection(self, state, user_data=None, passive=False): """retrieve the CollectionAdapter associated with the given state. Creates a new CollectionAdapter if one does not exist. """ if user_data is None: user_data = self.get(state, passive=passive) if user_data is PASSIVE_NORESULT: return user_data try: return getattr(user_data, '_sa_adapter') except AttributeError: # TODO: this codepath never occurs, and this # except/initialize should be removed collections.CollectionAdapter(self, state, user_data) return getattr(user_data, '_sa_adapter')class GenericBackrefExtension(interfaces.AttributeExtension): """An extension which synchronizes a two-way relationship. A typical two-way relationship is a parent object containing a list of child objects, where each child object references the parent. The other are two objects which contain scalar references to each other. """ def __init__(self, key): self.key = key def set(self, instance, child, oldchild, initiator): if oldchild is child: return if oldchild is not None: # With lazy=None, there's no guarantee that the full collection is # present when updating via a backref. impl = getattr(oldchild.__class__, self.key).impl try: impl.remove(oldchild._state, instance, initiator, passive=True) except (ValueError, KeyError, IndexError): pass if child is not None: getattr(child.__class__, self.key).impl.append(child._state, instance, initiator, passive=True) def append(self, instance, child, initiator): getattr(child.__class__, self.key).impl.append(child._state, instance, initiator, passive=True) def remove(self, instance, child, initiator): if child is not None: getattr(child.__class__, self.key).impl.remove(child._state, instance, initiator, passive=True)class ClassState(object): """tracks state information at the class level.""" def __init__(self): self.mappers = {} self.attrs = {} self.has_mutable_scalars = Falseclass InstanceState(object): """tracks state information at the instance level.""" def __init__(self, obj): self.class_ = obj.__class__ self.obj = weakref.ref(obj, self.__cleanup) self.dict = obj.__dict__ self.committed_state = {} self.modified = False self.callables = {} self.parents = {} self.pending = {} self.appenders = {} self.instance_dict = None self.runid = None def __cleanup(self, ref): # tiptoe around Python GC unpredictableness instance_dict = self.instance_dict if instance_dict is None: return instance_dict = instance_dict() if instance_dict is None or instance_dict._mutex is None: return # the mutexing here is based on the assumption that gc.collect() # may be firing off cleanup handlers in a different thread than that # which is normally operating upon the instance dict. instance_dict._mutex.acquire() try: try: self.__resurrect(instance_dict) except: # catch app cleanup exceptions. no other way around this # without warnings being produced pass finally: instance_dict._mutex.release() def _check_resurrect(self, instance_dict): instance_dict._mutex.acquire() try: return self.obj() or self.__resurrect(instance_dict) finally: instance_dict._mutex.release() def get_pending(self, key): if key not in self.pending: self.pending[key] = PendingCollection() return self.pending[key] def is_modified(self): if self.modified: return True elif self.class_._class_state.has_mutable_scalars: for attr in _managed_attributes(self.class_): if hasattr(attr.impl, 'check_mutable_modified') and attr.impl.check_mutable_modified(self): return True else: return False else: return False def __resurrect(self, instance_dict): if self.is_modified(): # store strong ref'ed version of the object; will revert # to weakref when changes are persisted obj = new_instance(self.class_, state=self) self.obj = weakref.ref(obj, self.__cleanup) self._strong_obj = obj obj.__dict__.update(self.dict) self.dict = obj.__dict__ return obj else: del instance_dict[self.dict['_instance_key']] return None def __getstate__(self): return {'committed_state':self.committed_state, 'pending':self.pending, 'parents':self.parents, 'modified':self.modified, 'instance':self.obj(), 'expired_attributes':getattr(self, 'expired_attributes', None), 'callables':self.callables} def __setstate__(self, state): self.committed_state = state['committed_state'] self.parents = state['parents'] self.pending = state['pending'] self.modified = state['modified'] self.obj = weakref.ref(state['instance']) self.class_ = self.obj().__class__ self.dict = self.obj().__dict__ self.callables = state['callables'] self.runid = None self.appenders = {} if state['expired_attributes'] is not None: self.expire_attributes(state['expired_attributes']) def initialize(self, key): getattr(self.class_, key).impl.initialize(self) def set_callable(self, key, callable_): self.dict.pop(key, None) self.callables[key] = callable_ def __call__(self): """__call__ allows the InstanceState to act as a deferred callable for loading expired attributes, which is also serializable. """ instance = self.obj() unmodified = self.unmodified self.class_._class_state.deferred_scalar_loader(instance, [ attr.impl.key for attr in _managed_attributes(self.class_) if attr.impl.accepts_scalar_loader and attr.impl.key in self.expired_attributes and attr.impl.key in unmodified ]) for k in self.expired_attributes: self.callables.pop(k, None) self.expired_attributes.clear() return ATTR_WAS_SET def unmodified(self): """a set of keys which have no uncommitted changes""" return util.Set([ attr.impl.key for attr in _managed_attributes(self.class_) if attr.impl.key not in self.committed_state and (not hasattr(attr.impl, 'commit_to_state') or not attr.impl.check_mutable_modified(self)) ]) unmodified = property(unmodified) def expire_attributes(self, attribute_names): if not hasattr(self, 'expired_attributes'): self.expired_attributes = util.Set() if attribute_names is None: for attr in _managed_attributes(self.class_): self.dict.pop(attr.impl.key, None) self.expired_attributes.add(attr.impl.key) if attr.impl.accepts_scalar_loader: self.callables[attr.impl.key] = self self.committed_state = {} else: for key in attribute_names:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -