Karthikeyan Singaravelan <tir.kar...@gmail.com> added the comment:
There seems to be a difference in obtaining the attribute out of the __dict__ and getattr. patch uses __dict__ [0] to access the attribute to be patched and falls back to getattr. There is a difference in detecting normal attribute access to be a coroutine function versus the one obtained from __dict__ . We can perhaps make _is_async_obj [1] to see if the passed obj is a classmethod/staticmethod and to use __func__ to detect a coroutine function. As a workaround for now you can pass AsyncMock explicitly to patch to return an AsyncMock. There was an open issue (issue36092) about patch and staticmethods/classmethods but not sure this is related to this. Retagging it to remove asyncio. Thanks for the report! # bpo39082.py from unittest.mock import patch import inspect class Helper: @classmethod async def async_class_method(cls): pass @staticmethod async def async_static_method(*args, **kwargs): pass print("Patching async static method") async_patcher = patch("__main__.Helper.async_class_method") print(f"{Helper.async_class_method = }") print(f"{Helper.__dict__['async_class_method'] = }") print(f"{inspect.iscoroutinefunction(Helper.async_class_method) = }") print(f"{inspect.iscoroutinefunction(Helper.__dict__['async_class_method']) = }") print(f"{inspect.iscoroutinefunction(getattr(Helper, 'async_class_method')) = }") mock_ = async_patcher.start() print(mock_) print("\nPatching async static method") async_patcher = patch("__main__.Helper.async_static_method") print(f"{Helper.async_static_method = }") print(f"{Helper.__dict__['async_static_method'] = }") print(f"{inspect.iscoroutinefunction(Helper.async_static_method) = }") print(f"{inspect.iscoroutinefunction(Helper.__dict__['async_static_method']) = }") print(f"{inspect.iscoroutinefunction(getattr(Helper, 'async_static_method')) = }") mock_ = async_patcher.start() print(mock_) $ python3.8 bpo39082.py Patching async static method Helper.async_class_method = <bound method Helper.async_class_method of <class '__main__.Helper'>> Helper.__dict__['async_class_method'] = <classmethod object at 0x10de7bcd0> inspect.iscoroutinefunction(Helper.async_class_method) = True inspect.iscoroutinefunction(Helper.__dict__['async_class_method']) = False inspect.iscoroutinefunction(getattr(Helper, 'async_class_method')) = True <MagicMock name='async_class_method' id='4537489440'> Patching async static method Helper.async_static_method = <function Helper.async_static_method at 0x10e77baf0> Helper.__dict__['async_static_method'] = <staticmethod object at 0x10de7bbb0> inspect.iscoroutinefunction(Helper.async_static_method) = True inspect.iscoroutinefunction(Helper.__dict__['async_static_method']) = False inspect.iscoroutinefunction(getattr(Helper, 'async_static_method')) = True <MagicMock name='async_static_method' id='4537741520'> Detect __func__ to be used for iscoroutinefunction diff --git Lib/unittest/mock.py Lib/unittest/mock.py index cd5a2aeb60..572468ca8d 100644 --- Lib/unittest/mock.py +++ Lib/unittest/mock.py @@ -48,6 +48,8 @@ _safe_super = super def _is_async_obj(obj): if _is_instance_mock(obj) and not isinstance(obj, AsyncMock): return False + if hasattr(obj, '__func__'): + obj = getattr(obj, '__func__') return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj) [0] https://github.com/python/cpython/blob/50d4f12958bf806a4e1a1021d70cfd5d448c5cba/Lib/unittest/mock.py#L1387 [1] https://github.com/python/cpython/blob/50d4f12958bf806a4e1a1021d70cfd5d448c5cba/Lib/unittest/mock.py#L48 ---------- components: +Library (Lib) -Tests, asyncio _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue39082> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com