8000 Fix mock patch.dict stub and add mock stub by rbtcollins · Pull Request #2173 · python/typeshed · GitHub
[go: up one dir, main page]

Skip to content

Fix mock patch.dict stub and add mock stub #2173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 1, 2018

Conversation

rbtcollins
Copy link
Member
@rbtcollins rbtcollins commented May 29, 2018

The dict stub was referring to an instance, not the type, leading to
call being considered when using as a decorator, rather than
init.

mock is a backport of the stdlib module and should be defined the same.

@@ -1,4 +1,5 @@
# Stubs for unittest.mock
# Please sync with third_party/2and3/mock.pyi when changing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put this in typeshed/tests/check_consistent.py (you'll have to make sure the files are byte-for-byte identical).

@rbtcollins rbtcollins force-pushed the master branch 4 times, most recently from d7c5ff3 to a0ab2e4 Compare May 30, 2018 01:14
The dict stub was referring to an instance, not the type, leading to
__call__ being considered when using as a decorator, rather than
__init__.

mock is a backport of the stdlib module and should be defined the same.
@rbtcollins
Copy link
Member Author

@JelleZijlstra is anything else needed from me for this to merge?

@JelleZijlstra JelleZijlstra merged commit 98badb6 into python:master Jun 1, 2018
@JelleZijlstra
Copy link
Member

Thanks for fixing this.

@rowillia
Copy link
Contributor
rowillia commented Jun 5, 2018

@rbtcollins @JelleZijlstra This just broke a bunch of our type checks:

from mock import MagicMock, call, mock_open, patch

Results in:

{file_name}:5: error: Module 'mock' has no attribute 'MagicMock'
{file_name}:5: error: Module 'mock' has no attribute 'call'
{file_name}:5: error: Module 'mock' has no attribute 'mock_open'
{file_name}:5: error: Module 'mock' has no attribute 'patch'

import sys
from typing import Any, Optional, Type

8000
if sys.version_info >= (3, 3):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be the problem - for Python 2 this is left as an empty module.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops you're right. We should make it so that everything gets unconditionally exported. (We don't care about 3.2 anyway.)

Can you make a PR making that change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitted #2201

@gvanrossum
Copy link
Member

@rbtcollins @JelleZijlstra @rowillia
I am wondering if having Mock being any more than an alias for Any was a good idea after all. In our Dropbox code base we have a lot of code where there's a library module that defines a function accepting (say) an Account instance, and a test module that passes a Mock instance. People like to annotate their tests, so now mypy complains that the Mock is not an Account. I ended up replacing Mock() with cast(Any, Mock()) in a lot of places, which didn't feel like a great use of my time. Would it make sense to roll this back?

@JelleZijlstra
Copy link
Member

Hm, that is indeed unfortunate. But doesn't Mock still inherit from Any in this PR?

@gvanrossum
Copy link
Member

But doesn't Mock still inherit from Any in this PR?

It does. And in a toy example it seems to be acceptable; I get no errors here:

class M(Any): pass
class A: pass
def f(a: A): pass
f(M())

I'll have to look over our code more carefully to see what scenario triggered those errors.

@gvanrossum
Copy link
Member

Oh, it's the other way around. Example:

class M(Any): pass # type: ignore
class A: pass
def foo(a: M): pass
foo(A())  # error: Argument 1 to "foo" has incompatible type "A"; expected "M"

This pattern happens a lot in our code (though typically it's a variable initialized with a Mock and later assigned something non-Mock).

@JelleZijlstra
Copy link
Member

So you have functions that are annotated as taking Mock objects as arguments? That seems a bit weird.

If it's common though, I'd be OK with making Mock just be an alias for Any.

@gvanrossum
Copy link
Member

I did find functions annotated as taking Mocks... I am not complaining about those, I changed them to Any. More common was instance variables initialized from a Mock and later assigned from a non-Mock (clearly an instance the class that the Mock was mocking). The initialization causes an implicit declaration. Sort of like

class Account:
    ...
def get_account(username):
    # type: (str) -> Account
    ...
class C:
    def __init__(self):
        # type: () -> None
        self.account = Mock()  # implies type: Mock
    def switch_account(self, username):
        # type: (str) -> None
        self.account = get_account(username)  # error here

@ilevkivskyi
Copy link
Member

Could the solution be to just give explicit annotation, like

        self.account: Account = Mock()

Also, the current behavior of classes with an Any base seems a bit ad-hoc. Is it specified in PEP 484? I would expect classes with an Any base behave as a bit more precise version of Any. They would be both subclass and superclass of another class, but known attributes will have precise types (unlike when accessed on a bare Any).

@JelleZijlstra
Copy link
Member

Oh thanks, that makes more sense!

I think even in this case though, mypy found a real type problem. The code works correctly if you do self.account = Mock() # type: Account, and that will then help mypy catch other errors elsewhere.

PiDelport added a commit to PiDelport/django-environ-base that referenced this pull request Jul 5, 2018
yedpodtrzitko pushed a commit to yedpodtrzitko/typeshed that referenced this pull request Jan 23, 2019
The dict stub was referring to an instance, not the type, leading to
__call__ being considered when using as a decorator, rather than
__init__.

mock is a backport of the stdlib module and should be defined the same.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants
0