]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Iteration over dict association proxies is now dict-like, not
authorJason Kirtland <jek@discorporate.us>
Thu, 14 Jun 2007 00:11:51 +0000 (00:11 +0000)
committerJason Kirtland <jek@discorporate.us>
Thu, 14 Jun 2007 00:11:51 +0000 (00:11 +0000)
  InstrumentedList-like (e.g. over keys instead of values).
- Don't tightly bind proxies to source collections (fixes #597)
- Handle slice objects on orderinglist's __setitem__

CHANGES
lib/sqlalchemy/ext/associationproxy.py
lib/sqlalchemy/ext/orderinglist.py
test/ext/associationproxy.py

diff --git a/CHANGES b/CHANGES
index 0abb91de354e30dfdaf9d68170d59f99f4e00179..62118dab1351eb10a019d30444fb18b6285d8b28 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+- ext
+    - iteration over dict association proxies is now dict-like, not
+      InstrumentedList-like (e.g. over keys instead of values)
+    - association proxies no longer bind tightly to source collections
+      [ticket:597], and are constructed with a thunk instead
 - orm
     - remember all that stuff about polymorphic_union ?  for 
       joined table inheritance ?  Funny thing...
index d6a995450ac97d4a4ceb95a80412ff13f1a03444..1b363c9acdfdf35fa894d75ab52bf31ca9db9690 100644 (file)
@@ -10,6 +10,7 @@ from sqlalchemy.orm.attributes import InstrumentedList
 import sqlalchemy.exceptions as exceptions
 import sqlalchemy.orm as orm
 import sqlalchemy.util as util
+import weakref
 
 def association_proxy(targetcollection, attr, **kw):
     """Convenience function for use in mapped classes.  Implements a Python
@@ -116,6 +117,18 @@ class AssociationProxy(object):
 
     def _target_is_scalar(self):
         return not self._get_property().uselist
+
+    def _lazy_collection(self, weakobjref):
+        target = self.target_collection
+        del self
+        def lazy_collection():
+            obj = weakobjref()
+            if obj is None:
+                raise exceptions.InvalidRequestError(
+                    "stale association proxy, parent object has gone out of "
+                    "scope")
+            return getattr(obj, target)
+        return lazy_collection
         
     def __get__(self, obj, class_):
         if obj is None:
@@ -130,7 +143,7 @@ class AssociationProxy(object):
             try:
                 return getattr(obj, self.key)
             except AttributeError:
-                proxy = self._new(getattr(obj, self.target_collection))
+                proxy = self._new(self._lazy_collection(weakref.ref(obj)))
                 setattr(obj, self.key, proxy)
                 return proxy
 
@@ -153,30 +166,32 @@ class AssociationProxy(object):
     def __delete__(self, obj):
         delattr(obj, self.key)
 
-    def _new(self, collection):
+    def _new(self, lazy_collection):
         creator = self.creator and self.creator or self.target_class
 
         # Prefer class typing here to spot dicts with the required append()
         # method.
+        collection = lazy_collection()
         if isinstance(collection.data, dict):
             self.collection_class = dict
         else:
             self.collection_class = util.duck_type_collection(collection.data)
+        del collection
 
         if self.proxy_factory:
-            return self.proxy_factory(collection, creator, self.value_attr)
+            return self.proxy_factory(lazy_collection, creator, self.value_attr)
 
         value_attr = self.value_attr
         getter = lambda o: getattr(o, value_attr)
         setter = lambda o, v: setattr(o, value_attr, v)
         
         if self.collection_class is list:
-            return _AssociationList(collection, creator, getter, setter)
+            return _AssociationList(lazy_collection, creator, getter, setter)
         elif self.collection_class is dict:
             kv_setter = lambda o, k, v: setattr(o, value_attr, v)
-            return _AssociationDict(collection, creator, getter, setter)
+            return _AssociationDict(lazy_collection, creator, getter, setter)
         elif self.collection_class is util.Set:
-            return _AssociationSet(collection, creator, getter, setter)
+            return _AssociationSet(lazy_collection, creator, getter, setter)
         else:
             raise exceptions.ArgumentError(
                 'could not guess which interface to use for '
@@ -203,11 +218,11 @@ class _AssociationList(object):
     converting association objects to and from a simplified value.
     """
 
-    def __init__(self, collection, creator, getter, setter):
+    def __init__(self, lazy_collection, creator, getter, setter):
         """
-        collection
-          A list-based collection of entities (usually an object attribute
-          managed by a SQLAlchemy relation())
+        lazy_collection
+          A callable returning a list-based collection of entities (usually
+          an object attribute managed by a SQLAlchemy relation())
           
         creator
           A function that creates new target entities.  Given one parameter:
@@ -223,11 +238,13 @@ class _AssociationList(object):
           that value on the object.
         """
 
