summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/attributes.py41
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py1
-rw-r--r--lib/sqlalchemy/orm/dynamic.py2
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py2
-rw-r--r--lib/sqlalchemy/orm/mapper.py36
-rw-r--r--lib/sqlalchemy/orm/properties.py31
-rw-r--r--lib/sqlalchemy/orm/session.py8
-rw-r--r--lib/sqlalchemy/orm/state.py137
-rw-r--r--lib/sqlalchemy/orm/util.py29
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: