diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2006-10-22 00:24:26 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2006-10-22 00:24:26 +0000 |
commit | bc240be3f87b41232671e4da7f59679744959154 (patch) | |
tree | 4e978300d6e796f8a476b2e6ccf543ba2e9d881f /lib/sqlalchemy/attributes.py | |
parent | 97feb4dbeee3ef5bc50de667ec25a43d44a5ff2c (diff) | |
download | sqlalchemy-bc240be3f87b41232671e4da7f59679744959154.tar.gz |
- attributes module and test suite moves underneath 'orm' package
- fixed table comparison example in metadata.txt
- docstrings all over the place
- renamed mapper _getattrbycolumn/_setattrbycolumn to get_attr_by_column,set_attr_by_column
- removed frommapper parameter from populate_instance(). the two operations can be performed separately
- fix to examples/adjacencytree/byroot_tree.py to fire off lazy loaders upon load, to reduce query calling
- added get(), get_by(), load() to MapperExtension
- re-implemented ExtensionOption (called by extension() function)
- redid _ExtensionCarrier to function dynamically based on __getattribute__
- added logging to attributes package, indicating the execution of a lazy callable
- going to close [ticket:329]
Diffstat (limited to 'lib/sqlalchemy/attributes.py')
-rw-r--r-- | lib/sqlalchemy/attributes.py | 752 |
1 files changed, 0 insertions, 752 deletions
diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py deleted file mode 100644 index 67c23ba95..000000000 --- a/lib/sqlalchemy/attributes.py +++ /dev/null @@ -1,752 +0,0 @@ -# attributes.py - manages object attributes -# Copyright (C) 2005,2006 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.php - -import util -import weakref - -class InstrumentedAttribute(object): - """a property object that instruments attribute access on object instances. All methods correspond to - a single attribute on a particular class.""" - - PASSIVE_NORESULT = object() - - def __init__(self, manager, key, uselist, callable_, typecallable, trackparent=False, extension=None, copy_function=None, compare_function=None, mutable_scalars=False, **kwargs): - self.manager = manager - self.key = key - self.uselist = uselist - self.callable_ = callable_ - self.typecallable= typecallable - self.trackparent = trackparent - self.mutable_scalars = mutable_scalars - if copy_function is None: - if uselist: - self._copyfunc = lambda x: [y for y in x] - else: - # scalar values are assumed to be immutable unless a copy function - # is passed - self._copyfunc = lambda x: x - else: - self._copyfunc = copy_function - if compare_function is None: - self._compare_function = lambda x,y: x == y - else: - self._compare_function = compare_function - self.extensions = util.to_list(extension or []) - - def __set__(self, obj, value): - self.set(None, obj, value) - def __delete__(self, obj): - self.delete(None, obj) - def __get__(self, obj, owner): - if obj is None: - return self - return self.get(obj) - - def is_equal(self, x, y): - return self._compare_function(x, y) - def copy(self, value): - return self._copyfunc(value) - - def check_mutable_modified(self, obj): - if self.mutable_scalars: - h = self.get_history(obj, passive=True) - if h is not None and h.is_modified(): - obj._state['modified'] = True - return True - else: - return False - else: - return False - - - def hasparent(self, item, 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 item._state.get(('hasparent', id(self)), optimistic) - - def sethasparent(self, item, value): - """sets 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.""" - item._state[('hasparent', id(self))] = value - - def get_history(self, obj, passive=False): - """return a new AttributeHistory object for the given object/this attribute's key. - - if passive is True, then dont execute any callables; if the attribute's value - can only be achieved via executing a callable, then return None.""" - # get the current state. this may trigger a lazy load if - # passive is False. - current = self.get(obj, passive=passive, raiseerr=False) - if current is InstrumentedAttribute.PASSIVE_NORESULT: - return None - return AttributeHistory(self, obj, current, passive=passive) - - def set_callable(self, obj, 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(obj) - else: - obj._state[('callable', self)] = callable_ - - def reset(self, obj): - """removes any per-instance callable functions corresponding to this InstrumentedAttribute's attribute - from the given object, and removes this InstrumentedAttribute's - attribute from the given object's dictionary.""" - try: - del obj._state[('callable', self)] - except KeyError: - pass - self.clear(obj) - - def clear(self, obj): - """removes this InstrumentedAttribute's attribute from the given object's dictionary. subsequent calls to - getattr(obj, key) will raise an AttributeError by default.""" - try: - del obj.__dict__[self.key] - except KeyError: - pass - - def _get_callable(self, obj): - if obj._state.has_key(('callable', self)): - return obj._state[('callable', self)] - elif self.callable_ is not None: - return self.callable_(obj) - else: - return None - - def _blank_list(self): - if self.typecallable is not None: - return self.typecallable() - else: - return [] - - def _adapt_list(self, data): - if self.typecallable is not None: - t = self.typecallable() - if data is not None: - [t.append(x) for x in data] - return t - else: - return data - - def initialize(self, obj): - """initialize this attribute on the given object instance. - - if this is a list-based attribute, a new, blank list will be created. - if a scalar attribute, the value will be initialized to None.""" - if self.uselist: - l = InstrumentedList(self, obj, self._blank_list()) - obj.__dict__[self.key] = l - return l - else: - obj.__dict__[self.key] = None - return None - - def get(self, obj, passive=False, raiseerr=True): - """retrieves 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 obj.__dict__[self.key] - except KeyError: - state = obj._state - # if an instance-wide "trigger" was set, call that - # and start again - if state.has_key('trigger'): - trig = state['trigger'] - del state['trigger'] - trig() - return self.get(obj, passive=passive, raiseerr=raiseerr) - - if self.uselist: - callable_ = self._get_callable(obj) - if callable_ is not None: - if passive: - return InstrumentedAttribute.PASSIVE_NORESULT - values = callable_() - l = InstrumentedList(self, obj, self._adapt_list(values), init=False) - - # if a callable was executed, then its part of the "committed state" - # if any, so commit the newly loaded data - orig = state.get('original', None) - if orig is not None: - orig.commit_attribute(self, obj, l) - - else: - # note that we arent raising AttributeErrors, just creating a new - # blank list and setting it. - # this might be a good thing to be changeable by options. - l = InstrumentedList(self, obj, self._blank_list(), init=False) - obj.__dict__[self.key] = l - return l - else: - callable_ = self._get_callable(obj) - if callable_ is not None: - if passive: - return InstrumentedAttribute.PASSIVE_NORESULT - value = callable_() - obj.__dict__[self.key] = value - - # if a callable was executed, then its part of the "committed state" - # if any, so commit the newly loaded data - orig = state.get('original', None) - if orig is not None: - orig.commit_attribute(self, obj) - return obj.__dict__[self.key] - else: - # note that we arent raising AttributeErrors, just returning None. - # this might be a good thing to be changeable by options. - return None - - def set(self, event, obj, value): - """sets a value on the given object. 'event' is the InstrumentedAttribute that - initiated the set() operation and is used to control the depth of a circular setter - operation.""" - if event is not self: - state = obj._state - # if an instance-wide "trigger" was set, call that - if state.has_key('trigger'): - trig = state['trigger'] - del state['trigger'] - trig() - if self.uselist: - value = InstrumentedList(self, obj, value) - old = self.get(obj) - obj.__dict__[self.key] = value - state['modified'] = True - if not self.uselist: - if self.trackparent: - if value is not None: - self.sethasparent(value, True) - if old is not None: - self.sethasparent(old, False) - for ext in self.extensions: - ext.set(event or self, obj, value, old) - else: - # mark all the old elements as detached from the parent - old.list_replaced() - - def delete(self, event, obj): - """deletes a value from the given object. 'event' is the InstrumentedAttribute that - initiated the delete() operation and is used to control the depth of a circular delete - operation.""" - if event is not self: - try: - if not self.uselist and (self.trackparent or len(self.extensions)): - old = self.get(obj) - del obj.__dict__[self.key] - except KeyError: - # TODO: raise this? not consistent with get() ? - raise AttributeError(self.key) - obj._state['modified'] = True - if not self.uselist: - if self.trackparent: - if old is not None: - self.sethasparent(old, False) - for ext in self.extensions: - ext.delete(event or self, obj, old) - - def append(self, event, obj, value): - """appends an element to a list based element or sets a scalar based element to the given value. - Used by GenericBackrefExtension to "append" an item independent of list/scalar semantics. - 'event' is the InstrumentedAttribute that initiated the append() operation and is used to control - the depth of a circular append operation.""" - if self.uselist: - if event is not self: - self.get(obj).append_with_event(value, event) - else: - self.set(event, obj, value) - - def remove(self, event, obj, value): - """removes an element from a list based element or sets a scalar based element to None. - Used by GenericBackrefExtension to "remove" an item independent of list/scalar semantics. - 'event' is the InstrumentedAttribute that initiated the remove() operation and is used to control - the depth of a circular remove operation.""" - if self.uselist: - if event is not self: - self.get(obj).remove_with_event(value, event) - else: - self.set(event, obj, None) - - def append_event(self, event, obj, value): - """called by InstrumentedList when an item is appended""" - obj._state['modified'] = True - if self.trackparent and value is not None: - self.sethasparent(value, True) - for ext in self.extensions: - ext.append(event or self, obj, value) - - def remove_event(self, event, obj, value): - """called by InstrumentedList when an item is removed""" - obj._state['modified'] = True - if self.trackparent and value is not None: - self.sethasparent(value, False) - for ext in self.extensions: - ext.delete(event or self, obj, value) - -class InstrumentedList(object): - """instruments a list-based attribute. all mutator operations (i.e. append, remove, etc.) will fire off events to the - InstrumentedAttribute that manages the object's attribute. those events in turn trigger things like - backref operations and whatever is implemented by do_list_value_changed on InstrumentedAttribute. - - note that this list does a lot less than earlier versions of SA list-based attributes, which used HistoryArraySet. - this list wrapper does *not* maintain setlike semantics, meaning you can add as many duplicates as - you want (which can break a lot of SQL), and also does not do anything related to history tracking. - - Please see ticket #213 for information on the future of this class, where it will be broken out into more - collection-specific subtypes.""" - def __init__(self, attr, obj, data, init=True): - self.attr = attr - # this weakref is to prevent circular references between the parent object - # and the list attribute, which interferes with immediate garbage collection. - self.__obj = weakref.ref(obj) - self.key = attr.key - self.data = data or attr._blank_list() - - # adapt to lists or sets - # TODO: make three subclasses of InstrumentedList that come off from a - # metaclass, based on the type of data sent in - if hasattr(self.data, 'append'): - self._data_appender = self.data.append - self._clear_data = self._clear_list - elif hasattr(self.data, 'add'): - self._data_appender = self.data.add - self._clear_data = self._clear_set - if isinstance(self.data, dict): - self._clear_data = self._clear_dict - - if init: - for x in self.data: - self.__setrecord(x) - - def list_replaced(self): - """fires off delete event handlers for each item in the list but - doesnt affect the original data list""" - [self.__delrecord(x) for x in self.data] - - def clear(self): - """clears all items in this InstrumentedList and fires off delete event handlers for each item""" - self._clear_data() - def _clear_dict(self): - [self.__delrecord(x) for x in self.data.values()] - self.data.clear() - def _clear_set(self): - [self.__delrecord(x) for x in self.data] - self.data.clear() - def _clear_list(self): - self[:] = [] - - def __getstate__(self): - """implemented to allow pickling, since __obj is a weakref, also the InstrumentedAttribute has callables - attached to it""" - return {'key':self.key, 'obj':self.obj, 'data':self.data} - def __setstate__(self, d): - """implemented to allow pickling, since __obj is a weakref, also the InstrumentedAttribute has callables - attached to it""" - self.key = d['key'] - self.__obj = weakref.ref(d['obj']) - self.data = d['data'] - self.attr = getattr(d['obj'].__class__, self.key) - - obj = property(lambda s:s.__obj()) - - def unchanged_items(self): - """deprecated""" - return self.attr.get_history(self.obj).unchanged_items - def added_items(self): - """deprecated""" - return self.attr.get_history(self.obj).added_items - def deleted_items(self): - """deprecated""" - return self.attr.get_history(self.obj).deleted_items - - def __iter__(self): - return iter(self.data) - def __repr__(self): - return repr(self.data) - - def __getattr__(self, attr): - """proxies unknown methods and attributes to the underlying - data array. this allows custom list classes to be used.""" - return getattr(self.data, attr) - - def __setrecord(self, item, event=None): - self.attr.append_event(event, self.obj, item) - return True - - def __delrecord(self, item, event=None): - self.attr.remove_event(event, self.obj, item) - return True - - def append_with_event(self, item, event): - self.__setrecord(item, event) - self._data_appender(item) - - def append_without_event(self, item): - self._data_appender(item) - - def remove_with_event(self, item, event): - self.__delrecord(item, event) - self.data.remove(item) - - def append(self, item, _mapper_nohistory=False): - """fires off dependent events, and appends the given item to the underlying list. - _mapper_nohistory is a backwards compatibility hack; call append_without_event instead.""" - if _mapper_nohistory: - self.append_without_event(item) - else: - self.__setrecord(item) - self._data_appender(item) - - - def __getitem__(self, i): - return self.data[i] - def __setitem__(self, i, item): - if isinstance(i, slice): - self.__setslice__(i.start, i.stop, item) - else: - self.__setrecord(item) - self.data[i] = item - def __delitem__(self, i): - if isinstance(i, slice): - self.__delslice__(i.start, i.stop) - else: - self.__delrecord(self.data[i], None) - del self.data[i] - - def __lt__(self, other): return self.data < self.__cast(other) - def __le__(self, other): return self.data <= self.__cast(other) - def __eq__(self, other): return self.data == self.__cast(other) - def __ne__(self, other): return self.data != self.__cast(other) - def __gt__(self, other): return self.data > self.__cast(other) - def __ge__(self, other): return self.data >= self.__cast(other) - def __cast(self, other): - if isinstance(other, InstrumentedList): return other.data - else: return other - def __cmp__(self, other): - return cmp(self.data, self.__cast(other)) - def __contains__(self, item): return item in self.data - def __len__(self): return len(self.data) - def __setslice__(self, i, j, other): - i = max(i, 0); j = max(j, 0) - [self.__delrecord(x) for x in self.data[i:]] - g = [a for a in list(other) if self.__setrecord(a)] - self.data[i:] = g - def __delslice__(self, i, j): - i = max(i, 0); j = max(j, 0) - for a in self.data[i:j]: - self.__delrecord(a) - del self.data[i:j] - def insert(self, i, item): - if self.__setrecord(item): - self.data.insert(i, item) - def pop(self, i=-1): - item = self.data[i] - self.__delrecord(item) - return self.data.pop(i) - def remove(self, item): - self.__delrecord(item) - self.data.remove(item) - def extend(self, item_list): - for item in item_list: - self.append(item) - def __add__(self, other): - raise NotImplementedError() - def __radd__(self, other): - raise NotImplementedError() - def __iadd__(self, other): - raise NotImplementedError() - -class AttributeExtension(object): - """an abstract class which specifies "append", "delete", and "set" - event handlers to be attached to an object property.""" - def append(self, event, obj, child): - pass - def delete(self, event, obj, child): - pass - def set(self, event, obj, child, oldchild): - pass - -class GenericBackrefExtension(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, event, obj, child, oldchild): - if oldchild is child: - return - if oldchild is not None: - getattr(oldchild.__class__, self.key).remove(event, oldchild, obj) - if child is not None: - getattr(child.__class__, self.key).append(event, child, obj) - def append(self, event, obj, child): - getattr(child.__class__, self.key).append(event, child, obj) - def delete(self, event, obj, child): - getattr(child.__class__, self.key).remove(event, child, obj) - -class CommittedState(object): - """stores the original state of an object when the commit() method on the attribute manager - is called.""" - NO_VALUE = object() - - def __init__(self, manager, obj): - self.data = {} - for attr in manager.managed_attributes(obj.__class__): - self.commit_attribute(attr, obj) - - def commit_attribute(self, attr, obj, value=NO_VALUE): - """establish the value of attribute 'attr' on instance 'obj' as "committed". - - this corresponds to a previously saved state being restored. """ - if value is CommittedState.NO_VALUE: - if obj.__dict__.has_key(attr.key): - value = obj.__dict__[attr.key] - if value is not CommittedState.NO_VALUE: - self.data[attr.key] = attr.copy(value) - - # not tracking parent on lazy-loaded instances at the moment. - # its not needed since they will be "optimistically" tested - #if attr.uselist: - #if attr.trackparent: - # [attr.sethasparent(x, True) for x in self.data[attr.key] if x is not None] - #else: - #if attr.trackparent and value is not None: - # attr.sethasparent(value, True) - - def rollback(self, manager, obj): - for attr in manager.managed_attributes(obj.__class__): - if self.data.has_key(attr.key): - if attr.uselist: - obj.__dict__[attr.key][:] = self.data[attr.key] - else: - obj.__dict__[attr.key] = self.data[attr.key] - else: - del obj.__dict__[attr.key] - - def __repr__(self): - return "CommittedState: %s" % repr(self.data) - -class AttributeHistory(object): - """calculates the "history" of a particular attribute on a particular instance, based on the CommittedState - associated with the instance, if any.""" - def __init__(self, attr, obj, current, passive=False): - self.attr = attr - - # get the "original" value. if a lazy load was fired when we got - # the 'current' value, this "original" was also populated just - # now as well (therefore we have to get it second) - orig = obj._state.get('original', None) - if orig is not None: - original = orig.data.get(attr.key) - else: - original = None - - if attr.uselist: - self._current = current - else: - self._current = [current] - if attr.uselist: - s = util.Set(original or []) - self._added_items = [] - self._unchanged_items = [] - self._deleted_items = [] - if current: - for a in current: - if a in s: - self._unchanged_items.append(a) - else: - self._added_items.append(a) - for a in s: - if a not in self._unchanged_items: - self._deleted_items.append(a) - else: - if attr.is_equal(current, original): - self._unchanged_items = [current] - self._added_items = [] - self._deleted_items = [] - else: - self._added_items = [current] - if original is not None: - self._deleted_items = [original] - else: - self._deleted_items = [] - self._unchanged_items = [] - #print "key", attr.key, "orig", original, "current", current, "added", self._added_items, "unchanged", self._unchanged_items, "deleted", self._deleted_items - def __iter__(self): - return iter(self._current) - def is_modified(self): - return len(self._deleted_items) > 0 or len(self._added_items) > 0 - def added_items(self): - return self._added_items - def unchanged_items(self): - return self._unchanged_items - def deleted_items(self): - return self._deleted_items - def hasparent(self, obj): - """deprecated. this should be called directly from the appropriate InstrumentedAttribute object.""" - return self.attr.hasparent(obj) - -class AttributeManager(object): - """allows the instrumentation of object attributes. AttributeManager is stateless, but can be - overridden by subclasses to redefine some of its factory operations.""" - - def rollback(self, *obj): - """retrieves the committed history for each object in the given list, and rolls back the attributes - each instance to their original value.""" - for o in obj: - orig = o._state.get('original') - if orig is not None: - orig.rollback(self, o) - else: - self._clear(o) - - def _clear(self, obj): - for attr in self.managed_attributes(obj.__class__): - try: - del obj.__dict__[attr.key] - except KeyError: - pass - - def commit(self, *obj): - """creates a CommittedState instance for each object in the given list, representing - its "unchanged" state, and associates it with the instance. AttributeHistory objects - will indicate the modified state of instance attributes as compared to its value in this - CommittedState object.""" - for o in obj: - o._state['original'] = CommittedState(self, o) - o._state['modified'] = False - - def managed_attributes(self, class_): - """returns an iterator of all InstrumentedAttribute objects associated with the given class.""" - if not isinstance(class_, type): - raise repr(class_) + " is not a type" - for key in dir(class_): - value = getattr(class_, key, None) - if isinstance(value, InstrumentedAttribute): - yield value - - def noninherited_managed_attributes(self, class_): - if not isinstance(class_, type): - raise repr(class_) + " is not a type" - for key in list(class_.__dict__): - value = getattr(class_, key, None) - if isinstance(value, InstrumentedAttribute): - yield value - - def is_modified(self, object): - for attr in self.managed_attributes(object.__class__): - if attr.check_mutable_modified(object): - return True - return object._state.get('modified', False) - - def init_attr(self, obj): - """sets up the __sa_attr_state dictionary on the given instance. This dictionary is - automatically created when the '_state' attribute of the class is first accessed, but calling - it here will save a single throw of an AttributeError that occurs in that creation step.""" - setattr(obj, '_%s__sa_attr_state' % obj.__class__.__name__, {}) - - def get_history(self, obj, key, **kwargs): - """returns a new AttributeHistory object for the given attribute on the given object.""" - return getattr(obj.__class__, key).get_history(obj, **kwargs) - - def get_as_list(self, obj, key, passive=False): - """returns an attribute of the given name from the given object. if the attribute - is a scalar, returns it as a single-item list, otherwise returns the list based attribute. - if the attribute's value is to be produced by an unexecuted callable, - the callable will only be executed if the given 'passive' flag is False. - """ - attr = getattr(obj.__class__, key) - x = attr.get(obj, passive=passive) - if x is InstrumentedAttribute.PASSIVE_NORESULT: - return [] - elif attr.uselist: - return x - else: - return [x] - - def trigger_history(self, obj, callable): - """clears all managed object attributes and places the given callable - as an attribute-wide "trigger", which will execute upon the next attribute access, after - which the trigger is removed.""" - self._clear(obj) - try: - del obj._state['original'] - except KeyError: - pass - obj._state['trigger'] = callable - - def untrigger_history(self, obj): - """removes a trigger function set by trigger_history. does not restore the previous state of the object.""" - del obj._state['trigger'] - - def has_trigger(self, obj): - """returns True if the given object has a trigger function set by trigger_history().""" - return obj._state.has_key('trigger') - - def reset_instance_attribute(self, obj, key): - """removes any per-instance callable functions corresponding to given attribute key - from the given object, and removes this attribute from the given object's dictionary.""" - attr = getattr(obj.__class__, key) - attr.reset(obj) - - def reset_class_managed(self, class_): - """removes all InstrumentedAttribute property objects from the given class.""" - for attr in self.noninherited_managed_attributes(class_): - delattr(class_, attr.key) - - def is_class_managed(self, class_, key): - """returns True if the given key correponds to an instrumented property on the given class.""" - return hasattr(class_, key) and isinstance(getattr(class_, key), InstrumentedAttribute) - - def init_instance_attribute(self, obj, key, uselist, callable_=None, **kwargs): - """initializes an attribute on an instance to either a blank value, cancelling - out any class- or instance-level callables that were present, or if a callable - is supplied sets the callable to be invoked when the attribute is next accessed.""" - getattr(obj.__class__, key).set_callable(obj, callable_) - - def create_prop(self, class_, key, uselist, callable_, typecallable, **kwargs): - """creates a scalar property object, defaulting to InstrumentedAttribute, which - will communicate change events back to this AttributeManager.""" - return InstrumentedAttribute(self, key, uselist, callable_, typecallable, **kwargs) - - def register_attribute(self, class_, key, uselist, callable_=None, **kwargs): - """registers an attribute at the class level to be instrumented for all instances - of the class.""" - #print self, "register attribute", key, "for class", class_ - if not hasattr(class_, '_state'): - def _get_state(self): - try: - return self.__sa_attr_state - except AttributeError: - self.__sa_attr_state = {} - return self.__sa_attr_state - class_._state = property(_get_state) - - typecallable = kwargs.pop('typecallable', None) - if typecallable is None: - typecallable = getattr(class_, key, None) - if isinstance(typecallable, InstrumentedAttribute): - typecallable = None - setattr(class_, key, self.create_prop(class_, key, uselist, callable_, typecallable=typecallable, **kwargs)) - |