python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.3k stars 2.8k forks source link

Confusing message for indirect import from a stub #4091

Open Dakkaron opened 7 years ago

Dakkaron commented 7 years ago

Indirect imports in stubs don't seem to work. I tested the following case against mypy 0.520, where everything worked (no error detected), mypy 0.530 (where the error occurred) and the current git version, which also had the same error as mypy 0.530.

I attached a zip file with the test case: testcase.zip

The mypy call line is just mypy --config-file=mypy.ini -p testcase.

The error I get is the following:

teststubs/a/stubA.pyi:1: error: Module 'b' has no attribute 'TestClass'
testcase/__init__.py:1: error: Module 'a.stubA' has no attribute 'TestClass'

The main problem is the first line. Module 'b' imports 'TestClass' from b.stubB in b.__init__.pyi, but mypy for some reason does not know that.

ilevkivskyi commented 7 years ago

This is not a bug. In stub files, the names that are imported for re-export (i.e. actually imported in runtime .py file, not just to annotate functions and variables in stub) must use the import name as name pattern. Please read the corresponding section https://www.python.org/dev/peps/pep-0484/#stub-files

This was not caught by previous versions of mypy, but now it conforms to PEP 484 in this aspect. If you are curious about the motivation, then consider this:

# file lib.pyi
from typing import List
from lib.inner import SomeClass

def func(arg: List[SomeClass]) -> None: ...

# file main.py
from lib import List  # this will obviously fail at runtime, but was not caught by mypy previously
from lib import SomeClass  # this will also fail if SomeClass is not actually imported in mod.py
                           # in this case the stub should use from lib.inner import SomeClass as SomeClass
Dakkaron commented 7 years ago

I understand the reasoning and the new way of doing things is good. But I think the error message is still misleading. Especially when migrating from mypy 0.520 or older to mypy 0.530 it can be very confusing that stuff that worked fine before does not work anymore. There should be a special error message to handle this case.

JukkaL commented 7 years ago

Fair enough, the error message could be better. Reopening (with a new title).

Dakkaron commented 7 years ago

By the way, how to deal with stuff like that when using *-imports?

from X import *

I guess, it is not possible to write from X import * as *. Is it now mandatory to convert all that into explicit imports? I know, *-imports are often not seen as good practice, but a lot of libraries still use them.

JukkaL commented 7 years ago

from x import * is implicitly exported in a stub, so no * as * is needed. Actually, you should only use from x import * in a stub if you want to export everything in x; otherwise you must use explicit imports.

Herst commented 7 years ago

From https://www.python.org/dev/peps/pep-0484/#stub-files:

Additional notes on stub files:

  • Modules and variables imported into the stub are not considered exported from the stub unless the import uses the import ... as ... form or the equivalent from ... import ... as ... form.
  • *However, as an exception to the previous bullet, all objects imported into a stub using `from ... import ` are considered exported. (This makes it easier to re-export all objects from a given module that may vary by Python version.)**

(Highlight by me)

Dakkaron commented 7 years ago

That's good to know, thank you!