diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-02-02 13:00:19 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-02-02 15:04:46 -0500 |
| commit | df55695f8e99f0523795a7b9e9cb9babee2e00e1 (patch) | |
| tree | 011e27e6a74f60421030b2ef29dcb960e5220278 /lib/sqlalchemy | |
| parent | 5401c4d8514aa42e8ac4b5579454e68151e78a93 (diff) | |
| download | sqlalchemy-df55695f8e99f0523795a7b9e9cb9babee2e00e1.tar.gz | |
- add changelog and migration notes for new Enum features,
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
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/dialects/mysql/enumerated.py | 42 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/sqltypes.py | 175 |
2 files changed, 143 insertions, 74 deletions
diff --git a/lib/sqlalchemy/dialects/mysql/enumerated.py b/lib/sqlalchemy/dialects/mysql/enumerated.py index 5c4dc61f0..567e95288 100644 --- a/lib/sqlalchemy/dialects/mysql/enumerated.py +++ b/lib/sqlalchemy/dialects/mysql/enumerated.py @@ -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) diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 21e57d519..81630fe4f 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -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 |
