testing-cabal / mock

The Python mock library
https://docs.python.org/dev/library/unittest.mock.html
BSD 2-Clause "Simplified" License
536 stars 107 forks source link

AttributeError: attribute '__signature__' of 'type' objects is not writable #464

Closed steog88 closed 5 years ago

steog88 commented 5 years ago

I found a problem with mock>2.0.0. When I want to patch a class with autospec=True, if the class itself has already the attribute __signature__ and it is not writable, an AttributeError is raised when running _check_signature.

In the real case, the object I want to patch inherits __signature__ from PySide2.QtWidgets.QObject, so renaming the property is not a viable option. Although my code is much more complicate, I can reproduce the error with the following code (save to test_mock3.py and run, I use python 2.7.15, mock 3.0.5, unittest2 1.1.0 and PySide25.12.3):

import unittest2 as unittest
from mock import patch
from PySide2.QtCore import QObject

class SampleClass(QObject):
    def __init__(self, txt):
        self.txt = txt

class TestMock(unittest.TestCase):
    def test_autospec(self):
        with patch("test_mock3.SampleClass", autospec=True) as _c:
            b = SampleClass("b")

if __name__ == "__main__":
    unittest.main()

The error is the following:

======================================================================
ERROR: test_autospec (__main__.TestMock)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_mock3.py", line 13, in test_autospec
    with patch("test_mock3.SampleClass", return_value=a, autospec=True) as _c:
  File "/usr/lib64/python2.7/site-packages/mock/mock.py", line 1476, in __enter__
    _name=self.attribute, **kwargs)
  File "/usr/lib64/python2.7/site-packages/mock/mock.py", line 2377, in create_autospec
    _check_signature(spec, mock, is_type, instance)
  File "/usr/lib64/python2.7/site-packages/mock/mock.py", line 198, in _check_signature
    type(mock).__signature__ = sig
AttributeError: attribute '__signature__' of 'type' objects is not writable

The same code works with mock==2.0.0.

tirkarthi commented 5 years ago

Thanks for the report. I guess I added it so that inspect can use __signature__ to give signature of the mock object. Can you reproduce it with normal objects or is this something you see with QObject only? Do you have the same error in Python 3?

tirkarthi commented 5 years ago

Sorry, I read it again now that it's a non-writable property. One solution would be to have catch the AttributeError. I think this would be an upstream bug too.

cjw296 commented 5 years ago

@tirkarthi - would you prefer @steog88 to re-log this on https://bugs.python.org/ or would you be happy to do so and get a PR for a workaround/fix going there?

I have to admit, I'm confused as to why setting an attribute on a Mock-ish class is failing like this. That said, _check_signature has a pretty bad code smell now: it's called _check but sets stuff, it takes a func arg that is often not a func. @tirkarthi - would you be up for sorting that out upstream too?

steog88 commented 5 years ago

@tirkarthi no problems with the builtin unittest.mock in python3. It also works if I define

class SampleClass():
    def __init__(self, txt):
        self.txt = txt

    @property
    def __signature__(self):
        return self.txt

Curious thing, even if SampleClass does not inherit from QObject like here, the test stops working if I import QObject anywhere in the code. I have no idea why.

tirkarthi commented 5 years ago

I am also confused but this is only reproducible with pyside2. type(mock) should actually be setting it to the mocked object but there is some code in NonCallableMock where __new__ that I wish to understand to see this has some metaclass

Opened upstream report : https://bugs.python.org/issue36848 Another report in pytest-qt : https://github.com/pytest-dev/pytest-qt/issues/258

tirkarthi commented 5 years ago

Yes, I don't know how to write a pure python reproducer without pyside2. I just installed pyside2 and grepped for __signature__. There is some code comment that it triggers signature initialization. I am not sure too :( I will update if I find anything.

$ rg -B 1 '__signature__' foo-venv/
foo-venv/lib/python3.8/site-packages/PySide2/__init__.py
22-    #   Trigger signature initialization.
23:    type.__signature__

foo-venv/lib/python3.8/site-packages/shiboken2/__init__.py
29-# Trigger signature initialization.
30:type.__signature__

foo-venv/lib/python3.8/site-packages/shiboken2/files.dir/shibokensupport/signature/lib/enum_sig.py
117-        ret = self.result_type()
118:        signature = getattr(func, '__signature__', None)

foo-venv/lib/python3.8/site-packages/shiboken2/files.dir/shibokensupport/signature/layout.py
46-differently formatted versions of signatures. The default
47:layout is known from the "__signature__" attribute.
cjw296 commented 5 years ago

Closing this in favour of where in appears to need to be fixed: https://bugreports.qt.io/browse/PYSIDE-1004

tirkarthi commented 5 years ago

@steog88 Just to add to this you can pass autospec=False as a workaround to mock the class where this signature setting code is not executed and you can use the mocked class but with the tradeoff that the calls to mock are signature validated.

Thanks for the report.

steog88 commented 5 years ago

Thanks, I will do as suggested then.