|
- # sql/sqltypes.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
-
- """SQL specific types.
-
- """
-
- import codecs
- import datetime as dt
- import decimal
- import json
-
- from . import coercions
- from . import elements
- from . import operators
- from . import roles
- from . import type_api
- from .base import _bind_or_error
- from .base import NO_ARG
- from .base import SchemaEventTarget
- from .elements import _NONE_NAME
- from .elements import quoted_name
- from .elements import Slice
- from .elements import TypeCoerce as type_coerce # noqa
- from .traversals import HasCacheKey
- from .traversals import InternalTraversal
- from .type_api import Emulated
- from .type_api import NativeForEmulated # noqa
- from .type_api import to_instance
- from .type_api import TypeDecorator
- from .type_api import TypeEngine
- from .type_api import Variant
- from .. import event
- from .. import exc
- from .. import inspection
- from .. import processors
- from .. import util
- from ..util import compat
- from ..util import langhelpers
- from ..util import OrderedDict
- from ..util import pickle
-
-
- class _LookupExpressionAdapter(object):
-
- """Mixin expression adaptations based on lookup tables.
-
- These rules are currently used by the numeric, integer and date types
- which have detailed cross-expression coercion rules.
-
- """
-
- @property
- def _expression_adaptations(self):
- raise NotImplementedError()
-
- class Comparator(TypeEngine.Comparator):
- _blank_dict = util.immutabledict()
-
- def _adapt_expression(self, op, other_comparator):
- othertype = other_comparator.type._type_affinity
- lookup = self.type._expression_adaptations.get(
- op, self._blank_dict
- ).get(othertype, self.type)
- if lookup is othertype:
- return (op, other_comparator.type)
- elif lookup is self.type._type_affinity:
- return (op, self.type)
- else:
- return (op, to_instance(lookup))
-
- comparator_factory = Comparator
-
-
- class Concatenable(object):
-
- """A mixin that marks a type as supporting 'concatenation',
- typically strings."""
-
- class Comparator(TypeEngine.Comparator):
- def _adapt_expression(self, op, other_comparator):
- if op is operators.add and isinstance(
- other_comparator,
- (Concatenable.Comparator, NullType.Comparator),
- ):
- return operators.concat_op, self.expr.type
- else:
- return super(Concatenable.Comparator, self)._adapt_expression(
- op, other_comparator
- )
-
- comparator_factory = Comparator
-
-
- class Indexable(object):
- """A mixin that marks a type as supporting indexing operations,
- such as array or JSON structures.
-
-
- .. versionadded:: 1.1.0
-
-
- """
-
- class Comparator(TypeEngine.Comparator):
- def _setup_getitem(self, index):
- raise NotImplementedError()
-
- def __getitem__(self, index):
- (
- adjusted_op,
- adjusted_right_expr,
- result_type,
- ) = self._setup_getitem(index)
- return self.operate(
- adjusted_op, adjusted_right_expr, result_type=result_type
- )
-
- comparator_factory = Comparator
-
-
- class String(Concatenable, TypeEngine):
-
- """The base for all string and character types.
-
- In SQL, corresponds to VARCHAR. Can also take Python unicode objects
- and encode to the database's encoding in bind params (and the reverse for
- result sets.)
-
- The `length` field is usually required when the `String` type is
- used within a CREATE TABLE statement, as VARCHAR requires a length
- on most databases.
-
- """
-
- __visit_name__ = "string"
-
- RETURNS_UNICODE = util.symbol(
- "RETURNS_UNICODE",
- """Indicates that the DBAPI returns Python Unicode for VARCHAR,
- NVARCHAR, and other character-based datatypes in all cases.
-
- This is the default value for
- :attr:`.DefaultDialect.returns_unicode_strings` under Python 3.
-
- .. versionadded:: 1.4
-
- """,
- )
-
- RETURNS_BYTES = util.symbol(
- "RETURNS_BYTES",
- """Indicates that the DBAPI returns byte objects under Python 3
- or non-Unicode string objects under Python 2 for VARCHAR, NVARCHAR,
- and other character-based datatypes in all cases.
-
- This may be applied to the
- :attr:`.DefaultDialect.returns_unicode_strings` attribute.
-
- .. versionadded:: 1.4
-
- """,
- )
-
- RETURNS_CONDITIONAL = util.symbol(
- "RETURNS_CONDITIONAL",
- """Indicates that the DBAPI may return Unicode or bytestrings for
- VARCHAR, NVARCHAR, and other character-based datatypes, and that
- SQLAlchemy's default String datatype will need to test on a per-row
- basis for Unicode or bytes.
-
- This may be applied to the
- :attr:`.DefaultDialect.returns_unicode_strings` attribute.
-
- .. versionadded:: 1.4
-
- """,
- )
-
- RETURNS_UNKNOWN = util.symbol(
- "RETURNS_UNKNOWN",
- """Indicates that the dialect should test on first connect what the
- string-returning behavior of character-based datatypes is.
-
- This is the default value for DefaultDialect.unicode_returns under
- Python 2.
-
- This may be applied to the
- :attr:`.DefaultDialect.returns_unicode_strings` attribute under
- Python 2 only. The value is disallowed under Python 3.
-
- .. versionadded:: 1.4
-
- .. deprecated:: 1.4 This value will be removed in SQLAlchemy 2.0.
-
- """,
- )
-
- @util.deprecated_params(
- convert_unicode=(
- "1.3",
- "The :paramref:`.String.convert_unicode` parameter is deprecated "
- "and will be removed in a future release. All modern DBAPIs "
- "now support Python Unicode directly and this parameter is "
- "unnecessary.",
- ),
- unicode_error=(
- "1.3",
- "The :paramref:`.String.unicode_errors` parameter is deprecated "
- "and will be removed in a future release. This parameter is "
- "unnecessary for modern Python DBAPIs and degrades performance "
- "significantly.",
- ),
- )
- def __init__(
- self,
- length=None,
- collation=None,
- convert_unicode=False,
- unicode_error=None,
- _warn_on_bytestring=False,
- _expect_unicode=False,
- ):
- """
- Create a string-holding type.
-
- :param length: optional, a length for the column for use in
- DDL and CAST expressions. May be safely omitted if no ``CREATE
- TABLE`` will be issued. Certain databases may require a
- ``length`` for use in DDL, and will raise an exception when
- the ``CREATE TABLE`` DDL is issued if a ``VARCHAR``
- with no length is included. Whether the value is
- interpreted as bytes or characters is database specific.
-
- :param collation: Optional, a column-level collation for
- use in DDL and CAST expressions. Renders using the
- COLLATE keyword supported by SQLite, MySQL, and PostgreSQL.
- E.g.::
-
- >>> from sqlalchemy import cast, select, String
- >>> print(select(cast('some string', String(collation='utf8'))))
- SELECT CAST(:param_1 AS VARCHAR COLLATE utf8) AS anon_1
-
- :param convert_unicode: When set to ``True``, the
- :class:`.String` type will assume that
- input is to be passed as Python Unicode objects under Python 2,
- and results returned as Python Unicode objects.
- In the rare circumstance that the DBAPI does not support
- Python unicode under Python 2, SQLAlchemy will use its own
- encoder/decoder functionality on strings, referring to the
- value of the :paramref:`_sa.create_engine.encoding` parameter
- parameter passed to :func:`_sa.create_engine` as the encoding.
-
- For the extremely rare case that Python Unicode
- is to be encoded/decoded by SQLAlchemy on a backend
- that *does* natively support Python Unicode,
- the string value ``"force"`` can be passed here which will
- cause SQLAlchemy's encode/decode services to be
- used unconditionally.
-
- .. note::
-
- SQLAlchemy's unicode-conversion flags and features only apply
- to Python 2; in Python 3, all string objects are Unicode objects.
- For this reason, as well as the fact that virtually all modern
- DBAPIs now support Unicode natively even under Python 2,
- the :paramref:`.String.convert_unicode` flag is inherently a
- legacy feature.
-
- .. note::
-
- In the vast majority of cases, the :class:`.Unicode` or
- :class:`.UnicodeText` datatypes should be used for a
- :class:`_schema.Column` that expects to store non-ascii data.
- These
- datatypes will ensure that the correct types are used on the
- database side as well as set up the correct Unicode behaviors
- under Python 2.
-
- .. seealso::
-
- :paramref:`_sa.create_engine.convert_unicode` -
- :class:`_engine.Engine`-wide parameter
-
- :param unicode_error: Optional, a method to use to handle Unicode
- conversion errors. Behaves like the ``errors`` keyword argument to
- the standard library's ``string.decode()`` functions, requires
- that :paramref:`.String.convert_unicode` is set to
- ``"force"``
-
- """
- if unicode_error is not None and convert_unicode != "force":
- raise exc.ArgumentError(
- "convert_unicode must be 'force' " "when unicode_error is set."
- )
-
- self.length = length
- self.collation = collation
- self._expect_unicode = convert_unicode or _expect_unicode
- self._expect_unicode_error = unicode_error
-
- self._warn_on_bytestring = _warn_on_bytestring
-
- def literal_processor(self, dialect):
- def process(value):
- value = value.replace("'", "''")
-
- if dialect.identifier_preparer._double_percents:
- value = value.replace("%", "%%")
-
- return "'%s'" % value
-
- return process
-
- def bind_processor(self, dialect):
- if self._expect_unicode or dialect.convert_unicode:
- if (
- dialect.supports_unicode_binds
- and self._expect_unicode != "force"
- ):
- if self._warn_on_bytestring:
-
- def process(value):
- if isinstance(value, util.binary_type):
- util.warn_limited(
- "Unicode type received non-unicode "
- "bind param value %r.",
- (util.ellipses_string(value),),
- )
- return value
-
- return process
- else:
- return None
- else:
- encoder = codecs.getencoder(dialect.encoding)
- warn_on_bytestring = self._warn_on_bytestring
-
- def process(value):
- if isinstance(value, util.text_type):
- return encoder(value, self._expect_unicode_error)[0]
- elif warn_on_bytestring and value is not None:
- util.warn_limited(
- "Unicode type received non-unicode bind "
- "param value %r.",
- (util.ellipses_string(value),),
- )
- return value
-
- return process
- else:
- return None
-
- def result_processor(self, dialect, coltype):
- wants_unicode = self._expect_unicode or dialect.convert_unicode
- needs_convert = wants_unicode and (
- dialect.returns_unicode_strings is not String.RETURNS_UNICODE
- or self._expect_unicode in ("force", "force_nocheck")
- )
- needs_isinstance = (
- needs_convert
- and dialect.returns_unicode_strings
- in (
- String.RETURNS_CONDITIONAL,
- String.RETURNS_UNICODE,
- )
- and self._expect_unicode != "force_nocheck"
- )
- if needs_convert:
- if needs_isinstance:
- return processors.to_conditional_unicode_processor_factory(
- dialect.encoding, self._expect_unicode_error
- )
- else:
- return processors.to_unicode_processor_factory(
- dialect.encoding, self._expect_unicode_error
- )
- else:
- return None
-
- @property
- def python_type(self):
- if self._expect_unicode:
- return util.text_type
- else:
- return str
-
- def get_dbapi_type(self, dbapi):
- return dbapi.STRING
-
- @classmethod
- def _warn_deprecated_unicode(cls):
- util.warn_deprecated(
- "The convert_unicode on Engine and String as well as the "
- "unicode_error flag on String are deprecated. All modern "
- "DBAPIs now support Python Unicode natively under Python 2, and "
- "under Python 3 all strings are inherently Unicode. These flags "
- "will be removed in a future release.",
- version="1.3",
- )
-
-
- class Text(String):
-
- """A variably sized string type.
-
- In SQL, usually corresponds to CLOB or TEXT. Can also take Python
- unicode objects and encode to the database's encoding in bind
- params (and the reverse for result sets.) In general, TEXT objects
- do not have a length; while some databases will accept a length
- argument here, it will be rejected by others.
-
- """
-
- __visit_name__ = "text"
-
-
- class Unicode(String):
-
- """A variable length Unicode string type.
-
- The :class:`.Unicode` type is a :class:`.String` subclass that assumes
- input and output strings that may contain non-ASCII characters, and for
- some backends implies an underlying column type that is explicitly
- supporting of non-ASCII data, such as ``NVARCHAR`` on Oracle and SQL
- Server. This will impact the output of ``CREATE TABLE`` statements and
- ``CAST`` functions at the dialect level, and also in some cases will
- indicate different behavior in the DBAPI itself in how it handles bound
- parameters.
-
- The character encoding used by the :class:`.Unicode` type that is used to
- transmit and receive data to the database is usually determined by the
- DBAPI itself. All modern DBAPIs accommodate non-ASCII strings but may have
- different methods of managing database encodings; if necessary, this
- encoding should be configured as detailed in the notes for the target DBAPI
- in the :ref:`dialect_toplevel` section.
-
- In modern SQLAlchemy, use of the :class:`.Unicode` datatype does not
- typically imply any encoding/decoding behavior within SQLAlchemy itself.
- Historically, when DBAPIs did not support Python ``unicode`` objects under
- Python 2, SQLAlchemy handled unicode encoding/decoding services itself
- which would be controlled by the flag :paramref:`.String.convert_unicode`;
- this flag is deprecated as it is no longer needed for Python 3.
-
- When using Python 2, data that is passed to columns that use the
- :class:`.Unicode` datatype must be of type ``unicode``, and not ``str``
- which in Python 2 is equivalent to ``bytes``. In Python 3, all data
- passed to columns that use the :class:`.Unicode` datatype should be
- of type ``str``. See the flag :paramref:`.String.convert_unicode` for
- more discussion of unicode encode/decode behavior under Python 2.
-
- .. warning:: Some database backends, particularly SQL Server with pyodbc,
- are known to have undesirable behaviors regarding data that is noted
- as being of ``NVARCHAR`` type as opposed to ``VARCHAR``, including
- datatype mismatch errors and non-use of indexes. See the section
- on :meth:`.DialectEvents.do_setinputsizes` for background on working
- around unicode character issues for backends like SQL Server with
- pyodbc as well as cx_Oracle.
-
- .. seealso::
-
- :class:`.UnicodeText` - unlengthed textual counterpart
- to :class:`.Unicode`.
-
- :paramref:`.String.convert_unicode`
-
- :meth:`.DialectEvents.do_setinputsizes`
-
-
- """
-
- __visit_name__ = "unicode"
-
- def __init__(self, length=None, **kwargs):
- """
- Create a :class:`.Unicode` object.
-
- Parameters are the same as that of :class:`.String`,
- with the exception that ``convert_unicode``
- defaults to ``True``.
-
- """
- kwargs.setdefault("_expect_unicode", True)
- kwargs.setdefault("_warn_on_bytestring", True)
- super(Unicode, self).__init__(length=length, **kwargs)
-
-
- class UnicodeText(Text):
-
- """An unbounded-length Unicode string type.
-
- See :class:`.Unicode` for details on the unicode
- behavior of this object.
-
- Like :class:`.Unicode`, usage the :class:`.UnicodeText` type implies a
- unicode-capable type being used on the backend, such as
- ``NCLOB``, ``NTEXT``.
-
- """
-
- __visit_name__ = "unicode_text"
-
- def __init__(self, length=None, **kwargs):
- """
- Create a Unicode-converting Text type.
-
- Parameters are the same as that of :class:`_expression.TextClause`,
- with the exception that ``convert_unicode``
- defaults to ``True``.
-
- """
- kwargs.setdefault("_expect_unicode", True)
- kwargs.setdefault("_warn_on_bytestring", True)
- super(UnicodeText, self).__init__(length=length, **kwargs)
-
- def _warn_deprecated_unicode(self):
- pass
-
-
- class Integer(_LookupExpressionAdapter, TypeEngine):
-
- """A type for ``int`` integers."""
-
- __visit_name__ = "integer"
-
- def get_dbapi_type(self, dbapi):
- return dbapi.NUMBER
-
- @property
- def python_type(self):
- return int
-
- def literal_processor(self, dialect):
- def process(value):
- return str(int(value))
-
- return process
-
- @util.memoized_property
- def _expression_adaptations(self):
- # TODO: need a dictionary object that will
- # handle operators generically here, this is incomplete
- return {
- operators.add: {
- Date: Date,
- Integer: self.__class__,
- Numeric: Numeric,
- },
- operators.mul: {
- Interval: Interval,
- Integer: self.__class__,
- Numeric: Numeric,
- },
- operators.div: {Integer: self.__class__, Numeric: Numeric},
- operators.truediv: {Integer: self.__class__, Numeric: Numeric},
- operators.sub: {Integer: self.__class__, Numeric: Numeric},
- }
-
-
- class SmallInteger(Integer):
-
- """A type for smaller ``int`` integers.
-
- Typically generates a ``SMALLINT`` in DDL, and otherwise acts like
- a normal :class:`.Integer` on the Python side.
-
- """
-
- __visit_name__ = "small_integer"
-
-
- class BigInteger(Integer):
-
- """A type for bigger ``int`` integers.
-
- Typically generates a ``BIGINT`` in DDL, and otherwise acts like
- a normal :class:`.Integer` on the Python side.
-
- """
-
- __visit_name__ = "big_integer"
-
-
- class Numeric(_LookupExpressionAdapter, TypeEngine):
-
- """A type for fixed precision numbers, such as ``NUMERIC`` or ``DECIMAL``.
-
- This type returns Python ``decimal.Decimal`` objects by default, unless
- the :paramref:`.Numeric.asdecimal` flag is set to False, in which case
- they are coerced to Python ``float`` objects.
-
- .. note::
-
- The :class:`.Numeric` type is designed to receive data from a database
- type that is explicitly known to be a decimal type
- (e.g. ``DECIMAL``, ``NUMERIC``, others) and not a floating point
- type (e.g. ``FLOAT``, ``REAL``, others).
- If the database column on the server is in fact a floating-point
- type, such as ``FLOAT`` or ``REAL``, use the :class:`.Float`
- type or a subclass, otherwise numeric coercion between
- ``float``/``Decimal`` may or may not function as expected.
-
- .. note::
-
- The Python ``decimal.Decimal`` class is generally slow
- performing; cPython 3.3 has now switched to use the `cdecimal
- <http://pypi.python.org/pypi/cdecimal/>`_ library natively. For
- older Python versions, the ``cdecimal`` library can be patched
- into any application where it will replace the ``decimal``
- library fully, however this needs to be applied globally and
- before any other modules have been imported, as follows::
-
- import sys
- import cdecimal
- sys.modules["decimal"] = cdecimal
-
- Note that the ``cdecimal`` and ``decimal`` libraries are **not
- compatible with each other**, so patching ``cdecimal`` at the
- global level is the only way it can be used effectively with
- various DBAPIs that hardcode to import the ``decimal`` library.
-
- """
-
- __visit_name__ = "numeric"
-
- _default_decimal_return_scale = 10
-
- def __init__(
- self,
- precision=None,
- scale=None,
- decimal_return_scale=None,
- asdecimal=True,
- ):
- """
- Construct a Numeric.
-
- :param precision: the numeric precision for use in DDL ``CREATE
- TABLE``.
-
- :param scale: the numeric scale for use in DDL ``CREATE TABLE``.
-
- :param asdecimal: default True. Return whether or not
- values should be sent as Python Decimal objects, or
- as floats. Different DBAPIs send one or the other based on
- datatypes - the Numeric type will ensure that return values
- are one or the other across DBAPIs consistently.
-
- :param decimal_return_scale: Default scale to use when converting
- from floats to Python decimals. Floating point values will typically
- be much longer due to decimal inaccuracy, and most floating point
- database types don't have a notion of "scale", so by default the
- float type looks for the first ten decimal places when converting.
- Specifying this value will override that length. Types which
- do include an explicit ".scale" value, such as the base
- :class:`.Numeric` as well as the MySQL float types, will use the
- value of ".scale" as the default for decimal_return_scale, if not
- otherwise specified.
-
- .. versionadded:: 0.9.0
-
- When using the ``Numeric`` type, care should be taken to ensure
- that the asdecimal setting is appropriate for the DBAPI in use -
- when Numeric applies a conversion from Decimal->float or float->
- Decimal, this conversion incurs an additional performance overhead
- for all result columns received.
-
- DBAPIs that return Decimal natively (e.g. psycopg2) will have
- better accuracy and higher performance with a setting of ``True``,
- as the native translation to Decimal reduces the amount of floating-
- point issues at play, and the Numeric type itself doesn't need
- to apply any further conversions. However, another DBAPI which
- returns floats natively *will* incur an additional conversion
- overhead, and is still subject to floating point data loss - in
- which case ``asdecimal=False`` will at least remove the extra
- conversion overhead.
-
- """
- self.precision = precision
- self.scale = scale
- self.decimal_return_scale = decimal_return_scale
- self.asdecimal = asdecimal
-
- @property
- def _effective_decimal_return_scale(self):
- if self.decimal_return_scale is not None:
- return self.decimal_return_scale
- elif getattr(self, "scale", None) is not None:
- return self.scale
- else:
- return self._default_decimal_return_scale
-
- def get_dbapi_type(self, dbapi):
- return dbapi.NUMBER
-
- def literal_processor(self, dialect):
- def process(value):
- return str(value)
-
- return process
-
- @property
- def python_type(self):
- if self.asdecimal:
- return decimal.Decimal
- else:
- return float
-
- def bind_processor(self, dialect):
- if dialect.supports_native_decimal:
- return None
- else:
- return processors.to_float
-
- def result_processor(self, dialect, coltype):
- if self.asdecimal:
- if dialect.supports_native_decimal:
- # we're a "numeric", DBAPI will give us Decimal directly
- return None
- else:
- util.warn(
- "Dialect %s+%s does *not* support Decimal "
- "objects natively, and SQLAlchemy must "
- "convert from floating point - rounding "
- "errors and other issues may occur. Please "
- "consider storing Decimal numbers as strings "
- "or integers on this platform for lossless "
- "storage." % (dialect.name, dialect.driver)
- )
-
- # we're a "numeric", DBAPI returns floats, convert.
- return processors.to_decimal_processor_factory(
- decimal.Decimal,
- self.scale
- if self.scale is not None
- else self._default_decimal_return_scale,
- )
- else:
- if dialect.supports_native_decimal:
- return processors.to_float
- else:
- return None
-
- @util.memoized_property
- def _expression_adaptations(self):
- return {
- operators.mul: {
- Interval: Interval,
- Numeric: self.__class__,
- Integer: self.__class__,
- },
- operators.div: {Numeric: self.__class__, Integer: self.__class__},
- operators.truediv: {
- Numeric: self.__class__,
- Integer: self.__class__,
- },
- operators.add: {Numeric: self.__class__, Integer: self.__class__},
- operators.sub: {Numeric: self.__class__, Integer: self.__class__},
- }
-
-
- class Float(Numeric):
-
- """Type representing floating point types, such as ``FLOAT`` or ``REAL``.
-
- This type returns Python ``float`` objects by default, unless the
- :paramref:`.Float.asdecimal` flag is set to True, in which case they
- are coerced to ``decimal.Decimal`` objects.
-
- .. note::
-
- The :class:`.Float` type is designed to receive data from a database
- type that is explicitly known to be a floating point type
- (e.g. ``FLOAT``, ``REAL``, others)
- and not a decimal type (e.g. ``DECIMAL``, ``NUMERIC``, others).
- If the database column on the server is in fact a Numeric
- type, such as ``DECIMAL`` or ``NUMERIC``, use the :class:`.Numeric`
- type or a subclass, otherwise numeric coercion between
- ``float``/``Decimal`` may or may not function as expected.
-
- """
-
- __visit_name__ = "float"
-
- scale = None
-
- def __init__(
- self, precision=None, asdecimal=False, decimal_return_scale=None
- ):
- r"""
- Construct a Float.
-
- :param precision: the numeric precision for use in DDL ``CREATE
- TABLE``.
-
- :param asdecimal: the same flag as that of :class:`.Numeric`, but
- defaults to ``False``. Note that setting this flag to ``True``
- results in floating point conversion.
-
- :param decimal_return_scale: Default scale to use when converting
- from floats to Python decimals. Floating point values will typically
- be much longer due to decimal inaccuracy, and most floating point
- database types don't have a notion of "scale", so by default the
- float type looks for the first ten decimal places when converting.
- Specifying this value will override that length. Note that the
- MySQL float types, which do include "scale", will use "scale"
- as the default for decimal_return_scale, if not otherwise specified.
-
- .. versionadded:: 0.9.0
-
- """
- self.precision = precision
- self.asdecimal = asdecimal
- self.decimal_return_scale = decimal_return_scale
-
- def result_processor(self, dialect, coltype):
- if self.asdecimal:
- return processors.to_decimal_processor_factory(
- decimal.Decimal, self._effective_decimal_return_scale
- )
- elif dialect.supports_native_decimal:
- return processors.to_float
- else:
- return None
-
-
- class DateTime(_LookupExpressionAdapter, TypeEngine):
-
- """A type for ``datetime.datetime()`` objects.
-
- Date and time types return objects from the Python ``datetime``
- module. Most DBAPIs have built in support for the datetime
- module, with the noted exception of SQLite. In the case of
- SQLite, date and time types are stored as strings which are then
- converted back to datetime objects when rows are returned.
-
- For the time representation within the datetime type, some
- backends include additional options, such as timezone support and
- fractional seconds support. For fractional seconds, use the
- dialect-specific datatype, such as :class:`.mysql.TIME`. For
- timezone support, use at least the :class:`_types.TIMESTAMP` datatype,
- if not the dialect-specific datatype object.
-
- """
-
- __visit_name__ = "datetime"
-
- def __init__(self, timezone=False):
- """Construct a new :class:`.DateTime`.
-
- :param timezone: boolean. Indicates that the datetime type should
- enable timezone support, if available on the
- **base date/time-holding type only**. It is recommended
- to make use of the :class:`_types.TIMESTAMP` datatype directly when
- using this flag, as some databases include separate generic
- date/time-holding types distinct from the timezone-capable
- TIMESTAMP datatype, such as Oracle.
-
-
- """
- self.timezone = timezone
-
- def get_dbapi_type(self, dbapi):
- return dbapi.DATETIME
-
- @property
- def python_type(self):
- return dt.datetime
-
- @util.memoized_property
- def _expression_adaptations(self):
-
- # Based on http://www.postgresql.org/docs/current/\
- # static/functions-datetime.html.
-
- return {
- operators.add: {Interval: self.__class__},
- operators.sub: {Interval: self.__class__, DateTime: Interval},
- }
-
-
- class Date(_LookupExpressionAdapter, TypeEngine):
-
- """A type for ``datetime.date()`` objects."""
-
- __visit_name__ = "date"
-
- def get_dbapi_type(self, dbapi):
- return dbapi.DATETIME
-
- @property
- def python_type(self):
- return dt.date
-
- @util.memoized_property
- def _expression_adaptations(self):
- # Based on http://www.postgresql.org/docs/current/\
- # static/functions-datetime.html.
-
- return {
- operators.add: {
- Integer: self.__class__,
- Interval: DateTime,
- Time: DateTime,
- },
- operators.sub: {
- # date - integer = date
- Integer: self.__class__,
- # date - date = integer.
- Date: Integer,
- Interval: DateTime,
- # date - datetime = interval,
- # this one is not in the PG docs
- # but works
- DateTime: Interval,
- },
- }
-
-
- class Time(_LookupExpressionAdapter, TypeEngine):
-
- """A type for ``datetime.time()`` objects."""
-
- __visit_name__ = "time"
-
- def __init__(self, timezone=False):
- self.timezone = timezone
-
- def get_dbapi_type(self, dbapi):
- return dbapi.DATETIME
-
- @property
- def python_type(self):
- return dt.time
-
- @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__},
- operators.sub: {Time: Interval, Interval: self.__class__},
- }
-
-
- class _Binary(TypeEngine):
-
- """Define base behavior for binary types."""
-
- def __init__(self, length=None):
- self.length = length
-
- def literal_processor(self, dialect):
- def process(value):
- value = value.decode(dialect.encoding).replace("'", "''")
- return "'%s'" % value
-
- return process
-
- @property
- def python_type(self):
- return util.binary_type
-
- # Python 3 - sqlite3 doesn't need the `Binary` conversion
- # here, though pg8000 does to indicate "bytea"
- def bind_processor(self, dialect):
- if dialect.dbapi is None:
- return None
-
- DBAPIBinary = dialect.dbapi.Binary
-
- def process(value):
- if value is not None:
- return DBAPIBinary(value)
- else:
- return None
-
- return process
-
- # Python 3 has native bytes() type
- # both sqlite3 and pg8000 seem to return it,
- # psycopg2 as of 2.5 returns 'memoryview'
- if util.py2k:
-
- def result_processor(self, dialect, coltype):
- return processors.to_str
-
- else:
-
- def result_processor(self, dialect, coltype):
- def process(value):
- if value is not None:
- value = bytes(value)
- return value
-
- return process
-
- def coerce_compared_value(self, op, value):
- """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
-
- if isinstance(value, util.string_types):
- return self
- else:
- return super(_Binary, self).coerce_compared_value(op, value)
-
- def get_dbapi_type(self, dbapi):
- return dbapi.BINARY
-
-
- class LargeBinary(_Binary):
-
- """A type for large binary byte data.
-
- The :class:`.LargeBinary` type corresponds to a large and/or unlengthed
- binary type for the target platform, such as BLOB on MySQL and BYTEA for
- PostgreSQL. It also handles the necessary conversions for the DBAPI.
-
- """
-
- __visit_name__ = "large_binary"
-
- def __init__(self, length=None):
- """
- Construct a LargeBinary type.
-
- :param length: optional, a length for the column for use in
- DDL statements, for those binary types that accept a length,
- such as the MySQL BLOB type.
-
- """
- _Binary.__init__(self, length=length)
-
-
- class SchemaType(SchemaEventTarget):
-
- """Mark a type as possibly requiring schema-level DDL for usage.
-
- Supports types that must be explicitly created/dropped (i.e. PG ENUM type)
- as well as types that are complimented by table or schema level
- constraints, triggers, and other rules.
-
- :class:`.SchemaType` classes can also be targets for the
- :meth:`.DDLEvents.before_parent_attach` and
- :meth:`.DDLEvents.after_parent_attach` events, where the events fire off
- surrounding the association of the type object with a parent
- :class:`_schema.Column`.
-
- .. seealso::
-
- :class:`.Enum`
-
- :class:`.Boolean`
-
-
- """
-
- _use_schema_map = True
-
- def __init__(
- self,
- name=None,
- schema=None,
- metadata=None,
- inherit_schema=False,
- quote=None,
- _create_events=True,
- ):
- if name is not None:
- self.name = quoted_name(name, quote)
- else:
- self.name = None
- self.schema = schema
- self.metadata = metadata
- self.inherit_schema = inherit_schema
- self._create_events = _create_events
-
- if _create_events and self.metadata:
- event.listen(
- self.metadata,
- "before_create",
- util.portable_instancemethod(self._on_metadata_create),
- )
- event.listen(
- self.metadata,
- "after_drop",
- util.portable_instancemethod(self._on_metadata_drop),
- )
-
- def _set_parent(self, column, **kw):
- column._on_table_attach(util.portable_instancemethod(self._set_table))
-
- def _variant_mapping_for_set_table(self, column):
- if isinstance(column.type, Variant):
- variant_mapping = column.type.mapping.copy()
- variant_mapping["_default"] = column.type.impl
- else:
- variant_mapping = None
- return variant_mapping
-
- def _set_table(self, column, table):
- if self.inherit_schema:
- self.schema = table.schema
- elif self.metadata and self.schema is None and self.metadata.schema:
- self.schema = self.metadata.schema
-
- if not self._create_events:
- return
-
- variant_mapping = self._variant_mapping_for_set_table(column)
-
- event.listen(
- table,
- "before_create",
- util.portable_instancemethod(
- self._on_table_create, {"variant_mapping": variant_mapping}
- ),
- )
- event.listen(
- table,
- "after_drop",
- util.portable_instancemethod(
- self._on_table_drop, {"variant_mapping": variant_mapping}
- ),
- )
- if self.metadata is None:
- # TODO: what's the difference between self.metadata
- # and table.metadata here ?
- event.listen(
- table.metadata,
- "before_create",
- util.portable_instancemethod(
- self._on_metadata_create,
- {"variant_mapping": variant_mapping},
- ),
- )
- event.listen(
- table.metadata,
- "after_drop",
- util.portable_instancemethod(
- self._on_metadata_drop,
- {"variant_mapping": variant_mapping},
- ),
- )
-
- def copy(self, **kw):
- return self.adapt(self.__class__, _create_events=True)
-
- def adapt(self, impltype, **kw):
- schema = kw.pop("schema", self.schema)
- metadata = kw.pop("metadata", self.metadata)
- _create_events = kw.pop("_create_events", False)
- return impltype(
- name=self.name,
- schema=schema,
- inherit_schema=self.inherit_schema,
- metadata=metadata,
- _create_events=_create_events,
- **kw
- )
-
- @property
- def bind(self):
- return self.metadata and self.metadata.bind or None
-
- def create(self, bind=None, checkfirst=False):
- """Issue CREATE DDL for this type, if applicable."""
-
- if bind is None:
- bind = _bind_or_error(self)
- t = self.dialect_impl(bind.dialect)
- if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
- t.create(bind=bind, checkfirst=checkfirst)
-
- def drop(self, bind=None, checkfirst=False):
- """Issue DROP DDL for this type, if applicable."""
-
- if bind is None:
- bind = _bind_or_error(self)
- t = self.dialect_impl(bind.dialect)
- if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
- t.drop(bind=bind, checkfirst=checkfirst)
-
- def _on_table_create(self, target, bind, **kw):
- if not self._is_impl_for_variant(bind.dialect, kw):
- return
-
- t = self.dialect_impl(bind.dialect)
- if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
- t._on_table_create(target, bind, **kw)
-
- def _on_table_drop(self, target, bind, **kw):
- if not self._is_impl_for_variant(bind.dialect, kw):
- return
-
- t = self.dialect_impl(bind.dialect)
- if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
- t._on_table_drop(target, bind, **kw)
-
- def _on_metadata_create(self, target, bind, **kw):
- if not self._is_impl_for_variant(bind.dialect, kw):
- return
-
- t = self.dialect_impl(bind.dialect)
- if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
- t._on_metadata_create(target, bind, **kw)
-
- def _on_metadata_drop(self, target, bind, **kw):
- if not self._is_impl_for_variant(bind.dialect, kw):
- return
-
- t = self.dialect_impl(bind.dialect)
- if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
- t._on_metadata_drop(target, bind, **kw)
-
- def _is_impl_for_variant(self, dialect, kw):
- variant_mapping = kw.pop("variant_mapping", None)
- if variant_mapping is None:
- return True
-
- # since PostgreSQL is the only DB that has ARRAY this can only
- # be integration tested by PG-specific tests
- def _we_are_the_impl(typ):
- return (
- typ is self or isinstance(typ, ARRAY) and typ.item_type is self
- )
-
- if dialect.name in variant_mapping and _we_are_the_impl(
- variant_mapping[dialect.name]
- ):
- return True
- elif dialect.name not in variant_mapping:
- return _we_are_the_impl(variant_mapping["_default"])
-
-
- class Enum(Emulated, String, SchemaType):
- """Generic Enum Type.
-
- 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.
- An option also exists to automatically produce a CHECK constraint
- when the VARCHAR (so called "non-native") variant is produced;
- see the :paramref:`.Enum.create_constraint` flag.
-
- The :class:`.Enum` type also provides in-Python validation of string
- values during both read and write operations. When reading a value
- from the database in a result set, the string value is always checked
- against the list of possible values and a ``LookupError`` is raised
- if no match is found. When passing a value to the database as a
- plain string within a SQL statement, if the
- :paramref:`.Enum.validate_strings` parameter is
- set to True, a ``LookupError`` is raised for any string value that's
- not located in the given list of possible values; note that this
- impacts usage of LIKE expressions with enumerated values (an unusual
- use case).
-
- .. 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 = 1
- two = 2
- three = 3
-
- t = Table(
- 'data', MetaData(),
- Column('value', Enum(MyEnum))
- )
-
- connection.execute(t.insert(), {"value": MyEnum.two})
- assert connection.scalar(t.select()) is MyEnum.two
-
- Above, the string names of each element, e.g. "one", "two", "three",
- are persisted to the database; the values of the Python Enum, here
- indicated as integers, are **not** used; the value of each enum can
- therefore be any kind of Python object whether or not it is persistable.
-
- In order to persist the values and not the names, the
- :paramref:`.Enum.values_callable` parameter may be used. The value of
- this parameter is a user-supplied callable, which is intended to be used
- with a PEP-435-compliant enumerated class and returns a list of string
- values to be persisted. For a simple enumeration that uses string values,
- a callable such as ``lambda x: [e.value for e in x]`` is sufficient.
-
- .. versionadded:: 1.1 - support for PEP-435-style enumerated
- classes.
-
-
- .. seealso::
-
- :class:`_postgresql.ENUM` - PostgreSQL-specific type,
- which has additional functionality.
-
- :class:`.mysql.ENUM` - MySQL-specific type
-
- """
-
- __visit_name__ = "enum"
-
- @util.deprecated_params(
- convert_unicode=(
- "1.3",
- "The :paramref:`.Enum.convert_unicode` parameter is deprecated "
- "and will be removed in a future release. All modern DBAPIs "
- "now support Python Unicode directly and this parameter is "
- "unnecessary.",
- )
- )
- def __init__(self, *enums, **kw):
- r"""Construct an enum.
-
- 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
- or one or more string labels.
-
- .. 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 under Python 2 only.
- Under Python 2, this is set automatically based on the presence of
- unicode label strings. This flag will be removed in SQLAlchemy 2.0.
-
- :param create_constraint: defaults to False. When creating a
- non-native enumerated type, also build a CHECK constraint on the
- database against the valid values.
-
- .. note:: it is strongly recommended that the CHECK constraint
- have an explicit name in order to support schema-management
- concerns. This can be established either by setting the
- :paramref:`.Enum.name` parameter or by setting up an
- appropriate naming convention; see
- :ref:`constraint_naming_conventions` for background.
-
- .. versionchanged:: 1.4 - this flag now defaults to False, meaning
- no CHECK constraint is generated 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
- created and dropped within ``create_all()`` and ``drop_all()``
- operations. If the type is not associated with any ``MetaData``
- object, it will associate itself with each ``Table`` in which it is
- used, and will be created when any of those individual tables are
- created, after a check is performed for its existence. The type is
- only dropped when ``drop_all()`` is called for that ``Table``
- object's metadata, however.
-
- The value of the :paramref:`_schema.MetaData.schema` parameter of
- the :class:`_schema.MetaData` object, if set, will be used as the
- default value of the :paramref:`_types.Enum.schema` on this object
- if an explicit value is not otherwise supplied.
-
- .. versionchanged:: 1.4.12 :class:`_types.Enum` inherits the
- :paramref:`_schema.MetaData.schema` parameter of the
- :class:`_schema.MetaData` object if present, when passed using
- the :paramref:`_types.Enum.metadata` parameter.
-
- :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 a PEP-435 enumerated
- class was used, its name (converted to lower case) is used by
- default.
-
- :param native_enum: Use the database's native ENUM type when
- available. Defaults to True. When False, uses VARCHAR + check
- constraint for all backends. The VARCHAR length can be controlled
- with :paramref:`.Enum.length`
-
- :param length: Allows specifying a custom length for the VARCHAR
- when :paramref:`.Enum.native_enum` is False. By default it uses the
- length of the longest value.
-
- .. versionadded:: 1.3.16
-
- :param schema: Schema name of this type. For types that exist on the
- target database as an independent schema construct (PostgreSQL),
- this parameter specifies the named schema in which the type is
- present.
-
- If not present, the schema name will be taken from the
- :class:`_schema.MetaData` collection if passed as
- :paramref:`_types.Enum.metadata`, for a :class:`_schema.MetaData`
- that includes the :paramref:`_schema.MetaData.schema` parameter.
-
- .. versionchanged:: 1.4.12 :class:`_types.Enum` inherits the
- :paramref:`_schema.MetaData.schema` parameter of the
- :class:`_schema.MetaData` object if present, when passed using
- the :paramref:`_types.Enum.metadata` parameter.
-
- Otherwise, if the :paramref:`_types.Enum.inherit_schema` flag is set
- to ``True``, the schema will be inherited from the associated
- :class:`_schema.Table` object if any; when
- :paramref:`_types.Enum.inherit_schema` is at its default of
- ``False``, the owning table's schema is **not** used.
-
-
- :param quote: Set explicit quoting preferences for the type's name.
-
- :param inherit_schema: When ``True``, the "schema" from the owning
- :class:`_schema.Table`
- will be copied to the "schema" attribute of this
- :class:`.Enum`, replacing whatever value was passed for the
- ``schema`` attribute. This also takes effect when using the
- :meth:`_schema.Table.to_metadata` operation.
-
- :param validate_strings: when True, string values that are being
- passed to the database in a SQL statement will be checked
- for validity against the list of enumerated values. Unrecognized
- values will result in a ``LookupError`` being raised.
-
- .. versionadded:: 1.1.0b2
-
- :param values_callable: A callable which will be passed the PEP-435
- compliant enumerated type, which should then return a list of string
- values to be persisted. This allows for alternate usages such as
- using the string value of an enum to be persisted to the database
- instead of its name.
-
- .. versionadded:: 1.2.3
-
- :param sort_key_function: a Python callable which may be used as the
- "key" argument in the Python ``sorted()`` built-in. The SQLAlchemy
- ORM requires that primary key columns which are mapped must
- be sortable in some way. When using an unsortable enumeration
- object such as a Python 3 ``Enum`` object, this parameter may be
- used to set a default sort key function for the objects. By
- default, the database value of the enumeration is used as the
- sorting function.
-
- .. versionadded:: 1.3.8
-
- :param omit_aliases: A boolean that when true will remove aliases from
- pep 435 enums. For backward compatibility it defaults to ``False``.
- A deprecation warning is raised if the enum has aliases and this
- flag was not set.
-
- .. versionadded:: 1.4.5
-
- .. deprecated:: 1.4 The default will be changed to ``True`` in
- SQLAlchemy 2.0.
-
- """
- 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", False)
- self.values_callable = kw.pop("values_callable", None)
- self._sort_key_function = kw.pop("sort_key_function", NO_ARG)
- length_arg = kw.pop("length", NO_ARG)
- self._omit_aliases = kw.pop("omit_aliases", NO_ARG)
-
- values, objects = self._parse_into_values(enums, kw)
- self._setup_for_values(values, objects, kw)
-
- convert_unicode = kw.pop("convert_unicode", None)
- self.validate_strings = kw.pop("validate_strings", False)
-
- if convert_unicode is None:
- for e in self.enums:
- # this is all py2k logic that can go away for py3k only,
- # "expect unicode" will always be implicitly true
- if isinstance(e, util.text_type):
- _expect_unicode = True
- break
- else:
- _expect_unicode = False
- else:
- _expect_unicode = convert_unicode
-
- if self.enums:
- length = max(len(x) for x in self.enums)
- else:
- length = 0
- if not self.native_enum and length_arg is not NO_ARG:
- if length_arg < length:
- raise ValueError(
- "When provided, length must be larger or equal"
- " than the length of the longest enum value. %s < %s"
- % (length_arg, length)
- )
- length = length_arg
-
- self._valid_lookup[None] = self._object_lookup[None] = None
-
- super(Enum, self).__init__(
- length=length, _expect_unicode=_expect_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]
-
- _members = self.enum_class.__members__
-
- aliases = [n for n, v in _members.items() if v.name != n]
- if self._omit_aliases is NO_ARG and aliases:
- util.warn_deprecated_20(
- "The provided enum %s contains the aliases %s. The "
- "``omit_aliases`` will default to ``True`` in SQLAlchemy "
- "2.0. Specify a value to silence this warning."
- % (self.enum_class.__name__, aliases)
- )
- if self._omit_aliases is True:
- # remove aliases
- members = OrderedDict(
- (n, v) for n, v in _members.items() if v.name == n
- )
- else:
- members = _members
- if self.values_callable:
- values = self.values_callable(self.enum_class)
- else:
- values = list(members)
- objects = [members[k] for k in members]
- 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(reversed(objects), reversed(values)))
-
- self._object_lookup = dict(zip(values, objects))
-
- self._valid_lookup.update(
- [
- (value, self._valid_lookup[self._object_lookup[value]])
- for value in values
- ]
- )
-
- @property
- def sort_key_function(self):
- if self._sort_key_function is NO_ARG:
- return self._db_value_for_elem
- else:
- return self._sort_key_function
-
- @property
- def native(self):
- return self.native_enum
-
- def _db_value_for_elem(self, elem):
- try:
- return self._valid_lookup[elem]
- except KeyError as err:
- # for unknown string values, we return as is. While we can
- # validate these if we wanted, that does not allow for lesser-used
- # end-user use cases, such as using a LIKE comparison with an enum,
- # or for an application that wishes to apply string tests to an
- # ENUM (see [ticket:3725]). While we can decide to differentiate
- # here between an INSERT statement and a criteria used in a SELECT,
- # for now we're staying conservative w/ behavioral changes (perhaps
- # someone has a trigger that handles strings on INSERT)
- if not self.validate_strings and isinstance(
- elem, compat.string_types
- ):
- return elem
- else:
- util.raise_(
- LookupError(
- "'%s' is not among the defined enum values. "
- "Enum name: %s. Possible values: %s"
- % (
- elem,
- self.name,
- langhelpers.repr_tuple_names(self.enums),
- )
- ),
- replace_context=err,
- )
-
- class Comparator(String.Comparator):
- def _adapt_expression(self, op, other_comparator):
- op, typ = super(Enum.Comparator, self)._adapt_expression(
- op, other_comparator
- )
- if op is operators.concat_op:
- typ = String(
- self.type.length, _expect_unicode=self.type._expect_unicode
- )
- return op, typ
-
- comparator_factory = Comparator
-
- def _object_value_for_elem(self, elem):
- try:
- return self._object_lookup[elem]
- except KeyError as err:
- util.raise_(
- LookupError(
- "'%s' is not among the defined enum values. "
- "Enum name: %s. Possible values: %s"
- % (
- elem,
- self.name,
- langhelpers.repr_tuple_names(self.enums),
- )
- ),
- replace_context=err,
- )
-
- def __repr__(self):
- return util.generic_repr(
- self,
- additional_kw=[("native_enum", True)],
- 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)
- 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)
- kw.setdefault("values_callable", self.values_callable)
- kw.setdefault("create_constraint", self.create_constraint)
- kw.setdefault("length", self.length)
- kw.setdefault("omit_aliases", self._omit_aliases)
- 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):
- return False
- return (
- not self.native_enum or not compiler.dialect.supports_native_enum
- )
-
- @util.preload_module("sqlalchemy.sql.schema")
- def _set_table(self, column, table):
- schema = util.preloaded.sql_schema
- SchemaType._set_table(self, column, table)
-
- if not self.create_constraint:
- return
-
- variant_mapping = self._variant_mapping_for_set_table(column)
-
- e = schema.CheckConstraint(
- type_coerce(column, self).in_(self.enums),
- name=_NONE_NAME if self.name is None else self.name,
- _create_rule=util.portable_instancemethod(
- self._should_create_constraint,
- {"variant_mapping": variant_mapping},
- ),
- _type_bound=True,
- )
- assert e.table is table
-
- def literal_processor(self, dialect):
- parent_processor = super(Enum, self).literal_processor(dialect)
-
- 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):
- value = self._db_value_for_elem(value)
- if parent_processor:
- value = parent_processor(value)
- return value
-
- parent_processor = super(Enum, self).bind_processor(dialect)
- return process
-
- def result_processor(self, dialect, coltype):
- parent_processor = super(Enum, self).result_processor(dialect, coltype)
-
- def process(value):
- if parent_processor:
- value = parent_processor(value)
-
- value = self._object_value_for_elem(value)
- return value
-
- return process
-
- def copy(self, **kw):
- return SchemaType.copy(self, **kw)
-
- @property
- def python_type(self):
- if self.enum_class:
- return self.enum_class
- else:
- return super(Enum, self).python_type
-
-
- class PickleType(TypeDecorator):
- """Holds Python objects, which are serialized using pickle.
-
- PickleType builds upon the Binary type to apply Python's
- ``pickle.dumps()`` to incoming objects, and ``pickle.loads()`` on
- the way out, allowing any pickleable Python object to be stored as
- a serialized binary field.
-
- To allow ORM change events to propagate for elements associated
- with :class:`.PickleType`, see :ref:`mutable_toplevel`.
-
- """
-
- impl = LargeBinary
- cache_ok = True
-
- def __init__(
- self,
- protocol=pickle.HIGHEST_PROTOCOL,
- pickler=None,
- comparator=None,
- impl=None,
- ):
- """
- Construct a PickleType.
-
- :param protocol: defaults to ``pickle.HIGHEST_PROTOCOL``.
-
- :param pickler: defaults to cPickle.pickle or pickle.pickle if
- cPickle is not available. May be any object with
- pickle-compatible ``dumps`` and ``loads`` methods.
-
- :param comparator: a 2-arg callable predicate used
- to compare values of this type. If left as ``None``,
- the Python "equals" operator is used to compare values.
-
- :param impl: A binary-storing :class:`_types.TypeEngine` class or
- instance to use in place of the default :class:`_types.LargeBinary`.
- For example the :class: `_mysql.LONGBLOB` class may be more effective
- when using MySQL.
-
- .. versionadded:: 1.4.20
-
- """
- self.protocol = protocol
- self.pickler = pickler or pickle
- self.comparator = comparator
- super(PickleType, self).__init__()
-
- if impl:
- self.impl = to_instance(impl)
-
- def __reduce__(self):
- return PickleType, (self.protocol, None, self.comparator)
-
- def bind_processor(self, dialect):
- impl_processor = self.impl.bind_processor(dialect)
- dumps = self.pickler.dumps
- protocol = self.protocol
- if impl_processor:
-
- def process(value):
- if value is not None:
- value = dumps(value, protocol)
- return impl_processor(value)
-
- else:
-
- def process(value):
- if value is not None:
- value = dumps(value, protocol)
- return value
-
- return process
-
- def result_processor(self, dialect, coltype):
- impl_processor = self.impl.result_processor(dialect, coltype)
- loads = self.pickler.loads
- if impl_processor:
-
- def process(value):
- value = impl_processor(value)
- if value is None:
- return None
- return loads(value)
-
- else:
-
- def process(value):
- if value is None:
- return None
- return loads(value)
-
- return process
-
- def compare_values(self, x, y):
- if self.comparator:
- return self.comparator(x, y)
- else:
- return x == y
-
-
- class Boolean(Emulated, TypeEngine, SchemaType):
-
- """A bool datatype.
-
- :class:`.Boolean` typically uses BOOLEAN or SMALLINT on the DDL side,
- and on the Python side deals in ``True`` or ``False``.
-
- The :class:`.Boolean` datatype currently has two levels of assertion
- that the values persisted are simple true/false values. For all
- backends, only the Python values ``None``, ``True``, ``False``, ``1``
- or ``0`` are accepted as parameter values. For those backends that
- don't support a "native boolean" datatype, an option exists to
- also create a CHECK constraint on the target column
-
- .. versionchanged:: 1.2 the :class:`.Boolean` datatype now asserts that
- incoming Python values are already in pure boolean form.
-
-
- """
-
- __visit_name__ = "boolean"
- native = True
-
- def __init__(
- self, create_constraint=False, name=None, _create_events=True
- ):
- """Construct a Boolean.
-
- :param create_constraint: defaults to False. If the boolean
- is generated as an int/smallint, also create a CHECK constraint
- on the table that ensures 1 or 0 as a value.
-
- .. note:: it is strongly recommended that the CHECK constraint
- have an explicit name in order to support schema-management
- concerns. This can be established either by setting the
- :paramref:`.Boolean.name` parameter or by setting up an
- appropriate naming convention; see
- :ref:`constraint_naming_conventions` for background.
-
- .. versionchanged:: 1.4 - this flag now defaults to False, meaning
- no CHECK constraint is generated for a non-native enumerated
- type.
-
- :param name: if a CHECK constraint is generated, specify
- the name of the constraint.
-
- """
- self.create_constraint = create_constraint
- self.name = name
- self._create_events = _create_events
-
- def _should_create_constraint(self, compiler, **kw):
- if not self._is_impl_for_variant(compiler.dialect, kw):
- return False
- return (
- not compiler.dialect.supports_native_boolean
- and compiler.dialect.non_native_boolean_check_constraint
- )
-
- @util.preload_module("sqlalchemy.sql.schema")
- def _set_table(self, column, table):
- schema = util.preloaded.sql_schema
- if not self.create_constraint:
- return
-
- variant_mapping = self._variant_mapping_for_set_table(column)
-
- e = schema.CheckConstraint(
- type_coerce(column, self).in_([0, 1]),
- name=_NONE_NAME if self.name is None else self.name,
- _create_rule=util.portable_instancemethod(
- self._should_create_constraint,
- {"variant_mapping": variant_mapping},
- ),
- _type_bound=True,
- )
- assert e.table is table
-
- @property
- def python_type(self):
- return bool
-
- _strict_bools = frozenset([None, True, False])
-
- def _strict_as_bool(self, value):
- if value not in self._strict_bools:
- if not isinstance(value, int):
- raise TypeError("Not a boolean value: %r" % value)
- else:
- raise ValueError(
- "Value %r is not None, True, or False" % value
- )
- return value
-
- def literal_processor(self, dialect):
- compiler = dialect.statement_compiler(dialect, None)
- true = compiler.visit_true(None)
- false = compiler.visit_false(None)
-
- def process(value):
- return true if self._strict_as_bool(value) else false
-
- return process
-
- def bind_processor(self, dialect):
- _strict_as_bool = self._strict_as_bool
- if dialect.supports_native_boolean:
- _coerce = bool
- else:
- _coerce = int
-
- def process(value):
- value = _strict_as_bool(value)
- if value is not None:
- value = _coerce(value)
- return value
-
- return process
-
- def result_processor(self, dialect, coltype):
- if dialect.supports_native_boolean:
- return None
- else:
- return processors.int_to_boolean
-
-
- 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.
-
- The Interval type deals with ``datetime.timedelta`` objects. In
- PostgreSQL, the native ``INTERVAL`` type is used; for others, the
- value is stored as a date which is relative to the "epoch"
- (Jan. 1, 1970).
-
- Note that the ``Interval`` type does not currently provide date arithmetic
- operations on platforms which do not support interval types natively. Such
- operations usually require transformation of both sides of the expression
- (such as, conversion of both sides into integer epoch values first) which
- currently is a manual procedure (such as via
- :attr:`~sqlalchemy.sql.expression.func`).
-
- """
-
- impl = DateTime
- epoch = dt.datetime.utcfromtimestamp(0)
- cache_ok = True
-
- def __init__(self, native=True, second_precision=None, day_precision=None):
- """Construct an Interval object.
-
- :param native: when True, use the actual
- INTERVAL type provided by the database, if
- supported (currently PostgreSQL, Oracle).
- Otherwise, represent the interval data as
- an epoch value regardless.
-
- :param second_precision: For native interval types
- which support a "fractional seconds precision" parameter,
- i.e. Oracle and PostgreSQL
-
- :param day_precision: for native interval types which
- support a "day precision" parameter, i.e. Oracle.
-
- """
- super(Interval, self).__init__()
- self.native = native
- self.second_precision = second_precision
- self.day_precision = day_precision
-
- @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
- if impl_processor:
-
- def process(value):
- if value is not None:
- value = epoch + value
- return impl_processor(value)
-
- else:
-
- def process(value):
- if value is not None:
- value = epoch + value
- return value
-
- return process
-
- def result_processor(self, dialect, coltype):
- impl_processor = self.impl.result_processor(dialect, coltype)
- epoch = self.epoch
- if impl_processor:
-
- def process(value):
- value = impl_processor(value)
- if value is None:
- return None
- return value - epoch
-
- else:
-
- def process(value):
- if value is None:
- return None
- return value - epoch
-
- return process
-
-
- class JSON(Indexable, TypeEngine):
- """Represent a SQL JSON type.
-
- .. note:: :class:`_types.JSON`
- is provided as a facade for vendor-specific
- JSON types. Since it supports JSON SQL operations, it only
- works on backends that have an actual JSON type, currently:
-
- * PostgreSQL - see :class:`sqlalchemy.dialects.postgresql.JSON` and
- :class:`sqlalchemy.dialects.postgresql.JSONB` for backend-specific
- notes
-
- * MySQL - see
- :class:`sqlalchemy.dialects.mysql.JSON` for backend-specific notes
-
- * SQLite as of version 3.9 - see
- :class:`sqlalchemy.dialects.sqlite.JSON` for backend-specific notes
-
- * Microsoft SQL Server 2016 and later - see
- :class:`sqlalchemy.dialects.mssql.JSON` for backend-specific notes
-
- :class:`_types.JSON` is part of the Core in support of the growing
- popularity of native JSON datatypes.
-
- The :class:`_types.JSON` type stores arbitrary JSON format data, e.g.::
-
- data_table = Table('data_table', metadata,
- Column('id', Integer, primary_key=True),
- Column('data', JSON)
- )
-
- with engine.connect() as conn:
- conn.execute(
- data_table.insert(),
- data = {"key1": "value1", "key2": "value2"}
- )
-
- **JSON-Specific Expression Operators**
-
- The :class:`_types.JSON`
- datatype provides these additional SQL operations:
-
- * Keyed index operations::
-
- data_table.c.data['some key']
-
- * Integer index operations::
-
- data_table.c.data[3]
-
- * Path index operations::
-
- data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')]
-
- * Data casters for specific JSON element types, subsequent to an index
- or path operation being invoked::
-
- data_table.c.data["some key"].as_integer()
-
- .. versionadded:: 1.3.11
-
- Additional operations may be available from the dialect-specific versions
- of :class:`_types.JSON`, such as
- :class:`sqlalchemy.dialects.postgresql.JSON` and
- :class:`sqlalchemy.dialects.postgresql.JSONB` which both offer additional
- PostgreSQL-specific operations.
-
- **Casting JSON Elements to Other Types**
-
- Index operations, i.e. those invoked by calling upon the expression using
- the Python bracket operator as in ``some_column['some key']``, return an
- expression object whose type defaults to :class:`_types.JSON` by default,
- so that
- further JSON-oriented instructions may be called upon the result type.
- However, it is likely more common that an index operation is expected
- to return a specific scalar element, such as a string or integer. In
- order to provide access to these elements in a backend-agnostic way,
- a series of data casters are provided:
-
- * :meth:`.JSON.Comparator.as_string` - return the element as a string
-
- * :meth:`.JSON.Comparator.as_boolean` - return the element as a boolean
-
- * :meth:`.JSON.Comparator.as_float` - return the element as a float
-
- * :meth:`.JSON.Comparator.as_integer` - return the element as an integer
-
- These data casters are implemented by supporting dialects in order to
- assure that comparisons to the above types will work as expected, such as::
-
- # integer comparison
- data_table.c.data["some_integer_key"].as_integer() == 5
-
- # boolean comparison
- data_table.c.data["some_boolean"].as_boolean() == True
-
- .. versionadded:: 1.3.11 Added type-specific casters for the basic JSON
- data element types.
-
- .. note::
-
- The data caster functions are new in version 1.3.11, and supersede
- the previous documented approaches of using CAST; for reference,
- this looked like::
-
- from sqlalchemy import cast, type_coerce
- from sqlalchemy import String, JSON
- cast(
- data_table.c.data['some_key'], String
- ) == type_coerce(55, JSON)
-
- The above case now works directly as::
-
- data_table.c.data['some_key'].as_integer() == 5
-
- For details on the previous comparison approach within the 1.3.x
- series, see the documentation for SQLAlchemy 1.2 or the included HTML
- files in the doc/ directory of the version's distribution.
-
- **Detecting Changes in JSON columns when using the ORM**
-
- The :class:`_types.JSON` type, when used with the SQLAlchemy ORM, does not
- detect in-place mutations to the structure. In order to detect these, the
- :mod:`sqlalchemy.ext.mutable` extension must be used. This extension will
- allow "in-place" changes to the datastructure to produce events which
- will be detected by the unit of work. See the example at :class:`.HSTORE`
- for a simple example involving a dictionary.
-
- **Support for JSON null vs. SQL NULL**
-
- When working with NULL values, the :class:`_types.JSON`
- type recommends the
- use of two specific constants in order to differentiate between a column
- that evaluates to SQL NULL, e.g. no value, vs. the JSON-encoded string
- of ``"null"``. To insert or select against a value that is SQL NULL,
- use the constant :func:`.null`::
-
- from sqlalchemy import null
- conn.execute(table.insert(), json_value=null())
-
- To insert or select against a value that is JSON ``"null"``, use the
- constant :attr:`_types.JSON.NULL`::
-
- conn.execute(table.insert(), json_value=JSON.NULL)
-
- The :class:`_types.JSON` type supports a flag
- :paramref:`_types.JSON.none_as_null` which when set to True will result
- in the Python constant ``None`` evaluating to the value of SQL
- NULL, and when set to False results in the Python constant
- ``None`` evaluating to the value of JSON ``"null"``. The Python
- value ``None`` may be used in conjunction with either
- :attr:`_types.JSON.NULL` and :func:`.null` in order to indicate NULL
- values, but care must be taken as to the value of the
- :paramref:`_types.JSON.none_as_null` in these cases.
-
- **Customizing the JSON Serializer**
-
- The JSON serializer and deserializer used by :class:`_types.JSON`
- defaults to
- Python's ``json.dumps`` and ``json.loads`` functions; in the case of the
- psycopg2 dialect, psycopg2 may be using its own custom loader function.
-
- In order to affect the serializer / deserializer, they are currently
- configurable at the :func:`_sa.create_engine` level via the
- :paramref:`_sa.create_engine.json_serializer` and
- :paramref:`_sa.create_engine.json_deserializer` parameters. For example,
- to turn off ``ensure_ascii``::
-
- engine = create_engine(
- "sqlite://",
- json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False))
-
- .. versionchanged:: 1.3.7
-
- SQLite dialect's ``json_serializer`` and ``json_deserializer``
- parameters renamed from ``_json_serializer`` and
- ``_json_deserializer``.
-
- .. seealso::
-
- :class:`sqlalchemy.dialects.postgresql.JSON`
-
- :class:`sqlalchemy.dialects.postgresql.JSONB`
-
- :class:`sqlalchemy.dialects.mysql.JSON`
-
- :class:`sqlalchemy.dialects.sqlite.JSON`
-
- .. versionadded:: 1.1
-
-
- """
-
- __visit_name__ = "JSON"
-
- hashable = False
- NULL = util.symbol("JSON_NULL")
- """Describe the json value of NULL.
-
- This value is used to force the JSON value of ``"null"`` to be
- used as the value. A value of Python ``None`` will be recognized
- either as SQL NULL or JSON ``"null"``, based on the setting
- of the :paramref:`_types.JSON.none_as_null` flag; the
- :attr:`_types.JSON.NULL`
- constant can be used to always resolve to JSON ``"null"`` regardless
- of this setting. This is in contrast to the :func:`_expression.null`
- construct,
- which always resolves to SQL NULL. E.g.::
-
- from sqlalchemy import null
- from sqlalchemy.dialects.postgresql import JSON
-
- # will *always* insert SQL NULL
- obj1 = MyObject(json_value=null())
-
- # will *always* insert JSON string "null"
- obj2 = MyObject(json_value=JSON.NULL)
-
- session.add_all([obj1, obj2])
- session.commit()
-
- In order to set JSON NULL as a default value for a column, the most
- transparent method is to use :func:`_expression.text`::
-
- Table(
- 'my_table', metadata,
- Column('json_data', JSON, default=text("'null'"))
- )
-
- While it is possible to use :attr:`_types.JSON.NULL` in this context, the
- :attr:`_types.JSON.NULL` value will be returned as the value of the
- column,
- which in the context of the ORM or other repurposing of the default
- value, may not be desirable. Using a SQL expression means the value
- will be re-fetched from the database within the context of retrieving
- generated defaults.
-
-
- """
-
- def __init__(self, none_as_null=False):
- """Construct a :class:`_types.JSON` type.
-
- :param none_as_null=False: if True, persist the value ``None`` as a
- SQL NULL value, not the JSON encoding of ``null``. Note that
- when this flag is False, the :func:`.null` construct can still
- be used to persist a NULL value::
-
- from sqlalchemy import null
- conn.execute(table.insert(), data=null())
-
- .. note::
-
- :paramref:`_types.JSON.none_as_null` does **not** apply to the
- values passed to :paramref:`_schema.Column.default` and
- :paramref:`_schema.Column.server_default`; a value of ``None``
- passed for these parameters means "no default present".
-
- .. seealso::
-
- :attr:`.types.JSON.NULL`
-
- """
- self.none_as_null = none_as_null
-
- class JSONElementType(TypeEngine):
- """Common function for index / path elements in a JSON expression."""
-
- _integer = Integer()
- _string = String()
-
- def string_bind_processor(self, dialect):
- return self._string._cached_bind_processor(dialect)
-
- def string_literal_processor(self, dialect):
- return self._string._cached_literal_processor(dialect)
-
- def bind_processor(self, dialect):
- int_processor = self._integer._cached_bind_processor(dialect)
- string_processor = self.string_bind_processor(dialect)
-
- def process(value):
- if int_processor and isinstance(value, int):
- value = int_processor(value)
- elif string_processor and isinstance(value, util.string_types):
- value = string_processor(value)
- return value
-
- return process
-
- def literal_processor(self, dialect):
- int_processor = self._integer._cached_literal_processor(dialect)
- string_processor = self.string_literal_processor(dialect)
-
- def process(value):
- if int_processor and isinstance(value, int):
- value = int_processor(value)
- elif string_processor and isinstance(value, util.string_types):
- value = string_processor(value)
- return value
-
- return process
-
- class JSONIndexType(JSONElementType):
- """Placeholder for the datatype of a JSON index value.
-
- This allows execution-time processing of JSON index values
- for special syntaxes.
-
- """
-
- class JSONIntIndexType(JSONIndexType):
- """Placeholder for the datatype of a JSON index value.
-
- This allows execution-time processing of JSON index values
- for special syntaxes.
-
- """
-
- class JSONStrIndexType(JSONIndexType):
- """Placeholder for the datatype of a JSON index value.
-
- This allows execution-time processing of JSON index values
- for special syntaxes.
-
- """
-
- class JSONPathType(JSONElementType):
- """Placeholder type for JSON path operations.
-
- This allows execution-time processing of a path-based
- index value into a specific SQL syntax.
-
- """
-
- class Comparator(Indexable.Comparator, Concatenable.Comparator):
- """Define comparison operations for :class:`_types.JSON`."""
-
- def _setup_getitem(self, index):
- if not isinstance(index, util.string_types) and isinstance(
- index, compat.collections_abc.Sequence
- ):
- index = coercions.expect(
- roles.BinaryElementRole,
- index,
- expr=self.expr,
- operator=operators.json_path_getitem_op,
- bindparam_type=JSON.JSONPathType,
- )
-
- operator = operators.json_path_getitem_op
- else:
- index = coercions.expect(
- roles.BinaryElementRole,
- index,
- expr=self.expr,
- operator=operators.json_getitem_op,
- bindparam_type=JSON.JSONIntIndexType
- if isinstance(index, int)
- else JSON.JSONStrIndexType,
- )
- operator = operators.json_getitem_op
-
- return operator, index, self.type
-
- def as_boolean(self):
- """Cast an indexed value as boolean.
-
- e.g.::
-
- stmt = select(
- mytable.c.json_column['some_data'].as_boolean()
- ).where(
- mytable.c.json_column['some_data'].as_boolean() == True
- )
-
- .. versionadded:: 1.3.11
-
- """
- return self._binary_w_type(Boolean(), "as_boolean")
-
- def as_string(self):
- """Cast an indexed value as string.
-
- e.g.::
-
- stmt = select(
- mytable.c.json_column['some_data'].as_string()
- ).where(
- mytable.c.json_column['some_data'].as_string() ==
- 'some string'
- )
-
- .. versionadded:: 1.3.11
-
- """
- return self._binary_w_type(String(), "as_string")
-
- def as_integer(self):
- """Cast an indexed value as integer.
-
- e.g.::
-
- stmt = select(
- mytable.c.json_column['some_data'].as_integer()
- ).where(
- mytable.c.json_column['some_data'].as_integer() == 5
- )
-
- .. versionadded:: 1.3.11
-
- """
- return self._binary_w_type(Integer(), "as_integer")
-
- def as_float(self):
- """Cast an indexed value as float.
-
- e.g.::
-
- stmt = select(
- mytable.c.json_column['some_data'].as_float()
- ).where(
- mytable.c.json_column['some_data'].as_float() == 29.75
- )
-
- .. versionadded:: 1.3.11
-
- """
- return self._binary_w_type(Float(), "as_float")
-
- def as_numeric(self, precision, scale, asdecimal=True):
- """Cast an indexed value as numeric/decimal.
-
- e.g.::
-
- stmt = select(
- mytable.c.json_column['some_data'].as_numeric(10, 6)
- ).where(
- mytable.c.
- json_column['some_data'].as_numeric(10, 6) == 29.75
- )
-
- .. versionadded:: 1.4.0b2
-
- """
- return self._binary_w_type(
- Numeric(precision, scale, asdecimal=asdecimal), "as_numeric"
- )
-
- def as_json(self):
- """Cast an indexed value as JSON.
-
- e.g.::
-
- stmt = select(mytable.c.json_column['some_data'].as_json())
-
- This is typically the default behavior of indexed elements in any
- case.
-
- Note that comparison of full JSON structures may not be
- supported by all backends.
-
- .. versionadded:: 1.3.11
-
- """
- return self.expr
-
- def _binary_w_type(self, typ, method_name):
- if not isinstance(
- self.expr, elements.BinaryExpression
- ) or self.expr.operator not in (
- operators.json_getitem_op,
- operators.json_path_getitem_op,
- ):
- raise exc.InvalidRequestError(
- "The JSON cast operator JSON.%s() only works with a JSON "
- "index expression e.g. col['q'].%s()"
- % (method_name, method_name)
- )
- expr = self.expr._clone()
- expr.type = typ
- return expr
-
- comparator_factory = Comparator
-
- @property
- def python_type(self):
- return dict
-
- @property
- def should_evaluate_none(self):
- """Alias of :attr:`_types.JSON.none_as_null`"""
- return not self.none_as_null
-
- @should_evaluate_none.setter
- def should_evaluate_none(self, value):
- self.none_as_null = not value
-
- @util.memoized_property
- def _str_impl(self):
- return String(_expect_unicode=True)
-
- def bind_processor(self, dialect):
- string_process = self._str_impl.bind_processor(dialect)
-
- json_serializer = dialect._json_serializer or json.dumps
-
- def process(value):
- if value is self.NULL:
- value = None
- elif isinstance(value, elements.Null) or (
- value is None and self.none_as_null
- ):
- return None
-
- serialized = json_serializer(value)
- if string_process:
- serialized = string_process(serialized)
- return serialized
-
- return process
-
- def result_processor(self, dialect, coltype):
- string_process = self._str_impl.result_processor(dialect, coltype)
- json_deserializer = dialect._json_deserializer or json.loads
-
- def process(value):
- if value is None:
- return None
- if string_process:
- value = string_process(value)
- return json_deserializer(value)
-
- return process
-
-
- class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine):
- """Represent a SQL Array type.
-
- .. note:: This type serves as the basis for all ARRAY operations.
- However, currently **only the PostgreSQL backend has support for SQL
- arrays in SQLAlchemy**. It is recommended to use the PostgreSQL-specific
- :class:`sqlalchemy.dialects.postgresql.ARRAY` type directly when using
- ARRAY types with PostgreSQL, as it provides additional operators
- specific to that backend.
-
- :class:`_types.ARRAY` is part of the Core in support of various SQL
- standard functions such as :class:`_functions.array_agg`
- which explicitly involve
- arrays; however, with the exception of the PostgreSQL backend and possibly
- some third-party dialects, no other SQLAlchemy built-in dialect has support
- for this type.
-
- An :class:`_types.ARRAY` type is constructed given the "type"
- of element::
-
- mytable = Table("mytable", metadata,
- Column("data", ARRAY(Integer))
- )
-
- The above type represents an N-dimensional array,
- meaning a supporting backend such as PostgreSQL will interpret values
- with any number of dimensions automatically. To produce an INSERT
- construct that passes in a 1-dimensional array of integers::
-
- connection.execute(
- mytable.insert(),
- data=[1,2,3]
- )
-
- The :class:`_types.ARRAY` type can be constructed given a fixed number
- of dimensions::
-
- mytable = Table("mytable", metadata,
- Column("data", ARRAY(Integer, dimensions=2))
- )
-
- Sending a number of dimensions is optional, but recommended if the
- datatype is to represent arrays of more than one dimension. This number
- is used:
-
- * When emitting the type declaration itself to the database, e.g.
- ``INTEGER[][]``
-
- * When translating Python values to database values, and vice versa, e.g.
- an ARRAY of :class:`.Unicode` objects uses this number to efficiently
- access the string values inside of array structures without resorting
- to per-row type inspection
-
- * When used with the Python ``getitem`` accessor, the number of dimensions
- serves to define the kind of type that the ``[]`` operator should
- return, e.g. for an ARRAY of INTEGER with two dimensions::
-
- >>> expr = table.c.column[5] # returns ARRAY(Integer, dimensions=1)
- >>> expr = expr[6] # returns Integer
-
- For 1-dimensional arrays, an :class:`_types.ARRAY` instance with no
- dimension parameter will generally assume single-dimensional behaviors.
-
- SQL expressions of type :class:`_types.ARRAY` have support for "index" and
- "slice" behavior. The Python ``[]`` operator works normally here, given
- integer indexes or slices. Arrays default to 1-based indexing.
- The operator produces binary expression
- constructs which will produce the appropriate SQL, both for
- SELECT statements::
-
- select(mytable.c.data[5], mytable.c.data[2:7])
-
- as well as UPDATE statements when the :meth:`_expression.Update.values`
- method
- is used::
-
- mytable.update().values({
- mytable.c.data[5]: 7,
- mytable.c.data[2:7]: [1, 2, 3]
- })
-
- The :class:`_types.ARRAY` type also provides for the operators
- :meth:`.types.ARRAY.Comparator.any` and
- :meth:`.types.ARRAY.Comparator.all`. The PostgreSQL-specific version of
- :class:`_types.ARRAY` also provides additional operators.
-
- .. versionadded:: 1.1.0
-
- .. seealso::
-
- :class:`sqlalchemy.dialects.postgresql.ARRAY`
-
- """
-
- __visit_name__ = "ARRAY"
-
- _is_array = True
-
- zero_indexes = False
- """If True, Python zero-based indexes should be interpreted as one-based
- on the SQL expression side."""
-
- class Comparator(Indexable.Comparator, Concatenable.Comparator):
-
- """Define comparison operations for :class:`_types.ARRAY`.
-
- More operators are available on the dialect-specific form
- of this type. See :class:`.postgresql.ARRAY.Comparator`.
-
- """
-
- def _setup_getitem(self, index):
- if isinstance(index, slice):
- return_type = self.type
- if self.type.zero_indexes:
- index = slice(index.start + 1, index.stop + 1, index.step)
- slice_ = Slice(
- index.start, index.stop, index.step, _name=self.expr.key
- )
- return operators.getitem, slice_, return_type
- else:
- if self.type.zero_indexes:
- index += 1
- if self.type.dimensions is None or self.type.dimensions == 1:
- return_type = self.type.item_type
- else:
- adapt_kw = {"dimensions": self.type.dimensions - 1}
- return_type = self.type.adapt(
- self.type.__class__, **adapt_kw
- )
-
- return operators.getitem, index, return_type
-
- def contains(self, *arg, **kw):
- raise NotImplementedError(
- "ARRAY.contains() not implemented for the base "
- "ARRAY type; please use the dialect-specific ARRAY type"
- )
-
- @util.preload_module("sqlalchemy.sql.elements")
- def any(self, other, operator=None):
- """Return ``other operator ANY (array)`` clause.
-
- Argument places are switched, because ANY requires array
- expression to be on the right hand-side.
-
- E.g.::
-
- from sqlalchemy.sql import operators
-
- conn.execute(
- select(table.c.data).where(
- table.c.data.any(7, operator=operators.lt)
- )
- )
-
- :param other: expression to be compared
- :param operator: an operator object from the
- :mod:`sqlalchemy.sql.operators`
- package, defaults to :func:`.operators.eq`.
-
- .. seealso::
-
- :func:`_expression.any_`
-
- :meth:`.types.ARRAY.Comparator.all`
-
- """
- elements = util.preloaded.sql_elements
- operator = operator if operator else operators.eq
-
- # send plain BinaryExpression so that negate remains at None,
- # leading to NOT expr for negation.
- return elements.BinaryExpression(
- coercions.expect(roles.ExpressionElementRole, other),
- elements.CollectionAggregate._create_any(self.expr),
- operator,
- )
-
- @util.preload_module("sqlalchemy.sql.elements")
- def all(self, other, operator=None):
- """Return ``other operator ALL (array)`` clause.
-
- Argument places are switched, because ALL requires array
- expression to be on the right hand-side.
-
- E.g.::
-
- from sqlalchemy.sql import operators
-
- conn.execute(
- select(table.c.data).where(
- table.c.data.all(7, operator=operators.lt)
- )
- )
-
- :param other: expression to be compared
- :param operator: an operator object from the
- :mod:`sqlalchemy.sql.operators`
- package, defaults to :func:`.operators.eq`.
-
- .. seealso::
-
- :func:`_expression.all_`
-
- :meth:`.types.ARRAY.Comparator.any`
-
- """
- elements = util.preloaded.sql_elements
- operator = operator if operator else operators.eq
-
- # send plain BinaryExpression so that negate remains at None,
- # leading to NOT expr for negation.
- return elements.BinaryExpression(
- coercions.expect(roles.ExpressionElementRole, other),
- elements.CollectionAggregate._create_all(self.expr),
- operator,
- )
-
- comparator_factory = Comparator
-
- def __init__(
- self, item_type, as_tuple=False, dimensions=None, zero_indexes=False
- ):
- """Construct an :class:`_types.ARRAY`.
-
- E.g.::
-
- Column('myarray', ARRAY(Integer))
-
- Arguments are:
-
- :param item_type: The data type of items of this array. Note that
- dimensionality is irrelevant here, so multi-dimensional arrays like
- ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as
- ``ARRAY(ARRAY(Integer))`` or such.
-
- :param as_tuple=False: Specify whether return results
- should be converted to tuples from lists. This parameter is
- not generally needed as a Python list corresponds well
- to a SQL array.
-
- :param dimensions: if non-None, the ARRAY will assume a fixed
- number of dimensions. This impacts how the array is declared
- on the database, how it goes about interpreting Python and
- result values, as well as how expression behavior in conjunction
- with the "getitem" operator works. See the description at
- :class:`_types.ARRAY` for additional detail.
-
- :param zero_indexes=False: when True, index values will be converted
- between Python zero-based and SQL one-based indexes, e.g.
- a value of one will be added to all index values before passing
- to the database.
-
- """
- if isinstance(item_type, ARRAY):
- raise ValueError(
- "Do not nest ARRAY types; ARRAY(basetype) "
- "handles multi-dimensional arrays of basetype"
- )
- if isinstance(item_type, type):
- item_type = item_type()
- self.item_type = item_type
- self.as_tuple = as_tuple
- self.dimensions = dimensions
- self.zero_indexes = zero_indexes
-
- @property
- def hashable(self):
- return self.as_tuple
-
- @property
- def python_type(self):
- return list
-
- def compare_values(self, x, y):
- return x == y
-
- def _set_parent(self, column, outer=False, **kw):
- """Support SchemaEventTarget"""
-
- if not outer and isinstance(self.item_type, SchemaEventTarget):
- self.item_type._set_parent(column, **kw)
-
- def _set_parent_with_dispatch(self, parent):
- """Support SchemaEventTarget"""
-
- super(ARRAY, self)._set_parent_with_dispatch(parent, outer=True)
-
- if isinstance(self.item_type, SchemaEventTarget):
- self.item_type._set_parent_with_dispatch(parent)
-
-
- class TupleType(TypeEngine):
- """represent the composite type of a Tuple."""
-
- _is_tuple_type = True
-
- def __init__(self, *types):
- self._fully_typed = NULLTYPE not in types
- self.types = types
-
- def _resolve_values_to_types(self, value):
- if self._fully_typed:
- return self
- else:
- return TupleType(
- *[
- _resolve_value_to_type(elem) if typ is NULLTYPE else typ
- for typ, elem in zip(self.types, value)
- ]
- )
-
- def result_processor(self, dialect, coltype):
- raise NotImplementedError(
- "The tuple type does not support being fetched "
- "as a column in a result row."
- )
-
-
- class REAL(Float):
-
- """The SQL REAL type."""
-
- __visit_name__ = "REAL"
-
-
- class FLOAT(Float):
-
- """The SQL FLOAT type."""
-
- __visit_name__ = "FLOAT"
-
-
- class NUMERIC(Numeric):
-
- """The SQL NUMERIC type."""
-
- __visit_name__ = "NUMERIC"
-
-
- class DECIMAL(Numeric):
-
- """The SQL DECIMAL type."""
-
- __visit_name__ = "DECIMAL"
-
-
- class INTEGER(Integer):
-
- """The SQL INT or INTEGER type."""
-
- __visit_name__ = "INTEGER"
-
-
- INT = INTEGER
-
-
- class SMALLINT(SmallInteger):
-
- """The SQL SMALLINT type."""
-
- __visit_name__ = "SMALLINT"
-
-
- class BIGINT(BigInteger):
-
- """The SQL BIGINT type."""
-
- __visit_name__ = "BIGINT"
-
-
- class TIMESTAMP(DateTime):
-
- """The SQL TIMESTAMP type.
-
- :class:`_types.TIMESTAMP` datatypes have support for timezone
- storage on some backends, such as PostgreSQL and Oracle. Use the
- :paramref:`~types.TIMESTAMP.timezone` argument in order to enable
- "TIMESTAMP WITH TIMEZONE" for these backends.
-
- """
-
- __visit_name__ = "TIMESTAMP"
-
- def __init__(self, timezone=False):
- """Construct a new :class:`_types.TIMESTAMP`.
-
- :param timezone: boolean. Indicates that the TIMESTAMP type should
- enable timezone support, if available on the target database.
- On a per-dialect basis is similar to "TIMESTAMP WITH TIMEZONE".
- If the target database does not support timezones, this flag is
- ignored.
-
-
- """
- super(TIMESTAMP, self).__init__(timezone=timezone)
-
- def get_dbapi_type(self, dbapi):
- return dbapi.TIMESTAMP
-
-
- class DATETIME(DateTime):
-
- """The SQL DATETIME type."""
-
- __visit_name__ = "DATETIME"
-
-
- class DATE(Date):
-
- """The SQL DATE type."""
-
- __visit_name__ = "DATE"
-
-
- class TIME(Time):
-
- """The SQL TIME type."""
-
- __visit_name__ = "TIME"
-
-
- class TEXT(Text):
-
- """The SQL TEXT type."""
-
- __visit_name__ = "TEXT"
-
-
- class CLOB(Text):
-
- """The CLOB type.
-
- This type is found in Oracle and Informix.
- """
-
- __visit_name__ = "CLOB"
-
-
- class VARCHAR(String):
-
- """The SQL VARCHAR type."""
-
- __visit_name__ = "VARCHAR"
-
-
- class NVARCHAR(Unicode):
-
- """The SQL NVARCHAR type."""
-
- __visit_name__ = "NVARCHAR"
-
-
- class CHAR(String):
-
- """The SQL CHAR type."""
-
- __visit_name__ = "CHAR"
-
-
- class NCHAR(Unicode):
-
- """The SQL NCHAR type."""
-
- __visit_name__ = "NCHAR"
-
-
- class BLOB(LargeBinary):
-
- """The SQL BLOB type."""
-
- __visit_name__ = "BLOB"
-
-
- class BINARY(_Binary):
-
- """The SQL BINARY type."""
-
- __visit_name__ = "BINARY"
-
-
- class VARBINARY(_Binary):
-
- """The SQL VARBINARY type."""
-
- __visit_name__ = "VARBINARY"
-
-
- class BOOLEAN(Boolean):
-
- """The SQL BOOLEAN type."""
-
- __visit_name__ = "BOOLEAN"
-
-
- class NullType(TypeEngine):
-
- """An unknown type.
-
- :class:`.NullType` is used as a default type for those cases where
- a type cannot be determined, including:
-
- * During table reflection, when the type of a column is not recognized
- by the :class:`.Dialect`
- * When constructing SQL expressions using plain Python objects of
- unknown types (e.g. ``somecolumn == my_special_object``)
- * When a new :class:`_schema.Column` is created,
- and the given type is passed
- as ``None`` or is not passed at all.
-
- The :class:`.NullType` can be used within SQL expression invocation
- without issue, it just has no behavior either at the expression
- construction level or at the bind-parameter/result processing level.
- :class:`.NullType` will result in a :exc:`.CompileError` if the compiler
- is asked to render the type itself, such as if it is used in a
- :func:`.cast` operation or within a schema creation operation such as that
- invoked by :meth:`_schema.MetaData.create_all` or the
- :class:`.CreateTable`
- construct.
-
- """
-
- __visit_name__ = "null"
-
- _isnull = True
-
- hashable = False
-
- def literal_processor(self, dialect):
- def process(value):
- raise exc.CompileError(
- "Don't know how to render literal SQL value: %r" % value
- )
-
- return process
-
- class Comparator(TypeEngine.Comparator):
- def _adapt_expression(self, op, other_comparator):
- if isinstance(
- other_comparator, NullType.Comparator
- ) or not operators.is_commutative(op):
- return op, self.expr.type
- else:
- return other_comparator._adapt_expression(op, self)
-
- comparator_factory = Comparator
-
-
- class TableValueType(HasCacheKey, TypeEngine):
- """Refers to a table value type."""
-
- _is_table_value = True
-
- _traverse_internals = [
- ("_elements", InternalTraversal.dp_clauseelement_list),
- ]
-
- def __init__(self, *elements):
- self._elements = [
- coercions.expect(roles.StrAsPlainColumnRole, elem)
- for elem in elements
- ]
-
-
- class MatchType(Boolean):
- """Refers to the return type of the MATCH operator.
-
- As the :meth:`.ColumnOperators.match` is probably the most open-ended
- operator in generic SQLAlchemy Core, we can't assume the return type
- at SQL evaluation time, as MySQL returns a floating point, not a boolean,
- and other backends might do something different. So this type
- acts as a placeholder, currently subclassing :class:`.Boolean`.
- The type allows dialects to inject result-processing functionality
- if needed, and on MySQL will return floating-point values.
-
- .. versionadded:: 1.0.0
-
- """
-
-
- NULLTYPE = NullType()
- BOOLEANTYPE = Boolean()
- STRINGTYPE = String()
- INTEGERTYPE = Integer()
- MATCHTYPE = MatchType()
- TABLEVALUE = TableValueType()
-
- _type_map = {
- int: Integer(),
- float: Float(),
- bool: BOOLEANTYPE,
- decimal.Decimal: Numeric(),
- dt.date: Date(),
- dt.datetime: DateTime(),
- dt.time: Time(),
- dt.timedelta: Interval(),
- util.NoneType: NULLTYPE,
- }
-
- if util.py3k:
- _type_map[bytes] = LargeBinary() # noqa
- _type_map[str] = Unicode()
- else:
- _type_map[unicode] = Unicode() # noqa
- _type_map[str] = String()
-
-
- _type_map_get = _type_map.get
-
-
- def _resolve_value_to_type(value):
- _result_type = _type_map_get(type(value), False)
- if _result_type is False:
- # use inspect() to detect SQLAlchemy built-in
- # objects.
- insp = inspection.inspect(value, False)
- if (
- insp is not None
- and
- # foil mock.Mock() and other impostors by ensuring
- # the inspection target itself self-inspects
- insp.__class__ in inspection._registrars
- ):
- raise exc.ArgumentError(
- "Object %r is not legal as a SQL literal value" % value
- )
- return NULLTYPE
- else:
- return _result_type
-
-
- # back-assign to type_api
- type_api.BOOLEANTYPE = BOOLEANTYPE
- type_api.STRINGTYPE = STRINGTYPE
- type_api.INTEGERTYPE = INTEGERTYPE
- type_api.NULLTYPE = NULLTYPE
- type_api.MATCHTYPE = MATCHTYPE
- type_api.INDEXABLE = Indexable
- type_api.TABLEVALUE = TABLEVALUE
- type_api._resolve_value_to_type = _resolve_value_to_type
- TypeEngine.Comparator.BOOLEANTYPE = BOOLEANTYPE
|