Skip to content
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

Add --exclude #9992

Merged
merged 22 commits into from
Feb 10, 2021
24 changes: 24 additions & 0 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ for full details, see :ref:`running-mypy`.
Asks mypy to type check the provided string as a program.


.. option:: --exclude

A regular expression that matches file names, directory names and paths
which mypy should ignore while recursively discovering files to check.
hauntsaninja marked this conversation as resolved.
Show resolved Hide resolved
Use forward slashes on all platforms.
hauntsaninja marked this conversation as resolved.
Show resolved Hide resolved

For instance, to avoid discovering any files named `setup.py` you could
pass ``--exclude '/setup\.py$'``. Similarly, you can ignore discovering
directories with a given name by e.g. ``--exclude /build/`` or
those matching a subpath with ``--exclude /project/vendor/``.

Note that this flag only affects recursive discovery, that is, when mypy is
discovering files within a directory tree or submodules of a package to
check. If you pass a file or module explicitly it will still be checked. For
instance, ``mypy --exclude '/setup.py$' but_still_check/setup.py``.

Note that mypy will never recursively discover files and directories named
"site-packages", "node_modules" or "__pycache__", or those whose name starts
with a period, exactly as ``--exclude
'/(site-packages|node_modules|__pycache__|\..*)/$'`` would. Mypy will also
never recursively discover files with extensions other than ``.py`` or
``.pyi``.


Optional arguments
******************

Expand Down
12 changes: 12 additions & 0 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ section of the command line docs.

This option may only be set in the global section (``[mypy]``).

.. confval:: exclude

:type: regular expression

A regular expression that matches file names, directory names and paths
which mypy should ignore while recursively discovering files to check.
Use forward slashes on all platforms.

For more details, see :option:`--exclude <mypy --exclude>`.

This option may only be set in the global section (``[mypy]``).
hauntsaninja marked this conversation as resolved.
Show resolved Hide resolved

.. confval:: namespace_packages

:type: boolean
Expand Down
3 changes: 2 additions & 1 deletion docs/source/running_mypy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,8 @@ to modules to type check.
- Mypy will check all paths provided that correspond to files.

- Mypy will recursively discover and check all files ending in ``.py`` or
``.pyi`` in directory paths provided.
``.pyi`` in directory paths provided, after accounting for
:option:`--exclude <mypy --exclude>`.

- For each file to be checked, mypy will attempt to associate the file (e.g.
``project/foo/bar/baz.py``) with a fully qualified module name (e.g.
Expand Down
16 changes: 7 additions & 9 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import gc
import json
import os
import pathlib
import re
import stat
import sys
Expand Down Expand Up @@ -2570,6 +2569,7 @@ def log_configuration(manager: BuildManager, sources: List[BuildSource]) -> None
("Current Executable", sys.executable),
("Cache Dir", manager.options.cache_dir),
("Compiled", str(not __file__.endswith(".py"))),
("Exclude", manager.options.exclude),
]

for conf_name, conf_value in configuration_vars:
Expand Down Expand Up @@ -2769,14 +2769,12 @@ def load_graph(sources: List[BuildSource], manager: BuildManager,
"Duplicate module named '%s' (also at '%s')" % (st.id, graph[st.id].xpath),
blocker=True,
)
p1 = len(pathlib.PurePath(st.xpath).parents)
p2 = len(pathlib.PurePath(graph[st.id].xpath).parents)

if p1 != p2:
manager.errors.report(
-1, -1,
"Are you missing an __init__.py?"
)
manager.errors.report(
-1, -1,
"Are you missing an __init__.py? Alternatively, consider using --exclude to "
"avoid checking one of them.",
severity='note'
)

manager.errors.raise_error()
graph[st.id] = st
Expand Down
11 changes: 9 additions & 2 deletions mypy/find_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import List, Sequence, Set, Tuple, Optional
from typing_extensions import Final

from mypy.modulefinder import BuildSource, PYTHON_EXTENSIONS, mypy_path
from mypy.modulefinder import BuildSource, PYTHON_EXTENSIONS, mypy_path, matches_exclude
from mypy.fscache import FileSystemCache
from mypy.options import Options