-        self.col = collection
+        self.lazy_collection = lazy_collection
         self.creator = creator
         self.getter = getter
         self.setter = setter
 
+    col = property(lambda self: self.lazy_collection())
+
     # For compatibility with 0.3.1 through 0.3.7- pass kw through to creator.
     # (see append() below)
     def _create(self, value, **kw):
@@ -320,11 +337,11 @@ class _AssociationDict(object):
     converting association objects to and from a simplified value.
     """
 
-    def __init__(self, collection, creator, getter, setter):
+    def __init__(self, lazy_collection, creator, getter, setter):
         """
-        collection
-          A list-based collection of entities (usually an object attribute
-          managed by a SQLAlchemy relation())
+        lazy_collection
+          A callable returning a dict-based collection of entities (usually
+          an object attribute managed by a SQLAlchemy relation())
           
         creator
           A function that creates new target entities.  Given two parameters:
@@ -340,11 +357,13 @@ class _AssociationDict(object):
           that value on the object.
         """
 
-        self.col = collection
+        self.lazy_collection = lazy_collection
         self.creator = creator
         self.getter = getter
         self.setter = setter
 
+    col = property(lambda self: self.lazy_collection())
+
     def _create(self, key, value):
         return self.creator(key, value)
 
@@ -380,7 +399,7 @@ class _AssociationDict(object):
     has_key = __contains__
 
     def __iter__(self):
-        return iter(self.col)
+        return self.col.iterkeys()
 
     def clear(self):
         self.col.clear()
@@ -465,11 +484,11 @@ class _AssociationSet(object):
     converting association objects to and from a simplified value.
     """
 
-    def __init__(self, collection, creator, getter, setter):
+    def __init__(self, lazy_collection, creator, getter, setter):
         """
         collection
-          A list-based collection of entities (usually an object attribute
-          managed by a SQLAlchemy relation())
+          A callable returning a set-based collection of entities (usually an
+          object attribute managed by a SQLAlchemy relation())
           
         creator
           A function that creates new target entities.  Given one parameter:
@@ -485,11 +504,13 @@ class _AssociationSet(object):
           that value on the object.
         """
 
-        self.col = collection
+        self.lazy_collection = lazy_collection
         self.creator = creator
         self.getter = getter
         self.setter = setter
 
+    col = property(lambda self: self.lazy_collection())
+
     def _create(self, value):
         return self.creator(value)
 
index 27ff408dcdd279b93391231d308b2f94f930a27d..e02990a2633d2787b014ff163c91128a1795b7d1 100644 (file)
@@ -165,8 +165,12 @@ class OrderingList(list):
         return entity
         
     def __setitem__(self, index, entity):
-        super(OrderingList, self).__setitem__(index, entity)
-        self._order_entity(index, entity, True)
+        if isinstance(index, slice):
+            for i in range(index.start or 0, index.stop or 0, index.step or 1):
+                self.__setitem__(i, entity[i])
+        else:
+            self._order_entity(index, entity, True)
+            super(OrderingList, self).__setitem__(index, entity)
             
     def __delitem__(self, index):
         super(OrderingList, self).__delitem__(index)
index b6476c836137eb3c7a9668506f97305574d336a3..e095d285e41060bf757974082176923979d35dfb 100644 (file)
@@ -130,7 +130,10 @@ class _CollectionOperations(PersistTest):
 
         self.assert_(len(p1._children) == 3)
         self.assert_(len(p1.children) == 3)
-        
+
+        p1._children = []
+        self.assert_(len(p1.children) == 0)
+
 class DefaultTest(_CollectionOperations):
     def __init__(self, *args, **kw):
         super(DefaultTest, self).__init__(*args, **kw)
@@ -208,10 +211,15 @@ class CustomDictTest(DictTest):
         self.assert_(len(p1._children) == 3)
         self.assert_(len(p1.children) == 3)
 
+        self.assert_(set(p1.children) == set(['d','e','f']))
+
         del ch
         p1 = self.roundtrip(p1)
         self.assert_(len(p1._children) == 3)
         self.assert_(len(p1.children) == 3)
+
+        p1._children = {}
+        self.assert_(len(p1.children) == 0)
     
 
 class SetTest(_CollectionOperations):
@@ -311,6 +319,9 @@ class SetTest(_CollectionOperations):
         p1 = self.roundtrip(p1)
         self.assert_(p1.children == set(['c']))
 
+        p1._children = []
+        self.assert_(len(p1.children) == 0)
+
     def test_set_comparisons(self):
         Parent, Child = self.Parent, self.Child