]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add explicit copy() to Enum
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 17 Oct 2016 04:22:38 +0000 (00:22 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 17 Oct 2016 16:13:01 +0000 (12:13 -0400)
The Boolean and Enum types both place SchemaType second in the
inheritance hierarchy.  In the case of Enum, this works
out that the copy() method is called from the base TypeEngine
which fails to transfer _create_events.   The test suite
doesn't seem to work with the inhertance hierarchy set up like
this as the event listeners don't work out, the _on_metadata_create
and _on_table_create hooks cause the production of an adapted type
which then adds event listeners that cause deque changed while
iteration.  It's not clear why Enum /Boolean don't have this problem.
But in any case it seems like the class mechanics for these types
remains fragile and would benefit from yet another refactor someday.

Change-Id: Ib641a5d2321b00f58bbe98dd0c5e789374db32b2
Fixes: #3827
doc/build/changelog/changelog_11.rst
lib/sqlalchemy/sql/sqltypes.py
test/sql/test_metadata.py

index 45d98aa223efd053e4963179f31b0831a1c6534c..11d49d7a7f980aa5c8f4892a24a3e5d61a7362df 100644 (file)
         paths, the "present" column loader will now override the deferred non-
         load for that entity regardless of row ordering.
 
+    .. change::
+        :tags: bug, sql, postgresql
+        :tickets: 3827
+
+        Fixed regression in :class:`.Enum` type where event handlers were not
+        transferred in the case of the type object being copied, due to a
+        conflicting copy() method added as part of [ticket:3250].  This copy
+        occurs normally in situations when the column is copied, such as
+        in tometadata() or when using declarative mixins with columns.  The
+        event handler not being present would impact the constraint being
+        created for a non-native enumerated type, but more critically the
+        ENUM object on the PostgreSQL backend.
+
 .. changelog::
     :version: 1.1.1
     :released: October 7, 2016
index 78547ccf074a555aec237234eca05d9df182ec78..118c26070acbe169f57c0c65bf62fa8ad0da7906 100644 (file)
@@ -1330,6 +1330,9 @@ class Enum(String, SchemaType):
         )
         assert e.table is table
 
+    def copy(self, **kw):
+        return SchemaType.copy(self, **kw)
+
     def adapt(self, impltype, **kw):
         schema = kw.pop('schema', self.schema)
         metadata = kw.pop('metadata', self.metadata)
index af291b8427d192ff1612b6a6d34dd88f6ff7d936..f2df4da063925a15f8b46ea769a49f5d120eff7f 100644 (file)
@@ -1,7 +1,6 @@
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import emits_warning
-
 import pickle
 from sqlalchemy import Integer, String, UniqueConstraint, \
     CheckConstraint, ForeignKey, MetaData, Sequence, \
@@ -16,7 +15,7 @@ import sqlalchemy as tsa
 from sqlalchemy.testing import fixtures
 from sqlalchemy import testing
 from sqlalchemy.testing import ComparesTables, AssertsCompiledSQL
-from sqlalchemy.testing import eq_, is_, mock
+from sqlalchemy.testing import eq_, is_, mock, is_true
 from contextlib import contextmanager
 from sqlalchemy import util
 
@@ -1534,26 +1533,39 @@ class PKAutoIncrementTest(fixtures.TestBase):
 
 class SchemaTypeTest(fixtures.TestBase):
 
-    class MyType(sqltypes.SchemaType, sqltypes.TypeEngine):
+    class TrackEvents(object):
         column = None
         table = None
         evt_targets = ()
 
         def _set_table(self, column, table):
-            super(SchemaTypeTest.MyType, self)._set_table(column, table)
+            super(SchemaTypeTest.TrackEvents, self)._set_table(column, table)
             self.column = column
             self.table = table
 
         def _on_table_create(self, target, bind, **kw):
-            super(SchemaTypeTest.MyType, self)._on_table_create(
+            super(SchemaTypeTest.TrackEvents, self)._on_table_create(
                 target, bind, **kw)
             self.evt_targets += (target,)
 
         def _on_metadata_create(self, target, bind, **kw):
-            super(SchemaTypeTest.MyType, self)._on_metadata_create(
+            super(SchemaTypeTest.TrackEvents, self)._on_metadata_create(
                 target, bind, **kw)
             self.evt_targets += (target,)
 
+    # TODO: Enum and Boolean put TypeEngine first.  Changing that here
+    # causes collection-mutate-while-iterated errors in the event system
+    # since the hooks here call upon the adapted type.  Need to figure out
+    # why Enum and Boolean don't have this problem.
+    class MyType(TrackEvents, sqltypes.SchemaType, sqltypes.TypeEngine):
+        pass
+
+    class WrapEnum(TrackEvents, Enum):
+        pass
+
+    class WrapBoolean(TrackEvents, Boolean):
+        pass
+
     class MyTypeWImpl(MyType):
 
         def _gen_dialect_impl(self, dialect):
@@ -1656,6 +1668,29 @@ class SchemaTypeTest(fixtures.TestBase):
         eq_(t1.c.y.type.evt_targets, (t1,))
         eq_(t2.c.y.type.evt_targets, (t2, t2))
 
+    def test_enum_column_copy_transfers_events(self):
+        m = MetaData()
+
+        type_ = self.WrapEnum('a', 'b', 'c', name='foo')
+        y = Column('y', type_)
+        y_copy = y.copy()
+        t1 = Table('x', m, y_copy)
+
+        is_true(y_copy.type._create_events)
+
+        m.dispatch.before_create(t1, testing.db)
+        eq_(t1.c.y.type.evt_targets, (t1, ))
+
+    def test_boolean_column_copy_transfers_events(self):
+        m = MetaData()
+
+        type_ = self.WrapBoolean()
+        y = Column('y', type_)
+        y_copy = y.copy()
+        t1 = Table('x', m, y_copy)
+
+        is_true(y_copy.type._create_events)
+
     def test_metadata_dispatch_no_new_impl(self):
         m1 = MetaData()
         typ = self.MyType(metadata=m1)