[Python-modules-commits] [asyncpg] 02/08: Import asyncpg_0.13.0.orig.tar.gz

Piotr Ożarowski piotr at moszumanska.debian.org
Wed Oct 25 09:02:43 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 5e271c5f75c54db706e449c339006be093f471be
Author: Piotr Ożarowski <piotr at debian.org>
Date:   Wed Oct 25 10:21:24 2017 +0200

    Import asyncpg_0.13.0.orig.tar.gz
---
 LICENSE                             |    43 +-
 Makefile                            |     5 +
 PKG-INFO                            |     5 +-
 README.rst                          |     2 +-
 asyncpg.egg-info/PKG-INFO           |     5 +-
 asyncpg.egg-info/SOURCES.txt        |     4 +
 asyncpg/__init__.py                 |     2 +
 asyncpg/_testbase.py                |    15 +-
 asyncpg/cluster.py                  |    44 +-
 asyncpg/connect_utils.py            |    14 +-
 asyncpg/connection.py               |   124 +-
 asyncpg/connresource.py             |    38 +
 asyncpg/cursor.py                   |    22 +-
 asyncpg/exceptions/__init__.py      |    12 +-
 asyncpg/exceptions/_base.py         |    36 +-
 asyncpg/introspection.py            |   170 +-
 asyncpg/pool.py                     |    27 +-
 asyncpg/prepared_stmt.py            |    30 +-
 asyncpg/protocol/buffer.pxd         |    10 +-
 asyncpg/protocol/buffer.pyx         |    35 +-
 asyncpg/protocol/codecs/misc.pyx    |    12 +
 asyncpg/protocol/codecs/network.pyx |    59 +-
 asyncpg/protocol/coreproto.pyx      |    22 +-
 asyncpg/protocol/pgtypes.pxi        |     8 +
 asyncpg/protocol/prepared_stmt.pyx  |    34 +-
 asyncpg/protocol/protocol.c         | 29593 +++++++++++++++++++++++-----------
 asyncpg/protocol/protocol.pxd       |     1 +
 asyncpg/protocol/protocol.pyx       |   175 +-
 docs/conf.py                        |    20 +-
 docs/index.rst                      |     2 +-
 setup.py                            |    13 +-
 tests/__init__.py                   |    14 +
 tests/test__environment.py          |    29 +
 tests/test__sourcecode.py           |    40 +
 tests/test_codecs.py                |    81 +-
 tests/test_connect.py               |     6 +
 tests/test_copy.py                  |    20 +-
 tests/test_execute.py               |     2 -
 tests/test_introspection.py         |    94 +
 tests/test_listeners.py             |    26 +
 tests/test_pool.py                  |    70 +-
 tests/test_prepare.py               |    17 +-
 42 files changed, 21227 insertions(+), 9754 deletions(-)

diff --git a/LICENSE b/LICENSE
index 418b71e..d931386 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,17 +1,4 @@
-   Copyright (c) 2016-present MagicStack Inc.  http://magic.io
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
+Copyright (C) 2016-present the asyncpg authors and contributors.
 
                                  Apache License
                            Version 2.0, January 2004
@@ -187,3 +174,31 @@
       defend, and hold each Contributor harmless for any liability
       incurred by, or claims asserted against, such Contributor by reason
       of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright (C) 2016-present the asyncpg authors and contributors
+   <see AUTHORS file>
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Makefile b/Makefile
index c3c5ce0..0fd1cf9 100644
--- a/Makefile
+++ b/Makefile
@@ -33,6 +33,11 @@ test:
 	USE_UVLOOP=1 $(PYTHON) setup.py test
 
 
+testinstalled:
+	$(PYTHON) tests/__init__.py
+	USE_UVLOOP=1 $(PYTHON) tests/__init__.py
+
+
 quicktest:
 	$(PYTHON) setup.py test
 
diff --git a/PKG-INFO b/PKG-INFO
index c527295..83716c7 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,11 +1,12 @@
 Metadata-Version: 1.1
 Name: asyncpg
-Version: 0.12.0
+Version: 0.13.0
 Summary: An asyncio PosgtreSQL driver
 Home-page: https://github.com/MagicStack/asyncpg
 Author: MagicStack Inc
 Author-email: hello at magic.io
 License: Apache License, Version 2.0
+Description-Content-Type: UNKNOWN
 Description: asyncpg -- A fast PostgreSQL Database Client Library for Python/asyncio
         =======================================================================
         
