Commits

Mike Bayer committed 5b4511a

- [bug] Fixed the repr() for CascadeOptions to
include refresh-expire. Also reworked
CascadeOptions to be a <frozenset>.
[ticket:2417]

Comments (0)

Files changed (3)

     derived function in the columns clause.  
     [ticket:2419]
 
+  - [bug] Fixed the repr() for CascadeOptions to
+    include refresh-expire.  Also reworked
+    CascadeOptions to be a <frozenset>.
+    [ticket:2417]
+
   - [feature] Added the ability to query for
     Table-bound column names when using 
     query(sometable).filter_by(colname=value).  

lib/sqlalchemy/orm/util.py

                                 PropComparator, MapperProperty
 from sqlalchemy.orm import attributes, exc
 import operator
+import re
 
 mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
 
 
 _INSTRUMENTOR = ('mapper', 'instrumentor')
 
-class CascadeOptions(dict):
+class CascadeOptions(frozenset):
     """Keeps track of the options sent to relationship().cascade"""
 
-    def __init__(self, arg=""):
-        if not arg:
-            values = set()
-        else:
-            values = set(c.strip() for c in arg.split(','))
+    _add_w_all_cascades = all_cascades.difference([
+                            'all', 'none', 'delete-orphan'])
+    _allowed_cascades = all_cascades
 
-        for name in ['save-update', 'delete', 'refresh-expire', 
-                            'merge', 'expunge']:
-            boolean = name in values or 'all' in values
-            setattr(self, name.replace('-', '_'), boolean)
-            if boolean:
-                self[name] = True
+    def __new__(cls, arg):
+        values = set([
+                    c for c 
+                    in re.split('\s*,\s*', arg or "")
+                    if c
+                ])
+
+        if values.difference(cls._allowed_cascades):
+            raise sa_exc.ArgumentError(
+                    "Invalid cascade option(s): %s" % 
+                    ", ".join([repr(x) for x in 
+                        sorted(
+                            values.difference(cls._allowed_cascades)
+                    )])
+            )
+
+        if "all" in values:
+            values.update(cls._add_w_all_cascades)
+        if "none" in values:
+            values.clear()
+        values.discard('all')
+
+        self = frozenset.__new__(CascadeOptions, values)
+        self.save_update = 'save-update' in values
+        self.delete = 'delete' in values
+        self.refresh_expire = 'refresh-expire' in values
+        self.merge = 'merge' in values
+        self.expunge = 'expunge' in values
         self.delete_orphan = "delete-orphan" in values
-        if self.delete_orphan:
-            self['delete-orphan'] = True
 
         if self.delete_orphan and not self.delete:
-            util.warn("The 'delete-orphan' cascade option requires "
-                        "'delete'.")
-
-        for x in values:
-            if x not in all_cascades:
-                raise sa_exc.ArgumentError("Invalid cascade option '%s'" % x)
+            util.warn("The 'delete-orphan' cascade "
+                        "option requires 'delete'.")
+        return self
 
     def __repr__(self):
-        return "CascadeOptions(%s)" % repr(",".join(
-            [x for x in ['delete', 'save_update', 'merge', 'expunge',
-                         'delete_orphan', 'refresh-expire']
-             if getattr(self, x, False) is True]))
+        return "CascadeOptions(%r)" % (
+            ",".join([x for x in sorted(self)])
+        )
 
 def _validator_events(desc, key, validator):
     """Runs a validation method on an attribute value to be set or appended."""

test/orm/test_cascade.py

     exc as sa_exc
 from test.lib.schema import Table, Column
 from sqlalchemy.orm import mapper, relationship, create_session, \
-    sessionmaker, class_mapper, backref, Session
+    sessionmaker, class_mapper, backref, Session, util as orm_util
 from sqlalchemy.orm import attributes, exc as orm_exc
 from test.lib import testing
 from test.lib.testing import eq_
         mapper(Address, addresses)
         assert_raises_message(
             sa_exc.ArgumentError,
-            "Invalid cascade option 'fake'",
-            relationship, Address, cascade="fake, all, delete-orphan"
+            r"Invalid cascade option\(s\): 'fake', 'fake2'",
+            relationship, Address, cascade="fake, all, delete-orphan, fake2"
         )
 
+    def test_cascade_repr(self):
+        eq_(
+            repr(orm_util.CascadeOptions("all, delete-orphan")),
+            "CascadeOptions('delete,delete-orphan,expunge,"
+                    "merge,refresh-expire,save-update')"
+        )
+
+    def test_cascade_immutable(self):
+        assert isinstance(
+            orm_util.CascadeOptions("all, delete-orphan"), 
+            frozenset)
 
 class O2MCascadeDeleteOrphanTest(fixtures.MappedTest):
     run_inserts = None