Skip to content

gh-153888: Resolve lazy imports before using them as a mock spec - #157768

Merged
encukou merged 1 commit into
python:mainfrom
brittanyrey:b-mock-patch-lazy-import
Sep 29, 2026
Merged

encukou merged 1 commit into
python:mainfrom
brittanyrey:b-mock-patch-lazy-import

Conversation

@brittanyrey

@brittanyrey brittanyrey commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

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, open since July 2026 + some additional tests and one extra check.

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

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

Copy link
Copy Markdown
Contributor Author

cc @encukou

@encukou

encukou commented Sep 28, 2026

Copy link
Copy Markdown
Member

This looks good to me -- though I'm not an expert on lazy modules or mock.

It is a behaviour change though. Is it possible that someone would mock out a lazy import because it's not importable in the test scenario?

@hugovk, does this fit in 3.15?

@hugovk

hugovk commented Sep 28, 2026

Copy link
Copy Markdown
Member

Yes, this can go into 3.15.0.

@encukou
encukou merged commit cac0167 into python:main Sep 29, 2026
54 checks passed
@encukou encukou added awaiting review needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Sep 29, 2026
@miss-islington-app

Copy link
Copy Markdown

Thanks @brittanyrey for the PR, and @encukou for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15.
🐍🍒⛏🤖

@bedevere-app

bedevere-app Bot commented Sep 29, 2026

Copy link
Copy Markdown

GH-158396 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label Sep 29, 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.

3 participants