summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2017-09-26 11:43:25 -0400
committerGerrit Code Review <gerrit@ci.zzzcomputing.com>2017-09-26 11:43:25 -0400
commit21af7fc4aa9a97e30846b7d95a6d53928e4f11d2 (patch)
tree4c713bd81b6d8c083ec832219c556c0ff6e5fd11 /lib/sqlalchemy/sql
parent1b0b35f254f545dbeb3ad6e2215ba24ae1c02894 (diff)
parent35a1f5481a28837490037a6bd99d63590f748933 (diff)
downloadsqlalchemy-21af7fc4aa9a97e30846b7d95a6d53928e4f11d2.tar.gz
Merge "Make a common approach for "emulated" types"
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py211
-rw-r--r--lib/sqlalchemy/sql/type_api.py70
2 files changed, 186 insertions, 95 deletions
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 5e357d39b..061f8de96 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -15,7 +15,8 @@ import collections
import json
from . import elements
-from .type_api import TypeEngine, TypeDecorator, to_instance, Variant
+from .type_api import TypeEngine, TypeDecorator, to_instance, Variant, \
+ Emulated, NativeForEmulated
from .elements import quoted_name, TypeCoerce as type_coerce, _defer_name, \
Slice, _literal_as_binds
from .. import exc, util, processors
@@ -1130,8 +1131,7 @@ class SchemaType(SchemaEventTarget):
return variant_mapping['_default'] is self
-class Enum(String, SchemaType):
-
+class Enum(Emulated, String, SchemaType):
"""Generic Enum Type.
The :class:`.Enum` type provides a set of possible string values
@@ -1195,11 +1195,12 @@ class Enum(String, SchemaType):
.. seealso::
- :class:`~.postgresql.ENUM` - PostgreSQL-specific type,
+ :class:`.postgresql.ENUM` - PostgreSQL-specific type,
which has additional functionality.
- """
+ :class:`.mysql.ENUM` - MySQL-specific type
+ """
__visit_name__ = 'enum'
def __init__(self, *enums, **kw):
@@ -1277,13 +1278,30 @@ class Enum(String, SchemaType):
.. versionadded:: 1.1.0b2
"""
+ self._enum_init(enums, kw)
+
+ @property
+ def _enums_argument(self):
+ if self.enum_class is not None:
+ return [self.enum_class]
+ else:
+ return self.enums
+
+ def _enum_init(self, enums, kw):
+ """internal init for :class:`.Enum` and subclasses.
+
+ friendly init helper used by subclasses to remove
+ all the Enum-specific keyword arguments from kw. Allows all
+ other arguments in kw to pass through.
+
+ """
+ self.native_enum = kw.pop('native_enum', True)
+ self.create_constraint = kw.pop('create_constraint', True)
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)
self.validate_strings = kw.pop('validate_strings', False)
if convert_unicode is None:
@@ -1300,19 +1318,31 @@ class Enum(String, SchemaType):
length = 0
self._valid_lookup[None] = self._object_lookup[None] = None
- String.__init__(self,
- length=length,
- convert_unicode=convert_unicode,
- )
- SchemaType.__init__(self, **kw)
+ super(Enum, self).__init__(
+ length=length,
+ convert_unicode=convert_unicode,
+ )
+
+ if self.enum_class:
+ kw.setdefault('name', self.enum_class.__name__.lower())
+ SchemaType.__init__(
+ self,
+ name=kw.pop('name', None),
+ schema=kw.pop('schema', None),
+ metadata=kw.pop('metadata', None),
+ inherit_schema=kw.pop('inherit_schema', False),
+ quote=kw.pop('quote', None),
+ _create_events=kw.pop('_create_events', True)
+ )
def _parse_into_values(self, enums, kw):
+ if not enums and '_enums' in kw:
+ enums = kw.pop('_enums')
+
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
@@ -1331,6 +1361,10 @@ class Enum(String, SchemaType):
[(value, value) for value in self._valid_lookup.values()]
)
+ @property
+ def native(self):
+ return self.native_enum
+
def _db_value_for_elem(self, elem):
try:
return self._valid_lookup[elem]
@@ -1371,10 +1405,27 @@ class Enum(String, SchemaType):
'"%s" is not among the defined enum values' % elem)
def __repr__(self):
- return util.generic_repr(self,
- additional_kw=[('native_enum', True)],
- to_inspect=[Enum, SchemaType],
- )
+ return util.generic_repr(
+ self,
+ additional_kw=[('native_enum', True)],
+ to_inspect=[Enum, SchemaType],
+ )
+
+ def adapt_to_emulated(self, impltype, **kw):
+ kw.setdefault("convert_unicode", self.convert_unicode)
+ kw.setdefault("validate_strings", self.validate_strings)
+ kw.setdefault('name', self.name)
+ kw.setdefault('schema', self.schema)
+ kw.setdefault('inherit_schema', self.inherit_schema)
+ kw.setdefault('metadata', self.metadata)
+ kw.setdefault('_create_events', False)
+ kw.setdefault('native_enum', self.native_enum)
+ assert '_enums' in kw
+ return impltype(**kw)
+
+ def adapt(self, impltype, **kw):
+ kw['_enums'] = self._enums_argument
+ return super(Enum, self).adapt(impltype, **kw)
def _should_create_constraint(self, compiler, **kw):
if not self._is_impl_for_variant(compiler.dialect, kw):
@@ -1384,8 +1435,7 @@ class Enum(String, SchemaType):
@util.dependencies("sqlalchemy.sql.schema")
def _set_table(self, schema, column, table):
- if self.native_enum:
- SchemaType._set_table(self, column, table)
+ SchemaType._set_table(self, column, table)
if not self.create_constraint:
return
@@ -1402,34 +1452,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)
- _create_events = kw.pop('_create_events', False)
- if issubclass(impltype, Enum):
- if self.enum_class is not None:
- args = [self.enum_class]
- else:
- args = self.enums
- return impltype(name=self.name,
- schema=schema,
- metadata=metadata,
- convert_unicode=self.convert_unicode,
- native_enum=self.native_enum,
- inherit_schema=self.inherit_schema,
- validate_strings=self.validate_strings,
- _create_events=_create_events,
- *args,
- **kw)
- else:
- # TODO: why would we be here?
- return super(Enum, self).adapt(impltype, **kw)
-
def literal_processor(self, dialect):
- parent_processor = super(Enum, self).literal_processor(dialect)
+ parent_processor = super(
+ Enum, self).literal_processor(dialect)
def process(value):
value = self._db_value_for_elem(value)
@@ -1461,6 +1486,9 @@ class Enum(String, SchemaType):
return process
+ def copy(self, **kw):
+ return SchemaType.copy(self, **kw)
+
@property
def python_type(self):
if self.enum_class:
@@ -1549,7 +1577,7 @@ class PickleType(TypeDecorator):
return x == y
-class Boolean(TypeEngine, SchemaType):
+class Boolean(Emulated, TypeEngine, SchemaType):
"""A bool datatype.
@@ -1559,6 +1587,7 @@ class Boolean(TypeEngine, SchemaType):
"""
__visit_name__ = 'boolean'
+ native = True
def __init__(
self, create_constraint=True, name=None, _create_events=True):
@@ -1624,7 +1653,43 @@ class Boolean(TypeEngine, SchemaType):
return processors.int_to_boolean
-class Interval(_LookupExpressionAdapter, TypeDecorator):
+class _AbstractInterval(_LookupExpressionAdapter, TypeEngine):
+ @util.memoized_property
+ def _expression_adaptations(self):
+ # Based on http://www.postgresql.org/docs/current/\
+ # static/functions-datetime.html.
+
+ return {
+ operators.add: {
+ Date: DateTime,
+ Interval: self.__class__,
+ DateTime: DateTime,
+ Time: Time,
+ },
+ operators.sub: {
+ Interval: self.__class__
+ },
+ operators.mul: {
+ Numeric: self.__class__
+ },
+ operators.truediv: {
+ Numeric: self.__class__
+ },
+ operators.div: {
+ Numeric: self.__class__
+ }
+ }
+
+ @property
+ def _type_affinity(self):
+ return Interval
+
+ def coerce_compared_value(self, op, value):
+ """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
+ return self.impl.coerce_compared_value(op, value)
+
+
+class Interval(Emulated, _AbstractInterval, TypeDecorator):
"""A type for ``datetime.timedelta()`` objects.
@@ -1669,20 +1734,13 @@ class Interval(_LookupExpressionAdapter, TypeDecorator):
self.second_precision = second_precision
self.day_precision = day_precision
- def adapt(self, cls, **kw):
- if self.native and hasattr(cls, '_adapt_from_generic_interval'):
- return cls._adapt_from_generic_interval(self, **kw)
- else:
- return self.__class__(
- native=self.native,
- second_precision=self.second_precision,
- day_precision=self.day_precision,
- **kw)
-
@property
def python_type(self):
return dt.timedelta
+ def adapt_to_emulated(self, impltype, **kw):
+ return _AbstractInterval.adapt(self, impltype, **kw)
+
def bind_processor(self, dialect):
impl_processor = self.impl.bind_processor(dialect)
epoch = self.epoch
@@ -1714,41 +1772,6 @@ class Interval(_LookupExpressionAdapter, TypeDecorator):
return value - epoch
return process
- @util.memoized_property
- def _expression_adaptations(self):
- # Based on http://www.postgresql.org/docs/current/\
- # static/functions-datetime.html.
-
- return {
- operators.add: {
- Date: DateTime,
- Interval: self.__class__,
- DateTime: DateTime,
- Time: Time,
- },
- operators.sub: {
- Interval: self.__class__
- },
- operators.mul: {
- Numeric: self.__class__
- },
- operators.truediv: {
- Numeric: self.__class__
- },
- operators.div: {
- Numeric: self.__class__
- }
- }
-
- @property
- def _type_affinity(self):
- return Interval
-
- def coerce_compared_value(self, op, value):
- """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
-
- return self.impl.coerce_compared_value(op, value)
-
class JSON(Indexable, TypeEngine):
"""Represent a SQL JSON type.
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index 89c2c9de5..aee9a8ad8 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -673,6 +673,73 @@ class UserDefinedType(util.with_metaclass(VisitableCheckKWArg, TypeEngine)):
return self
+class Emulated(object):
+ """Mixin for base types that emulate the behavior of a DB-native type.
+
+ An :class:`.Emulated` type will use an available database type
+ in conjunction with Python-side routines and/or database constraints
+ in order to approximate the behavior of a database type that is provided
+ natively by some backends. When a native-providing backend is in
+ use, the native version of the type is used. This native version
+ should include the :class:`.NativeForEmulated` mixin to allow it to be
+ distinguished from :class:`.Emulated`.
+
+ Current examples of :class:`.Emulated` are: :class:`.Interval`,
+ :class:`.Enum`, :class:`.Boolean`.
+
+ .. versionadded:: 1.2.0b3
+
+ """
+
+ def adapt_to_emulated(self, impltype, **kw):
+ """Given an impl class, adapt this type to the impl assuming "emulated".
+
+ The impl should also be an "emulated" version of this type,
+ most likely the same class as this type itself.
+
+ e.g.: sqltypes.Enum adapts to the Enum class.
+
+ """
+ return super(Emulated, self).adapt(impltype, **kw)
+
+ def adapt(self, impltype, **kw):
+ if hasattr(impltype, "adapt_emulated_to_native"):
+
+ if self.native:
+ # native support requested, dialect gave us a native
+ # implementor, pass control over to it
+ return impltype.adapt_emulated_to_native(self, **kw)
+ else:
+ # impltype adapts to native, and we are not native,
+ # so reject the impltype in favor of "us"
+ impltype = self.__class__
+
+ if issubclass(impltype, self.__class__):
+ return self.adapt_to_emulated(impltype, **kw)
+ else:
+ return super(Emulated, self).adapt(impltype, **kw)
+
+
+class NativeForEmulated(object):
+ """Indicates DB-native types supported by an :class:`.Emulated` type.
+
+ .. versionadded:: 1.2.0b3
+
+ """
+
+ @classmethod
+ def adapt_emulated_to_native(cls, impl, **kw):
+ """Given an impl, adapt this type's class to the impl assuming "native".
+
+ The impl will be an :class:`.Emulated` class but not a
+ :class:`.NativeForEmulated`.
+
+ e.g.: postgresql.ENUM produces a type given an Enum instance.
+
+ """
+ return cls(**kw)
+
+
class TypeDecorator(SchemaEventTarget, TypeEngine):
"""Allows the creation of types which add additional functionality
to an existing type.
@@ -773,7 +840,6 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
will cause the index value ``'foo'`` to be JSON encoded.
"""
-
__visit_name__ = "type_decorator"
def __init__(self, *args, **kwargs):
@@ -1205,6 +1271,8 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
return util.generic_repr(self, to_inspect=self.impl)
+
+
class Variant(TypeDecorator):
"""A wrapping type that selects among a variety of
implementations based on dialect in use.