-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Implement PEP 561 searching #4403
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
Changes from 29 commits
e4b5274
6b6a97d
2c936e1
8f64f81
ddc526f
329dc68
eba98d4
5576629
2397763
fda2a2c
59f0c49
c976008
8df8b8d
4ca6939
a96601c
336fb6b
8492d8b
c41eef2
1f005d7
45afe58
e7f23bb
d74eae8
5822884
c734bc2
1d7cc7e
1ac4253
f681bf9
85f233c
3726f18
59a2004
77b3b66
1da6e58
e54025f
056dc8f
297fac6
e35e7aa
ecb702e
1a41262
31dab8d
ff15057
9442765
a34e7e8
0fd592a
4cbe21a
f566ed0
206f70b
2dbff97
e33ceb7
644e9a5
af38a12
b7f0788
0564a5c
d17582b
085d002
8958dc7
84fd527
4c0ff1b
a917ff5
83ab973
cfac326
aefcb96
380e301
47d8e0d
5c19e8c
56dfcd4
4293dad
b810d3e
17488ad
e348471
8184937
97f29cc
928683a
ae65564
f6c4327
d475d4b
03cd378
3a8ca60
e1f9494
b5cbd09
fc58e4c
96e2f55
80f4a35
dc29e51
be733c5
c1264a0
ceacbff
f389f64
1bd9f66
806fbef
c9e3361
7be3555
5fc9d52
ae86abd
0bf024f
c9c35c1
dca9c54
a929b46
e56ffe3
72d7b0a
c9f6bc2
4698d23
db0680a
8367e57
24b6742
afb80ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,17 +8,21 @@ summary of command line flags can always be printed using the ``-h`` | |
flag (or its long form ``--help``):: | ||
|
||
$ mypy -h | ||
usage: mypy [-h] [-v] [-V] [--python-version x.y] [--platform PLATFORM] [-2] | ||
usage: mypy [-h] [-v] [-V] [--python-version x.y] | ||
[--python-executable PYTHON_EXECUTABLE] [--platform PLATFORM] [-2] | ||
[--ignore-missing-imports] | ||
[--follow-imports {normal,silent,skip,error}] | ||
[--disallow-any-{unimported,expr,decorated,explicit,generics}] | ||
[--disallow-untyped-calls] [--disallow-untyped-defs] | ||
[--disallow-any-unimported] [--disallow-any-expr] | ||
[--disallow-any-decorated] [--disallow-any-explicit] | ||
[--disallow-any-generics] [--disallow-untyped-calls] | ||
[--disallow-untyped-defs] [--disallow-incomplete-defs] | ||
[--check-untyped-defs] [--disallow-subclassing-any] | ||
[--warn-incomplete-stub] [--warn-redundant-casts] | ||
[--no-warn-no-return] [--warn-return-any] [--warn-unused-ignores] | ||
[--warn-incomplete-stub] [--disallow-untyped-decorators] | ||
[--warn-redundant-casts] [--no-warn-no-return] [--warn-return-any] | ||
[--warn-unused-ignores] [--warn-unused-configs] | ||
[--show-error-context] [--no-implicit-optional] [-i] | ||
[--quick-and-dirty] [--cache-dir DIR] [--skip-version-check] | ||
[--strict-optional] | ||
[--quick-and-dirty] [--cache-dir DIR] [--cache-fine-grained] | ||
[--skip-version-check] [--strict-optional] | ||
[--strict-optional-whitelist [GLOB [GLOB ...]]] | ||
[--junit-xml JUNIT_XML] [--pdb] [--show-traceback] [--stats] | ||
[--inferstats] [--custom-typing MODULE] | ||
|
@@ -28,9 +32,9 @@ flag (or its long form ``--help``):: | |
[--shadow-file SOURCE_FILE SHADOW_FILE] [--any-exprs-report DIR] | ||
[--cobertura-xml-report DIR] [--html-report DIR] | ||
[--linecount-report DIR] [--linecoverage-report DIR] | ||
[--memory-xml-report DIR] | ||
[--txt-report DIR] [--xml-report DIR] [--xslt-html-report DIR] | ||
[--xslt-txt-report DIR] [-m MODULE] [-c PROGRAM_TEXT] [-p PACKAGE] | ||
[--memory-xml-report DIR] [--txt-report DIR] [--xml-report DIR] | ||
[--xslt-html-report DIR] [--xslt-txt-report DIR] [-m MODULE] | ||
[-c PROGRAM_TEXT] [-p PACKAGE] | ||
[files [files ...]] | ||
|
||
(etc., too long to show everything here) | ||
|
@@ -366,6 +370,12 @@ Here are some more useful flags: | |
updates the cache, but regular incremental mode ignores cache files | ||
written by quick mode. | ||
|
||
- ``--python-executable EXECUTABLE`` will have mypy collect type information | ||
from PEP 561 compliant packages installed with the given Python executable. | ||
By default, mypy will use PEP 561 compliant packages installed for the Python | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way this is now written makes me wonder what happens if I use Python 3.6 to run |
||
executable running mypy. See :ref:`installed_packages` for more on making | ||
PEP 561 compliant packages. | ||
|
||
- ``--python-version X.Y`` will make mypy typecheck your code as if it were | ||
run under Python version X.Y. Without this option, mypy will default to using | ||
whatever version of Python is running mypy. Note that the ``-2`` and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should mention that this is inferred from the executable somewhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps combine the docs as something like ``--python-executable EXECUTABLE`` and ``--python-version X.Y`` will
have mypy will check your code as if it were run under the given python
installation.
Either argument can be used to infer the other.
If support of PEP561 packages is needed (see `--no-site-packages`), the executable
must either be specified or inferable. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ Mypy is a static type checker for Python. | |
command_line | ||
config_file | ||
python36 | ||
installed_packages | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't you add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized this file is essentially a duplicate of |
||
faq | ||
cheat_sheet | ||
cheat_sheet_py3 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
.. _installed-packages: | ||
|
||
Using Installed Packages | ||
======================== | ||
|
||
`PEP 561 <https://www.python.org/dev/peps/pep-0561/>`_ specifies how to mark | ||
a package as supporting type checking. Below is a summary of how to create | ||
PEP 561 compatible packages and have mypy use them in type checking. | ||
|
||
Making PEP 561 compatible packages | ||
********************************** | ||
|
||
Packages that must be imported at runtime that supply type information should | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change the second 'that' into 'and'? |
||
put a ``py.typed`` in their package directory. For example, with a directory | ||
structure as follows: | ||
|
||
.. code-block:: text | ||
|
||
setup.py | ||
package_a/ | ||
__init__.py | ||
lib.py | ||
py.typed | ||
|
||
the setup.py might look like: | ||
|
||
.. code-block:: python | ||
|
||
from distutils.core import setup | ||
|
||
setup( | ||
name="SuperPackageA", | ||
author="Me", | ||
version="0.1", | ||
package_data={"package_a": ["py.typed"]}, | ||
packages=["package_a"] | ||
) | ||
|
||
If the package is entirely made up of stub (``*.pyi``) files, the package | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key difference with the previous example isn't that it's entirely made of stubs, it's that it is not intended to be used at runtime. I'd introduce this as "stub-only packages" or something like that (what does PEP 561 call them?). Also can you show a third example (preferably in between the former and this one) that shows a package containing .py files and (some) .pyi files? This should explain that whenever both foo.py and foo.pyi exist, foo.py is used at runtime but foo.pyi is used by mypy. And it should show what you have to do to ensure that the foo.pyi is included in the distribution. Optionally it would be nice if it also showed (by example) that you don't need a .pyi for every .py (but you do need a .py for every .pyi in this case). |
||
should have a suffix of ``-stubs``. For example, if we had stubs for | ||
``package_b``, we might do the following: | ||
|
||
.. code-block:: text | ||
|
||
setup.py | ||
package_b-stubs/ | ||
__init__.pyi | ||
lib.pyi | ||
|
||
the setup.py might look like: | ||
|
||
.. code-block:: python | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These three lines need unindenting too |
||
|
||
from distutils.core import setup | ||
|
||
setup( | ||
name="SuperPackageB", | ||
author="Me", | ||
version="0.1", | ||
package_data={"package_b-stubs": ["__init__.pyi", "lib.pyi"]}, | ||
packages=["package_b-stubs"] | ||
) | ||
|
||
Using PEP 561 compatible packages with mypy | ||
******************************************* | ||
|
||
Generally, you do not need to do anything to use installed packages for the | ||
Python executable used to run mypy. They should be automatically picked up by | ||
mypy and used for type checking. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing that custom import hooks are not supported? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Custom import hooks are not supported by mypy normally, and this doesn't add support for that either. Do you have a specific need for that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, but it might be nice to note in the docs exactly where mypy stops respecting the normal import mechanics. Are zip imports supported? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add a note of that to the docs, and no, zip imports are not supported to my knowledge. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #4277 for the namespace packages implementation. That will be added later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, is there a dependency between this and #4277? Which should be merged first? |
||
|
||
If you use mypy to type check a Python other than the version running mypy, you | ||
can use the ``--python-executable`` flag to point to the executable, and mypy | ||
will find packages installed for that python executable. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
import re | ||
import site | ||
import stat | ||
from subprocess import check_output, STDOUT | ||
import sys | ||
import time | ||
from os.path import dirname, basename | ||
|
@@ -683,7 +684,7 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str: | |
|
||
def is_module(self, id: str) -> bool: | ||
"""Is there a file in the file system corresponding to module id?""" | ||
return find_module(id, self.lib_path) is not None | ||
return find_module(id, self.lib_path, self.options.python_executable) is not None | ||
|
||
def parse_file(self, id: str, path: str, source: str, ignore_errors: bool) -> MypyFile: | ||
"""Parse the source of a file with the given name. | ||
|
@@ -796,14 +797,14 @@ def remove_cwd_prefix_from_path(p: str) -> str: | |
return p | ||
|
||
|
||
# Cache find_module: (id, lib_path) -> result. | ||
find_module_cache = {} # type: Dict[Tuple[str, Tuple[str, ...]], Optional[str]] | ||
# Cache find_module: (id) -> result. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even better, drop the() around |
||
find_module_cache = {} # type: Dict[str, Optional[str]] | ||
|
||
# Cache some repeated work within distinct find_module calls: finding which | ||
# elements of lib_path have even the subdirectory they'd need for the module | ||
# to exist. This is shared among different module ids when they differ only | ||
# in the last component. | ||
find_module_dir_cache = {} # type: Dict[Tuple[str, Tuple[str, ...]], List[str]] | ||
find_module_dir_cache = {} # type: Dict[str, List[str]] | ||
|
||
# Cache directory listings. We assume that while one os.listdir() | ||
# call may be more expensive than one os.stat() call, a small number | ||
|
@@ -818,6 +819,9 @@ def remove_cwd_prefix_from_path(p: str) -> str: | |
# Cache for isdir(join(head, tail)) | ||
find_module_isdir_cache = {} # type: Dict[Tuple[str, str], bool] | ||
|
||
# Cache packages for Python executable | ||
package_dirs_cache = [] # type: List[str] | ||
|
||
|
||
def find_module_clear_caches() -> None: | ||
find_module_cache.clear() | ||
|
@@ -861,19 +865,70 @@ def is_file(path: str) -> bool: | |
return res | ||
|
||
|
||
def find_module(id: str, lib_path_arg: Iterable[str]) -> Optional[str]: | ||
USER_SITE_PACKAGES = \ | ||
'"import site; print(site.getusersitepackages()); print(*site.getsitepackages(), sep=\'\\n\')"' | ||
VIRTUALENV_SITE_PACKAGES = \ | ||
'"from distutils.sysconfig import get_python_lib; print(get_python_lib())"' | ||
|
||
|
||
def call_python(python: str, command: str) -> str: | ||
return check_output([python, '-c', command]).decode(sys.stdout.encoding) | ||
|
||
|
||
def get_package_dirs(python: Optional[str]) -> List[str]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here as everywhere I'd rename |
||
"""Find package directories for given python | ||
|
||
This defaults to the Python running mypy.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, another docstring style nit is that if the docstring is multi-line, the closing |
||
if package_dirs_cache: | ||
return package_dirs_cache | ||
package_dirs = [] # type: List[str] | ||
if python: | ||
# Use subprocess to get the package directory of given Python | ||
# executable | ||
check = check_output([python, '-V'], stderr=STDOUT).decode('UTF-8') | ||
assert check.startswith('Python'), \ | ||
"Mypy could not use the Python executable: {}".format(python) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine this will be a somewhat common user error, and when people see a failing assert + traceback they will mindlessly report a crash. So can you make this print a proper error message and exit with status 2? |
||
# If we have a working python executable, query information from it | ||
output = call_python(python, USER_SITE_PACKAGES) | ||
for line in output.splitlines(): | ||
if os.path.isdir(line): | ||
package_dirs.append(line) | ||
if not package_dirs: | ||
# if no paths are found, we fall back on sysconfig | ||
output = call_python(python, VIRTUALENV_SITE_PACKAGES) | ||
for line in output.splitlines(): | ||
if os.path.isdir(line): | ||
package_dirs.append(line) | ||
else: | ||
# Use running Python's package dirs | ||
try: | ||
user_dir = site.getusersitepackages() | ||
package_dirs = site.getsitepackages() + [user_dir] | ||
except AttributeError: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What thing above can raise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a comment? This is a bit obscure. Also if either of those exist but the call raises an unrelated AttributeError this would also hide that (though that's low probability). |
||
package_dirs = [get_python_lib()] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and the one above would be clearer as an early |
||
package_dirs_cache[:] = package_dirs | ||
return package_dirs | ||
|
||
|
||
def find_module(id: str, lib_path_arg: Iterable[str], | ||
python: Optional[str] = None) -> Optional[str]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename |
||
"""Return the path of the module source file, or None if not found.""" | ||
lib_path = tuple(lib_path_arg) | ||
package_dirs = get_package_dirs(python) | ||
if python: | ||
assert package_dirs, "Could not find package directories for Python '{}'".format(python) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds like it might be a user error that deserves better than an assert -- people will file crash bugs for failed asserts even if it's their own fault. |
||
components = id.split('.') | ||
dir_chain = os.sep.join(components[:-1]) # e.g., 'foo/bar' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did these two lines move outside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the loop below that removes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think at the very least all of this can move inside the |
||
|
||
def find() -> Optional[str]: | ||
# If we're looking for a module like 'foo.bar.baz', it's likely that most of the | ||
# many elements of lib_path don't even have a subdirectory 'foo/bar'. Discover | ||
# that only once and cache it for when we look for modules like 'foo.bar.blah' | ||
# that will require the same subdirectory. | ||
components = id.split('.') | ||
dir_chain = os.sep.join(components[:-1]) # e.g., 'foo/bar' | ||
if (dir_chain, lib_path) not in find_module_dir_cache: | ||
dirs = [] | ||
|
||
dirs = find_module_dir_cache.get(dir_chain, []) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I know what you mean, could you clarify? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A better fix would be to extra |
||
if not dirs: | ||
# Regular packages on the PATH | ||
for pathitem in lib_path: | ||
# e.g., '/usr/lib/python3.4/foo/bar' | ||
isdir = find_module_isdir_cache.get((pathitem, dir_chain)) | ||
|
@@ -883,8 +938,24 @@ def find() -> Optional[str]: | |
find_module_isdir_cache[pathitem, dir_chain] = isdir | ||
if isdir: | ||
dirs.append(dir) | ||
find_module_dir_cache[dir_chain, lib_path] = dirs | ||
candidate_base_dirs = find_module_dir_cache[dir_chain, lib_path] | ||
|
||
# Third-party stub/typed packages | ||
for pkg_dir in package_dirs: | ||
stub_name = components[0] + '-stubs' | ||
typed_file = os.path.join(pkg_dir, components[0], 'py.typed') | ||
stub_typed_file = os.path.join(pkg_dir, stub_name, 'py.typed') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised we're looking inside foo-stubs packages for a py.typed file. The example in the docs doesn't show a need for that or even mention the possibility. Shouldn't the mere presence of foo-stubs be enough? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I should update this. and remove the |
||
if os.path.isfile(stub_typed_file): | ||
components[0] = stub_name | ||
rest = components[:-1] | ||
path = os.path.join(pkg_dir, *rest) | ||
if os.path.isdir(path): | ||
dirs.append(path) | ||
elif os.path.isfile(typed_file): | ||
path = os.path.join(pkg_dir, dir_chain) | ||
dirs.append(path) | ||
|
||
find_module_dir_cache[dir_chain] = dirs | ||
candidate_base_dirs = find_module_dir_cache[dir_chain] | ||
|
||
# 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 | ||
|
@@ -897,23 +968,33 @@ def find() -> Optional[str]: | |
# Prefer package over module, i.e. baz/__init__.py* over baz.py*. | ||
for extension in PYTHON_EXTENSIONS: | ||
path = base_path + sepinit + extension | ||
path_stubs = base_path + '_stubs' + sepinit + extension | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you forget to change |
||
if is_file(path) and verify_module(id, path): | ||
return path | ||
elif is_file(path_stubs) and verify_module(id, path_stubs): | ||
return path_stubs | ||
# No package, look for module. | ||
for extension in PYTHON_EXTENSIONS: | ||
path = base_path + extension | ||
if is_file(path) and verify_module(id, path): | ||
return path | ||
return None | ||
|
||
key = (id, lib_path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does the key no longer contain lib_path? Should it contain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed it because the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is, Caching is hard, |
||
if key not in find_module_cache: | ||
find_module_cache[key] = find() | ||
return find_module_cache[key] | ||
if id not in find_module_cache: | ||
find_module_cache[id] = find() | ||
|
||
# If we searched for items with a base directory of site-packages/ we need to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment seems out of date now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No that is correct. Perhaps it needs to be re-worded? |
||
# remove it to avoid searching it for non-typed ids. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this could be worded better. |
||
for dir in package_dirs: | ||
if dir + os.sep in find_module_dir_cache[dir_chain]: | ||
find_module_dir_cache[dir_chain].remove(dir + os.sep) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be moved inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not easily, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can at least be moved inside the above |
||
|
||
return find_module_cache[id] | ||
|
||
|
||
def find_modules_recursive(module: str, lib_path: List[str]) -> List[BuildSource]: | ||
module_path = find_module(module, lib_path) | ||
def find_modules_recursive(module: str, lib_path: List[str], | ||
python: Optional[str]) -> List[BuildSource]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. python -> python_executable. |
||
module_path = find_module(module, lib_path, python) | ||
if not module_path: | ||
return [] | ||
result = [BuildSource(module_path, module, None)] | ||
|
@@ -933,14 +1014,14 @@ def find_modules_recursive(module: str, lib_path: List[str]) -> List[BuildSource | |
(os.path.isfile(os.path.join(abs_path, '__init__.py')) or | ||
os.path.isfile(os.path.join(abs_path, '__init__.pyi'))): | ||
hits.add(item) | ||
result += find_modules_recursive(module + '.' + item, lib_path) | ||
result += find_modules_recursive(module + '.' + item, lib_path, python) | ||
elif item != '__init__.py' and item != '__init__.pyi' and \ | ||
item.endswith(('.py', '.pyi')): | ||
mod = item.split('.')[0] | ||
if mod not in hits: | ||
hits.add(mod) | ||
result += find_modules_recursive( | ||
module + '.' + mod, lib_path) | ||
result += find_modules_recursive(module + '.' + mod, | ||
lib_path, python) | ||
return result | ||
|
||
|
||
|
@@ -1606,8 +1687,15 @@ def __init__(self, | |
# difference and just assume 'builtins' everywhere, | ||
# which simplifies code. | ||
file_id = '__builtin__' | ||
path = find_module(file_id, manager.lib_path) | ||
path = find_module(file_id, manager.lib_path, manager.options.python_executable) | ||
if path: | ||
# Installed package modules should be silenced. They are all under absolute | ||
# paths. When 3.4 is dropped, this should just use os.path.commonpath. | ||
if os.path.isabs(path): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could use a comment. IIUC its purpose is to ignore all errors for modules given by absolute path. That also seems dangerous -- what if somehow I were to run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, and since you misunderstood this, it definitely deserves some explanation in a comment :) We want to silence errors in modules that are installed packages. The best heuristic that works on all Python versions that I could find is to check that if we are have a path (eg
should accomplish the same task and be cleaner. The This does have the rather unfortunate unintended side-effect of silencing errors for typeshed and anything else under eg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I definitely don't want to silence errors in typeshed. Sometimes people pass flags that cause errors in typeshed, and those errors might explain other errors they see, so silencing them would be wrong. Also, maybe add a comment to the effect that the |
||
# Silence errors from module if it is in a package directory | ||
for dir in package_dirs_cache: | ||
if path.startswith(dir + os.sep): | ||
self.ignore_all = True | ||
# For non-stubs, look at options.follow_imports: | ||
# - normal (default) -> fully analyze | ||
# - silent -> analyze but silence errors | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,7 @@ def __init__(self) -> None: | |
# -- build options -- | ||
self.build_type = BuildType.STANDARD | ||
self.python_version = defaults.PYTHON3_VERSION | ||
self.python_executable = None # type: Optional[str] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I should probably change these to both default to |
||
self.platform = sys.platform | ||
self.custom_typing_module = None # type: Optional[str] | ||
self.custom_typeshed_dir = None # type: Optional[str] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where did this option come from, and if it's introduced in this patch, should it be documented too?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was introduced in #4526 but not added to the docs it seems.
I suppose it would be better to have it formally documented in a seperate PR.(Guido wants me to add it)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(FWIW the reason it didn't appear in the docs right away was simply that we don't regenerate this list every time we add a flag. But we should.)