10
10
import collections .abc
11
11
import struct
12
12
import time
13
+ import warnings
13
14
14
15
from . import compat
15
16
from . import connect_utils
@@ -762,22 +763,121 @@ async def _copy_in_records(self, copy_stmt, records, intro_stmt, timeout):
762
763
copy_stmt , None , None , records , intro_stmt , timeout )
763
764
764
765
async def set_type_codec (self , typename , * ,
765
- schema = 'public' , encoder , decoder , binary = False ):
766
+ schema = 'public' , encoder , decoder ,
767
+ binary = None , format = 'text' ):
766
768
"""Set an encoder/decoder pair for the specified data type.
767
769
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
+
778
870
"""
779
871
self ._check_open ()
780
872
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
+
781
881
if self ._type_by_name_stmt is None :
782
882
self ._type_by_name_stmt = await self .prepare (
783
883
introspection .TYPE_BY_NAME )
@@ -795,7 +895,40 @@ async def set_type_codec(self, typename, *,
795
895
796
896
self ._protocol .get_settings ().add_python_codec (
797
897
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 ()
799
932
800
933
async def set_builtin_type_codec (self , typename , * ,
801
934
schema = 'public' , codec_name ):
@@ -826,6 +959,9 @@ async def set_builtin_type_codec(self, typename, *,
826
959
self ._protocol .get_settings ().set_builtin_type_codec (
827
960
oid , typename , schema , 'scalar' , codec_name )
828
961
962
+ # Statement cache is no longer valid due to codec changes.
963
+ self ._drop_local_statement_cache ()
964
+
829
965
def is_closed (self ):
830
966
"""Return ``True`` if the connection is closed, ``False`` otherwise.
831
967
0 commit comments