Remove Elixir library

Update SQLAlchemy
This commit is contained in:
Ruud
2014-01-26 16:29:16 +01:00
parent f91081e39c
commit 1120b4ab51
178 changed files with 43610 additions and 35304 deletions
+293 -243
View File
@@ -1,5 +1,5 @@
# orm/state.py
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -11,33 +11,30 @@ defines a large part of the ORM's interactivity.
"""
from sqlalchemy.util import EMPTY_SET
import weakref
from sqlalchemy import util
from .. import util
from . import exc as orm_exc, interfaces
from .path_registry import PathRegistry
from .base import PASSIVE_NO_RESULT, SQL_OK, NEVER_SET, ATTR_WAS_SET, \
NO_VALUE, PASSIVE_NO_INITIALIZE, INIT_OK, PASSIVE_OFF
from . import base
from sqlalchemy.orm import exc as orm_exc, attributes, interfaces,\
util as orm_util
from sqlalchemy.orm.attributes import PASSIVE_OFF, PASSIVE_NO_RESULT, \
PASSIVE_NO_FETCH, NEVER_SET, ATTR_WAS_SET, NO_VALUE
mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
import sys
class InstanceState(object):
class InstanceState(interfaces._InspectionAttr):
"""tracks state information at the instance level."""
session_id = None
key = None
runid = None
load_options = EMPTY_SET
load_options = util.EMPTY_SET
load_path = ()
insert_order = None
mutable_dict = None
_strong_obj = None
modified = False
expired = False
deleted = False
_load_pending = False
is_instance = True
def __init__(self, obj, manager):
self.class_ = obj.__class__
@@ -46,23 +43,127 @@ class InstanceState(object):
self.callables = {}
self.committed_state = {}
@util.memoized_property
def attrs(self):
"""Return a namespace representing each attribute on
the mapped object, including its current value
and history.
The returned object is an instance of :class:`.AttributeState`.
"""
return util.ImmutableProperties(
dict(
(key, AttributeState(self, key))
for key in self.manager
)
)
@property
def transient(self):
"""Return true if the object is transient."""
return self.key is None and \
not self._attached
@property
def pending(self):
"""Return true if the object is pending."""
return self.key is None and \
self._attached
@property
def persistent(self):
"""Return true if the object is persistent."""
return self.key is not None and \
self._attached
@property
def detached(self):
"""Return true if the object is detached."""
return self.key is not None and \
not self._attached
@property
@util.dependencies("sqlalchemy.orm.session")
def _attached(self, sessionlib):
return self.session_id is not None and \
self.session_id in sessionlib._sessions
@property
@util.dependencies("sqlalchemy.orm.session")
def session(self, sessionlib):
"""Return the owning :class:`.Session` for this instance,
or ``None`` if none available."""
return sessionlib._state_session(self)
@property
def object(self):
"""Return the mapped object represented by this
:class:`.InstanceState`."""
return self.obj()
@property
def identity(self):
"""Return the mapped identity of the mapped object.
This is the primary key identity as persisted by the ORM
which can always be passed directly to
:meth:`.Query.get`.
Returns ``None`` if the object has no primary key identity.
.. note::
An object which is transient or pending
does **not** have a mapped identity until it is flushed,
even if its attributes include primary key values.
"""
if self.key is None:
return None
else:
return self.key[1]
@property
def identity_key(self):
"""Return the identity key for the mapped object.
This is the key used to locate the object within
the :attr:`.Session.identity_map` mapping. It contains
the identity as returned by :attr:`.identity` within it.
"""
# 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 the :class:`.Mapper` used for this mapepd object."""
return self.manager.mapper
@property
def has_identity(self):
"""Return ``True`` if this object has an identity key.
This should always have the same value as the
expression ``state.persistent or state.detached``.
"""
return bool(self.key)
def detach(self):
self.session_id = None
def _detach(self):
self.session_id = self._strong_obj = None
def dispose(self):
self.detach()
def _dispose(self):
self._detach()
del self.obj
def _cleanup(self, ref):
@@ -71,7 +172,7 @@ class InstanceState(object):
instance_dict.discard(self)
self.callables = {}
self.session_id = None
self.session_id = self._strong_obj = None
del self.obj
def obj(self):
@@ -81,19 +182,16 @@ class InstanceState(object):
def dict(self):
o = self.obj()
if o is not None:
return attributes.instance_dict(o)
return base.instance_dict(o)
else:
return {}
def initialize_instance(*mixed, **kwargs):
def _initialize_instance(*mixed, **kwargs):
self, instance, args = mixed[0], mixed[1], mixed[2:]
manager = self.manager
manager.dispatch.init(self, args, kwargs)
#if manager.mutable_attributes:
# assert self.__class__ is MutableAttrInstanceState
try:
return manager.original_init(*mixed[1:], **kwargs)
except:
@@ -106,49 +204,29 @@ 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(
state_dict = {'instance': self.obj()}
state_dict.update(
(k, self.__dict__[k]) for k in (
'committed_state', 'pending', 'modified', 'expired',
'callables', 'key', 'parents', 'load_options', 'mutable_dict',
'committed_state', '_pending_mutations', 'modified', 'expired',
'callables', 'key', 'parents', 'load_options',
'class_',
) if k in self.__dict__
)
if self.load_path:
d['load_path'] = interfaces.serialize_path(self.load_path)
state_dict['load_path'] = self.load_path.serialize()
self.manager.dispatch.pickle(self, d)
state_dict['manager'] = self.manager._serialize(self, state_dict)
return d
return state_dict
def __setstate__(self, state):
from sqlalchemy.orm import instrumentation
inst = state['instance']
def __setstate__(self, state_dict):
inst = state_dict['instance']
if inst is not None:
self.obj = weakref.ref(inst, self._cleanup)
self.class_ = inst.__class__
@@ -157,60 +235,49 @@ class InstanceState(object):
# due to storage of state in "parents". "class_"
# also new.
self.obj = None
self.class_ = state['class_']
self.manager = manager = instrumentation.manager_of_class(self.class_)
if manager is None:
raise orm_exc.UnmappedInstanceError(
inst,
"Cannot deserialize object of type %r - no mapper() has"
" been configured for this class within the current Python process!" %
self.class_)
elif manager.is_mapped and not manager.mapper.configured:
mapperlib.configure_mappers()
self.class_ = state_dict['class_']
self.committed_state = state.get('committed_state', {})
self.pending = state.get('pending', {})
self.parents = state.get('parents', {})
self.modified = state.get('modified', False)
self.expired = state.get('expired', False)
self.callables = state.get('callables', {})
if self.modified:
self._strong_obj = inst
self.committed_state = state_dict.get('committed_state', {})
self._pending_mutations = state_dict.get('_pending_mutations', {})
self.parents = state_dict.get('parents', {})
self.modified = state_dict.get('modified', False)
self.expired = state_dict.get('expired', False)
self.callables = state_dict.get('callables', {})
self.__dict__.update([
(k, state[k]) for k in (
'key', 'load_options', 'mutable_dict'
) if k in state
(k, state_dict[k]) for k in (
'key', 'load_options',
) if k in state_dict
])
if 'load_path' in state:
self.load_path = interfaces.deserialize_path(state['load_path'])
if 'load_path' in state_dict:
self.load_path = PathRegistry.\
deserialize(state_dict['load_path'])
# setup _sa_instance_state ahead of time so that
# unpickle events can access the object normally.
# see [ticket:2362]
manager.setup_instance(inst, self)
manager.dispatch.unpickle(self, state)
state_dict['manager'](self, inst, state_dict)
def initialize(self, key):
def _initialize(self, key):
"""Set this attribute to an empty value or collection,
based on the AttributeImpl in use."""
self.manager.get_impl(key).initialize(self, self.dict)
def reset(self, dict_, key):
def _reset(self, dict_, key):
"""Remove the given attribute and any
callables associated with it."""
dict_.pop(key, None)
old = dict_.pop(key, None)
if old is not None and self.manager[key].impl.collection:
self.manager[key].impl._invalidate_collection(old)
self.callables.pop(key, None)
def expire_attribute_pre_commit(self, dict_, key):
def _expire_attribute_pre_commit(self, dict_, key):
"""a fast expire that can be called by column loaders during a load.
The additional bookkeeping is finished up in commit_all().
Should only be called for scalar attributes.
This method is actually called a lot with joined-table
loading, when the second table isn't present in the result.
@@ -218,65 +285,73 @@ class InstanceState(object):
dict_.pop(key, None)
self.callables[key] = self
def set_callable(self, dict_, key, callable_):
"""Remove the given attribute and set the given callable
as a loader."""
@classmethod
def _row_processor(cls, manager, fn, key):
impl = manager[key].impl
if impl.collection:
def _set_callable(state, dict_, row):
old = dict_.pop(key, None)
if old is not None:
impl._invalidate_collection(old)
state.callables[key] = fn
else:
def _set_callable(state, dict_, row):
state.callables[key] = fn
return _set_callable
dict_.pop(key, None)
self.callables[key] = callable_
def expire(self, dict_, modified_set):
def _expire(self, dict_, modified_set):
self.expired = True
if self.modified:
modified_set.discard(self)
self.modified = False
self._strong_obj = None
self.committed_state.clear()
self.__dict__.pop('pending', None)
self.__dict__.pop('mutable_dict', None)
InstanceState._pending_mutations._reset(self)
# clear out 'parents' collection. not
# entirely clear how we can best determine
# which to remove, or not.
self.__dict__.pop('parents', None)
InstanceState.parents._reset(self)
for key in self.manager:
impl = self.manager[key].impl
if impl.accepts_scalar_loader and \
(impl.expire_missing or key in dict_):
(impl.expire_missing or key in dict_):
self.callables[key] = self
dict_.pop(key, None)
old = dict_.pop(key, None)
if impl.collection and old is not None:
impl._invalidate_collection(old)
self.manager.dispatch.expire(self, None)
def expire_attributes(self, dict_, attribute_names):
pending = self.__dict__.get('pending', None)
mutable_dict = self.mutable_dict
def _expire_attributes(self, dict_, attribute_names):
pending = self.__dict__.get('_pending_mutations', None)
for key in attribute_names:
impl = self.manager[key].impl
if impl.accepts_scalar_loader:
self.callables[key] = self
dict_.pop(key, None)
old = dict_.pop(key, None)
if impl.collection and old is not None:
impl._invalidate_collection(old)
self.committed_state.pop(key, None)
if mutable_dict:
mutable_dict.pop(key, None)
if pending:
pending.pop(key, None)
self.manager.dispatch.expire(self, attribute_names)
def __call__(self, passive):
def __call__(self, state, passive):
"""__call__ allows the InstanceState to act as a deferred
callable for loading expired attributes, which is also
serializable (picklable).
"""
if passive is PASSIVE_NO_FETCH:
if not passive & SQL_OK:
return PASSIVE_NO_RESULT
toload = self.expired_attributes.\
@@ -305,7 +380,6 @@ class InstanceState(object):
return set(keys).intersection(self.manager).\
difference(self.committed_state)
@property
def unloaded(self):
"""Return the set of keys which do not have a loaded value.
@@ -318,6 +392,13 @@ class InstanceState(object):
difference(self.committed_state).\
difference(self.dict)
@property
def _unloaded_non_object(self):
return self.unloaded.intersection(
attr for attr in self.manager
if self.manager[attr].impl.accepts_scalar_loader
)
@property
def expired_attributes(self):
"""Return the set of keys which are 'expired' to be loaded by
@@ -333,10 +414,9 @@ class InstanceState(object):
def _instance_dict(self):
return None
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 not attr.send_modified_events:
return
if attr.key not in self.committed_state:
if collection:
if previous is NEVER_SET:
@@ -348,29 +428,33 @@ class InstanceState(object):
self.committed_state[attr.key] = previous
# the "or not self.modified" is defensive at
# this point. The assertion below is expected
# to be True:
# assert self._strong_obj is None or self.modified
if self._strong_obj is None or not self.modified:
if (self.session_id and self._strong_obj is None) \
or not self.modified:
instance_dict = self._instance_dict()
if instance_dict:
instance_dict._modified.add(self)
self._strong_obj = self.obj()
if self._strong_obj is None:
# only create _strong_obj link if attached
# to a session
inst = self.obj()
if self.session_id:
self._strong_obj = inst
if inst is None:
raise orm_exc.ObjectDereferencedError(
"Can't emit change event for attribute '%s' - "
"parent object of type %s has been garbage "
"collected."
% (
self.manager[attr.key],
orm_util.state_class_str(self)
base.state_class_str(self)
))
self.modified = True
def commit(self, dict_, keys):
def _commit(self, dict_, keys):
"""Commit attributes.
This is used by a partial-attribute load operation to mark committed
@@ -380,16 +464,8 @@ class InstanceState(object):
this step if a value was not populated in state.dict.
"""
class_manager = self.manager
if class_manager.mutable_attributes:
for key in keys:
if key in dict_ and key in class_manager.mutable_attributes:
self.committed_state[key] = self.manager[key].impl.copy(dict_[key])
else:
self.committed_state.pop(key, None)
else:
for key in keys:
self.committed_state.pop(key, None)
for key in keys:
self.committed_state.pop(key, None)
self.expired = False
@@ -398,7 +474,7 @@ class InstanceState(object):
intersection(dict_):
del self.callables[key]
def commit_all(self, dict_, instance_dict=None):
def _commit_all(self, dict_, instance_dict=None):
"""commit all attributes unconditionally.
This is used after a flush() or a full load/refresh
@@ -409,137 +485,112 @@ class InstanceState(object):
- the "modified" flag is set to False
- any "expired" markers/callables for attributes loaded are removed.
Attributes marked as "expired" can potentially remain "expired" after this step
if a value was not populated in state.dict.
Attributes marked as "expired" can potentially remain
"expired" after this step if a value was not populated in state.dict.
"""
self._commit_all_states([(self, dict_)], instance_dict)
self.committed_state.clear()
self.__dict__.pop('pending', None)
@classmethod
def _commit_all_states(self, iter, instance_dict=None):
"""Mass version of commit_all()."""
callables = self.callables
for key in list(callables):
if key in dict_ and callables[key] is self:
del callables[key]
for state, dict_ in iter:
state.committed_state.clear()
InstanceState._pending_mutations._reset(state)
for key in self.manager.mutable_attributes:
if key in dict_:
self.committed_state[key] = self.manager[key].impl.copy(dict_[key])
callables = state.callables
for key in list(callables):
if key in dict_ and callables[key] is state:
del callables[key]
if instance_dict and self.modified:
instance_dict._modified.discard(self)
if instance_dict and state.modified:
instance_dict._modified.discard(state)
self.modified = self.expired = False
self._strong_obj = None
state.modified = state.expired = False
state._strong_obj = None
class MutableAttrInstanceState(InstanceState):
"""InstanceState implementation for objects that reference 'mutable'
attributes.
Has a more involved "cleanup" handler that checks mutable attributes
for changes upon dereference, resurrecting if needed.
class AttributeState(object):
"""Provide an inspection interface corresponding
to a particular attribute on a particular mapped object.
The :class:`.AttributeState` object is accessed
via the :attr:`.InstanceState.attrs` collection
of a particular :class:`.InstanceState`::
from sqlalchemy import inspect
insp = inspect(some_mapped_object)
attr_state = insp.attrs.some_attribute
"""
@util.memoized_property
def mutable_dict(self):
return {}
def _get_modified(self, dict_=None):
if self.__dict__.get('modified', False):
return True
else:
if dict_ is None:
dict_ = self.dict
for key in self.manager.mutable_attributes:
if self.manager[key].impl.check_mutable_modified(self, dict_):
return True
else:
return False
def _set_modified(self, value):
self.__dict__['modified'] = value
modified = property(_get_modified, _set_modified)
def __init__(self, state, key):
self.state = state
self.key = key
@property
def unmodified(self):
"""a set of keys which have no uncommitted changes"""
def loaded_value(self):
"""The current value of this attribute as loaded from the database.
dict_ = self.dict
return set([
key for key in self.manager
if (key not in self.committed_state or
(key in self.manager.mutable_attributes and
not self.manager[key].impl.check_mutable_modified(self, dict_)))])
def unmodified_intersection(self, keys):
"""Return self.unmodified.intersection(keys)."""
dict_ = self.dict
return set([
key for key in keys
if (key not in self.committed_state or
(key in self.manager.mutable_attributes and
not self.manager[key].impl.check_mutable_modified(self, dict_)))])
def _is_really_none(self):
"""do a check modified/resurrect.
This would be called in the extremely rare
race condition that the weakref returned None but
the cleanup handler had not yet established the
__resurrect callable as its replacement.
If the value has not been loaded, or is otherwise not present
in the object's dictionary, returns NO_VALUE.
"""
if self.modified:
self.obj = self.__resurrect
return self.obj()
else:
return None
return self.state.dict.get(self.key, NO_VALUE)
def reset(self, dict_, key):
self.mutable_dict.pop(key, None)
InstanceState.reset(self, dict_, key)
@property
def value(self):
"""Return the value of this attribute.
def _cleanup(self, ref):
"""weakref callback.
This method may be called by an asynchronous
gc.
If the state shows pending changes, the weakref
is replaced by the __resurrect callable which will
re-establish an object reference on next access,
else removes this InstanceState from the owning
identity map, if any.
This operation is equivalent to accessing the object's
attribute directly or via ``getattr()``, and will fire
off any pending loader callables if needed.
"""
if self._get_modified(self.mutable_dict):
self.obj = self.__resurrect
else:
instance_dict = self._instance_dict()
if instance_dict:
instance_dict.discard(self)
self.dispose()
return self.state.manager[self.key].__get__(
self.state.obj(), self.state.class_)
def __resurrect(self):
"""A substitute for the obj() weakref function which resurrects."""
@property
def history(self):
"""Return the current pre-flush change history for
this attribute, via the :class:`.History` interface.
# store strong ref'ed version of the object; will revert
# to weakref when changes are persisted
obj = self.manager.new_instance(state=self)
self.obj = weakref.ref(obj, self._cleanup)
self._strong_obj = obj
obj.__dict__.update(self.mutable_dict)
This method will **not** emit loader callables if the value of the
attribute is unloaded.
.. seealso::
:meth:`.AttributeState.load_history` - retrieve history
using loader callables if the value is not locally present.
:func:`.attributes.get_history` - underlying function
"""
return self.state.get_history(self.key,
PASSIVE_NO_INITIALIZE)
def load_history(self):
"""Return the current pre-flush change history for
this attribute, via the :class:`.History` interface.
This method **will** emit loader callables if the value of the
attribute is unloaded.
.. seealso::
:attr:`.AttributeState.history`
:func:`.attributes.get_history` - underlying function
.. versionadded:: 0.9.0
"""
return self.state.get_history(self.key,
PASSIVE_OFF ^ INIT_OK)
# re-establishes identity attributes from the key
self.manager.dispatch.resurrect(self)
return obj
class PendingCollection(object):
"""A writable placeholder for an unloaded collection.
@@ -564,4 +615,3 @@ class PendingCollection(object):
self.added_items.remove(value)
else:
self.deleted_items.add(value)