Commits

Mike Bayer committed d498caa

- Fixed bugs in sqlalchemy.ext.mutable extension where
`None` was not appropriately handled, replacement
events were not appropriately handled.
[ticket:2143]

Comments (0)

Files changed (3)

     "generic_associations".   Each presents an alternative
     table layout.
 
+- ext
+  - Fixed bugs in sqlalchemy.ext.mutable extension where
+    `None` was not appropriately handled, replacement
+    events were not appropriately handled.
+    [ticket:2143]
+
 0.7.0b4
 =======
 - general

lib/sqlalchemy/ext/mutable.py

             outgoing.
 
             """
-
             if not isinstance(value, cls):
-                value = cls.coerce(key, value) 
-            value._parents[target.obj()] = key
+                value = cls.coerce(key, value)
+            if value is not None:
+                value._parents[target.obj()] = key
             if isinstance(oldvalue, cls):
-                oldvalue._parents.pop(state.obj(), None)
+                oldvalue._parents.pop(target.obj(), None)
             return value
 
         def pickle(state, state_dict):
         """
         if value is None:
             return None
-        raise ValueError("Attribute '%s' accepts objects of type %s" % (key, cls))
+        raise ValueError("Attribute '%s' does not accept objects of type %s" % (key, type(value)))
 
     @classmethod
     def associate_with_attribute(cls, attribute):

test/ext/test_mutable.py

 from sqlalchemy.orm.mapper import Mapper
 from sqlalchemy.orm.instrumentation import ClassManager
 from test.lib.schema import Table, Column
-from test.lib.testing import eq_
+from test.lib.testing import eq_, assert_raises_message
 from test.lib.util import picklers
 from test.lib import testing
 from test.lib import fixtures
         ClassManager.dispatch._clear()
         super(_MutableDictTestBase, self).teardown()
 
+    def test_coerce_none(self):
+        sess = Session()
+        f1 = Foo(data=None)
+        sess.add(f1)
+        sess.commit()
+        eq_(f1.data, None)
+
+    def test_coerce_raise(self):
+        assert_raises_message(
+            ValueError,
+            "Attribute 'data' does not accept objects of "
+            "type <type 'set'>",
+            Foo, data=set([1,2,3])
+        )
+
     def test_in_place_mutation(self):
         sess = Session()
 
 
         eq_(f1.data, {'a':'c'})
 
+    def test_replace(self):
+        sess = Session()
+        f1 = Foo(data={'a':'b'})
+        sess.add(f1)
+        sess.flush()
+
+        f1.data = {'b':'c'}
+        sess.commit()
+        eq_(f1.data, {'b':'c'})
+
     def test_pickle_parent(self):
         sess = Session()
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.