[Python-modules-commits] [asyncpg] 01/03: Import asyncpg_0.12.0.orig.tar.gz

Piotr Ożarowski piotr at moszumanska.debian.org
Mon Jul 10 09:27:11 UTC 2017


This is an automated email from the git hooks/post-receive script.

piotr pushed a commit to branch master
in repository asyncpg.

commit a19dd240c66a4a3e38784046adbfda1a43d51529
Author: Piotr Ożarowski <piotr at debian.org>
Date:   Mon Jul 10 11:21:16 2017 +0200

    Import asyncpg_0.12.0.orig.tar.gz
---
 PKG-INFO                             |     2 +-
 asyncpg.egg-info/PKG-INFO            |     2 +-
 asyncpg/connection.py                |   257 +-
 asyncpg/exceptions/__init__.py       |    28 +-
 asyncpg/exceptions/_base.py          |    89 +-
 asyncpg/pool.py                      |    35 +-
 asyncpg/protocol/codecs/base.pxd     |    23 +-
 asyncpg/protocol/codecs/base.pyx     |   193 +-
 asyncpg/protocol/codecs/datetime.pyx |   173 +-
 asyncpg/protocol/codecs/int.pyx      |    48 +-
 asyncpg/protocol/codecs/numeric.pyx  |   303 +-
 asyncpg/protocol/consts.pxi          |     1 +
 asyncpg/protocol/coreproto.pxd       |     1 +
 asyncpg/protocol/coreproto.pyx       |    12 +-
 asyncpg/protocol/protocol.c          | 20599 ++++++++++++++++++++++-----------
 asyncpg/protocol/protocol.pyx        |    21 +-
 asyncpg/protocol/python.pxd          |     1 +
 asyncpg/protocol/record/recordobj.c  |    26 +-
 asyncpg/protocol/settings.pxd        |     6 +-
 asyncpg/protocol/settings.pyx        |    29 +-
 docs/conf.py                         |     7 +-
 docs/faq.rst                         |     2 +-
 docs/usage.rst                       |     4 +-
 setup.py                             |     2 +-
 tests/test_codecs.py                 |   206 +-
 tests/test_copy.py                   |    36 +-
 tests/test_listeners.py              |   157 +
 tests/test_pool.py                   |    14 +-
 tests/test_prepare.py                |    13 +
 tests/test_record.py                 |    31 +-
 30 files changed, 15186 insertions(+), 7135 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 88de2a5..c527295 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: asyncpg
-Version: 0.11.0
+Version: 0.12.0
 Summary: An asyncio PosgtreSQL driver
 Home-page: https://github.com/MagicStack/asyncpg
 Author: MagicStack Inc
diff --git a/asyncpg.egg-info/PKG-INFO b/asyncpg.egg-info/PKG-INFO
index 88de2a5..c527295 100644
--- a/asyncpg.egg-info/PKG-INFO
+++ b/asyncpg.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: asyncpg
-Version: 0.11.0
+Version: 0.12.0
 Summary: An asyncio PosgtreSQL driver
 Home-page: https://github.com/MagicStack/asyncpg
 Author: MagicStack Inc
diff --git a/asyncpg/connection.py b/asyncpg/connection.py
index 9e94de4..14f694b 100644
--- a/asyncpg/connection.py
+++ b/asyncpg/connection.py
@@ -10,6 +10,7 @@ import collections
 import collections.abc
 import struct
 import time
+import warnings
 
 from . import compat
 from . import connect_utils
@@ -41,7 +42,7 @@ class Connection(metaclass=ConnectionMeta):
                  '_stmt_cache', '_stmts_to_close', '_listeners',
                  '_server_version', '_server_caps', '_intro_query',
                  '_reset_query', '_proxy', '_stmt_exclusive_section',
-                 '_config', '_params', '_addr')
+                 '_config', '_params', '_addr', '_log_listeners')
 
     def __init__(self, protocol, transport, loop,
                  addr: (str, int) or str,
@@ -69,6 +70,7 @@ class Connection(metaclass=ConnectionMeta):
         self._stmts_to_close = set()
 
         self._listeners = {}
+        self._log_listeners = set()
 
         settings = self._protocol.get_settings()
         ver_string = settings.server_version
@@ -126,6 +128,31 @@ class Connection(metaclass=ConnectionMeta):
             del self._listeners[channel]
             await self.fetch('UNLISTEN {}'.format(channel))
 
+    def add_log_listener(self, callback):
+        """Add a listener for Postgres log messages.
+
+        It will be called when asyncronous NoticeResponse is received
+        from the connection.  Possible message types are: WARNING, NOTICE,
+        DEBUG, INFO, or LOG.
+
+        :param callable callback:
+            A callable receiving the following arguments:
+            **connection**: a Connection the callback is registered with;
+            **message**: the `exceptions.PostgresLogMessage` message.
+
+        .. versionadded:: 0.12.0
+        """
+        if self.is_closed():
+            raise exceptions.InterfaceError('connection is closed')
+        self._log_listeners.add(callback)
+
+    def remove_log_listener(self, callback):
+        """Remove a listening callback for log messages.
+
+        .. versionadded:: 0.12.0
+        """
+        self._log_listeners.discard(callback)
+
     def get_server_pid(self):
         """Return the PID of the Postgres server the connection is bound to."""
         return self._protocol.get_server_pid()
@@ -373,7 +400,7 @@ class Connection(metaclass=ConnectionMeta):
         :param float timeout:
             Optional timeout value in seconds.
 
-        The remaining kewyword arguments are ``COPY`` statement options,
+        The remaining keyword arguments are ``COPY`` statement options,
         see `COPY statement documentation`_ for details.
 
         :return: The status string of the COPY command.
@@ -441,7 +468,7 @@ class Connection(metaclass=ConnectionMeta):
         :param float timeout:
             Optional timeout value in seconds.
 
-        The remaining kewyword arguments are ``COPY`` statement options,
+        The remaining keyword arguments are ``COPY`` statement options,
         see `COPY statement documentation`_ for details.
 
         :return: The status string of the COPY command.
@@ -508,7 +535,7 @@ class Connection(metaclass=ConnectionMeta):
         :param float timeout:
             Optional timeout value in seconds.
 
-        The remaining kewyword arguments are ``COPY`` statement options,
+        The remaining keyword arguments are ``COPY`` statement options,
         see `COPY statement documentation`_ for details.
 
         :return: The status string of the COPY command.
@@ -741,22 +768,119 @@ class Connection(metaclass=ConnectionMeta):
             copy_stmt, None, None, records, intro_stmt, timeout)
 
     async def set_type_codec(self, typename, *,
-                             schema='public', encoder, decoder, binary=False):
+                             schema='public', encoder, decoder,
+                             binary=None, format='text'):
         """Set an encoder/decoder pair for the specified data type.
 
-        :param typename:  Name of the data type the codec is for.
-        :param schema:  Schema name of the data type the codec is for
-                        (defaults to 'public')
-        :param encoder:  Callable accepting a single argument and returning
-                         a string or a bytes object (if `binary` is True).
-        :param decoder:  Callable accepting a single string or bytes argument
-                         and returning a decoded object.
-        :param binary:  Specifies whether the codec is able to handle binary
-                        data.  If ``False`` (the default), the data is
-                        expected to be encoded/decoded in text.
+        :param typename:
+            Name of the data type the codec is for.
+
+        :param schema:
+            Schema name of the data type the codec is for
+            (defaults to ``'public'``)
+
+        :param format:
+            The type of the argument received by the *decoder* callback,
+            and the type of the *encoder* callback return value.
+
+            If *format* is ``'text'`` (the default), the exchange datum is a
+            ``str`` instance containing valid text representation of the
+            data type.
+
+            If *format* is ``'binary'``, the exchange datum is a ``bytes``
+            instance containing valid _binary_ representation of the
+            data type.
+
+            If *format* is ``'tuple'``, the exchange datum is a type-specific
+            ``tuple`` of values.  The table below lists supported data
+            types and their format for this mode.
+
+            +-----------------+---------------------------------------------+
+            |  Type           |                Tuple layout                 |
+            +=================+=============================================+
+            | ``interval``    | (``months``, ``days``, ``microseconds``)    |
+            +-----------------+---------------------------------------------+
+            | ``date``        | (``date ordinal relative to Jan 1 2000``,)  |
+            |                 | ``-2^31`` for negative infinity timestamp   |
+            |                 | ``2^31-1`` for positive infinity timestamp. |
+            +-----------------+---------------------------------------------+
+            | ``timestamp``   | (``microseconds relative to Jan 1 2000``,)  |
+            |                 | ``-2^63`` for negative infinity timestamp   |
+            |                 | ``2^63-1`` for positive infinity timestamp. |
+            +-----------------+---------------------------------------------+
+            | ``timestamp     | (``microseconds relative to Jan 1 2000      |
+            | with time zone``| UTC``,)                                     |
+            |                 | ``-2^63`` for negative infinity timestamp   |
+            |                 | ``2^63-1`` for positive infinity timestamp. |
+            +-----------------+---------------------------------------------+
+            | ``time``        | (``microseconds``,)                         |
+            +-----------------+---------------------------------------------+
+            | ``time with     | (``microseconds``,                          |
+            | time zone``     | ``time zone offset in seconds``)            |
+            +-----------------+---------------------------------------------+
+
+        :param encoder:
+            Callable accepting a Python object as a single argument and
+            returning a value encoded according to *format*.
+
+        :param decoder:
+            Callable accepting a single argument encoded according to *format*
+            and returning a decoded Python object.
+
+        :param binary:
+            **Deprecated**.  Use *format* instead.
+
+        Example:
+
+        .. code-block:: pycon
+
+            >>> import asyncpg
+            >>> import asyncio
+            >>> import datetime
+            >>> from dateutil.relativedelta import relativedelta
+            >>> async def run():
+            ...     con = await asyncpg.connect(user='postgres')
+            ...     def encoder(delta):
+            ...         ndelta = delta.normalized()
+            ...         return (ndelta.years * 12 + ndelta.months,
+            ...                 ndelta.days,
+            ...                 ((ndelta.hours * 3600 +
+            ...                    ndelta.minutes * 60 +
+            ...                    ndelta.seconds) * 1000000 +
+            ...                  ndelta.microseconds))
+            ...     def decoder(tup):
+            ...         return relativedelta(months=tup[0], days=tup[1],
+            ...                              microseconds=tup[2])
+            ...     await con.set_type_codec(
+            ...         'interval', schema='pg_catalog', encoder=encoder,
+            ...         decoder=decoder, format='tuple')
+            ...     result = await con.fetchval(
+            ...         "SELECT '2 years 3 mons 1 day'::interval")
+            ...     print(result)
+            ...     print(datetime.datetime(2002, 1, 1) + result)
+            >>> asyncio.get_event_loop().run_until_complete(run())
+            relativedelta(years=+2, months=+3, days=+1)
+            2004-04-02 00:00:00
+
+        .. versionadded:: 0.12.0
+            Added the ``format`` keyword argument and support for 'tuple'
+            format.
+
+        .. versionchanged:: 0.12.0
+            The ``binary`` keyword argument is deprecated in favor of
+            ``format``.
+
         """
         self._check_open()
 
