1010import collections .abc
1111import struct
1212import time
13+ import warnings
1314
1415from . import compat
1516from . import connect_utils
@@ -762,22 +763,121 @@ async def _copy_in_records(self, copy_stmt, records, intro_stmt, timeout):
762763 copy_stmt , None , None , records , intro_stmt , timeout )
763764
764765 async def set_type_codec (self , typename , * ,
765- schema = 'public' , encoder , decoder , binary = False ):
766+ schema = 'public' , encoder , decoder ,
767+ binary = None , format = 'text' ):
766768 """Set an encoder/decoder pair for the specified data type.
767769
768- :param typename: Name of the data type the codec is for.
769- :param schema: Schema name of the data type the codec is for
770- (defaults to 'public')
771- :param encoder: Callable accepting a single argument and returning
772- a string or a bytes object (if `binary` is True).
773- :param decoder: Callable accepting a single string or bytes argument
774- and returning a decoded object.
775- :param binary: Specifies whether the codec is able to handle binary
776- data. If ``False`` (the default), the data is
777- expected to be encoded/decoded in text.
770+ :param typename:
771+ Name of the data type the codec is for.
772+
773+ :param schema:
774+ Schema name of the data type the codec is for
775+ (defaults to ``'public'``)
776+
777+ :param format:
778+ The type of the argument received by the *decoder* callback,
779+ and the type of the *encoder* callback return value.
780+
781+ If *format* is ``'text'`` (the default), the exchange datum is a
782+ ``str`` instance containing valid text representation of the
783+ data type.
784+
785+ If *format* is ``'binary'``, the exchange datum is a ``bytes``
786+ instance containing valid _binary_ representation of the
787+ data type.
788+
789+ If *format* is ``'tuple'``, the exchange datum is a type-specific
790+ ``tuple`` of values. The table below lists supported data
791+ types and their format for this mode.
792+
793+ +-----------------+---------------------------------------------+
794+ | Type | Tuple layout |
795+ +=================+=============================================+
796+ | ``interval`` | (``months``, ``days``, ``seconds``, |
797+ | | ``microseconds``) |
798+ +-----------------+---------------------------------------------+
799+ | ``date`` | (``date ordinal relative to Jan 1 2000``,) |
800+ | | ``-2^31`` for negative infinity timestamp |
801+ | | ``2^31-1`` for positive infinity timestamp. |
802+ +-----------------+---------------------------------------------+
803+ | ``timestamp`` | (``microseconds relative to Jan 1 2000``,) |
804+ | | ``-2^63`` for negative infinity timestamp |
805+ | | ``2^63-1`` for positive infinity timestamp. |
806+ +-----------------+---------------------------------------------+
807+ | ``timestamp | (``microseconds relative to Jan 1 2000 |
808+ | with time zone``| UTC``,) |
809+ | | ``-2^63`` for negative infinity timestamp |
810+ | | ``2^63-1`` for positive infinity timestamp. |
811+ +-----------------+---------------------------------------------+
812+ | ``time`` | (``microseconds``,) |
813+ +-----------------+---------------------------------------------+
814+ | ``time with | (``microseconds``, |
815+ | time zone`` | ``time zone offset in seconds``) |
816+ +-----------------+---------------------------------------------+
817+
818+ :param encoder:
819+ Callable accepting a Python object as a single argument and
820+ returning a value encoded according to *format*.
821+
822+ :param decoder:
823+ Callable accepting a single argument encoded according to *format*
824+ and returning a decoded Python object.
825+
826+ :param binary:
827+ **Deprecated**. Use *format* instead.
828+
829+ Example:
830+
831+ .. code-block:: pycon
832+
833+ >>> import asyncpg
834+ >>> import asyncio
835+ >>> import datetime
836+ >>> from dateutil.relativedelta import relativedelta
837+ >>> async def run():
838+ ... con = await asyncpg.connect(user='postgres')
839+ ... def encoder(delta):
840+ ... ndelta = delta.normalized()
841+ ... return (ndelta.years * 12 + ndelta.months,
842+ ... ndelta.days,
843+ ... (ndelta.hours * 3600 +
844+ ... ndelta.minutes * 60 +
845+ ... ndelta.seconds),
846+ ... ndelta.microseconds)
847+ ... def decoder(tup):
848+ ... return relativedelta(months=tup[0], days=tup[1],
849+ ... seconds=tup[2],
850+ ... microseconds=tup[3])
851+ ... await con.set_type_codec(
852+ ... 'interval', schema='pg_catalog', encoder=encoder,
853+ ... decoder=decoder, format='tuple')
854+ ... result = await con.fetchval(
855+ ... "SELECT '2 years 3 mons 1 day'::interval")
856+ ... print(result)
857+ ... print(datetime.datetime(2002, 1, 1) + result)
858+ >>> asyncio.get_event_loop().run_until_complete(run())
859+ relativedelta(years=+2, months=+3, days=+1)
860+ 2004-04-02 00:00:00
861+
862+ .. versionadded:: 0.12.0
863+ Added the ``format`` keyword argument and support for 'tuple'
864+ format.
865+
866+ .. versionchanged:: 0.12.0
867+ The ``binary`` keyword argument is deprecated in favor of
868+ ``format``.
869+
778870 """
779871 self ._check_open ()
780872
873+ if binary is not None :
874+ format = 'binary' if binary else 'text'
875+ warnings .warn (
876+ "The `binary` keyword argument to "
877+ "set_type_codec() is deprecated and will be removed in "
878+ "asyncpg 0.13.0. Use the `format` keyword argument instead." ,
879+ DeprecationWarning , stacklevel = 2 )
880+
781881 if self ._type_by_name_stmt is None :
782882 self ._type_by_name_stmt = await self .prepare (
783883 introspection .TYPE_BY_NAME )
@@ -795,7 +895,40 @@ async def set_type_codec(self, typename, *,
795895
796896 self ._protocol .get_settings ().add_python_codec (
797897 oid , typename , schema , 'scalar' ,
798- encoder , decoder , binary )
898+ encoder , decoder , format )
899+
900+ # Statement cache is no longer valid due to codec changes.
901+ self ._drop_local_statement_cache ()
902+
903+ async def reset_type_codec (self , typename , * , schema = 'public' ):
904+ """Reset *typename* codec to the default implementation.
905+
906+ :param typename:
907+ Name of the data type the codec is for.
908+
909+ :param schema:
910+ Schema name of the data type the codec is for
911+ (defaults to ``'public'``)
912+
913+ .. versionadded:: 0.12.0
914+ """
915+
916+ if self ._type_by_name_stmt is None :
917+ self ._type_by_name_stmt = await self .prepare (
918+ introspection .TYPE_BY_NAME )
919+
920+ typeinfo = await self ._type_by_name_stmt .fetchrow (
921+ typename , schema )
922+ if not typeinfo :
923+ raise ValueError ('unknown type: {}.{}' .format (schema , typename ))
924+
925+ oid = typeinfo ['oid' ]
926+
927+ self ._protocol .get_settings ().remove_python_codec (
928+ oid , typename , schema )
929+
930+ # Statement cache is no longer valid due to codec changes.
931+ self ._drop_local_statement_cache ()
799932
800933 async def set_builtin_type_codec (self , typename , * ,
801934 schema = 'public' , codec_name ):
@@ -826,6 +959,9 @@ async def set_builtin_type_codec(self, typename, *,
826959 self ._protocol .get_settings ().set_builtin_type_codec (
827960 oid , typename , schema , 'scalar' , codec_name )
828961
962+ # Statement cache is no longer valid due to codec changes.
963+ self ._drop_local_statement_cache ()
964+
829965 def is_closed (self ):
830966 """Return ``True`` if the connection is closed, ``False`` otherwise.
831967
0 commit comments