summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-02-29 18:27:17 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2012-02-29 18:27:17 -0500
commit0987f8951637feb48dfbc22508aa46fe59a12f63 (patch)
treecae3cd06b2fc598e8842bfff7e26e705a0e94545
parent0536c48dafb670d34fc96d26078b41ed6accf01f (diff)
downloadsqlalchemy-0987f8951637feb48dfbc22508aa46fe59a12f63.tar.gz
- [bug] Fixed the repr() for CascadeOptions to
include refresh-expire. Also reworked CascadeOptions to be a <frozenset>. [ticket:2417]
-rw-r--r--CHANGES5
-rw-r--r--lib/sqlalchemy/orm/util.py65
-rw-r--r--test/orm/test_cascade.py17
3 files changed, 59 insertions, 28 deletions
diff --git a/CHANGES b/CHANGES
index 418fc6b23..4d8adcd87 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,11 @@ CHANGES
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).
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 6ac03d95a..0c5f203a7 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -11,6 +11,7 @@ from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\
PropComparator, MapperProperty
from sqlalchemy.orm import attributes, exc
import operator
+import re
mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
@@ -20,38 +21,52 @@ all_cascades = frozenset(("delete", "delete-orphan", "all", "merge",
_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."""
diff --git a/test/orm/test_cascade.py b/test/orm/test_cascade.py
index 8dd55d14e..26ea78da1 100644
--- a/test/orm/test_cascade.py
+++ b/test/orm/test_cascade.py
@@ -4,7 +4,7 @@ from sqlalchemy import Integer, String, ForeignKey, Sequence, \
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_
@@ -51,10 +51,21 @@ class CascadeArgTest(fixtures.MappedTest):
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