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(','))
-
- 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
+ _add_w_all_cascades = all_cascades.difference([
+ 'all', 'none', 'delete-orphan'])
+ _allowed_cascades = all_cascades
+
+ 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."""
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