summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-12-28 22:23:13 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2010-12-28 22:23:13 -0500
commit3b41b66981d8665282c645178643d273361eb6ad (patch)
tree51955294b658bbf3bc0e840901d3a888b557223e /examples
parent4f8f6b3989327398c048fa55bc2ed8f26fb022bd (diff)
downloadsqlalchemy-3b41b66981d8665282c645178643d273361eb6ad.tar.gz
- restore mapper.get_property() to use the _props dict. at the moment
synonyms for relationships might just be taken out altogether, since they aren't documented and are of little use. a plain proxying descriptor, combined with attribute-based usage with Query (as opposted to naming it by string) can do the same thing more simply. - add event support to composites, change the model around so that the composite is generated at the point of load. - add a recipe for tracking mutations on composites. will probably make both of these mutations examples into extensions since they're intricate, should have a lot of test coverage, and what they need to do is fairly straightforward. Will use metaclasses so that no extra userland step is needed beyond usage of the type.
Diffstat (limited to 'examples')
-rw-r--r--examples/mutable_events/composite.py139
-rw-r--r--examples/mutable_events/scalars.py10
2 files changed, 147 insertions, 2 deletions
diff --git a/examples/mutable_events/composite.py b/examples/mutable_events/composite.py
new file mode 100644
index 000000000..f46f28e6d
--- /dev/null
+++ b/examples/mutable_events/composite.py
@@ -0,0 +1,139 @@
+# this example is probably moving to be an extension.
+
+from sqlalchemy import event
+from sqlalchemy.orm import mapper, composite, object_mapper
+
+from sqlalchemy.util import memoized_property
+import weakref
+
+class _CompositeMutationsMixinMeta(type):
+ def __init__(cls, classname, bases, dict_):
+ cls._setup_listeners()
+ return type.__init__(cls, classname, bases, dict_)
+
+class CompositeMutationsMixin(object):
+ """Mixin that defines transparent propagation of change
+ events to a parent object.
+
+ This class might be moved to be a SQLA extension
+ due to its complexity and potential for widespread use.
+
+ """
+ __metaclass__ = _CompositeMutationsMixinMeta
+
+ @memoized_property
+ def _parents(self):
+ """Dictionary of parent object->attribute name on the parent."""
+
+ return weakref.WeakKeyDictionary()
+
+ def __setattr__(self, key, value):
+ object.__setattr__(self, key, value)
+ self.on_change()
+
+ def on_change(self):
+ """Subclasses should call this method whenever change events occur."""
+
+ for parent, key in self._parents.items():
+
+ prop = object_mapper(parent).get_property(key)
+ for value, attr_name in zip(self.__composite_values__(), prop._attribute_keys):
+ setattr(parent, attr_name, value)
+
+ @classmethod
+ def _listen_on_attribute(cls, attribute):
+ """Establish this type as a mutation listener for the given
+ mapped descriptor.
+
+ """
+ key = attribute.key
+ parent_cls = attribute.class_
+
+ def on_load(state):
+ """Listen for objects loaded or refreshed.
+
+ Wrap the target data member's value with
+ ``TrackMutationsMixin``.
+
+ """
+
+ val = state.dict.get(key, None)
+ if val is not None:
+ val._parents[state.obj()] = key
+
+ def on_set(target, value, oldvalue, initiator):
+ """Listen for set/replace events on the target
+ data member.
+
+ Establish a weak reference to the parent object
+ on the incoming value, remove it for the one
+ outgoing.
+
+ """
+
+ value._parents[target.obj()] = key
+ if isinstance(oldvalue, cls):
+ oldvalue._parents.pop(state.obj(), None)
+ 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)
+
+ @classmethod
+ def _setup_listeners(cls):
+ """Associate this wrapper with all future mapped compoistes
+ 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))
+
+ event.listen(mapper, 'on_mapper_configured', listen_for_type)
+
+
+if __name__ == '__main__':
+ from sqlalchemy import Column, Integer, create_engine
+ from sqlalchemy.orm import Session
+ from sqlalchemy.ext.declarative import declarative_base
+
+ class Point(CompositeMutationsMixin):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ def __composite_values__(self):
+ return self.x, self.y
+
+ def __eq__(self, other):
+ return isinstance(other, Point) and \
+ other.x == self.x and \
+ other.y == self.y
+
+ Base = declarative_base()
+ class Foo(Base):
+ __tablename__ = 'foo'
+ id = Column(Integer, primary_key=True)
+ data = composite(Point, Column('x', Integer), Column('y', Integer))
+
+ e = create_engine('sqlite://', echo=True)
+
+ Base.metadata.create_all(e)
+
+ sess = Session(e)
+ d = Point(3, 4)
+ f1 = Foo(data=d)
+ sess.add(f1)
+ sess.commit()
+
+ f1.data.y = 5
+ sess.commit()
+
+ assert f1.data == Point(3, 5)
+
+ \ No newline at end of file
diff --git a/examples/mutable_events/scalars.py b/examples/mutable_events/scalars.py
index b4d6b350d..1c135a957 100644
--- a/examples/mutable_events/scalars.py
+++ b/examples/mutable_events/scalars.py
@@ -1,3 +1,5 @@
+# this example is probably moving to be an extension.
+
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy import event
from sqlalchemy.orm import mapper
@@ -8,6 +10,9 @@ class TrackMutationsMixin(object):
"""Mixin that defines transparent propagation of change
events to a parent object.
+ This class might be moved to be a SQLA extension
+ due to its complexity and potential for widespread use.
+
"""
@memoized_property
def _parents(self):
@@ -76,7 +81,7 @@ class TrackMutationsMixin(object):
def listen_for_type(mapper, class_):
for prop in mapper.iterate_properties:
if hasattr(prop, 'columns') and isinstance(prop.columns[0].type, type_):
- cls.listen(getattr(class_, prop.key))
+ cls.associate_with_attribute(getattr(class_, prop.key))
event.listen(mapper, 'on_mapper_configured', listen_for_type)
@@ -121,7 +126,8 @@ if __name__ == '__main__':
def __delitem__(self, key):
dict.__delitem__(self, key)
self.on_change()
-
+
+ # TODO: do the metaclass approach the same as composite
MutationDict.associate_with_type(JSONEncodedDict)
Base = declarative_base()