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

Reply via email to