summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/mutable.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-12-03 19:49:42 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2012-12-03 19:49:42 -0500
commitd89d71d1dad4b795e14d5395f4008c5027b59baa (patch)
treee77a26aa322836de21ac4e7d352b146a602ef4e9 /lib/sqlalchemy/ext/mutable.py
parentc333b680713dd21b4c4bf4e17936dc2d8428d6c5 (diff)
downloadsqlalchemy-d89d71d1dad4b795e14d5395f4008c5027b59baa.tar.gz
The :class:`.MutableComposite` type did not allow for the
:meth:`.MutableBase.coerce` method to be used, even though the code seemed to indicate this intent, so this now works and a brief example is added. As a side-effect, the mechanics of this event handler have been changed so that new :class:`.MutableComposite` types no longer add per-type global event handlers. Also in 0.7.10 [ticket:2624]
Diffstat (limited to 'lib/sqlalchemy/ext/mutable.py')
-rw-r--r--lib/sqlalchemy/ext/mutable.py91
1 files changed, 56 insertions, 35 deletions
diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py
index 36d60d6d5..e290a93e2 100644
--- a/lib/sqlalchemy/ext/mutable.py
+++ b/lib/sqlalchemy/ext/mutable.py
@@ -302,6 +302,31 @@ will flag the attribute as "dirty" on the parent object::
>>> assert v1 in sess.dirty
True
+Coercing Mutable Composites
+---------------------------
+
+The :meth:`.MutableBase.coerce` method is also supported on composite types.
+In the case of :class:`.MutableComposite`, the :meth:`.MutableBase.coerce`
+method is only called for attribute set operations, not load operations.
+Overriding the :meth:`.MutableBase.coerce` method is essentially equivalent
+to using a :func:`.validates` validation routine for all attributes which
+make use of the custom composite type::
+
+ class Point(MutableComposite):
+ # other Point methods
+ # ...
+
+ def coerce(cls, key, value):
+ if isinstance(value, tuple):
+ value = Point(*value)
+ elif not isinstance(value, Point):
+ raise ValueError("tuple or Point expected")
+ return value
+
+.. versionadded:: 0.7.10,0.8.0b2
+ Support for the :meth:`.MutableBase.coerce` method in conjunction with
+ objects of type :class:`.MutableComposite`.
+
Supporting Pickling
--------------------
@@ -329,7 +354,7 @@ pickling process of the parent's object-relational state so that the
"""
from ..orm.attributes import flag_modified
from .. import event, types
-from ..orm import mapper, object_mapper
+from ..orm import mapper, object_mapper, Mapper
from ..util import memoized_property
import weakref
@@ -354,9 +379,27 @@ class MutableBase(object):
@classmethod
def coerce(cls, key, value):
- """Given a value, coerce it into this type.
+ """Given a value, coerce it into the target type.
+
+ Can be overridden by custom subclasses to coerce incoming
+ data into a particular type.
+
+ By default, raises ``ValueError``.
+
+ This method is called in different scenarios depending on if
+ the parent class is of type :class:`.Mutable` or of type
+ :class:`.MutableComposite`. In the case of the former, it is called
+ for both attribute-set operations as well as during ORM loading
+ operations. For the latter, it is only called during attribute-set
+ operations; the mechanics of the :func:`.composite` construct
+ handle coercion during load operations.
+
+
+ :param key: string name of the ORM-mapped attribute being set.
+ :param value: the incoming value.
+ :return: the method should return the coerced value, or raise
+ ``ValueError`` if the coercion cannot be completed.
- By default raises ValueError.
"""
if value is None:
return None
@@ -523,11 +566,6 @@ 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
@@ -536,16 +574,7 @@ class MutableComposite(MutableBase):
See the example in :ref:`mutable_composites` for usage information.
- .. 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.
-
"""
- __metaclass__ = _MutableCompositeMeta
def changed(self):
"""Subclasses should call this method whenever change events occur."""
@@ -558,24 +587,16 @@ class MutableComposite(MutableBase):
prop._attribute_keys):
setattr(parent, attr_name, value)
- @classmethod
- def _setup_listeners(cls):
- """Associate this wrapper with all future mapped composites
- of the given type.
-
- 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_)
-
- event.listen(mapper, 'mapper_configured', listen_for_type)
+def _setup_composite_listener():
+ def _listen_for_type(mapper, class_):
+ for prop in mapper.iterate_properties:
+ if (hasattr(prop, 'composite_class') and
+ issubclass(prop.composite_class, MutableComposite)):
+ prop.composite_class._listen_on_attribute(
+ getattr(class_, prop.key), False, class_)
+ if not Mapper.dispatch.mapper_configured._contains(Mapper, _listen_for_type):
+ event.listen(Mapper, 'mapper_configured', _listen_for_type)
+_setup_composite_listener()
class MutableDict(Mutable, dict):