def syntax(message=None, usage=None): if message: logger.error(message) outmsg = ((usage or maindoc()) + '\n\nFor usage, see: %s --help' % (sys.argv[0],)) print(outmsg, file=sys.stderr) raise SystemExit(1)
def fix_unused_and_missing_imports(codeblock, add_missing=True, remove_unused="AUTOMATIC", add_mandatory=True, db=None, params=None): r""" Check for unused and missing imports, and fix them automatically. Also formats imports. In the example below, C{m1} and C{m3} are unused, so are automatically removed. C{np} was undefined, so an C{import numpy as np} was automatically added. >>> codeblock = PythonBlock( ... 'from foo import m1, m2, m3, m4\n' ... 'm2, m4, np.foo', filename="/tmp/foo.py") >>> print fix_unused_and_missing_imports(codeblock, add_mandatory=False) [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m1' [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m3' [PYFLYBY] /tmp/foo.py: added 'import numpy as np' import numpy as np from foo import m2, m4 m2, m4, np.foo @type codeblock: L{PythonBlock} or convertible (C{str}) @rtype: L{PythonBlock} """ codeblock = PythonBlock(codeblock) if remove_unused == "AUTOMATIC": fn = codeblock.filename remove_unused = not (fn and (fn.base == "__init__.py" or ".pyflyby" in str(fn).split("/"))) elif remove_unused is True or remove_unused is False: pass else: raise ValueError("Invalid remove_unused=%r" % (remove_unused, )) params = ImportFormatParams(params) db = ImportDB.interpret_arg(db, target_filename=codeblock.filename) # Do a first pass reformatting the imports to get rid of repeated or # shadowed imports, e.g. L1 here: # import foo # L1 # import foo # L2 # foo # L3 codeblock = reformat_import_statements(codeblock, params=params) filename = codeblock.filename transformer = SourceToSourceFileImportsTransformation(codeblock) missing_imports, unused_imports = scan_for_import_issues( codeblock, find_unused_imports=remove_unused, parse_docstrings=True) logger.debug("missing_imports = %r", missing_imports) logger.debug("unused_imports = %r", unused_imports) if remove_unused and unused_imports: # Go through imports to remove. [This used to be organized by going # through import blocks and removing all relevant blocks from there, # but if one removal caused problems the whole thing would fail. The # CPU cost of calling without_imports() multiple times isn't worth # that.] # TODO: don't remove unused mandatory imports. [This isn't # implemented yet because this isn't necessary for __future__ imports # since they aren't reported as unused, and those are the only ones we # have by default right now.] for lineno, imp in unused_imports: try: imp = transformer.remove_import(imp, lineno) except NoSuchImportError: logger.error( "%s: couldn't remove import %r", filename, imp, ) except LineNumberNotFoundError as e: logger.error("%s: unused import %r on line %d not global", filename, str(imp), e.args[0]) else: logger.info("%s: removed unused '%s'", filename, imp) if add_missing and missing_imports: missing_imports.sort(key=lambda k: (k[1], k[0])) known = db.known_imports.by_import_as # Decide on where to put each import to be added. Find the import # block with the longest common prefix. Tie-break by preferring later # blocks. added_imports = set() for lineno, ident in missing_imports: import_as = ident.parts[0] try: imports = known[import_as] except KeyError: logger.warning( "%s:%s: undefined name %r and no known import for it", filename, lineno, import_as) continue if len(imports) != 1: logger.error("%s: don't know which of %r to use", filename, imports) continue imp_to_add = imports[0] if imp_to_add in added_imports: continue transformer.add_import(imp_to_add, lineno) added_imports.add(imp_to_add) logger.info("%s: added %r", filename, imp_to_add.pretty_print().strip()) if add_mandatory: # Todo: allow not adding to empty __init__ files? mandatory = db.mandatory_imports.imports for imp in mandatory: try: transformer.add_import(imp) except ImportAlreadyExistsError: pass else: logger.info("%s: added mandatory %r", filename, imp.pretty_print().strip()) return transformer.output(params=params)