diff --git a/asyncpg/connection.py b/asyncpg/connection.py index 77fbf48e..26e8ea37 100644 --- a/asyncpg/connection.py +++ b/asyncpg/connection.py @@ -13,6 +13,7 @@ import struct import time import urllib.parse +import warnings from . import cursor from . import exceptions @@ -216,7 +217,12 @@ async def execute(self, query: str, *args, timeout: float=None) -> str: _, status, _ = await self._execute(query, args, 0, timeout, True) return status.decode() - async def executemany(self, command: str, args, timeout: float=None): + async def executemany(self, command: str, args, + _timeout: float=None, **kw): + # The real signature of this method is: + # + # executemany(self, command: str, args, *, timeout: float=None) + # """Execute an SQL *command* for each sequence of arguments in *args*. Example: @@ -234,6 +240,23 @@ async def executemany(self, command: str, args, timeout: float=None): .. versionadded:: 0.7.0 """ + if 'timeout' in kw: + timeout = kw.pop('timeout') + else: + timeout = _timeout + if timeout is not None: + warnings.warn( + "Passing 'timeout' as a positional argument to " + "executemany() is deprecated and will be removed in " + "asyncpg 0.11.0. Pass it as a keyword argument instead: " + "`executemany(..., timeout=...)`.", + DeprecationWarning, stacklevel=2) + if kw: + first_kwarg = next(iter(kw)) + raise TypeError( + 'executemany() got an unexpected keyword argument {!r}'.format( + first_kwarg)) + return await self._executemany(command, args, timeout) async def _get_statement(self, query, timeout, *, named: bool=False): @@ -948,3 +971,21 @@ def _detect_server_capabilities(server_version, connection_settings): sql_reset=sql_reset, sql_close_all=sql_close_all ) + + +def _patch_executemany_signature(): + # Patch Connection.executemany() signature to remove '**kw' parameter + # and change '_timeout' keyword arg to 'timeout' keyword-only arg. + # TODO Remove in 0.11.0. + import inspect + sig = inspect.signature(Connection.executemany) + params = sig.parameters.copy() + params.pop('kw') + timeout = params.pop('_timeout') + timeout = timeout.replace(name='timeout', kind=timeout.KEYWORD_ONLY) + params['timeout'] = timeout + Connection.executemany.__signature__ = sig.replace( + parameters=params.values()) + + +_patch_executemany_signature() diff --git a/tests/test_execute.py b/tests/test_execute.py index 31b667c4..249be033 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -7,6 +7,8 @@ import asyncio import asyncpg +import inspect +import warnings from asyncpg import _testbase as tb @@ -152,3 +154,29 @@ async def test_execute_many_2(self): ''', good_data) finally: await self.con.execute('DROP TABLE exmany') + + async def test_execute_many_3_kwonly_timeout(self): + with self.assertWarnsRegex(DeprecationWarning, + "Passing 'timeout' as a positional"): + await self.con.executemany( + '''SELECT $1::int''', + [(1,), (2,)], + 1) + + with warnings.catch_warnings(): + warnings.simplefilter("error") + + # Test that passing timeout as a kwarg doesn't trigger a warning. + await self.con.executemany( + '''SELECT $1::int''', + [(1,), (2,)], + timeout=1) + + # Test that not passing timeout is fine too. + await self.con.executemany( + '''SELECT $1::int''', + [(1,), (2,)]) + + self.assertEqual( + str(inspect.signature(self.con.executemany)), + '(command:str, args, *, timeout:float=None)')