diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-23 15:27:47 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-23 15:27:47 -0500 |
| commit | 6cd730541f4e61fb8262ac50752c21cf1e7262ac (patch) | |
| tree | f6dceb9d0be15e5a101d6119182eb227b86f2958 /examples | |
| parent | e9d1b5b8de35dabc42d7dcf91c01783fa3133733 (diff) | |
| download | sqlalchemy-6cd730541f4e61fb8262ac50752c21cf1e7262ac.tar.gz | |
- restore declarative support for "composite"
- add an example of mutable scalars with events
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/mutable_events/__init__.py | 31 | ||||
| -rw-r--r-- | examples/mutable_events/scalars.py | 121 |
2 files changed, 152 insertions, 0 deletions
diff --git a/examples/mutable_events/__init__.py b/examples/mutable_events/__init__.py new file mode 100644 index 000000000..9802a109b --- /dev/null +++ b/examples/mutable_events/__init__.py @@ -0,0 +1,31 @@ +""" +Illustrates how to build and use "mutable" types, such as dictionaries and +user-defined classes, as scalar attributes which detect in-place changes. + +The example is based around the usage of the event model introduced in +:ref:`event_toplevel`, along with the :func:`attributes.flag_modified` function +which establishes the "dirty" flag on a particular mapped attribute. These +functions are encapsulated in a mixin called ``TrackMutationsMixin``. +Subclassing ``dict`` to provide "mutation tracking" looks like:: + + class MutationDict(TrackMutationsMixin, dict): + def __init__(self, other): + self.update(other) + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.on_change() + + def __delitem__(self, key): + dict.__delitem__(self, key) + self.on_change() + + Base = declarative_base() + class Foo(Base): + __tablename__ = 'foo' + id = Column(Integer, primary_key=True) + data = Column(JSONEncodedDict) + + MutationDict.listen(Foo.data) + +"""
\ No newline at end of file diff --git a/examples/mutable_events/scalars.py b/examples/mutable_events/scalars.py new file mode 100644 index 000000000..4d434fd54 --- /dev/null +++ b/examples/mutable_events/scalars.py @@ -0,0 +1,121 @@ +from sqlalchemy.orm.attributes import flag_modified +from sqlalchemy import event +import weakref + +class TrackMutationsMixin(object): + """Mixin that defines transparent propagation of change + events to a parent object. + + """ + _key = None + _parent = None + + def _set_parent(self, parent, key): + self._parent = weakref.ref(parent) + self._key = key + + def _remove_parent(self): + del self._parent + + def on_change(self, key=None): + """Subclasses should call this method whenever change events occur.""" + + if key is None: + key = self._key + if self._parent: + p = self._parent() + if p: + flag_modified(p, self._key) + + @classmethod + def listen(cls, attribute): + """Establish this type as a mutation listener for the given class and + attribute name. + + """ + key = attribute.key + parent_cls = attribute.class_ + + def on_load(state): + val = state.dict.get(key, None) + if val is not None: + val = cls(val) + state.dict[key] = val + val._set_parent(state.obj(), key) + + def on_set(target, value, oldvalue, initiator): + if not isinstance(value, cls): + value = cls(value) + value._set_parent(target.obj(), key) + if isinstance(oldvalue, cls): + oldvalue._remove_parent() + return value + + event.listen(parent_cls, 'on_load', on_load, raw=True) + event.listen(parent_cls, 'on_refresh', on_load, raw=True) + event.listen(attribute, 'on_set', on_set, raw=True, retval=True) + +if __name__ == '__main__': + from sqlalchemy import Column, Integer, VARCHAR, create_engine + from sqlalchemy.orm import Session + from sqlalchemy.types import TypeDecorator + from sqlalchemy.ext.declarative import declarative_base + import simplejson + + class JSONEncodedDict(TypeDecorator): + """Represents an immutable structure as a json-encoded string. + + Usage:: + + JSONEncodedDict(255) + + """ + + impl = VARCHAR + + def process_bind_param(self, value, dialect): + if value is not None: + value = simplejson.dumps(value, use_decimal=True) + + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = simplejson.loads(value, use_decimal=True) + return value + + class MutationDict(TrackMutationsMixin, dict): + def __init__(self, other): + self.update(other) + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.on_change() + + def __delitem__(self, key): + dict.__delitem__(self, key) + self.on_change() + + Base = declarative_base() + class Foo(Base): + __tablename__ = 'foo' + id = Column(Integer, primary_key=True) + data = Column(JSONEncodedDict) + + MutationDict.listen(Foo.data) + + e = create_engine('sqlite://', echo=True) + + Base.metadata.create_all(e) + + sess = Session(e) + f1 = Foo(data={'a':'b'}) + sess.add(f1) + sess.commit() + + f1.data['a'] = 'c' + sess.commit() + + assert f1.data == {'a':'c'} + +
\ No newline at end of file |
