https://github.com/python/cpython/commit/cac016736e5742395fff20b73692876f05ae312c
commit: cac016736e5742395fff20b73692876f05ae312c
branch: main
author: Brittany Reynoso <[email protected]>
committer: encukou <[email protected]>
date: 2026-09-29T11:30:57+02:00
summary:
gh-153888: Resolve lazy imports before using them as a mock spec (GH-157768)
Resolve lazy imports before using them as a mock spec
unittest.mock.patch() reads the attribute it replaces out of
target.__dict__, which for an unresolved lazy import holds a
types.LazyImportType placeholder. spec=True, spec_set=True and
autospec=True then specced the placeholder, so patching a function
produced a NonCallableMagicMock.
Reimplementation of python/cpython#153977:
- Resolve in _patch.__enter__(), and only when the spec is taken from
the original, rather than unconditionally in get_original().
Resolving on every patch() breaks patch("mod.dep", new=...) for a
lazy import of a module that is not installed.
- Test spec and spec_set as well as autospec, as asked for in review.
- Build the lazy binding in the test instead of importing json lazily
at module level: "lazy from" binds eagerly when the module is already
imported, so that test passed without the fix too.
files:
A Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst
M Lib/test/test_unittest/testmock/testpatch.py
M Lib/unittest/mock.py
diff --git a/Lib/test/test_unittest/testmock/testpatch.py
b/Lib/test/test_unittest/testmock/testpatch.py
index bd85fdcfc472a61..bd2a7fb6d6731d7 100644
--- a/Lib/test/test_unittest/testmock/testpatch.py
+++ b/Lib/test/test_unittest/testmock/testpatch.py
@@ -5,6 +5,7 @@
import os
import sys
from collections import OrderedDict
+from types import LazyImportType, ModuleType
import unittest
import test
@@ -2101,5 +2102,39 @@ def test(_):
test()
+class PatchLazyImportTest(unittest.TestCase):
+
+ def lazy_module(self):
+ # `lazy from` binds eagerly when the module it imports from is already
+ # imported, so publish the target only once the statement has run.
+ self.enterContext(uncache('lazy_patch_user', 'lazy_patch_target'))
+ user = ModuleType('lazy_patch_user')
+ exec('lazy from lazy_patch_target import function', user.__dict__)
+ sys.modules['lazy_patch_user'] = user
+ target = ModuleType('lazy_patch_target')
+ exec('def function(arg): pass', target.__dict__)
+ sys.modules['lazy_patch_target'] = target
+
+ self.assertIsInstance(user.__dict__['function'], LazyImportType)
+ return user
+
+ def test_autospec(self):
+ module = self.lazy_module()
+ with patch.object(module, 'function', autospec=True) as mock_function:
+ mock_function('arg')
+ with self.assertRaises(TypeError):
+ mock_function('arg', 'extra')
+
+ def test_spec(self):
+ module = self.lazy_module()
+ with patch.object(module, 'function', spec=True) as mock_function:
+ mock_function('arg')
+
+ def test_spec_set(self):
+ module = self.lazy_module()
+ with patch.object(module, 'function', spec_set=True) as mock_function:
+ mock_function('arg')
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 1effc70b5323139..d3d47ff81e85969 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -35,7 +35,7 @@
import threading
from annotationlib import Format
from dataclasses import fields, is_dataclass
-from types import CodeType, ModuleType, MethodType
+from types import CodeType, LazyImportType, ModuleType, MethodType
from unittest.util import safe_repr
from functools import wraps, partial
from threading import RLock
@@ -1507,6 +1507,10 @@ def __enter__(self):
original, local = self.get_original()
+ if (isinstance(original, LazyImportType)
+ and (spec is True or spec_set is True or autospec is True)):
+ original = original.resolve()
+
if new is DEFAULT and autospec is None:
inherit = False
if spec is True:
diff --git
a/Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst
b/Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst
new file mode 100644
index 000000000000000..278c9560cec216e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst
@@ -0,0 +1,2 @@
+Fix :func:`unittest.mock.patch` with ``autospec``, ``spec``, or ``spec_set``
+when used with lazy-imported objects.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]