summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-12-23 15:27:47 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2010-12-23 15:27:47 -0500
commit6cd730541f4e61fb8262ac50752c21cf1e7262ac (patch)
treef6dceb9d0be15e5a101d6119182eb227b86f2958 /examples
parente9d1b5b8de35dabc42d7dcf91c01783fa3133733 (diff)
downloadsqlalchemy-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__.py31
-rw-r--r--examples/mutable_events/scalars.py121
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