python / python/typeshed

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

Đang mở
#14,339 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
Python
Star
5.1k
Fork
2.1k
Merge trung bình
1 ngày 19 giờ
Pull request đã merge (30 ngày)
82

Mô tả

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 :)

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Xem xét các overload của unittest.mock.patch và patch.object được nêu trong issue, sau đó tìm các stub typeshed và các bài kiểm thử liên quan của chúng. Kiểm tra các kiểu được suy luận cho việc patch mặc định, new, new_callable và các trường hợp new_callable không thể gọi; hoàn tất khi cả hai API chỉ chấp nhận các factory có thể gọi và suy luận đúng kiểu được tạo ra.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
testing
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
48/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.