@@ -25,7 +26,7 @@ Description: asyncpg -- A fast PostgreSQL Database Client Library for Python/asy
         `blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
         
         asyncpg requires Python 3.5 or later and is supported for PostgreSQL
-        versions 9.1 to 9.6.
+        versions 9.2 to 10.
         
         
         Documentation
diff --git a/README.rst b/README.rst
index 8730e0f..a89bc01 100644
--- a/README.rst
+++ b/README.rst
@@ -17,7 +17,7 @@ framework.  You can read more about asyncpg in an introductory
 `blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
 
 asyncpg requires Python 3.5 or later and is supported for PostgreSQL
-versions 9.1 to 9.6.
+versions 9.2 to 10.
 
 
 Documentation
diff --git a/asyncpg.egg-info/PKG-INFO b/asyncpg.egg-info/PKG-INFO
index c527295..83716c7 100644
--- a/asyncpg.egg-info/PKG-INFO
+++ b/asyncpg.egg-info/PKG-INFO
@@ -1,11 +1,12 @@
 Metadata-Version: 1.1
 Name: asyncpg
-Version: 0.12.0
+Version: 0.13.0
 Summary: An asyncio PosgtreSQL driver
 Home-page: https://github.com/MagicStack/asyncpg
 Author: MagicStack Inc
 Author-email: hello at magic.io
 License: Apache License, Version 2.0
+Description-Content-Type: UNKNOWN
 Description: asyncpg -- A fast PostgreSQL Database Client Library for Python/asyncio
         =======================================================================
         
