Packages update
This commit is contained in:
+159
-141
@@ -1,19 +1,28 @@
|
||||
# orm/state.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2012 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
|
||||
|
||||
"""Defines instrumentation of instances.
|
||||
|
||||
This module is usually not directly visible to user applications, but
|
||||
defines a large part of the ORM's interactivity.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.util import EMPTY_SET
|
||||
import weakref
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.orm.attributes import PASSIVE_NO_RESULT, PASSIVE_OFF, \
|
||||
NEVER_SET, NO_VALUE, manager_of_class, \
|
||||
ATTR_WAS_SET
|
||||
from sqlalchemy.orm import attributes, exc as orm_exc, interfaces
|
||||
|
||||
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
|
||||
attributes.state = sys.modules['sqlalchemy.orm.state']
|
||||
|
||||
class InstanceState(object):
|
||||
"""tracks state information at the instance level."""
|
||||
@@ -34,10 +43,8 @@ class InstanceState(object):
|
||||
self.class_ = obj.__class__
|
||||
self.manager = manager
|
||||
self.obj = weakref.ref(obj, self._cleanup)
|
||||
|
||||
@util.memoized_property
|
||||
def committed_state(self):
|
||||
return {}
|
||||
self.callables = {}
|
||||
self.committed_state = {}
|
||||
|
||||
@util.memoized_property
|
||||
def parents(self):
|
||||
@@ -47,20 +54,12 @@ class InstanceState(object):
|
||||
def pending(self):
|
||||
return {}
|
||||
|
||||
@util.memoized_property
|
||||
def callables(self):
|
||||
return {}
|
||||
|
||||
@property
|
||||
def has_identity(self):
|
||||
return bool(self.key)
|
||||
|
||||
def detach(self):
|
||||
if self.session_id:
|
||||
try:
|
||||
del self.session_id
|
||||
except AttributeError:
|
||||
pass
|
||||
self.session_id = None
|
||||
|
||||
def dispose(self):
|
||||
self.detach()
|
||||
@@ -69,13 +68,11 @@ class InstanceState(object):
|
||||
def _cleanup(self, ref):
|
||||
instance_dict = self._instance_dict()
|
||||
if instance_dict:
|
||||
try:
|
||||
instance_dict.remove(self)
|
||||
except AssertionError:
|
||||
pass
|
||||
# remove possible cycles
|
||||
self.__dict__.pop('callables', None)
|
||||
self.dispose()
|
||||
instance_dict.discard(self)
|
||||
|
||||
self.callables = {}
|
||||
self.session_id = None
|
||||
del self.obj
|
||||
|
||||
def obj(self):
|
||||
return None
|
||||
@@ -88,90 +85,88 @@ class InstanceState(object):
|
||||
else:
|
||||
return {}
|
||||
|
||||
@property
|
||||
def sort_key(self):
|
||||
return self.key and self.key[1] or (self.insert_order, )
|
||||
|
||||
def initialize_instance(*mixed, **kwargs):
|
||||
self, instance, args = mixed[0], mixed[1], mixed[2:]
|
||||
manager = self.manager
|
||||
|
||||
for fn in manager.events.on_init:
|
||||
fn(self, instance, args, kwargs)
|
||||
manager.dispatch.init(self, args, kwargs)
|
||||
|
||||
# LESSTHANIDEAL:
|
||||
# adjust for the case where the InstanceState was created before
|
||||
# mapper compilation, and this actually needs to be a MutableAttrInstanceState
|
||||
if manager.mutable_attributes and self.__class__ is not MutableAttrInstanceState:
|
||||
self.__class__ = MutableAttrInstanceState
|
||||
self.obj = weakref.ref(self.obj(), self._cleanup)
|
||||
self.mutable_dict = {}
|
||||
#if manager.mutable_attributes:
|
||||
# assert self.__class__ is MutableAttrInstanceState
|
||||
|
||||
try:
|
||||
return manager.events.original_init(*mixed[1:], **kwargs)
|
||||
return manager.original_init(*mixed[1:], **kwargs)
|
||||
except:
|
||||
for fn in manager.events.on_init_failure:
|
||||
fn(self, instance, args, kwargs)
|
||||
manager.dispatch.init_failure(self, args, kwargs)
|
||||
raise
|
||||
|
||||
def get_history(self, key, **kwargs):
|
||||
return self.manager.get_impl(key).get_history(self, self.dict, **kwargs)
|
||||
def get_history(self, key, passive):
|
||||
return self.manager[key].impl.get_history(self, self.dict, passive)
|
||||
|
||||
def get_impl(self, key):
|
||||
return self.manager.get_impl(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, key, passive=PASSIVE_OFF):
|
||||
"""return an InstanceState attribute as a list,
|
||||
regardless of it being a scalar or collection-based
|
||||
attribute.
|
||||
def value_as_iterable(self, dict_, key, passive=PASSIVE_OFF):
|
||||
"""Return a list of tuples (state, obj) for the given
|
||||
key.
|
||||
|
||||
returns None if passive is not PASSIVE_OFF and the getter returns
|
||||
PASSIVE_NO_RESULT.
|
||||
returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
|
||||
"""
|
||||
|
||||
impl = self.get_impl(key)
|
||||
dict_ = self.dict
|
||||
impl = self.manager[key].impl
|
||||
x = impl.get(self, dict_, passive=passive)
|
||||
if x is PASSIVE_NO_RESULT:
|
||||
return None
|
||||
if x is PASSIVE_NO_RESULT or x is None:
|
||||
return []
|
||||
elif hasattr(impl, 'get_collection'):
|
||||
return impl.get_collection(self, dict_, x, passive=passive)
|
||||
return [
|
||||
(attributes.instance_state(o), o) for o in
|
||||
impl.get_collection(self, dict_, x, passive=passive)
|
||||
]
|
||||
else:
|
||||
return [x]
|
||||
|
||||
def _run_on_load(self, instance):
|
||||
self.manager.events.run('on_load', instance)
|
||||
return [(attributes.instance_state(x), x)]
|
||||
|
||||
def __getstate__(self):
|
||||
d = {'instance':self.obj()}
|
||||
|
||||
d.update(
|
||||
(k, self.__dict__[k]) for k in (
|
||||
'committed_state', 'pending', 'parents', 'modified', 'expired',
|
||||
'callables', 'key', 'load_options', 'mutable_dict'
|
||||
'committed_state', 'pending', 'modified', 'expired',
|
||||
'callables', 'key', 'parents', 'load_options', 'mutable_dict',
|
||||
'class_',
|
||||
) if k in self.__dict__
|
||||
)
|
||||
if self.load_path:
|
||||
d['load_path'] = interfaces.serialize_path(self.load_path)
|
||||
|
||||
self.manager.dispatch.pickle(self, d)
|
||||
|
||||
return d
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.obj = weakref.ref(state['instance'], self._cleanup)
|
||||
self.class_ = state['instance'].__class__
|
||||
self.manager = manager = manager_of_class(self.class_)
|
||||
from sqlalchemy.orm import instrumentation
|
||||
inst = state['instance']
|
||||
if inst is not None:
|
||||
self.obj = weakref.ref(inst, self._cleanup)
|
||||
self.class_ = inst.__class__
|
||||
else:
|
||||
# None being possible here generally new as of 0.7.4
|
||||
# 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(
|
||||
state['instance'],
|
||||
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.compiled:
|
||||
manager.mapper.compile()
|
||||
elif manager.is_mapped and not manager.mapper.configured:
|
||||
mapperlib.configure_mappers()
|
||||
|
||||
self.committed_state = state.get('committed_state', {})
|
||||
self.pending = state.get('pending', {})
|
||||
@@ -181,7 +176,7 @@ class InstanceState(object):
|
||||
self.callables = state.get('callables', {})
|
||||
|
||||
if self.modified:
|
||||
self._strong_obj = state['instance']
|
||||
self._strong_obj = inst
|
||||
|
||||
self.__dict__.update([
|
||||
(k, state[k]) for k in (
|
||||
@@ -192,6 +187,12 @@ class InstanceState(object):
|
||||
if 'load_path' in state:
|
||||
self.load_path = interfaces.deserialize_path(state['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)
|
||||
|
||||
def initialize(self, key):
|
||||
"""Set this attribute to an empty value or collection,
|
||||
based on the AttributeImpl in use."""
|
||||
@@ -224,62 +225,59 @@ class InstanceState(object):
|
||||
dict_.pop(key, None)
|
||||
self.callables[key] = callable_
|
||||
|
||||
def expire_attributes(self, dict_, attribute_names, instance_dict=None):
|
||||
"""Expire all or a group of attributes.
|
||||
def expire(self, dict_, modified_set):
|
||||
self.expired = True
|
||||
if self.modified:
|
||||
modified_set.discard(self)
|
||||
|
||||
If all attributes are expired, the "expired" flag is set to True.
|
||||
self.modified = False
|
||||
|
||||
"""
|
||||
# we would like to assert that 'self.key is not None' here,
|
||||
# but there are many cases where the mapper will expire
|
||||
# a newly persisted instance within the flush, before the
|
||||
# key is assigned, and even cases where the attribute refresh
|
||||
# occurs fully, within the flush(), before this key is assigned.
|
||||
# the key is assigned late within the flush() to assist in
|
||||
# "key switch" bookkeeping scenarios.
|
||||
self.committed_state.clear()
|
||||
|
||||
if attribute_names is None:
|
||||
attribute_names = self.manager.keys()
|
||||
self.expired = True
|
||||
if self.modified:
|
||||
if not instance_dict:
|
||||
instance_dict = self._instance_dict()
|
||||
if instance_dict:
|
||||
instance_dict._modified.discard(self)
|
||||
else:
|
||||
instance_dict._modified.discard(self)
|
||||
self.__dict__.pop('pending', None)
|
||||
self.__dict__.pop('mutable_dict', None)
|
||||
|
||||
self.modified = False
|
||||
filter_deferred = True
|
||||
else:
|
||||
filter_deferred = False
|
||||
# clear out 'parents' collection. not
|
||||
# entirely clear how we can best determine
|
||||
# which to remove, or not.
|
||||
self.__dict__.pop('parents', None)
|
||||
|
||||
to_clear = (
|
||||
self.__dict__.get('pending', None),
|
||||
self.__dict__.get('committed_state', None),
|
||||
self.mutable_dict
|
||||
)
|
||||
|
||||
for key in attribute_names:
|
||||
for key in self.manager:
|
||||
impl = self.manager[key].impl
|
||||
if impl.accepts_scalar_loader and \
|
||||
(not filter_deferred or impl.expire_missing or key in dict_):
|
||||
(impl.expire_missing or key in dict_):
|
||||
self.callables[key] = self
|
||||
dict_.pop(key, None)
|
||||
|
||||
for d in to_clear:
|
||||
if d is not None:
|
||||
d.pop(key, None)
|
||||
self.manager.dispatch.expire(self, None)
|
||||
|
||||
def __call__(self, **kw):
|
||||
def expire_attributes(self, dict_, attribute_names):
|
||||
pending = self.__dict__.get('pending', None)
|
||||
mutable_dict = self.mutable_dict
|
||||
|
||||
for key in attribute_names:
|
||||
impl = self.manager[key].impl
|
||||
if impl.accepts_scalar_loader:
|
||||
self.callables[key] = self
|
||||
dict_.pop(key, None)
|
||||
|
||||
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):
|
||||
"""__call__ allows the InstanceState to act as a deferred
|
||||
callable for loading expired attributes, which is also
|
||||
serializable (picklable).
|
||||
|
||||
"""
|
||||
|
||||
if kw.get('passive') is attributes.PASSIVE_NO_FETCH:
|
||||
return attributes.PASSIVE_NO_RESULT
|
||||
if passive is PASSIVE_NO_FETCH:
|
||||
return PASSIVE_NO_RESULT
|
||||
|
||||
toload = self.expired_attributes.\
|
||||
intersection(self.unmodified)
|
||||
@@ -301,6 +299,13 @@ class InstanceState(object):
|
||||
|
||||
return set(self.manager).difference(self.committed_state)
|
||||
|
||||
def unmodified_intersection(self, keys):
|
||||
"""Return self.unmodified.intersection(keys)."""
|
||||
|
||||
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.
|
||||
@@ -331,21 +336,18 @@ class InstanceState(object):
|
||||
def _is_really_none(self):
|
||||
return self.obj()
|
||||
|
||||
def modified_event(self, dict_, attr, should_copy, previous, passive=PASSIVE_OFF):
|
||||
def modified_event(self, dict_, attr, previous, collection=False):
|
||||
if attr.key not in self.committed_state:
|
||||
if previous is NEVER_SET:
|
||||
if passive:
|
||||
if collection:
|
||||
if previous is NEVER_SET:
|
||||
if attr.key in dict_:
|
||||
previous = dict_[attr.key]
|
||||
else:
|
||||
previous = attr.get(self, dict_)
|
||||
|
||||
if should_copy and previous not in (None, NO_VALUE, NEVER_SET):
|
||||
previous = attr.copy(previous)
|
||||
if previous not in (None, NO_VALUE, NEVER_SET):
|
||||
previous = attr.copy(previous)
|
||||
|
||||
self.committed_state[attr.key] = previous
|
||||
|
||||
|
||||
# the "or not self.modified" is defensive at
|
||||
# this point. The assertion below is expected
|
||||
# to be True:
|
||||
@@ -357,7 +359,15 @@ class InstanceState(object):
|
||||
instance_dict._modified.add(self)
|
||||
|
||||
self._strong_obj = self.obj()
|
||||
|
||||
if self._strong_obj 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)
|
||||
))
|
||||
self.modified = True
|
||||
|
||||
def commit(self, dict_, keys):
|
||||
@@ -371,10 +381,14 @@ class InstanceState(object):
|
||||
|
||||
"""
|
||||
class_manager = self.manager
|
||||
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:
|
||||
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)
|
||||
|
||||
self.expired = False
|
||||
@@ -400,14 +414,13 @@ class InstanceState(object):
|
||||
|
||||
"""
|
||||
|
||||
self.__dict__.pop('committed_state', None)
|
||||
self.committed_state.clear()
|
||||
self.__dict__.pop('pending', None)
|
||||
|
||||
if 'callables' in self.__dict__:
|
||||
callables = self.callables
|
||||
for key in list(callables):
|
||||
if key in dict_ and callables[key] is self:
|
||||
del callables[key]
|
||||
callables = self.callables
|
||||
for key in list(callables):
|
||||
if key in dict_ and callables[key] is self:
|
||||
del callables[key]
|
||||
|
||||
for key in self.manager.mutable_attributes:
|
||||
if key in dict_:
|
||||
@@ -461,6 +474,18 @@ class MutableAttrInstanceState(InstanceState):
|
||||
(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.
|
||||
|
||||
@@ -498,10 +523,7 @@ class MutableAttrInstanceState(InstanceState):
|
||||
else:
|
||||
instance_dict = self._instance_dict()
|
||||
if instance_dict:
|
||||
try:
|
||||
instance_dict.remove(self)
|
||||
except AssertionError:
|
||||
pass
|
||||
instance_dict.discard(self)
|
||||
self.dispose()
|
||||
|
||||
def __resurrect(self):
|
||||
@@ -509,19 +531,13 @@ class MutableAttrInstanceState(InstanceState):
|
||||
|
||||
# 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)
|
||||
|
||||
# re-establishes identity attributes from the key
|
||||
self.manager.events.run('on_resurrect', self, obj)
|
||||
|
||||
# TODO: don't really think we should run this here.
|
||||
# resurrect is only meant to preserve the minimal state needed to
|
||||
# do an UPDATE, not to produce a fully usable object
|
||||
self._run_on_load(obj)
|
||||
self.manager.dispatch.resurrect(self)
|
||||
|
||||
return obj
|
||||
|
||||
@@ -540,10 +556,12 @@ class PendingCollection(object):
|
||||
def append(self, value):
|
||||
if value in self.deleted_items:
|
||||
self.deleted_items.remove(value)
|
||||
self.added_items.add(value)
|
||||
else:
|
||||
self.added_items.add(value)
|
||||
|
||||
def remove(self, value):
|
||||
if value in self.added_items:
|
||||
self.added_items.remove(value)
|
||||
self.deleted_items.add(value)
|
||||
else:
|
||||
self.deleted_items.add(value)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user