|
- # sql/types_api.py
- # Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: http://www.opensource.org/licenses/mit-license.php
-
- """Base types API.
-
- """
-
-
- from . import operators
- from .base import SchemaEventTarget
- from .traversals import NO_CACHE
- from .visitors import Traversible
- from .visitors import TraversibleType
- from .. import exc
- from .. import util
-
- # these are back-assigned by sqltypes.
- BOOLEANTYPE = None
- INTEGERTYPE = None
- NULLTYPE = None
- STRINGTYPE = None
- MATCHTYPE = None
- INDEXABLE = None
- TABLEVALUE = None
- _resolve_value_to_type = None
-
-
- class TypeEngine(Traversible):
- """The ultimate base class for all SQL datatypes.
-
- Common subclasses of :class:`.TypeEngine` include
- :class:`.String`, :class:`.Integer`, and :class:`.Boolean`.
-
- For an overview of the SQLAlchemy typing system, see
- :ref:`types_toplevel`.
-
- .. seealso::
-
- :ref:`types_toplevel`
-
- """
-
- _sqla_type = True
- _isnull = False
- _is_tuple_type = False
- _is_table_value = False
- _is_array = False
- _is_type_decorator = False
-
- class Comparator(operators.ColumnOperators):
- """Base class for custom comparison operations defined at the
- type level. See :attr:`.TypeEngine.comparator_factory`.
-
-
- """
-
- __slots__ = "expr", "type"
-
- default_comparator = None
-
- def __clause_element__(self):
- return self.expr
-
- def __init__(self, expr):
- self.expr = expr
- self.type = expr.type
-
- @util.preload_module("sqlalchemy.sql.default_comparator")
- def operate(self, op, *other, **kwargs):
- default_comparator = util.preloaded.sql_default_comparator
- o = default_comparator.operator_lookup[op.__name__]
- return o[0](self.expr, op, *(other + o[1:]), **kwargs)
-
- @util.preload_module("sqlalchemy.sql.default_comparator")
- def reverse_operate(self, op, other, **kwargs):
- default_comparator = util.preloaded.sql_default_comparator
- o = default_comparator.operator_lookup[op.__name__]
- return o[0](self.expr, op, other, reverse=True, *o[1:], **kwargs)
-
- def _adapt_expression(self, op, other_comparator):
- """evaluate the return type of <self> <op> <othertype>,
- and apply any adaptations to the given operator.
-
- This method determines the type of a resulting binary expression
- given two source types and an operator. For example, two
- :class:`_schema.Column` objects, both of the type
- :class:`.Integer`, will
- produce a :class:`.BinaryExpression` that also has the type
- :class:`.Integer` when compared via the addition (``+``) operator.
- However, using the addition operator with an :class:`.Integer`
- and a :class:`.Date` object will produce a :class:`.Date`, assuming
- "days delta" behavior by the database (in reality, most databases
- other than PostgreSQL don't accept this particular operation).
-
- The method returns a tuple of the form <operator>, <type>.
- The resulting operator and type will be those applied to the
- resulting :class:`.BinaryExpression` as the final operator and the
- right-hand side of the expression.
-
- Note that only a subset of operators make usage of
- :meth:`._adapt_expression`,
- including math operators and user-defined operators, but not
- boolean comparison or special SQL keywords like MATCH or BETWEEN.
-
- """
-
- return op, self.type
-
- def __reduce__(self):
- return _reconstitute_comparator, (self.expr,)
-
- hashable = True
- """Flag, if False, means values from this type aren't hashable.
-
- Used by the ORM when uniquing result lists.
-
- """
-
- comparator_factory = Comparator
- """A :class:`.TypeEngine.Comparator` class which will apply
- to operations performed by owning :class:`_expression.ColumnElement`
- objects.
-
- The :attr:`.comparator_factory` attribute is a hook consulted by
- the core expression system when column and SQL expression operations
- are performed. When a :class:`.TypeEngine.Comparator` class is
- associated with this attribute, it allows custom re-definition of
- all existing operators, as well as definition of new operators.
- Existing operators include those provided by Python operator overloading
- such as :meth:`.operators.ColumnOperators.__add__` and
- :meth:`.operators.ColumnOperators.__eq__`,
- those provided as standard
- attributes of :class:`.operators.ColumnOperators` such as
- :meth:`.operators.ColumnOperators.like`
- and :meth:`.operators.ColumnOperators.in_`.
-
- Rudimentary usage of this hook is allowed through simple subclassing
- of existing types, or alternatively by using :class:`.TypeDecorator`.
- See the documentation section :ref:`types_operators` for examples.
-
- """
-
- sort_key_function = None
- """A sorting function that can be passed as the key to sorted.
-
- The default value of ``None`` indicates that the values stored by
- this type are self-sorting.
-
- .. versionadded:: 1.3.8
-
- """
-
- should_evaluate_none = False
- """If True, the Python constant ``None`` is considered to be handled
- explicitly by this type.
-
- The ORM uses this flag to indicate that a positive value of ``None``
- is passed to the column in an INSERT statement, rather than omitting
- the column from the INSERT statement which has the effect of firing
- off column-level defaults. It also allows types which have special
- behavior for Python None, such as a JSON type, to indicate that
- they'd like to handle the None value explicitly.
-
- To set this flag on an existing type, use the
- :meth:`.TypeEngine.evaluates_none` method.
-
- .. seealso::
-
- :meth:`.TypeEngine.evaluates_none`
-
- .. versionadded:: 1.1
-
-
- """
-
- def evaluates_none(self):
- """Return a copy of this type which has the :attr:`.should_evaluate_none`
- flag set to True.
-
- E.g.::
-
- Table(
- 'some_table', metadata,
- Column(
- String(50).evaluates_none(),
- nullable=True,
- server_default='no value')
- )
-
- The ORM uses this flag to indicate that a positive value of ``None``
- is passed to the column in an INSERT statement, rather than omitting
- the column from the INSERT statement which has the effect of firing
- off column-level defaults. It also allows for types which have
- special behavior associated with the Python None value to indicate
- that the value doesn't necessarily translate into SQL NULL; a
- prime example of this is a JSON type which may wish to persist the
- JSON value ``'null'``.
-
- In all cases, the actual NULL SQL value can be always be
- persisted in any column by using
- the :obj:`_expression.null` SQL construct in an INSERT statement
- or associated with an ORM-mapped attribute.
-
- .. note::
-
- The "evaluates none" flag does **not** apply to a value
- of ``None`` passed to :paramref:`_schema.Column.default` or
- :paramref:`_schema.Column.server_default`; in these cases,
- ``None``
- still means "no default".
-
- .. versionadded:: 1.1
-
- .. seealso::
-
- :ref:`session_forcing_null` - in the ORM documentation
-
- :paramref:`.postgresql.JSON.none_as_null` - PostgreSQL JSON
- interaction with this flag.
-
- :attr:`.TypeEngine.should_evaluate_none` - class-level flag
-
- """
- typ = self.copy()
- typ.should_evaluate_none = True
- return typ
-
- def copy(self, **kw):
- return self.adapt(self.__class__)
-
- def compare_against_backend(self, dialect, conn_type):
- """Compare this type against the given backend type.
-
- This function is currently not implemented for SQLAlchemy
- types, and for all built in types will return ``None``. However,
- it can be implemented by a user-defined type
- where it can be consumed by schema comparison tools such as
- Alembic autogenerate.
-
- A future release of SQLAlchemy will potentially implement this method
- for builtin types as well.
-
- The function should return True if this type is equivalent to the
- given type; the type is typically reflected from the database
- so should be database specific. The dialect in use is also
- passed. It can also return False to assert that the type is
- not equivalent.
-
- :param dialect: a :class:`.Dialect` that is involved in the comparison.
-
- :param conn_type: the type object reflected from the backend.
-
- .. versionadded:: 1.0.3
-
- """
- return None
-
- def copy_value(self, value):
- return value
-
- def literal_processor(self, dialect):
- """Return a conversion function for processing literal values that are
- to be rendered directly without using binds.
-
- This function is used when the compiler makes use of the
- "literal_binds" flag, typically used in DDL generation as well
- as in certain scenarios where backends don't accept bound parameters.
-
- .. versionadded:: 0.9.0
-
- """
- return None
-
- def bind_processor(self, dialect):
- """Return a conversion function for processing bind values.
-
- Returns a callable which will receive a bind parameter value
- as the sole positional argument and will return a value to
- send to the DB-API.
-
- If processing is not necessary, the method should return ``None``.
-
- :param dialect: Dialect instance in use.
-
- """
- return None
-
- def result_processor(self, dialect, coltype):
- """Return a conversion function for processing result row values.
-
- Returns a callable which will receive a result row column
- value as the sole positional argument and will return a value
- to return to the user.
-
- If processing is not necessary, the method should return ``None``.
-
- :param dialect: Dialect instance in use.
-
- :param coltype: DBAPI coltype argument received in cursor.description.
-
- """
- return None
-
- def column_expression(self, colexpr):
- """Given a SELECT column expression, return a wrapping SQL expression.
-
- This is typically a SQL function that wraps a column expression
- as rendered in the columns clause of a SELECT statement.
- It is used for special data types that require
- columns to be wrapped in some special database function in order
- to coerce the value before being sent back to the application.
- It is the SQL analogue of the :meth:`.TypeEngine.result_processor`
- method.
-
- The method is evaluated at statement compile time, as opposed
- to statement construction time.
-
- .. seealso::
-
- :ref:`types_sql_value_processing`
-
- """
-
- return None
-
- @util.memoized_property
- def _has_column_expression(self):
- """memoized boolean, check if column_expression is implemented.
-
- Allows the method to be skipped for the vast majority of expression
- types that don't use this feature.
-
- """
-
- return (
- self.__class__.column_expression.__code__
- is not TypeEngine.column_expression.__code__
- )
-
- def bind_expression(self, bindvalue):
- """Given a bind value (i.e. a :class:`.BindParameter` instance),
- return a SQL expression in its place.
-
- This is typically a SQL function that wraps the existing bound
- parameter within the statement. It is used for special data types
- that require literals being wrapped in some special database function
- in order to coerce an application-level value into a database-specific
- format. It is the SQL analogue of the
- :meth:`.TypeEngine.bind_processor` method.
-
- The method is evaluated at statement compile time, as opposed
- to statement construction time.
-
- Note that this method, when implemented, should always return
- the exact same structure, without any conditional logic, as it
- may be used in an executemany() call against an arbitrary number
- of bound parameter sets.
-
- .. seealso::
-
- :ref:`types_sql_value_processing`
-
- """
- return None
-
- @util.memoized_property
- def _has_bind_expression(self):
- """memoized boolean, check if bind_expression is implemented.
-
- Allows the method to be skipped for the vast majority of expression
- types that don't use this feature.
-
- """
-
- return util.method_is_overridden(self, TypeEngine.bind_expression)
-
- @staticmethod
- def _to_instance(cls_or_self):
- return to_instance(cls_or_self)
-
- def compare_values(self, x, y):
- """Compare two values for equality."""
-
- return x == y
-
- def get_dbapi_type(self, dbapi):
- """Return the corresponding type object from the underlying DB-API, if
- any.
-
- This can be useful for calling ``setinputsizes()``, for example.
-
- """
- return None
-
- @property
- def python_type(self):
- """Return the Python type object expected to be returned
- by instances of this type, if known.
-
- Basically, for those types which enforce a return type,
- or are known across the board to do such for all common
- DBAPIs (like ``int`` for example), will return that type.
-
- If a return type is not defined, raises
- ``NotImplementedError``.
-
- Note that any type also accommodates NULL in SQL which
- means you can also get back ``None`` from any type
- in practice.
-
- """
- raise NotImplementedError()
-
- def with_variant(self, type_, dialect_name):
- r"""Produce a new type object that will utilize the given
- type when applied to the dialect of the given name.
-
- e.g.::
-
- from sqlalchemy.types import String
- from sqlalchemy.dialects import mysql
-
- s = String()
-
- s = s.with_variant(mysql.VARCHAR(collation='foo'), 'mysql')
-
- The construction of :meth:`.TypeEngine.with_variant` is always
- from the "fallback" type to that which is dialect specific.
- The returned type is an instance of :class:`.Variant`, which
- itself provides a :meth:`.Variant.with_variant`
- that can be called repeatedly.
-
- :param type\_: a :class:`.TypeEngine` that will be selected
- as a variant from the originating type, when a dialect
- of the given name is in use.
- :param dialect_name: base name of the dialect which uses
- this type. (i.e. ``'postgresql'``, ``'mysql'``, etc.)
-
- """
- return Variant(self, {dialect_name: to_instance(type_)})
-
- @util.memoized_property
- def _type_affinity(self):
- """Return a rudimental 'affinity' value expressing the general class
- of type."""
-
- typ = None
- for t in self.__class__.__mro__:
- if t in (TypeEngine, UserDefinedType):
- return typ
- elif issubclass(t, (TypeEngine, UserDefinedType)):
- typ = t
- else:
- return self.__class__
-
- @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 (
- t.__module__
- in (
- "sqlalchemy.sql.sqltypes",
- "sqlalchemy.sql.type_api",
- )
- and issubclass(t, TypeEngine)
- and t is not TypeEngine
- and t.__name__[0] != "_"
- ):
- 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
- :class:`.TypeEngine`.
-
- """
- try:
- return dialect._type_memos[self]["impl"]
- except KeyError:
- return self._dialect_info(dialect)["impl"]
-
- def _unwrapped_dialect_impl(self, dialect):
- """Return the 'unwrapped' dialect impl for this type.
-
- For a type that applies wrapping logic (e.g. TypeDecorator), give
- us the real, actual dialect-level type that is used.
-
- This is used by TypeDecorator itself as well at least one case where
- dialects need to check that a particular specific dialect-level
- type is in use, within the :meth:`.DefaultDialect.set_input_sizes`
- method.
-
- """
- return self.dialect_impl(dialect)
-
- def _cached_literal_processor(self, dialect):
- """Return a dialect-specific literal processor for this type."""
- try:
- return dialect._type_memos[self]["literal"]
- except KeyError:
- pass
- # avoid KeyError context coming into literal_processor() function
- # raises
- d = self._dialect_info(dialect)
- d["literal"] = lp = d["impl"].literal_processor(dialect)
- return lp
-
- def _cached_bind_processor(self, dialect):
- """Return a dialect-specific bind processor for this type."""
-
- try:
- return dialect._type_memos[self]["bind"]
- except KeyError:
- pass
- # avoid KeyError context coming into bind_processor() function
- # raises
- d = self._dialect_info(dialect)
- d["bind"] = bp = d["impl"].bind_processor(dialect)
- return bp
-
- def _cached_result_processor(self, dialect, coltype):
- """Return a dialect-specific result processor for this type."""
-
- try:
- return dialect._type_memos[self][coltype]
- except KeyError:
- pass
- # avoid KeyError context coming into result_processor() function
- # raises
- d = self._dialect_info(dialect)
- # key assumption: DBAPI type codes are
- # constants. Else this dictionary would
- # grow unbounded.
- d[coltype] = rp = d["impl"].result_processor(dialect, coltype)
- return rp
-
- def _cached_custom_processor(self, dialect, key, fn):
- try:
- return dialect._type_memos[self][key]
- except KeyError:
- pass
- # avoid KeyError context coming into fn() function
- # raises
- d = self._dialect_info(dialect)
- impl = d["impl"]
- d[key] = result = fn(impl)
- return result
-
- def _dialect_info(self, dialect):
- """Return a dialect-specific registry which
- caches a dialect-specific implementation, bind processing
- function, and one or more result processing functions."""
-
- if self in dialect._type_memos:
- return dialect._type_memos[self]
- else:
- impl = self._gen_dialect_impl(dialect)
- if impl is self:
- impl = self.adapt(type(self))
- # this can't be self, else we create a cycle
- assert impl is not self
- dialect._type_memos[self] = d = {"impl": impl}
- return d
-
- def _gen_dialect_impl(self, dialect):
- return dialect.type_descriptor(self)
-
- @util.memoized_property
- def _static_cache_key(self):
- names = util.get_cls_kwargs(self.__class__)
- return (self.__class__,) + tuple(
- (
- k,
- self.__dict__[k]._static_cache_key
- if isinstance(self.__dict__[k], TypeEngine)
- else self.__dict__[k],
- )
- for k in names
- if k in self.__dict__ and not k.startswith("_")
- )
-
- def adapt(self, cls, **kw):
- """Produce an "adapted" form of this type, given an "impl" class
- to work with.
-
- This method is used internally to associate generic
- types with "implementation" types that are specific to a particular
- dialect.
- """
- return util.constructor_copy(self, cls, **kw)
-
- def coerce_compared_value(self, op, value):
- """Suggest a type for a 'coerced' Python value in an expression.
-
- Given an operator and value, gives the type a chance
- to return a type which the value should be coerced into.
-
- The default behavior here is conservative; if the right-hand
- side is already coerced into a SQL type based on its
- Python type, it is usually left alone.
-
- End-user functionality extension here should generally be via
- :class:`.TypeDecorator`, which provides more liberal behavior in that
- it defaults to coercing the other side of the expression into this
- type, thus applying special Python conversions above and beyond those
- needed by the DBAPI to both ides. It also provides the public method
- :meth:`.TypeDecorator.coerce_compared_value` which is intended for
- end-user customization of this behavior.
-
- """
- _coerced_type = _resolve_value_to_type(value)
- if (
- _coerced_type is NULLTYPE
- or _coerced_type._type_affinity is self._type_affinity
- ):
- return self
- else:
- return _coerced_type
-
- def _compare_type_affinity(self, other):
- return self._type_affinity is other._type_affinity
-
- def compile(self, dialect=None):
- """Produce a string-compiled form of this :class:`.TypeEngine`.
-
- When called with no arguments, uses a "default" dialect
- to produce a string result.
-
- :param dialect: a :class:`.Dialect` instance.
-
- """
- # arg, return value is inconsistent with
- # ClauseElement.compile()....this is a mistake.
-
- if not dialect:
- dialect = self._default_dialect()
-
- return dialect.type_compiler.process(self)
-
- @util.preload_module("sqlalchemy.engine.default")
- def _default_dialect(self):
- default = util.preloaded.engine_default
- return default.StrCompileDialect()
-
- def __str__(self):
- if util.py2k:
- return unicode(self.compile()).encode( # noqa
- "ascii", "backslashreplace"
- ) # noqa
- else:
- return str(self.compile())
-
- def __repr__(self):
- return util.generic_repr(self)
-
-
- class VisitableCheckKWArg(util.EnsureKWArgType, TraversibleType):
- pass
-
-
- class UserDefinedType(util.with_metaclass(VisitableCheckKWArg, TypeEngine)):
- """Base for user defined types.
-
- This should be the base of new types. Note that
- for most cases, :class:`.TypeDecorator` is probably
- more appropriate::
-
- import sqlalchemy.types as types
-
- class MyType(types.UserDefinedType):
- def __init__(self, precision = 8):
- self.precision = precision
-
- def get_col_spec(self, **kw):
- return "MYTYPE(%s)" % self.precision
-
- def bind_processor(self, dialect):
- def process(value):
- return value
- return process
-
- def result_processor(self, dialect, coltype):
- def process(value):
- return value
- return process
-
- Once the type is made, it's immediately usable::
-
- table = Table('foo', meta,
- Column('id', Integer, primary_key=True),
- Column('data', MyType(16))
- )
-
- The ``get_col_spec()`` method will in most cases receive a keyword
- argument ``type_expression`` which refers to the owning expression
- of the type as being compiled, such as a :class:`_schema.Column` or
- :func:`.cast` construct. This keyword is only sent if the method
- accepts keyword arguments (e.g. ``**kw``) in its argument signature;
- introspection is used to check for this in order to support legacy
- forms of this function.
-
- .. versionadded:: 1.0.0 the owning expression is passed to
- the ``get_col_spec()`` method via the keyword argument
- ``type_expression``, if it receives ``**kw`` in its signature.
-
- """
-
- __visit_name__ = "user_defined"
-
- ensure_kwarg = "get_col_spec"
-
- def coerce_compared_value(self, op, value):
- """Suggest a type for a 'coerced' Python value in an expression.
-
- Default behavior for :class:`.UserDefinedType` is the
- same as that of :class:`.TypeDecorator`; by default it returns
- ``self``, assuming the compared value should be coerced into
- the same type as this one. See
- :meth:`.TypeDecorator.coerce_compared_value` for more detail.
-
- """
-
- 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:
- # non-native support, let the native implementor
- # decide also, at the moment this is just to help debugging
- # as only the default logic is implemented.
- return impltype.adapt_native_to_emulated(self, **kw)
- else:
- 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_native_to_emulated(cls, impl, **kw):
- """Given an impl, adapt this type's class to the impl assuming
- "emulated".
-
-
- """
- impltype = impl.__class__
- return impl.adapt(impltype, **kw)
-
- @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.
-
- This method is preferred to direct subclassing of SQLAlchemy's
- built-in types as it ensures that all required functionality of
- the underlying type is kept in place.
-
- Typical usage::
-
- import sqlalchemy.types as types
-
- class MyType(types.TypeDecorator):
- '''Prefixes Unicode values with "PREFIX:" on the way in and
- strips it off on the way out.
- '''
-
- impl = types.Unicode
-
- cache_ok = True
-
- def process_bind_param(self, value, dialect):
- return "PREFIX:" + value
-
- def process_result_value(self, value, dialect):
- return value[7:]
-
- def copy(self, **kw):
- return MyType(self.impl.length)
-
- The class-level ``impl`` attribute is required, and can reference any
- :class:`.TypeEngine` class. Alternatively, the :meth:`load_dialect_impl`
- method can be used to provide different type classes based on the dialect
- given; in this case, the ``impl`` variable can reference
- ``TypeEngine`` as a placeholder.
-
- The :attr:`.TypeDecorator.cache_ok` class-level flag indicates if this
- custom :class:`.TypeDecorator` is safe to be used as part of a cache key.
- This flag defaults to ``None`` which will initially generate a warning
- when the SQL compiler attempts to generate a cache key for a statement
- that uses this type. If the :class:`.TypeDecorator` is not guaranteed
- to produce the same bind/result behavior and SQL generation
- every time, this flag should be set to ``False``; otherwise if the
- class produces the same behavior each time, it may be set to ``True``.
- See :attr:`.TypeDecorator.cache_ok` for further notes on how this works.
-
- Types that receive a Python type that isn't similar to the ultimate type
- used may want to define the :meth:`TypeDecorator.coerce_compared_value`
- method. This is used to give the expression system a hint when coercing
- Python objects into bind parameters within expressions. Consider this
- expression::
-
- mytable.c.somecol + datetime.date(2009, 5, 15)
-
- Above, if "somecol" is an ``Integer`` variant, it makes sense that
- we're doing date arithmetic, where above is usually interpreted
- by databases as adding a number of days to the given date.
- The expression system does the right thing by not attempting to
- coerce the "date()" value into an integer-oriented bind parameter.
-
- However, in the case of ``TypeDecorator``, we are usually changing an
- incoming Python type to something new - ``TypeDecorator`` by default will
- "coerce" the non-typed side to be the same type as itself. Such as below,
- we define an "epoch" type that stores a date value as an integer::
-
- class MyEpochType(types.TypeDecorator):
- impl = types.Integer
-
- epoch = datetime.date(1970, 1, 1)
-
- def process_bind_param(self, value, dialect):
- return (value - self.epoch).days
-
- def process_result_value(self, value, dialect):
- return self.epoch + timedelta(days=value)
-
- Our expression of ``somecol + date`` with the above type will coerce the
- "date" on the right side to also be treated as ``MyEpochType``.
-
- This behavior can be overridden via the
- :meth:`~TypeDecorator.coerce_compared_value` method, which returns a type
- that should be used for the value of the expression. Below we set it such
- that an integer value will be treated as an ``Integer``, and any other
- value is assumed to be a date and will be treated as a ``MyEpochType``::
-
- def coerce_compared_value(self, op, value):
- if isinstance(value, int):
- return Integer()
- else:
- return self
-
- .. warning::
-
- Note that the **behavior of coerce_compared_value is not inherited
- by default from that of the base type**.
- If the :class:`.TypeDecorator` is augmenting a
- type that requires special logic for certain types of operators,
- this method **must** be overridden. A key example is when decorating
- the :class:`_postgresql.JSON` and :class:`_postgresql.JSONB` types;
- the default rules of :meth:`.TypeEngine.coerce_compared_value` should
- be used in order to deal with operators like index operations::
-
- class MyJsonType(TypeDecorator):
- impl = postgresql.JSON
-
- cache_ok = True
-
- def coerce_compared_value(self, op, value):
- return self.impl.coerce_compared_value(op, value)
-
- Without the above step, index operations such as ``mycol['foo']``
- will cause the index value ``'foo'`` to be JSON encoded.
-
- """
-
- __visit_name__ = "type_decorator"
-
- _is_type_decorator = True
-
- def __init__(self, *args, **kwargs):
- """Construct a :class:`.TypeDecorator`.
-
- Arguments sent here are passed to the constructor
- of the class assigned to the ``impl`` class level attribute,
- assuming the ``impl`` is a callable, and the resulting
- object is assigned to the ``self.impl`` instance attribute
- (thus overriding the class attribute of the same name).
-
- If the class level ``impl`` is not a callable (the unusual case),
- it will be assigned to the same instance attribute 'as-is',
- ignoring those arguments passed to the constructor.
-
- Subclasses can override this to customize the generation
- of ``self.impl`` entirely.
-
- """
-
- if not hasattr(self.__class__, "impl"):
- raise AssertionError(
- "TypeDecorator implementations "
- "require a class-level variable "
- "'impl' which refers to the class of "
- "type being decorated"
- )
- self.impl = to_instance(self.__class__.impl, *args, **kwargs)
-
- coerce_to_is_types = (util.NoneType,)
- """Specify those Python types which should be coerced at the expression
- level to "IS <constant>" when compared using ``==`` (and same for
- ``IS NOT`` in conjunction with ``!=``).
-
- For most SQLAlchemy types, this includes ``NoneType``, as well as
- ``bool``.
-
- :class:`.TypeDecorator` modifies this list to only include ``NoneType``,
- as typedecorator implementations that deal with boolean types are common.
-
- Custom :class:`.TypeDecorator` classes can override this attribute to
- return an empty tuple, in which case no values will be coerced to
- constants.
-
- """
-
- cache_ok = None
- """Indicate if statements using this :class:`.TypeDecorator` are "safe to
- cache".
-
- The default value ``None`` will emit a warning and then not allow caching
- of a statement which includes this type. Set to ``False`` to disable
- statements using this type from being cached at all without a warning.
- When set to ``True``, the object's class and selected elements from its
- state will be used as part of the cache key, e.g.::
-
- class MyType(TypeDecorator):
- impl = String
-
- cache_ok = True
-
- def __init__(self, choices):
- self.choices = tuple(choices)
- self.internal_only = True
-
- The cache key for the above type would be equivalent to::
-
- (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c')))
-
- The caching scheme will extract attributes from the type that correspond
- to the names of parameters in the ``__init__()`` method. Above, the
- "choices" attribute becomes part of the cache key but "internal_only"
- does not, because there is no parameter named "internal_only".
-
- The requirements for cacheable elements is that they are hashable
- and also that they indicate the same SQL rendered for expressions using
- this type every time for a given cache value.
-
- .. versionadded:: 1.4.14 - added the ``cache_ok`` flag to allow
- some configurability of caching for :class:`.TypeDecorator` classes.
-
- .. seealso::
-
- :ref:`sql_caching`
-
- """
-
- class Comparator(TypeEngine.Comparator):
- """A :class:`.TypeEngine.Comparator` that is specific to
- :class:`.TypeDecorator`.
-
- User-defined :class:`.TypeDecorator` classes should not typically
- need to modify this.
-
-
- """
-
- __slots__ = ()
-
- def operate(self, op, *other, **kwargs):
- kwargs["_python_is_types"] = self.expr.type.coerce_to_is_types
- return super(TypeDecorator.Comparator, self).operate(
- op, *other, **kwargs
- )
-
- def reverse_operate(self, op, other, **kwargs):
- kwargs["_python_is_types"] = self.expr.type.coerce_to_is_types
- return super(TypeDecorator.Comparator, self).reverse_operate(
- op, other, **kwargs
- )
-
- @property
- def comparator_factory(self):
- if TypeDecorator.Comparator in self.impl.comparator_factory.__mro__:
- return self.impl.comparator_factory
- else:
- return type(
- "TDComparator",
- (TypeDecorator.Comparator, self.impl.comparator_factory),
- {},
- )
-
- @property
- def _static_cache_key(self):
- if self.cache_ok is None:
- util.warn(
- "TypeDecorator %r will not produce a cache key because "
- "the ``cache_ok`` flag is not set to True. "
- "Set this flag to True if this type object's "
- "state is safe to use in a cache key, or False to "
- "disable this warning." % self
- )
- elif self.cache_ok is True:
- return super(TypeDecorator, self)._static_cache_key
-
- return NO_CACHE
-
- def _gen_dialect_impl(self, dialect):
- """
- #todo
- """
- adapted = dialect.type_descriptor(self)
- if adapted is not self:
- return adapted
-
- # otherwise adapt the impl type, link
- # to a copy of this TypeDecorator and return
- # that.
- typedesc = self.load_dialect_impl(dialect).dialect_impl(dialect)
- tt = self.copy()
- if not isinstance(tt, self.__class__):
- raise AssertionError(
- "Type object %s does not properly "
- "implement the copy() method, it must "
- "return an object of type %s" % (self, self.__class__)
- )
- tt.impl = typedesc
- return tt
-
- @property
- def _type_affinity(self):
- """
- #todo
- """
- return self.impl._type_affinity
-
- def _set_parent(self, column, outer=False, **kw):
- """Support SchemaEventTarget"""
-
- super(TypeDecorator, self)._set_parent(column)
-
- if not outer and isinstance(self.impl, SchemaEventTarget):
- self.impl._set_parent(column, outer=False, **kw)
-
- def _set_parent_with_dispatch(self, parent):
- """Support SchemaEventTarget"""
-
- super(TypeDecorator, self)._set_parent_with_dispatch(
- parent, outer=True
- )
-
- if isinstance(self.impl, SchemaEventTarget):
- self.impl._set_parent_with_dispatch(parent)
-
- def type_engine(self, dialect):
- """Return a dialect-specific :class:`.TypeEngine` instance
- for this :class:`.TypeDecorator`.
-
- In most cases this returns a dialect-adapted form of
- the :class:`.TypeEngine` type represented by ``self.impl``.
- Makes usage of :meth:`dialect_impl`.
- Behavior can be customized here by overriding
- :meth:`load_dialect_impl`.
-
- """
- adapted = dialect.type_descriptor(self)
- if not isinstance(adapted, type(self)):
- return adapted
- else:
- return self.load_dialect_impl(dialect)
-
- def load_dialect_impl(self, dialect):
- """Return a :class:`.TypeEngine` object corresponding to a dialect.
-
- This is an end-user override hook that can be used to provide
- differing types depending on the given dialect. It is used
- by the :class:`.TypeDecorator` implementation of :meth:`type_engine`
- to help determine what type should ultimately be returned
- for a given :class:`.TypeDecorator`.
-
- By default returns ``self.impl``.
-
- """
- return self.impl
-
- def _unwrapped_dialect_impl(self, dialect):
- """Return the 'unwrapped' dialect impl for this type.
-
- This is used by the :meth:`.DefaultDialect.set_input_sizes`
- method.
-
- """
- # some dialects have a lookup for a TypeDecorator subclass directly.
- # postgresql.INTERVAL being the main example
- typ = self.dialect_impl(dialect)
-
- # if we are still a type decorator, load the per-dialect switch
- # (such as what Variant uses), then get the dialect impl for that.
- if isinstance(typ, self.__class__):
- return typ.load_dialect_impl(dialect).dialect_impl(dialect)
- else:
- return typ
-
- def __getattr__(self, key):
- """Proxy all other undefined accessors to the underlying
- implementation."""
- return getattr(self.impl, key)
-
- def process_literal_param(self, value, dialect):
- """Receive a literal parameter value to be rendered inline within
- a statement.
-
- This method is used when the compiler renders a
- literal value without using binds, typically within DDL
- such as in the "server default" of a column or an expression
- within a CHECK constraint.
-
- The returned string will be rendered into the output string.
-
- .. versionadded:: 0.9.0
-
- """
- raise NotImplementedError()
-
- def process_bind_param(self, value, dialect):
- """Receive a bound parameter value to be converted.
-
- Subclasses override this method to return the
- value that should be passed along to the underlying
- :class:`.TypeEngine` object, and from there to the
- DBAPI ``execute()`` method.
-
- The operation could be anything desired to perform custom
- behavior, such as transforming or serializing data.
- This could also be used as a hook for validating logic.
-
- This operation should be designed with the reverse operation
- in mind, which would be the process_result_value method of
- this class.
-
- :param value: Data to operate upon, of any type expected by
- this method in the subclass. Can be ``None``.
- :param dialect: the :class:`.Dialect` in use.
-
- """
-
- raise NotImplementedError()
-
- def process_result_value(self, value, dialect):
- """Receive a result-row column value to be converted.
-
- Subclasses should implement this method to operate on data
- fetched from the database.
-
- Subclasses override this method to return the
- value that should be passed back to the application,
- given a value that is already processed by
- the underlying :class:`.TypeEngine` object, originally
- from the DBAPI cursor method ``fetchone()`` or similar.
-
- The operation could be anything desired to perform custom
- behavior, such as transforming or serializing data.
- This could also be used as a hook for validating logic.
-
- :param value: Data to operate upon, of any type expected by
- this method in the subclass. Can be ``None``.
- :param dialect: the :class:`.Dialect` in use.
-
- This operation should be designed to be reversible by
- the "process_bind_param" method of this class.
-
- """
-
- raise NotImplementedError()
-
- @util.memoized_property
- def _has_bind_processor(self):
- """memoized boolean, check if process_bind_param is implemented.
-
- Allows the base process_bind_param to raise
- NotImplementedError without needing to test an expensive
- exception throw.
-
- """
-
- 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 util.method_is_overridden(
- self, TypeDecorator.process_literal_param
- )
-
- def literal_processor(self, dialect):
- """Provide a literal processing function for the given
- :class:`.Dialect`.
-
- Subclasses here will typically override
- :meth:`.TypeDecorator.process_literal_param` instead of this method
- directly.
-
- By default, this method makes use of
- :meth:`.TypeDecorator.process_bind_param` if that method is
- implemented, where :meth:`.TypeDecorator.process_literal_param` is
- not. The rationale here is that :class:`.TypeDecorator` typically
- deals with Python conversions of data that are above the layer of
- database presentation. With the value converted by
- :meth:`.TypeDecorator.process_bind_param`, the underlying type will
- then handle whether it needs to be presented to the DBAPI as a bound
- parameter or to the database as an inline SQL value.
-
- .. versionadded:: 0.9.0
-
- """
- if self._has_literal_processor:
- process_param = self.process_literal_param
- elif self._has_bind_processor:
- # the bind processor should normally be OK
- # for TypeDecorator since it isn't doing DB-level
- # handling, the handling here won't be different for bound vs.
- # literals.
- process_param = self.process_bind_param
- else:
- process_param = None
-
- if process_param:
- impl_processor = self.impl.literal_processor(dialect)
- if impl_processor:
-
- def process(value):
- return impl_processor(process_param(value, dialect))
-
- else:
-
- def process(value):
- return process_param(value, dialect)
-
- return process
- else:
- return self.impl.literal_processor(dialect)
-
- def bind_processor(self, dialect):
- """Provide a bound value processing function for the
- given :class:`.Dialect`.
-
- This is the method that fulfills the :class:`.TypeEngine`
- contract for bound value conversion. :class:`.TypeDecorator`
- will wrap a user-defined implementation of
- :meth:`process_bind_param` here.
-
- User-defined code can override this method directly,
- though its likely best to use :meth:`process_bind_param` so that
- the processing provided by ``self.impl`` is maintained.
-
- :param dialect: Dialect instance in use.
-
- This method is the reverse counterpart to the
- :meth:`result_processor` method of this class.
-
- """
- if self._has_bind_processor:
- process_param = self.process_bind_param
- impl_processor = self.impl.bind_processor(dialect)
- if impl_processor:
-
- def process(value):
- return impl_processor(process_param(value, dialect))
-
- else:
-
- def process(value):
- return process_param(value, dialect)
-
- return process
- else:
- return self.impl.bind_processor(dialect)
-
- @util.memoized_property
- def _has_result_processor(self):
- """memoized boolean, check if process_result_value is implemented.
-
- Allows the base process_result_value to raise
- NotImplementedError without needing to test an expensive
- exception throw.
-
- """
-
- return util.method_is_overridden(
- self, TypeDecorator.process_result_value
- )
-
- def result_processor(self, dialect, coltype):
- """Provide a result value processing function for the given
- :class:`.Dialect`.
-
- This is the method that fulfills the :class:`.TypeEngine`
- contract for result value conversion. :class:`.TypeDecorator`
- will wrap a user-defined implementation of
- :meth:`process_result_value` here.
-
- User-defined code can override this method directly,
- though its likely best to use :meth:`process_result_value` so that
- the processing provided by ``self.impl`` is maintained.
-
- :param dialect: Dialect instance in use.
- :param coltype: A SQLAlchemy data type
-
- This method is the reverse counterpart to the
- :meth:`bind_processor` method of this class.
-
- """
- if self._has_result_processor:
- process_value = self.process_result_value
- impl_processor = self.impl.result_processor(dialect, coltype)
- if impl_processor:
-
- def process(value):
- return process_value(impl_processor(value), dialect)
-
- else:
-
- def process(value):
- return process_value(value, dialect)
-
- return process
- else:
- return self.impl.result_processor(dialect, coltype)
-
- @util.memoized_property
- def _has_bind_expression(self):
-
- return (
- 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)
-
- @util.memoized_property
- def _has_column_expression(self):
- """memoized boolean, check if column_expression is implemented.
-
- Allows the method to be skipped for the vast majority of expression
- types that don't use this feature.
-
- """
-
- return (
- 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)
-
- def coerce_compared_value(self, op, value):
- """Suggest a type for a 'coerced' Python value in an expression.
-
- By default, returns self. This method is called by
- the expression system when an object using this type is
- on the left or right side of an expression against a plain Python
- object which does not yet have a SQLAlchemy type assigned::
-
- expr = table.c.somecolumn + 35
-
- Where above, if ``somecolumn`` uses this type, this method will
- be called with the value ``operator.add``
- and ``35``. The return value is whatever SQLAlchemy type should
- be used for ``35`` for this particular operation.
-
- """
- return self
-
- def copy(self, **kw):
- """Produce a copy of this :class:`.TypeDecorator` instance.
-
- This is a shallow copy and is provided to fulfill part of
- the :class:`.TypeEngine` contract. It usually does not
- need to be overridden unless the user-defined :class:`.TypeDecorator`
- has local state that should be deep-copied.
-
- """
-
- instance = self.__class__.__new__(self.__class__)
- instance.__dict__.update(self.__dict__)
- return instance
-
- def get_dbapi_type(self, dbapi):
- """Return the DBAPI type object represented by this
- :class:`.TypeDecorator`.
-
- By default this calls upon :meth:`.TypeEngine.get_dbapi_type` of the
- underlying "impl".
- """
- return self.impl.get_dbapi_type(dbapi)
-
- def compare_values(self, x, y):
- """Given two values, compare them for equality.
-
- By default this calls upon :meth:`.TypeEngine.compare_values`
- of the underlying "impl", which in turn usually
- uses the Python equals operator ``==``.
-
- This function is used by the ORM to compare
- an original-loaded value with an intercepted
- "changed" value, to determine if a net change
- has occurred.
-
- """
- return self.impl.compare_values(x, y)
-
- @property
- def sort_key_function(self):
- return self.impl.sort_key_function
-
- def __repr__(self):
- 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.
-
- The :class:`.Variant` type is typically constructed
- using the :meth:`.TypeEngine.with_variant` method.
-
- .. seealso:: :meth:`.TypeEngine.with_variant` for an example of use.
-
- """
-
- cache_ok = True
-
- def __init__(self, base, mapping):
- """Construct a new :class:`.Variant`.
-
- :param base: the base 'fallback' type
- :param mapping: dictionary of string dialect names to
- :class:`.TypeEngine` instances.
-
- """
- self.impl = base
- self.mapping = mapping
-
- @util.memoized_property
- def _static_cache_key(self):
- # TODO: needs tests in test/sql/test_compare.py
- return (self.__class__,) + (
- self.impl._static_cache_key,
- tuple(
- (key, self.mapping[key]._static_cache_key)
- for key in sorted(self.mapping)
- ),
- )
-
- def coerce_compared_value(self, operator, value):
- result = self.impl.coerce_compared_value(operator, value)
- if result is self.impl:
- return self
- else:
- return result
-
- def load_dialect_impl(self, dialect):
- if dialect.name in self.mapping:
- return self.mapping[dialect.name]
- else:
- return self.impl
-
- def _set_parent(self, column, outer=False, **kw):
- """Support SchemaEventTarget"""
-
- if isinstance(self.impl, SchemaEventTarget):
- self.impl._set_parent(column, **kw)
- for impl in self.mapping.values():
- if isinstance(impl, SchemaEventTarget):
- impl._set_parent(column, **kw)
-
- def _set_parent_with_dispatch(self, parent):
- """Support SchemaEventTarget"""
-
- if isinstance(self.impl, SchemaEventTarget):
- self.impl._set_parent_with_dispatch(parent)
- for impl in self.mapping.values():
- if isinstance(impl, SchemaEventTarget):
- impl._set_parent_with_dispatch(parent)
-
- def with_variant(self, type_, dialect_name):
- r"""Return a new :class:`.Variant` which adds the given
- type + dialect name to the mapping, in addition to the
- mapping present in this :class:`.Variant`.
-
- :param type\_: a :class:`.TypeEngine` that will be selected
- as a variant from the originating type, when a dialect
- of the given name is in use.
- :param dialect_name: base name of the dialect which uses
- this type. (i.e. ``'postgresql'``, ``'mysql'``, etc.)
-
- """
-
- if dialect_name in self.mapping:
- raise exc.ArgumentError(
- "Dialect '%s' is already present in "
- "the mapping for this Variant" % dialect_name
- )
- mapping = self.mapping.copy()
- mapping[dialect_name] = type_
- return Variant(self.impl, mapping)
-
- @property
- def comparator_factory(self):
- """express comparison behavior in terms of the base type"""
- return self.impl.comparator_factory
-
-
- def _reconstitute_comparator(expression):
- return expression.comparator
-
-
- def to_instance(typeobj, *arg, **kw):
- if typeobj is None:
- return NULLTYPE
-
- if callable(typeobj):
- return typeobj(*arg, **kw)
- else:
- return typeobj
-
-
- def adapt_type(typeobj, colspecs):
- if isinstance(typeobj, type):
- typeobj = typeobj()
- for t in typeobj.__class__.__mro__[0:-1]:
- try:
- impltype = colspecs[t]
- break
- except KeyError:
- pass
- else:
- # couldn't adapt - so just return the type itself
- # (it may be a user-defined type)
- return typeobj
- # if we adapted the given generic type to a database-specific type,
- # but it turns out the originally given "generic" type
- # is actually a subclass of our resulting type, then we were already
- # given a more specific type than that required; so use that.
- if issubclass(typeobj.__class__, impltype):
- return typeobj
- return typeobj.adapt(impltype)
|