[Python-checkins] gh-102978: Fix mock.patch function signatures for class and staticmethod decorators (#103228)
cjw296
webhook-mailer at python.org
Thu Apr 13 03:38:31 EDT 2023
https://github.com/python/cpython/commit/59e0de4903c02e72b329e505fddf1ad9794928bc
commit: 59e0de4903c02e72b329e505fddf1ad9794928bc
branch: main
author: Tomas R <tomas.roun8 at gmail.com>
committer: cjw296 <chris at withers.org>
date: 2023-04-13T08:37:57+01:00
summary:
gh-102978: Fix mock.patch function signatures for class and staticmethod decorators (#103228)
Fixes unittest.mock.patch not enforcing function signatures for methods
decorated with @classmethod or @staticmethod when patch is called with
autospec=True.
files:
A Misc/NEWS.d/next/Library/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst
M Lib/test/test_unittest/testmock/testhelpers.py
M Lib/test/test_unittest/testmock/testpatch.py
M Lib/unittest/mock.py
M Misc/ACKS
diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py
index dc4d004cda8a..74785a83757a 100644
--- a/Lib/test/test_unittest/testmock/testhelpers.py
+++ b/Lib/test/test_unittest/testmock/testhelpers.py
@@ -952,6 +952,24 @@ def __getattr__(self, attribute):
self.assertFalse(hasattr(autospec, '__name__'))
+ def test_autospec_signature_staticmethod(self):
+ class Foo:
+ @staticmethod
+ def static_method(a, b=10, *, c): pass
+
+ mock = create_autospec(Foo.__dict__['static_method'])
+ self.assertEqual(inspect.signature(Foo.static_method), inspect.signature(mock))
+
+
+ def test_autospec_signature_classmethod(self):
+ class Foo:
+ @classmethod
+ def class_method(cls, a, b=10, *, c): pass
+
+ mock = create_autospec(Foo.__dict__['class_method'])
+ self.assertEqual(inspect.signature(Foo.class_method), inspect.signature(mock))
+
+
def test_spec_inspect_signature(self):
def myfunc(x, y): pass
diff --git a/Lib/test/test_unittest/testmock/testpatch.py b/Lib/test/test_unittest/testmock/testpatch.py
index 8ceb5d973e1a..833d7da1f31a 100644
--- a/Lib/test/test_unittest/testmock/testpatch.py
+++ b/Lib/test/test_unittest/testmock/testpatch.py
@@ -996,6 +996,36 @@ def test_autospec_classmethod(self):
method.assert_called_once_with()
+ def test_autospec_staticmethod_signature(self):
+ # Patched methods which are decorated with @staticmethod should have the same signature
+ class Foo:
+ @staticmethod
+ def static_method(a, b=10, *, c): pass
+
+ Foo.static_method(1, 2, c=3)
+
+ with patch.object(Foo, 'static_method', autospec=True) as method:
+ method(1, 2, c=3)
+ self.assertRaises(TypeError, method)
+ self.assertRaises(TypeError, method, 1)
+ self.assertRaises(TypeError, method, 1, 2, 3, c=4)
+
+
+ def test_autospec_classmethod_signature(self):
+ # Patched methods which are decorated with @classmethod should have the same signature
+ class Foo:
+ @classmethod
+ def class_method(cls, a, b=10, *, c): pass
+
+ Foo.class_method(1, 2, c=3)
+
+ with patch.object(Foo, 'class_method', autospec=True) as method:
+ method(1, 2, c=3)
+ self.assertRaises(TypeError, method)
+ self.assertRaises(TypeError, method, 1)
+ self.assertRaises(TypeError, method, 1, 2, 3, c=4)
+
+
def test_autospec_with_new(self):
patcher = patch('%s.function' % __name__, new=3, autospec=True)
self.assertRaises(TypeError, patcher.start)
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 0f93cb53c3d5..7ca085760650 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -98,6 +98,12 @@ def _get_signature_object(func, as_instance, eat_self):
func = func.__init__
# Skip the `self` argument in __init__
eat_self = True
+ elif isinstance(func, (classmethod, staticmethod)):
+ if isinstance(func, classmethod):
+ # Skip the `cls` argument of a class method
+ eat_self = True
+ # Use the original decorated method to extract the correct function signature
+ func = func.__func__
elif not isinstance(func, FunctionTypes):
# If we really want to model an instance of the passed type,
# __call__ should be looked up, not __init__.
diff --git a/Misc/ACKS b/Misc/ACKS
index 1e94d33a665e..d0ff4e8aeb5c 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1550,6 +1550,7 @@ Hugo van Rossum
Saskia van Rossum
Robin Roth
Clement Rouault
+Tomas Roun
Donald Wallace Rouse II
Liam Routt
Todd Rovito
diff --git a/Misc/NEWS.d/next/Library/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst b/Misc/NEWS.d/next/Library/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst
new file mode 100644
index 000000000000..df63af10a385
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst
@@ -0,0 +1,3 @@
+Fixes :func:`unittest.mock.patch` not enforcing function signatures for methods
+decorated with ``@classmethod`` or ``@staticmethod`` when patch is called with
+``autospec=True``.
More information about the Python-checkins
mailing list