Expand Down Expand Up @@ -91,6 +91,8 @@ def __init__(self, fscache: FileSystemCache, options: Options) -> None:
self.fscache = fscache
self.explicit_package_bases = get_explicit_package_bases(options)
self.namespace_packages = options.namespace_packages
self.exclude = options.exclude
self.verbosity = options.verbosity

def is_explicit_package_base(self, path: str) -> bool:
assert self.explicit_package_bases
Expand All @@ -103,10 +105,15 @@ def find_sources_in_dir(self, path: str) -> List[BuildSource]:
names = sorted(self.fscache.listdir(path), key=keyfunc)
for name in names:
# Skip certain names altogether
if name == '__pycache__' or name.startswith('.') or name.endswith('~'):
if name in ("__pycache__", "site-packages", "node_modules") or name.startswith("."):
continue
hauntsaninja marked this conversation as resolved.
Show resolved Hide resolved
subpath = os.path.join(path, name)

if matches_exclude(
subpath, self.exclude, self.fscache, self.verbosity >= 2
):
continue

if self.fscache.isdir(subpath):
sub_sources = self.find_sources_in_dir(subpath)
if sub_sources:
Expand Down
9 changes: 9 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,15 @@ def add_invertible_flag(flag: str,
code_group.add_argument(
'--explicit-package-bases', action='store_true',
help="Use current directory and MYPYPATH to determine module names of files passed")
code_group.add_argument(
"--exclude",
metavar="PATTERN",
default="",
help=(
"Regular expression to match file names, directory names or paths which mypy should "
"ignore while recursively discovering files to check, e.g. --exclude '/setup\\.py$'"
)
)
code_group.add_argument(
'-m', '--module', action='append', metavar='MODULE',
default=[],
Expand Down
25 changes: 22 additions & 3 deletions mypy/modulefinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import collections
import functools
import os
import re
import subprocess
import sys
from enum import Enum
Expand Down Expand Up @@ -443,10 +444,15 @@ def find_modules_recursive(self, module: str) -> List[BuildSource]:
names = sorted(self.fscache.listdir(package_path))
for name in names:
# Skip certain names altogether
if name == '__pycache__' or name.startswith('.') or name.endswith('~'):
if name in ("__pycache__", "site-packages", "node_modules") or name.startswith("."):
continue
subpath = os.path.join(package_path, name)

if self.options and matches_exclude(
subpath, self.options.exclude, self.fscache, self.options.verbosity >= 2
):
continue

if self.fscache.isdir(subpath):
# Only recurse into packages
if (self.options and self.options.namespace_packages) or (
Expand All @@ -460,13 +466,26 @@ def find_modules_recursive(self, module: str) -> List[BuildSource]:
if stem == '__init__':
continue
if stem not in seen and '.' not in stem and suffix in PYTHON_EXTENSIONS:
# (If we sorted names) we could probably just make the BuildSource ourselves,
# but this ensures compatibility with find_module / the cache
# (If we sorted names by keyfunc) we could probably just make the BuildSource
# ourselves, but this ensures compatibility with find_module / the cache
seen.add(stem)
sources.extend(self.find_modules_recursive(module + '.' + stem))
return sources


def matches_exclude(subpath: str, exclude: str, fscache: FileSystemCache, verbose: bool) -> bool:
if not exclude:
return False
subpath_str = os.path.abspath(subpath).replace(os.sep, "/")
if fscache.isdir(subpath):
subpath_str += "/"
if re.search(exclude, subpath_str):
if verbose:
print("TRACE: Excluding {}".format(subpath_str), file=sys.stderr)
return True
return False


def verify_module(fscache: FileSystemCache, id: str, path: str, prefix: str) -> bool:
"""Check that all packages containing id have a __init__ file."""
if path.endswith(('__init__.py', '__init__.pyi')):
Expand Down
2 changes: 2 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def __init__(self) -> None:
# sufficient to determine module names for files. As a possible alternative, add a single
# top-level __init__.py to your packages.
self.explicit_package_bases = False
# File names, directory names or subpaths to avoid checking
self.exclude = "" # type: str

# disallow_any options
self.disallow_any_generics = False
Expand Down
Loading