]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add changelog and migration notes for new Enum features,
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 2 Feb 2016 18:00:19 +0000 (13:00 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 2 Feb 2016 20:04:46 +0000 (15:04 -0500)
fixes #3095, #3292
- reorganize enum constructor to again work with the MySQL
ENUM type
- add a new create_constraint flag to Enum to complement that of
Boolean
- reinstate the CHECK constraint tests for enum, these already
fail /skip against the MySQL backend
- simplify lookup rules in Enum, have them apply to all varieties
of Enum equally

doc/build/changelog/changelog_11.rst
doc/build/changelog/migration_11.rst
lib/sqlalchemy/dialects/mysql/enumerated.py
lib/sqlalchemy/sql/sqltypes.py
test/dialect/mysql/test_types.py
test/sql/test_types.py

index 37cb773d8e833af3b890b323db824aedd8b5b823..5c3ad7163ca9ea09f1f9d7f73a725e7deec81db7 100644 (file)
 .. changelog::
     :version: 1.1.0b1
 
+    .. change::
+        :tags: feature, sql
+        :tickets: 3292, 3095
+
+        Added support for PEP-435-style enumerated classes, namely
+        Python 3's ``enum.Enum`` class but also including compatible
+        enumeration libraries, to the :class:`.types.Enum` datatype.
+        The :class:`.types.Enum` datatype now also performs in-Python validation
+        of incoming values, and adds an option to forego creating the
+        CHECK constraint :paramref:`.Enum.create_constraint`.
+        Pull request courtesy Alex Grönholm.
+
+        .. seealso::
+
+            :ref:`change_3292`
+
+            :ref:`change_3095`
+
     .. change::
         :tags: change, postgresql
 
index 5f17b4e7e03791d0ce4089025472e453b1698863..07223be34fc1ec2223799f3bdf05eb50420c6d64 100644 (file)
@@ -702,6 +702,80 @@ used for the fetch.
 
 :ticket:`3501`
 
+.. _change_3292:
+
+Support for Python's native ``enum`` type and compatible forms
+---------------------------------------------------------------
+
+The :class:`.Enum` type can now be constructed using any
+PEP-435 compliant enumerated type.   When using this mode, input values
+and return values are the actual enumerated objects, not the
+string values::
+
+    import enum
+    from sqlalchemy import Table, MetaData, Column, Enum, create_engine
+
+
+    class MyEnum(enum.Enum):
+        one = "one"
+        two = "two"
+        three = "three"
+
+
+    t = Table(
+        'data', MetaData(),
+        Column('value', Enum(MyEnum))
+    )
+
+    e = create_engine("sqlite://")
+    t.create(e)
+
+    e.execute(t.insert(), {"value": MyEnum.two})
+    assert e.scalar(t.select()) is MyEnum.two
+
+
+:ticket:`3292`
+
+.. _change_3095:
+
+The ``Enum`` type now does in-Python validation of values
+---------------------------------------------------------
+
+To accomodate for Python native enumerated objects, as well as for edge
+cases such as that of where a non-native ENUM type is used within an ARRAY
+and a CHECK contraint is infeasible, the :class:`.Enum` datatype now adds
+in-Python validation of input values::
+
+
+    >>> from sqlalchemy import Table, MetaData, Column, Enum, create_engine
+    >>> t = Table(
+    ...     'data', MetaData(),
+    ...     Column('value', Enum("one", "two", "three"))
+    ... )
+    >>> e = create_engine("sqlite://")
+    >>> t.create(e)
+    >>> e.execute(t.insert(), {"value": "four"})
+    Traceback (most recent call last):
+      ...
+    sqlalchemy.exc.StatementError: (exceptions.LookupError)
+    "four" is not among the defined enum values
+    [SQL: u'INSERT INTO data (value) VALUES (?)']
+    [parameters: [{'value': 'four'}]]
+
+For simplicity and consistency, this validation is now turned on in all cases,
+whether or not the enumerated type uses a database-native form, whether
+or not the CHECK constraint is in use, as well as whether or not a
+PEP-435 enumerated type or plain list of string values is used.  The
+check also occurs on the result-handling side as well, when values coming
+from the database are returned.
+
+This validation is in addition to the existing behavior of creating a
+CHECK constraint when a non-native enumerated type is used.  The creation of
+this CHECK constraint can now be disabled using the new
+:paramref:`.Enum.create_constraint` flag.
+
+:ticket:`3095`
+
 .. _change_2528:
 
 A UNION or similar of SELECTs with LIMIT/OFFSET/ORDER BY now parenthesizes the embedded selects
index 5c4dc61f0821f09646d7e90f8ef3ae74cde2992e..567e95288c811286ac4f6eabfac1ea775e9338fb 100644 (file)
@@ -69,13 +69,16 @@ class ENUM(sqltypes.Enum, _EnumeratedValues):
 
         :param enums: The range of valid values for this ENUM.  Values will be
           quoted when generating the schema according to the quoting flag (see
-          below).
+          below).  This object may also be a PEP-435-compliant enumerated
+          type.
 
-        :param strict: Defaults to False: ensure that a given value is in this
-          ENUM's range of permissible values when inserting or updating rows.
-          Note that MySQL will not raise a fatal error if you attempt to store
-          an out of range value- an alternate value will be stored instead.
-          (See MySQL ENUM documentation.)
+          .. versionadded: 1.1 added support for PEP-435-compliant enumerated
+             types.
+
+        :param strict: This flag has no effect.
+
+         .. versionchanged:: The MySQL ENUM type as well as the base Enum
+            type now validates all Python data values.
 
         :param charset: Optional, a column-level character set for this string
           value.  Takes precedence to 'ascii' or 'unicode' short-hand.
@@ -109,8 +112,9 @@ class ENUM(sqltypes.Enum, _EnumeratedValues):
           literals for you.  This is a transitional option.
 
         """
-        values, length = self._init_values(enums, kw)
-        self.strict = kw.pop('strict', False)
+
+        kw.pop('strict', None)
+        sqltypes.Enum.__init__(self, *enums)
         kw.pop('metadata', None)
         kw.pop('schema', None)
         kw.pop('name', None)
@@ -118,29 +122,17 @@ class ENUM(sqltypes.Enum, _EnumeratedValues):
         kw.pop('native_enum', None)
         kw.pop('inherit_schema', None)
         kw.pop('_create_events', None)
-        _StringType.__init__(self, length=length, **kw)
-        sqltypes.Enum.__init__(self, *values)
+        _StringType.__init__(self, length=self.length, **kw)
+
+    def _setup_for_values(self, values, objects, kw):
+        values, length = self._init_values(values, kw)
+        return sqltypes.Enum._setup_for_values(self, values, objects, kw)
 
     def __repr__(self):
         return util.generic_repr(
             self, to_inspect=[ENUM, _StringType, sqltypes.Enum])
 
-    def bind_processor(self, dialect):
-        super_convert = super(ENUM, self).bind_processor(dialect)
-
-        def process(value):
-            if self.strict and value is not None and value not in self.enums:
-                raise exc.InvalidRequestError('"%s" not a valid value for '
-                                              'this enum' % value)
-            if super_convert:
-                return super_convert(value)
-            else:
-                return value
-        return process
-
     def adapt(self, cls, **kw):
-        if issubclass(cls, ENUM):
-            kw['strict'] = self.strict
         return sqltypes.Enum.adapt(self, cls, **kw)
 
 
index 21e57d519b50b81939edce7a828a201a1f0ea939..81630fe4f73c42512bd658b76c9245cdc1337c8c 100644 (file)
@@ -1082,11 +1082,52 @@ class Enum(String, SchemaType):
 
     """Generic Enum Type.
 
-    The Enum type provides a set of possible string values which the
-    column is constrained towards.
+    The :class:`.Enum` type provides a set of possible string values
+    which the column is constrained towards.
+
+    The :class:`.Enum` type will make use of the backend's native "ENUM"
+    type if one is available; otherwise, it uses a VARCHAR datatype and
+    produces a CHECK constraint.  Use of the backend-native enum type
+    can be disabled using the :paramref:`.Enum.native_enum` flag, and
+    the production of the CHECK constraint is configurable using the
+    :paramref:`.Enum.create_constraint` flag.
+
+    The :class:`.Enum` type also provides in-Python validation of both
+    input values and database-returned values.   A ``LookupError`` is raised
+    for any Python value that's not located in the given list of possible
+    values.
+
+    .. versionchanged:: 1.1 the :class:`.Enum` type now provides in-Python
+       validation of input values as well as on data being returned by
+       the database.
+
+    The source of enumerated values may be a list of string values, or
+    alternatively a PEP-435-compliant enumerated class.  For the purposes
+    of the :class:`.Enum` datatype, this class need only provide a
+    ``__members__`` method.
+
+    When using an enumerated class, the enumerated objects are used
+    both for input and output, rather than strings as is the case with
+    a plain-string enumerated type::
+
+        import enum
+        class MyEnum(enum.Enum):
+            one = "one"
+            two = "two"
+            three = "three"
+
+
+        t = Table(
+            'data', MetaData(),
+            Column('value', Enum(MyEnum))
+        )
+
+        connection.execute(t.insert(), {"value": MyEnum.two})
+        assert connection.scalar(t.select()) is MyEnum.two
+
+    .. versionadded:: 1.1 - support for PEP-435-style enumerated
+       classes.
 
-    By default, uses the backend's native ENUM type if available,
-    else uses VARCHAR + a CHECK constraint.
 
     .. seealso::
 
@@ -1103,14 +1144,25 @@ class Enum(String, SchemaType):
         Keyword arguments which don't apply to a specific backend are ignored
         by that backend.
 
-        :param \*enums: either exactly one PEP 435 compliant enumerated type
+        :param \*enums: either exactly one PEP-435 compliant enumerated type
            or one or more string or unicode enumeration labels. If unicode
            labels are present, the `convert_unicode` flag is auto-enabled.
 
+           .. versionadded:: 1.1 a PEP-435 style enumerated class may be
+              passed.
+
         :param convert_unicode: Enable unicode-aware bind parameter and
            result-set processing for this Enum's data. This is set
            automatically based on the presence of unicode label strings.
 
+        :param create_constraint: defaults to True.  When creating a non-native
+           enumerated type, also build a CHECK constraint on the database
+           against the valid values.
+
+           .. versionadded:: 1.1 - added :paramref:`.Enum.create_constraint`
+              which provides the option to disable the production of the
+              CHECK constraint for a non-native enumerated type.
+
         :param metadata: Associate this type directly with a ``MetaData``
            object. For types that exist on the target database as an
            independent schema construct (Postgresql), this type will be
@@ -1125,7 +1177,7 @@ class Enum(String, SchemaType):
         :param name: The name of this type. This is required for Postgresql
            and any future supported database which requires an explicitly
            named type, or an explicitly named constraint in order to generate
-           the type and/or a table that uses it. If an :class:`~enum.Enum`
+           the type and/or a table that uses it. If a PEP-435 enumerated
            class was used, its name (converted to lower case) is used by
            default.
 
@@ -1153,21 +1205,14 @@ class Enum(String, SchemaType):
            ``schema`` attribute.   This also takes effect when using the
            :meth:`.Table.tometadata` operation.
 
-           .. versionadded:: 0.8
-
         """
-        if len(enums) == 1 and hasattr(enums[0], '__members__'):
-            self.enums = list(enums[0].__members__)
-            self.enum_class = enums[0]
-            kw.setdefault('name', enums[0].__name__.lower())
-            self.key_lookup = dict((value, key) for key, value in enums[0].__members__.items())
-            self.value_lookup = enums[0].__members__.copy()
-        else:
-            self.enums = enums
-            self.enum_class = self.key_lookup = self.value_lookup = None
+
+        values, objects = self._parse_into_values(enums, kw)
+        self._setup_for_values(values, objects, kw)
 
         self.native_enum = kw.pop('native_enum', True)
         convert_unicode = kw.pop('convert_unicode', None)
+        self.create_constraint = kw.pop('create_constraint', True)
         if convert_unicode is None:
             for e in self.enums:
                 if isinstance(e, util.text_type):
@@ -1180,12 +1225,53 @@ class Enum(String, SchemaType):
             length = max(len(x) for x in self.enums)
         else:
             length = 0
+        self._valid_lookup[None] = self._object_lookup[None] = None
+
         String.__init__(self,
                         length=length,
                         convert_unicode=convert_unicode,
                         )
         SchemaType.__init__(self, **kw)
 
+    def _parse_into_values(self, enums, kw):
+        if len(enums) == 1 and hasattr(enums[0], '__members__'):
+            self.enum_class = enums[0]
+            values = list(self.enum_class.__members__)
+            objects = [self.enum_class.__members__[k] for k in values]
+            kw.setdefault('name', self.enum_class.__name__.lower())
+
+            return values, objects
+        else:
+            self.enum_class = None
+            return enums, enums
+
+    def _setup_for_values(self, values, objects, kw):
+        self.enums = list(values)
+
+        self._valid_lookup = dict(
+            zip(objects, values)
+        )
+        self._object_lookup = dict(
+            (value, key) for key, value in self._valid_lookup.items()
+        )
+        self._valid_lookup.update(
+            [(value, value) for value in self._valid_lookup.values()]
+        )
+
+    def _db_value_for_elem(self, elem):
+        try:
+            return self._valid_lookup[elem]
+        except KeyError:
+            raise LookupError(
+                '"%s" is not among the defined enum values' % elem)
+
+    def _object_value_for_elem(self, elem):
+        try:
+            return self._object_lookup[elem]
+        except KeyError:
+            raise LookupError(
+                '"%s" is not among the defined enum values' % elem)
+
     def __repr__(self):
         return util.generic_repr(self,
                                  additional_kw=[('native_enum', True)],
@@ -1201,6 +1287,9 @@ class Enum(String, SchemaType):
         if self.native_enum:
             SchemaType._set_table(self, column, table)
 
+        if not self.create_constraint:
+            return
+
         e = schema.CheckConstraint(
             type_coerce(column, self).in_(self.enums),
             name=_defer_name(self.name),
@@ -1215,7 +1304,10 @@ class Enum(String, SchemaType):
         metadata = kw.pop('metadata', self.metadata)
         _create_events = kw.pop('_create_events', False)
         if issubclass(impltype, Enum):
-            args = [self.enum_class] if self.enum_class is not None else self.enums
+            if self.enum_class is not None:
+                args = [self.enum_class]
+            else:
+                args = self.enums
             return impltype(name=self.name,
                             schema=schema,
                             metadata=metadata,
@@ -1231,26 +1323,17 @@ class Enum(String, SchemaType):
 
     def literal_processor(self, dialect):
         parent_processor = super(Enum, self).literal_processor(dialect)
-        if self.key_lookup:
-            def process(value):
-                value = self.key_lookup.get(value, value)
-                if parent_processor:
-                    return parent_processor(value)
 
-            return process
-        else:
-            return parent_processor
+        def process(value):
+            value = self._db_value_for_elem(value)
+            if parent_processor:
+                value = parent_processor(value)
+            return value
+        return process
 
     def bind_processor(self, dialect):
         def process(value):
-            if isinstance(value, util.string_types):
-                if value not in self.enums:
-                    raise LookupError(
-                        '"%s" is not among the defined enum values' %
-                        value)
-            elif self.key_lookup and value in self.key_lookup:
-                value = self.key_lookup[value]
-
+            value = self._db_value_for_elem(value)
             if parent_processor:
                 value = parent_processor(value)
             return value
@@ -1259,22 +1342,17 @@ class Enum(String, SchemaType):
         return process
 
     def result_processor(self, dialect, coltype):
-        parent_processor = super(Enum, self).result_processor(dialect,
-                                                              coltype)
-        if self.value_lookup:
-            def process(value):
-                if parent_processor:
-                    value = parent_processor(value)
+        parent_processor = super(Enum, self).result_processor(
+            dialect, coltype)
 
-                try:
-                    return self.value_lookup[value]
-                except KeyError:
-                    raise LookupError('No such member in enum class %s: %s' %
-                                      (self.enum_class.__name__, value))
+        def process(value):
+            if parent_processor:
+                value = parent_processor(value)
 
-            return process
-        else:
-            return parent_processor
+            value = self._object_value_for_elem(value)
+            return value
+
+        return process
 
     @property
     def python_type(self):
@@ -1285,7 +1363,6 @@ class Enum(String, SchemaType):
 
 
 class PickleType(TypeDecorator):
-
     """Holds Python objects, which are serialized using pickle.
 
     PickleType builds upon the Binary type to apply Python's
index 1fb152377a11899f4dcdc964f38713c2f617821e..e570e0db1f531e59f20789ad06b8f67fde4f7772 100644 (file)
@@ -976,12 +976,12 @@ class EnumSetTest(
 
         eq_(
             t2.c.value.type.enums[0:2],
-            (u('réveillé'), u('drôle'))  # u'S’il') # eh ?
+            [u('réveillé'), u('drôle')]  # u'S’il') # eh ?
         )
 
         eq_(
             t2.c.value2.type.enums[0:2],
-            (u('réveillé'), u('drôle'))  # u'S’il') # eh ?
+            [u('réveillé'), u('drôle')]  # u'S’il') # eh ?
         )
 
     def test_enum_compile(self):
@@ -1019,13 +1019,13 @@ class EnumSetTest(
         reflected = Table('mysql_enum', MetaData(testing.db),
                           autoload=True)
         for t in enum_table, reflected:
-            eq_(t.c.e1.type.enums, ("a",))
-            eq_(t.c.e2.type.enums, ("",))
-            eq_(t.c.e3.type.enums, ("a",))
-            eq_(t.c.e4.type.enums, ("",))
-            eq_(t.c.e5.type.enums, ("a", ""))
-            eq_(t.c.e6.type.enums, ("", "a"))
-            eq_(t.c.e7.type.enums, ("", "'a'", "b'b", "'"))
+            eq_(t.c.e1.type.enums, ["a"])
+            eq_(t.c.e2.type.enums, [""])
+            eq_(t.c.e3.type.enums, ["a"])
+            eq_(t.c.e4.type.enums, [""])
+            eq_(t.c.e5.type.enums, ["a", ""])
+            eq_(t.c.e6.type.enums, ["", "a"])
+            eq_(t.c.e7.type.enums, ["", "'a'", "b'b", "'"])
 
     @testing.provide_metadata
     @testing.exclude('mysql', '<', (5,))
index bd62c4fd3b294843626779bbadccedbfee7316bf..3d527b261d6d951780fb6c275156af65ce720a8c 100644 (file)
@@ -31,19 +31,6 @@ from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import mock
 
 
-class SomeEnum(object):
-    # Implements PEP 435 in the minimal fashion needed by SQLAlchemy
-    __members__ = OrderedDict()
-
-    def __init__(self, name, value):
-        self.name = name
-        self.value = value
-        self.__members__[name] = self
-        setattr(SomeEnum, name, self)
-
-SomeEnum('one', 1)
-SomeEnum('two', 2)
-SomeEnum('three', 3)
 
 
 class AdaptTest(fixtures.TestBase):
@@ -198,7 +185,6 @@ class AdaptTest(fixtures.TestBase):
         eq_(types.Unicode().python_type, util.text_type)
         eq_(types.String(convert_unicode=True).python_type, util.text_type)
         eq_(types.Enum('one', 'two', 'three').python_type, str)
-        eq_(types.Enum(SomeEnum).python_type, SomeEnum)
 
         assert_raises(
             NotImplementedError,
@@ -295,8 +281,6 @@ class PickleTypesTest(fixtures.TestBase):
                 Column('Lar', LargeBinary()),
                 Column('Pic', PickleType()),
                 Column('Int', Interval()),
-                Column('Enu', Enum('x', 'y', 'z', name="somename")),
-                Column('En2', Enum(SomeEnum)),
             ]
             for column_type in column_types:
                 meta = MetaData()
@@ -1108,6 +1092,21 @@ class UnicodeTest(fixtures.TestBase):
 
 
 class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
+    __backend__ = True
+
+    class SomeEnum(object):
+        # Implements PEP 435 in the minimal fashion needed by SQLAlchemy
+        __members__ = OrderedDict()
+
+        def __init__(self, name, value):
+            self.name = name
+            self.value = value
+            self.__members__[name] = self
+            setattr(self.__class__, name, self)
+
+    one = SomeEnum('one', 1)
+    two = SomeEnum('two', 2)
+    three = SomeEnum('three', 3)
 
     @classmethod
     def define_tables(cls, metadata):
@@ -1120,14 +1119,92 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
             'non_native_enum_table', metadata,
             Column("id", Integer, primary_key=True),
             Column('someenum', Enum('one', 'two', 'three', native_enum=False)),
+            Column('someotherenum',
+                Enum('one', 'two', 'three',
+                     create_constraint=False, native_enum=False)),
         )
 
         Table(
             'stdlib_enum_table', metadata,
             Column("id", Integer, primary_key=True),
-            Column('someenum', Enum(SomeEnum))
+            Column('someenum', Enum(cls.SomeEnum))
+        )
+
+    def test_python_type(self):
+        eq_(types.Enum(self.SomeEnum).python_type, self.SomeEnum)
+
+    def test_pickle_types(self):
+        global SomeEnum
+        SomeEnum = self.SomeEnum
+        for loads, dumps in picklers():
+            column_types = [
+                Column('Enu', Enum('x', 'y', 'z', name="somename")),
+                Column('En2', Enum(self.SomeEnum)),
+            ]
+            for column_type in column_types:
+                meta = MetaData()
+                Table('foo', meta, column_type)
+                loads(dumps(column_type))
+                loads(dumps(meta))
+
+    def test_validators_pep435(self):
+        type_ = Enum(self.SomeEnum)
+
+        bind_processor = type_.bind_processor(testing.db.dialect)
+        eq_(bind_processor('one'), "one")
+        eq_(bind_processor(self.one), "one")
+        assert_raises_message(
+            LookupError,
+            '"foo" is not among the defined enum values',
+            bind_processor, "foo"
+        )
+
+        result_processor = type_.result_processor(testing.db.dialect, None)
+
+        eq_(result_processor('one'), self.one)
+        assert_raises_message(
+            LookupError,
+            '"foo" is not among the defined enum values',
+            result_processor, "foo"
         )
 
+        literal_processor = type_.literal_processor(testing.db.dialect)
+        eq_(literal_processor("one"), "'one'")
+        assert_raises_message(
+            LookupError,
+            '"foo" is not among the defined enum values',
+            literal_processor, "foo"
+        )
+
+    def test_validators_plain(self):
+        type_ = Enum("one", "two")
+
+        bind_processor = type_.bind_processor(testing.db.dialect)
+        eq_(bind_processor('one'), "one")
+        assert_raises_message(
+            LookupError,
+            '"foo" is not among the defined enum values',
+            bind_processor, "foo"
+        )
+
+        result_processor = type_.result_processor(testing.db.dialect, None)
+
+        eq_(result_processor('one'), "one")
+        assert_raises_message(
+            LookupError,
+            '"foo" is not among the defined enum values',
+            result_processor, "foo"
+        )
+
+        literal_processor = type_.literal_processor(testing.db.dialect)
+        eq_(literal_processor("one"), "'one'")
+        assert_raises_message(
+            LookupError,
+            '"foo" is not among the defined enum values',
+            literal_processor, "foo"
+        )
+
+
     @testing.fails_on(
         'postgresql+zxjdbc',
         'zxjdbc fails on ENUM: column "XXX" is of type XXX '
@@ -1150,6 +1227,48 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
             ]
         )
 
+    def test_null_round_trip(self):
+        enum_table = self.tables.enum_table
+        non_native_enum_table = self.tables.non_native_enum_table
+
+        with testing.db.connect() as conn:
+            conn.execute(enum_table.insert(), {"id": 1, "someenum": None})
+            eq_(conn.scalar(select([enum_table.c.someenum])), None)
+
+        with testing.db.connect() as conn:
+            conn.execute(
+                non_native_enum_table.insert(), {"id": 1, "someenum": None})
+            eq_(conn.scalar(select([non_native_enum_table.c.someenum])), None)
+
+
+    @testing.fails_on(
+        'mysql',
+        "The CHECK clause is parsed but ignored by all storage engines.")
+    @testing.fails_on(
+        'mssql', "FIXME: MS-SQL 2005 doesn't honor CHECK ?!?")
+    def test_check_constraint(self):
+        assert_raises(
+            (exc.IntegrityError, exc.ProgrammingError),
+            testing.db.execute,
+            "insert into non_native_enum_table "
+            "(id, someenum) values(1, 'four')")
+
+    def test_skip_check_constraint(self):
+        with testing.db.connect() as conn:
+            conn.execute(
+                "insert into non_native_enum_table "
+                "(id, someotherenum) values(1, 'four')"
+            )
+            eq_(
+                conn.scalar("select someotherenum from non_native_enum_table"),
+                "four")
+            assert_raises_message(
+                LookupError,
+                '"four" is not among the defined enum values',
+                conn.scalar,
+                select([self.tables.non_native_enum_table.c.someotherenum])
+            )
+
     def test_non_native_round_trip(self):
         non_native_enum_table = self.tables['non_native_enum_table']
 
@@ -1160,7 +1279,9 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
         ])
 
         eq_(
-            non_native_enum_table.select().
+            select([
+                non_native_enum_table.c.id,
+                non_native_enum_table.c.someenum]).
             order_by(non_native_enum_table.c.id).execute().fetchall(),
             [
                 (1, 'two'),
@@ -1169,22 +1290,22 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
             ]
         )
 
-    def test_stdlib_enum_round_trip(self):
+    def test_pep435_enum_round_trip(self):
         stdlib_enum_table = self.tables['stdlib_enum_table']
 
         stdlib_enum_table.insert().execute([
-            {'id': 1, 'someenum': SomeEnum.two},
-            {'id': 2, 'someenum': SomeEnum.two},
-            {'id': 3, 'someenum': SomeEnum.one},
+            {'id': 1, 'someenum': self.SomeEnum.two},
+            {'id': 2, 'someenum': self.SomeEnum.two},
+            {'id': 3, 'someenum': self.SomeEnum.one},
         ])
 
         eq_(
             stdlib_enum_table.select().
             order_by(stdlib_enum_table.c.id).execute().fetchall(),
             [
-                (1, SomeEnum.two),
-                (2, SomeEnum.two),
-                (3, SomeEnum.one),
+                (1, self.SomeEnum.two),
+                (2, self.SomeEnum.two),
+                (3, self.SomeEnum.one),
             ]
         )
 
@@ -1197,7 +1318,7 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
         e1 = Enum('one', 'two', 'three', name='foo', schema='bar')
         eq_(e1.adapt(ENUM).name, 'foo')
         eq_(e1.adapt(ENUM).schema, 'bar')
-        e1 = Enum(SomeEnum)
+        e1 = Enum(self.SomeEnum)
         eq_(e1.adapt(ENUM).name, 'someenum')
         eq_(e1.adapt(ENUM).enums, ['one', 'two', 'three'])
 
@@ -1241,7 +1362,8 @@ class EnumTest(AssertsCompiledSQL, fixtures.TablesTest):
 
     def test_lookup_failure(self):
         assert_raises(
-            exc.StatementError, self.tables['non_native_enum_table'].insert().execute,
+            exc.StatementError,
+            self.tables['non_native_enum_table'].insert().execute,
             {'id': 4, 'someenum': 'four'}
         )