From 1c5807ddff8e3c299d6ce264da62e6cb5123f390 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 16 Jul 2025 15:47:35 +0200 Subject: [PATCH 1/4] Improve unittest.mock stubs * Mark `_patcher` and `_patch_pass_arg` as `@type_check_only`. * Add `patch()` default arguments. * Forbid certain argument combinations for `patch()`. * Reorder `patch()` overloads. * Add `unsafe` argument. * Add overloads for `path.multiple()`. --- stdlib/unittest/mock.pyi | 124 +++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 37 deletions(-) diff --git a/stdlib/unittest/mock.pyi b/stdlib/unittest/mock.pyi index ec5fced32ea4..bee036a000bb 100644 --- a/stdlib/unittest/mock.pyi +++ b/stdlib/unittest/mock.pyi @@ -3,7 +3,7 @@ from _typeshed import MaybeNone from collections.abc import Awaitable, Callable, Coroutine, Iterable, Mapping, Sequence from contextlib import _GeneratorContextManager from types import TracebackType -from typing import Any, ClassVar, Final, Generic, Literal, TypeVar, overload +from typing import Any, ClassVar, Final, Generic, Literal, TypeVar, overload, type_check_only from typing_extensions import ParamSpec, Self, TypeAlias _T = TypeVar("_T") @@ -262,6 +262,7 @@ class _patch(Generic[_T]): # This class does not exist at runtime, it's a hack to make this work: # @patch("foo") # def bar(..., mock: MagicMock) -> None: ... +@type_check_only class _patch_pass_arg(_patch[_T]): @overload def __call__(self, func: _TT) -> _TT: ... @@ -288,6 +289,7 @@ class _patch_dict: # This class does not exist at runtime, it's a hack to add methods to the # patch() function. +@type_check_only class _patcher: TEST_PREFIX: str dict: type[_patch_dict] @@ -299,23 +301,28 @@ class _patcher: self, target: str, new: _T, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., - new_callable: Callable[..., Any] | None = ..., - **kwargs: Any, + spec: Literal[False] | None = None, + create: bool = False, + spec_set: Literal[False] | None = None, + autospec: Literal[False] | None = None, + new_callable: None = None, + *, + unsafe: bool = False, ) -> _patch[_T]: ... @overload def __call__( self, target: str, *, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., + # If not False or None, this is passed to new_callable + spec: Any | Literal[False] | None = None, + create: bool = False, + # If not False or None, this is passed to new_callable + spec_set: Any | Literal[False] | None = None, + autospec: Literal[False] | None = None, new_callable: Callable[..., _T], + unsafe: bool = False, + # kwargs are passed to new_callable **kwargs: Any, ) -> _patch_pass_arg[_T]: ... @overload @@ -323,25 +330,31 @@ class _patcher: self, target: str, *, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., - new_callable: None = ..., + spec: Any | Literal[False] | None = None, + create: bool = False, + spec_set: Any | bool | None = None, + autospec: Literal[False] | None = None, + new_callable: None = None, + unsafe: bool = False, + # kwargs are passed to the MagicMock/AsyncMock constructor **kwargs: Any, ) -> _patch_pass_arg[MagicMock | AsyncMock]: ... + # This overload also covers the case, where new==DEFAULT. In this case, the return type is _patch[Any]. + # Ideally we'd be able to add an overload for it so that the return type is _patch[MagicMock], + # but that's impossible with the current type system. @overload @staticmethod def object( target: Any, attribute: str, new: _T, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., - new_callable: Callable[..., Any] | None = ..., - **kwargs: Any, + spec: Literal[False] | None = None, + create: bool = False, + spec_set: Literal[False] | None = None, + autospec: Literal[False] | None = None, + new_callable: None = None, + *, + unsafe: bool = False, ) -> _patch[_T]: ... @overload @staticmethod @@ -349,11 +362,15 @@ class _patcher: target: Any, attribute: str, *, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., + # If not False or None, this is passed to new_callable + spec: Any | Literal[False] | None = None, + create: bool = False, + # If not False or None, this is passed to new_callable + spec_set: Any | Literal[False] | None = None, + autospec: Literal[False] | None = None, new_callable: Callable[..., _T], + unsafe: bool = False, + # kwargs are passed to new_callable **kwargs: Any, ) -> _patch_pass_arg[_T]: ... @overload @@ -362,21 +379,54 @@ class _patcher: target: Any, attribute: str, *, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., - new_callable: None = ..., + spec: Any | None = None, + create: bool = False, + spec_set: Any | None = None, + autospec: Literal[False] | None = None, + new_callable: None = None, + unsafe: bool = False, + # kwargs are passed to the MagicMock/AsyncMock constructor **kwargs: Any, ) -> _patch_pass_arg[MagicMock | AsyncMock]: ... + @overload @staticmethod def multiple( - target: Any, - spec: Any | None = ..., - create: bool = ..., - spec_set: Any | None = ..., - autospec: Any | None = ..., - new_callable: Any | None = ..., + target: Any | str, + # If not False or None, this is passed to new_callable + spec: Any | Literal[False] | None = None, + create: bool = False, + # If not False or None, this is passed to new_callable + spec_set: Any | Literal[False] | None = None, + autospec: Literal[False] | None = None, + *, + new_callable: Callable[..., _T], + # The kwargs must be DEFAULT + **kwargs: Any, + ) -> _patch_pass_arg[_T]: ... + @overload + @staticmethod + def multiple( + target: Any | str, + # If not False or None, this is passed to new_callable + spec: Any | Literal[False] | None, + create: bool, + # If not False or None, this is passed to new_callable + spec_set: Any | Literal[False] | None, + autospec: Literal[False] | None, + new_callable: Callable[..., _T], + # The kwargs must be DEFAULT + **kwargs: Any, + ) -> _patch_pass_arg[_T]: ... + @overload + @staticmethod + def multiple( + target: Any | str, + spec: Any | bool | None = None, + create: bool = False, + spec_set: Any | bool | None = None, + autospec: Any | bool | None = None, + new_callable: None = None, + # The kwargs are the mock objects or DEFAULT **kwargs: Any, ) -> _patch[Any]: ... @staticmethod From e5f08369547dc47e14d54b6eef1afc86191345fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:49:29 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/unittest/mock.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/unittest/mock.pyi b/stdlib/unittest/mock.pyi index bee036a000bb..dcf3718a91e5 100644 --- a/stdlib/unittest/mock.pyi +++ b/stdlib/unittest/mock.pyi @@ -393,10 +393,10 @@ class _patcher: def multiple( target: Any | str, # If not False or None, this is passed to new_callable - spec: Any | Literal[False] | None = None, + spec: Any | Literal[False] | None = None, create: bool = False, # If not False or None, this is passed to new_callable - spec_set: Any | Literal[False] | None = None, + spec_set: Any | Literal[False] | None = None, autospec: Literal[False] | None = None, *, new_callable: Callable[..., _T], @@ -411,7 +411,7 @@ class _patcher: spec: Any | Literal[False] | None, create: bool, # If not False or None, this is passed to new_callable - spec_set: Any | Literal[False] | None, + spec_set: Any | Literal[False] | None, autospec: Literal[False] | None, new_callable: Callable[..., _T], # The kwargs must be DEFAULT From f5a1a754ff47fb511cb526cb5331f045f7d354a0 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 16 Jul 2025 15:59:24 +0200 Subject: [PATCH 3/4] Ignore a mypy build error --- stdlib/unittest/mock.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/unittest/mock.pyi b/stdlib/unittest/mock.pyi index dcf3718a91e5..19a788e741ed 100644 --- a/stdlib/unittest/mock.pyi +++ b/stdlib/unittest/mock.pyi @@ -297,7 +297,7 @@ class _patcher: # Ideally we'd be able to add an overload for it so that the return type is _patch[MagicMock], # but that's impossible with the current type system. @overload - def __call__( + def __call__( # type: ignore[overload-overlap] self, target: str, new: _T, From 454dfa70bab46c5dbd2e619b0a19d55bb13543dc Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Wed, 16 Jul 2025 16:25:59 +0200 Subject: [PATCH 4/4] Fix --- stdlib/unittest/mock.pyi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stdlib/unittest/mock.pyi b/stdlib/unittest/mock.pyi index 19a788e741ed..6a6cba887bfe 100644 --- a/stdlib/unittest/mock.pyi +++ b/stdlib/unittest/mock.pyi @@ -330,10 +330,10 @@ class _patcher: self, target: str, *, - spec: Any | Literal[False] | None = None, + spec: Any | bool | None = None, create: bool = False, spec_set: Any | bool | None = None, - autospec: Literal[False] | None = None, + autospec: Any | bool | None = None, new_callable: None = None, unsafe: bool = False, # kwargs are passed to the MagicMock/AsyncMock constructor @@ -379,10 +379,10 @@ class _patcher: target: Any, attribute: str, *, - spec: Any | None = None, + spec: Any | bool | None = None, create: bool = False, - spec_set: Any | None = None, - autospec: Literal[False] | None = None, + spec_set: Any | bool | None = None, + autospec: Any | bool | None = None, new_callable: None = None, unsafe: bool = False, # kwargs are passed to the MagicMock/AsyncMock constructor