summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/mutable.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/ext/mutable.py')
-rw-r--r--lib/sqlalchemy/ext/mutable.py101
1 files changed, 58 insertions, 43 deletions
diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py
index db05a82b4..36d60d6d5 100644
--- a/lib/sqlalchemy/ext/mutable.py
+++ b/lib/sqlalchemy/ext/mutable.py
@@ -7,13 +7,13 @@
"""Provide support for tracking of in-place changes to scalar values,
which are propagated into ORM change events on owning parent objects.
-The :mod:`sqlalchemy.ext.mutable` extension replaces SQLAlchemy's legacy approach to in-place
-mutations of scalar values, established by the :class:`.types.MutableType`
-class as well as the ``mutable=True`` type flag, with a system that allows
-change events to be propagated from the value to the owning parent, thereby
-removing the need for the ORM to maintain copies of values as well as the very
-expensive requirement of scanning through all "mutable" values on each flush
-call, looking for changes.
+The :mod:`sqlalchemy.ext.mutable` extension replaces SQLAlchemy's legacy
+approach to in-place mutations of scalar values, established by the
+:class:`.types.MutableType` class as well as the ``mutable=True`` type flag,
+with a system that allows change events to be propagated from the value to
+the owning parent, thereby removing the need for the ORM to maintain copies
+of values as well as the very expensive requirement of scanning through all
+"mutable" values on each flush call, looking for changes.
.. _mutable_scalars:
@@ -43,8 +43,8 @@ JSON strings before being persisted::
value = json.loads(value)
return value
-The usage of ``json`` is only for the purposes of example. The :mod:`sqlalchemy.ext.mutable`
-extension can be used
+The usage of ``json`` is only for the purposes of example. The
+:mod:`sqlalchemy.ext.mutable` extension can be used
with any type whose target Python type may be mutable, including
:class:`.PickleType`, :class:`.postgresql.ARRAY`, etc.
@@ -86,19 +86,19 @@ The above dictionary class takes the approach of subclassing the Python
built-in ``dict`` to produce a dict
subclass which routes all mutation events through ``__setitem__``. There are
variants on this approach, such as subclassing ``UserDict.UserDict`` or
-``collections.MutableMapping``; the part that's important to this
-example is that the :meth:`.Mutable.changed` method is called whenever an in-place change to the
-datastructure takes place.
+``collections.MutableMapping``; the part that's important to this example is
+that the :meth:`.Mutable.changed` method is called whenever an in-place
+change to the datastructure takes place.
We also redefine the :meth:`.Mutable.coerce` method which will be used to
convert any values that are not instances of ``MutableDict``, such
as the plain dictionaries returned by the ``json`` module, into the
-appropriate type. Defining this method is optional; we could just as well created our
-``JSONEncodedDict`` such that it always returns an instance of ``MutableDict``,
-and additionally ensured that all calling code uses ``MutableDict``
-explicitly. When :meth:`.Mutable.coerce` is not overridden, any values
-applied to a parent object which are not instances of the mutable type
-will raise a ``ValueError``.
+appropriate type. Defining this method is optional; we could just as well
+created our ``JSONEncodedDict`` such that it always returns an instance
+of ``MutableDict``, and additionally ensured that all calling code
+uses ``MutableDict`` explicitly. When :meth:`.Mutable.coerce` is not
+overridden, any values applied to a parent object which are not instances
+of the mutable type will raise a ``ValueError``.
Our new ``MutableDict`` type offers a class method
:meth:`~.Mutable.as_mutable` which we can use within column metadata
@@ -156,9 +156,10 @@ will flag the attribute as "dirty" on the parent object::
True
The ``MutableDict`` can be associated with all future instances
-of ``JSONEncodedDict`` in one step, using :meth:`~.Mutable.associate_with`. This
-is similar to :meth:`~.Mutable.as_mutable` except it will intercept
-all occurrences of ``MutableDict`` in all mappings unconditionally, without
+of ``JSONEncodedDict`` in one step, using
+:meth:`~.Mutable.associate_with`. This is similar to
+:meth:`~.Mutable.as_mutable` except it will intercept all occurrences
+of ``MutableDict`` in all mappings unconditionally, without
the need to declare it individually::
MutableDict.associate_with(JSONEncodedDict)
@@ -330,11 +331,14 @@ from ..orm.attributes import flag_modified
from .. import event, types
from ..orm import mapper, object_mapper
from ..util import memoized_property
-from .. import exc
import weakref
+
class MutableBase(object):
- """Common base class to :class:`.Mutable` and :class:`.MutableComposite`."""
+ """Common base class to :class:`.Mutable`
+ and :class:`.MutableComposite`.
+
+ """
@memoized_property
def _parents(self):
@@ -356,7 +360,8 @@ class MutableBase(object):
"""
if value is None:
return None
- raise ValueError("Attribute '%s' does not accept objects of type %s" % (key, type(value)))
+ msg = "Attribute '%s' does not accept objects of type %s"
+ raise ValueError(msg % (key, type(value)))
@classmethod
def _listen_on_attribute(cls, attribute, coerce, parent_cls):
@@ -414,12 +419,17 @@ class MutableBase(object):
for val in state_dict['ext.mutable.values']:
val._parents[state.obj()] = key
+ event.listen(parent_cls, 'load', load,
+ raw=True, propagate=True)
+ event.listen(parent_cls, 'refresh', load,
+ raw=True, propagate=True)
+ event.listen(attribute, 'set', set,
+ raw=True, retval=True, propagate=True)
+ event.listen(parent_cls, 'pickle', pickle,
+ raw=True, propagate=True)
+ event.listen(parent_cls, 'unpickle', unpickle,
+ raw=True, propagate=True)
- event.listen(parent_cls, 'load', load, raw=True, propagate=True)
- event.listen(parent_cls, 'refresh', load, raw=True, propagate=True)
- event.listen(attribute, 'set', set, raw=True, retval=True, propagate=True)
- event.listen(parent_cls, 'pickle', pickle, raw=True, propagate=True)
- event.listen(parent_cls, 'unpickle', unpickle, raw=True, propagate=True)
class Mutable(MutableBase):
"""Mixin that defines transparent propagation of change
@@ -448,15 +458,16 @@ class Mutable(MutableBase):
"""Associate this wrapper with all future mapped columns
of the given type.
- This is a convenience method that calls ``associate_with_attribute`` automatically.
+ This is a convenience method that calls
+ ``associate_with_attribute`` automatically.
.. warning::
The listeners established by this method are *global*
to all mappers, and are *not* garbage collected. Only use
- :meth:`.associate_with` for types that are permanent to an application,
- not with ad-hoc types else this will cause unbounded growth
- in memory usage.
+ :meth:`.associate_with` for types that are permanent to an
+ application, not with ad-hoc types else this will cause unbounded
+ growth in memory usage.
"""
@@ -483,8 +494,8 @@ class Mutable(MutableBase):
)
Note that the returned type is always an instance, even if a class
- is given, and that only columns which are declared specifically with that
- type instance receive additional instrumentation.
+ is given, and that only columns which are declared specifically with
+ that type instance receive additional instrumentation.
To associate a particular mutable type with all occurrences of a
particular type, use the :meth:`.Mutable.associate_with` classmethod
@@ -511,11 +522,13 @@ class Mutable(MutableBase):
return sqltype
+
class _MutableCompositeMeta(type):
def __init__(cls, classname, bases, dict_):
cls._setup_listeners()
return type.__init__(cls, classname, bases, dict_)
+
class MutableComposite(MutableBase):
"""Mixin that defines transparent propagation of change
events on a SQLAlchemy "composite" object to its
@@ -526,10 +539,10 @@ class MutableComposite(MutableBase):
.. warning::
The listeners established by the :class:`.MutableComposite`
- class are *global* to all mappers, and are *not* garbage collected. Only use
- :class:`.MutableComposite` for types that are permanent to an application,
- not with ad-hoc types else this will cause unbounded growth
- in memory usage.
+ class are *global* to all mappers, and are *not* garbage
+ collected. Only use :class:`.MutableComposite` for types that are
+ permanent to an application, not with ad-hoc types else this will
+ cause unbounded growth in memory usage.
"""
__metaclass__ = _MutableCompositeMeta
@@ -550,19 +563,21 @@ class MutableComposite(MutableBase):
"""Associate this wrapper with all future mapped composites
of the given type.
- This is a convenience method that calls ``associate_with_attribute`` automatically.
+ This is a convenience method that calls ``associate_with_attribute``
+ automatically.
"""
def listen_for_type(mapper, class_):
for prop in mapper.iterate_properties:
- if hasattr(prop, 'composite_class') and issubclass(prop.composite_class, cls):
- cls._listen_on_attribute(getattr(class_, prop.key), False, class_)
+ if (hasattr(prop, 'composite_class') and
+ issubclass(prop.composite_class, cls)):
+ cls._listen_on_attribute(
+ getattr(class_, prop.key), False, class_)
event.listen(mapper, 'mapper_configured', listen_for_type)
-
class MutableDict(Mutable, dict):
"""A dictionary type that implements :class:`.Mutable`.