Update SQLAlchemy

This commit is contained in:
Ruud
2013-06-14 11:00:06 +02:00
parent 267ecfacab
commit 4aa6700ceb
124 changed files with 6500 additions and 5207 deletions
File diff suppressed because it is too large Load Diff
+117 -97
View File
@@ -1,5 +1,5 @@
# orm/attributes.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -29,7 +29,7 @@ NO_VALUE = util.symbol('NO_VALUE')
NEVER_SET = util.symbol('NEVER_SET')
PASSIVE_RETURN_NEVER_SET = util.symbol('PASSIVE_RETURN_NEVER_SET',
"""Symbol indicating that loader callables can be
"""Symbol indicating that loader callables can be
fired off, but if no callable is applicable and no value is
present, the attribute should remain non-initialized.
NEVER_SET is returned in this case.
@@ -37,14 +37,14 @@ NEVER_SET is returned in this case.
PASSIVE_NO_INITIALIZE = util.symbol('PASSIVE_NO_INITIALIZE',
"""Symbol indicating that loader callables should
not be fired off, and a non-initialized attribute
not be fired off, and a non-initialized attribute
should remain that way.
""")
PASSIVE_NO_FETCH = util.symbol('PASSIVE_NO_FETCH',
"""Symbol indicating that loader callables should not emit SQL,
"""Symbol indicating that loader callables should not emit SQL,
but a value can be fetched from the current session.
Non-initialized attributes should be initialized to an empty value.
""")
@@ -53,9 +53,9 @@ PASSIVE_NO_FETCH_RELATED = util.symbol('PASSIVE_NO_FETCH_RELATED',
"""Symbol indicating that loader callables should not emit SQL for
loading a related object, but can refresh the attributes of the local
instance in order to locate a related object in the current session.
Non-initialized attributes should be initialized to an empty value.
The unit of work uses this mode to check if history is present
on many-to-one attributes with minimal SQL emitted.
@@ -81,7 +81,7 @@ PASSIVE_OFF = util.symbol('PASSIVE_OFF',
class QueryableAttribute(interfaces.PropComparator):
"""Base class for class-bound attributes. """
def __init__(self, class_, key, impl=None,
def __init__(self, class_, key, impl=None,
comparator=None, parententity=None):
self.class_ = class_
self.key = key
@@ -92,7 +92,7 @@ class QueryableAttribute(interfaces.PropComparator):
manager = manager_of_class(class_)
# manager is None in the case of AliasedClass
if manager:
# propagate existing event listeners from
# propagate existing event listeners from
# immediate superclass
for base in manager._bases:
if key in base:
@@ -134,8 +134,8 @@ class QueryableAttribute(interfaces.PropComparator):
except AttributeError:
raise AttributeError(
'Neither %r object nor %r object has an attribute %r' % (
type(self).__name__,
type(self.comparator).__name__,
type(self).__name__,
type(self.comparator).__name__,
key)
)
@@ -151,7 +151,7 @@ class InstrumentedAttribute(QueryableAttribute):
"""Class bound instrumented attribute which adds descriptor methods."""
def __set__(self, instance, value):
self.impl.set(instance_state(instance),
self.impl.set(instance_state(instance),
instance_dict(instance), value, None)
def __delete__(self, instance):
@@ -179,12 +179,12 @@ def create_proxied_attribute(descriptor):
class Proxy(QueryableAttribute):
"""Presents the :class:`.QueryableAttribute` interface as a
proxy on top of a Python descriptor / :class:`.PropComparator`
proxy on top of a Python descriptor / :class:`.PropComparator`
combination.
"""
def __init__(self, class_, key, descriptor, comparator,
def __init__(self, class_, key, descriptor, comparator,
adapter=None, doc=None):
self.class_ = class_
self.key = key
@@ -233,8 +233,8 @@ def create_proxied_attribute(descriptor):
except AttributeError:
raise AttributeError(
'Neither %r object nor %r object has an attribute %r' % (
type(descriptor).__name__,
type(self.comparator).__name__,
type(descriptor).__name__,
type(self.comparator).__name__,
attribute)
)
@@ -250,7 +250,7 @@ class AttributeImpl(object):
def __init__(self, class_, key,
callable_, dispatch, trackparent=False, extension=None,
compare_function=None, active_history=False,
compare_function=None, active_history=False,
parent_token=None, expire_missing=True,
**kwargs):
"""Construct an AttributeImpl.
@@ -287,12 +287,12 @@ class AttributeImpl(object):
parent_token
Usually references the MapperProperty, used as a key for
the hasparent() function to identify an "owning" attribute.
Allows multiple AttributeImpls to all match a single
Allows multiple AttributeImpls to all match a single
owner attribute.
expire_missing
if False, don't add an "expiry" callable to this attribute
during state.expire_attributes(None), if no value is present
during state.expire_attributes(None), if no value is present
for this key.
"""
@@ -331,7 +331,7 @@ class AttributeImpl(object):
def hasparent(self, state, optimistic=False):
"""Return the boolean value of a `hasparent` flag attached to
"""Return the boolean value of a `hasparent` flag attached to
the given state.
The `optimistic` flag determines what the default return value
@@ -375,8 +375,8 @@ class AttributeImpl(object):
"state %s along attribute '%s', "
"but the parent record "
"has gone stale, can't be sure this "
"is the most recent parent." %
(mapperutil.state_str(state),
"is the most recent parent." %
(mapperutil.state_str(state),
mapperutil.state_str(parent_state),
self.key))
@@ -406,18 +406,18 @@ class AttributeImpl(object):
raise NotImplementedError()
def get_all_pending(self, state, dict_):
"""Return a list of tuples of (state, obj)
for all objects in this attribute's current state
"""Return a list of tuples of (state, obj)
for all objects in this attribute's current state
+ history.
Only applies to object-based attributes.
This is an inlining of existing functionality
which roughly correponds to:
which roughly corresponds to:
get_state_history(
state,
key,
state,
key,
passive=PASSIVE_NO_INITIALIZE).sum()
"""
@@ -478,14 +478,14 @@ class AttributeImpl(object):
self.set(state, dict_, value, initiator, passive=passive)
def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
self.set(state, dict_, None, initiator,
self.set(state, dict_, None, initiator,
passive=passive, check_old=value)
def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
self.set(state, dict_, None, initiator,
self.set(state, dict_, None, initiator,
passive=passive, check_old=value, pop=True)
def set(self, state, dict_, value, initiator,
def set(self, state, dict_, value, initiator,
passive=PASSIVE_OFF, check_old=None, pop=False):
raise NotImplementedError()
@@ -532,7 +532,7 @@ class ScalarAttributeImpl(AttributeImpl):
return History.from_scalar_attribute(
self, state, dict_.get(self.key, NO_VALUE))
def set(self, state, dict_, value, initiator,
def set(self, state, dict_, value, initiator,
passive=PASSIVE_OFF, check_old=None, pop=False):
if initiator and initiator.parent_token is self.parent_token:
return
@@ -543,7 +543,7 @@ class ScalarAttributeImpl(AttributeImpl):
old = dict_.get(self.key, NO_VALUE)
if self.dispatch.set:
value = self.fire_replace_event(state, dict_,
value = self.fire_replace_event(state, dict_,
value, old, initiator)
state.modified_event(dict_, self, old)
dict_[self.key] = value
@@ -575,10 +575,10 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl):
class_manager, copy_function=None,
compare_function=None, **kwargs):
super(ScalarAttributeImpl, self).__init__(
class_,
key,
class_,
key,
callable_, dispatch,
compare_function=compare_function,
compare_function=compare_function,
**kwargs)
class_manager.mutable_attributes.add(key)
if copy_function is None:
@@ -611,15 +611,15 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl):
ScalarAttributeImpl.delete(self, state, dict_)
state.mutable_dict.pop(self.key)
def set(self, state, dict_, value, initiator,
def set(self, state, dict_, value, initiator,
passive=PASSIVE_OFF, check_old=None, pop=False):
ScalarAttributeImpl.set(self, state, dict_, value,
ScalarAttributeImpl.set(self, state, dict_, value,
initiator, passive, check_old=check_old, pop=pop)
state.mutable_dict[self.key] = value
class ScalarObjectAttributeImpl(ScalarAttributeImpl):
"""represents a scalar-holding InstrumentedAttribute,
"""represents a scalar-holding InstrumentedAttribute,
where the target object is also instrumented.
Adds events to delete/set operations.
@@ -653,7 +653,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
if current is not None:
ret = [(instance_state(current), current)]
else:
ret = []
ret = [(None, None)]
if self.key in state.committed_state:
original = state.committed_state[self.key]
@@ -665,7 +665,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
else:
return []
def set(self, state, dict_, value, initiator,
def set(self, state, dict_, value, initiator,
passive=PASSIVE_OFF, check_old=None, pop=False):
"""Set a value on the given InstanceState.
@@ -686,7 +686,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
old is not PASSIVE_NO_RESULT and \
check_old is not old:
if pop:
return
return
else:
raise ValueError(
"Object %s not associated with %s on attribute '%s'" % (
@@ -744,12 +744,12 @@ class CollectionAttributeImpl(AttributeImpl):
typecallable=None, trackparent=False, extension=None,
copy_function=None, compare_function=None, **kwargs):
super(CollectionAttributeImpl, self).__init__(
class_,
key,
class_,
key,
callable_, dispatch,
trackparent=trackparent,
extension=extension,
compare_function=compare_function,
compare_function=compare_function,
**kwargs)
if copy_function is None:
@@ -777,11 +777,11 @@ class CollectionAttributeImpl(AttributeImpl):
if self.key in state.committed_state:
original = state.committed_state[self.key]
if original is not NO_VALUE:
current_states = [((c is not None) and
instance_state(c) or None, c)
current_states = [((c is not None) and
instance_state(c) or None, c)
for c in current]
original_states = [((c is not None) and
instance_state(c) or None, c)
original_states = [((c is not None) and
instance_state(c) or None, c)
for c in original]
current_set = dict(current_states)
@@ -869,13 +869,13 @@ class CollectionAttributeImpl(AttributeImpl):
def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
try:
# TODO: better solution here would be to add
# a "popper" role to collections.py to complement
# a "popper" role to collections.py to complement
# "remover".
self.remove(state, dict_, value, initiator, passive=passive)
except (ValueError, KeyError, IndexError):
pass
def set(self, state, dict_, value, initiator,
def set(self, state, dict_, value, initiator,
passive=PASSIVE_OFF, pop=False):
"""Set a value on the given object.
@@ -954,7 +954,7 @@ class CollectionAttributeImpl(AttributeImpl):
return user_data
def get_collection(self, state, dict_,
def get_collection(self, state, dict_,
user_data=None, passive=PASSIVE_OFF):
"""Retrieve the CollectionAdapter associated with the given state.
@@ -973,6 +973,14 @@ def backref_listeners(attribute, key, uselist):
# use easily recognizable names for stack traces
parent_token = attribute.impl.parent_token
def _acceptable_key_err(child_state, initiator):
raise ValueError(
"Object %s not associated with attribute of "
"type %s" % (mapperutil.state_str(child_state),
manager_of_class(initiator.class_)[initiator.key]))
def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
if oldchild is child:
return child
@@ -983,61 +991,73 @@ def backref_listeners(attribute, key, uselist):
old_state, old_dict = instance_state(oldchild),\
instance_dict(oldchild)
impl = old_state.manager[key].impl
impl.pop(old_state,
old_dict,
state.obj(),
impl.pop(old_state,
old_dict,
state.obj(),
initiator, passive=PASSIVE_NO_FETCH)
if child is not None:
child_state, child_dict = instance_state(child),\
instance_dict(child)
child_state.manager[key].impl.append(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
child_impl = child_state.manager[key].impl
if initiator.parent_token is not parent_token and \
initiator.parent_token is not child_impl.parent_token:
_acceptable_key_err(state, initiator)
child_impl.append(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
return child
def emit_backref_from_collection_append_event(state, child, initiator):
child_state, child_dict = instance_state(child), \
instance_dict(child)
child_state.manager[key].impl.append(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
child_impl = child_state.manager[key].impl
if initiator.parent_token is not parent_token and \
initiator.parent_token is not child_impl.parent_token:
_acceptable_key_err(state, initiator)
child_impl.append(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
return child
def emit_backref_from_collection_remove_event(state, child, initiator):
if child is not None:
child_state, child_dict = instance_state(child),\
instance_dict(child)
child_state.manager[key].impl.pop(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
child_impl = child_state.manager[key].impl
# can't think of a path that would produce an initiator
# mismatch here, as it would require an existing collection
# mismatch.
child_impl.pop(
child_state,
child_dict,
state.obj(),
initiator,
passive=PASSIVE_NO_FETCH)
if uselist:
event.listen(attribute, "append",
emit_backref_from_collection_append_event,
event.listen(attribute, "append",
emit_backref_from_collection_append_event,
retval=True, raw=True)
else:
event.listen(attribute, "set",
emit_backref_from_scalar_set_event,
event.listen(attribute, "set",
emit_backref_from_scalar_set_event,
retval=True, raw=True)
# TODO: need coverage in test/orm/ of remove event
event.listen(attribute, "remove",
emit_backref_from_collection_remove_event,
event.listen(attribute, "remove",
emit_backref_from_collection_remove_event,
retval=True, raw=True)
_NO_HISTORY = util.symbol('NO_HISTORY')
_NO_STATE_SYMBOLS = frozenset([
id(PASSIVE_NO_RESULT),
id(NO_VALUE),
id(PASSIVE_NO_RESULT),
id(NO_VALUE),
id(NEVER_SET)])
class History(tuple):
"""A 3-tuple of added, unchanged and deleted values,
@@ -1078,7 +1098,7 @@ class History(tuple):
return not bool(
(self.added or self.deleted)
or self.unchanged and self.unchanged != [None]
)
)
def sum(self):
"""Return a collection of added + unchanged + deleted."""
@@ -1126,11 +1146,11 @@ class History(tuple):
return cls((), (), ())
else:
return cls((), [current], ())
# dont let ClauseElement expressions here trip things up
# don't let ClauseElement expressions here trip things up
elif attribute.is_equal(current, original) is True:
return cls((), [current], ())
else:
# current convention on native scalars is to not
# current convention on native scalars is to not
# include information
# about missing previous value in "deleted", but
# we do include None, which helps in some primary
@@ -1156,11 +1176,11 @@ class History(tuple):
elif current is original:
return cls((), [current], ())
else:
# current convention on related objects is to not
# current convention on related objects is to not
# include information
# about missing previous value in "deleted", and
# to also not include None - the dependency.py rules
# ignore the None in any case.
# ignore the None in any case.
if id(original) in _NO_STATE_SYMBOLS or original is None:
deleted = ()
else:
@@ -1181,11 +1201,11 @@ class History(tuple):
return cls((), list(current), ())
else:
current_states = [((c is not None) and instance_state(c) or None, c)
for c in current
current_states = [((c is not None) and instance_state(c) or None, c)
for c in current
]
original_states = [((c is not None) and instance_state(c) or None, c)
for c in original
original_states = [((c is not None) and instance_state(c) or None, c)
for c in original
]
current_set = dict(current_states)
@@ -1200,7 +1220,7 @@ class History(tuple):
HISTORY_BLANK = History(None, None, None)
def get_history(obj, key, passive=PASSIVE_OFF):
"""Return a :class:`.History` record for the given object
"""Return a :class:`.History` record for the given object
and attribute key.
:param obj: an object whose class is instrumented by the
@@ -1239,14 +1259,14 @@ def register_attribute(class_, key, **kw):
comparator = kw.pop('comparator', None)
parententity = kw.pop('parententity', None)
doc = kw.pop('doc', None)
desc = register_descriptor(class_, key,
desc = register_descriptor(class_, key,
comparator, parententity, doc=doc)
register_attribute_impl(class_, key, **kw)
return desc
def register_attribute_impl(class_, key,
uselist=False, callable_=None,
useobject=False, mutable_scalars=False,
uselist=False, callable_=None,
useobject=False, mutable_scalars=False,
impl_class=None, backref=None, **kw):
manager = manager_of_class(class_)
@@ -1281,7 +1301,7 @@ def register_attribute_impl(class_, key,
manager.post_configure_attribute(key)
return manager[key]
def register_descriptor(class_, key, comparator=None,
def register_descriptor(class_, key, comparator=None,
parententity=None, doc=None):
manager = manager_of_class(class_)
@@ -1310,7 +1330,7 @@ def init_collection(obj, key):
:func:`~sqlalchemy.orm.attributes.set_committed_value`.
obj is an instrumented object instance. An InstanceState
is accepted directly for backwards compatibility but
is accepted directly for backwards compatibility but
this usage is deprecated.
"""
@@ -1328,7 +1348,7 @@ def init_state_collection(state, dict_, key):
def set_committed_value(instance, key, value):
"""Set the value of an attribute with no history events.
Cancels any previous history present. The value should be
Cancels any previous history present. The value should be
a scalar value for scalar-holding attributes, or
an iterable for any collection-holding attribute.
@@ -1385,7 +1405,7 @@ def del_attribute(instance, key):
def flag_modified(instance, key):
"""Mark an attribute on an instance as 'modified'.
This sets the 'modified' flag on the instance and
This sets the 'modified' flag on the instance and
establishes an unconditional change event for the given attribute.
"""
+96 -19
View File
@@ -1,5 +1,5 @@
# orm/collections.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -111,27 +111,62 @@ import weakref
from sqlalchemy.sql import expression
from sqlalchemy import schema, util, exc as sa_exc
__all__ = ['collection', 'collection_adapter',
'mapped_collection', 'column_mapped_collection',
'attribute_mapped_collection']
__instrumentation_mutex = util.threading.Lock()
class _SerializableColumnGetter(object):
def __init__(self, colkeys):
self.colkeys = colkeys
self.composite = len(colkeys) > 1
class _PlainColumnGetter(object):
"""Plain column getter, stores collection of Column objects
directly.
Serializes to a :class:`._SerializableColumnGetterV2`
which has more expensive __call__() performance
and some rare caveats.
"""
def __init__(self, cols):
self.cols = cols
self.composite = len(cols) > 1
def __reduce__(self):
return _SerializableColumnGetter, (self.colkeys,)
return _SerializableColumnGetterV2._reduce_from_cols(self.cols)
def _cols(self, mapper):
return self.cols
def __call__(self, value):
state = instance_state(value)
m = _state_mapper(state)
key = [
m._get_state_attr_by_column(state, state.dict, col)
for col in self._cols(m)
]
if self.composite:
return tuple(key)
else:
return key[0]
class _SerializableColumnGetter(object):
"""Column-based getter used in version 0.7.6 only.
Remains here for pickle compatibility with 0.7.6.
"""
def __init__(self, colkeys):
self.colkeys = colkeys
self.composite = len(colkeys) > 1
def __reduce__(self):
return _SerializableColumnGetter, (self.colkeys,)
def __call__(self, value):
state = instance_state(value)
m = _state_mapper(state)
key = [m._get_state_attr_by_column(
state, state.dict,
state, state.dict,
m.mapped_table.columns[k])
for k in self.colkeys]
if self.composite:
@@ -139,6 +174,48 @@ class _SerializableColumnGetter(object):
else:
return key[0]
class _SerializableColumnGetterV2(_PlainColumnGetter):
"""Updated serializable getter which deals with
multi-table mapped classes.
Two extremely unusual cases are not supported.
Mappings which have tables across multiple metadata
objects, or which are mapped to non-Table selectables
linked across inheriting mappers may fail to function
here.
"""
def __init__(self, colkeys):
self.colkeys = colkeys
self.composite = len(colkeys) > 1
def __reduce__(self):
return self.__class__, (self.colkeys,)
@classmethod
def _reduce_from_cols(cls, cols):
def _table_key(c):
if not isinstance(c.table, expression.TableClause):
return None
else:
return c.table.key
colkeys = [(c.key, _table_key(c)) for c in cols]
return _SerializableColumnGetterV2, (colkeys,)
def _cols(self, mapper):
cols = []
metadata = getattr(mapper.local_table, 'metadata', None)
for (ckey, tkey) in self.colkeys:
if tkey is None or \
metadata is None or \
tkey not in metadata:
cols.append(mapper.local_table.c[ckey])
else:
cols.append(metadata.tables[tkey].c[ckey])
return cols
def column_mapped_collection(mapping_spec):
"""A dictionary-based collection type with column-based keying.
@@ -155,10 +232,10 @@ def column_mapped_collection(mapping_spec):
from sqlalchemy.orm.util import _state_mapper
from sqlalchemy.orm.attributes import instance_state
cols = [c.key for c in [
expression._only_column_elements(q, "mapping_spec")
for q in util.to_list(mapping_spec)]]
keyfunc = _SerializableColumnGetter(cols)
cols = [expression._only_column_elements(q, "mapping_spec")
for q in util.to_list(mapping_spec)
]
keyfunc = _PlainColumnGetter(cols)
return lambda: MappedCollection(keyfunc)
class _SerializableAttrGetter(object):
@@ -632,8 +709,8 @@ class CollectionAdapter(object):
"""
if initiator is not False and item is not None:
return self.attr.fire_append_event(
self.owner_state,
self.owner_state.dict,
self.owner_state,
self.owner_state.dict,
item, initiator)
else:
return item
@@ -648,8 +725,8 @@ class CollectionAdapter(object):
"""
if initiator is not False and item is not None:
self.attr.fire_remove_event(
self.owner_state,
self.owner_state.dict,
self.owner_state,
self.owner_state.dict,
item, initiator)
def fire_pre_remove_event(self, initiator=None):
@@ -660,8 +737,8 @@ class CollectionAdapter(object):
"""
self.attr.fire_pre_remove_event(
self.owner_state,
self.owner_state.dict,
self.owner_state,
self.owner_state.dict,
initiator=initiator)
def __getstate__(self):
+200 -200
View File
@@ -1,5 +1,5 @@
# orm/dependency.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -48,7 +48,7 @@ class DependencyProcessor(object):
def hasparent(self, state):
"""return True if the given object instance has a parent,
according to the ``InstrumentedAttribute`` handled by this
according to the ``InstrumentedAttribute`` handled by this
``DependencyProcessor``.
"""
@@ -69,29 +69,29 @@ class DependencyProcessor(object):
before_delete = unitofwork.ProcessAll(uow, self, True, True)
parent_saves = unitofwork.SaveUpdateAll(
uow,
uow,
self.parent.primary_base_mapper
)
child_saves = unitofwork.SaveUpdateAll(
uow,
uow,
self.mapper.primary_base_mapper
)
parent_deletes = unitofwork.DeleteAll(
uow,
uow,
self.parent.primary_base_mapper
)
child_deletes = unitofwork.DeleteAll(
uow,
uow,
self.mapper.primary_base_mapper
)
self.per_property_dependencies(uow,
parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
self.per_property_dependencies(uow,
parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
before_delete
)
@@ -99,7 +99,7 @@ class DependencyProcessor(object):
def per_state_flush_actions(self, uow, states, isdelete):
"""establish actions and dependencies related to a flush.
These actions will operate on all relevant states
These actions will operate on all relevant states
individually. This occurs only if there are cycles
in the 'aggregated' version of events.
@@ -141,14 +141,14 @@ class DependencyProcessor(object):
# check if the "parent" side is part of the cycle
if not isdelete:
parent_saves = unitofwork.SaveUpdateAll(
uow,
uow,
self.parent.base_mapper)
parent_deletes = before_delete = None
if parent_saves in uow.cycles:
parent_in_cycles = True
else:
parent_deletes = unitofwork.DeleteAll(
uow,
uow,
self.parent.base_mapper)
parent_saves = after_save = None
if parent_deletes in uow.cycles:
@@ -165,19 +165,19 @@ class DependencyProcessor(object):
continue
if isdelete:
before_delete = unitofwork.ProcessState(uow,
before_delete = unitofwork.ProcessState(uow,
self, True, state)
if parent_in_cycles:
parent_deletes = unitofwork.DeleteState(
uow,
state,
uow,
state,
parent_base_mapper)
else:
after_save = unitofwork.ProcessState(uow, self, False, state)
if parent_in_cycles:
parent_saves = unitofwork.SaveUpdateState(
uow,
state,
uow,
state,
parent_base_mapper)
if child_in_cycles:
@@ -190,24 +190,24 @@ class DependencyProcessor(object):
if deleted:
child_action = (
unitofwork.DeleteState(
uow, child_state,
child_base_mapper),
uow, child_state,
child_base_mapper),
True)
else:
child_action = (
unitofwork.SaveUpdateState(
uow, child_state,
child_base_mapper),
uow, child_state,
child_base_mapper),
False)
child_actions.append(child_action)
# establish dependencies between our possibly per-state
# parent action and our possibly per-state child action.
for child_action, childisdelete in child_actions:
self.per_state_dependencies(uow, parent_saves,
parent_deletes,
child_action,
after_save, before_delete,
self.per_state_dependencies(uow, parent_saves,
parent_deletes,
child_action,
after_save, before_delete,
isdelete, childisdelete)
@@ -232,12 +232,12 @@ class DependencyProcessor(object):
passive = attributes.PASSIVE_OFF
for s in states:
# TODO: add a high speed method
# TODO: add a high speed method
# to InstanceState which returns: attribute
# has a non-None value, or had one
history = uowcommit.get_attribute_history(
s,
self.key,
s,
self.key,
passive)
if history and not history.empty():
return True
@@ -248,7 +248,7 @@ class DependencyProcessor(object):
def _verify_canload(self, state):
if state is not None and \
not self.mapper._canload(state,
not self.mapper._canload(state,
allow_subtypes=not self.enable_typechecks):
if self.mapper._canload(state, allow_subtypes=True):
raise exc.FlushError('Attempting to flush an item of type '
@@ -287,11 +287,11 @@ class DependencyProcessor(object):
return None
process_key = tuple(sorted(
[self.key] +
[self.key] +
[p.key for p in self.prop._reverse_property]
))
return uow.memo(
('reverse_key', process_key),
('reverse_key', process_key),
set
)
@@ -299,7 +299,7 @@ class DependencyProcessor(object):
for x in related:
if x is not None:
uowcommit.issue_post_update(
state,
state,
[r for l, r in self.prop.synchronize_pairs]
)
break
@@ -312,21 +312,21 @@ class DependencyProcessor(object):
class OneToManyDP(DependencyProcessor):
def per_property_dependencies(self, uow, parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
def per_property_dependencies(self, uow, parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
before_delete,
):
if self.post_update:
child_post_updates = unitofwork.IssuePostUpdate(
uow,
self.mapper.primary_base_mapper,
uow,
self.mapper.primary_base_mapper,
False)
child_pre_updates = unitofwork.IssuePostUpdate(
uow,
self.mapper.primary_base_mapper,
uow,
self.mapper.primary_base_mapper,
True)
uow.dependencies.update([
@@ -352,22 +352,22 @@ class OneToManyDP(DependencyProcessor):
(before_delete, child_deletes),
])
def per_state_dependencies(self, uow,
save_parent,
delete_parent,
child_action,
after_save, before_delete,
def per_state_dependencies(self, uow,
save_parent,
delete_parent,
child_action,
after_save, before_delete,
isdelete, childisdelete):
if self.post_update:
child_post_updates = unitofwork.IssuePostUpdate(
uow,
self.mapper.primary_base_mapper,
uow,
self.mapper.primary_base_mapper,
False)
child_pre_updates = unitofwork.IssuePostUpdate(
uow,
self.mapper.primary_base_mapper,
uow,
self.mapper.primary_base_mapper,
True)
# TODO: this whole block is not covered
@@ -393,7 +393,7 @@ class OneToManyDP(DependencyProcessor):
else:
uow.dependencies.update([
(before_delete, child_pre_updates),
(child_pre_updates, delete_parent),
(child_pre_updates, delete_parent),
])
elif not isdelete:
uow.dependencies.update([
@@ -408,16 +408,16 @@ class OneToManyDP(DependencyProcessor):
])
def presort_deletes(self, uowcommit, states):
# head object is being deleted, and we manage its list of
# child objects the child objects have to have their
# head object is being deleted, and we manage its list of
# child objects the child objects have to have their
# foreign key to the parent set to NULL
should_null_fks = not self.cascade.delete and \
not self.passive_deletes == 'all'
for state in states:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
if history:
for child in history.deleted:
@@ -430,7 +430,7 @@ class OneToManyDP(DependencyProcessor):
if should_null_fks:
for child in history.unchanged:
if child is not None:
uowcommit.register_object(child,
uowcommit.register_object(child,
operation="delete", prop=self.prop)
@@ -447,25 +447,25 @@ class OneToManyDP(DependencyProcessor):
passive = attributes.PASSIVE_OFF
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
passive)
if history:
for child in history.added:
if child is not None:
uowcommit.register_object(child, cancel_delete=True,
operation="add",
uowcommit.register_object(child, cancel_delete=True,
operation="add",
prop=self.prop)
children_added.update(history.added)
for child in history.deleted:
if not self.cascade.delete_orphan:
uowcommit.register_object(child, isdelete=False,
operation='delete',
uowcommit.register_object(child, isdelete=False,
operation='delete',
prop=self.prop)
elif self.hasparent(child) is False:
uowcommit.register_object(child, isdelete=True,
uowcommit.register_object(child, isdelete=True,
operation="delete", prop=self.prop)
for c, m, st_, dct_ in self.mapper.cascade_iterator(
'delete', child):
@@ -478,16 +478,16 @@ class OneToManyDP(DependencyProcessor):
for child in history.unchanged:
if child is not None:
uowcommit.register_object(
child,
False,
child,
False,
self.passive_updates,
operation="pk change",
prop=self.prop)
def process_deletes(self, uowcommit, states):
# head object is being deleted, and we manage its list of
# child objects the child objects have to have their foreign
# key to the parent set to NULL this phase can be called
# head object is being deleted, and we manage its list of
# child objects the child objects have to have their foreign
# key to the parent set to NULL this phase can be called
# safely for any cascade but is unnecessary if delete cascade
# is on.
@@ -496,17 +496,17 @@ class OneToManyDP(DependencyProcessor):
for state in states:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
if history:
for child in history.deleted:
if child is not None and \
self.hasparent(child) is False:
self._synchronize(
state,
child,
None, True,
state,
child,
None, True,
uowcommit, False)
if self.post_update and child:
self._post_update(child, uowcommit, [state])
@@ -516,18 +516,18 @@ class OneToManyDP(DependencyProcessor):
difference(children_added):
if child is not None:
self._synchronize(
state,
child,
None, True,
state,
child,
None, True,
uowcommit, False)
if self.post_update and child:
self._post_update(child,
uowcommit,
self._post_update(child,
uowcommit,
[state])
# technically, we can even remove each child from the
# collection here too. but this would be a somewhat
# inconsistent behavior since it wouldn't happen
# collection here too. but this would be a somewhat
# inconsistent behavior since it wouldn't happen
#if the old parent wasn't deleted but child was moved.
def process_saves(self, uowcommit, states):
@@ -538,7 +538,7 @@ class OneToManyDP(DependencyProcessor):
attributes.PASSIVE_NO_INITIALIZE)
if history:
for child in history.added:
self._synchronize(state, child, None,
self._synchronize(state, child, None,
False, uowcommit, False)
if child is not None and self.post_update:
self._post_update(child, uowcommit, [state])
@@ -546,15 +546,15 @@ class OneToManyDP(DependencyProcessor):
for child in history.deleted:
if not self.cascade.delete_orphan and \
not self.hasparent(child):
self._synchronize(state, child, None, True,
self._synchronize(state, child, None, True,
uowcommit, False)
if self._pks_changed(uowcommit, state):
for child in history.unchanged:
self._synchronize(state, child, None,
self._synchronize(state, child, None,
False, uowcommit, True)
def _synchronize(self, state, child,
def _synchronize(self, state, child,
associationrow, clearkeys, uowcommit,
pks_changed):
source = state
@@ -566,15 +566,15 @@ class OneToManyDP(DependencyProcessor):
if clearkeys:
sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
else:
sync.populate(source, self.parent, dest, self.mapper,
sync.populate(source, self.parent, dest, self.mapper,
self.prop.synchronize_pairs, uowcommit,
self.passive_updates and pks_changed)
def _pks_changed(self, uowcommit, state):
return sync.source_modified(
uowcommit,
state,
self.parent,
uowcommit,
state,
self.parent,
self.prop.synchronize_pairs)
class ManyToOneDP(DependencyProcessor):
@@ -582,22 +582,22 @@ class ManyToOneDP(DependencyProcessor):
DependencyProcessor.__init__(self, prop)
self.mapper._dependency_processors.append(DetectKeySwitch(prop))
def per_property_dependencies(self, uow,
parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
def per_property_dependencies(self, uow,
parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
before_delete):
if self.post_update:
parent_post_updates = unitofwork.IssuePostUpdate(
uow,
self.parent.primary_base_mapper,
uow,
self.parent.primary_base_mapper,
False)
parent_pre_updates = unitofwork.IssuePostUpdate(
uow,
self.parent.primary_base_mapper,
uow,
self.parent.primary_base_mapper,
True)
uow.dependencies.update([
@@ -618,19 +618,19 @@ class ManyToOneDP(DependencyProcessor):
(parent_deletes, child_deletes)
])
def per_state_dependencies(self, uow,
save_parent,
delete_parent,
child_action,
after_save, before_delete,
def per_state_dependencies(self, uow,
save_parent,
delete_parent,
child_action,
after_save, before_delete,
isdelete, childisdelete):
if self.post_update:
if not isdelete:
parent_post_updates = unitofwork.IssuePostUpdate(
uow,
self.parent.primary_base_mapper,
uow,
self.parent.primary_base_mapper,
False)
if childisdelete:
uow.dependencies.update([
@@ -646,8 +646,8 @@ class ManyToOneDP(DependencyProcessor):
])
else:
parent_pre_updates = unitofwork.IssuePostUpdate(
uow,
self.parent.primary_base_mapper,
uow,
self.parent.primary_base_mapper,
True)
uow.dependencies.update([
@@ -677,8 +677,8 @@ class ManyToOneDP(DependencyProcessor):
if self.cascade.delete or self.cascade.delete_orphan:
for state in states:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
if history:
if self.cascade.delete_orphan:
@@ -688,7 +688,7 @@ class ManyToOneDP(DependencyProcessor):
for child in todelete:
if child is None:
continue
uowcommit.register_object(child, isdelete=True,
uowcommit.register_object(child, isdelete=True,
operation="delete", prop=self.prop)
for c, m, st_, dct_ in self.mapper.cascade_iterator(
'delete', child):
@@ -700,14 +700,14 @@ class ManyToOneDP(DependencyProcessor):
uowcommit.register_object(state, operation="add", prop=self.prop)
if self.cascade.delete_orphan:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
if history:
ret = True
for child in history.deleted:
if self.hasparent(child) is False:
uowcommit.register_object(child, isdelete=True,
uowcommit.register_object(child, isdelete=True,
operation="delete", prop=self.prop)
for c, m, st_, dct_ in self.mapper.cascade_iterator(
@@ -721,15 +721,15 @@ class ManyToOneDP(DependencyProcessor):
not self.cascade.delete_orphan and \
not self.passive_deletes == 'all':
# post_update means we have to update our
# post_update means we have to update our
# row to not reference the child object
# before we can DELETE the row
for state in states:
self._synchronize(state, None, None, True, uowcommit)
if state and self.post_update:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
if history:
self._post_update(state, uowcommit, history.sum())
@@ -737,12 +737,12 @@ class ManyToOneDP(DependencyProcessor):
def process_saves(self, uowcommit, states):
for state in states:
history = uowcommit.get_attribute_history(
state,
state,
self.key,
attributes.PASSIVE_NO_INITIALIZE)
if history:
for child in history.added:
self._synchronize(state, child, None, False,
self._synchronize(state, child, None, False,
uowcommit, "add")
if self.post_update:
@@ -759,7 +759,7 @@ class ManyToOneDP(DependencyProcessor):
not uowcommit.session._contains_state(child):
util.warn(
"Object of type %s not in session, %s "
"operation along '%s' won't proceed" %
"operation along '%s' won't proceed" %
(mapperutil.state_class_str(child), operation, self.prop))
return
@@ -767,14 +767,14 @@ class ManyToOneDP(DependencyProcessor):
sync.clear(state, self.parent, self.prop.synchronize_pairs)
else:
self._verify_canload(child)
sync.populate(child, self.mapper, state,
self.parent,
self.prop.synchronize_pairs,
sync.populate(child, self.mapper, state,
self.parent,
self.prop.synchronize_pairs,
uowcommit,
False)
False)
class DetectKeySwitch(DependencyProcessor):
"""For many-to-one relationships with no one-to-many backref,
"""For many-to-one relationships with no one-to-many backref,
searches for parents through the unit of work when a primary
key has changed and updates them.
@@ -798,7 +798,7 @@ class DetectKeySwitch(DependencyProcessor):
def per_property_flush_actions(self, uow):
parent_saves = unitofwork.SaveUpdateAll(
uow,
uow,
self.parent.base_mapper)
after_save = unitofwork.ProcessAll(uow, self, False, False)
uow.dependencies.update([
@@ -837,7 +837,7 @@ class DetectKeySwitch(DependencyProcessor):
def _key_switchers(self, uow, states):
switched, notswitched = uow.memo(
('pk_switchers', self),
('pk_switchers', self),
lambda: (set(), set())
)
@@ -865,29 +865,29 @@ class DetectKeySwitch(DependencyProcessor):
related is not None:
related_state = attributes.instance_state(dict_[self.key])
if related_state in switchers:
uowcommit.register_object(state,
False,
uowcommit.register_object(state,
False,
self.passive_updates)
sync.populate(
related_state,
self.mapper, state,
self.parent, self.prop.synchronize_pairs,
related_state,
self.mapper, state,
self.parent, self.prop.synchronize_pairs,
uowcommit, self.passive_updates)
def _pks_changed(self, uowcommit, state):
return bool(state.key) and sync.source_modified(uowcommit,
state,
self.mapper,
return bool(state.key) and sync.source_modified(uowcommit,
state,
self.mapper,
self.prop.synchronize_pairs)
class ManyToManyDP(DependencyProcessor):
def per_property_dependencies(self, uow, parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
def per_property_dependencies(self, uow, parent_saves,
child_saves,
parent_deletes,
child_deletes,
after_save,
before_delete
):
@@ -896,9 +896,9 @@ class ManyToManyDP(DependencyProcessor):
(child_saves, after_save),
(after_save, child_deletes),
# a rowswitch on the parent from deleted to saved
# can make this one occur, as the "save" may remove
# an element from the
# a rowswitch on the parent from deleted to saved
# can make this one occur, as the "save" may remove
# an element from the
# "deleted" list before we have a chance to
# process its child rows
(before_delete, parent_saves),
@@ -908,11 +908,11 @@ class ManyToManyDP(DependencyProcessor):
(before_delete, child_saves),
])
def per_state_dependencies(self, uow,
save_parent,
delete_parent,
child_action,
after_save, before_delete,
def per_state_dependencies(self, uow,
save_parent,
delete_parent,
child_action,
after_save, before_delete,
isdelete, childisdelete):
if not isdelete:
if childisdelete:
@@ -933,25 +933,25 @@ class ManyToManyDP(DependencyProcessor):
def presort_deletes(self, uowcommit, states):
if not self.passive_deletes:
# if no passive deletes, load history on
# if no passive deletes, load history on
# the collection, so that prop_has_changes()
# returns True
for state in states:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
def presort_saves(self, uowcommit, states):
if not self.passive_updates:
# if no passive updates, load history on
# if no passive updates, load history on
# each collection where parent has changed PK,
# so that prop_has_changes() returns True
for state in states:
if self._pks_changed(uowcommit, state):
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
attributes.PASSIVE_OFF)
if not self.cascade.delete_orphan:
@@ -961,16 +961,16 @@ class ManyToManyDP(DependencyProcessor):
# if delete_orphan check is turned on.
for state in states:
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
attributes.PASSIVE_NO_INITIALIZE)
if history:
for child in history.deleted:
if self.hasparent(child) is False:
uowcommit.register_object(child, isdelete=True,
uowcommit.register_object(child, isdelete=True,
operation="delete", prop=self.prop)
for c, m, st_, dct_ in self.mapper.cascade_iterator(
'delete',
'delete',
child):
uowcommit.register_object(
st_, isdelete=True)
@@ -983,23 +983,23 @@ class ManyToManyDP(DependencyProcessor):
processed = self._get_reversed_processed_set(uowcommit)
tmp = set()
for state in states:
# this history should be cached already, as
# this history should be cached already, as
# we loaded it in preprocess_deletes
history = uowcommit.get_attribute_history(
state,
self.key,
state,
self.key,
self._passive_delete_flag)
if history:
for child in history.non_added():
if child is None or \
(processed is not None and
(processed is not None and
(state, child) in processed):
continue
associationrow = {}
if not self._synchronize(
state,
child,
associationrow,
state,
child,
associationrow,
False, uowcommit, "delete"):
continue
secondary_delete.append(associationrow)
@@ -1009,7 +1009,7 @@ class ManyToManyDP(DependencyProcessor):
if processed is not None:
processed.update(tmp)
self._run_crud(uowcommit, secondary_insert,
self._run_crud(uowcommit, secondary_insert,
secondary_update, secondary_delete)
def process_saves(self, uowcommit, states):
@@ -1022,7 +1022,7 @@ class ManyToManyDP(DependencyProcessor):
for state in states:
need_cascade_pks = not self.passive_updates and \
self._pks_changed(uowcommit, state)
self._pks_changed(uowcommit, state)
if need_cascade_pks:
passive = attributes.PASSIVE_OFF
else:
@@ -1032,45 +1032,45 @@ class ManyToManyDP(DependencyProcessor):
if history:
for child in history.added:
if child is None or \
(processed is not None and
(processed is not None and
(state, child) in processed):
continue
associationrow = {}
if not self._synchronize(state,
child,
associationrow,
if not self._synchronize(state,
child,
associationrow,
False, uowcommit, "add"):
continue
secondary_insert.append(associationrow)
for child in history.deleted:
if child is None or \
(processed is not None and
(processed is not None and
(state, child) in processed):
continue
associationrow = {}
if not self._synchronize(state,
child,
associationrow,
if not self._synchronize(state,
child,
associationrow,
False, uowcommit, "delete"):
continue
secondary_delete.append(associationrow)
tmp.update((c, state)
tmp.update((c, state)
for c in history.added + history.deleted)
if need_cascade_pks:
for child in history.unchanged:
associationrow = {}
sync.update(state,
self.parent,
associationrow,
"old_",
sync.update(state,
self.parent,
associationrow,
"old_",
self.prop.synchronize_pairs)
sync.update(child,
self.mapper,
associationrow,
"old_",
sync.update(child,
self.mapper,
associationrow,
"old_",
self.prop.secondary_synchronize_pairs)
secondary_update.append(associationrow)
@@ -1078,18 +1078,18 @@ class ManyToManyDP(DependencyProcessor):
if processed is not None:
processed.update(tmp)
self._run_crud(uowcommit, secondary_insert,
self._run_crud(uowcommit, secondary_insert,
secondary_update, secondary_delete)
def _run_crud(self, uowcommit, secondary_insert,
def _run_crud(self, uowcommit, secondary_insert,
secondary_update, secondary_delete):
connection = uowcommit.transaction.connection(self.mapper)
if secondary_delete:
associationrow = secondary_delete[0]
statement = self.secondary.delete(sql.and_(*[
c == sql.bindparam(c.key, type_=c.type)
for c in self.secondary.c
c == sql.bindparam(c.key, type_=c.type)
for c in self.secondary.c
if c.key in associationrow
]))
result = connection.execute(statement, secondary_delete)
@@ -1098,7 +1098,7 @@ class ManyToManyDP(DependencyProcessor):
result.rowcount != len(secondary_delete):
raise exc.StaleDataError(
"DELETE statement on table '%s' expected to delete %d row(s); "
"Only %d were matched." %
"Only %d were matched." %
(self.secondary.description, len(secondary_delete),
result.rowcount)
)
@@ -1106,8 +1106,8 @@ class ManyToManyDP(DependencyProcessor):
if secondary_update:
associationrow = secondary_update[0]
statement = self.secondary.update(sql.and_(*[
c == sql.bindparam("old_" + c.key, type_=c.type)
for c in self.secondary.c
c == sql.bindparam("old_" + c.key, type_=c.type)
for c in self.secondary.c
if c.key in associationrow
]))
result = connection.execute(statement, secondary_update)
@@ -1115,7 +1115,7 @@ class ManyToManyDP(DependencyProcessor):
result.rowcount != len(secondary_update):
raise exc.StaleDataError(
"UPDATE statement on table '%s' expected to update %d row(s); "
"Only %d were matched." %
"Only %d were matched." %
(self.secondary.description, len(secondary_update),
result.rowcount)
)
@@ -1124,7 +1124,7 @@ class ManyToManyDP(DependencyProcessor):
statement = self.secondary.insert()
connection.execute(statement, secondary_insert)
def _synchronize(self, state, child, associationrow,
def _synchronize(self, state, child, associationrow,
clearkeys, uowcommit, operation):
if associationrow is None:
return
@@ -1133,13 +1133,13 @@ class ManyToManyDP(DependencyProcessor):
if not child.deleted:
util.warn(
"Object of type %s not in session, %s "
"operation along '%s' won't proceed" %
"operation along '%s' won't proceed" %
(mapperutil.state_class_str(child), operation, self.prop))
return False
self._verify_canload(child)
sync.populate_dict(state, self.parent, associationrow,
sync.populate_dict(state, self.parent, associationrow,
self.prop.synchronize_pairs)
sync.populate_dict(child, self.mapper, associationrow,
self.prop.secondary_synchronize_pairs)
@@ -1148,9 +1148,9 @@ class ManyToManyDP(DependencyProcessor):
def _pks_changed(self, uowcommit, state):
return sync.source_modified(
uowcommit,
state,
self.parent,
uowcommit,
state,
self.parent,
self.prop.synchronize_pairs)
_direction_to_processor = {
+49 -48
View File
@@ -1,5 +1,5 @@
# orm/deprecated_interfaces.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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,10 +11,10 @@ from interfaces import EXT_CONTINUE
class MapperExtension(object):
"""Base implementation for :class:`.Mapper` event hooks.
.. note::
.. note::
:class:`.MapperExtension` is deprecated. Please
refer to :func:`.event.listen` as well as
refer to :func:`.event.listen` as well as
:class:`.MapperEvents`.
New extension classes subclass :class:`.MapperExtension` and are specified
@@ -42,8 +42,8 @@ class MapperExtension(object):
to the next ``MapperExtension`` for processing". For methods
that return objects like translated rows or new object
instances, EXT_CONTINUE means the result of the method
should be ignored. In some cases it's required for a
default mapper activity to be performed, such as adding a
should be ignored. In some cases it's required for a
default mapper activity to be performed, such as adding a
new instance to a result list.
The symbol EXT_STOP has significance within a chain
@@ -91,29 +91,29 @@ class MapperExtension(object):
def reconstruct(instance, ctx):
ls_meth(self, instance)
return reconstruct
event.listen(self.class_manager, 'load',
event.listen(self.class_manager, 'load',
go(ls_meth), raw=False, propagate=True)
elif meth == 'init_instance':
def go(ls_meth):
def init_instance(instance, args, kwargs):
ls_meth(self, self.class_,
self.class_manager.original_init,
ls_meth(self, self.class_,
self.class_manager.original_init,
instance, args, kwargs)
return init_instance
event.listen(self.class_manager, 'init',
event.listen(self.class_manager, 'init',
go(ls_meth), raw=False, propagate=True)
elif meth == 'init_failed':
def go(ls_meth):
def init_failed(instance, args, kwargs):
util.warn_exception(ls_meth, self, self.class_,
self.class_manager.original_init,
util.warn_exception(ls_meth, self, self.class_,
self.class_manager.original_init,
instance, args, kwargs)
return init_failed
event.listen(self.class_manager, 'init_failure',
event.listen(self.class_manager, 'init_failure',
go(ls_meth), raw=False, propagate=True)
else:
event.listen(self, "%s" % meth, ls_meth,
event.listen(self, "%s" % meth, ls_meth,
raw=False, retval=True, propagate=True)
@@ -121,7 +121,7 @@ class MapperExtension(object):
"""Receive a class when the mapper is first constructed, and has
applied instrumentation to the mapped class.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -130,25 +130,25 @@ class MapperExtension(object):
def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
"""Receive an instance when it's constructor is called.
This method is only called during a userland construction of
This method is only called during a userland construction of
an object. It is not called when an object is loaded from the
database.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
return EXT_CONTINUE
def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
"""Receive an instance when it's constructor has been called,
"""Receive an instance when it's constructor has been called,
and raised an exception.
This method is only called during a userland construction of
This method is only called during a userland construction of
an object. It is not called when an object is loaded from the
database.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -160,9 +160,9 @@ class MapperExtension(object):
This is called when the mapper first receives a row, before
the object identity or the instance itself has been derived
from that row. The given row may or may not be a
from that row. The given row may or may not be a
``RowProxy`` object - it will always be a dictionary-like
object which contains mapped columns as keys. The
object which contains mapped columns as keys. The
returned object should also be a dictionary-like object
which recognizes mapped columns as keys.
@@ -197,7 +197,7 @@ class MapperExtension(object):
"""
return EXT_CONTINUE
def append_result(self, mapper, selectcontext, row, instance,
def append_result(self, mapper, selectcontext, row, instance,
result, **flags):
"""Receive an object instance before that instance is appended
to a result list.
@@ -231,7 +231,7 @@ class MapperExtension(object):
return EXT_CONTINUE
def populate_instance(self, mapper, selectcontext, row,
def populate_instance(self, mapper, selectcontext, row,
instance, **flags):
"""Receive an instance before that instance has
its attributes populated.
@@ -247,10 +247,11 @@ class MapperExtension(object):
instance population will not proceed, giving this extension an
opportunity to populate the instance itself, if desired.
As of 0.5, most usages of this hook are obsolete. For a
generic "object has been newly created from a row" hook, use
``reconstruct_instance()``, or the ``@orm.reconstructor``
decorator.
.. deprecated:: 0.5
Most usages of this hook are obsolete. For a
generic "object has been newly created from a row" hook, use
``reconstruct_instance()``, or the ``@orm.reconstructor``
decorator.
"""
return EXT_CONTINUE
@@ -265,11 +266,11 @@ class MapperExtension(object):
instance's lifetime.
Note that during a result-row load, this method is called upon
the first row received for this instance. Note that some
attributes and collections may or may not be loaded or even
the first row received for this instance. Note that some
attributes and collections may or may not be loaded or even
initialized, depending on what's present in the result rows.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -284,12 +285,12 @@ class MapperExtension(object):
Column-based attributes can be modified within this method
which will result in the new value being inserted. However
*no* changes to the overall flush plan can be made, and
*no* changes to the overall flush plan can be made, and
manipulation of the ``Session`` will not have the desired effect.
To manipulate the ``Session`` within an extension, use
To manipulate the ``Session`` within an extension, use
``SessionExtension``.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -299,7 +300,7 @@ class MapperExtension(object):
def after_insert(self, mapper, connection, instance):
"""Receive an object instance after that instance is inserted.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -326,12 +327,12 @@ class MapperExtension(object):
Column-based attributes can be modified within this method
which will result in the new value being updated. However
*no* changes to the overall flush plan can be made, and
*no* changes to the overall flush plan can be made, and
manipulation of the ``Session`` will not have the desired effect.
To manipulate the ``Session`` within an extension, use
To manipulate the ``Session`` within an extension, use
``SessionExtension``.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -341,7 +342,7 @@ class MapperExtension(object):
def after_update(self, mapper, connection, instance):
"""Receive an object instance after that instance is updated.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -356,7 +357,7 @@ class MapperExtension(object):
desired effect. To manipulate the ``Session`` within an
extension, use ``SessionExtension``.
The return value is only significant within the ``MapperExtension``
The return value is only significant within the ``MapperExtension``
chain; the parent mapper's behavior isn't modified by this method.
"""
@@ -377,10 +378,10 @@ class SessionExtension(object):
"""Base implementation for :class:`.Session` event hooks.
.. note::
.. note::
:class:`.SessionExtension` is deprecated. Please
refer to :func:`.event.listen` as well as
refer to :func:`.event.listen` as well as
:class:`.SessionEvents`.
Subclasses may be installed into a :class:`.Session` (or
@@ -497,10 +498,10 @@ class AttributeExtension(object):
"""Base implementation for :class:`.AttributeImpl` event hooks, events
that fire upon attribute mutations in user code.
.. note::
.. note::
:class:`.AttributeExtension` is deprecated. Please
refer to :func:`.event.listen` as well as
refer to :func:`.event.listen` as well as
:class:`.AttributeEvents`.
:class:`.AttributeExtension` is used to listen for set,
@@ -554,10 +555,10 @@ class AttributeExtension(object):
active_history=listener.active_history,
raw=True, retval=True)
event.listen(self, 'remove', listener.remove,
active_history=listener.active_history,
active_history=listener.active_history,
raw=True, retval=True)
event.listen(self, 'set', listener.set,
active_history=listener.active_history,
active_history=listener.active_history,
raw=True, retval=True)
def append(self, state, value, initiator):
+27 -27
View File
@@ -1,5 +1,5 @@
# orm/descriptor_props.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -19,7 +19,7 @@ from sqlalchemy.sql import expression
properties = util.importlater('sqlalchemy.orm', 'properties')
class DescriptorProperty(MapperProperty):
""":class:`.MapperProperty` which proxies access to a
""":class:`.MapperProperty` which proxies access to a
user-defined descriptor."""
doc = None
@@ -35,7 +35,7 @@ class DescriptorProperty(MapperProperty):
self.key = key
if hasattr(prop, 'get_history'):
def get_history(self, state, dict_,
def get_history(self, state, dict_,
passive=attributes.PASSIVE_OFF):
return prop.get_history(state, dict_, passive)
@@ -62,7 +62,7 @@ class DescriptorProperty(MapperProperty):
create_proxied_attribute(self.descriptor)\
(
self.parent.class_,
self.key,
self.key,
self.descriptor,
lambda: self._comparator_factory(mapper),
doc=self.doc
@@ -89,7 +89,7 @@ class CompositeProperty(DescriptorProperty):
self._setup_event_handlers()
def do_init(self):
"""Initialization which occurs after the :class:`.CompositeProperty`
"""Initialization which occurs after the :class:`.CompositeProperty`
has been associated with its parent mapper.
"""
@@ -97,7 +97,7 @@ class CompositeProperty(DescriptorProperty):
self._setup_arguments_on_columns()
def _create_descriptor(self):
"""Create the Python descriptor that will serve as
"""Create the Python descriptor that will serve as
the access point on instances of the mapped class.
"""
@@ -113,12 +113,12 @@ class CompositeProperty(DescriptorProperty):
values = [getattr(instance, key) for key in self._attribute_keys]
# current expected behavior here is that the composite is
# created on access if the object is persistent or if
# col attributes have non-None. This would be better
# created on access if the object is persistent or if
# col attributes have non-None. This would be better
# if the composite were created unconditionally,
# but that would be a behavioral change.
if self.key not in dict_ and (
state.key is not None or
state.key is not None or
not _none_set.issuperset(values)
):
dict_[self.key] = self.composite_class(*values)
@@ -139,7 +139,7 @@ class CompositeProperty(DescriptorProperty):
setattr(instance, key, None)
else:
for key, value in zip(
self._attribute_keys,
self._attribute_keys,
value.__composite_values__()):
setattr(instance, key, value)
@@ -198,7 +198,7 @@ class CompositeProperty(DescriptorProperty):
return
# if column elements aren't loaded, skip.
# __get__() will initiate a load for those
# __get__() will initiate a load for those
# columns
for k in self._attribute_keys:
if k not in dict_:
@@ -206,7 +206,7 @@ class CompositeProperty(DescriptorProperty):
#assert self.key not in dict_
dict_[self.key] = self.composite_class(
*[state.dict[key] for key in
*[state.dict[key] for key in
self._attribute_keys]
)
@@ -217,16 +217,16 @@ class CompositeProperty(DescriptorProperty):
def insert_update_handler(mapper, connection, state):
"""After an insert or update, some columns may be expired due
to server side defaults, or re-populated due to client side
defaults. Pop out the composite value here so that it
defaults. Pop out the composite value here so that it
recreates.
"""
state.dict.pop(self.key, None)
event.listen(self.parent, 'after_insert',
event.listen(self.parent, 'after_insert',
insert_update_handler, raw=True)
event.listen(self.parent, 'after_update',
event.listen(self.parent, 'after_update',
insert_update_handler, raw=True)
event.listen(self.parent, 'load', load_handler, raw=True, propagate=True)
event.listen(self.parent, 'refresh', load_handler, raw=True, propagate=True)
@@ -307,19 +307,19 @@ class CompositeProperty(DescriptorProperty):
return str(self.parent.class_.__name__) + "." + self.key
class ConcreteInheritedProperty(DescriptorProperty):
"""A 'do nothing' :class:`.MapperProperty` that disables
"""A 'do nothing' :class:`.MapperProperty` that disables
an attribute on a concrete subclass that is only present
on the inherited mapper, not the concrete classes' mapper.
Cases where this occurs include:
* When the superclass mapper is mapped against a
"polymorphic union", which includes all attributes from
* When the superclass mapper is mapped against a
"polymorphic union", which includes all attributes from
all subclasses.
* When a relationship() is configured on an inherited mapper,
but not on the subclass mapper. Concrete mappers require
that relationship() is configured explicitly on each
subclass.
that relationship() is configured explicitly on each
subclass.
"""
@@ -337,7 +337,7 @@ class ConcreteInheritedProperty(DescriptorProperty):
def warn():
raise AttributeError("Concrete %s does not implement "
"attribute %r at the instance level. Add this "
"property explicitly to %s." %
"property explicitly to %s." %
(self.parent, self.key, self.parent))
class NoninheritedConcreteProp(object):
@@ -354,7 +354,7 @@ class ConcreteInheritedProperty(DescriptorProperty):
class SynonymProperty(DescriptorProperty):
def __init__(self, name, map_column=None,
def __init__(self, name, map_column=None,
descriptor=None, comparator_factory=None,
doc=None):
self.name = name
@@ -387,7 +387,7 @@ class SynonymProperty(DescriptorProperty):
if self.key not in parent.mapped_table.c:
raise sa_exc.ArgumentError(
"Can't compile synonym '%s': no column on table "
"'%s' named '%s'"
"'%s' named '%s'"
% (self.name, parent.mapped_table.description, self.key))
elif parent.mapped_table.c[self.key] in \
parent._columntoproperty and \
@@ -397,13 +397,13 @@ class SynonymProperty(DescriptorProperty):
raise sa_exc.ArgumentError(
"Can't call map_column=True for synonym %r=%r, "
"a ColumnProperty already exists keyed to the name "
"%r for column %r" %
"%r for column %r" %
(self.key, self.name, self.name, self.key)
)
p = properties.ColumnProperty(parent.mapped_table.c[self.key])
parent._configure_property(
self.name, p,
init=init,
self.name, p,
init=init,
setparent=True)
p._mapped_by_synonym = self.key
+19 -16
View File
@@ -1,5 +1,5 @@
# orm/dynamic.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -12,7 +12,6 @@ basic add/delete mutation.
"""
from sqlalchemy import log, util
from sqlalchemy import exc as sa_exc
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.sql import operators
from sqlalchemy.orm import (
@@ -20,12 +19,16 @@ from sqlalchemy.orm import (
)
from sqlalchemy.orm.query import Query
from sqlalchemy.orm.util import has_identity
from sqlalchemy.orm import attributes, collections
from sqlalchemy.orm import collections
class DynaLoader(strategies.AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.is_class_level = True
if not self.uselist:
util.warn(
"On relationship %s, 'dynamic' loaders cannot be used with "
"many-to-one/one-to-one relationships and/or "
"uselist=False." % self.parent_property)
strategies._register_attribute(self,
mapper,
useobject=True,
@@ -63,7 +66,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
else:
return self.query_class(self, state)
def get_collection(self, state, dict_, user_data=None,
def get_collection(self, state, dict_, user_data=None,
passive=attributes.PASSIVE_NO_INITIALIZE):
if passive is not attributes.PASSIVE_OFF:
return self._get_collection_history(state,
@@ -97,7 +100,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)
@@ -107,7 +110,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
return state.committed_state[self.key]
def set(self, state, dict_, value, initiator,
passive=attributes.PASSIVE_OFF,
passive=attributes.PASSIVE_OFF,
check_old=None, pop=False):
if initiator and initiator.parent_token is self.parent_token:
return
@@ -144,8 +147,8 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
def get_all_pending(self, state, dict_):
c = self._get_collection_history(state, True)
return [
(attributes.instance_state(x), x)
for x in
(attributes.instance_state(x), x)
for x in
c.added_items + c.unchanged_items + c.deleted_items
]
@@ -160,12 +163,12 @@ class DynamicAttributeImpl(attributes.AttributeImpl):
else:
return c
def append(self, state, dict_, value, initiator,
def append(self, state, dict_, value, initiator,
passive=attributes.PASSIVE_OFF):
if initiator is not self:
self.fire_append_event(state, dict_, value, initiator)
def remove(self, state, dict_, value, initiator,
def remove(self, state, dict_, value, initiator,
passive=attributes.PASSIVE_OFF):
if initiator is not self:
self.fire_remove_event(state, dict_, value, initiator)
@@ -204,9 +207,9 @@ class AppenderMixin(object):
mapper = object_mapper(instance)
prop = mapper._props[self.attr.key]
self._criterion = prop.compare(
operators.eq,
instance,
value_is_parent=True,
operators.eq,
instance,
value_is_parent=True,
alias_secondary=False)
if self.attr.order_by:
@@ -280,12 +283,12 @@ class AppenderMixin(object):
def append(self, item):
self.attr.append(
attributes.instance_state(self.instance),
attributes.instance_state(self.instance),
attributes.instance_dict(self.instance), item, None)
def remove(self, item):
self.attr.remove(
attributes.instance_state(self.instance),
attributes.instance_state(self.instance),
attributes.instance_dict(self.instance), item, None)
+3 -3
View File
@@ -1,5 +1,5 @@
# orm/evaluator.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -13,10 +13,10 @@ class UnevaluatableError(Exception):
pass
_straight_ops = set(getattr(operators, op)
for op in ('add', 'mul', 'sub',
for op in ('add', 'mul', 'sub',
# Py2K
'div',
# end Py2K
# end Py2K
'mod', 'truediv',
'lt', 'le', 'ne', 'gt', 'ge', 'eq'))
+175 -167
View File
@@ -1,5 +1,5 @@
# orm/events.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -91,11 +91,11 @@ class InstanceEvents(event.Events):
When using :class:`.InstanceEvents`, several modifiers are
available to the :func:`.event.listen` function.
:param propagate=False: When True, the event listener should
be applied to all inheriting mappers as well as the
:param propagate=False: When True, the event listener should
be applied to all inheriting mappers as well as the
mapper which is the target of this listener.
:param raw=False: When True, the "target" argument passed
to applicable event listener functions will be the
to applicable event listener functions will be the
instance's :class:`.InstanceState` management
object, rather than the mapped instance itself.
@@ -142,17 +142,17 @@ class InstanceEvents(event.Events):
def init(self, target, args, kwargs):
"""Receive an instance when it's constructor is called.
This method is only called during a userland construction of
This method is only called during a userland construction of
an object. It is not called when an object is loaded from the
database.
"""
def init_failure(self, target, args, kwargs):
"""Receive an instance when it's constructor has been called,
"""Receive an instance when it's constructor has been called,
and raised an exception.
This method is only called during a userland construction of
This method is only called during a userland construction of
an object. It is not called when an object is loaded from the
database.
@@ -168,12 +168,12 @@ class InstanceEvents(event.Events):
instance's lifetime.
Note that during a result-row load, this method is called upon
the first row received for this instance. Note that some
attributes and collections may or may not be loaded or even
the first row received for this instance. Note that some
attributes and collections may or may not be loaded or even
initialized, depending on what's present in the result rows.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:param context: the :class:`.QueryContext` corresponding to the
@@ -184,16 +184,16 @@ class InstanceEvents(event.Events):
"""
def refresh(self, target, context, attrs):
"""Receive an object instance after one or more attributes have
"""Receive an object instance after one or more attributes have
been refreshed from a query.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:param context: the :class:`.QueryContext` corresponding to the
current :class:`.Query` in progress.
:param attrs: iterable collection of attribute names which
:param attrs: iterable collection of attribute names which
were populated, or None if all column-mapped, non-deferred
attributes were populated.
@@ -206,23 +206,23 @@ class InstanceEvents(event.Events):
'keys' is a list of attribute names. If None, the entire
state was expired.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:param attrs: iterable collection of attribute
names which were expired, or None if all attributes were
names which were expired, or None if all attributes were
expired.
"""
def resurrect(self, target):
"""Receive an object instance as it is 'resurrected' from
"""Receive an object instance as it is 'resurrected' from
garbage collection, which occurs when a "dirty" state falls
out of scope.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
@@ -232,28 +232,28 @@ class InstanceEvents(event.Events):
"""Receive an object instance when its associated state is
being pickled.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:param state_dict: the dictionary returned by
:param state_dict: the dictionary returned by
:class:`.InstanceState.__getstate__`, containing the state
to be pickled.
"""
def unpickle(self, target, state_dict):
"""Receive an object instance after it's associated state has
been unpickled.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:param state_dict: the dictionary sent to
:class:`.InstanceState.__setstate__`, containing the state
dictionary which was pickled.
"""
class MapperEvents(event.Events):
@@ -267,7 +267,7 @@ class MapperEvents(event.Events):
# execute a stored procedure upon INSERT,
# apply the value to the row to be inserted
target.calculated_value = connection.scalar(
"select my_special_function(%d)"
"select my_special_function(%d)"
% target.special_number)
# associate the listener function with SomeMappedClass,
@@ -304,16 +304,16 @@ class MapperEvents(event.Events):
When using :class:`.MapperEvents`, several modifiers are
available to the :func:`.event.listen` function.
:param propagate=False: When True, the event listener should
be applied to all inheriting mappers as well as the
:param propagate=False: When True, the event listener should
be applied to all inheriting mappers as well as the
mapper which is the target of this listener.
:param raw=False: When True, the "target" argument passed
to applicable event listener functions will be the
to applicable event listener functions will be the
instance's :class:`.InstanceState` management
object, rather than the mapped instance itself.
:param retval=False: when True, the user-defined event function
must have a return value, the purpose of which is either to
control subsequent event propagation, or to otherwise alter
control subsequent event propagation, or to otherwise alter
the operation in progress by the mapper. Possible return
values are:
@@ -322,7 +322,7 @@ class MapperEvents(event.Events):
* ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent
event handlers in the chain.
* other values - the return value specified by specific listeners,
such as :meth:`~.MapperEvents.translate_row` or
such as :meth:`~.MapperEvents.translate_row` or
:meth:`~.MapperEvents.create_instance`.
"""
@@ -340,7 +340,7 @@ class MapperEvents(event.Events):
return target
@classmethod
def _listen(cls, target, identifier, fn,
def _listen(cls, target, identifier, fn,
raw=False, retval=False, propagate=False):
if not raw or not retval:
@@ -370,7 +370,7 @@ class MapperEvents(event.Events):
event.Events._listen(target, identifier, fn)
def instrument_class(self, mapper, class_):
"""Receive a class when the mapper is first constructed,
"""Receive a class when the mapper is first constructed,
before instrumentation is applied to the mapped class.
This event is the earliest phase of mapper construction.
@@ -388,8 +388,16 @@ class MapperEvents(event.Events):
def mapper_configured(self, mapper, class_):
"""Called when the mapper for the class is fully configured.
This event is the latest phase of mapper construction.
The mapper should be in its final state.
This event is the latest phase of mapper construction, and
is invoked when the mapped classes are first used, so that relationships
between mappers can be resolved. When the event is called,
the mapper should be in its final state.
While the configuration event normally occurs automatically,
it can be forced to occur ahead of time, in the case where the event
is needed before any actual mapper usage, by using the
:func:`.configure_mappers` function.
:param mapper: the :class:`.Mapper` which is the target
of this event.
@@ -404,11 +412,11 @@ class MapperEvents(event.Events):
This corresponds to the :func:`.orm.configure_mappers` call, which
note is usually called automatically as mappings are first
used.
Theoretically this event is called once per
application, but is actually called any time new mappers
have been affected by a :func:`.orm.configure_mappers` call. If new mappings
are constructed after existing ones have already been used,
are constructed after existing ones have already been used,
this event can be called again.
"""
@@ -420,9 +428,9 @@ class MapperEvents(event.Events):
This listener is typically registered with ``retval=True``.
It is called when the mapper first receives a row, before
the object identity or the instance itself has been derived
from that row. The given row may or may not be a
from that row. The given row may or may not be a
:class:`.RowProxy` object - it will always be a dictionary-like
object which contains mapped columns as keys. The
object which contains mapped columns as keys. The
returned object should also be a dictionary-like object
which recognizes mapped columns as keys.
@@ -431,7 +439,7 @@ class MapperEvents(event.Events):
:param context: the :class:`.QueryContext`, which includes
a handle to the current :class:`.Query` in progress as well
as additional state information.
:param row: the result row being handled. This may be
:param row: the result row being handled. This may be
an actual :class:`.RowProxy` or may be a dictionary containing
:class:`.Column` objects as keys.
:return: When configured with ``retval=True``, the function
@@ -454,18 +462,18 @@ class MapperEvents(event.Events):
:param context: the :class:`.QueryContext`, which includes
a handle to the current :class:`.Query` in progress as well
as additional state information.
:param row: the result row being handled. This may be
:param row: the result row being handled. This may be
an actual :class:`.RowProxy` or may be a dictionary containing
:class:`.Column` objects as keys.
:param class\_: the mapped class.
:return: When configured with ``retval=True``, the return value
should be a newly created instance of the mapped class,
should be a newly created instance of the mapped class,
or ``EXT_CONTINUE`` indicating that default object construction
should take place.
"""
def append_result(self, mapper, context, row, target,
def append_result(self, mapper, context, row, target,
result, **flags):
"""Receive an object instance before that instance is appended
to a result list.
@@ -478,27 +486,27 @@ class MapperEvents(event.Events):
:param context: the :class:`.QueryContext`, which includes
a handle to the current :class:`.Query` in progress as well
as additional state information.
:param row: the result row being handled. This may be
:param row: the result row being handled. This may be
an actual :class:`.RowProxy` or may be a dictionary containing
:class:`.Column` objects as keys.
:param target: the mapped instance being populated. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being populated. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:param result: a list-like object where results are being
appended.
:param \**flags: Additional state information about the
:param \**flags: Additional state information about the
current handling of the row.
:return: If this method is registered with ``retval=True``,
a return value of ``EXT_STOP`` will prevent the instance
from being appended to the given result list, whereas a
from being appended to the given result list, whereas a
return value of ``EXT_CONTINUE`` will result in the default
behavior of appending the value to the result list.
"""
def populate_instance(self, mapper, context, row,
def populate_instance(self, mapper, context, row,
target, **flags):
"""Receive an instance before that instance has
its attributes populated.
@@ -518,11 +526,11 @@ class MapperEvents(event.Events):
:param context: the :class:`.QueryContext`, which includes
a handle to the current :class:`.Query` in progress as well
as additional state information.
:param row: the result row being handled. This may be
:param row: the result row being handled. This may be
an actual :class:`.RowProxy` or may be a dictionary containing
:class:`.Column` objects as keys.
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: When configured with ``retval=True``, a return
@@ -536,9 +544,9 @@ class MapperEvents(event.Events):
"""Receive an object instance before an INSERT statement
is emitted corresponding to that instance.
This event is used to modify local, non-object related
This event is used to modify local, non-object related
attributes on the instance before an INSERT occurs, as well
as to emit additional SQL statements on the given
as to emit additional SQL statements on the given
connection.
The event is often called for a batch of objects of the
@@ -552,23 +560,23 @@ class MapperEvents(event.Events):
.. warning::
Mapper-level flush events are designed to operate **on attributes
local to the immediate object being handled
local to the immediate object being handled
and via SQL operations with the given** :class:`.Connection` **only.**
Handlers here should **not** make alterations to the state of
Handlers here should **not** make alterations to the state of
the :class:`.Session` overall, and in general should not
affect any :func:`.relationship` -mapped attributes, as
affect any :func:`.relationship` -mapped attributes, as
session cascade rules will not function properly, nor is it
always known if the related class has already been handled.
always known if the related class has already been handled.
Operations that **are not supported in mapper events** include:
* :meth:`.Session.add`
* :meth:`.Session.delete`
* Mapped collection append, add, remove, delete, discard, etc.
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
Operations which manipulate the state of the object
relative to other objects are better handled:
* In the ``__init__()`` method of the mapped object itself, or another method
designed to establish some particular state.
* In a ``@validates`` handler, see :ref:`simple_validators`
@@ -576,12 +584,12 @@ class MapperEvents(event.Events):
:param mapper: the :class:`.Mapper` which is the target
of this event.
:param connection: the :class:`.Connection` being used to
:param connection: the :class:`.Connection` being used to
emit INSERT statements for this instance. This
provides a handle into the current transaction on the
provides a handle into the current transaction on the
target database specific to this instance.
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: No return value is supported by this event.
@@ -594,7 +602,7 @@ class MapperEvents(event.Events):
This event is used to modify in-Python-only
state on the instance after an INSERT occurs, as well
as to emit additional SQL statements on the given
as to emit additional SQL statements on the given
connection.
The event is often called for a batch of objects of the
@@ -608,23 +616,23 @@ class MapperEvents(event.Events):
.. warning::
Mapper-level flush events are designed to operate **on attributes
local to the immediate object being handled
local to the immediate object being handled
and via SQL operations with the given** :class:`.Connection` **only.**
Handlers here should **not** make alterations to the state of
Handlers here should **not** make alterations to the state of
the :class:`.Session` overall, and in general should not
affect any :func:`.relationship` -mapped attributes, as
affect any :func:`.relationship` -mapped attributes, as
session cascade rules will not function properly, nor is it
always known if the related class has already been handled.
always known if the related class has already been handled.
Operations that **are not supported in mapper events** include:
* :meth:`.Session.add`
* :meth:`.Session.delete`
* Mapped collection append, add, remove, delete, discard, etc.
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
Operations which manipulate the state of the object
relative to other objects are better handled:
* In the ``__init__()`` method of the mapped object itself, or another method
designed to establish some particular state.
* In a ``@validates`` handler, see :ref:`simple_validators`
@@ -632,12 +640,12 @@ class MapperEvents(event.Events):
:param mapper: the :class:`.Mapper` which is the target
of this event.
:param connection: the :class:`.Connection` being used to
:param connection: the :class:`.Connection` being used to
emit INSERT statements for this instance. This
provides a handle into the current transaction on the
provides a handle into the current transaction on the
target database specific to this instance.
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: No return value is supported by this event.
@@ -648,9 +656,9 @@ class MapperEvents(event.Events):
"""Receive an object instance before an UPDATE statement
is emitted corresponding to that instance.
This event is used to modify local, non-object related
This event is used to modify local, non-object related
attributes on the instance before an UPDATE occurs, as well
as to emit additional SQL statements on the given
as to emit additional SQL statements on the given
connection.
This method is called for all instances that are
@@ -683,23 +691,23 @@ class MapperEvents(event.Events):
.. warning::
Mapper-level flush events are designed to operate **on attributes
local to the immediate object being handled
local to the immediate object being handled
and via SQL operations with the given** :class:`.Connection` **only.**
Handlers here should **not** make alterations to the state of
Handlers here should **not** make alterations to the state of
the :class:`.Session` overall, and in general should not
affect any :func:`.relationship` -mapped attributes, as
affect any :func:`.relationship` -mapped attributes, as
session cascade rules will not function properly, nor is it
always known if the related class has already been handled.
always known if the related class has already been handled.
Operations that **are not supported in mapper events** include:
* :meth:`.Session.add`
* :meth:`.Session.delete`
* Mapped collection append, add, remove, delete, discard, etc.
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
Operations which manipulate the state of the object
relative to other objects are better handled:
* In the ``__init__()`` method of the mapped object itself, or another method
designed to establish some particular state.
* In a ``@validates`` handler, see :ref:`simple_validators`
@@ -707,12 +715,12 @@ class MapperEvents(event.Events):
:param mapper: the :class:`.Mapper` which is the target
of this event.
:param connection: the :class:`.Connection` being used to
:param connection: the :class:`.Connection` being used to
emit UPDATE statements for this instance. This
provides a handle into the current transaction on the
provides a handle into the current transaction on the
target database specific to this instance.
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: No return value is supported by this event.
@@ -724,12 +732,12 @@ class MapperEvents(event.Events):
This event is used to modify in-Python-only
state on the instance after an UPDATE occurs, as well
as to emit additional SQL statements on the given
as to emit additional SQL statements on the given
connection.
This method is called for all instances that are
marked as "dirty", *even those which have no net changes
to their column-based attributes*, and for which
to their column-based attributes*, and for which
no UPDATE statement has proceeded. An object is marked
as dirty when any of its column-based attributes have a
"set attribute" operation called or when any of its
@@ -756,23 +764,23 @@ class MapperEvents(event.Events):
.. warning::
Mapper-level flush events are designed to operate **on attributes
local to the immediate object being handled
local to the immediate object being handled
and via SQL operations with the given** :class:`.Connection` **only.**
Handlers here should **not** make alterations to the state of
Handlers here should **not** make alterations to the state of
the :class:`.Session` overall, and in general should not
affect any :func:`.relationship` -mapped attributes, as
affect any :func:`.relationship` -mapped attributes, as
session cascade rules will not function properly, nor is it
always known if the related class has already been handled.
always known if the related class has already been handled.
Operations that **are not supported in mapper events** include:
* :meth:`.Session.add`
* :meth:`.Session.delete`
* Mapped collection append, add, remove, delete, discard, etc.
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
Operations which manipulate the state of the object
relative to other objects are better handled:
* In the ``__init__()`` method of the mapped object itself, or another method
designed to establish some particular state.
* In a ``@validates`` handler, see :ref:`simple_validators`
@@ -780,12 +788,12 @@ class MapperEvents(event.Events):
:param mapper: the :class:`.Mapper` which is the target
of this event.
:param connection: the :class:`.Connection` being used to
:param connection: the :class:`.Connection` being used to
emit UPDATE statements for this instance. This
provides a handle into the current transaction on the
provides a handle into the current transaction on the
target database specific to this instance.
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being persisted. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: No return value is supported by this event.
@@ -796,33 +804,33 @@ class MapperEvents(event.Events):
"""Receive an object instance before a DELETE statement
is emitted corresponding to that instance.
This event is used to emit additional SQL statements on
This event is used to emit additional SQL statements on
the given connection as well as to perform application
specific bookkeeping related to a deletion event.
The event is often called for a batch of objects of the
same class before their DELETE statements are emitted at
once in a later step.
once in a later step.
.. warning::
Mapper-level flush events are designed to operate **on attributes
local to the immediate object being handled
local to the immediate object being handled
and via SQL operations with the given** :class:`.Connection` **only.**
Handlers here should **not** make alterations to the state of
Handlers here should **not** make alterations to the state of
the :class:`.Session` overall, and in general should not
affect any :func:`.relationship` -mapped attributes, as
affect any :func:`.relationship` -mapped attributes, as
session cascade rules will not function properly, nor is it
always known if the related class has already been handled.
always known if the related class has already been handled.
Operations that **are not supported in mapper events** include:
* :meth:`.Session.add`
* :meth:`.Session.delete`
* Mapped collection append, add, remove, delete, discard, etc.
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
Operations which manipulate the state of the object
relative to other objects are better handled:
* In the ``__init__()`` method of the mapped object itself, or another method
designed to establish some particular state.
* In a ``@validates`` handler, see :ref:`simple_validators`
@@ -830,12 +838,12 @@ class MapperEvents(event.Events):
:param mapper: the :class:`.Mapper` which is the target
of this event.
:param connection: the :class:`.Connection` being used to
:param connection: the :class:`.Connection` being used to
emit DELETE statements for this instance. This
provides a handle into the current transaction on the
provides a handle into the current transaction on the
target database specific to this instance.
:param target: the mapped instance being deleted. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being deleted. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: No return value is supported by this event.
@@ -846,33 +854,33 @@ class MapperEvents(event.Events):
"""Receive an object instance after a DELETE statement
has been emitted corresponding to that instance.
This event is used to emit additional SQL statements on
This event is used to emit additional SQL statements on
the given connection as well as to perform application
specific bookkeeping related to a deletion event.
The event is often called for a batch of objects of the
same class after their DELETE statements have been emitted at
once in a previous step.
once in a previous step.
.. warning::
Mapper-level flush events are designed to operate **on attributes
local to the immediate object being handled
local to the immediate object being handled
and via SQL operations with the given** :class:`.Connection` **only.**
Handlers here should **not** make alterations to the state of
Handlers here should **not** make alterations to the state of
the :class:`.Session` overall, and in general should not
affect any :func:`.relationship` -mapped attributes, as
affect any :func:`.relationship` -mapped attributes, as
session cascade rules will not function properly, nor is it
always known if the related class has already been handled.
always known if the related class has already been handled.
Operations that **are not supported in mapper events** include:
* :meth:`.Session.add`
* :meth:`.Session.delete`
* Mapped collection append, add, remove, delete, discard, etc.
* Mapped relationship attribute set/del events, i.e. ``someobject.related = someotherobject``
Operations which manipulate the state of the object
relative to other objects are better handled:
* In the ``__init__()`` method of the mapped object itself, or another method
designed to establish some particular state.
* In a ``@validates`` handler, see :ref:`simple_validators`
@@ -880,12 +888,12 @@ class MapperEvents(event.Events):
:param mapper: the :class:`.Mapper` which is the target
of this event.
:param connection: the :class:`.Connection` being used to
:param connection: the :class:`.Connection` being used to
emit DELETE statements for this instance. This
provides a handle into the current transaction on the
provides a handle into the current transaction on the
target database specific to this instance.
:param target: the mapped instance being deleted. If
the event is configured with ``raw=True``, this will
:param target: the mapped instance being deleted. If
the event is configured with ``raw=True``, this will
instead be the :class:`.InstanceState` state-management
object associated with the instance.
:return: No return value is supported by this event.
@@ -952,7 +960,7 @@ class SessionEvents(event.Events):
transaction is ongoing.
:param session: The target :class:`.Session`.
"""
def after_commit(self, session):
@@ -960,19 +968,19 @@ class SessionEvents(event.Events):
Note that this may not be per-flush if a longer running
transaction is ongoing.
:param session: The target :class:`.Session`.
"""
def after_rollback(self, session):
"""Execute after a real DBAPI rollback has occurred.
Note that this event only fires when the *actual* rollback against
the database occurs - it does *not* fire each time the
:meth:`.Session.rollback` method is called, if the underlying
the database occurs - it does *not* fire each time the
:meth:`.Session.rollback` method is called, if the underlying
DBAPI transaction has already been rolled back. In many
cases, the :class:`.Session` will not be in
cases, the :class:`.Session` will not be in
an "active" state during this event, as the current
transaction is not valid. To acquire a :class:`.Session`
which is active after the outermost rollback has proceeded,
@@ -984,30 +992,30 @@ class SessionEvents(event.Events):
"""
def after_soft_rollback(self, session, previous_transaction):
"""Execute after any rollback has occurred, including "soft"
"""Execute after any rollback has occurred, including "soft"
rollbacks that don't actually emit at the DBAPI level.
This corresponds to both nested and outer rollbacks, i.e.
the innermost rollback that calls the DBAPI's
rollback() method, as well as the enclosing rollback
the innermost rollback that calls the DBAPI's
rollback() method, as well as the enclosing rollback
calls that only pop themselves from the transaction stack.
The given :class:`.Session` can be used to invoke SQL and
:meth:`.Session.query` operations after an outermost rollback
The given :class:`.Session` can be used to invoke SQL and
:meth:`.Session.query` operations after an outermost rollback
by first checking the :attr:`.Session.is_active` flag::
@event.listens_for(Session, "after_soft_rollback")
def do_something(session, previous_transaction):
if session.is_active:
session.execute("select * from some_table")
:param session: The target :class:`.Session`.
:param previous_transaction: The :class:`.SessionTransaction` transactional
marker object which was just closed. The current :class:`.SessionTransaction`
for the given :class:`.Session` is available via the
:attr:`.Session.transaction` attribute.
New in 0.7.3.
.. versionadded:: 0.7.3
"""
@@ -1030,7 +1038,7 @@ class SessionEvents(event.Events):
Note that the session's state is still in pre-flush, i.e. 'new',
'dirty', and 'deleted' lists still show pre-flush state as well
as the history settings on instance attributes.
:param session: The target :class:`.Session`.
:param flush_context: Internal :class:`.UOWTransaction` object
which handles the details of the flush.
@@ -1044,8 +1052,8 @@ class SessionEvents(event.Events):
This will be when the 'new', 'dirty', and 'deleted' lists are in
their final state. An actual commit() may or may not have
occurred, depending on whether or not the flush started its own
transaction or participated in a larger transaction.
transaction or participated in a larger transaction.
:param session: The target :class:`.Session`.
:param flush_context: Internal :class:`.UOWTransaction` object
which handles the details of the flush.
@@ -1056,9 +1064,9 @@ class SessionEvents(event.Events):
:param session: The target :class:`.Session`.
:param transaction: The :class:`.SessionTransaction`.
:param connection: The :class:`~.engine.base.Connection` object
:param connection: The :class:`~.engine.base.Connection` object
which will be used for SQL statements.
"""
def after_attach(self, session, instance):
@@ -1072,7 +1080,7 @@ class SessionEvents(event.Events):
This is called as a result of the :meth:`.Query.update` method.
:param query: the :class:`.Query` object that this update operation was
called upon.
called upon.
:param query_context: The :class:`.QueryContext` object, corresponding
to the invocation of an ORM query.
:param result: the :class:`.ResultProxy` returned as a result of the
@@ -1086,7 +1094,7 @@ class SessionEvents(event.Events):
This is called as a result of the :meth:`.Query.delete` method.
:param query: the :class:`.Query` object that this update operation was
called upon.
called upon.
:param query_context: The :class:`.QueryContext` object, corresponding
to the invocation of an ORM query.
:param result: the :class:`.ResultProxy` returned as a result of the
@@ -1137,15 +1145,15 @@ class AttributeEvents(event.Events):
:param propagate=False: When True, the listener function will
be established not just for the class attribute given, but
for attributes of the same name on all current subclasses
of that class, as well as all future subclasses of that
class, using an additional listener that listens for
for attributes of the same name on all current subclasses
of that class, as well as all future subclasses of that
class, using an additional listener that listens for
instrumentation events.
:param raw=False: When True, the "target" argument to the
event will be the :class:`.InstanceState` management
object, rather than the mapped instance itself.
:param retval=False: when True, the user-defined event
listening must return the "value" argument from the
:param retval=False: when True, the user-defined event
listening must return the "value" argument from the
function. This gives the listening function the opportunity
to change the value that is ultimately used for a "set"
or "append" event.
@@ -1161,7 +1169,7 @@ class AttributeEvents(event.Events):
return target
@classmethod
def _listen(cls, target, identifier, fn, active_history=False,
def _listen(cls, target, identifier, fn, active_history=False,
raw=False, retval=False,
propagate=False):
if active_history:
@@ -1202,9 +1210,9 @@ class AttributeEvents(event.Events):
be the :class:`.InstanceState` object.
:param value: the value being appended. If this listener
is registered with ``retval=True``, the listener
function must return this value, or a new value which
function must return this value, or a new value which
replaces it.
:param initiator: the attribute implementation object
:param initiator: the attribute implementation object
which initiated this event.
:return: if the event was registered with ``retval=True``,
the given value, or a new effective value, should be returned.
@@ -1218,7 +1226,7 @@ class AttributeEvents(event.Events):
If the listener is registered with ``raw=True``, this will
be the :class:`.InstanceState` object.
:param value: the value being removed.
:param initiator: the attribute implementation object
:param initiator: the attribute implementation object
which initiated this event.
:return: No return value is defined for this event.
"""
@@ -1231,15 +1239,15 @@ class AttributeEvents(event.Events):
be the :class:`.InstanceState` object.
:param value: the value being set. If this listener
is registered with ``retval=True``, the listener
function must return this value, or a new value which
function must return this value, or a new value which
replaces it.
:param oldvalue: the previous value being replaced. This
may also be the symbol ``NEVER_SET`` or ``NO_VALUE``.
If the listener is registered with ``active_history=True``,
the previous value of the attribute will be loaded from
the database if the existing value is currently unloaded
the database if the existing value is currently unloaded
or expired.
:param initiator: the attribute implementation object
:param initiator: the attribute implementation object
which initiated this event.
:return: if the event was registered with ``retval=True``,
the given value, or a new effective value, should be returned.
+16 -14
View File
@@ -1,5 +1,5 @@
# orm/exc.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -18,21 +18,23 @@ class StaleDataError(sa.exc.SQLAlchemyError):
Conditions which cause this to happen include:
* A flush may have attempted to update or delete rows
and an unexpected number of rows were matched during
the UPDATE or DELETE statement. Note that when
and an unexpected number of rows were matched during
the UPDATE or DELETE statement. Note that when
version_id_col is used, rows in UPDATE or DELETE statements
are also matched against the current known version
identifier.
* A mapped object with version_id_col was refreshed,
* A mapped object with version_id_col was refreshed,
and the version number coming back from the database does
not match that of the object itself.
* A object is detached from its parent object, however
the object was previously attached to a different parent
identity which was garbage collected, and a decision
cannot be made if the new parent was really the most
recent "parent" (new in 0.7.4).
recent "parent".
.. versionadded:: 0.7.4
"""
@@ -50,7 +52,7 @@ class ObjectDereferencedError(sa.exc.SQLAlchemyError):
"""An operation cannot complete due to an object being garbage collected."""
class DetachedInstanceError(sa.exc.SQLAlchemyError):
"""An attempt to access unloaded attributes on a
"""An attempt to access unloaded attributes on a
mapped instance that is detached."""
class UnmappedInstanceError(UnmappedError):
@@ -89,21 +91,21 @@ class UnmappedClassError(UnmappedError):
class ObjectDeletedError(sa.exc.InvalidRequestError):
"""A refresh operation failed to retrieve the database
row corresponding to an object's known primary key identity.
A refresh operation proceeds when an expired attribute is
A refresh operation proceeds when an expired attribute is
accessed on an object, or when :meth:`.Query.get` is
used to retrieve an object which is, upon retrieval, detected
as expired. A SELECT is emitted for the target row
based on primary key; if no row is returned, this
exception is raised.
The true meaning of this exception is simply that
The true meaning of this exception is simply that
no row exists for the primary key identifier associated
with a persistent object. The row may have been
with a persistent object. The row may have been
deleted, or in some cases the primary key updated
to a new value, outside of the ORM's management of the target
object.
object.
"""
def __init__(self, state, msg=None):
if not msg:
+2 -2
View File
@@ -1,5 +1,5 @@
# orm/identity.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -128,7 +128,7 @@ class WeakInstanceDict(IdentityMap):
o = existing_state._is_really_none()
if o is not None:
raise AssertionError("A conflicting state is already "
"present in the identity map for key %r"
"present in the identity map for key %r"
% (key, ))
else:
return
+13 -13
View File
@@ -1,5 +1,5 @@
# orm/instrumentation.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -91,7 +91,7 @@ class ClassManager(dict):
self.originals = {}
self._bases = [mgr for mgr in [
manager_of_class(base)
manager_of_class(base)
for base in self.class_.__bases__
if isinstance(base, type)
] if mgr is not None]
@@ -139,7 +139,7 @@ class ClassManager(dict):
def _instrument_init(self):
# TODO: self.class_.__init__ is often the already-instrumented
# __init__ from an instrumented superclass. We still need to make
# __init__ from an instrumented superclass. We still need to make
# our own wrapper, but it would
# be nice to wrap the original __init__ and not our existing wrapper
# of such, since this adds method overhead.
@@ -212,7 +212,7 @@ class ClassManager(dict):
if key in self.mutable_attributes:
self.mutable_attributes.remove(key)
for cls in self.class_.__subclasses__():
manager = manager_of_class(cls)
manager = manager_of_class(cls)
if manager:
manager.uninstrument_attribute(key, True)
@@ -277,12 +277,12 @@ class ClassManager(dict):
def new_instance(self, state=None):
instance = self.class_.__new__(self.class_)
setattr(instance, self.STATE_ATTR,
setattr(instance, self.STATE_ATTR,
state or self._state_constructor(instance, self))
return instance
def setup_instance(self, instance, state=None):
setattr(instance, self.STATE_ATTR,
setattr(instance, self.STATE_ATTR,
state or self._state_constructor(instance, self))
def teardown_instance(self, instance):
@@ -387,7 +387,7 @@ class _ClassInstrumentationAdapter(ClassManager):
if delegate:
return delegate(key, state, factory)
else:
return ClassManager.initialize_collection(self, key,
return ClassManager.initialize_collection(self, key,
state, factory)
def new_instance(self, state=None):
@@ -463,7 +463,7 @@ def is_instrumented(instance, key):
class InstrumentationRegistry(object):
"""Private instrumentation registration singleton.
All classes are routed through this registry
All classes are routed through this registry
when first instrumented, however the InstrumentationRegistry
is not actually needed unless custom ClassManagers are in use.
@@ -501,7 +501,7 @@ class InstrumentationRegistry(object):
if factory != ClassManager and not self._extended:
# somebody invoked a custom ClassManager.
# reinstall global "getter" functions with the more
# reinstall global "getter" functions with the more
# expensive ones.
self._extended = True
_install_lookup_strategy(self)
@@ -543,7 +543,7 @@ class InstrumentationRegistry(object):
return factories
def manager_of_class(self, cls):
# this is only called when alternate instrumentation
# this is only called when alternate instrumentation
# has been established
if cls is None:
return None
@@ -555,7 +555,7 @@ class InstrumentationRegistry(object):
return finder(cls)
def state_of(self, instance):
# this is only called when alternate instrumentation
# this is only called when alternate instrumentation
# has been established
if instance is None:
raise AttributeError("None has no persistent state.")
@@ -566,7 +566,7 @@ class InstrumentationRegistry(object):
instance.__class__)
def dict_of(self, instance):
# this is only called when alternate instrumentation
# this is only called when alternate instrumentation
# has been established
if instance is None:
raise AttributeError("None has no persistent state.")
@@ -632,7 +632,7 @@ instrumentation_finders.append(find_native_user_instrumentation_hook)
def _generate_init(class_, class_manager):
"""Build an __init__ decorator that triggers ClassManager events."""
# TODO: we should use the ClassManager's notion of the
# TODO: we should use the ClassManager's notion of the
# original '__init__' method, once ClassManager is fixed
# to always reference that.
original__init__ = class_.__init__
+28 -28
View File
@@ -1,5 +1,5 @@
# orm/interfaces.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -61,13 +61,13 @@ class MapperProperty(object):
attribute, as well as that attribute as it appears on individual
instances of the class, including attribute instrumentation,
attribute access, loading behavior, and dependency calculations.
The most common occurrences of :class:`.MapperProperty` are the
mapped :class:`.Column`, which is represented in a mapping as
mapped :class:`.Column`, which is represented in a mapping as
an instance of :class:`.ColumnProperty`,
and a reference to another class produced by :func:`.relationship`,
represented in the mapping as an instance of :class:`.RelationshipProperty`.
"""
cascade = ()
@@ -87,7 +87,7 @@ class MapperProperty(object):
pass
def create_row_processor(self, context, path, reduced_path,
def create_row_processor(self, context, path, reduced_path,
mapper, row, adapter):
"""Return a 3-tuple consisting of three row processing functions.
@@ -263,13 +263,13 @@ class PropComparator(operators.ColumnOperators):
"""Return true if this collection contains any member that meets the
given criterion.
The usual implementation of ``any()`` is
The usual implementation of ``any()`` is
:meth:`.RelationshipProperty.Comparator.any`.
:param criterion: an optional ClauseElement formulated against the
:param criterion: an optional ClauseElement formulated against the
member class' table or attributes.
:param \**kwargs: key/value pairs corresponding to member class attribute
:param \**kwargs: key/value pairs corresponding to member class attribute
names which will be compared via equality to the corresponding
values.
@@ -281,13 +281,13 @@ class PropComparator(operators.ColumnOperators):
"""Return true if this element references a member which meets the
given criterion.
The usual implementation of ``has()`` is
The usual implementation of ``has()`` is
:meth:`.RelationshipProperty.Comparator.has`.
:param criterion: an optional ClauseElement formulated against the
:param criterion: an optional ClauseElement formulated against the
member class' table or attributes.
:param \**kwargs: key/value pairs corresponding to member class attribute
:param \**kwargs: key/value pairs corresponding to member class attribute
names which will be compared via equality to the corresponding
values.
@@ -337,12 +337,12 @@ class StrategizedProperty(MapperProperty):
def setup(self, context, entity, path, reduced_path, adapter, **kwargs):
self._get_context_strategy(context, reduced_path + (self.key,)).\
setup_query(context, entity, path,
setup_query(context, entity, path,
reduced_path, adapter, **kwargs)
def create_row_processor(self, context, path, reduced_path, mapper, row, adapter):
return self._get_context_strategy(context, reduced_path + (self.key,)).\
create_row_processor(context, path,
create_row_processor(context, path,
reduced_path, mapper, row, adapter)
def do_init(self):
@@ -365,7 +365,7 @@ def serialize_path(path):
return None
return zip(
[m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
[m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
[path[i] for i in range(1, len(path), 2)] + [None]
)
@@ -382,7 +382,7 @@ class MapperOption(object):
"""Describe a modification to a Query."""
propagate_to_loaders = False
"""if True, indicate this option should be carried along
"""if True, indicate this option should be carried along
Query object generated by scalar or object lazy loaders.
"""
@@ -464,9 +464,9 @@ class PropertyOption(MapperOption):
else:
raise sa_exc.ArgumentError(
"Can't find property '%s' on any entity "
"specified in this Query. Note the full path "
"from root (%s) to target entity must be specified."
% (token, ",".join(str(x) for
"specified in this Query. Note the full path "
"from root (%s) to target entity must be specified."
% (token, ",".join(str(x) for
x in query._mapper_entities))
)
else:
@@ -494,7 +494,7 @@ class PropertyOption(MapperOption):
l = []
mappers = []
# _current_path implies we're in a
# _current_path implies we're in a
# secondary load with an existing path
current_path = list(query._current_path)
@@ -520,8 +520,8 @@ class PropertyOption(MapperOption):
if not entity:
entity = self._find_entity_basestring(
query,
token,
query,
token,
raiseerr)
if entity is None:
return [], []
@@ -555,8 +555,8 @@ class PropertyOption(MapperOption):
if not entity:
entity = self._find_entity_prop_comparator(
query,
prop.key,
token.parententity,
prop.key,
token.parententity,
raiseerr)
if not entity:
return [], []
@@ -587,7 +587,7 @@ class PropertyOption(MapperOption):
)
if current_path:
# ran out of tokens before
# ran out of tokens before
# current_path was exhausted.
assert not tokens
return [], []
@@ -630,9 +630,9 @@ def _reduce_path(path):
of the mapper referenced by Mapper.prop1.
"""
return tuple([i % 2 != 0 and
element or
getattr(element, 'base_mapper', element)
return tuple([i % 2 != 0 and
element or
getattr(element, 'base_mapper', element)
for i, element in enumerate(path)])
class LoaderStrategy(object):
@@ -678,7 +678,7 @@ class LoaderStrategy(object):
def setup_query(self, context, entity, path, reduced_path, adapter, **kwargs):
pass
def create_row_processor(self, context, path, reduced_path, mapper,
def create_row_processor(self, context, path, reduced_path, mapper,
row, adapter):
"""Return row processing functions which fulfill the contract
specified by MapperProperty.create_row_processor.
+140 -125
View File
@@ -1,5 +1,5 @@
# orm/mapper.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -214,16 +214,16 @@ class Mapper(object):
local_table = None
"""The :class:`.Selectable` which this :class:`.Mapper` manages.
Typically is an instance of :class:`.Table` or :class:`.Alias`.
May also be ``None``.
Typically is an instance of :class:`.Table` or :class:`.Alias`.
May also be ``None``.
The "local" table is the
selectable that the :class:`.Mapper` is directly responsible for
selectable that the :class:`.Mapper` is directly responsible for
managing from an attribute access and flush perspective. For
non-inheriting mappers, the local table is the same as the
"mapped" table. For joined-table inheritance mappers, local_table
will be the particular sub-table of the overall "join" which
this :class:`.Mapper` represents. If this mapper is a
this :class:`.Mapper` represents. If this mapper is a
single-table inheriting mapper, local_table will be ``None``.
See also :attr:`~.Mapper.mapped_table`.
@@ -233,11 +233,11 @@ class Mapper(object):
mapped_table = None
"""The :class:`.Selectable` to which this :class:`.Mapper` is mapped.
Typically an instance of :class:`.Table`, :class:`.Join`, or
Typically an instance of :class:`.Table`, :class:`.Join`, or
:class:`.Alias`.
The "mapped" table is the selectable that
the mapper selects from during queries. For non-inheriting
The "mapped" table is the selectable that
the mapper selects from during queries. For non-inheriting
mappers, the mapped table is the same as the "local" table.
For joined-table inheritance mappers, mapped_table references the
full :class:`.Join` representing full rows for this particular
@@ -249,7 +249,7 @@ class Mapper(object):
"""
inherits = None
"""References the :class:`.Mapper` which this :class:`.Mapper`
"""References the :class:`.Mapper` which this :class:`.Mapper`
inherits from, if any.
This is a *read only* attribute determined during mapper construction.
@@ -268,7 +268,7 @@ class Mapper(object):
"""
concrete = None
"""Represent ``True`` if this :class:`.Mapper` is a concrete
"""Represent ``True`` if this :class:`.Mapper` is a concrete
inheritance mapper.
This is a *read only* attribute determined during mapper construction.
@@ -291,7 +291,7 @@ class Mapper(object):
primary_key = None
"""An iterable containing the collection of :class:`.Column` objects
which comprise the 'primary key' of the mapped table, from the
which comprise the 'primary key' of the mapped table, from the
perspective of this :class:`.Mapper`.
This list is against the selectable in :attr:`~.Mapper.mapped_table`. In the
@@ -301,7 +301,7 @@ class Mapper(object):
referenced by the :class:`.Join`.
The list is also not necessarily the same as the primary key column
collection associated with the underlying tables; the :class:`.Mapper`
collection associated with the underlying tables; the :class:`.Mapper`
features a ``primary_key`` argument that can override what the
:class:`.Mapper` considers as primary key columns.
@@ -328,7 +328,7 @@ class Mapper(object):
"""
single = None
"""Represent ``True`` if this :class:`.Mapper` is a single table
"""Represent ``True`` if this :class:`.Mapper` is a single table
inheritance mapper.
:attr:`~.Mapper.local_table` will be ``None`` if this flag is set.
@@ -339,8 +339,8 @@ class Mapper(object):
"""
non_primary = None
"""Represent ``True`` if this :class:`.Mapper` is a "non-primary"
mapper, e.g. a mapper that is used only to selet rows but not for
"""Represent ``True`` if this :class:`.Mapper` is a "non-primary"
mapper, e.g. a mapper that is used only to selet rows but not for
persistence management.
This is a *read only* attribute determined during mapper construction.
@@ -364,10 +364,10 @@ class Mapper(object):
"""A mapping of "polymorphic identity" identifiers mapped to :class:`.Mapper`
instances, within an inheritance scenario.
The identifiers can be of any type which is comparable to the
The identifiers can be of any type which is comparable to the
type of column represented by :attr:`~.Mapper.polymorphic_on`.
An inheritance chain of mappers will all reference the same
An inheritance chain of mappers will all reference the same
polymorphic map object. The object is used to correlate incoming
result rows to target mappers.
@@ -402,10 +402,10 @@ class Mapper(object):
"""
columns = None
"""A collection of :class:`.Column` or other scalar expression
"""A collection of :class:`.Column` or other scalar expression
objects maintained by this :class:`.Mapper`.
The collection behaves the same as that of the ``c`` attribute on
The collection behaves the same as that of the ``c`` attribute on
any :class:`.Table` object, except that only those columns included in
this mapping are present, and are keyed based on the attribute name
defined in the mapping, not necessarily the ``key`` attribute of the
@@ -419,11 +419,11 @@ class Mapper(object):
validators = None
"""An immutable dictionary of attributes which have been decorated
using the :func:`~.orm.validates` decorator.
using the :func:`~.orm.validates` decorator.
The dictionary contains string attribute names as keys
mapped to the actual validation method.
"""
c = None
@@ -443,13 +443,13 @@ class Mapper(object):
self.inherits = class_mapper(self.inherits, compile=False)
if not issubclass(self.class_, self.inherits.class_):
raise sa_exc.ArgumentError(
"Class '%s' does not inherit from '%s'" %
"Class '%s' does not inherit from '%s'" %
(self.class_.__name__, self.inherits.class_.__name__))
if self.non_primary != self.inherits.non_primary:
np = not self.non_primary and "primary" or "non-primary"
raise sa_exc.ArgumentError(
"Inheritance of %s mapper for class '%s' is "
"only allowed from a %s mapper" %
"only allowed from a %s mapper" %
(np, self.class_.__name__, np))
# inherit_condition is optional.
if self.local_table is None:
@@ -472,7 +472,7 @@ class Mapper(object):
self.inherits.local_table,
self.local_table)
self.mapped_table = sql.join(
self.inherits.mapped_table,
self.inherits.mapped_table,
self.local_table,
self.inherit_condition)
@@ -499,7 +499,7 @@ class Mapper(object):
"the inherited versioning column. "
"version_id_col should only be specified on "
"the base-most mapper that includes versioning." %
(self.version_id_col.description,
(self.version_id_col.description,
self.inherits.version_id_col.description)
)
@@ -528,7 +528,7 @@ class Mapper(object):
if self.mapped_table is None:
raise sa_exc.ArgumentError(
"Mapper '%s' does not have a mapped_table specified."
"Mapper '%s' does not have a mapped_table specified."
% self)
def _set_with_polymorphic(self, with_polymorphic):
@@ -580,6 +580,12 @@ class Mapper(object):
self.inherits._inheriting_mappers.add(self)
self.passive_updates = self.inherits.passive_updates
self._all_tables = self.inherits._all_tables
for key, prop in mapper._props.iteritems():
if key not in self._props and \
not self._should_exclude(key, key, local=False,
column=None):
self._adapt_inherited_property(key, prop, False)
def _set_polymorphic_on(self, polymorphic_on):
self.polymorphic_on = polymorphic_on
@@ -589,7 +595,7 @@ class Mapper(object):
if self.inherits:
self.dispatch._update(self.inherits.dispatch)
super_extensions = set(chain(*[m._deprecated_extensions
super_extensions = set(chain(*[m._deprecated_extensions
for m in self.inherits.iterate_to_root()]))
else:
super_extensions = set()
@@ -600,7 +606,7 @@ class Mapper(object):
def _configure_listeners(self):
if self.inherits:
super_extensions = set(chain(*[m._deprecated_extensions
super_extensions = set(chain(*[m._deprecated_extensions
for m in self.inherits.iterate_to_root()]))
else:
super_extensions = set()
@@ -647,8 +653,8 @@ class Mapper(object):
"remove *all* current mappers from all classes." %
self.class_)
#else:
# a ClassManager may already exist as
# ClassManager.instrument_attribute() creates
# a ClassManager may already exist as
# ClassManager.instrument_attribute() creates
# new managers for each subclass if they don't yet exist.
_mapper_registry[self] = True
@@ -662,8 +668,8 @@ class Mapper(object):
manager.mapper = self
manager.deferred_scalar_loader = self._load_scalar_attributes
# The remaining members can be added by any mapper,
# The remaining members can be added by any mapper,
# e_name None or not.
if manager.info.get(_INSTRUMENTOR, False):
return
@@ -678,9 +684,10 @@ class Mapper(object):
self._reconstructor = method
event.listen(manager, 'load', _event_on_load, raw=True)
elif hasattr(method, '__sa_validators__'):
include_removes = getattr(method, "__sa_include_removes__", False)
for name in method.__sa_validators__:
self.validators = self.validators.union(
{name : method}
{name : (method, include_removes)}
)
manager.info[_INSTRUMENTOR] = self
@@ -746,10 +753,10 @@ class Mapper(object):
self._readonly_props = set(
self._columntoproperty[col]
for col in self._columntoproperty
if not hasattr(col, 'table') or
if not hasattr(col, 'table') or
col.table not in self._cols_by_table)
# if explicit PK argument sent, add those columns to the
# if explicit PK argument sent, add those columns to the
# primary key mappings
if self._primary_key_argument:
for k in self._primary_key_argument:
@@ -762,23 +769,23 @@ class Mapper(object):
len(self._pks_by_table[self.mapped_table]) == 0:
raise sa_exc.ArgumentError(
"Mapper %s could not assemble any primary "
"key columns for mapped table '%s'" %
"key columns for mapped table '%s'" %
(self, self.mapped_table.description))
elif self.local_table not in self._pks_by_table and \
isinstance(self.local_table, schema.Table):
util.warn("Could not assemble any primary "
"keys for locally mapped table '%s' - "
"no rows will be persisted in this Table."
"no rows will be persisted in this Table."
% self.local_table.description)
if self.inherits and \
not self.concrete and \
not self._primary_key_argument:
# if inheriting, the "primary key" for this mapper is
# if inheriting, the "primary key" for this mapper is
# that of the inheriting (unless concrete or explicit)
self.primary_key = self.inherits.primary_key
else:
# determine primary key from argument or mapped_table pks -
# determine primary key from argument or mapped_table pks -
# reduce to the minimal set of columns
if self._primary_key_argument:
primary_key = sqlutil.reduce_columns(
@@ -793,7 +800,7 @@ class Mapper(object):
if len(primary_key) == 0:
raise sa_exc.ArgumentError(
"Mapper %s could not assemble any primary "
"key columns for mapped table '%s'" %
"key columns for mapped table '%s'" %
(self, self.mapped_table.description))
self.primary_key = tuple(primary_key)
@@ -845,19 +852,19 @@ class Mapper(object):
if column in mapper._columntoproperty:
column_key = mapper._columntoproperty[column].key
self._configure_property(column_key,
column,
init=False,
self._configure_property(column_key,
column,
init=False,
setparent=True)
def _configure_polymorphic_setter(self, init=False):
"""Configure an attribute on the mapper representing the
'polymorphic_on' column, if applicable, and not
"""Configure an attribute on the mapper representing the
'polymorphic_on' column, if applicable, and not
already generated by _configure_properties (which is typical).
Also create a setter function which will assign this
attribute to the value of the 'polymorphic_identity'
upon instance construction, also if applicable. This
upon instance construction, also if applicable. This
routine will run when an instance is created.
"""
@@ -906,15 +913,15 @@ class Mapper(object):
else:
# polymorphic_on is a Column or SQL expression and doesn't
# appear to be mapped.
# this means it can be 1. only present in the with_polymorphic
# this means it can be 1. only present in the with_polymorphic
# selectable or 2. a totally standalone SQL expression which we'd
# hope is compatible with this mapper's mapped_table
col = self.mapped_table.corresponding_column(self.polymorphic_on)
if col is None:
# polymorphic_on doesn't derive from any column/expression
# polymorphic_on doesn't derive from any column/expression
# isn't present in the mapped table.
# we will make a "hidden" ColumnProperty for it.
# Just check that if it's directly a schema.Column and we
# we will make a "hidden" ColumnProperty for it.
# Just check that if it's directly a schema.Column and we
# have with_polymorphic, it's likely a user error if the
# schema.Column isn't represented somehow in either mapped_table or
# with_polymorphic. Otherwise as of 0.7.4 we just go with it
@@ -932,15 +939,14 @@ class Mapper(object):
"loads will not function properly"
% col.description)
else:
# column/expression that polymorphic_on derives from
# column/expression that polymorphic_on derives from
# is present in our mapped table
# and is probably mapped, but polymorphic_on itself
# is not. This happens when
# the polymorphic_on is only directly present in the
# is not. This happens when
# the polymorphic_on is only directly present in the
# with_polymorphic selectable, as when use polymorphic_union.
# we'll make a separate ColumnProperty for it.
instrument = True
key = getattr(col, 'key', None)
if key:
if self._should_exclude(col.key, col.key, False, col):
@@ -952,7 +958,7 @@ class Mapper(object):
key = col.key
self._configure_property(
key,
key,
properties.ColumnProperty(col, _instrument=instrument),
init=init, setparent=True)
polymorphic_key = key
@@ -998,15 +1004,15 @@ class Mapper(object):
self._configure_property(key, prop, init=False, setparent=False)
elif key not in self._props:
self._configure_property(
key,
properties.ConcreteInheritedProperty(),
key,
properties.ConcreteInheritedProperty(),
init=init, setparent=True)
def _configure_property(self, key, prop, init=True, setparent=True):
self._log("_configure_property(%s, %s)", key, prop.__class__.__name__)
if not isinstance(prop, MapperProperty):
# we were passed a Column or a list of Columns;
# we were passed a Column or a list of Columns;
# generate a properties.ColumnProperty
columns = util.to_list(prop)
column = columns[0]
@@ -1026,7 +1032,7 @@ class Mapper(object):
"explicitly."
% (prop.columns[-1], column, key))
# existing properties.ColumnProperty from an inheriting
# existing properties.ColumnProperty from an inheriting
# mapper. make a copy and append our column to it
prop = prop.copy()
prop.columns.insert(0, column)
@@ -1065,14 +1071,14 @@ class Mapper(object):
"(including its availability as a foreign key), "
"use the 'include_properties' or 'exclude_properties' "
"mapper arguments to control specifically which table "
"columns get mapped." %
"columns get mapped." %
(key, self, column.key, prop))
if isinstance(prop, properties.ColumnProperty):
col = self.mapped_table.corresponding_column(prop.columns[0])
# if the column is not present in the mapped table,
# test if a column has been added after the fact to the
# if the column is not present in the mapped table,
# test if a column has been added after the fact to the
# parent table (or their parent, etc.) [ticket:1570]
if col is None and self.inherits:
path = [self]
@@ -1086,20 +1092,20 @@ class Mapper(object):
break
path.append(m)
# subquery expression, column not present in the mapped
# subquery expression, column not present in the mapped
# selectable.
if col is None:
col = prop.columns[0]
# column is coming in after _readonly_props was
# column is coming in after _readonly_props was
# initialized; check for 'readonly'
if hasattr(self, '_readonly_props') and \
(not hasattr(col, 'table') or
(not hasattr(col, 'table') or
col.table not in self._cols_by_table):
self._readonly_props.add(prop)
else:
# if column is coming in after _cols_by_table was
# if column is coming in after _cols_by_table was
# initialized, ensure the col is in the right set
if hasattr(self, '_cols_by_table') and \
col.table in self._cols_by_table and \
@@ -1199,10 +1205,10 @@ class Mapper(object):
def _log_desc(self):
return "(" + self.class_.__name__ + \
"|" + \
(self.local_table is not None and
self.local_table.description or
(self.local_table is not None and
self.local_table.description or
str(self.local_table)) +\
(self.non_primary and
(self.non_primary and
"|non-primary" or "") + ")"
def _log(self, msg, *args):
@@ -1223,7 +1229,7 @@ class Mapper(object):
def __str__(self):
return "Mapper|%s|%s%s" % (
self.class_.__name__,
self.local_table is not None and
self.local_table is not None and
self.local_table.description or None,
self.non_primary and "|non-primary" or ""
)
@@ -1288,7 +1294,7 @@ class Mapper(object):
for m in mappers:
if not m.isa(self):
raise sa_exc.InvalidRequestError(
"%r does not inherit from %r" %
"%r does not inherit from %r" %
(m, self))
else:
mappers = []
@@ -1387,7 +1393,7 @@ class Mapper(object):
mappers])
):
if getattr(c, '_is_polymorphic_discriminator', False) and \
(self.polymorphic_on is None or
(self.polymorphic_on is None or
c.columns[0] is not self.polymorphic_on):
continue
yield c
@@ -1452,7 +1458,7 @@ class Mapper(object):
return result
def _is_userland_descriptor(self, obj):
if isinstance(obj, (MapperProperty,
if isinstance(obj, (MapperProperty,
attributes.QueryableAttribute)):
return False
elif not hasattr(obj, '__get__'):
@@ -1505,7 +1511,7 @@ class Mapper(object):
return False
def common_parent(self, other):
"""Return true if the given mapper shares a
"""Return true if the given mapper shares a
common inherited parent as this mapper."""
return self.base_mapper is other.base_mapper
@@ -1634,7 +1640,7 @@ class Mapper(object):
for col in self.primary_key
]
def _get_state_attr_by_column(self, state, dict_, column,
def _get_state_attr_by_column(self, state, dict_, column,
passive=attributes.PASSIVE_OFF):
prop = self._columntoproperty[column]
return state.manager[prop.key].impl.get(state, dict_, passive=passive)
@@ -1648,7 +1654,7 @@ class Mapper(object):
dict_ = attributes.instance_dict(obj)
return self._get_committed_state_attr_by_column(state, dict_, column)
def _get_committed_state_attr_by_column(self, state, dict_,
def _get_committed_state_attr_by_column(self, state, dict_,
column, passive=attributes.PASSIVE_OFF):
prop = self._columntoproperty[column]
@@ -1674,8 +1680,8 @@ class Mapper(object):
statement = self._optimized_get_statement(state, attribute_names)
if statement is not None:
result = session.query(self).from_statement(statement).\
_load_on_ident(None,
only_load_props=attribute_names,
_load_on_ident(None,
only_load_props=attribute_names,
refresh_state=state)
if result is False:
@@ -1698,16 +1704,16 @@ class Mapper(object):
_none_set.issuperset(identity_key):
util.warn("Instance %s to be refreshed doesn't "
"contain a full primary key - can't be refreshed "
"(and shouldn't be expired, either)."
"(and shouldn't be expired, either)."
% state_str(state))
return
result = session.query(self)._load_on_ident(
identity_key,
refresh_state=state,
identity_key,
refresh_state=state,
only_load_props=attribute_names)
# if instance is pending, a refresh operation
# if instance is pending, a refresh operation
# may not complete (even if PK attributes are assigned)
if has_key and result is None:
raise orm_exc.ObjectDeletedError(state)
@@ -1716,16 +1722,16 @@ class Mapper(object):
"""assemble a WHERE clause which retrieves a given state by primary
key, using a minimized set of tables.
Applies to a joined-table inheritance mapper where the
Applies to a joined-table inheritance mapper where the
requested attribute names are only present on joined tables,
not the base table. The WHERE clause attempts to include
not the base table. The WHERE clause attempts to include
only those tables to minimize joins.
"""
props = self._props
tables = set(chain(
*[sqlutil.find_tables(c, check_columns=True)
*[sqlutil.find_tables(c, check_columns=True)
for key in attribute_names
for c in props[key].columns]
))
@@ -1744,8 +1750,8 @@ class Mapper(object):
if leftcol.table not in tables:
leftval = self._get_committed_state_attr_by_column(
state, state.dict,
leftcol,
state, state.dict,
leftcol,
passive=attributes.PASSIVE_NO_INITIALIZE)
if leftval is attributes.PASSIVE_NO_RESULT or leftval is None:
raise ColumnsNotAvailable()
@@ -1753,8 +1759,8 @@ class Mapper(object):
type_=binary.right.type)
elif rightcol.table not in tables:
rightval = self._get_committed_state_attr_by_column(
state, state.dict,
rightcol,
state, state.dict,
rightcol,
passive=attributes.PASSIVE_NO_INITIALIZE)
if rightval is attributes.PASSIVE_NO_RESULT or rightval is None:
raise ColumnsNotAvailable()
@@ -1770,8 +1776,8 @@ class Mapper(object):
start = True
if start and not mapper.single:
allconds.append(visitors.cloned_traverse(
mapper.inherit_condition,
{},
mapper.inherit_condition,
{},
{'binary':visit_binary}
)
)
@@ -1804,7 +1810,7 @@ class Mapper(object):
visited_states = set()
prp, mpp = object(), object()
visitables = deque([(deque(self._props.values()), prp,
visitables = deque([(deque(self._props.values()), prp,
state, state.dict)])
while visitables:
@@ -1817,7 +1823,7 @@ class Mapper(object):
prop = iterator.popleft()
if type_ not in prop.cascade:
continue
queue = deque(prop.cascade_iterator(type_, parent_state,
queue = deque(prop.cascade_iterator(type_, parent_state,
parent_dict, visited_states, halt_on))
if queue:
visitables.append((queue,mpp, None, None))
@@ -1826,8 +1832,8 @@ class Mapper(object):
corresponding_dict = iterator.popleft()
yield instance, instance_mapper, \
corresponding_state, corresponding_dict
visitables.append((deque(instance_mapper._props.values()),
prp, corresponding_state,
visitables.append((deque(instance_mapper._props.values()),
prp, corresponding_state,
corresponding_dict))
@_memoized_configured_property
@@ -1884,7 +1890,7 @@ class Mapper(object):
@util.memoized_property
def _table_to_equated(self):
"""memoized map of tables to collections of columns to be
"""memoized map of tables to collections of columns to be
synchronized upwards to the base mapper."""
result = util.defaultdict(list)
@@ -1900,16 +1906,16 @@ class Mapper(object):
return result
def _instance_processor(self, context, path, reduced_path, adapter,
polymorphic_from=None,
def _instance_processor(self, context, path, reduced_path, adapter,
polymorphic_from=None,
only_load_props=None, refresh_state=None,
polymorphic_discriminator=None):
"""Produce a mapper level row processor callable
"""Produce a mapper level row processor callable
which processes rows into mapped instances."""
# note that this method, most of which exists in a closure
# called _instance(), resists being broken out, as
# called _instance(), resists being broken out, as
# attempts to do so tend to add significant function
# call overhead. _instance() is the most
# performance-critical section in the whole ORM.
@@ -2019,7 +2025,7 @@ class Mapper(object):
identitykey = self._identity_key_from_state(refresh_state)
else:
identitykey = (
identity_class,
identity_class,
tuple([row[column] for column in pk_cols])
)
@@ -2036,22 +2042,22 @@ class Mapper(object):
version_id_col is not None and \
context.version_check and \
self._get_state_attr_by_column(
state,
dict_,
state,
dict_,
self.version_id_col) != \
row[version_id_col]:
raise orm_exc.StaleDataError(
"Instance '%s' has version id '%s' which "
"does not match database-loaded version id '%s'."
% (state_str(state),
"does not match database-loaded version id '%s'."
% (state_str(state),
self._get_state_attr_by_column(
state, dict_,
self.version_id_col),
row[version_id_col]))
elif refresh_state:
# out of band refresh_state detected (i.e. its not in the
# session.identity_map) honor it anyway. this can happen
# session.identity_map) honor it anyway. this can happen
# if a _get() occurs within save_obj(), such as
# when eager_defaults is True.
state = refresh_state
@@ -2072,7 +2078,7 @@ class Mapper(object):
if create_instance:
for fn in create_instance:
instance = fn(self, context,
instance = fn(self, context,
row, self.class_)
if instance is not EXT_CONTINUE:
manager = attributes.manager_of_class(
@@ -2103,8 +2109,8 @@ class Mapper(object):
if populate_instance:
for fn in populate_instance:
ret = fn(self, context, row, state,
only_load_props=only_load_props,
ret = fn(self, context, row, state,
only_load_props=only_load_props,
instancekey=identitykey, isnew=isnew)
if ret is not EXT_CONTINUE:
break
@@ -2132,8 +2138,8 @@ class Mapper(object):
if populate_instance:
for fn in populate_instance:
ret = fn(self, context, row, state,
only_load_props=attrs,
ret = fn(self, context, row, state,
only_load_props=attrs,
instancekey=identitykey, isnew=isnew)
if ret is not EXT_CONTINUE:
break
@@ -2153,7 +2159,7 @@ class Mapper(object):
if result is not None:
if append_result:
for fn in append_result:
if fn(self, context, row, state,
if fn(self, context, row, state,
result, instancekey=identitykey,
isnew=isnew) is not EXT_CONTINUE:
break
@@ -2173,7 +2179,7 @@ class Mapper(object):
pops = (new_populators, existing_populators, delayed_populators, eager_populators)
for prop in self._props.itervalues():
for i, pop in enumerate(prop.create_row_processor(
context, path,
context, path,
reduced_path,
self, row, adapter)):
if pop is not None:
@@ -2196,8 +2202,8 @@ class Mapper(object):
if mapper is self:
return None
# replace the tip of the path info with the subclass mapper
# being used. that way accurate "load_path" info is available
# replace the tip of the path info with the subclass mapper
# being used. that way accurate "load_path" info is available
# for options invoked during deferred loads.
# we lose AliasedClass path elements this way, but currently,
# those are not needed at this stage.
@@ -2205,7 +2211,7 @@ class Mapper(object):
# this asserts to true
#assert mapper.isa(_class_to_mapper(path[-1]))
return mapper._instance_processor(context, path[0:-1] + (mapper,),
return mapper._instance_processor(context, path[0:-1] + (mapper,),
reduced_path[0:-1] + (mapper.base_mapper,),
adapter,
polymorphic_from=self)
@@ -2217,14 +2223,14 @@ def configure_mappers():
"""Initialize the inter-mapper relationships of all mappers that
have been constructed thus far.
This function can be called any number of times, but in
This function can be called any number of times, but in
most cases is handled internally.
"""
global _new_mappers
if not _new_mappers:
return
return
_call_configured = None
_COMPILE_MUTEX.acquire()
@@ -2240,8 +2246,8 @@ def configure_mappers():
return
# initialize properties on all mappers
# note that _mapper_registry is unordered, which
# may randomly conceal/reveal issues related to
# note that _mapper_registry is unordered, which
# may randomly conceal/reveal issues related to
# the order of mapper compilation
for mapper in list(_mapper_registry):
if getattr(mapper, '_configure_failed', False):
@@ -2291,7 +2297,7 @@ def reconstructor(fn):
fn.__sa_reconstructor__ = True
return fn
def validates(*names):
def validates(*names, **kw):
"""Decorate a method as a 'validator' for one or more named properties.
Designates a method as a validator, a method which receives the
@@ -2307,9 +2313,18 @@ def validates(*names):
an assertion to avoid recursion overflows. This is a reentrant
condition which is not supported.
:param \*names: list of attribute names to be validated.
:param include_removes: if True, "remove" events will be
sent as well - the validation function must accept an additional
argument "is_remove" which will be a boolean.
.. versionadded:: 0.7.7
"""
include_removes = kw.pop('include_removes', False)
def wrap(fn):
fn.__sa_validators__ = names
fn.__sa_include_removes__ = include_removes
return fn
return wrap
@@ -2320,7 +2335,7 @@ def _event_on_load(state, ctx):
def _event_on_first_init(manager, cls):
"""Initial mapper compilation trigger.
instrumentation calls this one when InstanceState
is first generated, and is needed for legacy mutable
attributes to work.
@@ -2333,11 +2348,11 @@ def _event_on_first_init(manager, cls):
def _event_on_init(state, args, kwargs):
"""Run init_instance hooks.
This also includes mapper compilation, normally not needed
here but helps with some piecemeal configuration
scenarios (such as in the ORM tutorial).
"""
instrumenting_mapper = state.manager.info.get(_INSTRUMENTOR)
+109 -107
View File
@@ -1,5 +1,5 @@
# orm/persistence.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -23,7 +23,7 @@ from sqlalchemy.orm import attributes, sync, \
from sqlalchemy.orm.util import _state_mapper, state_str
def save_obj(base_mapper, states, uowtransaction, single=False):
"""Issue ``INSERT`` and/or ``UPDATE`` statements for a list
"""Issue ``INSERT`` and/or ``UPDATE`` statements for a list
of objects.
This is called within the context of a UOWTransaction during a
@@ -40,30 +40,30 @@ def save_obj(base_mapper, states, uowtransaction, single=False):
return
states_to_insert, states_to_update = _organize_states_for_save(
base_mapper,
states,
base_mapper,
states,
uowtransaction)
cached_connections = _cached_connection_dict(base_mapper)
for table, mapper in base_mapper._sorted_tables.iteritems():
insert = _collect_insert_commands(base_mapper, uowtransaction,
insert = _collect_insert_commands(base_mapper, uowtransaction,
table, states_to_insert)
update = _collect_update_commands(base_mapper, uowtransaction,
update = _collect_update_commands(base_mapper, uowtransaction,
table, states_to_update)
if update:
_emit_update_statements(base_mapper, uowtransaction,
cached_connections,
_emit_update_statements(base_mapper, uowtransaction,
cached_connections,
mapper, table, update)
if insert:
_emit_insert_statements(base_mapper, uowtransaction,
cached_connections,
_emit_insert_statements(base_mapper, uowtransaction,
cached_connections,
table, insert)
_finalize_insert_update_commands(base_mapper, uowtransaction,
_finalize_insert_update_commands(base_mapper, uowtransaction,
states_to_insert, states_to_update)
def post_update(base_mapper, states, uowtransaction, post_update_cols):
@@ -74,18 +74,18 @@ def post_update(base_mapper, states, uowtransaction, post_update_cols):
cached_connections = _cached_connection_dict(base_mapper)
states_to_update = _organize_states_for_post_update(
base_mapper,
base_mapper,
states, uowtransaction)
for table, mapper in base_mapper._sorted_tables.iteritems():
update = _collect_post_update_commands(base_mapper, uowtransaction,
table, states_to_update,
update = _collect_post_update_commands(base_mapper, uowtransaction,
table, states_to_update,
post_update_cols)
if update:
_emit_post_update_statements(base_mapper, uowtransaction,
cached_connections,
_emit_post_update_statements(base_mapper, uowtransaction,
cached_connections,
mapper, table, update)
def delete_obj(base_mapper, states, uowtransaction):
@@ -99,19 +99,19 @@ def delete_obj(base_mapper, states, uowtransaction):
cached_connections = _cached_connection_dict(base_mapper)
states_to_delete = _organize_states_for_delete(
base_mapper,
base_mapper,
states,
uowtransaction)
table_to_mapper = base_mapper._sorted_tables
for table in reversed(table_to_mapper.keys()):
delete = _collect_delete_commands(base_mapper, uowtransaction,
delete = _collect_delete_commands(base_mapper, uowtransaction,
table, states_to_delete)
mapper = table_to_mapper[table]
_emit_delete_statements(base_mapper, uowtransaction,
_emit_delete_statements(base_mapper, uowtransaction,
cached_connections, mapper, table, delete)
for state, state_dict, mapper, has_identity, connection \
@@ -121,20 +121,20 @@ def delete_obj(base_mapper, states, uowtransaction):
def _organize_states_for_save(base_mapper, states, uowtransaction):
"""Make an initial pass across a set of states for INSERT or
UPDATE.
This includes splitting out into distinct lists for
each, calling before_insert/before_update, obtaining
key information for each state including its dictionary,
mapper, the connection to use for the execution per state,
and the identity flag.
"""
states_to_insert = []
states_to_update = []
for state, dict_, mapper, connection in _connections_for_states(
base_mapper, uowtransaction,
base_mapper, uowtransaction,
states):
has_identity = bool(state.key)
@@ -148,9 +148,9 @@ def _organize_states_for_save(base_mapper, states, uowtransaction):
else:
mapper.dispatch.before_update(mapper, connection, state)
# detect if we have a "pending" instance (i.e. has
# no instance_key attached to it), and another instance
# with the same identity key already exists as persistent.
# detect if we have a "pending" instance (i.e. has
# no instance_key attached to it), and another instance
# with the same identity key already exists as persistent.
# convert to an UPDATE if so.
if not has_identity and \
instance_key in uowtransaction.session.identity_map:
@@ -160,14 +160,14 @@ def _organize_states_for_save(base_mapper, states, uowtransaction):
if not uowtransaction.is_deleted(existing):
raise orm_exc.FlushError(
"New instance %s with identity key %s conflicts "
"with persistent instance %s" %
"with persistent instance %s" %
(state_str(state), instance_key,
state_str(existing)))
base_mapper._log_debug(
"detected row switch for identity %s. "
"will update %s, remove %s from "
"transaction", instance_key,
"transaction", instance_key,
state_str(state), state_str(existing))
# remove the "delete" flag from the existing element
@@ -176,55 +176,55 @@ def _organize_states_for_save(base_mapper, states, uowtransaction):
if not has_identity and not row_switch:
states_to_insert.append(
(state, dict_, mapper, connection,
(state, dict_, mapper, connection,
has_identity, instance_key, row_switch)
)
else:
states_to_update.append(
(state, dict_, mapper, connection,
(state, dict_, mapper, connection,
has_identity, instance_key, row_switch)
)
return states_to_insert, states_to_update
def _organize_states_for_post_update(base_mapper, states,
def _organize_states_for_post_update(base_mapper, states,
uowtransaction):
"""Make an initial pass across a set of states for UPDATE
corresponding to post_update.
This includes obtaining key information for each state
including its dictionary, mapper, the connection to use for
This includes obtaining key information for each state
including its dictionary, mapper, the connection to use for
the execution per state.
"""
return list(_connections_for_states(base_mapper, uowtransaction,
return list(_connections_for_states(base_mapper, uowtransaction,
states))
def _organize_states_for_delete(base_mapper, states, uowtransaction):
"""Make an initial pass across a set of states for DELETE.
This includes calling out before_delete and obtaining
key information for each state including its dictionary,
mapper, the connection to use for the execution per state.
"""
states_to_delete = []
for state, dict_, mapper, connection in _connections_for_states(
base_mapper, uowtransaction,
base_mapper, uowtransaction,
states):
mapper.dispatch.before_delete(mapper, connection, state)
states_to_delete.append((state, dict_, mapper,
states_to_delete.append((state, dict_, mapper,
bool(state.key), connection))
return states_to_delete
def _collect_insert_commands(base_mapper, uowtransaction, table,
def _collect_insert_commands(base_mapper, uowtransaction, table,
states_to_insert):
"""Identify sets of values to use in INSERT statements for a
list of states.
"""
insert = []
for state, state_dict, mapper, connection, has_identity, \
@@ -242,7 +242,7 @@ def _collect_insert_commands(base_mapper, uowtransaction, table,
if col is mapper.version_id_col:
params[col.key] = mapper.version_id_generator(None)
else:
# pull straight from the dict for
# pull straight from the dict for
# pending objects
prop = mapper._columntoproperty[col]
value = state_dict.get(prop.key, None)
@@ -259,15 +259,15 @@ def _collect_insert_commands(base_mapper, uowtransaction, table,
else:
params[col.key] = value
insert.append((state, state_dict, params, mapper,
insert.append((state, state_dict, params, mapper,
connection, value_params, has_all_pks))
return insert
def _collect_update_commands(base_mapper, uowtransaction,
def _collect_update_commands(base_mapper, uowtransaction,
table, states_to_update):
"""Identify sets of values to use in UPDATE statements for a
list of states.
This function works intricately with the history system
to determine exactly what values should be updated
as well as how the row should be matched within an UPDATE
@@ -292,14 +292,14 @@ def _collect_update_commands(base_mapper, uowtransaction,
if col is mapper.version_id_col:
params[col._label] = \
mapper._get_committed_state_attr_by_column(
row_switch or state,
row_switch and row_switch.dict
row_switch or state,
row_switch and row_switch.dict
or state_dict,
col)
prop = mapper._columntoproperty[col]
history = attributes.get_state_history(
state, prop.key,
state, prop.key,
attributes.PASSIVE_NO_INITIALIZE
)
if history.added:
@@ -309,20 +309,20 @@ def _collect_update_commands(base_mapper, uowtransaction,
params[col.key] = mapper.version_id_generator(
params[col._label])
# HACK: check for history, in case the
# HACK: check for history, in case the
# history is only
# in a different table than the one
# in a different table than the one
# where the version_id_col is.
for prop in mapper._columntoproperty.itervalues():
history = attributes.get_state_history(
state, prop.key,
state, prop.key,
attributes.PASSIVE_NO_INITIALIZE)
if history.added:
hasdata = True
else:
prop = mapper._columntoproperty[col]
history = attributes.get_state_history(
state, prop.key,
state, prop.key,
attributes.PASSIVE_NO_INITIALIZE)
if history.added:
if isinstance(history.added[0],
@@ -344,7 +344,7 @@ def _collect_update_commands(base_mapper, uowtransaction,
value = history.added[0]
params[col._label] = value
else:
# use the old value to
# use the old value to
# locate the row
value = history.deleted[0]
params[col._label] = value
@@ -374,12 +374,12 @@ def _collect_update_commands(base_mapper, uowtransaction,
"Can't update table "
"using NULL for primary "
"key value")
update.append((state, state_dict, params, mapper,
update.append((state, state_dict, params, mapper,
connection, value_params))
return update
def _collect_post_update_commands(base_mapper, uowtransaction, table,
def _collect_post_update_commands(base_mapper, uowtransaction, table,
states_to_update, post_update_cols):
"""Identify sets of values to use in UPDATE statements for a
list of states within a post_update operation.
@@ -403,20 +403,20 @@ def _collect_post_update_commands(base_mapper, uowtransaction, table,
elif col in post_update_cols:
prop = mapper._columntoproperty[col]
history = attributes.get_state_history(
state, prop.key,
state, prop.key,
attributes.PASSIVE_NO_INITIALIZE)
if history.added:
value = history.added[0]
params[col.key] = value
hasdata = True
if hasdata:
update.append((state, state_dict, params, mapper,
update.append((state, state_dict, params, mapper,
connection))
return update
def _collect_delete_commands(base_mapper, uowtransaction, table,
def _collect_delete_commands(base_mapper, uowtransaction, table,
states_to_delete):
"""Identify values to use in DELETE statements for a list of
"""Identify values to use in DELETE statements for a list of
states to be deleted."""
delete = util.defaultdict(list)
@@ -448,7 +448,7 @@ def _collect_delete_commands(base_mapper, uowtransaction, table,
return delete
def _emit_update_statements(base_mapper, uowtransaction,
def _emit_update_statements(base_mapper, uowtransaction,
cached_connections, mapper, table, update):
"""Emit UPDATE statements corresponding to value lists collected
by _collect_update_commands()."""
@@ -466,7 +466,7 @@ def _emit_update_statements(base_mapper, uowtransaction,
if needs_version_id:
clause.clauses.append(mapper.version_id_col ==\
sql.bindparam(mapper.version_id_col._label,
type_=col.type))
type_=mapper.version_id_col.type))
return table.update(clause)
@@ -486,13 +486,13 @@ def _emit_update_statements(base_mapper, uowtransaction,
_postfetch(
mapper,
uowtransaction,
table,
state,
state_dict,
c.context.prefetch_cols,
uowtransaction,
table,
state,
state_dict,
c.context.prefetch_cols,
c.context.postfetch_cols,
c.context.compiled_parameters[0],
c.context.compiled_parameters[0],
value_params)
rows += c.rowcount
@@ -505,11 +505,11 @@ def _emit_update_statements(base_mapper, uowtransaction,
elif needs_version_id:
util.warn("Dialect %s does not support updated rowcount "
"- versioning cannot be verified." %
"- versioning cannot be verified." %
c.dialect.dialect_description,
stacklevel=12)
def _emit_insert_statements(base_mapper, uowtransaction,
def _emit_insert_statements(base_mapper, uowtransaction,
cached_connections, table, insert):
"""Emit INSERT statements corresponding to value lists collected
by _collect_insert_commands()."""
@@ -517,10 +517,10 @@ def _emit_insert_statements(base_mapper, uowtransaction,
statement = base_mapper._memo(('insert', table), table.insert)
for (connection, pkeys, hasvalue, has_all_pks), \
records in groupby(insert,
lambda rec: (rec[4],
rec[2].keys(),
bool(rec[5]),
records in groupby(insert,
lambda rec: (rec[4],
rec[2].keys(),
bool(rec[5]),
rec[6])
):
if has_all_pks and not hasvalue:
@@ -529,19 +529,19 @@ def _emit_insert_statements(base_mapper, uowtransaction,
c = cached_connections[connection].\
execute(statement, multiparams)
for (state, state_dict, params, mapper,
for (state, state_dict, params, mapper,
conn, value_params, has_all_pks), \
last_inserted_params in \
zip(records, c.context.compiled_parameters):
_postfetch(
mapper,
uowtransaction,
uowtransaction,
table,
state,
state,
state_dict,
c.context.prefetch_cols,
c.context.postfetch_cols,
last_inserted_params,
last_inserted_params,
value_params)
else:
@@ -561,31 +561,31 @@ def _emit_insert_statements(base_mapper, uowtransaction,
if primary_key is not None:
# set primary key attributes
for pk, col in zip(primary_key,
for pk, col in zip(primary_key,
mapper._pks_by_table[table]):
prop = mapper._columntoproperty[col]
if state_dict.get(prop.key) is None:
# TODO: would rather say:
#state_dict[prop.key] = pk
mapper._set_state_attr_by_column(
state,
state_dict,
state,
state_dict,
col, pk)
_postfetch(
mapper,
uowtransaction,
table,
state,
uowtransaction,
table,
state,
state_dict,
result.context.prefetch_cols,
result.context.prefetch_cols,
result.context.postfetch_cols,
result.context.compiled_parameters[0],
result.context.compiled_parameters[0],
value_params)
def _emit_post_update_statements(base_mapper, uowtransaction,
def _emit_post_update_statements(base_mapper, uowtransaction,
cached_connections, mapper, table, update):
"""Emit UPDATE statements corresponding to value lists collected
by _collect_post_update_commands()."""
@@ -603,19 +603,19 @@ def _emit_post_update_statements(base_mapper, uowtransaction,
# execute each UPDATE in the order according to the original
# list of states to guarantee row access order, but
# also group them into common (connection, cols) sets
# also group them into common (connection, cols) sets
# to support executemany().
for key, grouper in groupby(
update, lambda rec: (rec[4], rec[2].keys())
):
connection = key[0]
multiparams = [params for state, state_dict,
multiparams = [params for state, state_dict,
params, mapper, conn in grouper]
cached_connections[connection].\
execute(statement, multiparams)
def _emit_delete_statements(base_mapper, uowtransaction, cached_connections,
def _emit_delete_statements(base_mapper, uowtransaction, cached_connections,
mapper, table, delete):
"""Emit DELETE statements corresponding to value lists collected
by _collect_delete_commands()."""
@@ -631,9 +631,9 @@ def _emit_delete_statements(base_mapper, uowtransaction, cached_connections,
if need_version_id:
clause.clauses.append(
mapper.version_id_col ==
mapper.version_id_col ==
sql.bindparam(
mapper.version_id_col.key,
mapper.version_id_col.key,
type_=mapper.version_id_col.type
)
)
@@ -657,13 +657,13 @@ def _emit_delete_statements(base_mapper, uowtransaction, cached_connections,
if rows != len(del_objects):
raise orm_exc.StaleDataError(
"DELETE statement on table '%s' expected to "
"delete %d row(s); %d were matched." %
"delete %d row(s); %d were matched." %
(table.description, len(del_objects), c.rowcount)
)
else:
util.warn(
"Dialect %s does not support deleted rowcount "
"- versioning cannot be verified." %
"- versioning cannot be verified." %
connection.dialect.dialect_description,
stacklevel=12)
connection.execute(statement, del_objects)
@@ -671,11 +671,11 @@ def _emit_delete_statements(base_mapper, uowtransaction, cached_connections,
connection.execute(statement, del_objects)
def _finalize_insert_update_commands(base_mapper, uowtransaction,
def _finalize_insert_update_commands(base_mapper, uowtransaction,
states_to_insert, states_to_update):
"""finalize state on states that have been inserted or updated,
including calling after_insert/after_update events.
"""
for state, state_dict, mapper, connection, has_identity, \
instance_key, row_switch in states_to_insert + \
@@ -683,7 +683,7 @@ def _finalize_insert_update_commands(base_mapper, uowtransaction,
if mapper._readonly_props:
readonly = state.unmodified_intersection(
[p.key for p in mapper._readonly_props
[p.key for p in mapper._readonly_props
if p.expire_on_flush or p.key not in state.dict]
)
if readonly:
@@ -703,7 +703,7 @@ def _finalize_insert_update_commands(base_mapper, uowtransaction,
else:
mapper.dispatch.after_update(mapper, connection, state)
def _postfetch(mapper, uowtransaction, table,
def _postfetch(mapper, uowtransaction, table,
state, dict_, prefetch_cols, postfetch_cols,
params, value_params):
"""Expire attributes in need of newly persisted database state,
@@ -718,9 +718,9 @@ def _postfetch(mapper, uowtransaction, table,
mapper._set_state_attr_by_column(state, dict_, c, params[c.key])
if postfetch_cols:
state.expire_attributes(state.dict,
[mapper._columntoproperty[c].key
for c in postfetch_cols if c in
state.expire_attributes(state.dict,
[mapper._columntoproperty[c].key
for c in postfetch_cols if c in
mapper._columntoproperty]
)
@@ -728,33 +728,35 @@ def _postfetch(mapper, uowtransaction, table,
# TODO: this still goes a little too often. would be nice to
# have definitive list of "columns that changed" here
for m, equated_pairs in mapper._table_to_equated[table]:
sync.populate(state, m, state, m,
equated_pairs,
sync.populate(state, m, state, m,
equated_pairs,
uowtransaction,
mapper.passive_updates)
def _connections_for_states(base_mapper, uowtransaction, states):
"""Return an iterator of (state, state.dict, mapper, connection).
The states are sorted according to _sort_states, then paired
with the connection they should be using for the given
unit of work transaction.
"""
# if session has a connection callable,
# organize individual states with the connection
# organize individual states with the connection
# to use for update
if uowtransaction.session.connection_callable:
connection_callable = \
uowtransaction.session.connection_callable
else:
connection = uowtransaction.transaction.connection(
base_mapper)
connection = None
connection_callable = None
for state in _sort_states(states):
if connection_callable:
connection = connection_callable(base_mapper, state.obj())
elif not connection:
connection = uowtransaction.transaction.connection(
base_mapper)
mapper = _state_mapper(state)
+139 -144
View File
@@ -1,5 +1,5 @@
# orm/properties.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -33,9 +33,9 @@ from descriptor_props import CompositeProperty, SynonymProperty, \
class ColumnProperty(StrategizedProperty):
"""Describes an object attribute that corresponds to a table column.
Public constructor is the :func:`.orm.column_property` function.
"""
def __init__(self, *columns, **kwargs):
@@ -62,7 +62,7 @@ class ColumnProperty(StrategizedProperty):
"""
self._orig_columns = [expression._labeled(c) for c in columns]
self.columns = [expression._labeled(_orm_deannotate(c))
self.columns = [expression._labeled(_orm_deannotate(c))
for c in columns]
self.group = kwargs.pop('group', None)
self.deferred = kwargs.pop('deferred', False)
@@ -88,7 +88,7 @@ class ColumnProperty(StrategizedProperty):
if kwargs:
raise TypeError(
"%s received unexpected keyword argument(s): %s" % (
self.__class__.__name__,
self.__class__.__name__,
', '.join(sorted(kwargs.keys()))))
util.set_creation_order(self)
@@ -104,9 +104,9 @@ class ColumnProperty(StrategizedProperty):
return
attributes.register_descriptor(
mapper.class_,
self.key,
comparator=self.comparator_factory(self, mapper),
mapper.class_,
self.key,
comparator=self.comparator_factory(self, mapper),
parententity=mapper,
doc=self.doc
)
@@ -124,19 +124,21 @@ class ColumnProperty(StrategizedProperty):
def copy(self):
return ColumnProperty(
deferred=self.deferred,
group=self.group,
deferred=self.deferred,
group=self.group,
active_history=self.active_history,
*self.columns)
def _getcommitted(self, state, dict_, column,
def _getcommitted(self, state, dict_, column,
passive=attributes.PASSIVE_OFF):
return state.get_impl(self.key).\
get_committed_value(state, dict_, passive=passive)
def merge(self, session, source_state, source_dict, dest_state,
def merge(self, session, source_state, source_dict, dest_state,
dest_dict, load, _recursive):
if self.key in source_dict:
if not self.instrument:
return
elif self.key in source_dict:
value = source_dict[self.key]
if not load:
@@ -144,9 +146,8 @@ class ColumnProperty(StrategizedProperty):
else:
impl = dest_state.get_impl(self.key)
impl.set(dest_state, dest_dict, value, None)
else:
if dest_state.has_identity and self.key not in dest_dict:
dest_state.expire_attributes(dest_dict, [self.key])
elif dest_state.has_identity and self.key not in dest_dict:
dest_state.expire_attributes(dest_dict, [self.key])
class Comparator(PropComparator):
@util.memoized_instancemethod
@@ -176,20 +177,20 @@ log.class_logger(ColumnProperty)
class RelationshipProperty(StrategizedProperty):
"""Describes an object property that holds a single item or list
of items that correspond to a related database table.
Public constructor is the :func:`.orm.relationship` function.
Of note here is the :class:`.RelationshipProperty.Comparator`
class, which implements comparison operations for scalar-
and collection-referencing mapped attributes.
"""
strategy_wildcard_key = 'relationship:*'
def __init__(self, argument,
secondary=None, primaryjoin=None,
secondaryjoin=None,
secondaryjoin=None,
foreign_keys=None,
uselist=None,
order_by=False,
@@ -207,7 +208,7 @@ class RelationshipProperty(StrategizedProperty):
active_history=False,
cascade_backrefs=True,
load_on_pending=False,
strategy_class=None, _local_remote_pairs=None,
strategy_class=None, _local_remote_pairs=None,
query_class=None):
self.uselist = uselist
@@ -256,7 +257,7 @@ class RelationshipProperty(StrategizedProperty):
self.cascade = CascadeOptions("save-update, merge")
if self.passive_deletes == 'all' and \
("delete" in self.cascade or
("delete" in self.cascade or
"delete-orphan" in self.cascade):
raise sa_exc.ArgumentError(
"Can't set passive_deletes='all' in conjunction "
@@ -278,9 +279,9 @@ class RelationshipProperty(StrategizedProperty):
def instrument_class(self, mapper):
attributes.register_descriptor(
mapper.class_,
self.key,
comparator=self.comparator_factory(self, mapper),
mapper.class_,
self.key,
comparator=self.comparator_factory(self, mapper),
parententity=mapper,
doc=self.doc,
)
@@ -292,7 +293,7 @@ class RelationshipProperty(StrategizedProperty):
def __init__(self, prop, mapper, of_type=None, adapter=None):
"""Construction of :class:`.RelationshipProperty.Comparator`
is internal to the ORM's attribute mechanics.
"""
self.prop = prop
self.mapper = mapper
@@ -322,29 +323,23 @@ class RelationshipProperty(StrategizedProperty):
else:
return elem
def operate(self, op, *other, **kwargs):
return op(self, *other, **kwargs)
def reverse_operate(self, op, other, **kwargs):
return op(self, *other, **kwargs)
def of_type(self, cls):
"""Produce a construct that represents a particular 'subtype' of
attribute for the parent class.
Currently this is usable in conjunction with :meth:`.Query.join`
and :meth:`.Query.outerjoin`.
"""
return RelationshipProperty.Comparator(
self.property,
self.mapper,
self.property,
self.mapper,
cls, adapter=self.adapter)
def in_(self, other):
"""Produce an IN clause - this is not implemented
"""Produce an IN clause - this is not implemented
for :func:`~.orm.relationship`-based attributes at this time.
"""
raise NotImplementedError('in_() not yet supported for '
'relationships. For a simple many-to-one, use '
@@ -361,20 +356,20 @@ class RelationshipProperty(StrategizedProperty):
this will typically produce a
clause such as::
mytable.related_id == <some id>
Where ``<some id>`` is the primary key of the given
Where ``<some id>`` is the primary key of the given
object.
The ``==`` operator provides partial functionality for non-
many-to-one comparisons:
* Comparisons against collections are not supported.
Use :meth:`~.RelationshipProperty.Comparator.contains`.
* Compared to a scalar one-to-many, will produce a
* Compared to a scalar one-to-many, will produce a
clause that compares the target columns in the parent to
the given target.
the given target.
* Compared to a scalar many-to-many, an alias
of the association table will be rendered as
well, forming a natural join that is part of the
@@ -448,9 +443,9 @@ class RelationshipProperty(StrategizedProperty):
# limit this adapter to annotated only?
criterion = target_adapter.traverse(criterion)
# only have the "joined left side" of what we
# only have the "joined left side" of what we
# return be subject to Query adaption. The right
# side of it is used for an exists() subquery and
# side of it is used for an exists() subquery and
# should not correlate or otherwise reach out
# to anything in the enclosing query.
if criterion is not None:
@@ -464,42 +459,42 @@ class RelationshipProperty(StrategizedProperty):
def any(self, criterion=None, **kwargs):
"""Produce an expression that tests a collection against
particular criterion, using EXISTS.
An expression like::
session.query(MyClass).filter(
MyClass.somereference.any(SomeRelated.x==2)
)
Will produce a query like::
SELECT * FROM my_table WHERE
EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
AND related.x=2)
Because :meth:`~.RelationshipProperty.Comparator.any` uses
a correlated subquery, its performance is not nearly as
good when compared against large target tables as that of
using a join.
:meth:`~.RelationshipProperty.Comparator.any` is particularly
useful for testing for empty collections::
session.query(MyClass).filter(
~MyClass.somereference.any()
)
will produce::
SELECT * FROM my_table WHERE
NOT EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id)
:meth:`~.RelationshipProperty.Comparator.any` is only
valid for collections, i.e. a :func:`.relationship`
that has ``uselist=True``. For scalar references,
use :meth:`~.RelationshipProperty.Comparator.has`.
"""
if not self.property.uselist:
raise sa_exc.InvalidRequestError(
@@ -514,14 +509,14 @@ class RelationshipProperty(StrategizedProperty):
particular criterion, using EXISTS.
An expression like::
session.query(MyClass).filter(
MyClass.somereference.has(SomeRelated.x==2)
)
Will produce a query like::
SELECT * FROM my_table WHERE
EXISTS (SELECT 1 FROM related WHERE related.id==my_table.related_id
AND related.x=2)
@@ -530,12 +525,12 @@ class RelationshipProperty(StrategizedProperty):
a correlated subquery, its performance is not nearly as
good when compared against large target tables as that of
using a join.
:meth:`~.RelationshipProperty.Comparator.has` is only
valid for scalar references, i.e. a :func:`.relationship`
that has ``uselist=False``. For collection references,
use :meth:`~.RelationshipProperty.Comparator.any`.
"""
if self.property.uselist:
raise sa_exc.InvalidRequestError(
@@ -544,46 +539,46 @@ class RelationshipProperty(StrategizedProperty):
return self._criterion_exists(criterion, **kwargs)
def contains(self, other, **kwargs):
"""Return a simple expression that tests a collection for
"""Return a simple expression that tests a collection for
containment of a particular item.
:meth:`~.RelationshipProperty.Comparator.contains` is
only valid for a collection, i.e. a
:func:`~.orm.relationship` that implements
one-to-many or many-to-many with ``uselist=True``.
When used in a simple one-to-many context, an
When used in a simple one-to-many context, an
expression like::
MyClass.contains(other)
Produces a clause like::
mytable.id == <some id>
Where ``<some id>`` is the value of the foreign key
attribute on ``other`` which refers to the primary
key of its parent object. From this it follows that
:meth:`~.RelationshipProperty.Comparator.contains` is
very useful when used with simple one-to-many
operations.
For many-to-many operations, the behavior of
:meth:`~.RelationshipProperty.Comparator.contains`
has more caveats. The association table will be
rendered in the statement, producing an "implicit"
join, that is, includes multiple tables in the FROM
clause which are equated in the WHERE clause::
query(MyClass).filter(MyClass.contains(other))
Produces a query like::
SELECT * FROM my_table, my_association_table AS
my_association_table_1 WHERE
my_table.id = my_association_table_1.parent_id
AND my_association_table_1.child_id = <some id>
Where ``<some id>`` would be the primary key of
``other``. From the above, it is clear that
:meth:`~.RelationshipProperty.Comparator.contains`
@@ -597,7 +592,7 @@ class RelationshipProperty(StrategizedProperty):
a less-performant alternative using EXISTS, or refer
to :meth:`.Query.outerjoin` as well as :ref:`ormtutorial_joins`
for more details on constructing outer joins.
"""
if not self.property.uselist:
raise sa_exc.InvalidRequestError(
@@ -635,7 +630,7 @@ class RelationshipProperty(StrategizedProperty):
adapt(x) == None)
for (x, y) in self.property.local_remote_pairs])
criterion = sql.and_(*[x==y for (x, y) in
criterion = sql.and_(*[x==y for (x, y) in
zip(
self.property.mapper.primary_key,
self.property.\
@@ -648,26 +643,26 @@ class RelationshipProperty(StrategizedProperty):
"""Implement the ``!=`` operator.
In a many-to-one context, such as::
MyClass.some_prop != <some object>
This will typically produce a clause such as::
mytable.related_id != <some id>
Where ``<some id>`` is the primary key of the
given object.
The ``!=`` operator provides partial functionality for non-
many-to-one comparisons:
* Comparisons against collections are not supported.
Use
:meth:`~.RelationshipProperty.Comparator.contains`
in conjunction with :func:`~.expression.not_`.
* Compared to a scalar one-to-many, will produce a
* Compared to a scalar one-to-many, will produce a
clause that compares the target columns in the parent to
the given target.
the given target.
* Compared to a scalar many-to-many, an alias
of the association table will be rendered as
well, forming a natural join that is part of the
@@ -681,7 +676,7 @@ class RelationshipProperty(StrategizedProperty):
membership tests.
* Comparisons against ``None`` given in a one-to-many
or many-to-many context produce an EXISTS clause.
"""
if isinstance(other, (NoneType, expression._Null)):
if self.property.direction == MANYTOONE:
@@ -702,26 +697,26 @@ class RelationshipProperty(StrategizedProperty):
configure_mappers()
return self.prop
def compare(self, op, value,
value_is_parent=False,
def compare(self, op, value,
value_is_parent=False,
alias_secondary=True):
if op == operators.eq:
if value is None:
if self.uselist:
return ~sql.exists([1], self.primaryjoin)
else:
return self._optimized_compare(None,
return self._optimized_compare(None,
value_is_parent=value_is_parent,
alias_secondary=alias_secondary)
else:
return self._optimized_compare(value,
return self._optimized_compare(value,
value_is_parent=value_is_parent,
alias_secondary=alias_secondary)
else:
return op(self.comparator, value)
def _optimized_compare(self, value, value_is_parent=False,
adapt_source=None,
def _optimized_compare(self, value, value_is_parent=False,
adapt_source=None,
alias_secondary=True):
if value is not None:
value = attributes.instance_state(value)
@@ -733,12 +728,12 @@ class RelationshipProperty(StrategizedProperty):
def __str__(self):
return str(self.parent.class_.__name__) + "." + self.key
def merge(self,
def merge(self,
session,
source_state,
source_dict,
dest_state,
dest_dict,
dest_dict,
load, _recursive):
if load:
@@ -848,8 +843,8 @@ class RelationshipProperty(StrategizedProperty):
raise AssertionError("Attribute '%s' on class '%s' "
"doesn't handle objects "
"of type '%s'" % (
self.key,
self.parent.class_,
self.key,
self.parent.class_,
c.__class__
))
@@ -877,11 +872,11 @@ class RelationshipProperty(StrategizedProperty):
@util.memoized_property
def mapper(self):
"""Return the targeted :class:`.Mapper` for this
"""Return the targeted :class:`.Mapper` for this
:class:`.RelationshipProperty`.
This is a lazy-initializing static attribute.
"""
if isinstance(self.argument, type):
mapper_ = mapper.class_mapper(self.argument,
@@ -905,8 +900,8 @@ class RelationshipProperty(StrategizedProperty):
@util.memoized_property
@util.deprecated("0.7", "Use .target")
def table(self):
"""Return the selectable linked to this
:class:`.RelationshipProperty` object's target
"""Return the selectable linked to this
:class:`.RelationshipProperty` object's target
:class:`.Mapper`."""
return self.target
@@ -922,7 +917,7 @@ class RelationshipProperty(StrategizedProperty):
super(RelationshipProperty, self).do_init()
def _check_conflicts(self):
"""Test that this relationship is legal, warn about
"""Test that this relationship is legal, warn about
inheritance conflicts."""
if not self.is_primary() \
@@ -949,11 +944,11 @@ class RelationshipProperty(StrategizedProperty):
% (self.key, self.parent, inheriting))
def _process_dependent_arguments(self):
"""Convert incoming configuration arguments to their
"""Convert incoming configuration arguments to their
proper form.
Callables are resolved, ORM annotations removed.
"""
# accept callables for other attributes which may require
# deferred initialization. This technique is used
@@ -983,20 +978,20 @@ class RelationshipProperty(StrategizedProperty):
# remote_side are all columns, not strings.
if self.order_by is not False and self.order_by is not None:
self.order_by = [
expression._only_column_elements(x, "order_by")
expression._only_column_elements(x, "order_by")
for x in
util.to_list(self.order_by)]
self._user_defined_foreign_keys = \
util.column_set(
expression._only_column_elements(x, "foreign_keys")
expression._only_column_elements(x, "foreign_keys")
for x in util.to_column_set(
self._user_defined_foreign_keys
))
self.remote_side = \
util.column_set(
expression._only_column_elements(x, "remote_side")
expression._only_column_elements(x, "remote_side")
for x in
util.to_column_set(self.remote_side))
@@ -1010,10 +1005,10 @@ class RelationshipProperty(StrategizedProperty):
def _determine_joins(self):
"""Determine the 'primaryjoin' and 'secondaryjoin' attributes,
if not passed to the constructor already.
This is based on analysis of the foreign key relationships
between the parent and target mapped selectables.
"""
if self.secondaryjoin is not None and self.secondary is None:
raise sa_exc.ArgumentError("Property '" + self.key
@@ -1029,7 +1024,7 @@ class RelationshipProperty(StrategizedProperty):
# for more specificity, then if not found will try the more
# general mapped table, which in the case of inheritance is
# a join.
return join_condition(mapper.mapped_table, table,
return join_condition(mapper.mapped_table, table,
a_subset=mapper.local_table)
try:
@@ -1053,9 +1048,9 @@ class RelationshipProperty(StrategizedProperty):
% self)
def _columns_are_mapped(self, *cols):
"""Return True if all columns in the given collection are
"""Return True if all columns in the given collection are
mapped by the tables referenced by this :class:`.Relationship`.
"""
for c in cols:
if self.secondary is not None \
@@ -1070,11 +1065,11 @@ class RelationshipProperty(StrategizedProperty):
"""Determine a list of "source"/"destination" column pairs
based on the given join condition, as well as the
foreign keys argument.
"source" would be a column referenced by a foreign key,
and "destination" would be the column who has a foreign key
reference to "source".
"""
fks = self._user_defined_foreign_keys
@@ -1083,7 +1078,7 @@ class RelationshipProperty(StrategizedProperty):
consider_as_foreign_keys=fks,
any_operator=self.viewonly)
# couldn't find any fks, but we have
# couldn't find any fks, but we have
# "secondary" - assume the "secondary" columns
# are the fks
if not eq_pairs and \
@@ -1108,19 +1103,19 @@ class RelationshipProperty(StrategizedProperty):
# Filter out just to columns that are mapped.
# If viewonly, allow pairs where the FK col
# was part of "foreign keys" - the column it references
# may be in an un-mapped table - see
# may be in an un-mapped table - see
# test.orm.test_relationships.ViewOnlyComplexJoin.test_basic
# for an example of this.
eq_pairs = [(l, r) for (l, r) in eq_pairs
if self._columns_are_mapped(l, r)
or self.viewonly and
or self.viewonly and
r in fks]
if eq_pairs:
return eq_pairs
# from here below is just determining the best error message
# to report. Check for a join condition using any operator
# to report. Check for a join condition using any operator
# (not just ==), perhaps they need to turn on "viewonly=True".
if not self.viewonly and criterion_as_pairs(join_condition,
consider_as_foreign_keys=self._user_defined_foreign_keys,
@@ -1130,8 +1125,8 @@ class RelationshipProperty(StrategizedProperty):
"foreign-key-equated, locally mapped column "\
"pairs for %s "\
"condition '%s' on relationship %s." % (
primary and 'primaryjoin' or 'secondaryjoin',
join_condition,
primary and 'primaryjoin' or 'secondaryjoin',
join_condition,
self
)
@@ -1160,10 +1155,10 @@ class RelationshipProperty(StrategizedProperty):
"have adequate ForeignKey and/or "
"ForeignKeyConstraint objects established "
"(in which case 'foreign_keys' is usually "
"unnecessary)?"
"unnecessary)?"
% (
primary and 'primaryjoin' or 'secondaryjoin',
join_condition,
join_condition,
self,
primary and 'mapped' or 'secondary'
))
@@ -1174,18 +1169,18 @@ class RelationshipProperty(StrategizedProperty):
"referencing Column objects have a "
"ForeignKey present, or are otherwise part "
"of a ForeignKeyConstraint on their parent "
"Table, or specify the foreign_keys parameter "
"Table, or specify the foreign_keys parameter "
"to this relationship."
% (
primary and 'primaryjoin' or 'secondaryjoin',
join_condition,
primary and 'primaryjoin' or 'secondaryjoin',
join_condition,
self
))
def _determine_synchronize_pairs(self):
"""Resolve 'primary'/foreign' column pairs from the primaryjoin
and secondaryjoin arguments.
"""
if self.local_remote_pairs:
if not self._user_defined_foreign_keys:
@@ -1200,7 +1195,7 @@ class RelationshipProperty(StrategizedProperty):
self.synchronize_pairs.append((r, l))
else:
self.synchronize_pairs = self._sync_pairs_from_join(
self.primaryjoin,
self.primaryjoin,
True)
self._calculated_foreign_keys = util.column_set(
@@ -1209,7 +1204,7 @@ class RelationshipProperty(StrategizedProperty):
if self.secondaryjoin is not None:
self.secondary_synchronize_pairs = self._sync_pairs_from_join(
self.secondaryjoin,
self.secondaryjoin,
False)
self._calculated_foreign_keys.update(
r for (l, r) in
@@ -1218,12 +1213,12 @@ class RelationshipProperty(StrategizedProperty):
self.secondary_synchronize_pairs = None
def _determine_direction(self):
"""Determine if this relationship is one to many, many to one,
"""Determine if this relationship is one to many, many to one,
many to many.
This is derived from the primaryjoin, presence of "secondary",
and in the case of self-referential the "remote side".
"""
if self.secondaryjoin is not None:
self.direction = MANYTOMANY
@@ -1300,19 +1295,19 @@ class RelationshipProperty(StrategizedProperty):
% self)
def _determine_local_remote_pairs(self):
"""Determine pairs of columns representing "local" to
"""Determine pairs of columns representing "local" to
"remote", where "local" columns are on the parent mapper,
"remote" are on the target mapper.
These pairs are used on the load side only to generate
lazy loading clauses.
"""
if not self.local_remote_pairs and not self.remote_side:
# the most common, trivial case. Derive
# the most common, trivial case. Derive
# local/remote pairs from the synchronize pairs.
eq_pairs = util.unique_list(
self.synchronize_pairs +
self.synchronize_pairs +
(self.secondary_synchronize_pairs or []))
if self.direction is MANYTOONE:
self.local_remote_pairs = [(r, l) for l, r in eq_pairs]
@@ -1474,8 +1469,8 @@ class RelationshipProperty(StrategizedProperty):
if not self.viewonly and self._dependency_processor:
self._dependency_processor.per_property_preprocessors(uow)
def _create_joins(self, source_polymorphic=False,
source_selectable=None, dest_polymorphic=False,
def _create_joins(self, source_polymorphic=False,
source_selectable=None, dest_polymorphic=False,
dest_selectable=None, of_type=None):
if source_selectable is None:
if source_polymorphic and self.parent.with_polymorphic:
@@ -1497,10 +1492,10 @@ class RelationshipProperty(StrategizedProperty):
# place a barrier on the destination such that
# replacement traversals won't ever dig into it.
# its internal structure remains fixed
# its internal structure remains fixed
# regardless of context.
dest_selectable = _shallow_annotate(
dest_selectable,
dest_selectable,
{'no_replacement_traverse':True})
aliased = aliased or (source_selectable is not None)
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -1,5 +1,5 @@
# orm/scoping.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -16,10 +16,10 @@ __all__ = ['ScopedSession']
class ScopedSession(object):
"""Provides thread-local management of Sessions.
Typical invocation is via the :func:`.scoped_session`
function::
Session = scoped_session(sessionmaker())
The internal registry is accessible,
@@ -71,7 +71,7 @@ class ScopedSession(object):
self.session_factory.configure(**kwargs)
def query_property(self, query_cls=None):
"""return a class property which produces a `Query` object
"""return a class property which produces a `Query` object
against the class when called.
e.g.::
@@ -122,7 +122,7 @@ def makeprop(name):
def get(self):
return getattr(self.registry(), name)
return property(get, set)
for prop in ('bind', 'dirty', 'deleted', 'new', 'identity_map',
for prop in ('bind', 'dirty', 'deleted', 'new', 'identity_map',
'is_active', 'autoflush', 'no_autoflush'):
setattr(ScopedSession, prop, makeprop(prop))
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
# orm/shard.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
+15 -15
View File
@@ -1,5 +1,5 @@
# orm/state.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -124,7 +124,7 @@ class InstanceState(object):
return []
elif hasattr(impl, 'get_collection'):
return [
(attributes.instance_state(o), o) for o in
(attributes.instance_state(o), o) for o in
impl.get_collection(self, dict_, x, passive=passive)
]
else:
@@ -134,10 +134,10 @@ class InstanceState(object):
d = {'instance':self.obj()}
d.update(
(k, self.__dict__[k]) for k in (
'committed_state', 'pending', 'modified', 'expired',
'committed_state', 'pending', 'modified', 'expired',
'callables', 'key', 'parents', 'load_options', 'mutable_dict',
'class_',
) if k in self.__dict__
) if k in self.__dict__
)
if self.load_path:
d['load_path'] = interfaces.serialize_path(self.load_path)
@@ -181,26 +181,26 @@ class InstanceState(object):
self.__dict__.update([
(k, state[k]) for k in (
'key', 'load_options', 'mutable_dict'
) if k in state
) if k in state
])
if 'load_path' in state:
self.load_path = interfaces.deserialize_path(state['load_path'])
# setup _sa_instance_state ahead of time so that
# 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,
"""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):
"""Remove the given attribute and any
"""Remove the given attribute and any
callables associated with it."""
dict_.pop(key, None)
@@ -284,7 +284,7 @@ class InstanceState(object):
self.manager.deferred_scalar_loader(self, toload)
# if the loader failed, or this
# if the loader failed, or this
# instance state didn't have an identity,
# the attributes still might be in the callables
# dict. ensure they are removed.
@@ -321,7 +321,7 @@ class InstanceState(object):
@property
def expired_attributes(self):
"""Return the set of keys which are 'expired' to be loaded by
the manager's deferred scalar loader, assuming no pending
the manager's deferred scalar loader, assuming no pending
changes.
see also the ``unmodified`` collection which is intersected
@@ -348,7 +348,7 @@ class InstanceState(object):
self.committed_state[attr.key] = previous
# the "or not self.modified" is defensive at
# 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
@@ -363,9 +363,9 @@ class InstanceState(object):
raise orm_exc.ObjectDereferencedError(
"Can't emit change event for attribute '%s' - "
"parent object of type %s has been garbage "
"collected."
"collected."
% (
self.manager[attr.key],
self.manager[attr.key],
orm_util.state_class_str(self)
))
self.modified = True
@@ -433,7 +433,7 @@ class InstanceState(object):
self._strong_obj = None
class MutableAttrInstanceState(InstanceState):
"""InstanceState implementation for objects that reference 'mutable'
"""InstanceState implementation for objects that reference 'mutable'
attributes.
Has a more involved "cleanup" handler that checks mutable attributes
@@ -491,7 +491,7 @@ class MutableAttrInstanceState(InstanceState):
This would be called in the extremely rare
race condition that the weakref returned None but
the cleanup handler had not yet established the
the cleanup handler had not yet established the
__resurrect callable as its replacement.
"""
+134 -126
View File
@@ -1,10 +1,10 @@
# orm/strategies.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
"""sqlalchemy.orm.interfaces.LoaderStrategy
"""sqlalchemy.orm.interfaces.LoaderStrategy
implementations, and related MapperOptions."""
from sqlalchemy import exc as sa_exc
@@ -23,15 +23,15 @@ from sqlalchemy.orm.query import Query
import itertools
def _register_attribute(strategy, mapper, useobject,
compare_function=None,
compare_function=None,
typecallable=None,
copy_function=None,
mutable_scalars=False,
copy_function=None,
mutable_scalars=False,
uselist=False,
callable_=None,
proxy_property=None,
callable_=None,
proxy_property=None,
active_history=False,
impl_class=None,
impl_class=None,
**kw
):
@@ -45,11 +45,11 @@ def _register_attribute(strategy, mapper, useobject,
listen_hooks.append(single_parent_validator)
if prop.key in prop.parent.validators:
fn, include_removes = prop.parent.validators[prop.key]
listen_hooks.append(
lambda desc, prop: mapperutil._validator_events(desc,
prop.key,
prop.parent.validators[prop.key])
)
lambda desc, prop: mapperutil._validator_events(desc,
prop.key, fn, include_removes)
)
if useobject:
listen_hooks.append(unitofwork.track_cascade_events)
@@ -59,8 +59,8 @@ def _register_attribute(strategy, mapper, useobject,
backref = kw.pop('backref', None)
if backref:
listen_hooks.append(
lambda desc, prop: attributes.backref_listeners(desc,
backref,
lambda desc, prop: attributes.backref_listeners(desc,
backref,
uselist)
)
@@ -68,18 +68,18 @@ def _register_attribute(strategy, mapper, useobject,
if prop is m._props.get(prop.key):
desc = attributes.register_attribute_impl(
m.class_,
prop.key,
m.class_,
prop.key,
parent_token=prop,
mutable_scalars=mutable_scalars,
uselist=uselist,
copy_function=copy_function,
compare_function=compare_function,
uselist=uselist,
copy_function=copy_function,
compare_function=compare_function,
useobject=useobject,
extension=attribute_ext,
trackparent=useobject and (prop.single_parent or prop.direction is interfaces.ONETOMANY),
extension=attribute_ext,
trackparent=useobject and (prop.single_parent or prop.direction is interfaces.ONETOMANY),
typecallable=typecallable,
callable_=callable_,
callable_=callable_,
active_history=active_history,
impl_class=impl_class,
doc=prop.doc,
@@ -99,7 +99,7 @@ class UninstrumentedColumnLoader(LoaderStrategy):
def init(self):
self.columns = self.parent_property.columns
def setup_query(self, context, entity, path, reduced_path, adapter,
def setup_query(self, context, entity, path, reduced_path, adapter,
column_collection=None, **kwargs):
for c in self.columns:
if adapter:
@@ -116,7 +116,7 @@ class ColumnLoader(LoaderStrategy):
self.columns = self.parent_property.columns
self.is_composite = hasattr(self.parent_property, 'composite_class')
def setup_query(self, context, entity, path, reduced_path,
def setup_query(self, context, entity, path, reduced_path,
adapter, column_collection, **kwargs):
for c in self.columns:
if adapter:
@@ -137,7 +137,7 @@ class ColumnLoader(LoaderStrategy):
active_history = active_history
)
def create_row_processor(self, context, path, reduced_path,
def create_row_processor(self, context, path, reduced_path,
mapper, row, adapter):
key = self.key
# look through list of columns represented here
@@ -199,10 +199,10 @@ class DeferredColumnLoader(LoaderStrategy):
expire_missing=False
)
def setup_query(self, context, entity, path, reduced_path, adapter,
def setup_query(self, context, entity, path, reduced_path, adapter,
only_load_props=None, **kwargs):
if (
self.group is not None and
self.group is not None and
context.attributes.get(('undefer', self.group), False)
) or (only_load_props and self.key in only_load_props):
self.parent_property._get_strategy(ColumnLoader).\
@@ -220,10 +220,10 @@ class DeferredColumnLoader(LoaderStrategy):
if self.group:
toload = [
p.key for p in
localparent.iterate_properties
if isinstance(p, StrategizedProperty) and
isinstance(p.strategy, DeferredColumnLoader) and
p.key for p in
localparent.iterate_properties
if isinstance(p, StrategizedProperty) and
isinstance(p.strategy, DeferredColumnLoader) and
p.group==self.group
]
else:
@@ -236,12 +236,12 @@ class DeferredColumnLoader(LoaderStrategy):
if session is None:
raise orm_exc.DetachedInstanceError(
"Parent instance %s is not bound to a Session; "
"deferred load operation of attribute '%s' cannot proceed" %
"deferred load operation of attribute '%s' cannot proceed" %
(mapperutil.state_str(state), self.key)
)
query = session.query(localparent)
if query._load_on_ident(state.key,
if query._load_on_ident(state.key,
only_load_props=group, refresh_state=state) is None:
raise orm_exc.ObjectDeletedError(state)
@@ -297,14 +297,14 @@ class AbstractRelationshipLoader(LoaderStrategy):
class NoLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
with "lazy=None".
"""
def init_class_attribute(self, mapper):
self.is_class_level = True
_register_attribute(self, mapper,
useobject=True,
useobject=True,
uselist=self.parent_property.uselist,
typecallable = self.parent_property.collection_class,
)
@@ -319,7 +319,7 @@ log.class_logger(NoLoader)
class LazyLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
with "lazy=True", that is loads when first accessed.
"""
def init(self):
@@ -331,7 +331,7 @@ class LazyLoader(AbstractRelationshipLoader):
self._rev_lazywhere, \
self._rev_bind_to_col, \
self._rev_equated_columns = self._create_lazy_clause(
self.parent_property,
self.parent_property,
reverse_direction=True)
self.logger.info("%s lazy loading clause %s", self, self._lazywhere)
@@ -341,8 +341,8 @@ class LazyLoader(AbstractRelationshipLoader):
#from sqlalchemy.orm import query
self.use_get = not self.uselist and \
self.mapper._get_clause[0].compare(
self._lazywhere,
use_proxies=True,
self._lazywhere,
use_proxies=True,
equivalents=self.mapper._equivalent_columns
)
@@ -358,13 +358,13 @@ class LazyLoader(AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.is_class_level = True
# MANYTOONE currently only needs the
# MANYTOONE currently only needs the
# "old" value for delete-orphan
# cascades. the required _SingleParentValidator
# cascades. the required _SingleParentValidator
# will enable active_history
# in that case. otherwise we don't need the
# in that case. otherwise we don't need the
# "old" value during backref operations.
_register_attribute(self,
_register_attribute(self,
mapper,
useobject=True,
callable_=self._load_for_state,
@@ -378,12 +378,12 @@ class LazyLoader(AbstractRelationshipLoader):
not self.use_get,
)
def lazy_clause(self, state, reverse_direction=False,
alias_secondary=False,
def lazy_clause(self, state, reverse_direction=False,
alias_secondary=False,
adapt_source=None):
if state is None:
return self._lazy_none_clause(
reverse_direction,
reverse_direction,
adapt_source=adapt_source)
if not reverse_direction:
@@ -414,14 +414,14 @@ class LazyLoader(AbstractRelationshipLoader):
if bindparam._identifying_key in bind_to_col:
bindparam.callable = \
lambda: mapper._get_committed_state_attr_by_column(
state, dict_,
state, dict_,
bind_to_col[bindparam._identifying_key])
else:
def visit_bindparam(bindparam):
if bindparam._identifying_key in bind_to_col:
bindparam.callable = \
lambda: mapper._get_state_attr_by_column(
state, dict_,
state, dict_,
bind_to_col[bindparam._identifying_key])
@@ -465,10 +465,10 @@ class LazyLoader(AbstractRelationshipLoader):
if (
(passive is attributes.PASSIVE_NO_FETCH or \
passive is attributes.PASSIVE_NO_FETCH_RELATED) and
passive is attributes.PASSIVE_NO_FETCH_RELATED) and
not self.use_get
) or (
passive is attributes.PASSIVE_ONLY_PERSISTENT and
passive is attributes.PASSIVE_ONLY_PERSISTENT and
pending
):
return attributes.PASSIVE_NO_RESULT
@@ -477,11 +477,11 @@ class LazyLoader(AbstractRelationshipLoader):
if not session:
raise orm_exc.DetachedInstanceError(
"Parent instance %s is not bound to a Session; "
"lazy load operation of attribute '%s' cannot proceed" %
"lazy load operation of attribute '%s' cannot proceed" %
(mapperutil.state_str(state), self.key)
)
# if we have a simple primary key load, check the
# if we have a simple primary key load, check the
# identity map without generating a Query at all
if self.use_get:
ident = self._get_ident_for_use_get(
@@ -555,7 +555,7 @@ class LazyLoader(AbstractRelationshipLoader):
q = q.order_by(*util.to_list(self.parent_property.order_by))
for rev in self.parent_property._reverse_property:
# reverse props that are MANYTOONE are loading *this*
# reverse props that are MANYTOONE are loading *this*
# object from get(), so don't need to eager out to those.
if rev.direction is interfaces.MANYTOONE and \
rev._use_get and \
@@ -580,7 +580,7 @@ class LazyLoader(AbstractRelationshipLoader):
if l > 1:
util.warn(
"Multiple rows returned with "
"uselist=False for lazily-loaded attribute '%s' "
"uselist=False for lazily-loaded attribute '%s' "
% self.parent_property)
return result[0]
@@ -588,30 +588,30 @@ class LazyLoader(AbstractRelationshipLoader):
return None
def create_row_processor(self, context, path, reduced_path,
def create_row_processor(self, context, path, reduced_path,
mapper, row, adapter):
key = self.key
if not self.is_class_level:
def set_lazy_callable(state, dict_, row):
# we are not the primary manager for this attribute
# we are not the primary manager for this attribute
# on this class - set up a
# per-instance lazyloader, which will override the
# per-instance lazyloader, which will override the
# class-level behavior.
# this currently only happens when using a
# this currently only happens when using a
# "lazyload" option on a "no load"
# attribute - "eager" attributes always have a
# attribute - "eager" attributes always have a
# class-level lazyloader installed.
state.set_callable(dict_, key, LoadLazyAttribute(state, key))
return set_lazy_callable, None, None
else:
def reset_for_lazy_callable(state, dict_, row):
# we are the primary manager for this attribute on
# we are the primary manager for this attribute on
# this class - reset its
# per-instance attribute state, so that the class-level
# per-instance attribute state, so that the class-level
# lazy loader is
# executed when next referenced on this instance.
# this is needed in
# populate_existing() types of scenarios to reset
# populate_existing() types of scenarios to reset
# any existing state.
state.reset(dict_, key)
@@ -648,7 +648,7 @@ class LazyLoader(AbstractRelationshipLoader):
if prop.secondaryjoin is None or not reverse_direction:
lazywhere = visitors.replacement_traverse(
lazywhere, {}, col_to_bind)
lazywhere, {}, col_to_bind)
if prop.secondaryjoin is not None:
secondaryjoin = prop.secondaryjoin
@@ -685,12 +685,12 @@ class ImmediateLoader(AbstractRelationshipLoader):
_get_strategy(LazyLoader).\
init_class_attribute(mapper)
def setup_query(self, context, entity,
def setup_query(self, context, entity,
path, reduced_path, adapter, column_collection=None,
parentmapper=None, **kwargs):
pass
def create_row_processor(self, context, path, reduced_path,
def create_row_processor(self, context, path, reduced_path,
mapper, row, adapter):
def load_immediate(state, dict_, row):
state.get_impl(self.key).get(state, dict_)
@@ -707,8 +707,8 @@ class SubqueryLoader(AbstractRelationshipLoader):
_get_strategy(LazyLoader).\
init_class_attribute(mapper)
def setup_query(self, context, entity,
path, reduced_path, adapter,
def setup_query(self, context, entity,
path, reduced_path, adapter,
column_collection=None,
parentmapper=None, **kwargs):
@@ -738,7 +738,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
self._get_leftmost(subq_path)
orig_query = context.attributes.get(
("orig_query", SubqueryLoader),
("orig_query", SubqueryLoader),
context.query)
# generate a new Query from the original, then
@@ -748,7 +748,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
leftmost_attr, subq_path
)
# generate another Query that will join the
# generate another Query that will join the
# left alias to the target relationships.
# basically doing a longhand
# "from_self()". (from_self() itself not quite industrial
@@ -770,7 +770,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
q = self._setup_options(q, subq_path, orig_query)
q = self._setup_outermost_orderby(q)
# add new query to attributes to be picked up
# add new query to attributes to be picked up
# by create_row_processor
context.attributes[('subquery', reduced_path)] = q
@@ -801,7 +801,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
# to look only for significant columns
q = orig_query._clone()
# TODO: why does polymporphic etc. require hardcoding
# TODO: why does polymporphic etc. require hardcoding
# into _adapt_col_list ? Does query.add_columns(...) work
# with polymorphic loading ?
q._set_entities(q._adapt_col_list(leftmost_attr))
@@ -823,7 +823,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
def _prep_for_joins(self, left_alias, subq_path):
# figure out what's being joined. a.k.a. the fun part
to_join = [
(subq_path[i], subq_path[i+1])
(subq_path[i], subq_path[i+1])
for i in xrange(0, len(subq_path), 2)
]
@@ -836,13 +836,13 @@ class SubqueryLoader(AbstractRelationshipLoader):
parent_alias = left_alias
elif subq_path[-2].isa(self.parent):
# In the case of multiple levels, retrieve
# it from subq_path[-2]. This is the same as self.parent
# in the vast majority of cases, and [ticket:2014]
# it from subq_path[-2]. This is the same as self.parent
# in the vast majority of cases, and [ticket:2014]
# illustrates a case where sub_path[-2] is a subclass
# of self.parent
parent_alias = mapperutil.AliasedClass(subq_path[-2])
else:
# if of_type() were used leading to this relationship,
# if of_type() were used leading to this relationship,
# self.parent is more specific than subq_path[-2]
parent_alias = mapperutil.AliasedClass(self.parent)
@@ -860,10 +860,10 @@ class SubqueryLoader(AbstractRelationshipLoader):
for i, (mapper, key) in enumerate(to_join):
# we need to use query.join() as opposed to
# orm.join() here because of the
# rich behavior it brings when dealing with
# orm.join() here because of the
# rich behavior it brings when dealing with
# "with_polymorphic" mappers. "aliased"
# and "from_joinpoint" take care of most of
# and "from_joinpoint" take care of most of
# the chaining and aliasing for us.
first = i == 0
@@ -897,12 +897,14 @@ class SubqueryLoader(AbstractRelationshipLoader):
# these will fire relative to subq_path.
q = q._with_current_path(subq_path)
q = q._conditional_options(*orig_query._with_options)
if orig_query._populate_existing:
q._populate_existing = orig_query._populate_existing
return q
def _setup_outermost_orderby(self, q):
if self.parent_property.order_by:
# if there's an ORDER BY, alias it the same
# way joinedloader does, but we have to pull out
# way joinedloader does, but we have to pull out
# the "eagerjoin" from the query.
# this really only picks up the "secondary" table
# right now.
@@ -917,12 +919,12 @@ class SubqueryLoader(AbstractRelationshipLoader):
q = q.order_by(*eager_order_by)
return q
def create_row_processor(self, context, path, reduced_path,
def create_row_processor(self, context, path, reduced_path,
mapper, row, adapter):
if not self.parent.class_manager[self.key].impl.supports_population:
raise sa_exc.InvalidRequestError(
"'%s' does not support object "
"population - eager loading cannot be applied." %
"population - eager loading cannot be applied." %
self)
reduced_path = reduced_path + (self.key,)
@@ -934,10 +936,16 @@ class SubqueryLoader(AbstractRelationshipLoader):
q = context.attributes[('subquery', reduced_path)]
collections = dict(
(k, [v[0] for v in v])
# cache the loaded collections in the context
# so that inheriting mappers don't re-load when they
# call upon create_row_processor again
if ('collections', reduced_path) in context.attributes:
collections = context.attributes[('collections', reduced_path)]
else:
collections = context.attributes[('collections', reduced_path)] = dict(
(k, [v[0] for v in v])
for k, v in itertools.groupby(
q,
q,
lambda x:x[1:]
))
@@ -952,7 +960,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
def _create_collection_loader(self, collections, local_cols):
def load_collection_from_subq(state, dict_, row):
collection = collections.get(
tuple([row[col] for col in local_cols]),
tuple([row[col] for col in local_cols]),
()
)
state.get_impl(self.key).\
@@ -963,7 +971,7 @@ class SubqueryLoader(AbstractRelationshipLoader):
def _create_scalar_loader(self, collections, local_cols):
def load_scalar_from_subq(state, dict_, row):
collection = collections.get(
tuple([row[col] for col in local_cols]),
tuple([row[col] for col in local_cols]),
(None,)
)
if len(collection) > 1:
@@ -983,7 +991,7 @@ log.class_logger(SubqueryLoader)
class JoinedLoader(AbstractRelationshipLoader):
"""Provide loading behavior for a :class:`.RelationshipProperty`
using joined eager loading.
"""
def init(self):
super(JoinedLoader, self).init()
@@ -1014,7 +1022,7 @@ class JoinedLoader(AbstractRelationshipLoader):
)
else:
# check for join_depth or basic recursion,
# if the current path was not explicitly stated as
# if the current path was not explicitly stated as
# a desired "loaderstrategy" (i.e. via query.options())
if ("loaderstrategy", reduced_path) not in context.attributes:
if self.join_depth:
@@ -1035,16 +1043,16 @@ class JoinedLoader(AbstractRelationshipLoader):
for value in self.mapper._polymorphic_properties:
value.setup(
context,
entity,
path,
context,
entity,
path,
reduced_path,
clauses,
parentmapper=self.mapper,
clauses,
parentmapper=self.mapper,
column_collection=add_to_collection,
allow_innerjoin=allow_innerjoin)
def _get_user_defined_adapter(self, context, entity,
def _get_user_defined_adapter(self, context, entity,
reduced_path, adapter):
clauses = context.attributes[
("user_defined_eager_row_processor",
@@ -1063,12 +1071,12 @@ class JoinedLoader(AbstractRelationshipLoader):
add_to_collection = context.primary_columns
return clauses, adapter, add_to_collection
def _generate_row_adapter(self,
def _generate_row_adapter(self,
context, entity, path, reduced_path, adapter,
column_collection, parentmapper, allow_innerjoin
):
clauses = mapperutil.ORMAdapter(
mapperutil.AliasedClass(self.mapper),
mapperutil.AliasedClass(self.mapper),
equivalents=self.mapper._equivalent_columns,
adapt_required=True)
@@ -1076,7 +1084,7 @@ class JoinedLoader(AbstractRelationshipLoader):
context.multi_row_eager_loaders = True
innerjoin = allow_innerjoin and context.attributes.get(
("eager_join_type", path),
("eager_join_type", path),
self.parent_property.innerjoin)
if not innerjoin:
# if this is an outer join, all eager joins from
@@ -1084,8 +1092,8 @@ class JoinedLoader(AbstractRelationshipLoader):
allow_innerjoin = False
context.create_eager_joins.append(
(self._create_eager_join, context,
entity, path, adapter,
(self._create_eager_join, context,
entity, path, adapter,
parentmapper, clauses, innerjoin)
)
@@ -1095,8 +1103,8 @@ class JoinedLoader(AbstractRelationshipLoader):
] = clauses
return clauses, adapter, add_to_collection, allow_innerjoin
def _create_eager_join(self, context, entity,
path, adapter, parentmapper,
def _create_eager_join(self, context, entity,
path, adapter, parentmapper,
clauses, innerjoin):
if parentmapper is None:
@@ -1105,7 +1113,7 @@ class JoinedLoader(AbstractRelationshipLoader):
localparent = parentmapper
# whether or not the Query will wrap the selectable in a subquery,
# and then attach eager load joins to that (i.e., in the case of
# and then attach eager load joins to that (i.e., in the case of
# LIMIT/OFFSET etc.)
should_nest_selectable = context.multi_row_eager_loaders and \
context.query._should_nest_selectable
@@ -1120,7 +1128,7 @@ class JoinedLoader(AbstractRelationshipLoader):
if clause is not None:
# join to an existing FROM clause on the query.
# key it to its list index in the eager_joins dict.
# Query._compile_context will adapt as needed and
# Query._compile_context will adapt as needed and
# append to the FROM clause of the select().
entity_key, default_towrap = index, clause
@@ -1138,14 +1146,14 @@ class JoinedLoader(AbstractRelationshipLoader):
else:
onclause = getattr(
mapperutil.AliasedClass(
self.parent,
self.parent,
adapter.selectable
),
),
self.key, self.parent_property
)
if onclause is self.parent_property:
# TODO: this is a temporary hack to
# TODO: this is a temporary hack to
# account for polymorphic eager loads where
# the eagerload is referencing via of_type().
join_to_left = True
@@ -1154,10 +1162,10 @@ class JoinedLoader(AbstractRelationshipLoader):
context.eager_joins[entity_key] = eagerjoin = \
mapperutil.join(
towrap,
clauses.aliased_class,
onclause,
join_to_left=join_to_left,
towrap,
clauses.aliased_class,
onclause,
join_to_left=join_to_left,
isouter=not innerjoin
)
@@ -1167,11 +1175,11 @@ class JoinedLoader(AbstractRelationshipLoader):
if self.parent_property.secondary is None and \
not parentmapper:
# for parentclause that is the non-eager end of the join,
# ensure all the parent cols in the primaryjoin are actually
# ensure all the parent cols in the primaryjoin are actually
# in the
# columns clause (i.e. are not deferred), so that aliasing applied
# columns clause (i.e. are not deferred), so that aliasing applied
# by the Query propagates those columns outward.
# This has the effect
# This has the effect
# of "undefering" those columns.
for col in sql_util.find_columns(
self.parent_property.primaryjoin):
@@ -1196,7 +1204,7 @@ class JoinedLoader(AbstractRelationshipLoader):
decorator = context.attributes[
("user_defined_eager_row_processor",
reduced_path)]
# user defined eagerloads are part of the "primary"
# user defined eagerloads are part of the "primary"
# portion of the load.
# the adapters applied to the Query should be honored.
if context.adapter and decorator:
@@ -1213,7 +1221,7 @@ class JoinedLoader(AbstractRelationshipLoader):
self.mapper.identity_key_from_row(row, decorator)
return decorator
except KeyError:
# no identity key - dont return a row
# no identity key - dont return a row
# processor, will cause a degrade to lazy
return False
@@ -1221,23 +1229,23 @@ class JoinedLoader(AbstractRelationshipLoader):
if not self.parent.class_manager[self.key].impl.supports_population:
raise sa_exc.InvalidRequestError(
"'%s' does not support object "
"population - eager loading cannot be applied." %
"population - eager loading cannot be applied." %
self)
our_path = path + (self.key,)
our_reduced_path = reduced_path + (self.key,)
eager_adapter = self._create_eager_adapter(
context,
row,
context,
row,
adapter, our_path,
our_reduced_path)
if eager_adapter is not False:
key = self.key
_instance = self.mapper._instance_processor(
context,
our_path + (self.mapper,),
context,
our_path + (self.mapper,),
our_reduced_path + (self.mapper.base_mapper,),
eager_adapter)
@@ -1249,7 +1257,7 @@ class JoinedLoader(AbstractRelationshipLoader):
return self.parent_property.\
_get_strategy(LazyLoader).\
create_row_processor(
context, path,
context, path,
reduced_path,
mapper, row, adapter)
@@ -1380,7 +1388,7 @@ class LoadEagerFromAliasOption(PropertyOption):
prop = root_mapper._props[propname]
adapter = query._polymorphic_adapters.get(prop.mapper, None)
query._attributes.setdefault(
("user_defined_eager_row_processor",
("user_defined_eager_row_processor",
interfaces._reduce_path(path)), adapter)
if self.alias is not None:
@@ -1389,7 +1397,7 @@ class LoadEagerFromAliasOption(PropertyOption):
prop = root_mapper._props[propname]
self.alias = prop.target.alias(self.alias)
query._attributes[
("user_defined_eager_row_processor",
("user_defined_eager_row_processor",
interfaces._reduce_path(paths[-1]))
] = sql_util.ColumnAdapter(self.alias)
else:
@@ -1397,18 +1405,18 @@ class LoadEagerFromAliasOption(PropertyOption):
prop = root_mapper._props[propname]
adapter = query._polymorphic_adapters.get(prop.mapper, None)
query._attributes[
("user_defined_eager_row_processor",
("user_defined_eager_row_processor",
interfaces._reduce_path(paths[-1]))] = adapter
def single_parent_validator(desc, prop):
def _do_check(state, value, oldvalue, initiator):
if value is not None and initiator.key == prop.key:
hasparent = initiator.hasparent(attributes.instance_state(value))
if hasparent and oldvalue is not value:
if hasparent and oldvalue is not value:
raise sa_exc.InvalidRequestError(
"Instance %s is already associated with an instance "
"of %s via its %s attribute, and is only allowed a "
"single parent." %
"single parent." %
(mapperutil.instance_str(value), state.class_, prop)
)
return value
+8 -8
View File
@@ -1,17 +1,17 @@
# orm/sync.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
"""private module containing functions used for copying data
"""private module containing functions used for copying data
between instances based on join conditions.
"""
from sqlalchemy.orm import exc, util as mapperutil, attributes
def populate(source, source_mapper, dest, dest_mapper,
def populate(source, source_mapper, dest, dest_mapper,
synchronize_pairs, uowcommit, flag_cascaded_pks):
source_dict = source.dict
dest_dict = dest.dict
@@ -20,7 +20,7 @@ def populate(source, source_mapper, dest, dest_mapper,
try:
# inline of source_mapper._get_state_attr_by_column
prop = source_mapper._columntoproperty[l]
value = source.manager[prop.key].impl.get(source, source_dict,
value = source.manager[prop.key].impl.get(source, source_dict,
attributes.PASSIVE_OFF)
except exc.UnmappedColumnError:
_raise_col_to_prop(False, source_mapper, l, dest_mapper, r)
@@ -47,7 +47,7 @@ def clear(dest, dest_mapper, synchronize_pairs):
if r.primary_key:
raise AssertionError(
"Dependency rule tried to blank-out primary key "
"column '%s' on instance '%s'" %
"column '%s' on instance '%s'" %
(r, mapperutil.state_str(dest))
)
try:
@@ -75,7 +75,7 @@ def populate_dict(source, source_mapper, dict_, synchronize_pairs):
dict_[r.key] = value
def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
"""return true if the source object has changes from an old to a
"""return true if the source object has changes from an old to a
new value on the given synchronize pairs
"""
@@ -84,7 +84,7 @@ def source_modified(uowcommit, source, source_mapper, synchronize_pairs):
prop = source_mapper._columntoproperty[l]
except exc.UnmappedColumnError:
_raise_col_to_prop(False, source_mapper, l, None, r)
history = uowcommit.get_attribute_history(source, prop.key,
history = uowcommit.get_attribute_history(source, prop.key,
attributes.PASSIVE_NO_INITIALIZE)
return bool(history.deleted)
else:
@@ -103,6 +103,6 @@ def _raise_col_to_prop(isdest, source_mapper, source_column, dest_mapper, dest_c
"Can't execute sync rule for source column '%s'; mapper '%s' "
"does not map this column. Try using an explicit `foreign_keys`"
" collection which does not include destination column '%s' (or "
"use a viewonly=True relation)." %
"use a viewonly=True relation)." %
(source_column, source_mapper, dest_column)
)
+34 -34
View File
@@ -1,5 +1,5 @@
# orm/unitofwork.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -26,7 +26,7 @@ def track_cascade_events(descriptor, prop):
key = prop.key
def append(state, item, initiator):
# process "save_update" cascade rules for when
# process "save_update" cascade rules for when
# an instance is appended to the list of another instance
sess = session._state_session(state)
@@ -51,7 +51,7 @@ def track_cascade_events(descriptor, prop):
sess.expunge(item)
def set_(state, newvalue, oldvalue, initiator):
# process "save_update" cascade rules for when an instance
# process "save_update" cascade rules for when an instance
# is attached to another instance
if oldvalue is newvalue:
return newvalue
@@ -86,12 +86,12 @@ class UOWTransaction(object):
def __init__(self, session):
self.session = session
# dictionary used by external actors to
# dictionary used by external actors to
# store arbitrary state information.
self.attributes = {}
# dictionary of mappers to sets of
# DependencyProcessors, which are also
# dictionary of mappers to sets of
# DependencyProcessors, which are also
# set to be part of the sorted flush actions,
# which have that mapper as a parent.
self.deps = util.defaultdict(set)
@@ -106,7 +106,7 @@ class UOWTransaction(object):
# and determine if a flush action is needed
self.presort_actions = {}
# dictionary of PostSortRec objects, each
# dictionary of PostSortRec objects, each
# one issues work during the flush within
# a certain ordering.
self.postsort_actions = {}
@@ -124,7 +124,7 @@ class UOWTransaction(object):
# tracks InstanceStates which will be receiving
# a "post update" call. Keys are mappers,
# values are a set of states and a set of the
# values are a set of states and a set of the
# columns which should be included in the update.
self.post_update_states = util.defaultdict(lambda: (set(), set()))
@@ -133,7 +133,7 @@ class UOWTransaction(object):
return bool(self.states)
def is_deleted(self, state):
"""return true if the given state is marked as deleted
"""return true if the given state is marked as deleted
within this uowtransaction."""
return state in self.states and self.states[state][0]
@@ -152,7 +152,7 @@ class UOWTransaction(object):
self.states[state] = (isdelete, True)
def get_attribute_history(self, state, key,
def get_attribute_history(self, state, key,
passive=attributes.PASSIVE_NO_INITIALIZE):
"""facade to attributes.get_state_history(), including caching of results."""
@@ -164,12 +164,12 @@ class UOWTransaction(object):
if hashkey in self.attributes:
history, state_history, cached_passive = self.attributes[hashkey]
# if the cached lookup was "passive" and now
# if the cached lookup was "passive" and now
# we want non-passive, do a non-passive lookup and re-cache
if cached_passive is not attributes.PASSIVE_OFF \
and passive is attributes.PASSIVE_OFF:
impl = state.manager[key].impl
history = impl.get_history(state, state.dict,
history = impl.get_history(state, state.dict,
attributes.PASSIVE_OFF)
if history and impl.uses_objects:
state_history = history.as_state()
@@ -197,13 +197,13 @@ class UOWTransaction(object):
if key not in self.presort_actions:
self.presort_actions[key] = Preprocess(processor, fromparent)
def register_object(self, state, isdelete=False,
def register_object(self, state, isdelete=False,
listonly=False, cancel_delete=False,
operation=None, prop=None):
if not self.session._contains_state(state):
if not state.deleted and operation is not None:
util.warn("Object of type %s not in session, %s operation "
"along '%s' will not proceed" %
"along '%s' will not proceed" %
(mapperutil.state_class_str(state), operation, prop))
return False
@@ -228,8 +228,8 @@ class UOWTransaction(object):
@util.memoized_property
def _mapper_for_dep(self):
"""return a dynamic mapping of (Mapper, DependencyProcessor) to
True or False, indicating if the DependencyProcessor operates
"""return a dynamic mapping of (Mapper, DependencyProcessor) to
True or False, indicating if the DependencyProcessor operates
on objects of that Mapper.
The result is stored in the dictionary persistently once
@@ -241,7 +241,7 @@ class UOWTransaction(object):
)
def filter_states_for_dep(self, dep, states):
"""Filter the given list of InstanceStates to those relevant to the
"""Filter the given list of InstanceStates to those relevant to the
given DependencyProcessor.
"""
@@ -273,7 +273,7 @@ class UOWTransaction(object):
# see if the graph of mapper dependencies has cycles.
self.cycles = cycles = topological.find_cycles(
self.dependencies,
self.dependencies,
self.postsort_actions.values())
if cycles:
@@ -319,14 +319,14 @@ class UOWTransaction(object):
# execute
if self.cycles:
for set_ in topological.sort_as_subsets(
self.dependencies,
self.dependencies,
postsort_actions):
while set_:
n = set_.pop()
n.execute_aggregate(self, set_)
else:
for rec in topological.sort(
self.dependencies,
self.dependencies,
postsort_actions):
rec.execute(self)
@@ -470,7 +470,7 @@ class SaveUpdateAll(PostSortRec):
assert mapper is mapper.base_mapper
def execute(self, uow):
persistence.save_obj(self.mapper,
persistence.save_obj(self.mapper,
uow.states_for_mapper_hierarchy(self.mapper, False, False),
uow
)
@@ -478,8 +478,8 @@ class SaveUpdateAll(PostSortRec):
def per_state_flush_actions(self, uow):
states = list(uow.states_for_mapper_hierarchy(self.mapper, False, False))
for rec in self.mapper._per_state_flush_actions(
uow,
states,
uow,
states,
False):
yield rec
@@ -501,8 +501,8 @@ class DeleteAll(PostSortRec):
def per_state_flush_actions(self, uow):
states = list(uow.states_for_mapper_hierarchy(self.mapper, True, False))
for rec in self.mapper._per_state_flush_actions(
uow,
states,
uow,
states,
True):
yield rec
@@ -520,8 +520,8 @@ class ProcessState(PostSortRec):
cls_ = self.__class__
dependency_processor = self.dependency_processor
delete = self.delete
our_recs = [r for r in recs
if r.__class__ is cls_ and
our_recs = [r for r in recs
if r.__class__ is cls_ and
r.dependency_processor is dependency_processor and
r.delete is delete]
recs.difference_update(our_recs)
@@ -547,13 +547,13 @@ class SaveUpdateState(PostSortRec):
def execute_aggregate(self, uow, recs):
cls_ = self.__class__
mapper = self.mapper
our_recs = [r for r in recs
if r.__class__ is cls_ and
our_recs = [r for r in recs
if r.__class__ is cls_ and
r.mapper is mapper]
recs.difference_update(our_recs)
persistence.save_obj(mapper,
[self.state] +
[r.state for r in our_recs],
[self.state] +
[r.state for r in our_recs],
uow)
def __repr__(self):
@@ -570,13 +570,13 @@ class DeleteState(PostSortRec):
def execute_aggregate(self, uow, recs):
cls_ = self.__class__
mapper = self.mapper
our_recs = [r for r in recs
if r.__class__ is cls_ and
our_recs = [r for r in recs
if r.__class__ is cls_ and
r.mapper is mapper]
recs.difference_update(our_recs)
states = [self.state] + [r.state for r in our_recs]
persistence.delete_obj(mapper,
[s for s in states if uow.states[s][0]],
[s for s in states if uow.states[s][0]],
uow)
def __repr__(self):
+73 -60
View File
@@ -1,5 +1,5 @@
# orm/util.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 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
@@ -30,15 +30,15 @@ class CascadeOptions(frozenset):
def __new__(cls, arg):
values = set([
c for c
c for c
in re.split('\s*,\s*', arg or "")
if c
])
if values.difference(cls._allowed_cascades):
raise sa_exc.ArgumentError(
"Invalid cascade option(s): %s" %
", ".join([repr(x) for x in
"Invalid cascade option(s): %s" %
", ".join([repr(x) for x in
sorted(
values.difference(cls._allowed_cascades)
)])
@@ -68,30 +68,42 @@ class CascadeOptions(frozenset):
",".join([x for x in sorted(self)])
)
def _validator_events(desc, key, validator):
def _validator_events(desc, key, validator, include_removes):
"""Runs a validation method on an attribute value to be set or appended."""
def append(state, value, initiator):
return validator(state.obj(), key, value)
if include_removes:
def append(state, value, initiator):
return validator(state.obj(), key, value, False)
def set_(state, value, oldvalue, initiator):
return validator(state.obj(), key, value)
def set_(state, value, oldvalue, initiator):
return validator(state.obj(), key, value, False)
def remove(state, value, initiator):
validator(state.obj(), key, value, True)
else:
def append(state, value, initiator):
return validator(state.obj(), key, value)
def set_(state, value, oldvalue, initiator):
return validator(state.obj(), key, value)
event.listen(desc, 'append', append, raw=True, retval=True)
event.listen(desc, 'set', set_, raw=True, retval=True)
if include_removes:
event.listen(desc, "remove", remove, raw=True, retval=True)
def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=True):
"""Create a ``UNION`` statement used by a polymorphic mapper.
See :ref:`concrete_inheritance` for an example of how
this is used.
:param table_map: mapping of polymorphic identities to
:param table_map: mapping of polymorphic identities to
:class:`.Table` objects.
:param typecolname: string name of a "discriminator" column, which will be
:param typecolname: string name of a "discriminator" column, which will be
derived from the query, producing the polymorphic identity for each row. If
``None``, no polymorphic discriminator is generated.
:param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
:param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
construct generated.
:param cast_nulls: if True, non-existent columns, which are represented as labeled
NULLs, will be passed into CAST. This is a legacy behavior that is problematic
@@ -105,7 +117,7 @@ def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=Tr
for key in table_map.keys():
table = table_map[key]
# mysql doesnt like selecting from a select;
# mysql doesnt like selecting from a select;
# make it an alias of the select
if isinstance(table, sql.Select):
table = table.alias()
@@ -187,7 +199,7 @@ def identity_key(*args, **kwargs):
% ", ".join(kwargs.keys()))
mapper = class_mapper(class_)
if "ident" in locals():
return mapper.identity_key_from_primary_key(ident)
return mapper.identity_key_from_primary_key(util.to_list(ident))
return mapper.identity_key_from_row(row)
instance = kwargs.pop("instance")
if kwargs:
@@ -203,14 +215,14 @@ class ORMAdapter(sql_util.ColumnAdapter):
and the AliasedClass if any is referenced.
"""
def __init__(self, entity, equivalents=None,
def __init__(self, entity, equivalents=None,
chain_to=None, adapt_required=False):
self.mapper, selectable, is_aliased_class = _entity_info(entity)
if is_aliased_class:
self.aliased_class = entity
else:
self.aliased_class = None
sql_util.ColumnAdapter.__init__(self, selectable,
sql_util.ColumnAdapter.__init__(self, selectable,
equivalents, chain_to,
adapt_required=adapt_required)
@@ -236,51 +248,51 @@ class AliasedClass(object):
session.query(User, user_alias).\\
join((user_alias, User.id > user_alias.id)).\\
filter(User.name==user_alias.name)
The resulting object is an instance of :class:`.AliasedClass`, however
it implements a ``__getattribute__()`` scheme which will proxy attribute
access to that of the ORM class being aliased. All classmethods
on the mapped entity should also be available here, including
on the mapped entity should also be available here, including
hybrids created with the :ref:`hybrids_toplevel` extension,
which will receive the :class:`.AliasedClass` as the "class" argument
when classmethods are called.
:param cls: ORM mapped entity which will be "wrapped" around an alias.
:param alias: a selectable, such as an :func:`.alias` or :func:`.select`
construct, which will be rendered in place of the mapped table of the
ORM entity. If left as ``None``, an ordinary :class:`.Alias` of the
ORM entity. If left as ``None``, an ordinary :class:`.Alias` of the
ORM entity's mapped table will be generated.
:param name: A name which will be applied both to the :class:`.Alias`
if one is generated, as well as the name present in the "named tuple"
returned by the :class:`.Query` object when results are returned.
:param adapt_on_names: if True, more liberal "matching" will be used when
mapping the mapped columns of the ORM entity to those of the given selectable -
a name-based match will be performed if the given selectable doesn't
otherwise have a column that corresponds to one on the entity. The
mapping the mapped columns of the ORM entity to those of the given selectable -
a name-based match will be performed if the given selectable doesn't
otherwise have a column that corresponds to one on the entity. The
use case for this is when associating an entity with some derived
selectable such as one that uses aggregate functions::
class UnitPrice(Base):
__tablename__ = 'unit_price'
...
unit_id = Column(Integer)
price = Column(Numeric)
aggregated_unit_price = Session.query(
func.sum(UnitPrice.price).label('price')
).group_by(UnitPrice.unit_id).subquery()
aggregated_unit_price = aliased(UnitPrice, alias=aggregated_unit_price, adapt_on_names=True)
Above, functions on ``aggregated_unit_price`` which
refer to ``.price`` will return the
``fund.sum(UnitPrice.price).label('price')`` column,
as it is matched on the name "price". Ordinarily, the "price" function wouldn't
have any "column correspondence" to the actual ``UnitPrice.price`` column
as it is not a proxy of the original.
``adapt_on_names`` is new in 0.7.3.
.. versionadded:: 0.7.3
"""
def __init__(self, cls, alias=None, name=None, adapt_on_names=False):
self.__mapper = _class_to_mapper(cls)
@@ -299,8 +311,8 @@ class AliasedClass(object):
def __getstate__(self):
return {
'mapper':self.__mapper,
'alias':self.__alias,
'mapper':self.__mapper,
'alias':self.__alias,
'name':self._sa_label_name,
'adapt_on_names':self.__adapt_on_names,
}
@@ -321,7 +333,7 @@ class AliasedClass(object):
def __adapt_element(self, elem):
return self.__adapter.traverse(elem).\
_annotate({
'parententity': self,
'parententity': self,
'parentmapper':self.__mapper}
)
@@ -388,7 +400,7 @@ class _ORMJoin(expression.Join):
__visit_name__ = expression.Join.__visit_name__
def __init__(self, left, right, onclause=None,
def __init__(self, left, right, onclause=None,
isouter=False, join_to_left=True):
adapt_from = None
@@ -447,7 +459,7 @@ class _ORMJoin(expression.Join):
def join(left, right, onclause=None, isouter=False, join_to_left=True):
"""Produce an inner join between left and right clauses.
:func:`.orm.join` is an extension to the core join interface
provided by :func:`.sql.expression.join()`, where the
left and right selectables may be not only core selectable
@@ -460,30 +472,30 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
in whatever form it is passed, to the selectable
passed as the left side. If False, the onclause
is used as is.
:func:`.orm.join` is not commonly needed in modern usage,
as its functionality is encapsulated within that of the
:meth:`.Query.join` method, which features a
significant amount of automation beyond :func:`.orm.join`
by itself. Explicit usage of :func:`.orm.join`
with :class:`.Query` involves usage of the
by itself. Explicit usage of :func:`.orm.join`
with :class:`.Query` involves usage of the
:meth:`.Query.select_from` method, as in::
from sqlalchemy.orm import join
session.query(User).\\
select_from(join(User, Address, User.addresses)).\\
filter(Address.email_address=='foo@bar.com')
In modern SQLAlchemy the above join can be written more
In modern SQLAlchemy the above join can be written more
succinctly as::
session.query(User).\\
join(User.addresses).\\
filter(Address.email_address=='foo@bar.com')
See :meth:`.Query.join` for information on modern usage
of ORM level joins.
"""
return _ORMJoin(left, right, onclause, isouter, join_to_left)
@@ -504,23 +516,24 @@ def with_parent(instance, prop):
The SQL rendered is the same as that rendered when a lazy loader
would fire off from the given parent on that attribute, meaning
that the appropriate state is taken from the parent object in
that the appropriate state is taken from the parent object in
Python without the need to render joins to the parent table
in the rendered statement.
As of 0.6.4, this method accepts parent instances in all
persistence states, including transient, persistent, and detached.
Only the requisite primary key/foreign key attributes need to
be populated. Previous versions didn't work with transient
instances.
.. versionchanged:: 0.6.4
This method accepts parent instances in all
persistence states, including transient, persistent, and detached.
Only the requisite primary key/foreign key attributes need to
be populated. Previous versions didn't work with transient
instances.
:param instance:
An instance which has some :func:`.relationship`.
:param property:
String property name, or class-bound attribute, which indicates
what relationship from the instance should be used to reconcile the
parent/child relationship.
what relationship from the instance should be used to reconcile the
parent/child relationship.
"""
if isinstance(prop, basestring):
@@ -529,8 +542,8 @@ def with_parent(instance, prop):
elif isinstance(prop, attributes.QueryableAttribute):
prop = prop.property
return prop.compare(operators.eq,
instance,
return prop.compare(operators.eq,
instance,
value_is_parent=True)
@@ -584,7 +597,7 @@ def _entity_descriptor(entity, key):
return getattr(entity, key)
except AttributeError:
raise sa_exc.InvalidRequestError(
"Entity '%s' has no property '%s'" %
"Entity '%s' has no property '%s'" %
(description, key)
)
@@ -626,7 +639,7 @@ def object_mapper(instance):
raise exc.UnmappedInstanceError(instance)
def class_mapper(class_, compile=True):
"""Given a class, return the primary :class:`.Mapper` associated
"""Given a class, return the primary :class:`.Mapper` associated
with the key.
Raises :class:`.UnmappedClassError` if no mapping is configured
@@ -640,8 +653,8 @@ def class_mapper(class_, compile=True):
mapper = class_manager.mapper
except exc.NO_STATE:
if not isinstance(class_, type):
raise sa_exc.ArgumentError("Class object expected, got '%r'." % class_)
if not isinstance(class_, type):
raise sa_exc.ArgumentError("Class object expected, got '%r'." % class_)
raise exc.UnmappedClassError(class_)
if compile and mapperlib.module._new_mappers:
@@ -672,7 +685,7 @@ def has_identity(object):
return state.has_identity
def _is_mapped_class(cls):
"""Return True if the given object is a mapped class,
"""Return True if the given object is a mapped class,
:class:`.Mapper`, or :class:`.AliasedClass`."""
if isinstance(cls, (AliasedClass, mapperlib.Mapper)):
@@ -685,7 +698,7 @@ def _is_mapped_class(cls):
return False
def _mapper_or_none(cls):
"""Return the :class:`.Mapper` for the given class or None if the
"""Return the :class:`.Mapper` for the given class or None if the
class is not mapped."""
manager = attributes.manager_of_class(cls)