diff options
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 41 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dynamic.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/instrumentation.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 36 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 31 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/state.py | 137 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 29 |
9 files changed, 214 insertions, 73 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index ec0b84a60..d75682443 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -16,7 +16,7 @@ defines a large part of the ORM's interactivity. import operator from operator import itemgetter -from sqlalchemy import util, event, exc as sa_exc +from sqlalchemy import util, event, exc as sa_exc, inspection from sqlalchemy.orm import interfaces, collections, events, exc as orm_exc @@ -94,7 +94,6 @@ PASSIVE_NO_FETCH_RELATED = PASSIVE_OFF ^ RELATED_OBJECT_OK PASSIVE_ONLY_PERSISTENT = PASSIVE_OFF ^ NON_PERSISTENT_OK - class QueryableAttribute(interfaces.PropComparator): """Base class for class-bound attributes. """ @@ -164,6 +163,10 @@ class QueryableAttribute(interfaces.PropComparator): return self.comparator.property +@inspection._inspects(QueryableAttribute) +def _get_prop(source): + return source.property + class InstrumentedAttribute(QueryableAttribute): """Class bound instrumented attribute which adds descriptor methods.""" @@ -201,11 +204,13 @@ def create_proxied_attribute(descriptor): """ - def __init__(self, class_, key, descriptor, comparator, + def __init__(self, class_, key, descriptor, property_, + comparator, adapter=None, doc=None): self.class_ = class_ self.key = key self.descriptor = descriptor + self.original_property = property_ self._comparator = comparator self.adapter = adapter self.__doc__ = doc @@ -542,7 +547,7 @@ class ScalarAttributeImpl(AttributeImpl): if self.dispatch.remove: self.fire_remove_event(state, dict_, old, None) - state.modified_event(dict_, self, old) + state._modified_event(dict_, self, old) del dict_[self.key] def get_history(self, state, dict_, passive=PASSIVE_OFF): @@ -562,7 +567,7 @@ class ScalarAttributeImpl(AttributeImpl): if self.dispatch.set: value = self.fire_replace_event(state, dict_, value, old, initiator) - state.modified_event(dict_, self, old) + state._modified_event(dict_, self, old) dict_[self.key] = value def fire_replace_event(self, state, dict_, value, previous, initiator): @@ -721,7 +726,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): for fn in self.dispatch.remove: fn(state, value, initiator or self) - state.modified_event(dict_, self, value) + state._modified_event(dict_, self, value) def fire_replace_event(self, state, dict_, value, previous, initiator): if self.trackparent: @@ -733,7 +738,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): for fn in self.dispatch.set: value = fn(state, value, previous, initiator or self) - state.modified_event(dict_, self, previous) + state._modified_event(dict_, self, previous) if self.trackparent: if value is not None: @@ -816,7 +821,7 @@ class CollectionAttributeImpl(AttributeImpl): for fn in self.dispatch.append: value = fn(state, value, initiator or self) - state.modified_event(dict_, self, NEVER_SET, True) + state._modified_event(dict_, self, NEVER_SET, True) if self.trackparent and value is not None: self.sethasparent(instance_state(value), state, True) @@ -824,7 +829,7 @@ class CollectionAttributeImpl(AttributeImpl): return value def fire_pre_remove_event(self, state, dict_, initiator): - state.modified_event(dict_, self, NEVER_SET, True) + state._modified_event(dict_, self, NEVER_SET, True) def fire_remove_event(self, state, dict_, value, initiator): if self.trackparent and value is not None: @@ -833,13 +838,13 @@ class CollectionAttributeImpl(AttributeImpl): for fn in self.dispatch.remove: fn(state, value, initiator or self) - state.modified_event(dict_, self, NEVER_SET, True) + state._modified_event(dict_, self, NEVER_SET, True) def delete(self, state, dict_): if self.key not in dict_: return - state.modified_event(dict_, self, NEVER_SET, True) + state._modified_event(dict_, self, NEVER_SET, True) collection = self.get_collection(state, state.dict) collection.clear_with_event() @@ -866,7 +871,7 @@ class CollectionAttributeImpl(AttributeImpl): value = self.fire_append_event(state, dict_, value, initiator) assert self.key not in dict_, \ "Collection was loaded during event handling." - state.get_pending(self.key).append(value) + state._get_pending_mutation(self.key).append(value) else: collection.append_with_event(value, initiator) @@ -879,7 +884,7 @@ class CollectionAttributeImpl(AttributeImpl): self.fire_remove_event(state, dict_, value, initiator) assert self.key not in dict_, \ "Collection was loaded during event handling." - state.get_pending(self.key).remove(value) + state._get_pending_mutation(self.key).remove(value) else: collection.remove_with_event(value, initiator) @@ -935,7 +940,7 @@ class CollectionAttributeImpl(AttributeImpl): return # place a copy of "old" in state.committed_state - state.modified_event(dict_, self, old, True) + state._modified_event(dict_, self, old, True) old_collection = getattr(old, '_sa_adapter') @@ -956,12 +961,12 @@ class CollectionAttributeImpl(AttributeImpl): state.commit(dict_, [self.key]) - if self.key in state.pending: + if self.key in state._pending_mutations: # pending items exist. issue a modified event, # add/remove new items. - state.modified_event(dict_, self, user_data, True) + state._modified_event(dict_, self, user_data, True) - pending = state.pending.pop(self.key) + pending = state._pending_mutations.pop(self.key) added = pending.added_items removed = pending.deleted_items for item in added: @@ -1408,5 +1413,5 @@ def flag_modified(instance, key): """ state, dict_ = instance_state(instance), instance_dict(instance) impl = state.manager[key].impl - state.modified_event(dict_, impl, NO_VALUE) + state._modified_event(dict_, impl, NO_VALUE) diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index ed0d4924e..57c245028 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -64,6 +64,7 @@ class DescriptorProperty(MapperProperty): self.parent.class_, self.key, self.descriptor, + self, lambda: self._comparator_factory(mapper), doc=self.doc ) diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 1a3e52a36..18fc76aa9 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -97,7 +97,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl): if self.key not in state.committed_state: state.committed_state[self.key] = CollectionHistory(self, state) - state.modified_event(dict_, + state._modified_event(dict_, self, attributes.NEVER_SET) diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py index af9ef7841..1012af67a 100644 --- a/lib/sqlalchemy/orm/instrumentation.py +++ b/lib/sqlalchemy/orm/instrumentation.py @@ -23,7 +23,7 @@ An example of full customization is in /examples/custom_attributes. from sqlalchemy.orm import exc, collections, events from operator import attrgetter, itemgetter -from sqlalchemy import event, util +from sqlalchemy import event, util, inspection import weakref from sqlalchemy.orm import state, attributes diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index afabac05a..0771bbf3d 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -33,6 +33,7 @@ from sqlalchemy.orm.util import _INSTRUMENTOR, _class_to_mapper, \ import sys sessionlib = util.importlater("sqlalchemy.orm", "session") properties = util.importlater("sqlalchemy.orm", "properties") +descriptor_props = util.importlater("sqlalchemy.orm", "descriptor_props") __all__ = ( 'Mapper', @@ -1393,12 +1394,35 @@ class Mapper(object): continue yield c - @property - def properties(self): - raise NotImplementedError( - "Public collection of MapperProperty objects is " - "provided by the get_property() and iterate_properties " - "accessors.") + @util.memoized_property + def attr(self): + if _new_mappers: + configure_mappers() + return util.ImmutableProperties(self._props) + + @_memoized_configured_property + def synonyms(self): + return self._filter_properties(descriptor_props.SynonymProperty) + + @_memoized_configured_property + def column_attrs(self): + return self._filter_properties(properties.ColumnProperty) + + @_memoized_configured_property + def relationships(self): + return self._filter_properties(properties.RelationshipProperty) + + @_memoized_configured_property + def composites(self): + return self._filter_properties(descriptor_props.CompositeProperty) + + def _filter_properties(self, type_): + if _new_mappers: + configure_mappers() + return util.ImmutableProperties(dict( + (k, v) for k, v in self._props.iteritems() + if isinstance(v, type_) + )) @_memoized_configured_property def _get_clause(self): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 424795ee4..fd64e7b81 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -99,6 +99,13 @@ class ColumnProperty(StrategizedProperty): else: self.strategy_class = strategies.ColumnLoader + @property + def expression(self): + """Return the primary column or expression for this ColumnProperty. + + """ + return self.columns[0] + def instrument_class(self, mapper): if not self.instrument: return @@ -276,7 +283,6 @@ class RelationshipProperty(StrategizedProperty): else: self.backref = backref - def instrument_class(self, mapper): attributes.register_descriptor( mapper.class_, @@ -805,6 +811,27 @@ class RelationshipProperty(StrategizedProperty): dest_state.get_impl(self.key).set(dest_state, dest_dict, obj, None) + def _value_as_iterable(self, state, dict_, key, + passive=attributes.PASSIVE_OFF): + """Return a list of tuples (state, obj) for the given + key. + + returns an empty list if the value is None/empty/PASSIVE_NO_RESULT + """ + + impl = state.manager[key].impl + x = impl.get(state, dict_, passive=passive) + if x is attributes.PASSIVE_NO_RESULT or x is None: + return [] + elif hasattr(impl, 'get_collection'): + return [ + (attributes.instance_state(o), o) for o in + impl.get_collection(state, dict_, x, passive=passive) + ] + else: + return [(attributes.instance_state(x), x)] + + def cascade_iterator(self, type_, state, dict_, visited_states, halt_on=None): #assert type_ in self.cascade @@ -819,7 +846,7 @@ class RelationshipProperty(StrategizedProperty): get_all_pending(state, dict_) else: - tuples = state.value_as_iterable(dict_, self.key, + tuples = self._value_as_iterable(state, dict_, self.key, passive=passive) skip_pending = type_ == 'refresh-expire' and 'delete-orphan' \ diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 14778705d..87968da82 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -835,7 +835,7 @@ class Session(object): """ for state in self.identity_map.all_states() + list(self._new): - state.detach() + state._detach() self.identity_map = self._identity_cls() self._new = {} @@ -1135,7 +1135,7 @@ class Session(object): state.expire(state.dict, self.identity_map._modified) elif state in self._new: self._new.pop(state) - state.detach() + state._detach() @util.deprecated("0.7", "The non-weak-referencing identity map " "feature is no longer needed.") @@ -1177,11 +1177,11 @@ class Session(object): def _expunge_state(self, state): if state in self._new: self._new.pop(state) - state.detach() + state._detach() elif self.identity_map.contains_state(state): self.identity_map.discard(state) self._deleted.pop(state, None) - state.detach() + state._detach() elif self.transaction: self.transaction._deleted.pop(state, None) diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 30a08faba..64fca8715 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -17,12 +17,12 @@ from sqlalchemy import util from sqlalchemy.orm import exc as orm_exc, attributes, interfaces,\ util as orm_util -from sqlalchemy.orm.attributes import PASSIVE_OFF, PASSIVE_NO_RESULT, \ - SQL_OK, NEVER_SET, ATTR_WAS_SET, NO_VALUE +from sqlalchemy.orm.attributes import PASSIVE_NO_RESULT, \ + SQL_OK, NEVER_SET, ATTR_WAS_SET, NO_VALUE,\ + PASSIVE_NO_INITIALIZE mapperlib = util.importlater("sqlalchemy.orm", "mapperlib") - -import sys +sessionlib = util.importlater("sqlalchemy.orm", "session") class InstanceState(object): """tracks state information at the instance level.""" @@ -47,22 +47,81 @@ class InstanceState(object): self.committed_state = {} @util.memoized_property + def attr(self): + return util.ImmutableProperties( + dict( + (key, InspectAttr(self, key)) + for key in self.manager + ) + ) + + @property + def transient(self): + return self.key is None and \ + not self._attached + + @property + def pending(self): + return self.key is None and \ + self._attached + + @property + def persistent(self): + return self.key is not None and \ + self._attached + + @property + def detached(self): + return self.key is not None and \ + not self._attached + + @property + def _attached(self): + return self.session_id is not None and \ + self.session_id in sessionlib._sessions + + @property + def session(self): + return sessionlib._state_session(self) + + @property + def object(self): + return self.obj() + + @property + def identity(self): + if self.key is None: + return None + else: + return self.key[1] + + @property + def identity_key(self): + # TODO: just change .key to .identity_key across + # the board ? probably + return self.key + + @util.memoized_property def parents(self): return {} @util.memoized_property - def pending(self): + def _pending_mutations(self): return {} + @util.memoized_property + def mapper(self): + return self.manager.mapper + @property def has_identity(self): return bool(self.key) - def detach(self): + def _detach(self): self.session_id = None - def dispose(self): - self.detach() + def _dispose(self): + self._detach() del self.obj def _cleanup(self, ref): @@ -106,35 +165,16 @@ class InstanceState(object): def get_impl(self, key): return self.manager[key].impl - def get_pending(self, key): - if key not in self.pending: - self.pending[key] = PendingCollection() - return self.pending[key] - - def value_as_iterable(self, dict_, key, passive=PASSIVE_OFF): - """Return a list of tuples (state, obj) for the given - key. - - returns an empty list if the value is None/empty/PASSIVE_NO_RESULT - """ - - impl = self.manager[key].impl - x = impl.get(self, dict_, passive=passive) - if x is PASSIVE_NO_RESULT or x is None: - return [] - elif hasattr(impl, 'get_collection'): - return [ - (attributes.instance_state(o), o) for o in - impl.get_collection(self, dict_, x, passive=passive) - ] - else: - return [(attributes.instance_state(x), x)] + def _get_pending_mutation(self, key): + if key not in self._pending_mutations: + self._pending_mutations[key] = PendingCollection() + return self._pending_mutations[key] def __getstate__(self): d = {'instance':self.obj()} d.update( (k, self.__dict__[k]) for k in ( - 'committed_state', 'pending', 'modified', 'expired', + 'committed_state', '_pending_mutations', 'modified', 'expired', 'callables', 'key', 'parents', 'load_options', 'mutable_dict', 'class_', ) if k in self.__dict__ @@ -169,7 +209,7 @@ class InstanceState(object): mapperlib.configure_mappers() self.committed_state = state.get('committed_state', {}) - self.pending = state.get('pending', {}) + self._pending_mutations = state.get('_pending_mutations', {}) self.parents = state.get('parents', {}) self.modified = state.get('modified', False) self.expired = state.get('expired', False) @@ -234,7 +274,7 @@ class InstanceState(object): self.committed_state.clear() - self.__dict__.pop('pending', None) + self.__dict__.pop('_pending_mutations', None) self.__dict__.pop('mutable_dict', None) # clear out 'parents' collection. not @@ -252,7 +292,7 @@ class InstanceState(object): self.manager.dispatch.expire(self, None) def expire_attributes(self, dict_, attribute_names): - pending = self.__dict__.get('pending', None) + pending = self.__dict__.get('_pending_mutations', None) mutable_dict = self.mutable_dict for key in attribute_names: @@ -336,7 +376,7 @@ class InstanceState(object): def _is_really_none(self): return self.obj() - def modified_event(self, dict_, attr, previous, collection=False): + def _modified_event(self, dict_, attr, previous, collection=False): if attr.key not in self.committed_state: if collection: if previous is NEVER_SET: @@ -415,7 +455,7 @@ class InstanceState(object): """ self.committed_state.clear() - self.__dict__.pop('pending', None) + self.__dict__.pop('_pending_mutations', None) callables = self.callables for key in list(callables): @@ -432,6 +472,27 @@ class InstanceState(object): self.modified = self.expired = False self._strong_obj = None +class InspectAttr(object): + """Provide inspection interface to an object's state.""" + + def __init__(self, state, key): + self.state = state + self.key = key + + @property + def loaded_value(self): + return self.state.dict.get(self.key, NO_VALUE) + + @property + def value(self): + return self.state.manager[self.key].__get__( + self.state.obj(), self.state.class_) + + @property + def history(self): + return self.state.get_history(self.key, + PASSIVE_NO_INITIALIZE) + class MutableAttrInstanceState(InstanceState): """InstanceState implementation for objects that reference 'mutable' attributes. @@ -524,7 +585,7 @@ class MutableAttrInstanceState(InstanceState): instance_dict = self._instance_dict() if instance_dict: instance_dict.discard(self) - self.dispose() + self._dispose() def __resurrect(self): """A substitute for the obj() weakref function which resurrects.""" diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index c14c22bac..f4dfef3d5 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -5,7 +5,7 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php -from sqlalchemy import sql, util, event, exc as sa_exc +from sqlalchemy import sql, util, event, exc as sa_exc, inspection from sqlalchemy.sql import expression, util as sql_util, operators from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\ PropComparator, MapperProperty @@ -642,15 +642,34 @@ def object_mapper(instance): Raises UnmappedInstanceError if no mapping is configured. + This function is available via the inspection system as:: + + inspect(instance).mapper + + """ + return object_state(instance).mapper + +@inspection._inspects(object) +def object_state(instance): + """Given an object, return the primary Mapper associated with the object + instance. + + Raises UnmappedInstanceError if no mapping is configured. + + This function is available via the inspection system as:: + + inspect(instance) + """ try: - state = attributes.instance_state(instance) - return state.manager.mapper + return attributes.instance_state(instance) except exc.UnmappedClassError: raise exc.UnmappedInstanceError(instance) except exc.NO_STATE: raise exc.UnmappedInstanceError(instance) + +@inspection._inspects(type) def class_mapper(class_, compile=True): """Given a class, return the primary :class:`.Mapper` associated with the key. @@ -659,6 +678,10 @@ def class_mapper(class_, compile=True): on the given class, or :class:`.ArgumentError` if a non-class object is passed. + This function is available via the inspection system as:: + + inspect(some_mapped_class) + """ try: |
