Skip to content

Commit 9bf8d82

Browse files
authored
gh-94924: support inspect.iscoroutinefunction in create_autospec(async_def) (#94962)
* support inspect.iscoroutinefunction in create_autospec(async_def) * test create_autospec with inspect.iscoroutine and inspect.iscoroutinefunction * test when create_autospec functions check their signature
1 parent 0f885ff commit 9bf8d82

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

Lib/test/test_unittest/testmock/testasync.py

+23
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,9 @@ async def main():
232232
run(main())
233233

234234
self.assertTrue(iscoroutinefunction(spec))
235+
self.assertTrue(inspect.iscoroutinefunction(spec))
235236
self.assertTrue(asyncio.iscoroutine(awaitable))
237+
self.assertTrue(inspect.iscoroutine(awaitable))
236238
self.assertEqual(spec.await_count, 1)
237239
self.assertEqual(spec.await_args, call(1, 2, c=3))
238240
self.assertEqual(spec.await_args_list, [call(1, 2, c=3)])
@@ -244,6 +246,25 @@ async def main():
244246
with self.assertRaises(AssertionError):
245247
spec.assert_any_await(e=1)
246248

249+
def test_autospec_checks_signature(self):
250+
spec = create_autospec(async_func_args)
251+
# signature is not checked when called
252+
awaitable = spec()
253+
self.assertListEqual(spec.mock_calls, [])
254+
255+
async def main():
256+
await awaitable
257+
258+
# but it is checked when awaited
259+
with self.assertRaises(TypeError):
260+
run(main())
261+
262+
# _checksig_ raises before running or awaiting the mock
263+
self.assertListEqual(spec.mock_calls, [])
264+
self.assertEqual(spec.await_count, 0)
265+
self.assertIsNone(spec.await_args)
266+
self.assertEqual(spec.await_args_list, [])
267+
spec.assert_not_awaited()
247268

248269
def test_patch_with_autospec(self):
249270

@@ -253,7 +274,9 @@ async def test_async():
253274
self.assertIsInstance(mock_method.mock, AsyncMock)
254275

255276
self.assertTrue(iscoroutinefunction(mock_method))
277+
self.assertTrue(inspect.iscoroutinefunction(mock_method))
256278
self.assertTrue(asyncio.iscoroutine(awaitable))
279+
self.assertTrue(inspect.iscoroutine(awaitable))
257280
self.assertTrue(inspect.isawaitable(awaitable))
258281

259282
# Verify the default values during mock setup

Lib/unittest/mock.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,33 @@ def checksig(*args, **kwargs):
204204
_setup_func(funcopy, mock, sig)
205205
return funcopy
206206

207+
def _set_async_signature(mock, original, instance=False, is_async_mock=False):
208+
# creates an async function with signature (*args, **kwargs) that delegates to a
209+
# mock. It still does signature checking by calling a lambda with the same
210+
# signature as the original.
211+
212+
skipfirst = isinstance(original, type)
213+
result = _get_signature_object(original, instance, skipfirst)
214+
if result is None:
215+
return mock
216+
func, sig = result
217+
def checksig(*args, **kwargs):
218+
sig.bind(*args, **kwargs)
219+
_copy_func_details(func, checksig)
220+
221+
name = original.__name__
222+
if not name.isidentifier():
223+
name = 'funcopy'
224+
context = {'_checksig_': checksig, 'mock': mock}
225+
src = """async def %s(*args, **kwargs):
226+
_checksig_(*args, **kwargs)
227+
return await mock(*args, **kwargs)""" % name
228+
exec (src, context)
229+
funcopy = context[name]
230+
_setup_func(funcopy, mock, sig)
231+
_setup_async_mock(funcopy)
232+
return funcopy
233+
207234

208235
def _setup_func(funcopy, mock, sig):
209236
funcopy.mock = mock
@@ -2745,9 +2772,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
27452772
if isinstance(spec, FunctionTypes):
27462773
# should only happen at the top level because we don't
27472774
# recurse for functions
2748-
mock = _set_signature(mock, spec)
27492775
if is_async_func:
2750-
_setup_async_mock(mock)
2776+
mock = _set_async_signature(mock, spec)
2777+
else:
2778+
mock = _set_signature(mock, spec)
27512779
else:
27522780
_check_signature(spec, mock, is_type, instance)
27532781

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:func:`unittest.mock.create_autospec` now properly returns coroutine functions compatible with :func:`inspect.iscoroutinefunction`

0 commit comments

Comments
 (0)