Skip to content

gh-153888: autospec doesn't work with lazy-imported symbols - #153977

Closed
palakkhinvasara wants to merge 11 commits into
python:mainfrom
palakkhinvasara:fix-unittest-lazy-import-patch
Closed

palakkhinvasara wants to merge 11 commits into
python:mainfrom
palakkhinvasara:fix-unittest-lazy-import-patch

Conversation

@palakkhinvasara

Copy link
Copy Markdown
Contributor

#153888 Fix unittest.mock.patch with lazy imports and autospec

unittest.mock.patch() currently reads unresolved lazy-import bindings directly from target.__dict__.

When autospec=True (and similarly spec/spec_set) is used with a lazy-imported object, patch() receives the LazyImportType proxy instead of the actual imported object. This causes create_autospec() to create a NonCallableMagicMock for callable objects.

For example:

lazy from json import dumps

with patch("__main__.dumps", autospec=True) as mock_dumps:
    mock_dumps({})

@palakkhinvasara palakkhinvasara changed the title issue #153888: autospec doesn't work with lazy-imported symbols issue gh-153888: autospec doesn't work with lazy-imported symbols Jul 18, 2026
@palakkhinvasara

Copy link
Copy Markdown
Contributor Author

hello @cjw296 , can you pls review this pr?

Thanks & Regards
Palak

builtin_string = 'builtins'

PTModule = sys.modules[__name__]
lazy from json import dumps as lazy_dumps # noqa: F401

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this import here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corrected

Comment thread Lib/unittest/mock.py Outdated
original = getattr(target, name, DEFAULT)
else:
local = True
local = True

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation looks off.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corrected

@palakkhinvasara
palakkhinvasara force-pushed the fix-unittest-lazy-import-patch branch from 0bcfebb to 59a848a Compare July 20, 2026 12:55
@StanFromIreland StanFromIreland changed the title issue gh-153888: autospec doesn't work with lazy-imported symbols gh-153888: autospec doesn't work with lazy-imported symbols Jul 21, 2026
Comment thread Lib/unittest/mock.py
Comment on lines +1474 to +1475
if isinstance(original, types.LazyImportType):
original = original.resolve()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation looks off here as well

@@ -0,0 +1,2 @@
Fix :func:`unittest.mock.patch` with ``autospec``, ``spec``, or ``spec_set``

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The news entry mentions that your change fixes calls using autospec, spec, and spec_set, but only autospec is actually tested. Would you mind providing additional tests for the two other use cases as well?

brittanyrey added a commit to brittanyrey/cpython that referenced this pull request Sep 18, 2026
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#153977, open since July 2026:

- 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.

Reported as pythongh-153888.
encukou pushed a commit that referenced this pull request Sep 29, 2026
…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 #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.
@encukou

encukou commented Oct 2, 2026

Copy link
Copy Markdown
Member

Fixed in #157768.

@encukou encukou closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants