summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-03-29 09:42:43 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2019-03-29 09:46:55 -0400
commitfd2ecb5f87c1c0132263b5a35067c4bb76160fb2 (patch)
tree4559cdf6cf6c299542a15b17d8d568d56be4f3b2
parent9168aae5e7cc9a753acf7e132c89e35eb56bb4ea (diff)
downloadsqlalchemy-fd2ecb5f87c1c0132263b5a35067c4bb76160fb2.tar.gz
Remove "subclass existing types" use case
Thanks to :ref:`change_3981`, we no longer need to rely on recipes that subclass dialect-specific types directly, :class:`.TypeDecorator` can now handle all cases. Additionally, the above change made it slightly less likely that a direct subclass of a base SQLAlchemy type would work as expected, which could be misleading. Documentation has been updated to use :class:`.TypeDecorator` for these examples including the PostgreSQL "ArrayOfEnum" example datatype and direct support for the "subclass a type directly" has been removed. Fixes: #4580 Change-Id: I866f246cccc736ea618dc965ab3604762f7a52fe
-rw-r--r--doc/build/changelog/unreleased_13/4580.rst12
-rw-r--r--doc/build/core/custom_types.rst67
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py11
3 files changed, 34 insertions, 56 deletions
diff --git a/doc/build/changelog/unreleased_13/4580.rst b/doc/build/changelog/unreleased_13/4580.rst
new file mode 100644
index 000000000..f646b7c0c
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/4580.rst
@@ -0,0 +1,12 @@
+.. change::
+ :tags: bug, documentation, sql
+ :tickets: 4580
+
+ Thanks to :ref:`change_3981`, we no longer need to rely on recipes that
+ subclass dialect-specific types directly, :class:`.TypeDecorator` can now
+ handle all cases. Additionally, the above change made it slightly less
+ likely that a direct subclass of a base SQLAlchemy type would work as
+ expected, which could be misleading. Documentation has been updated to use
+ :class:`.TypeDecorator` for these examples including the PostgreSQL
+ "ArrayOfEnum" example datatype and direct support for the "subclass a type
+ directly" has been removed.
diff --git a/doc/build/core/custom_types.rst b/doc/build/core/custom_types.rst
index 196f76b3c..dc7a3d9b8 100644
--- a/doc/build/core/custom_types.rst
+++ b/doc/build/core/custom_types.rst
@@ -48,10 +48,11 @@ to and from the database is required.
.. note::
The bind- and result-processing of :class:`.TypeDecorator`
- is *in addition* to the processing already performed by the hosted
+ is **in addition** to the processing already performed by the hosted
type, which is customized by SQLAlchemy on a per-DBAPI basis to perform
- processing specific to that DBAPI. To change the DBAPI-level processing
- for an existing type, see the section :ref:`replacing_processors`.
+ processing specific to that DBAPI. While it is possible to replace this
+ handling for a given type through direct subclassing, it is never needed in
+ practice and SQLAlchemy no longer supports this as a public use case.
.. autoclass:: TypeDecorator
:members:
@@ -283,56 +284,12 @@ have no meaning with a JSON object such as "LIKE", rather than automatically
coercing to text.
-.. _replacing_processors:
-
-Replacing the Bind/Result Processing of Existing Types
-------------------------------------------------------
-
-Most augmentation of type behavior at the bind/result level
-is achieved using :class:`.TypeDecorator`. For the rare scenario
-where the specific processing applied by SQLAlchemy at the DBAPI
-level needs to be replaced, the SQLAlchemy type can be subclassed
-directly, and the ``bind_processor()`` or ``result_processor()``
-methods can be overridden. Doing so requires that the
-``adapt()`` method also be overridden. This method is the mechanism
-by which SQLAlchemy produces DBAPI-specific type behavior during
-statement execution. Overriding it allows a copy of the custom
-type to be used in lieu of a DBAPI-specific type. Below we subclass
-the :class:`.types.TIME` type to have custom result processing behavior.
-The ``process()`` function will receive ``value`` from the DBAPI
-cursor directly::
-
- class MySpecialTime(TIME):
- def __init__(self, special_argument):
- super(MySpecialTime, self).__init__()
- self.special_argument = special_argument
-
- def result_processor(self, dialect, coltype):
- import datetime
- time = datetime.time
- def process(value):
- if value is not None:
- microseconds = value.microseconds
- seconds = value.seconds
- minutes = seconds / 60
- return time(
- minutes / 60,
- minutes % 60,
- seconds - minutes * 60,
- microseconds)
- else:
- return None
- return process
-
- def adapt(self, impltype, **kw):
- return MySpecialTime(self.special_argument)
-
.. _types_sql_value_processing:
Applying SQL-level Bind/Result Processing
-----------------------------------------
-As seen in the sections :ref:`types_typedecorator` and :ref:`replacing_processors`,
+As seen in the section :ref:`types_typedecorator`,
SQLAlchemy allows Python functions to be invoked both when parameters are sent
to a statement, as well as when result rows are loaded from the database, to apply
transformations to the values as they are sent to or from the database. It is also
@@ -401,19 +358,21 @@ Output::
SELECT ST_AsText(geometry.geom_data) AS my_data
FROM geometry
-For an example of subclassing a built in type directly, we subclass
+Another example is we decorate
:class:`.postgresql.BYTEA` to provide a ``PGPString``, which will make use of the
PostgreSQL ``pgcrypto`` extension to encrypt/decrypt values
transparently::
from sqlalchemy import create_engine, String, select, func, \
- MetaData, Table, Column, type_coerce
+ MetaData, Table, Column, type_coerce, TypeDecorator
from sqlalchemy.dialects.postgresql import BYTEA
- class PGPString(BYTEA):
- def __init__(self, passphrase, length=None):
- super(PGPString, self).__init__(length)
+ class PGPString(TypeDecorator):
+ impl = BYTEA
+
+ def __init__(self, passphrase):
+ super(PGPString, self).__init__()
self.passphrase = passphrase
def bind_expression(self, bindvalue):
@@ -430,7 +389,7 @@ transparently::
message = Table('message', metadata,
Column('username', String(50)),
Column('message',
- PGPString("this is my passphrase", length=1000)),
+ PGPString("this is my passphrase")),
)
engine = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 3781a7ba2..ceb624644 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -857,9 +857,16 @@ Using ENUM with ARRAY
The combination of ENUM and ARRAY is not directly supported by backend
DBAPIs at this time. In order to send and receive an ARRAY of ENUM,
-use the following workaround type::
+use the following workaround type, which decorates the
+:class:`.postgresql.ARRAY` datatype.
- class ArrayOfEnum(ARRAY):
+.. sourcecode:: python
+
+ from sqlalchemy import TypeDecorator
+ from sqlalchemy.dialects.postgresql import ARRAY
+
+ class ArrayOfEnum(TypeDecorator):
+ impl = ARRAY
def bind_expression(self, bindvalue):
return sa.cast(bindvalue, self)