📄 attributes.py
字号:
# attributes.py - manages object attributes# Copyright (C) 2005, 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com## This module is part of SQLAlchemy and is released under# the MIT License: http://www.opensource.org/licenses/mit-license.phpimport weakref, threading, operatorfrom itertools import chainimport UserDictfrom sqlalchemy import utilfrom sqlalchemy.orm import interfaces, collectionsfrom sqlalchemy.orm.util import identity_equalfrom sqlalchemy import exceptionsPASSIVE_NORESULT = object()ATTR_WAS_SET = object()NO_VALUE = object()NEVER_SET = object()class InstrumentedAttribute(interfaces.PropComparator): """public-facing instrumented attribute, placed in the class dictionary. """ def __init__(self, impl, comparator=None): """Construct an InstrumentedAttribute. comparator a sql.Comparator to which class-level compare/math events will be sent """ self.impl = impl self.comparator = comparator def __set__(self, instance, value): self.impl.set(instance._state, value, None) def __delete__(self, instance): self.impl.delete(instance._state) def __get__(self, instance, owner): if instance is None: return self return self.impl.get(instance._state) def get_history(self, instance, **kwargs): return self.impl.get_history(instance._state, **kwargs) def clause_element(self): return self.comparator.clause_element() def expression_element(self): return self.comparator.expression_element() def operate(self, op, *other, **kwargs): return op(self.comparator, *other, **kwargs) def reverse_operate(self, op, other, **kwargs): return op(other, self.comparator, **kwargs) def hasparent(self, instance, optimistic=False): return self.impl.hasparent(instance._state, optimistic=optimistic) def _property(self): from sqlalchemy.orm.mapper import class_mapper return class_mapper(self.impl.class_).get_property(self.impl.key) property = property(_property, doc="the MapperProperty object associated with this attribute")class ProxiedAttribute(InstrumentedAttribute): """a 'proxy' attribute which adds InstrumentedAttribute class-level behavior to any user-defined class property. """ class ProxyImpl(object): accepts_scalar_loader = False def __init__(self, key): self.key = key def __init__(self, key, user_prop, comparator=None): self.user_prop = user_prop self._comparator = comparator self.key = key self.impl = ProxiedAttribute.ProxyImpl(key) def comparator(self): if callable(self._comparator): self._comparator = self._comparator() return self._comparator comparator = property(comparator) def __get__(self, instance, owner): if instance is None: self.user_prop.__get__(instance, owner) return self return self.user_prop.__get__(instance, owner) def __set__(self, instance, value): return self.user_prop.__set__(instance, value) def __delete__(self, instance): return self.user_prop.__delete__(instance)class AttributeImpl(object): """internal implementation for instrumented attributes.""" def __init__(self, class_, key, callable_, trackparent=False, extension=None, compare_function=None, **kwargs): """Construct an AttributeImpl. class_ the class to be instrumented. key string name of the attribute callable_ optional function which generates a callable based on a parent instance, which produces the "default" values for a scalar or collection attribute when it's first accessed, if not present already. trackparent if True, attempt to track if an instance has a parent attached to it via this attribute. extension an AttributeExtension object which will receive set/delete/append/remove/etc. events. compare_function a function that compares two values which are normally assignable to this attribute. """ self.class_ = class_ self.key = key self.callable_ = callable_ self.trackparent = trackparent if compare_function is None: self.is_equal = operator.eq else: self.is_equal = compare_function self.extensions = util.to_list(extension or []) def hasparent(self, state, optimistic=False): """Return the boolean value of a `hasparent` flag attached to the given item. The `optimistic` flag determines what the default return value should be if no `hasparent` flag can be located. As this function is used to determine if an instance is an *orphan*, instances that were loaded from storage should be assumed to not be orphans, until a True/False value for this flag is set. An instance attribute that is loaded by a callable function will also not have a `hasparent` flag. """ return state.parents.get(id(self), optimistic) def sethasparent(self, state, value): """Set a boolean flag on the given item corresponding to whether or not it is attached to a parent object via the attribute represented by this ``InstrumentedAttribute``. """ state.parents[id(self)] = value def set_callable(self, state, callable_): """Set a callable function for this attribute on the given object. This callable will be executed when the attribute is next accessed, and is assumed to construct part of the instances previously stored state. When its value or values are loaded, they will be established as part of the instance's *committed state*. While *trackparent* information will be assembled for these instances, attribute-level event handlers will not be fired. The callable overrides the class level callable set in the ``InstrumentedAttribute` constructor. """ if callable_ is None: self.initialize(state) else: state.callables[self.key] = callable_ def get_history(self, state, passive=False): raise NotImplementedError() def _get_callable(self, state): if self.key in state.callables: return state.callables[self.key] elif self.callable_ is not None: return self.callable_(state.obj()) else: return None def initialize(self, state): """Initialize this attribute on the given object instance with an empty value.""" state.dict[self.key] = None return None def get(self, state, passive=False): """Retrieve a value from the given object. If a callable is assembled on this object's attribute, and passive is False, the callable will be executed and the resulting value will be set as the new value for this attribute. """ try: return state.dict[self.key] except KeyError: # if no history, check for lazy callables, etc. if self.key not in state.committed_state: callable_ = self._get_callable(state) if callable_ is not None: if passive: return PASSIVE_NORESULT value = callable_() if value is not ATTR_WAS_SET: return self.set_committed_value(state, value) else: if self.key not in state.dict: return self.get(state, passive=passive) return state.dict[self.key] # Return a new, empty value return self.initialize(state) def append(self, state, value, initiator, passive=False): self.set(state, value, initiator) def remove(self, state, value, initiator, passive=False): self.set(state, None, initiator) def set(self, state, value, initiator): raise NotImplementedError() def get_committed_value(self, state): """return the unchanged value of this attribute""" if self.key in state.committed_state: return state.committed_state.get(self.key) else: return self.get(state) def set_committed_value(self, state, value): """set an attribute value on the given instance and 'commit' it.""" state.commit_attr(self, value) # remove per-instance callable, if any state.callables.pop(self.key, None) state.dict[self.key] = value return valueclass ScalarAttributeImpl(AttributeImpl): """represents a scalar value-holding InstrumentedAttribute.""" accepts_scalar_loader = True def delete(self, state): if self.key not in state.committed_state: state.committed_state[self.key] = state.dict.get(self.key, NO_VALUE) # TODO: catch key errors, convert to attributeerror? del state.dict[self.key] state.modified=True def get_history(self, state, passive=False): return _create_history(self, state, state.dict.get(self.key, NO_VALUE)) def set(self, state, value, initiator): if initiator is self: return if self.key not in state.committed_state: state.committed_state[self.key] = state.dict.get(self.key, NO_VALUE) state.dict[self.key] = value state.modified=True def type(self): self.property.columns[0].type type = property(type)class MutableScalarAttributeImpl(ScalarAttributeImpl): """represents a scalar value-holding InstrumentedAttribute, which can detect changes within the value itself. """ def __init__(self, class_, key, callable_, copy_function=None, compare_function=None, **kwargs): super(ScalarAttributeImpl, self).__init__(class_, key, callable_, compare_function=compare_function, **kwargs) class_._class_state.has_mutable_scalars = True if copy_function is None: raise exceptions.ArgumentError("MutableScalarAttributeImpl requires a copy function") self.copy = copy_function def get_history(self, state, passive=False): return _create_history(self, state, state.dict.get(self.key, NO_VALUE)) def commit_to_state(self, state, value): state.committed_state[self.key] = self.copy(value) def check_mutable_modified(self, state): (added, unchanged, deleted) = self.get_history(state, passive=True) if added or deleted: state.modified = True return True else: return False def set(self, state, value, initiator): if initiator is self: return if self.key not in state.committed_state: if self.key in state.dict: state.committed_state[self.key] = self.copy(state.dict[self.key]) else: state.committed_state[self.key] = NO_VALUE state.dict[self.key] = value state.modified=Trueclass ScalarObjectAttributeImpl(ScalarAttributeImpl): """represents a scalar-holding InstrumentedAttribute, where the target object is also instrumented. Adds events to delete/set operations. """ accepts_scalar_loader = False def __init__(self, class_, key, callable_, trackparent=False, extension=None, copy_function=None, compare_function=None, **kwargs): super(ScalarObjectAttributeImpl, self).__init__(class_, key, callable_, trackparent=trackparent, extension=extension, compare_function=compare_function, **kwargs) if compare_function is None: self.is_equal = identity_equal def delete(self, state): old = self.get(state) # TODO: catch key errors, convert to attributeerror? del state.dict[self.key] self.fire_remove_event(state, old, self) 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 set(self, state, value, initiator): """Set a value on the given InstanceState. `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 # TODO: add options to allow the get() to be passive old = self.get(state) state.dict[self.key] = value self.fire_replace_event(state, value, old, initiator) def fire_remove_event(self, state, value, initiator): if self.key not in state.committed_state: state.committed_state[self.key] = value 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 fire_replace_event(self, state, value, previous, initiator): if self.key not in state.committed_state: state.committed_state[self.key] = previous state.modified = True if self.trackparent: if value is not None: self.sethasparent(value._state, True) if previous is not value and previous is not None: self.sethasparent(previous._state, False) instance = state.obj() for ext in self.extensions: ext.set(instance, value, previous, initiator or self)class CollectionAttributeImpl(AttributeImpl): """A collection-holding attribute that instruments changes in membership.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -