classification
Title: mock.create_autospec fails if an attribute is a partial function
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.8, Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: anthony-flury, berker.peksag, cbelu, xtreak
Priority: normal Keywords: patch

Created on 2017-11-28 10:23 by cbelu, last changed 2018-11-07 19:36 by xtreak.

Pull Requests
URL Status Linked Edit
PR 10398 open xtreak, 2018-11-07 19:32
Messages (8)
msg307115 - (view) Author: Claudiu Belu (cbelu) * Date: 2017-11-28 10:23
If an object's attribute is a partial function, mock.create_autospec will fail while trying to copy the partial functions' details to the mocked function, as the partial function does not have the __name__ attribute.

Example:

    import functools

    from unittest import mock


    class Proxy(object):
        def __init__(self, obj):
            self.obj

        def __getattr__(self, name):
            return functools.partial(self.__run_method, name)

        def __run_method(self, name, *args, **kwargs):
            return getattr(self.obj, name)(*args, **kwargs)

            
    a = mock.Mock()
    proxy = Proxy(a)

    mock.create_autospec(proxy)

Output:

    Traceback (most recent call last):
      File "py3_mock_functools.py", line 20, in <module>
        mock.create_autospec(proxy)
      File "/usr/lib/python3.5/unittest/mock.py", line 2156, in create_autospec
        _check_signature(spec, mock, is_type, instance)
      File "/usr/lib/python3.5/unittest/mock.py", line 112, in _check_signature
        _copy_func_details(func, checksig)
      File "/usr/lib/python3.5/unittest/mock.py", line 117, in _copy_func_details
        funcopy.__name__ = func.__name__
    AttributeError: 'functools.partial' object has no attribute '__name__'
msg325465 - (view) Author: Anthony Flury (anthony-flury) * Date: 2018-09-16 00:02
It seems to me that we have three alternatives : 

  1. Refuse to create the mock object with a suitable Exception (rather than a crash
  2. Copy the object and simply ignore the missing dunder_name (so that funcopy dunder_name is not set
  3. Set funcopy dunder_name to a known string when the source dunder_name is missing

It seems obvious to me that option 3 is correct - just a question of what funcopy dunder_name should be set to. I would imagine there is code that uses funcopy dunder_name in some way ?
msg325466 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2018-09-16 02:22
I think option 2 is what functools.update_wrapper() does and it may be better to make create_autospec() consistent with it.

Perhaps we can replace _copy_func_details() with functools.update_wrapper():

    assigments = (
        '__name__', '__doc__', '__text_signature__',
        # And the rest of the attributes in _copy_func_details().
    )
    functools.update_wrapper(..., assigned=assignments, updated=())
msg325467 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2018-09-16 02:29
Or we can special case functools.partial() in _copy_func_details() and use partial_object.func.__name__.

(I'm not sure which solution is better, but we need to the same thing for __doc__ as well.)
msg325487 - (view) Author: Anthony Flury (anthony-flury) * Date: 2018-09-16 14:14
Am not a big fan of special casing, 

I think the functools.update_wrapper is the way to go - will have a look later and produce a pull request with some test cases.
msg327529 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python triager) Date: 2018-10-11 10:18
I think the original error has been fixed with issue28919 where the attribute errors are ignored while copying the functions as suggested by Anthony in solution 2. So can this issue be closed as outdated to reopen a new one for using update_wrapper as enhancement or the title can be changed to reflect the fact that autospec should now use update_wrapper instead of using _copy_func_details ? Correct me if I am wrong on the workflow to update the ticket.

Also there doesn't seem to be any test for this that can possibly added.

Current implementation : 

def _copy_func_details(func, funcopy):
    # we explicitly don't copy func.__dict__ into this copy as it would
    # expose original attributes that should be mocked
    for attribute in (
        '__name__', '__doc__', '__text_signature__',
        '__module__', '__defaults__', '__kwdefaults__',
    ):
        try:
            setattr(funcopy, attribute, getattr(func, attribute))
        except AttributeError:
            pass


Thanks
msg327531 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2018-10-11 10:51
Yes, it would be great if we could convert the snippet in msg307115 to a test case before closing this issue. 3.6 still has this bug, but since we are pretty close to the end of its bugfix maintenance period, we can skip it.
msg329433 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python triager) Date: 2018-11-07 19:36
@berker.peksag I have created a unit test PR and verified that the test fails in 3.6 and passes on master.

# 3.6 branch

   cpython git:(25bd107399)   ./python.exe
Python 3.6.7+ (remotes/upstream/3.6:25bd107399, Nov  8 2018, 00:50:43)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> ^D
   cpython git:(25bd107399)   ./python.exe -m unittest -v unittest.test.testmock.testhelpers.SpecSignatureTest.test_autospec_getattr_partial_function
test_autospec_getattr_partial_function (unittest.test.testmock.testhelpers.SpecSignatureTest) ... ERROR

======================================================================
ERROR: test_autospec_getattr_partial_function (unittest.test.testmock.testhelpers.SpecSignatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/test/testmock/testhelpers.py", line 884, in test_autospec_getattr_partial_function
    autospec = create_autospec(proxy)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/mock.py", line 2182, in create_autospec
    _check_signature(spec, mock, is_type, instance)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/mock.py", line 102, in _check_signature
    _copy_func_details(func, checksig)
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/mock.py", line 107, in _copy_func_details
    funcopy.__name__ = func.__name__
AttributeError: 'functools.partial' object has no attribute '__name__'

----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (errors=1)

# Master

   cpython git:(bpo32153) ./python.exe
Python 3.8.0a0 (heads/bpo32153:3e9cd8d982, Nov  8 2018, 00:53:46)
[Clang 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> ^D
   cpython git:(bpo32153) ./python.exe -m unittest -v unittest.test.testmock.testhelpers.SpecSignatureTest.test_autospec_getattr_partial_function
test_autospec_getattr_partial_function (unittest.test.testmock.testhelpers.SpecSignatureTest) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.005s

OK
History
Date User Action Args
2018-11-07 19:36:17xtreaksetmessages: + msg329433
2018-11-07 19:32:53xtreaksetkeywords: + patch
stage: needs patch -> patch review
pull_requests: + pull_request9683
2018-10-11 10:51:10berker.peksagsetmessages: + msg327531
versions: - Python 3.6
2018-10-11 10:18:20xtreaksetmessages: + msg327529
2018-09-16 14:14:59anthony-flurysetmessages: + msg325487
2018-09-16 02:29:58berker.peksagsetmessages: + msg325467
2018-09-16 02:22:43berker.peksagsetversions: + Python 3.8, - Python 3.5
nosy: + berker.peksag

messages: + msg325466

type: behavior
stage: needs patch
2018-09-16 00:02:54anthony-flurysetnosy: + anthony-flury
messages: + msg325465
2018-09-14 09:31:41xtreaksetnosy: + xtreak
2017-11-28 10:26:00cbelusetversions: - Python 3.8
2017-11-28 10:23:15cbelucreate