Author xtreak
Recipients asvetlov, czardoz, lisroach, xtreak, yselivanov
Date 2019-12-18.05:52:11
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1576648331.48.0.214494385886.issue39082@roundup.psfhosted.org>
In-reply-to
Content
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
History
Date User Action Args
2019-12-18 05:52:11xtreaksetrecipients: + xtreak, asvetlov, yselivanov, czardoz, lisroach
2019-12-18 05:52:11xtreaksetmessageid: <1576648331.48.0.214494385886.issue39082@roundup.psfhosted.org>
2019-12-18 05:52:11xtreaklinkissue39082 messages
2019-12-18 05:52:11xtreakcreate