You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Patched async function not registering calls until coroutine is awaited #137594
When using autospec=True the patched async function only has calls registered after the coroutine is awaited. This behavior started happening after I upgraded from 3.12 to 3.13.6.
Code to reproduce
File tests/other_module.py
asyncdefa(value):
returnvalue*2
File tests/test_1.py
fromunittest.mockimportpatchimportpytestimportother_modulepytestmark=pytest.mark.asyncio(loop_scope="session")
defb(value):
returnother_module.a(value)
asyncdeftest_1():
withpatch.object(other_module, "a", side_effect=other_module.a, autospec=True) asa_mock:
result=b(10)
assertnota_mock.called# This should be True because 'a' was called, but it's Falseawaitresultasserta_mock.called# Now it's True
This seems to be a regression with 9bf8d82 where async def is used instead of def to help inspect.iscoroutinefunction . create_autospec should check for signature as per docs despite the function being async as per the docs. Since the change was in Python 3.13 and not backported maybe set the signature as sync and mark it using markcoroutinefunction introduced in Python 3.12 but this might be an issue with mock backport since inspect will not have markcoroutinefunction.
unittest.mock.create_autospec(spec, spec_set=False, instance=False, **kwargs)
Create a mock object using another object as a spec. Attributes on the mock will use the corresponding attribute on the spec object as their spec.
Functions or methods being mocked will have their arguments checked to ensure that they are called with the correct signature.
python gh137594_autospec.py
Traceback (most recent call last):
File "/home/karthikeyan/stuff/python/gh137594/gh137594_autospec.py", line 7, in <module>
spec()
~~~~^^
File "<string>", line 2, in a
File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 192, in checksig
sig.bind(*args, **kwargs)
~~~~~~~~^^^^^^^^^^^^^^^^^
File "/home/karthikeyan/stuff/python/cpython/Lib/inspect.py", line 3233, in bind
return self._bind(args, kwargs)
~~~~~~~~~~^^^^^^^^^^^^^^
File "/home/karthikeyan/stuff/python/cpython/Lib/inspect.py", line 3146, in _bind
raise TypeError(msg) from None
TypeError: missing a required argument: 'b'
python gh137594_autospec.py
/home/karthikeyan/stuff/python/gh137594/gh137594_autospec.py:7: RuntimeWarning: coroutine 'a' was never awaited
spec()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
A simpler reproducer for the issue without pytest dependency
importasynciofromunittest.mockimportpatchimportother_moduleasyncdefa(i):
passasyncdefa1(i):
passdefb(value):
returna(value)
asyncdeftest_1():
withpatch(f"{__name__}.a", side_effect=a1, autospec=True) asa_mock:
result=b(10)
asserta_mock.called# This should be True because 'a' was called, but it's Falseawaitresultasserta_mock.called# Now it's Trueasyncio.run(test_1())
@tirkarthi - nice work on the investigation. Would your reproducer be something we can add as a unit test to prevent this happening again? If so, then the fix becomes getting that test to pass while still keeping the test(s) introduced in #94962 passing.
I'm not sure I understand the fix you're proposing, but if you could put it and the unit test in a new PR that would be a great place to start. The backport is a secondary concern...
If so, then the fix becomes getting that test to pass while still keeping the test(s) introduced in #94962 passing.
My patch will need a change to the tests since #94962 explicitly has a test so that a coroutine function can be called with invalid signature and the validation is done only when it's awaited unlike the normal flow and in previous versions of python where the signature validation is during the call to coroutine function itself instead of during await. With autospec I would expect the validation to be done during the call itself.
python3.11 -m asyncio
asyncio REPL 3.11.11 (main, Dec 4 2024, 08:55:08) [GCC 9.4.0] on linux
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> async def foo(a, b): pass
...
>>> foo()
Traceback (most recent call last):
File "/usr/lib/python3.11/concurrent/futures/_base.py", line 456, in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result
raise self._exception
File "/usr/lib/python3.11/asyncio/__main__.py", line 34, in callback
coro = func()
^^^^^^
File "<console>", line 1, in <module>
TypeError: foo() missing 2 required positional arguments: 'a' and 'b'
>>> from unittest.mock import create_autospec
>>> mock = create_autospec(foo)
>>> mock()
Traceback (most recent call last):
File "/usr/lib/python3.11/concurrent/futures/_base.py", line 456, in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result
raise self._exception
File "/usr/lib/python3.11/asyncio/__main__.py", line 34, in callback
coro = func()
^^^^^^
File "<console>", line 1, in <module>
File "<string>", line 2, in foo
File "/usr/lib/python3.11/unittest/mock.py", line 192, in checksig
sig.bind(*args, **kwargs)
File "/usr/lib/python3.11/inspect.py", line 3195, in bind
return self._bind(args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/inspect.py", line 3110, in _bind
raise TypeError(msg) from None
TypeError: missing a required argument: 'a'
main branch where invalid signature is not validated during call time.
./python -m asyncio
asyncio REPL 3.15.0a0 (heads/main:d2deb8fdef1, Oct 9 2025, 18:30:25) [GCC 9.4.0] on linux
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> async def foo(a, b): pass
...
>>> foo()
Traceback (most recent call last):
File "/home/karthikeyan/stuff/python/cpython/Lib/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/home/karthikeyan/stuff/python/cpython/Lib/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/karthikeyan/stuff/python/cpython/Lib/asyncio/__main__.py", line 44, in callback
coro = func()
File "<python-input-1>", line 1, in <module>
foo()
~~~^^
TypeError: foo() missing 2 required positional arguments: 'a' and 'b'
>>> from unittest.mock import create_autospec
>>> mock = create_autospec(foo)
# mock() is allowed and signature is validated only during await
>>> m = mock()
>>> await m
Traceback (most recent call last):
File "/home/karthikeyan/stuff/python/cpython/Lib/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/home/karthikeyan/stuff/python/cpython/Lib/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "<python-input-6>", line 1, in <module>
await m
File "<string>", line 2, in foo
File "/home/karthikeyan/stuff/python/cpython/Lib/unittest/mock.py", line 218, in checksig
sig.bind(*args, **kwargs)
~~~~~~~~^^^^^^^^^^^^^^^^^
File "/home/karthikeyan/stuff/python/cpython/Lib/inspect.py", line 3233, in bind
return self._bind(args, kwargs)
~~~~~~~~~~^^^^^^^^^^^^^^
File "/home/karthikeyan/stuff/python/cpython/Lib/inspect.py", line 3146, in _bind
raise TypeError(msg) from None
TypeError: missing a required argument: 'a'
Activity
corona10 commentedon Aug 10, 2025
cc @kumaraditya303
kumaraditya303 commentedon Aug 16, 2025
Can you do git bisect to find which commit caused the change in behavior?
tirkarthi commentedon Sep 6, 2025
This seems to be a regression with 9bf8d82 where
async defis used instead ofdefto helpinspect.iscoroutinefunction.create_autospecshould check for signature as per docs despite the function being async as per the docs. Since the change was in Python 3.13 and not backported maybe set the signature as sync and mark it usingmarkcoroutinefunctionintroduced in Python 3.12 but this might be an issue with mock backport since inspect will not havemarkcoroutinefunction.https://docs.python.org/3/library/inspect.html#inspect.markcoroutinefunction
https://docs.python.org/3/library/unittest.mock.html#unittest.mock.create_autospec
Before 9bf8d82
main as of c919d02
A simpler reproducer for the issue without pytest dependency
cc: @graingert @cjw296
cjw296 commentedon Sep 15, 2025
@tirkarthi - nice work on the investigation. Would your reproducer be something we can add as a unit test to prevent this happening again? If so, then the fix becomes getting that test to pass while still keeping the test(s) introduced in #94962 passing.
I'm not sure I understand the fix you're proposing, but if you could put it and the unit test in a new PR that would be a great place to start. The backport is a secondary concern...
tirkarthi commentedon Oct 9, 2025
My patch will need a change to the tests since #94962 explicitly has a test so that a coroutine function can be called with invalid signature and the validation is done only when it's awaited unlike the normal flow and in previous versions of python where the signature validation is during the call to coroutine function itself instead of during await. With autospec I would expect the validation to be done during the call itself.
Relevant test : https://github.com/python/cpython/pull/94962/files#diff-701e692796f7bc8001aec6f354f4770ad9fdf05116ca6934fc1dbf7705f80fdbR251-R252
Python 3.11 vs main difference
main branch where invalid signature is not validated during call time.