Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mock.patch.object does not persist __module__ name for functions #81015

Open
goodspark mannequin opened this issue May 7, 2019 · 2 comments
Open

mock.patch.object does not persist __module__ name for functions #81015

goodspark mannequin opened this issue May 7, 2019 · 2 comments
Labels
3.7 (EOL) end of life 3.8 only security fixes stdlib Python modules in the Lib dir tests Tests in the Lib/test dir type-bug An unexpected behavior, bug, or error

Comments

@goodspark
Copy link
Mannequin

goodspark mannequin commented May 7, 2019

BPO 36834
Nosy @cjw296, @voidspace, @mariocj89, @goodspark, @tirkarthi
Files
  • minimal-test-case.zip: ZIP file with minimal test case
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2019-05-07.14:44:21.027>
    labels = ['3.7', 'tests', '3.8', 'type-bug', 'library']
    title = 'mock.patch.object does not persist __module__ name for functions'
    updated_at = <Date 2019-05-07.15:46:44.614>
    user = 'https://github.com/goodspark'

    bugs.python.org fields:

    activity = <Date 2019-05-07.15:46:44.614>
    actor = 'xtreak'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)', 'Tests']
    creation = <Date 2019-05-07.14:44:21.027>
    creator = 'spark'
    dependencies = []
    files = ['48309']
    hgrepos = []
    issue_num = 36834
    keywords = []
    message_count = 2.0
    messages = ['341738', '341756']
    nosy_count = 5.0
    nosy_names = ['cjw296', 'michael.foord', 'mariocj89', 'spark', 'xtreak']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = None
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue36834'
    versions = ['Python 3.7', 'Python 3.8']

    @goodspark
    Copy link
    Mannequin Author

    goodspark mannequin commented May 7, 2019

    The expectation is that the __module__ attribute for a patched function should persist after patching.

    Minimal test case is attached. Simply run pytest in a venv with the files.

    Output:
    def test_zxc():
    with mock.patch.object(mymodule, 'asd', side_effect=mymodule.asd, autospec=True) as spy_asd:

          assert spy_asd.\_\_module__ == 'mymodule'
    

    E AssertionError: assert None == 'mymodule'
    E + where None = <function asd at 0x7fe4cd6fd620>.__module__

    test_mymodule.py:8: AssertionError

    Originally reported at pytest-dev/pytest-mock#146 before it was determined this was a unittest.mock issue.

    Happens on both Python 2.7 and 3.7. Probably not really tied to a specific Python version and more of mock library issue.

    My local venv:
    Python 3.7.2
    pytest 4.4.1

    @goodspark goodspark mannequin added 3.7 (EOL) end of life stdlib Python modules in the Lib dir tests Tests in the Lib/test dir labels May 7, 2019
    @tirkarthi
    Copy link
    Member

    I guess the problem is that to patch 'bar' in foo.bar the signature of bar is taken and then it's evaluated using exec [1] where the function body has _check_sig that checks for the signature during function call. _check_sig has attributes of func copied. Since it's evaluated using exec the returned funcopy variable might not have __module__ set as per the execution context. One possible approach would be to _copy_func_details(func, funcopy) so that the __module__ and other attributes are copied.

    This produces a test failure where in case of lambdas the __name__ is <lambda> which is not a valid identifier and hence funcopy is used. Then __name__ is set as '<lambda>' by my patch. Hence, 'funcopy' is replaced by '<lambda>' causing below test failure. This could be resolved by setting __name__ again to the expected value. This was newly added for test coverage with adbf178.

    My main worry is that it's used in create_autospec hence do we really want to copy __module__ and others to the patched function for places with create_autospec also with functions supplying autospec=True. This is a user expectation but I would like to know maintainers opinion as well of this change.

    Reproducer

    # cat /tmp/foo.py

    def bar(a: int):
        pass
    
    # /tmp/bar.py
    from unittest import mock
    
    import foo
    
    with mock.patch.object(foo, 'bar', autospec=True) as mocked_attribute:
        assert mocked_attribute.__module__ == 'foo'
    # Python 3.7 __module__ is None
    ➜  cpython git:(master) ✗ python3.7 /tmp/bar.py
    Traceback (most recent call last):
      File "/tmp/bar.py", line 6, in <module>
        assert mocked_attribute.__module__ == 'foo', f'__module__ is {mocked_attribute.__module__}'
    AssertionError: __module__ is None

    # With patch

    ➜ cpython git:(master) ✗ ./python.exe /tmp/bar.py # No failure

    # Patch

    diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
    index 1e8057d5f5..a7006fcf7d 100644
    --- a/Lib/unittest/mock.py
    +++ b/Lib/unittest/mock.py
    @@ -165,6 +165,8 @@ def _set_signature(mock, original, instance=False):
         exec (src, context)
         funcopy = context[name]
         _setup_func(funcopy, mock, sig)
    +    _copy_func_details(func, funcopy)
    +    funcopy.__name__ = name
         return funcopy

    # Test failure without funcopy.__name__ = name in my patch where I don't restore the 'funcopy' for lambdas. But this can be fixed.

    ======================================================================
    FAIL: test_spec_function_no_name (unittest.test.testmock.testhelpers.SpecSignatureTest)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/unittest/test/testmock/testhelpers.py", line 946, in test_spec_function_no_name
        self.assertEqual(mock.__name__, 'funcopy')
    AssertionError: '<lambda>' != 'funcopy'
    - <lambda>
    + funcopy

    Ran 386 tests in 2.911s

    [1]

    exec (src, context)

    @tirkarthi tirkarthi added 3.8 only security fixes type-bug An unexpected behavior, bug, or error labels May 7, 2019
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life 3.8 only security fixes stdlib Python modules in the Lib dir tests Tests in the Lib/test dir type-bug An unexpected behavior, bug, or error
    Projects
    Status: No status
    Development

    No branches or pull requests

    1 participant