From: Mike Bayer Date: Wed, 29 Feb 2012 23:27:17 +0000 (-0500) Subject: - [bug] Fixed the repr() for CascadeOptions to X-Git-Tag: rel_0_7_6~28 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0987f8951637feb48dfbc22508aa46fe59a12f63;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - [bug] Fixed the repr() for CascadeOptions to include refresh-expire. Also reworked CascadeOptions to be a . [ticket:2417] --- diff --git a/CHANGES b/CHANGES index 418fc6b23c..4d8adcd87d 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 . + [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 6ac03d95a9..0c5f203a72 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 8dd55d14e9..26ea78da1c 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