From 1a393123fe761a5d799a541736ced30ff4489ee5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Sun, 17 Jul 2022 12:43:30 +0100 Subject: [PATCH 1/4] support inspect.iscoroutinefunction in create_autospec(async_def) --- Lib/unittest/mock.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index cd46fea5162aba..8ee56cd7ad3cd3 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -197,6 +197,33 @@ def checksig(*args, **kwargs): _setup_func(funcopy, mock, sig) return funcopy +def _set_async_signature(mock, original, instance=False, is_async_mock=False): + # creates an async function with signature (*args, **kwargs) that delegates to a + # mock. It still does signature checking by calling a lambda with the same + # signature as the original. + + skipfirst = isinstance(original, type) + result = _get_signature_object(original, instance, skipfirst) + if result is None: + return mock + func, sig = result + def checksig(*args, **kwargs): + sig.bind(*args, **kwargs) + _copy_func_details(func, checksig) + + name = original.__name__ + if not name.isidentifier(): + name = 'funcopy' + context = {'_checksig_': checksig, 'mock': mock} + src = """async def %s(*args, **kwargs): + _checksig_(*args, **kwargs) + return await mock(*args, **kwargs)""" % name + exec (src, context) + funcopy = context[name] + _setup_func(funcopy, mock, sig) + _setup_async_mock(funcopy) + return funcopy + def _setup_func(funcopy, mock, sig): funcopy.mock = mock @@ -2681,9 +2708,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if isinstance(spec, FunctionTypes): # should only happen at the top level because we don't # recurse for functions - mock = _set_signature(mock, spec) if is_async_func: - _setup_async_mock(mock) + mock = _set_async_signature(mock, spec) + else: + mock = _set_signature(mock, spec) else: _check_signature(spec, mock, is_type, instance) From 0596416d31a41195042ddf1c519c144a8a5c8aa9 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 18 Jul 2022 14:58:10 +0100 Subject: [PATCH 2/4] test create_autospec with inspect.iscoroutine and inspect.iscoroutinefunction --- Lib/test/test_unittest/testmock/testasync.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_unittest/testmock/testasync.py b/Lib/test/test_unittest/testmock/testasync.py index 1bab671acdef18..989a3c3a1d8600 100644 --- a/Lib/test/test_unittest/testmock/testasync.py +++ b/Lib/test/test_unittest/testmock/testasync.py @@ -219,7 +219,9 @@ async def main(): run(main()) self.assertTrue(iscoroutinefunction(spec)) + self.assertTrue(inspect.iscoroutinefunction(spec)) self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.iscoroutine(awaitable)) self.assertEqual(spec.await_count, 1) self.assertEqual(spec.await_args, call(1, 2, c=3)) self.assertEqual(spec.await_args_list, [call(1, 2, c=3)]) @@ -240,7 +242,9 @@ async def test_async(): self.assertIsInstance(mock_method.mock, AsyncMock) self.assertTrue(iscoroutinefunction(mock_method)) + self.assertTrue(inspect.iscoroutinefunction(mock_method)) self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.iscoroutine(awaitable)) self.assertTrue(inspect.isawaitable(awaitable)) # Verify the default values during mock setup From 4ab38df251070aca1afb3820e65953a355f90fe8 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 18 Jul 2022 15:14:07 +0100 Subject: [PATCH 3/4] test when create_autospec functions check their signature --- Lib/test/test_unittest/testmock/testasync.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lib/test/test_unittest/testmock/testasync.py b/Lib/test/test_unittest/testmock/testasync.py index 989a3c3a1d8600..933c9f0139267b 100644 --- a/Lib/test/test_unittest/testmock/testasync.py +++ b/Lib/test/test_unittest/testmock/testasync.py @@ -233,6 +233,25 @@ async def main(): with self.assertRaises(AssertionError): spec.assert_any_await(e=1) + def test_autospec_checks_signature(self): + spec = create_autospec(async_func_args) + # signature is not checked when called + awaitable = spec() + self.assertListEqual(spec.mock_calls, []) + + async def main(): + await awaitable + + # but it is checked when awaited + with self.assertRaises(TypeError): + run(main()) + + # _checksig_ raises before running or awaiting the mock + self.assertListEqual(spec.mock_calls, []) + self.assertEqual(spec.await_count, 0) + self.assertIsNone(spec.await_args) + self.assertEqual(spec.await_args_list, []) + spec.assert_not_awaited() def test_patch_with_autospec(self): From 034f947cb3e7bda8fa7317e94eb3c7b87f2027aa Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 18 Jul 2022 14:20:59 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst diff --git a/Misc/NEWS.d/next/Library/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst b/Misc/NEWS.d/next/Library/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst new file mode 100644 index 00000000000000..7882f224e75ad5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst @@ -0,0 +1 @@ +:func:`unittest.mock.create_autospec` now properly returns coroutine functions compatible with :func:`inspect.iscoroutinefunction`