From c8e8d77fdc0503bd1ac48ba35921745ff61f4cc3 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 22 Feb 2010 20:37:44 +0000 Subject: [PATCH] - added util.portable_instancemethod to provide a quick way to make an instancemethod "serializable" - SchemaType and subclasses Boolean, Enum are now serializable, including their ddl listener and other event callables. [ticket:1694] [ticket:1698] - AddConstraint/DropConstraint use the wrapper for _create_rule - added test coverage for AddConstraint override of _create_rule --- CHANGES | 4 +++ lib/sqlalchemy/schema.py | 17 ++++++++++-- lib/sqlalchemy/types.py | 53 +++++++++++++++++++++++------------- lib/sqlalchemy/util.py | 12 ++++++++ test/sql/test_constraints.py | 30 ++++++++++++++++++-- test/sql/test_types.py | 33 ++++++++++++++++++++-- 6 files changed, 123 insertions(+), 26 deletions(-) diff --git a/CHANGES b/CHANGES index 41761c207f..986ba6ba4f 100644 --- a/CHANGES +++ b/CHANGES @@ -78,6 +78,10 @@ CHANGES encouraged to use those functions whenever they correspond to their needs instead of implementing custom ones. + - SchemaType and subclasses Boolean, Enum are now serializable, + including their ddl listener and other event callables. + [ticket:1694] [ticket:1698] + - Added math negation operator support, -x. - FunctionElement subclasses are now directly executable the diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index d51ae3a458..c68d1f1354 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -1426,7 +1426,8 @@ class CheckConstraint(Constraint): Can be included in the definition of a Table or Column. """ - def __init__(self, sqltext, name=None, deferrable=None, initially=None, table=None, _create_rule=None): + def __init__(self, sqltext, name=None, deferrable=None, + initially=None, table=None, _create_rule=None): """Construct a CHECK constraint. sqltext @@ -2291,6 +2292,16 @@ class _CreateDropBase(DDLElement): self.on = on self.bind = bind + def _create_rule_disable(self, compiler): + """Allow disable of _create_rule using a callable. + + Pass to _create_rule using + util.portable_instancemethod(self._create_rule_disable) + to retain serializability. + + """ + return False + class CreateTable(_CreateDropBase): """Represent a CREATE TABLE statement.""" @@ -2332,7 +2343,7 @@ class AddConstraint(_CreateDropBase): def __init__(self, element, *args, **kw): super(AddConstraint, self).__init__(element, *args, **kw) - element._create_rule = lambda compiler: False + element._create_rule = util.portable_instancemethod(self._create_rule_disable) class DropConstraint(_CreateDropBase): """Represent an ALTER TABLE DROP CONSTRAINT statement.""" @@ -2342,7 +2353,7 @@ class DropConstraint(_CreateDropBase): def __init__(self, element, cascade=False, **kw): self.cascade = cascade super(DropConstraint, self).__init__(element, **kw) - element._create_rule = lambda compiler: False + element._create_rule = util.portable_instancemethod(self._create_rule_disable) def _bind_or_error(schemaitem, msg=None): bind = schemaitem.bind diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index a52ffe59c6..356edecd85 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -1038,22 +1038,36 @@ class SchemaType(object): self.schema = kw.pop('schema', None) self.metadata = kw.pop('metadata', None) if self.metadata: - self.metadata.append_ddl_listener('before-create', - self._on_metadata_create) - self.metadata.append_ddl_listener('after-drop', - self._on_metadata_drop) + self.metadata.append_ddl_listener( + 'before-create', + util.portable_instancemethod(self._on_metadata_create) + ) + self.metadata.append_ddl_listener( + 'after-drop', + util.portable_instancemethod(self._on_metadata_drop) + ) def _set_parent(self, column): - column._on_table_attach(self._set_table) + column._on_table_attach(util.portable_instancemethod(self._set_table)) def _set_table(self, table, column): - table.append_ddl_listener('before-create', self._on_table_create) - table.append_ddl_listener('after-drop', self._on_table_drop) + table.append_ddl_listener( + 'before-create', + util.portable_instancemethod(self._on_table_create) + ) + table.append_ddl_listener( + 'after-drop', + util.portable_instancemethod(self._on_table_drop) + ) if self.metadata is None: - table.metadata.append_ddl_listener('before-create', - self._on_metadata_create) - table.metadata.append_ddl_listener('after-drop', - self._on_metadata_drop) + table.metadata.append_ddl_listener( + 'before-create', + util.portable_instancemethod(self._on_metadata_create) + ) + table.metadata.append_ddl_listener( + 'after-drop', + util.portable_instancemethod(self._on_metadata_drop) + ) @property def bind(self): @@ -1178,19 +1192,20 @@ class Enum(String, SchemaType): assert_unicode=assert_unicode ) SchemaType.__init__(self, **kw) + + def _should_create_constraint(self, compiler): + return not self.native_enum or \ + not compiler.dialect.supports_native_enum def _set_table(self, table, column): if self.native_enum: SchemaType._set_table(self, table, column) - def should_create_constraint(compiler): - return not self.native_enum or \ - not compiler.dialect.supports_native_enum e = schema.CheckConstraint( column.in_(self.enums), name=self.name, - _create_rule=should_create_constraint + _create_rule=util.portable_instancemethod(self._should_create_constraint) ) table.append_constraint(e) @@ -1314,18 +1329,18 @@ class Boolean(TypeEngine, SchemaType): """ self.create_constraint = create_constraint self.name = name + + def _should_create_constraint(self, compiler): + return not compiler.dialect.supports_native_boolean def _set_table(self, table, column): if not self.create_constraint: return - def should_create_constraint(compiler): - return not compiler.dialect.supports_native_boolean - e = schema.CheckConstraint( column.in_([0, 1]), name=self.name, - _create_rule=should_create_constraint + _create_rule=util.portable_instancemethod(self._should_create_constraint) ) table.append_constraint(e) diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index 07b8269a91..873243de16 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -453,6 +453,18 @@ def unbound_method_to_callable(func_or_cls): else: return func_or_cls +class portable_instancemethod(object): + """Turn an instancemethod into a (parent, name) pair + to produce a serializable callable. + + """ + def __init__(self, meth): + self.target = meth.im_self + self.name = meth.__name__ + + def __call__(self, *arg, **kw): + return getattr(self.target, self.name)(*arg, **kw) + def class_hierarchy(cls): """Return an unordered sequence of all classes related to cls. diff --git a/test/sql/test_constraints.py b/test/sql/test_constraints.py index 55dcd3484e..97f5190d4c 100644 --- a/test/sql/test_constraints.py +++ b/test/sql/test_constraints.py @@ -316,10 +316,36 @@ class ConstraintCompilationTest(TestBase, AssertsCompiledSQL): Column('b', Integer) ) - constraint = CheckConstraint('a < b',name="my_test_constraint", deferrable=True,initially='DEFERRED', table=t) + constraint = CheckConstraint('a < b',name="my_test_constraint", + deferrable=True,initially='DEFERRED', table=t) + + + # before we create an AddConstraint, + # the CONSTRAINT comes out inline + self.assert_compile( + schema.CreateTable(t), + "CREATE TABLE tbl (" + "a INTEGER, " + "b INTEGER, " + "CONSTRAINT my_test_constraint CHECK (a < b) DEFERRABLE INITIALLY DEFERRED" + ")" + ) + self.assert_compile( schema.AddConstraint(constraint), - "ALTER TABLE tbl ADD CONSTRAINT my_test_constraint CHECK (a < b) DEFERRABLE INITIALLY DEFERRED" + "ALTER TABLE tbl ADD CONSTRAINT my_test_constraint " + "CHECK (a < b) DEFERRABLE INITIALLY DEFERRED" + ) + + # once we make an AddConstraint, + # inline compilation of the CONSTRAINT + # is disabled + self.assert_compile( + schema.CreateTable(t), + "CREATE TABLE tbl (" + "a INTEGER, " + "b INTEGER" + ")" ) self.assert_compile( diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 77fdcf6e68..cd1430f4cf 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -10,6 +10,7 @@ import sqlalchemy.engine.url as url from sqlalchemy.databases import * from sqlalchemy.test.schema import Table, Column from sqlalchemy.test import * +from sqlalchemy.test.util import picklers class AdaptTest(TestBase): @@ -77,8 +78,36 @@ class TypeAffinityTest(TestBase): (PickleType(), PickleType(), True), ]: eq_(t1._compare_type_affinity(t2), comp, "%s %s" % (t1, t2)) - - + +class PickleMetadataTest(TestBase): + def testmeta(self): + for loads, dumps in picklers(): + column_types = [ + Column('Boo', Boolean()), + Column('Str', String()), + Column('Tex', Text()), + Column('Uni', Unicode()), + Column('Int', Integer()), + Column('Sma', SmallInteger()), + Column('Big', BigInteger()), + Column('Num', Numeric()), + Column('Flo', Float()), + Column('Dat', DateTime()), + Column('Dat', Date()), + Column('Tim', Time()), + Column('Lar', LargeBinary()), + Column('Pic', PickleType()), + Column('Int', Interval()), + Column('Enu', Enum('x','y','z', name="somename")), + ] + for column_type in column_types: + #print column_type + meta = MetaData() + Table('foo', meta, column_type) + ct = loads(dumps(column_type)) + mt = loads(dumps(meta)) + + class UserDefinedTest(TestBase): """tests user-defined types.""" -- 2.47.3