Skip to content

Add --almost-silent, which modifies --silent-imports to warn about silenced imports #1371

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 6 commits into from
Apr 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
DUMP_TYPE_STATS = 'dump-type-stats'
DUMP_INFER_STATS = 'dump-infer-stats'
SILENT_IMPORTS = 'silent-imports' # Silence imports of .py files
ALMOST_SILENT = 'almost-silent' # If SILENT_IMPORTS: report silenced imports as errors
INCREMENTAL = 'incremental' # Incremental mode: use the cache
FAST_PARSER = 'fast-parser' # Use experimental fast parser
# Disallow calling untyped functions from typed ones
Expand Down Expand Up @@ -941,7 +942,7 @@ def __init__(self,
manager: BuildManager,
caller_state: 'State' = None,
caller_line: int = 0,
is_ancestor: bool = False,
ancestor_for: 'State' = None,
) -> None:
assert id or path or source is not None, "Neither id, path nor source given"
self.manager = manager
Expand Down Expand Up @@ -971,13 +972,18 @@ def __init__(self,
if path:
# In silent mode, don't import .py files, except from stubs.
if (SILENT_IMPORTS in manager.flags and
path.endswith('.py') and (caller_state or is_ancestor)):
path.endswith('.py') and (caller_state or ancestor_for)):
# (Never silence builtins, even if it's a .py file;
# this can happen in tests!)
if (id != 'builtins' and
not ((caller_state and
caller_state.tree and
caller_state.tree.is_stub))):
if ALMOST_SILENT in manager.flags:
if ancestor_for:
self.skipping_ancestor(id, path, ancestor_for)
else:
self.skipping_module(id, path)
path = None
manager.missing_modules.add(id)
raise ModuleNotFound
Expand All @@ -986,10 +992,12 @@ def __init__(self,
# misspelled module name, missing stub, module not in
# search path or the module has not been installed.
if caller_state:
if not (SILENT_IMPORTS in manager.flags or
(caller_state.tree is not None and
(caller_line in caller_state.tree.ignored_lines or
'import' in caller_state.tree.weak_opts))):
suppress_message = ((SILENT_IMPORTS in manager.flags and
ALMOST_SILENT not in manager.flags) or
(caller_state.tree is not None and
(caller_line in caller_state.tree.ignored_lines or
'import' in caller_state.tree.weak_opts)))
if not suppress_message:
save_import_context = manager.errors.import_context()
manager.errors.set_import_context(caller_state.import_context)
manager.module_not_found(caller_state.xpath, caller_line, id)
Expand All @@ -1015,6 +1023,36 @@ def __init__(self,
# Parse the file (and then some) to get the dependencies.
self.parse_file()

def skipping_ancestor(self, id: str, path: str, ancestor_for: 'State') -> None:
# TODO: Read the path (the __init__.py file) and return
# immediately if it's empty or only contains comments.
# But beware, some package may be the ancestor of many modules,
# so we'd need to cache the decision.
manager = self.manager
manager.errors.set_import_context([])
manager.errors.set_file(ancestor_for.xpath)
manager.errors.report(-1, "Ancestor package '%s' silently ignored" % (id,),
severity='note', only_once=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you intend to apply this only_once here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I did -- if a package has many children it would be annoying to hear about this each time. And there's no possibility that some of the children have their ancestor ignored while others don't -- it's one ancestor and it's either ignored or not.

manager.errors.report(-1, "(Using --silent-imports, submodule passed on command line)",
severity='note', only_once=True)
manager.errors.report(-1, "(This note brought to you by --almost-silent)",
severity='note', only_once=True)

def skipping_module(self, id: str, path: str) -> None:
assert self.caller_state, (id, path)
manager = self.manager
save_import_context = manager.errors.import_context()
manager.errors.set_import_context(self.caller_state.import_context)
manager.errors.set_file(self.caller_state.xpath)
line = self.caller_line
manager.errors.report(line, "Import of '%s' silently ignored" % (id,),
severity='note')
manager.errors.report(line, "(Using --silent-imports, module not passed on command line)",
severity='note', only_once=True)
manager.errors.report(line, "(This note courtesy of --almost-silent)",
severity='note', only_once=True)
manager.errors.set_import_context(save_import_context)

def add_ancestors(self) -> None:
# All parent packages are new ancestors.
ancestors = []
Expand Down Expand Up @@ -1215,7 +1253,7 @@ def load_graph(sources: List[BuildSource], manager: BuildManager) -> Graph:
# TODO: Why not 'if dep not in st.dependencies' ?
# Ancestors don't have import context.
newst = State(id=dep, path=None, source=None, manager=manager,
is_ancestor=True)
ancestor_for=st)
else:
newst = State(id=dep, path=None, source=None, manager=manager,
caller_state=st, caller_line=st.dep_line_map.get(dep, 1))
Expand Down
11 changes: 9 additions & 2 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,12 @@ def parse_version(v):
help='use Python x.y')
parser.add_argument('--py2', dest='python_version', action='store_const',
const=defaults.PYTHON2_VERSION, help="use Python 2 mode")
parser.add_argument('-s', '--silent-imports', '--silent', action='store_true',
parser.add_argument('-s', '--silent-imports', action='store_true',
help="don't follow imports to .py files")
parser.add_argument('--silent', action='store_true',
help="deprecated name for --silent-imports")
parser.add_argument('--almost-silent', action='store_true',
help="like --silent-imports but reports the imports as errors")
parser.add_argument('--disallow-untyped-calls', action='store_true',
help="disallow calling functions without type annotations"
" from functions with type annotations")
Expand Down Expand Up @@ -208,8 +212,11 @@ def parse_version(v):
if args.inferstats:
options.build_flags.append(build.DUMP_INFER_STATS)

if args.silent_imports:
if args.silent_imports or args.silent:
options.build_flags.append(build.SILENT_IMPORTS)
if args.almost_silent:
options.build_flags.append(build.SILENT_IMPORTS)
options.build_flags.append(build.ALMOST_SILENT)

if args.disallow_untyped_calls:
options.build_flags.append(build.DISALLOW_UNTYPED_CALLS)
Expand Down
23 changes: 23 additions & 0 deletions mypy/test/data/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,29 @@ class C(B):
[out]
tmp/bar.py:1: error: Module has no attribute 'B'

[case testImportSuppressedWhileAlmostSilent]
# cmd: mypy -m main
[file main.py]
# flags: silent-imports almost-silent
import mod
[file mod.py]
[builtins fixtures/module.py]
[out]
tmp/main.py:2: note: Import of 'mod' silently ignored
tmp/main.py:2: note: (Using --silent-imports, module not passed on command line)
tmp/main.py:2: note: (This note courtesy of --almost-silent)

[case testAncestorSuppressedWhileAlmostSilent]
# cmd: mypy -m foo.bar
[file foo/bar.py]
# flags: silent-imports almost-silent
[file foo/__init__.py]
[builtins fixtures/module.py]
[out]
tmp/foo/bar.py: note: Ancestor package 'foo' silently ignored
tmp/foo/bar.py: note: (Using --silent-imports, submodule passed on command line)
tmp/foo/bar.py: note: (This note brought to you by --almost-silent)

[case testStubImportNonStubWhileSilent]
# cmd: mypy -m main
[file main.py]
Expand Down