Repository navigation
Patched async function not registering calls until coroutine is awaited #137594
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Aug 9, 2025 - addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Aug 9, 2025 This behavior started happening after I upgraded from 3.12 to 3.13.6.
Can you do git bisect to find which commit caused the change in behavior?
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
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.# gh137594_autospec from unittest.mock import create_autospec async def a(b, c): pass spec = create_autospec(a) spec()
Before 9bf8d82
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'main as of c919d02
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 tracebackA simpler reproducer for the issue without pytest dependency
import asyncio from unittest.mock import patch import other_module async def a(i): pass async def a1(i): pass def b(value): return a(value) async def test_1(): with patch(f"{__name__}.a", side_effect=a1, autospec=True) as a_mock: result = b(10) assert a_mock.called # This should be True because 'a' was called, but it's False await result assert a_mock.called # Now it's True asyncio.run(test_1())
cc: @graingert @cjw296
@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.
Relevant test : https://github.com/python/cpython/pull/94962/files#diff-701e692796f7bc8001aec6f354f4770ad9fdf05116ca6934fc1dbf7705f80fdbR251-R252
Python 3.11 vs main difference
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'
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsTodo
Bug report
Bug description:
When using
autospec=Truethe 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.pyFile
tests/test_1.pyCPython versions tested on:
3.13
Operating systems tested on:
Linux