+        if binary is not None:
+            format = 'binary' if binary else 'text'
+            warnings.warn(
+                "The `binary` keyword argument to "
+                "set_type_codec() is deprecated and will be removed in "
+                "asyncpg 0.13.0.  Use the `format` keyword argument instead.",
+                DeprecationWarning, stacklevel=2)
+
         if self._type_by_name_stmt is None:
             self._type_by_name_stmt = await self.prepare(
                 introspection.TYPE_BY_NAME)
@@ -774,7 +898,40 @@ class Connection(metaclass=ConnectionMeta):
 
         self._protocol.get_settings().add_python_codec(
             oid, typename, schema, 'scalar',
-            encoder, decoder, binary)
+            encoder, decoder, format)
+
+        # Statement cache is no longer valid due to codec changes.
+        self._drop_local_statement_cache()
+
+    async def reset_type_codec(self, typename, *, schema='public'):
+        """Reset *typename* codec to the default implementation.
+
+        :param typename:
+            Name of the data type the codec is for.
+
+        :param schema:
+            Schema name of the data type the codec is for
+            (defaults to ``'public'``)
+
+        .. versionadded:: 0.12.0
+        """
+
+        if self._type_by_name_stmt is None:
+            self._type_by_name_stmt = await self.prepare(
+                introspection.TYPE_BY_NAME)
+
+        typeinfo = await self._type_by_name_stmt.fetchrow(
+            typename, schema)
+        if not typeinfo:
+            raise ValueError('unknown type: {}.{}'.format(schema, typename))
+
+        oid = typeinfo['oid']
+
+        self._protocol.get_settings().remove_python_codec(
+            oid, typename, schema)
+
+        # Statement cache is no longer valid due to codec changes.
+        self._drop_local_statement_cache()
 
     async def set_builtin_type_codec(self, typename, *,
                                      schema='public', codec_name):
@@ -805,6 +962,9 @@ class Connection(metaclass=ConnectionMeta):
         self._protocol.get_settings().set_builtin_type_codec(
             oid, typename, schema, 'scalar', codec_name)
 
