Repository: python/mypy

Issue:
Installed stubs not always taking precedence in PEP 561 implementation
I installed a package with inline type annotations using `python setup.py install` and installed the corresponding stubs package using `python setup.py install --user`. Mypy picked up the inline type annotations instead of the stubs, even though I'd expect that a stub package takes precedence. I suspect that this is caused by the package and the stubs being in different directories, and that the order in which we search through the directories may affect which types will be used by mypy.

@ethanhs Can you take a look at this?



Discussion:
AFAIK this was intentional -- the search finds the first directory that has
either a .py or a .pyi file, and then prefers .pyi over .py -- but it
doesn't continue to search for .pyi files further down on the path if the
first hit has only a .py file.

Perhaps with PEP 561 we want to change this?

I guess either approach could be okay, as long as it's clearly documented. Also, it would be good to have a way for mypy to print out the full search path, in case mypy isn't finding the expected stubs/annotations. `mypy -v` could be modified to do this, for example.
The PEP is not specific here (see the section about this https://www.python.org/dev/peps/pep-0561/#type-checker-module-resolution-order), perhaps it should be. I think within the category of "installed packages" it shouldn't matter where they are installed. I have a simple patch which should fix this. (PR incoming)

Resolving patch:
diff --git a/mypy/build.py b/mypy/build.py
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -858,7 +858,10 @@ def _find_module(self, id: str, lib_path: Tuple[str, ...],
         dir_chain = os.sep.join(components[:-1])  # e.g., 'foo/bar'
         # TODO (ethanhs): refactor each path search to its own method with lru_cache
 
-        third_party_dirs = []
+        # We have two sets of folders so that we collect *all* stubs folders and
+        # put them in the front of the search path
+        third_party_inline_dirs = []
+        third_party_stubs_dirs = []
         # Third-party stub/typed packages
         for pkg_dir in _get_site_packages_dirs(python_executable):
             stub_name = components[0] + '-stubs'
@@ -868,11 +871,12 @@ def _find_module(self, id: str, lib_path: Tuple[str, ...],
                 stub_components = [stub_name] + components[1:]
                 path = os.path.join(pkg_dir, *stub_components[:-1])
                 if fscache.isdir(path):
-                    third_party_dirs.append(path)
+                    third_party_stubs_dirs.append(path)
             elif fscache.isfile(typed_file):
                 path = os.path.join(pkg_dir, dir_chain)
-                third_party_dirs.append(path)
-        candidate_base_dirs = self.find_lib_path_dirs(dir_chain, lib_path) + third_party_dirs
+                third_party_inline_dirs.append(path)
+        candidate_base_dirs = self.find_lib_path_dirs(dir_chain, lib_path) + \
+            third_party_stubs_dirs + third_party_inline_dirs
 
         # If we're looking for a module like 'foo.bar.baz', then candidate_base_dirs now
         # contains just the subdirectories 'foo/bar' that actually exist under the


Test patch: