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

Aperta
#14,339 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
python
Ambito
testing

Direzione di ricerca

Esamina gli overload di unittest.mock.patch e patch.object mostrati nell'issue, quindi trova i relativi stub di typeshed e i test associati. Controlla i tipi inferiti per il patching predefinito, new, new_callable e i casi con new_callable non chiamabile; il lavoro è completo quando entrambe le API accettano solo factory chiamabili e inferiscono correttamente il tipo creato.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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

Lingua principale
Python
Stelle
5.1k
Fork
2.1k
Merge medio
2g 1h
PR unite (30g)
115

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di python/typeshed

Tutte le issue di python/typeshed

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.