+        # Statement cache is no longer valid due to codec changes.
+        self._drop_local_statement_cache()
+
     def is_closed(self):
         """Return ``True`` if the connection is closed, ``False`` otherwise.
 
@@ -818,20 +978,23 @@ class Connection(metaclass=ConnectionMeta):
         if self.is_closed():
             return
         self._mark_stmts_as_closed()
-        self._listeners = {}
+        self._listeners.clear()
+        self._log_listeners.clear()
         self._aborted = True
         await self._protocol.close()
 
     def terminate(self):
         """Terminate the connection without waiting for pending data."""
         self._mark_stmts_as_closed()
-        self._listeners = {}
+        self._listeners.clear()
+        self._log_listeners.clear()
         self._aborted = True
         self._protocol.abort()
 
     async def reset(self):
         self._check_open()
         self._listeners.clear()
+        self._log_listeners.clear()
         reset_query = self._get_reset_query()
         if reset_query:
             await self.execute(reset_query)
@@ -857,10 +1020,10 @@ class Connection(metaclass=ConnectionMeta):
     def _maybe_gc_stmt(self, stmt):
         if stmt.refs == 0 and not self._stmt_cache.has(stmt.query):
             # If low-level `stmt` isn't referenced from any high-level
-            # `PreparedStatament` object and is not in the `_stmt_cache`:
+            # `PreparedStatement` object and is not in the `_stmt_cache`:
             #
             #  * mark it as closed, which will make it non-usable
-            #    for any `PreparedStatament` or for methods like
+            #    for any `PreparedStatement` or for methods like
             #    `Connection.fetch()`.
             #
             # * schedule it to be formally closed on the server.
@@ -909,28 +1072,56 @@ class Connection(metaclass=ConnectionMeta):
 
         self._loop.create_task(cancel())
 
-    def _notify(self, pid, channel, payload):
+    def _process_log_message(self, fields, last_query):
+        if not self._log_listeners:
+            return
+
+        message = exceptions.PostgresLogMessage.new(fields, query=last_query)
+
+        con_ref = self._unwrap()
+        for cb in self._log_listeners:
+            self._loop.call_soon(
+                self._call_log_listener, cb, con_ref, message)
+
+    def _call_log_listener(self, cb, con_ref, message):
+        try:
+            cb(con_ref, message)
+        except Exception as ex:
+            self._loop.call_exception_handler({
+                'message': 'Unhandled exception in asyncpg log message '
+                           'listener callback {!r}'.format(cb),
+                'exception': ex
+            })
+
+    def _process_notification(self, pid, channel, payload):
         if channel not in self._listeners:
             return
 
+        con_ref = self._unwrap()
+        for cb in self._listeners[channel]:
+            self._loop.call_soon(
+                self._call_listener, cb, con_ref, pid, channel, payload)
+
+    def _call_listener(self, cb, con_ref, pid, channel, payload):
+        try:
+            cb(con_ref, pid, channel, payload)
+        except Exception as ex:
+            self._loop.call_exception_handler({
+                'message': 'Unhandled exception in asyncpg notification '
+                           'listener callback {!r}'.format(cb),
+                'exception': ex
+            })
+
+    def _unwrap(self):
         if self._proxy is None:
             con_ref = self
         else:
             # `_proxy` is not None when the connection is a member
             # of a connection pool.  Which means that the user is working
-            # with a PooledConnectionProxy instance, and expects to see it
+            # with a `PoolConnectionProxy` instance, and expects to see it
             # (and not the actual Connection) in their event callbacks.
             con_ref = self._proxy
-
-        for cb in self._listeners[channel]:
-            try:
-                cb(con_ref, pid, channel, payload)
-            except Exception as ex:
-                self._loop.call_exception_handler({
-                    'message': 'Unhandled exception in asyncpg notification '
-                               'listener callback {!r}'.format(cb),
-                    'exception': ex
-                })
+        return con_ref
 
     def _get_reset_query(self):
         if self._reset_query is not None:
@@ -1370,7 +1561,7 @@ def _detect_server_capabilities(server_version, connection_settings):
         sql_reset = True
         sql_close_all = False
     elif hasattr(connection_settings, 'crdb_version'):
-        # CocroachDB detected.
+        # CockroachDB detected.
         advisory_locks = False
         notifications = False
         plpgsql = False
diff --git a/asyncpg/exceptions/__init__.py b/asyncpg/exceptions/__init__.py
index 30bca3f..7fd0cb8 100644
--- a/asyncpg/exceptions/__init__.py
+++ b/asyncpg/exceptions/__init__.py
@@ -5,7 +5,7 @@ from ._base import *  # NOQA
 from . import _base
 
 
-class PostgresWarning(Warning, _base.PostgresMessage):
+class PostgresWarning(_base.PostgresLogMessage, Warning):
     sqlstate = '01000'
 
 
@@ -261,6 +261,10 @@ class NumericValueOutOfRangeError(DataError):
     sqlstate = '22003'
 
 
+class SequenceGeneratorLimitExceededError(DataError):
+    sqlstate = '2200H'
+
+
 class StringDataLengthMismatchError(DataError):
     sqlstate = '22026'
 
@@ -608,6 +612,10 @@ class WrongObjectTypeError(SyntaxOrAccessError):
     sqlstate = '42809'
 
 
+class GeneratedAlwaysError(SyntaxOrAccessError):
+    sqlstate = '428C9'
+
+
 class UndefinedColumnError(SyntaxOrAccessError):
     sqlstate = '42703'
 
@@ -772,6 +780,10 @@ class LockNotAvailableError(ObjectNotInPrerequisiteStateError):
     sqlstate = '55P03'
 
 
+class UnsafeNewEnumValueUsageError(ObjectNotInPrerequisiteStateError):
+    sqlstate = '55P04'
+
+
 class OperatorInterventionError(_base.PostgresError):
     sqlstate = '57000'
 
@@ -1007,7 +1019,8 @@ __all__ = _base.__all__ + (
     'FDWUnableToCreateExecutionError', 'FDWUnableToCreateReplyError',
     'FDWUnableToEstablishConnectionError', 'FeatureNotSupportedError',
     'ForeignKeyViolationError', 'FunctionExecutedNoReturnStatementError',
-    'GroupingError', 'HeldCursorRequiresSameIsolationLevelError',
+    'GeneratedAlwaysError', 'GroupingError',
+    'HeldCursorRequiresSameIsolationLevelError',
     'IdleInTransactionSessionTimeoutError', 'ImplicitZeroBitPadding',
     'InFailedSQLTransactionError',
     'InappropriateAccessModeForBranchTransactionError',
@@ -1073,7 +1086,8 @@ __all__ = _base.__all__ + (
     'ReadingSQLDataNotPermittedError', 'ReservedNameError',
     'RestrictViolationError', 'SQLRoutineError',
     'SQLStatementNotYetCompleteError', 'SavepointError',
-    'SchemaAndDataStatementMixingNotSupportedError', 'SerializationError',
+    'SchemaAndDataStatementMixingNotSupportedError',
+    'SequenceGeneratorLimitExceededError', 'SerializationError',
     'SnapshotTooOldError', 'SrfProtocolViolatedError',
     'StackedDiagnosticsAccessedWithoutActiveHandlerError',
     'StatementCompletionUnknownError', 'StatementTooComplexError',
@@ -1088,8 +1102,8 @@ __all__ = _base.__all__ + (
     'UndefinedColumnError', 'UndefinedFileError',
     'UndefinedFunctionError', 'UndefinedObjectError',
     'UndefinedParameterError', 'UndefinedTableError',
-    'UniqueViolationError', 'UnterminatedCStringError',
-    'UntranslatableCharacterError', 'WindowingError',
-    'WithCheckOptionViolationError', 'WrongObjectTypeError',
-    'ZeroLengthCharacterStringError'
+    'UniqueViolationError', 'UnsafeNewEnumValueUsageError',
+    'UnterminatedCStringError', 'UntranslatableCharacterError',
+    'WindowingError', 'WithCheckOptionViolationError',
+    'WrongObjectTypeError', 'ZeroLengthCharacterStringError'
 )
diff --git a/asyncpg/exceptions/_base.py b/asyncpg/exceptions/_base.py
index 72e9ec7..99b525c 100644
--- a/asyncpg/exceptions/_base.py
+++ b/asyncpg/exceptions/_base.py
@@ -5,11 +5,12 @@
 # the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
 
 
+import asyncpg
 import sys
 
 
 __all__ = ('PostgresError', 'FatalPostgresError', 'UnknownPostgresError',
-           'InterfaceError')
+           'InterfaceError', 'PostgresLogMessage')
 
 
 def _is_asyncpg_class(cls):
@@ -18,9 +19,11 @@ def _is_asyncpg_class(cls):
 
 
 class PostgresMessageMeta(type):
+
     _message_map = {}
     _field_map = {
         'S': 'severity',
+        'V': 'severity_en',
         'C': 'sqlstate',
         'M': 'message',
         'D': 'detail',
@@ -68,35 +71,31 @@ class PostgresMessageMeta(type):
 
 
 class PostgresMessage(metaclass=PostgresMessageMeta):
-    def __str__(self):
-        msg = self.args[0]
-        if self.detail:
-            msg += '\nDETAIL:  {}'.format(self.detail)
-        if self.hint:
-            msg += '\nHINT:  {}'.format(self.hint)
 
-        return msg
+    @classmethod
+    def _get_error_class(cls, fields):
+        sqlstate = fields.get('C')
+        return type(cls).get_message_class_for_sqlstate(sqlstate)
 
     @classmethod
-    def _get_error_template(cls, fields, query):
-        errcode = fields.get('C')
-        mcls = cls.__class__
-        exccls = mcls.get_message_class_for_sqlstate(errcode)
+    def _get_error_dict(cls, fields, query):
         dct = {
             'query': query
         }
 
+        field_map = type(cls)._field_map
         for k, v in fields.items():
-            field = mcls._field_map.get(k)
+            field = field_map.get(k)
             if field:
                 dct[field] = v
 
-        return exccls, dct
+        return dct
 
     @classmethod
-    def new(cls, fields, query=None):
-        exccls, dct = cls._get_error_template(fields, query)
+    def _make_constructor(cls, fields, query=None):
+        dct = cls._get_error_dict(fields, query)
 
+        exccls = cls._get_error_class(fields)
         message = dct.get('message', '')
 
         # PostgreSQL will raise an exception when it detects
@@ -121,15 +120,36 @@ class PostgresMessage(metaclass=PostgresMessageMeta):
             message = ('cached statement plan is invalid due to a database '
                        'schema or configuration change')
 
-        e = exccls(message)
-        e.__dict__.update(dct)
+        return exccls, message, dct
 
-        return e
+    def as_dict(self):
+        dct = {}
+        for f in type(self)._field_map.values():
+            val = getattr(self, f)
+            if val is not None:
+                dct[f] = val
+        return dct
 
 
 class PostgresError(PostgresMessage, Exception):
     """Base class for all Postgres errors."""
 
+    def __str__(self):
+        msg = self.args[0]
+        if self.detail:
+            msg += '\nDETAIL:  {}'.format(self.detail)
+        if self.hint:
+            msg += '\nHINT:  {}'.format(self.hint)
+
+        return msg
+
+    @classmethod
+    def new(cls, fields, query=None):
+        exccls, message, dct = cls._make_constructor(fields, query)
+        ex = exccls(message)
+        ex.__dict__.update(dct)
+        return ex
+
 
 class FatalPostgresError(PostgresError):
     """A fatal error that should result in server disconnection."""
@@ -141,3 +161,34 @@ class UnknownPostgresError(FatalPostgresError):
 
 class InterfaceError(Exception):
     """An error caused by improper use of asyncpg API."""
+
+
+class PostgresLogMessage(PostgresMessage):
+    """A base class for non-error server messages."""
+
+    def __str__(self):
+        return '{}: {}'.format(type(self).__name__, self.message)
+
+    def __setattr__(self, name, val):
+        raise TypeError('instances of {} are immutable'.format(
+            type(self).__name__))
+
+    @classmethod
+    def new(cls, fields, query=None):
+        exccls, message_text, dct = cls._make_constructor(fields, query)
+
+        if exccls is UnknownPostgresError:
+            exccls = PostgresLogMessage
+
+        if exccls is PostgresLogMessage:
+            severity = dct.get('severity_en') or dct.get('severity')
+            if severity and severity.upper() == 'WARNING':
+                exccls = asyncpg.PostgresWarning
+
+        if issubclass(exccls, (BaseException, Warning)):
+            msg = exccls(message_text)
+        else:
+            msg = exccls()
+
+        msg.__dict__.update(dct)
+        return msg
diff --git a/asyncpg/pool.py b/asyncpg/pool.py
index d6ef855..a689fbb 100644
--- a/asyncpg/pool.py
+++ b/asyncpg/pool.py
@@ -18,11 +18,6 @@ class PoolConnectionProxyMeta(type):
 
     def __new__(mcls, name, bases, dct, *, wrap=False):
         if wrap:
-            def get_wrapper(meth):
-                def wrapper(self, *args, **kwargs):
-                    return self._dispatch_method_call(meth, args, kwargs)
-                return wrapper
-
             for attrname in dir(connection.Connection):
                 if attrname.startswith('_') or attrname in dct:
                     continue
@@ -31,7 +26,7 @@ class PoolConnectionProxyMeta(type):
                 if not inspect.isfunction(meth):
                     continue
 
-                wrapper = get_wrapper(meth)
+                wrapper = mcls._wrap_connection_method(attrname)
                 wrapper = functools.update_wrapper(wrapper, meth)
                 dct[attrname] = wrapper
 
@@ -44,6 +39,21 @@ class PoolConnectionProxyMeta(type):
         # Needed for Python 3.5 to handle `wrap` class keyword argument.
         super().__init__(name, bases, dct)
 
+    @staticmethod
+    def _wrap_connection_method(meth_name):
+        def call_con_method(self, *args, **kwargs):
+            # This method will be owned by PoolConnectionProxy class.
+            if self._con is None:
+                raise exceptions.InterfaceError(
+                    'cannot call Connection.{}(): '
+                    'connection has been released back to the pool'.format(
+                        meth_name))
+
+            meth = getattr(self._con.__class__, meth_name)
+            return meth(self._con, *args, **kwargs)
+
+        return call_con_method
+
 
 class PoolConnectionProxy(connection._ConnectionProxy,
                           metaclass=PoolConnectionProxyMeta,
@@ -57,6 +67,10 @@ class PoolConnectionProxy(connection._ConnectionProxy,
         self._holder = holder
         con._set_proxy(self)
 
+    def __getattr__(self, attr):
+        # Proxy all unresolved attributes to the wrapped Connection object.
+        return getattr(self._con, attr)
+
     def _detach(self):
         if self._con is None:
             raise exceptions.InterfaceError(
@@ -65,15 +79,6 @@ class PoolConnectionProxy(connection._ConnectionProxy,
         con, self._con = self._con, None
         con._set_proxy(None)
 
-    def _dispatch_method_call(self, meth, args, kwargs):
-        if self._con is None:
-            raise exceptions.InterfaceError(
-                'cannot call Connection.{}(): '
-                'connection has been released back to the pool'.format(
-                    meth.__name__))
-
-        return meth(self._con, *args, **kwargs)
-
     def __repr__(self):
         if self._con is None:
             return '<{classname} [released] {id:#x}>'.format(
diff --git a/asyncpg/protocol/codecs/base.pxd b/asyncpg/protocol/codecs/base.pxd
index d7eb979..59e7523 100644
--- a/asyncpg/protocol/codecs/base.pxd
+++ b/asyncpg/protocol/codecs/base.pxd
@@ -31,12 +31,17 @@ cdef enum CodecType:
     CODEC_RANGE     = 5
 
 
-cdef enum CodecFormat:
+cdef enum ServerDataFormat:
     PG_FORMAT_ANY = -1
     PG_FORMAT_TEXT = 0
     PG_FORMAT_BINARY = 1
 
 
+cdef enum ClientExchangeFormat:
+    PG_XFORMAT_OBJECT = 1
+    PG_XFORMAT_TUPLE = 2
+
+
 cdef class Codec:
     cdef:
         uint32_t        oid
@@ -46,7 +51,8 @@ cdef class Codec:
         str             kind
 
         CodecType       type
-        CodecFormat     format
+        ServerDataFormat format
+        ClientExchangeFormat xformat
 
         encode_func     c_encoder
         decode_func     c_decoder
@@ -68,7 +74,8 @@ cdef class Codec:
         codec_decode_func decoder
 
     cdef init(self, str name, str schema, str kind,
-              CodecType type, CodecFormat format,
+              CodecType type, ServerDataFormat format,
+              ClientExchangeFormat xformat,
               encode_func c_encoder, decode_func c_decoder,
               object py_encoder, object py_decoder,
               Codec element_codec, tuple element_type_oids,
@@ -140,7 +147,7 @@ cdef class Codec:
     cdef Codec new_composite_codec(uint32_t oid,
                                    str name,
                                    str schema,
-                                   CodecFormat format,
+                                   ServerDataFormat format,
                                    list element_codecs,
                                    tuple element_type_oids,
                                    object element_names)
@@ -152,7 +159,10 @@ cdef class Codec:
                                 str kind,
                                 object encoder,
                                 object decoder,
-                                CodecFormat format)
+                                encode_func c_encoder,
+                                decode_func c_decoder,
+                                ServerDataFormat format,
+                                ClientExchangeFormat xformat)
 
 
 cdef class DataCodecConfig:
@@ -160,4 +170,5 @@ cdef class DataCodecConfig:
         dict _type_codecs_cache
         dict _local_type_codecs
 
-    cdef inline Codec get_codec(self, uint32_t oid, CodecFormat format)
+    cdef inline Codec get_codec(self, uint32_t oid, ServerDataFormat format)
+    cdef inline Codec get_local_codec(self, uint32_t oid)
diff --git a/asyncpg/protocol/codecs/base.pyx b/asyncpg/protocol/codecs/base.pyx
index 8714286..9b2a2db 100644
--- a/asyncpg/protocol/codecs/base.pyx
+++ b/asyncpg/protocol/codecs/base.pyx
@@ -5,8 +5,8 @@
 # the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
 
 
-cdef void* binary_codec_map[MAXSUPPORTEDOID + 1]
-cdef void* text_codec_map[MAXSUPPORTEDOID + 1]
+cdef void* binary_codec_map[(MAXSUPPORTEDOID + 1) * 2]
+cdef void* text_codec_map[(MAXSUPPORTEDOID + 1) * 2]
 cdef dict TYPE_CODECS_CACHE = {}
 cdef dict EXTRA_CODECS = {}
 
@@ -19,7 +19,8 @@ cdef class Codec:
         self.type = CODEC_UNDEFINED
 
     cdef init(self, str name, str schema, str kind,
-              CodecType type, CodecFormat format,
+              CodecType type, ServerDataFormat format,
+              ClientExchangeFormat xformat,
               encode_func c_encoder, decode_func c_decoder,
               object py_encoder, object py_decoder,
               Codec element_codec, tuple element_type_oids,
@@ -31,6 +32,7 @@ cdef class Codec:
         self.kind = kind
         self.type = type
         self.format = format
+        self.xformat = xformat
         self.c_encoder = c_encoder
         self.c_decoder = c_decoder
         self.py_encoder = py_encoder
@@ -81,7 +83,7 @@ cdef class Codec:
 
         codec = Codec(self.oid)
         codec.init(self.name, self.schema, self.kind,
-                   self.type, self.format,
+                   self.type, self.format, self.xformat,
                    self.c_encoder, self.c_decoder,
                    self.py_encoder, self.py_decoder,
                    self.element_codec,
@@ -139,11 +141,20 @@ cdef class Codec:
 
     cdef encode_in_python(self, ConnectionSettings settings, WriteBuffer buf,
                           object obj):
-        bb = self.py_encoder(obj)
-        if self.format == PG_FORMAT_BINARY:
-            bytea_encode(settings, buf, bb)
+        data = self.py_encoder(obj)
+        if self.xformat == PG_XFORMAT_OBJECT:
+            if self.format == PG_FORMAT_BINARY:
+                bytea_encode(settings, buf, data)
+            elif self.format == PG_FORMAT_TEXT:
+                text_encode(settings, buf, data)
+            else:
+                raise RuntimeError(
+                    'unexpected data format: {}'.format(self.format))
+        elif self.xformat == PG_XFORMAT_TUPLE:
+            self.c_encoder(settings, buf, data)
         else:
-            text_encode(settings, buf, bb)
+            raise RuntimeError(
+                'unexpected exchange format: {}'.format(self.xformat))
 
     cdef encode(self, ConnectionSettings settings, WriteBuffer buf,
                 object obj):
@@ -210,12 +221,21 @@ cdef class Codec:
 
     cdef decode_in_python(self, ConnectionSettings settings,
                           FastReadBuffer buf):
-        if self.format == PG_FORMAT_BINARY:
-            bb = bytea_decode(settings, buf)
+        if self.xformat == PG_XFORMAT_OBJECT:
+            if self.format == PG_FORMAT_BINARY:
+                data = bytea_decode(settings, buf)
+            elif self.format == PG_FORMAT_TEXT:
+                data = text_decode(settings, buf)
+            else:
+                raise RuntimeError(
+                    'unexpected data format: {}'.format(self.format))
+        elif self.xformat == PG_XFORMAT_TUPLE:
+            data = self.c_decoder(settings, buf)
         else:
-            bb = text_decode(settings, buf)
+            raise RuntimeError(
+                'unexpected exchange format: {}'.format(self.xformat))
 
-        return self.py_decoder(bb)
+        return self.py_decoder(data)
 
     cdef inline decode(self, ConnectionSettings settings, FastReadBuffer buf):
         return self.decoder(self, settings, buf)
@@ -274,8 +294,8 @@ cdef class Codec:
         cdef Codec codec
         codec = Codec(oid)
         codec.init(name, schema, 'array', CODEC_ARRAY, element_codec.format,
-                   NULL, NULL, None, None, element_codec, None, None, None,
-                   element_delimiter)
+                   PG_XFORMAT_OBJECT, NULL, NULL, None, None, element_codec,
+                   None, None, None, element_delimiter)
         return codec
 
     @staticmethod
@@ -286,21 +306,22 @@ cdef class Codec:
         cdef Codec codec
         codec = Codec(oid)
         codec.init(name, schema, 'range', CODEC_RANGE, element_codec.format,
-                   NULL, NULL, None, None, element_codec, None, None, None, 0)
+                   PG_XFORMAT_OBJECT, NULL, NULL, None, None, element_codec,
+                   None, None, None, 0)
         return codec
 
     @staticmethod
     cdef Codec new_composite_codec(uint32_t oid,
                                    str name,
                                    str schema,
-                                   CodecFormat format,
+                                   ServerDataFormat format,
                                    list element_codecs,
                                    tuple element_type_oids,
                                    object element_names):
         cdef Codec codec
         codec = Codec(oid)
         codec.init(name, schema, 'composite', CODEC_COMPOSITE,
-                   format, NULL, NULL, None, None, None,
+                   format, PG_XFORMAT_OBJECT, NULL, NULL, None, None, None,
                    element_type_oids, element_names, element_codecs, 0)
         return codec
 
@@ -311,11 +332,15 @@ cdef class Codec:
                                 str kind,
                                 object encoder,
                                 object decoder,
-                                CodecFormat format):
+                                encode_func c_encoder,
+                                decode_func c_decoder,
+                                ServerDataFormat format,
+                                ClientExchangeFormat xformat):
         cdef Codec codec
         codec = Codec(oid)
-        codec.init(name, schema, kind, CODEC_PY, format, NULL, NULL,
-                   encoder, decoder, None, None, None, None, 0)
+        codec.init(name, schema, kind, CODEC_PY, format, xformat,
+                   c_encoder, c_decoder, encoder, decoder,
+                   None, None, None, None, 0)
         return codec
 
 
@@ -344,8 +369,8 @@ cdef class DataCodecConfig:
         cdef:
             Codec elem_codec
             list comp_elem_codecs
-            CodecFormat format
-            CodecFormat elem_format
+            ServerDataFormat format
+            ServerDataFormat elem_format
             bint has_text_elements
             Py_UCS4 elem_delim
 
@@ -476,13 +501,32 @@ cdef class DataCodecConfig:
                 self.declare_fallback_codec(oid, name, schema)
 
     def add_python_codec(self, typeoid, typename, typeschema, typekind,
-                         encoder, decoder, binary):
-        format = PG_FORMAT_BINARY if binary else PG_FORMAT_TEXT
-
-        self._local_type_codecs[typeoid, format] = \
+                         encoder, decoder, format, xformat):
+        cdef:
+            Codec core_codec
+            encode_func c_encoder = NULL
+            decode_func c_decoder = NULL
+
... 47619 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/asyncpg.git



More information about the Python-modules-commits mailing list