[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