`unittest.mock.patch` and `unittest.mock.patch.object` are wrongly typed when new_callable is provided

未關閉
#14,339 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
3/5
預估耗時
1-2 天
新手友好度
48/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
停滯
技術堆疊
python
領域
testing

研究方向

檢查 issue 中顯示的 unittest.mock.patch 和 patch.object 多載,然後找到它們的 typeshed stub 和相關測試。檢查預設 patching、new、new_callable,以及不可呼叫的 new_callable 情況下推斷出的型別;完成的標準是兩個 API 都只接受可呼叫的 factory,並正確推斷所建立物件的型別。

由索引模型根據 Issue 內容生成。

描述

Steps to reproduce

from unittest.mock import patch


class NewPrinter:
    pass


with patch("pprint.PrettyPrinter", new_callable=lambda: NewPrinter) as patched_printer:
    print(patched_printer)  # <class '__main__.NewPrinter'>
    reveal_type(patched_printer)  # "Union[unittest.mock.MagicMock, unittest.mock.AsyncMock]"

Same deal with unittest.mock.patch.object

from unittest.mock import patch
import pprint

class NewPrinter:
    ...


with patch.object(pprint, "PrettyPrinter", new_callable=lambda: NewPrinter) as patched_printer:
    print(patched_printer)  # <class '__main__.NewPrinter'>
    reveal_type(patched_printer)  # "Union[unittest.mock.MagicMock, unittest.mock.AsyncMock]"



Suggested fix

We discuss unittest.mock.patch in the following, but the same principles apply to unittest.mock.patch.object too

Current signatures:

    @overload
    def __call__(
        self,
        target: str,
        new: _T,
        spec: Any | None = ...,
        create: bool = ...,
        spec_set: Any | None = ...,
        autospec: Any | None = ...,
        new_callable: Any | None = ...,
        **kwargs: Any,
    ) -> _patch[_T]: ...
    @overload
    def __call__(
        self,
        target: str,
        *,
        spec: Any | None = ...,
        create: bool = ...,
        spec_set: Any | None = ...,
        autospec: Any | None = ...,
        new_callable: Any | None = ...,
        **kwargs: Any,
    ) -> _patch_default_new: ...

There are 2 problems here:

  • new_callable shouldn't be Any | None in the first place, it should only accept Callable| None, as the it will raise if the provided value is not a callable
    with patch("pprint.PrettyPrinter", new_callable=1) as patched_printer:  # TypeError: 'int' object is not callable
    
  • Need to differentiate the case where new_callable is provided and left as default None
    • When new_callable: Callable[..., T], returns _patch[_T]
    • When new_callable: None = ..., returns _patch_default_new

Seems like this would do the job:

    @overload
    def __call__(
        self,
        target: str,
        new: _T,
        spec: Any | None = ...,
        create: bool = ...,
        spec_set: Any | None = ...,
        autospec: Any | None = ...,
        new_callable: Callable[..., Any] | None = ...,
        **kwargs: Any,
    ) -> _patch[_T]: ...
    @overload
    def __call__(
        self,
        target: str,
        *,
        spec: Any | None = ...,
        create: bool = ...,
        spec_set: Any | None = ...,
        autospec: Any | None = ...,
        new_callable: Callable[..., _T],
        **kwargs: Any,
    ) -> _patch[_T]
    @overload
    def __call__(
        self,
        target: str,
        *,
        spec: Any | None = ...,
        create: bool = ...,
        spec_set: Any | None = ...,
        autospec: Any | None = ...,
        new_callable: None = ...,
        **kwargs: Any,
    ) -> _patch_default_new: ...

Testing results

from unittest.mock import patch


class NewPrinter:
    pass


with patch("pprint.PrettyPrinter") as patched_printer:
    print(patched_printer)  # <MagicMock name='PrettyPrinter' id='125908774402896'>
    reveal_type(patched_printer)  # MagicMock | AsyncMock


with patch("pprint.PrettyPrinter", new=NewPrinter) as patched_printer:
    print(patched_printer)  # <class '__main__.NewPrinter'>
    reveal_type(patched_printer)  # type[NewPrinter]


with patch("pprint.PrettyPrinter", new_callable=lambda: NewPrinter) as patched_printer:
    print(patched_printer)  # <class '__main__.NewPrinter'>
    reveal_type(patched_printer)  # type[NewPrinter]


# No overload variant matches for non-callable type
with patch("pprint.PrettyPrinter", new_callable="non-callable") as patched_printer:
    ...

Haven't tried unittest.mock.patch.object yet, but probably same thing


Would be great to see what the maintainers think.

I'll dig deeper and try to open a PR, maybe this weekend, will be my first PR in typeshed :)

主要語言
Python
星號
5.1k
分支
2.1k
平均合併
2 天 7 分鐘
30 天內合併 PR
116

環境準備

  • 沒有 Dockerfile 或 Docker Compose 檔案
  • 沒有 Pull Request 範本
  • 閱讀貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

python/typeshed 的其他 Issue

查看 python/typeshed 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。