
This issue trackerhas been migrated toGitHub, and is currentlyread-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.
Created on2019-12-18 00:36 byczardoz, last changed2022-04-11 14:59 byadmin. This issue is nowclosed.
| Pull Requests | |||
|---|---|---|---|
| URL | Status | Linked | Edit |
| PR 17717 | closed | czardoz,2019-12-27 20:31 | |
| PR 18116 | merged | python-dev,2020-01-22 14:36 | |
| PR 18190 | merged | mkokotovich,2020-01-26 02:33 | |
| Messages (7) | |||
|---|---|---|---|
| msg358601 -(view) | Author: Aniket Panse (czardoz)* | Date: 2019-12-18 00:36 | |
Currently, patch is unable to correctly patch coroutinefunctions decorated with `@staticmethod` or `@classmethod`.Example:```[*] aniketpanse [~/git/cpython] -> ./python ±[master]Python 3.9.0a1+ (heads/master:50d4f12958, Dec 17 2019, 16:31:30) [GCC 9.2.1 20190827 (Red Hat 9.2.1-1)] on linuxType "help", "copyright", "credits" or "license" for more information.>>> class Helper:... @classmethod... async def async_class_method(cls):... pass... >>> from unittest.mock import patch>>> patch("Helper.async_class_method")<unittest.mock._patch object at 0x7fc28ddbbf40>```This should ideally return an `AsyncMock()`. | |||
| msg358609 -(view) | Author: Karthikeyan Singaravelan (xtreak)*![]() | Date: 2019-12-18 03:21 | |
Using patch as a function just returns a patch object. You need to call start method on the patch object to return a Mock. | |||
| msg358612 -(view) | Author: Karthikeyan Singaravelan (xtreak)*![]() | Date: 2019-12-18 05:52 | |
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.pyfrom unittest.mock import patchimport inspectclass Helper: @classmethod async def async_class_method(cls): pass @staticmethod async def async_static_method(*args, **kwargs): passprint("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.pyPatching async static methodHelper.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) = Trueinspect.iscoroutinefunction(Helper.__dict__['async_class_method']) = Falseinspect.iscoroutinefunction(getattr(Helper, 'async_class_method')) = True<MagicMock name='async_class_method' id='4537489440'>Patching async static methodHelper.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) = Trueinspect.iscoroutinefunction(Helper.__dict__['async_static_method']) = Falseinspect.iscoroutinefunction(getattr(Helper, 'async_static_method')) = True<MagicMock name='async_static_method' id='4537741520'>Detect __func__ to be used for iscoroutinefunctiondiff --gitLib/unittest/mock.pyLib/unittest/mock.pyindexcd5a2aeb60..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 | |||
| msg358618 -(view) | Author: Karthikeyan Singaravelan (xtreak)*![]() | Date: 2019-12-18 09:21 | |
Found Raymond's answer on the difference between __dict__ and attribute lookup regarding descriptors to be useful here :https://stackoverflow.com/a/44600603 | |||
| msg360470 -(view) | Author: Matt Kokotovich (mkokotovich)* | Date: 2020-01-22 14:39 | |
I'd love to see this issue resolved, as it is keeping me from being able to switch to 3.8. I have a PR with Karthikeyan's suggestion, as I agree it makes more sense and could apply to more cases:https://github.com/python/cpython/pull/18116 | |||
| msg360679 -(view) | Author: Chris Withers (cjw296)*![]() | Date: 2020-01-25 10:17 | |
New changeset62865f4532094017a9b780b704686ca9734bc329 by Chris Withers (Matthew Kokotovich) in branch 'master':bpo-39082: Allow AsyncMock to correctly patch static/class methods (GH-18116)https://github.com/python/cpython/commit/62865f4532094017a9b780b704686ca9734bc329 | |||
| msg360727 -(view) | Author: Chris Withers (cjw296)*![]() | Date: 2020-01-26 15:30 | |
New changeset19be85c76503535c101b38194d282187de0ff631 by Chris Withers (Matthew Kokotovich) in branch '3.8':[3.8]bpo-39082: Allow AsyncMock to correctly patch static/class methods (GH-18190)https://github.com/python/cpython/commit/19be85c76503535c101b38194d282187de0ff631 | |||
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2022-04-11 14:59:24 | admin | set | github: 83263 |
| 2020-01-26 15:30:33 | cjw296 | set | messages: +msg360727 |
| 2020-01-26 02:33:44 | mkokotovich | set | pull_requests: +pull_request17573 |
| 2020-01-25 10:18:45 | cjw296 | set | status: open -> closed resolution: fixed stage: patch review -> resolved |
| 2020-01-25 10:17:50 | cjw296 | set | nosy: +cjw296 messages: +msg360679 |
| 2020-01-22 14:39:26 | mkokotovich | set | messages: +msg360470 |
| 2020-01-22 14:36:43 | python-dev | set | pull_requests: +pull_request17503 |
| 2020-01-20 20:45:40 | mkokotovich | set | nosy: +mkokotovich |
| 2019-12-27 20:31:19 | czardoz | set | keywords: +patch stage: patch review pull_requests: +pull_request17162 |
| 2019-12-18 09:21:58 | xtreak | set | messages: +msg358618 |
| 2019-12-18 05:52:11 | xtreak | set | messages: +msg358612 components: + Library (Lib), - Tests, asyncio |
| 2019-12-18 03:21:39 | xtreak | set | nosy: +lisroach,xtreak messages: +msg358609 |
| 2019-12-18 00:36:45 | czardoz | create | |