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

Created on 2017-11-28 10:23 by cbelu, last changed 2018-09-16 14:14 by anthony-flury.

Messages (5)
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.
History
Date User Action Args
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