[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