This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

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

Created on 2017-11-28 10:23 by cbelu, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 10398 merged xtreak, 2018-11-07 19:32
PR 10855 merged miss-islington, 2018-12-03 08:09
Messages (12)
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 committer) 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 committer) 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
msg330907 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2018-12-03 07:58
New changeset c667b094ae37799a7e42ba5cd2ad501cc7920888 by Chris Withers (Xtreak) in branch 'master':
bpo-32153: Add unit test for create_autospec with partial function returned in getattr (#10398)
https://github.com/python/cpython/commit/c667b094ae37799a7e42ba5cd2ad501cc7920888
msg330909 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2018-12-03 08:26
New changeset 1ef06c62d3c05cbba6448c56af30a09c551c9ec2 by Chris Withers (Miss Islington (bot)) in branch '3.7':
bpo-32153: Add unit test for create_autospec with partial function returned in getattr (GH-10398) (#10855)
https://github.com/python/cpython/commit/1ef06c62d3c05cbba6448c56af30a09c551c9ec2
msg331675 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2018-12-12 08:00
@cjw296 since the unit tests were added and the original report is fixed with 3.7 and above can this be closed?
msg331677 - (view) Author: Chris Withers (cjw296) * (Python committer) Date: 2018-12-12 08:05
Yep! Good catch :-)
History
Date User Action Args
2022-04-11 14:58:54adminsetgithub: 76334
2018-12-12 08:05:43cjw296setstatus: open -> closed
resolution: fixed
messages: + msg331677

stage: patch review -> resolved
2018-12-12 08:00:15xtreaksetmessages: + msg331675
2018-12-03 08:26:10cjw296setmessages: + msg330909
2018-12-03 08:09:02miss-islingtonsetpull_requests: + pull_request10090
2018-12-03 07:58:25cjw296setnosy: + cjw296
messages: + msg330907
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