diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-12-03 19:49:42 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-12-03 19:49:42 -0500 |
commit | d89d71d1dad4b795e14d5395f4008c5027b59baa (patch) | |
tree | e77a26aa322836de21ac4e7d352b146a602ef4e9 /lib/sqlalchemy/ext/mutable.py | |
parent | c333b680713dd21b4c4bf4e17936dc2d8428d6c5 (diff) | |
download | sqlalchemy-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.py | 91 |
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): |