]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Remove "subclass existing types" use case
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 29 Mar 2019 13:42:43 +0000 (09:42 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 29 Mar 2019 13:46:55 +0000 (09:46 -0400)
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

doc/build/changelog/unreleased_13/4580.rst [new file with mode: 0644]
doc/build/core/custom_types.rst
lib/sqlalchemy/dialects/postgresql/base.py

diff --git a/doc/build/changelog/unreleased_13/4580.rst b/doc/build/changelog/unreleased_13/4580.rst
new file mode 100644 (file)
index 0000000..f646b7c
--- /dev/null
@@ -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.
index 196f76b3cd9873ad0c4ca27dd264940311f6cc8d..dc7a3d9b8e3b658f256d3bf1c59217f7624b35a6 100644 (file)
@@ -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)
index 3781a7ba22ddba81618c4fefc5ade2f4b6bf30bd..ceb62464429bbc5625cd472e356cdd425facadb2 100644 (file)
@@ -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)