summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorGord Thompson <gord@gordthompson.com>2020-12-07 18:37:29 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2020-12-08 19:54:05 -0500
commitb71e46f0470964358d44aec08f940260f78691f0 (patch)
tree44c9b5934ad550b4688700e8124204411e42190f /lib/sqlalchemy/sql
parent18b1b261ff988549e75b011f2f4296fb13b24d64 (diff)
downloadsqlalchemy-b71e46f0470964358d44aec08f940260f78691f0.tar.gz
Implement `TypeEngine.as_generic`
Added :meth:`_types.TypeEngine.as_generic` to map dialect-specific types, such as :class:`sqlalchemy.dialects.mysql.INTEGER`, with the "best match" generic SQLAlchemy type, in this case :class:`_types.Integer`. Pull request courtesy Andrew Hannigan. Abstract away how we check for "overridden methods" so it is more clear what the intent is and that the methodology can be independently tested. Fixes: #5659 Closes: #5714 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5714 Pull-request-sha: 91afb9a0ba3bfa81a1ded80c025989213cf6e4eb Change-Id: Ic54d6690ecc10dc69e6e72856d5620036cea472a
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/events.py3
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py12
-rw-r--r--lib/sqlalchemy/sql/type_api.py101
3 files changed, 85 insertions, 31 deletions
diff --git a/lib/sqlalchemy/sql/events.py b/lib/sqlalchemy/sql/events.py
index 58d04f7aa..797ca697f 100644
--- a/lib/sqlalchemy/sql/events.py
+++ b/lib/sqlalchemy/sql/events.py
@@ -314,4 +314,7 @@ class DDLEvents(event.Events):
:ref:`automap_intercepting_columns` -
in the :ref:`automap_toplevel` documentation
+ :ref:`metadata_reflection_dbagnostic_types` - in
+ the :ref:`metadata_reflection_toplevel` documentation
+
"""
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index 45d4f0b7f..581573d17 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -1607,6 +1607,18 @@ class Enum(Emulated, String, SchemaType):
to_inspect=[Enum, SchemaType],
)
+ def as_generic(self, allow_nulltype=False):
+ if hasattr(self, "enums"):
+ args = self.enums
+ else:
+ raise NotImplementedError(
+ "TypeEngine.as_generic() heuristic "
+ "is undefined for types that inherit Enum but do not have "
+ "an `enums` attribute."
+ )
+
+ return util.constructor_copy(self, self._generic_type_affinity, *args)
+
def adapt_to_emulated(self, impltype, **kw):
kw.setdefault("_expect_unicode", self._expect_unicode)
kw.setdefault("validate_strings", self.validate_strings)
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index bca6e9020..b48886cca 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -17,7 +17,6 @@ from .visitors import TraversibleType
from .. import exc
from .. import util
-
# these are back-assigned by sqltypes.
BOOLEANTYPE = None
INTEGERTYPE = None
@@ -372,10 +371,7 @@ class TypeEngine(Traversible):
"""
- return (
- self.__class__.bind_expression.__code__
- is not TypeEngine.bind_expression.__code__
- )
+ return util.method_is_overridden(self, TypeEngine.bind_expression)
@staticmethod
def _to_instance(cls_or_self):
@@ -456,12 +452,13 @@ class TypeEngine(Traversible):
else:
return self.__class__
- @classmethod
- def _is_generic_type(cls):
- n = cls.__name__
- return n.upper() != n
-
+ @util.memoized_property
def _generic_type_affinity(self):
+ best_camelcase = None
+ best_uppercase = None
+
+ if not isinstance(self, (TypeEngine, UserDefinedType)):
+ return self.__class__
for t in self.__class__.__mro__:
if (
@@ -470,13 +467,56 @@ class TypeEngine(Traversible):
"sqlalchemy.sql.sqltypes",
"sqlalchemy.sql.type_api",
)
- and t._is_generic_type()
+ and issubclass(t, TypeEngine)
+ and t is not TypeEngine
+ and t.__name__[0] != "_"
):
- if t in (TypeEngine, UserDefinedType):
- return NULLTYPE.__class__
- return t
- else:
- return self.__class__
+ if t.__name__.isupper() and not best_uppercase:
+ best_uppercase = t
+ elif not t.__name__.isupper() and not best_camelcase:
+ best_camelcase = t
+
+ return best_camelcase or best_uppercase or NULLTYPE.__class__
+
+ def as_generic(self, allow_nulltype=False):
+ """
+ Return an instance of the generic type corresponding to this type
+ using heuristic rule. The method may be overridden if this
+ heuristic rule is not sufficient.
+
+ >>> from sqlalchemy.dialects.mysql import INTEGER
+ >>> INTEGER(display_width=4).as_generic()
+ Integer()
+
+ >>> from sqlalchemy.dialects.mysql import NVARCHAR
+ >>> NVARCHAR(length=100).as_generic()
+ Unicode(length=100)
+
+ .. versionadded:: 1.4.0b2
+
+
+ .. seealso::
+
+ :ref:`metadata_reflection_dbagnostic_types` - describes the
+ use of :meth:`_types.TypeEngine.as_generic` in conjunction with
+ the :meth:`_sql.DDLEvents.column_reflect` event, which is its
+ intended use.
+
+ """
+ if (
+ not allow_nulltype
+ and self._generic_type_affinity == NULLTYPE.__class__
+ ):
+ raise NotImplementedError(
+ "Default TypeEngine.as_generic() "
+ "heuristic method was unsuccessful for {}. A custom "
+ "as_generic() method must be implemented for this "
+ "type class.".format(
+ self.__class__.__module__ + "." + self.__class__.__name__
+ )
+ )
+
+ return util.constructor_copy(self, self._generic_type_affinity)
def dialect_impl(self, dialect):
"""Return a dialect-specific implementation for this
@@ -1171,18 +1211,16 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
"""
- return (
- self.__class__.process_bind_param.__code__
- is not TypeDecorator.process_bind_param.__code__
+ return util.method_is_overridden(
+ self, TypeDecorator.process_bind_param
)
@util.memoized_property
def _has_literal_processor(self):
"""memoized boolean, check if process_literal_param is implemented."""
- return (
- self.__class__.process_literal_param.__code__
- is not TypeDecorator.process_literal_param.__code__
+ return util.method_is_overridden(
+ self, TypeDecorator.process_literal_param
)
def literal_processor(self, dialect):
@@ -1278,9 +1316,9 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
exception throw.
"""
- return (
- self.__class__.process_result_value.__code__
- is not TypeDecorator.process_result_value.__code__
+
+ return util.method_is_overridden(
+ self, TypeDecorator.process_result_value
)
def result_processor(self, dialect, coltype):
@@ -1322,10 +1360,11 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
@util.memoized_property
def _has_bind_expression(self):
+
return (
- self.__class__.bind_expression.__code__
- is not TypeDecorator.bind_expression.__code__
- ) or self.impl._has_bind_expression
+ util.method_is_overridden(self, TypeDecorator.bind_expression)
+ or self.impl._has_bind_expression
+ )
def bind_expression(self, bindparam):
return self.impl.bind_expression(bindparam)
@@ -1340,9 +1379,9 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
"""
return (
- self.__class__.column_expression.__code__
- is not TypeDecorator.column_expression.__code__
- ) or self.impl._has_column_expression
+ util.method_is_overridden(self, TypeDecorator.column_expression)
+ or self.impl._has_column_expression
+ )
def column_expression(self, column):
return self.impl.column_expression(column)