@@ -25,7 +26,7 @@ Description: asyncpg -- A fast PostgreSQL Database Client Library for Python/asy
         `blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
         
         asyncpg requires Python 3.5 or later and is supported for PostgreSQL
-        versions 9.1 to 9.6.
+        versions 9.2 to 10.
         
         
         Documentation
diff --git a/asyncpg.egg-info/SOURCES.txt b/asyncpg.egg-info/SOURCES.txt
index e8bca63..0ddc6cc 100644
--- a/asyncpg.egg-info/SOURCES.txt
+++ b/asyncpg.egg-info/SOURCES.txt
@@ -10,6 +10,7 @@ asyncpg/cluster.py
 asyncpg/compat.py
 asyncpg/connect_utils.py
 asyncpg/connection.py
+asyncpg/connresource.py
 asyncpg/cursor.py
 asyncpg/introspection.py
 asyncpg/pool.py
@@ -76,6 +77,8 @@ docs/installation.rst
 docs/usage.rst
 docs/api/index.rst
 tests/__init__.py
+tests/test__environment.py
+tests/test__sourcecode.py
 tests/test_cache_invalidation.py
 tests/test_cancellation.py
 tests/test_codecs.py
@@ -84,6 +87,7 @@ tests/test_copy.py
 tests/test_cursor.py
 tests/test_exceptions.py
 tests/test_execute.py
+tests/test_introspection.py
 tests/test_listeners.py
 tests/test_pool.py
 tests/test_prepare.py
diff --git a/asyncpg/__init__.py b/asyncpg/__init__.py
index 380dc26..2ef274c 100644
--- a/asyncpg/__init__.py
+++ b/asyncpg/__init__.py
@@ -14,3 +14,5 @@ from .types import *  # NOQA
 
 __all__ = ('connect', 'create_pool', 'Record', 'Connection') + \
           exceptions.__all__  # NOQA
+
+__version__ = '0.13.0'
diff --git a/asyncpg/_testbase.py b/asyncpg/_testbase.py
index ed0566e..9b56b58 100644
--- a/asyncpg/_testbase.py
+++ b/asyncpg/_testbase.py
@@ -93,9 +93,12 @@ class TestCase(unittest.TestCase, metaclass=TestCaseMeta):
         try:
             yield
         finally:
-            if time.monotonic() - st > delta:
+            elapsed = time.monotonic() - st
+            if elapsed > delta:
                 raise AssertionError(
-                    'running block took longer than {}'.format(delta))
+                    'running block took {:0.3f}s which is longer '
+                    'than the expected maximum of {:0.3f}s'.format(
+                        elapsed, delta))
 
     @contextlib.contextmanager
     def assertLoopErrorHandlerCalled(self, msg_re: str):
@@ -214,18 +217,18 @@ def with_connection_options(**options):
 
 class ConnectedTestCase(ClusterTestCase):
 
-    def getExtraConnectOptions(self):
-        return {}
-
     def setUp(self):
         super().setUp()
 
         # Extract options set up with `with_connection_options`.
         test_func = getattr(self, self._testMethodName).__func__
         opts = getattr(test_func, '__connect_options__', {})
+        if 'database' not in opts:
+            opts = dict(opts)
+            opts['database'] = 'postgres'
 
         self.con = self.loop.run_until_complete(
-            self.cluster.connect(database='postgres', loop=self.loop, **opts))
+            self.cluster.connect(loop=self.loop, **opts))
 
         self.server_version = self.con.get_server_version()
 
diff --git a/asyncpg/cluster.py b/asyncpg/cluster.py
index afb8e6c..5526220 100644
--- a/asyncpg/cluster.py
+++ b/asyncpg/cluster.py
@@ -84,8 +84,7 @@ class Cluster:
     def __init__(self, data_dir, *, pg_config_path=None):
         self._data_dir = data_dir
         self._pg_config_path = pg_config_path
-        self._pg_config = None
-        self._pg_config_data = None
+        self._pg_bin_dir = os.environ.get('PGINSTALLATION')
         self._pg_ctl = None
         self._daemon_pid = None
         self._daemon_process = None
@@ -374,11 +373,18 @@ class Cluster:
             self.reload()
 
     def _init_env(self):
-        self._pg_config = self._find_pg_config(self._pg_config_path)
-        self._pg_config_data = self._run_pg_config(self._pg_config)
-        self._pg_version = self._get_pg_version()
+        if not self._pg_bin_dir:
+            pg_config = self._find_pg_config(self._pg_config_path)
+            pg_config_data = self._run_pg_config(pg_config)
+
+            self._pg_bin_dir = pg_config_data.get('bindir')
+            if not self._pg_bin_dir:
+                raise ClusterError(
+                    'pg_config output did not provide the BINDIR value')
+
         self._pg_ctl = self._find_pg_binary('pg_ctl')
         self._postgres = self._find_pg_binary('postgres')
+        self._pg_version = self._get_pg_version()
 
     def _connection_addr_from_pidfile(self):
         pidfile = os.path.join(self._data_dir, 'postmaster.pid')
@@ -505,13 +511,7 @@ class Cluster:
         return pg_config_path
 
     def _find_pg_binary(self, binary):
-        bindir = self._pg_config_data.get('bindir')
-        if not bindir:
-            raise ClusterError(
-                'could not find {} executable: '.format(binary) +
-                'pg_config output did not provide the BINDIR value')
-
-        bpath = platform_exe(os.path.join(bindir, binary))
+        bpath = platform_exe(os.path.join(self._pg_bin_dir, binary))
 
         if not os.path.isfile(bpath):
             raise ClusterError(
@@ -521,9 +521,23 @@ class Cluster:
         return bpath
 
     def _get_pg_version(self):
-        version_string = self._pg_config_data.get('version')
-        if not version_string:
-            raise ClusterError('could not determine PostgreSQL version')
+        process = subprocess.run(
+            [self._postgres, '--version'],
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        stdout, stderr = process.stdout, process.stderr
+
+        if process.returncode != 0:
+            raise ClusterError(
+                'postgres --version exited with status {:d}: {}'.format(
+                    process.returncode, stderr))
+
+        version_string = stdout.decode('utf-8').strip(' \n')
+        prefix = 'postgres (PostgreSQL) '
+        if not version_string.startswith(prefix):
+            raise ClusterError(
+                'could not determine server version from {!r}'.format(
+                    version_string))
+        version_string = version_string[len(prefix):]
 
         return serverversion.split_server_version_string(version_string)
 
diff --git a/asyncpg/connect_utils.py b/asyncpg/connect_utils.py
index 8a3979b..da61652 100644
--- a/asyncpg/connect_utils.py
+++ b/asyncpg/connect_utils.py
@@ -9,6 +9,7 @@ import asyncio
 import collections
 import getpass
 import os
+import platform
 import socket
 import struct
 import time
@@ -40,6 +41,9 @@ _ClientConfiguration = collections.namedtuple(
     ])
 
 
+_system = platform.uname().system
+
+
 def _parse_connect_dsn_and_args(*, dsn, host, port, user,
                                 password, database, ssl, connect_timeout,
                                 server_settings):
@@ -123,9 +127,13 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user,
     if host is None:
         host = os.getenv('PGHOST')
         if not host:
-            host = ['/tmp', '/private/tmp',
-                    '/var/pgsql_socket', '/run/postgresql',
-                    'localhost']
+            if _system == 'Windows':
+                host = ['localhost']
+            else:
+                host = ['/tmp', '/private/tmp',
+                        '/var/pgsql_socket', '/run/postgresql',
+                        'localhost']
+
     if not isinstance(host, list):
         host = [host]
 
diff --git a/asyncpg/connection.py b/asyncpg/connection.py
index 14f694b..ce346fb 100644
--- a/asyncpg/connection.py
+++ b/asyncpg/connection.py
@@ -8,6 +8,7 @@
 import asyncio
 import collections
 import collections.abc
+import itertools
 import struct
 import time
 import warnings
@@ -37,12 +38,13 @@ class Connection(metaclass=ConnectionMeta):
     Connections are created by calling :func:`~asyncpg.connection.connect`.
     """
 
-    __slots__ = ('_protocol', '_transport', '_loop', '_types_stmt',
-                 '_type_by_name_stmt', '_top_xact', '_uid', '_aborted',
-                 '_stmt_cache', '_stmts_to_close', '_listeners',
-                 '_server_version', '_server_caps', '_intro_query',
-                 '_reset_query', '_proxy', '_stmt_exclusive_section',
-                 '_config', '_params', '_addr', '_log_listeners')
+    __slots__ = ('_protocol', '_transport', '_loop',
+                 '_top_xact', '_uid', '_aborted',
+                 '_pool_release_ctr', '_stmt_cache', '_stmts_to_close',
+                 '_listeners', '_server_version', '_server_caps',
+                 '_intro_query', '_reset_query', '_proxy',
+                 '_stmt_exclusive_section', '_config', '_params', '_addr',
+                 '_log_listeners')
 
     def __init__(self, protocol, transport, loop,
                  addr: (str, int) or str,
@@ -51,11 +53,13 @@ class Connection(metaclass=ConnectionMeta):
         self._protocol = protocol
         self._transport = transport
         self._loop = loop
-        self._types_stmt = None
-        self._type_by_name_stmt = None
         self._top_xact = None
         self._uid = 0
         self._aborted = False
+        # Incremented very time the connection is released back to a pool.
+        # Used to catch invalid references to connection-related resources
+        # post-release (e.g. explicit prepared statements).
+        self._pool_release_ctr = 0
 
         self._addr = addr
         self._config = config
@@ -80,10 +84,7 @@ class Connection(metaclass=ConnectionMeta):
         self._server_caps = _detect_server_capabilities(
             self._server_version, settings)
 
-        if self._server_version < (9, 2):
-            self._intro_query = introspection.INTRO_LOOKUP_TYPES_91
-        else:
-            self._intro_query = introspection.INTRO_LOOKUP_TYPES
+        self._intro_query = introspection.INTRO_LOOKUP_TYPES
 
         self._reset_query = None
         self._proxy = None
@@ -283,14 +284,17 @@ class Connection(metaclass=ConnectionMeta):
             stmt_name = ''
 
         statement = await self._protocol.prepare(stmt_name, query, timeout)
-
         ready = statement._init_types()
         if ready is not True:
-            if self._types_stmt is None:
-                self._types_stmt = await self.prepare(self._intro_query)
-
-            types = await self._types_stmt.fetch(list(ready))
+            types, intro_stmt = await self.__execute(
+                self._intro_query, (list(ready),), 0, timeout)
             self._protocol.get_settings().register_data_types(types)
+            if not intro_stmt.name and not statement.name:
+                # The introspection query has used an anonymous statement,
+                # which has blown away the anonymous statement we've prepared
+                # for the query, so we need to re-prepare it.
+                statement = await self._protocol.prepare(
+                    stmt_name, query, timeout)
 
         if use_cache:
             self._stmt_cache.put(query, statement)
@@ -352,7 +356,8 @@ class Connection(metaclass=ConnectionMeta):
                             ``command_timeout`` argument to the ``Connection``
                             instance constructor.
 
-        :return: The value of the specified column of the first record.
+        :return: The value of the specified column of the first record, or
+                 None if no records were returned by the query.
         """
         self._check_open()
         data = await self._execute(query, args, 1, timeout)
@@ -367,7 +372,8 @@ class Connection(metaclass=ConnectionMeta):
         :param args: Query arguments
         :param float timeout: Optional timeout value in seconds.
 
-        :return: The first row as a :class:`Record` instance.
+        :return: The first row as a :class:`Record` instance, or None if
+                 no records were returned by the query.
         """
         self._check_open()
         data = await self._execute(query, args, 1, timeout)
@@ -827,9 +833,6 @@ class Connection(metaclass=ConnectionMeta):
             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
@@ -870,23 +873,14 @@ class Connection(metaclass=ConnectionMeta):
             The ``binary`` keyword argument is deprecated in favor of
             ``format``.
 
+        .. versionchanged:: 0.13.0
+            The ``binary`` keyword argument was removed 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)
-
-        typeinfo = await self._type_by_name_stmt.fetchrow(
-            typename, schema)
+        typeinfo = await self.fetchrow(
+            introspection.TYPE_BY_NAME, typename, schema)
         if not typeinfo:
             raise ValueError('unknown type: {}.{}'.format(schema, typename))
 
@@ -916,12 +910,8 @@ class Connection(metaclass=ConnectionMeta):
         .. 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)
+        typeinfo = await self.fetchrow(
+            introspection.TYPE_BY_NAME, typename, schema)
         if not typeinfo:
             raise ValueError('unknown type: {}.{}'.format(schema, typename))
 
@@ -944,12 +934,8 @@ class Connection(metaclass=ConnectionMeta):
         """
         self._check_open()
 
-        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)
+        typeinfo = await self.fetchrow(
+            introspection.TYPE_BY_NAME, typename, schema)
         if not typeinfo:
             raise ValueError('unknown type: {}.{}'.format(schema, typename))
 
@@ -1168,6 +1154,29 @@ class Connection(metaclass=ConnectionMeta):
 
         self._proxy = proxy
 
+    def _check_listeners(self, listeners, listener_type):
+        if listeners:
+            count = len(listeners)
+
+            w = exceptions.InterfaceWarning(
+                '{conn!r} is being released to the pool but has {c} active '
+                '{type} listener{s}'.format(
+                    conn=self, c=count, type=listener_type,
+                    s='s' if count > 1 else ''))
+
+            warnings.warn(w)
+
+    def _on_release(self, stacklevel=1):
+        # Invalidate external references to the connection.
+        self._pool_release_ctr += 1
+        # Called when the connection is about to be released to the pool.
+        # Let's check that the user has not left any listeners on it.
+        self._check_listeners(
+            list(itertools.chain.from_iterable(self._listeners.values())),
+            'notification')
+        self._check_listeners(
+            self._log_listeners, 'log')
+
     def _drop_local_statement_cache(self):
         self._stmt_cache.clear()
 
@@ -1181,18 +1190,25 @@ class Connection(metaclass=ConnectionMeta):
             self._drop_local_statement_cache()
 
     async def _execute(self, query, args, limit, timeout, return_status=False):
+        with self._stmt_exclusive_section:
+            result, _ = await self.__execute(
+                query, args, limit, timeout, return_status=return_status)
+        return result
+
+    async def __execute(self, query, args, limit, timeout,
+                        return_status=False):
         executor = lambda stmt, timeout: self._protocol.bind_execute(
             stmt, args, '', limit, return_status, timeout)
         timeout = self._protocol._get_timeout(timeout)
-        with self._stmt_exclusive_section:
-            return await self._do_execute(query, executor, timeout)
+        return await self._do_execute(query, executor, timeout)
 
     async def _executemany(self, query, args, timeout):
         executor = lambda stmt, timeout: self._protocol.bind_execute_many(
             stmt, args, '', timeout)
         timeout = self._protocol._get_timeout(timeout)
         with self._stmt_exclusive_section:
-            return await self._do_execute(query, executor, timeout)
+            result, _ = await self._do_execute(query, executor, timeout)
+        return result
 
     async def _do_execute(self, query, executor, timeout, retry=True):
         if timeout is None:
@@ -1214,7 +1230,7 @@ class Connection(metaclass=ConnectionMeta):
                     after = time.monotonic()
                     timeout -= after - before
 
-        except exceptions.InvalidCachedStatementError as e:
+        except exceptions.InvalidCachedStatementError:
             # PostgreSQL will raise an exception when it detects
             # that the result type of the query has changed from
             # when the statement was prepared.  This may happen,
@@ -1241,10 +1257,10 @@ class Connection(metaclass=ConnectionMeta):
             if self._protocol.is_in_transaction() or not retry:
                 raise
             else:
-                result = await self._do_execute(
+                return await self._do_execute(
                     query, executor, timeout, retry=False)
 
-        return result
+        return result, stmt
 
 
 async def connect(dsn=None, *,
diff --git a/asyncpg/connresource.py b/asyncpg/connresource.py
new file mode 100644
index 0000000..2c5170b
--- /dev/null
+++ b/asyncpg/connresource.py
@@ -0,0 +1,38 @@
+
+# Copyright (C) 2016-present the asyncpg authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of asyncpg and is released under
+# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
+
+
+import functools
+
+from . import exceptions
+
+
+def guarded(meth):
+    """A decorator to add a sanity check to ConnectionResource methods."""
+
+    @functools.wraps(meth)
+    def _check(self, *args, **kwargs):
+        self._check_conn_validity(meth.__name__)
+        return meth(self, *args, **kwargs)
+
+    return _check
+
+
+class ConnectionResource:
+    __slots__ = ('_connection', '_con_release_ctr')
+
+    def __init__(self, connection):
+        self._connection = connection
+        self._con_release_ctr = connection._pool_release_ctr
+
+    def _check_conn_validity(self, meth_name):
+        con_release_ctr = self._connection._pool_release_ctr
+        if con_release_ctr != self._con_release_ctr:
+            raise exceptions.InterfaceError(
+                'cannot call {}.{}(): '
+                'the underlying connection has been released back '
+                'to the pool'.format(self.__class__.__name__, meth_name))
diff --git a/asyncpg/cursor.py b/asyncpg/cursor.py
index eedb939..030def0 100644
--- a/asyncpg/cursor.py
+++ b/asyncpg/cursor.py
@@ -8,21 +8,21 @@
 import collections
 
 from . import compat
+from . import connresource
 from . import exceptions
 
 
-class CursorFactory:
+class CursorFactory(connresource.ConnectionResource):
     """A cursor interface for the results of a query.
 
     A cursor interface can be used to initiate efficient traversal of the
     results of a large query.
     """
 
-    __slots__ = ('_state', '_connection', '_args', '_prefetch',
-                 '_query', '_timeout')
+    __slots__ = ('_state', '_args', '_prefetch', '_query', '_timeout')
 
     def __init__(self, connection, query, state, args, prefetch, timeout):
-        self._connection = connection
+        super().__init__(connection)
         self._args = args
         self._prefetch = prefetch
         self._query = query
@@ -32,6 +32,7 @@ class CursorFactory:
             state.attach()
 
     @compat.aiter_compat
+    @connresource.guarded
     def __aiter__(self):
         prefetch = 50 if self._prefetch is None else self._prefetch
         return CursorIterator(self._connection,
@@ -39,6 +40,7 @@ class CursorFactory:
                               self._args, prefetch,
                               self._timeout)
 
+    @connresource.guarded
     def __await__(self):
         if self._prefetch is not None:
             raise exceptions.InterfaceError(
@@ -53,14 +55,13 @@ class CursorFactory:
             self._connection._maybe_gc_stmt(self._state)
 
 
-class BaseCursor:
+class BaseCursor(connresource.ConnectionResource):
 
-    __slots__ = ('_state', '_connection', '_args', '_portal_name',
-                 '_exhausted', '_query')
+    __slots__ = ('_state', '_args', '_portal_name', '_exhausted', '_query')
 
     def __init__(self, connection, query, state, args):
+        super().__init__(connection)
         self._args = args
-        self._connection = connection
         self._state = state
         if state is not None:
             state.attach()
@@ -162,9 +163,11 @@ class CursorIterator(BaseCursor):
         self._timeout = timeout
 
     @compat.aiter_compat
+    @connresource.guarded
     def __aiter__(self):
         return self
 
+    @connresource.guarded
     async def __anext__(self):
         if self._state is None:
             self._state = await self._connection._get_statement(
@@ -199,6 +202,7 @@ class Cursor(BaseCursor):
         await self._bind(timeout)
         return self
 
+    @connresource.guarded
     async def fetch(self, n, *, timeout=None):
         r"""Return the next *n* rows as a list of :class:`Record` objects.
 
@@ -216,6 +220,7 @@ class Cursor(BaseCursor):
             self._exhausted = True
         return recs
 
+    @connresource.guarded
     async def fetchrow(self, *, timeout=None):
         r"""Return the next row.
 
@@ -232,6 +237,7 @@ class Cursor(BaseCursor):
             return None
         return recs[0]
 
+    @connresource.guarded
     async def forward(self, n, *, timeout=None) -> int:
         r"""Skip over the next *n* rows.
 
diff --git a/asyncpg/exceptions/__init__.py b/asyncpg/exceptions/__init__.py
index 7fd0cb8..9eb2d42 100644
--- a/asyncpg/exceptions/__init__.py
+++ b/asyncpg/exceptions/__init__.py
@@ -780,10 +780,6 @@ class LockNotAvailableError(ObjectNotInPrerequisiteStateError):
     sqlstate = '55P03'
 
 
-class UnsafeNewEnumValueUsageError(ObjectNotInPrerequisiteStateError):
-    sqlstate = '55P04'
-
-
 class OperatorInterventionError(_base.PostgresError):
     sqlstate = '57000'
 
@@ -1102,8 +1098,8 @@ __all__ = _base.__all__ + (
     'UndefinedColumnError', 'UndefinedFileError',
     'UndefinedFunctionError', 'UndefinedObjectError',
     'UndefinedParameterError', 'UndefinedTableError',
-    'UniqueViolationError', 'UnsafeNewEnumValueUsageError',
-    'UnterminatedCStringError', 'UntranslatableCharacterError',
-    'WindowingError', 'WithCheckOptionViolationError',
-    'WrongObjectTypeError', 'ZeroLengthCharacterStringError'
+    'UniqueViolationError', 'UnterminatedCStringError',
+    'UntranslatableCharacterError', 'WindowingError',
+    'WithCheckOptionViolationError', 'WrongObjectTypeError',
+    'ZeroLengthCharacterStringError'
 )
diff --git a/asyncpg/exceptions/_base.py b/asyncpg/exceptions/_base.py
index 99b525c..b7c146a 100644
--- a/asyncpg/exceptions/_base.py
+++ b/asyncpg/exceptions/_base.py
@@ -10,7 +10,8 @@ import sys
 
 
 __all__ = ('PostgresError', 'FatalPostgresError', 'UnknownPostgresError',
-           'InterfaceError', 'PostgresLogMessage')
+           'InterfaceError', 'InterfaceWarning', 'PostgresLogMessage',
+           'InternalClientError')
 
 
 def _is_asyncpg_class(cls):
@@ -159,9 +160,40 @@ class UnknownPostgresError(FatalPostgresError):
     """An error with an unknown SQLSTATE code."""
 
 
-class InterfaceError(Exception):
+class InterfaceMessage:
+    def __init__(self, *, detail=None, hint=None):
+        self.detail = detail
+        self.hint = hint
+
+    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
+
+
+class InterfaceError(InterfaceMessage, Exception):
     """An error caused by improper use of asyncpg API."""
 
+    def __init__(self, msg, *, detail=None, hint=None):
+        InterfaceMessage.__init__(self, detail=detail, hint=hint)
+        Exception.__init__(self, msg)
+
+
+class InterfaceWarning(InterfaceMessage, UserWarning):
+    """A warning caused by an improper use of asyncpg API."""
+
+    def __init__(self, msg, *, detail=None, hint=None):
+        InterfaceMessage.__init__(self, detail=detail, hint=hint)
+        UserWarning.__init__(self, msg)
+
+
+class InternalClientError(Exception):
+    pass
+
 
 class PostgresLogMessage(PostgresMessage):
     """A base class for non-error server messages."""
diff --git a/asyncpg/introspection.py b/asyncpg/introspection.py
index d9128ea..ad19c1d 100644
--- a/asyncpg/introspection.py
+++ b/asyncpg/introspection.py
@@ -5,29 +5,8 @@
 # the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
 
 
-INTRO_LOOKUP_TYPES = '''\
-WITH RECURSIVE typeinfo_tree(
-    oid, ns, name, kind, basetype, has_bin_io, elemtype, elemdelim,
-    range_subtype, elem_has_bin_io, attrtypoids, attrnames, depth)
-AS (
-    WITH composite_attrs
-    AS (
-        SELECT
-            c.reltype                                        AS comptype_oid,
-            array_agg(ia.atttypid ORDER BY ia.attnum)        AS typoids,
-            array_agg(ia.attname::text ORDER BY ia.attnum)   AS names
-        FROM
-            pg_attribute ia
-            INNER JOIN pg_class c
-                ON (ia.attrelid = c.oid)
-        WHERE
-            ia.attnum > 0 AND NOT ia.attisdropped
-        GROUP BY
-            c.reltype
-    ),
-
-    typeinfo
-    AS (
+_TYPEINFO = '''\
+    (
         SELECT
             t.oid                           AS oid,
             ns.nspname                      AS ns,
@@ -76,16 +55,28 @@ AS (
                     elem_t.typsend::oid != 0
             END)                            AS elem_has_bin_io,
             (CASE WHEN t.typtype = 'c' THEN
-                (SELECT ca.typoids
-                FROM composite_attrs AS ca
-                WHERE ca.comptype_oid = t.oid)
+                (SELECT
+                    array_agg(ia.atttypid ORDER BY ia.attnum)
+                FROM
+                    pg_attribute ia
+                    INNER JOIN pg_class c
+                        ON (ia.attrelid = c.oid)
+                WHERE
+                    ia.attnum > 0 AND NOT ia.attisdropped
+                    AND c.reltype = t.oid)
 
                 ELSE NULL
             END)                            AS attrtypoids,
             (CASE WHEN t.typtype = 'c' THEN
-                (SELECT ca.names
-                FROM composite_attrs AS ca
-                WHERE ca.comptype_oid = t.oid)
+                (SELECT
+                    array_agg(ia.attname::text ORDER BY ia.attnum)
+                FROM
+                    pg_attribute ia
+                    INNER JOIN pg_class c
+                        ON (ia.attrelid = c.oid)
+                WHERE
+                    ia.attnum > 0 AND NOT ia.attisdropped
+                    AND c.reltype = t.oid)
 
                 ELSE NULL
             END)                            AS attrnames
@@ -102,133 +93,20 @@ AS (
                 t.oid = range_t.rngtypid
             )
     )
-
-    SELECT
-        ti.oid, ti.ns, ti.name, ti.kind, ti.basetype, ti.has_bin_io,
-        ti.elemtype, ti.elemdelim, ti.range_subtype, ti.elem_has_bin_io,
-        ti.attrtypoids, ti.attrnames, 0
-    FROM
-        typeinfo AS ti
-    WHERE
-        ti.oid = any($1::oid[])
-
-    UNION ALL
-
-    SELECT
-        ti.oid, ti.ns, ti.name, ti.kind, ti.basetype, ti.has_bin_io,
-        ti.elemtype, ti.elemdelim, ti.range_subtype, ti.elem_has_bin_io,
-        ti.attrtypoids, ti.attrnames, tt.depth + 1
-    FROM
-        typeinfo ti,
-        typeinfo_tree tt
-    WHERE
-        (tt.elemtype IS NOT NULL AND ti.oid = tt.elemtype)
-        OR (tt.attrtypoids IS NOT NULL AND ti.oid = any(tt.attrtypoids))
-        OR (tt.range_subtype IS NOT NULL AND ti.oid = tt.range_subtype)
-)
-
-SELECT DISTINCT
-    *
-FROM
-    typeinfo_tree
-ORDER BY
-    depth DESC
 '''
 
 
-# Prior to 9.2 PostgreSQL did not have range types.
-INTRO_LOOKUP_TYPES_91 = '''\
+INTRO_LOOKUP_TYPES = '''\
 WITH RECURSIVE typeinfo_tree(
     oid, ns, name, kind, basetype, has_bin_io, elemtype, elemdelim,
     range_subtype, elem_has_bin_io, attrtypoids, attrnames, depth)
 AS (
-    WITH composite_attrs
-    AS (
-        SELECT
-            c.reltype                                        AS comptype_oid,
-            array_agg(ia.atttypid ORDER BY ia.attnum)        AS typoids,
-            array_agg(ia.attname::text ORDER BY ia.attnum)   AS names
-        FROM
-            pg_attribute ia
-            INNER JOIN pg_class c
-                ON (ia.attrelid = c.oid)
-        WHERE
-            ia.attnum > 0 AND NOT ia.attisdropped
-        GROUP BY
-            c.reltype
-    ),
-
-    typeinfo
... 60012 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