class TestConfig(TestCase): def setUp(self): self.include = "test|test2|tests.py" self.exclude = "__init__.py|tests/" self.config_toml = Session( config_file=CONFIG_TOML["config_file"]).config self.config_cfg = Session(config_file=CONFIG_CFG["config_file"]).config def test_toml_attr(self): self.assertEqual(self.include, self.config_toml.include) self.assertEqual(self.exclude, self.config_toml.exclude) def test_toml_is_available_to_parse(self): setattr(CONFIG, "HAS_TOML", True) self.assertTrue( self.config_toml.is_available_to_parse( self.config_toml.config_file)) def test_toml_but_has_toml_false(self): setattr(CONFIG, "HAS_TOML", False) self.assertFalse( self.config_toml.is_available_to_parse( self.config_toml.config_file)) def test_cfg_attr(self): self.assertEqual(self.include, self.config_cfg.include) self.assertEqual(self.exclude, self.config_cfg.exclude) def test_cfg_is_available_to_parse(self): self.assertTrue( self.config_cfg.is_available_to_parse(self.config_cfg.config_file)) def test_config_file_none(self): none_config = Session(config_file=None).config self.assertIsNone(none_config)
class RefactorTestCase(unittest.TestCase): maxDiff = None include_star_import = False def setUp(self): self.session = Session(include_star_import=self.include_star_import) def assertActionAfterRefactorEqualToAction(self, action): super().assertEqual( textwrap.dedent(action), self.session.refactor(textwrap.dedent(action)), ) def assertActionAfterRefactorEqualToExpected(self, action, expected): super().assertEqual( textwrap.dedent(expected), self.session.refactor(textwrap.dedent(action)), ) def assertActionAfterRefactorEqualToEmpty(self, action): self.assertActionAfterRefactorEqualToExpected( action, """\ """, )
class TestSession(unittest.TestCase): maxDiff = None include_star_import = True def setUp(self): self.session = Session(include_star_import=self.include_star_import) def test_list_paths_and_read(self): for path in [Path("tests"), Path("tests/test_config.py")]: for p in self.session.list_paths(path): self.assertTrue(str(p).endswith(".py")) def temp_refactor(self, source: str, expected: str, apply: bool = False): with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as tmp: tmp.write(source) tmp.seek(0) result = self.session.refactor_file(path=Path(tmp.name), apply=apply) self.assertEqual(result, expected) def test_refactor_file(self): self.temp_refactor(source="import os", expected="") def test_refactor_file_apply(self): self.temp_refactor(source="import os", expected="", apply=True) def test_diff(self): diff = ("--- \n", "+++ \n", "@@ -1 +0,0 @@\n", "-import os") self.assertEqual(diff, self.session.diff("import os")) def test_diff_file(self): with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as tmp: tmp.write("import os") tmp.seek(0) diff_file = self.session.diff_file(path=Path(tmp.name)) diff = ( f"--- {tmp.name}\n", "+++ \n", "@@ -1 +0,0 @@\n", "-import os", ) self.assertEqual(diff, diff_file) def test_read(self): source = "b�se" with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as tmp: tmp.write(source) tmp.seek(0) self.assertEqual((source, "utf-8"), self.session.read(Path(tmp.name)))
class TestSession(unittest.TestCase): maxDiff = None include_star_import = True def setUp(self): self.session = Session(include_star_import=self.include_star_import) def test_list_paths_and_read(self): for path in [Path("tests"), Path("tests/test_config.py")]: for p in self.session.list_paths(path): self.assertTrue(str(p).endswith(".py")) def temp_refactor(self, source: str, expected: str, apply: bool = False): with reopenable_temp_file(source) as tmp_path: result, _ = self.session.refactor_file(path=tmp_path, apply=apply) self.assertEqual(result, expected) def test_bad_encoding(self): # Make conflict between BOM and encoding Cookie. # https://docs.python.org/3/library/tokenize.html#tokenize.detect_encoding bad_encoding = "\ufeff\n# -*- coding: utf-32 -*-\nbad encoding" self.temp_refactor(source=bad_encoding, expected="") def test_refactor_file(self): self.temp_refactor(source="import os", expected="") def test_refactor_file_apply(self): self.temp_refactor(source="import os", expected="", apply=True) def test_diff(self): diff = ("--- \n", "+++ \n", "@@ -1 +0,0 @@\n", "-import os") self.assertEqual(diff, self.session.diff("import os")) def test_diff_file(self): with reopenable_temp_file("import os") as tmp_path: diff_file = self.session.diff_file(path=tmp_path) diff = ( f"--- {tmp_path.as_posix()}\n", "+++ \n", "@@ -1 +0,0 @@\n", "-import os", ) self.assertEqual(diff, diff_file) def test_read(self): source = "b�se" with reopenable_temp_file(source) as tmp_path: self.assertEqual((source, "utf-8"), self.session.read(tmp_path))
def test_config_default_values(self): config = Session(config_file=None).config self.assertFalse(config.include) self.assertFalse(config.exclude) self.assertFalse(config.sources) self.assertFalse(config.gitignore) self.assertFalse(config.requirements) self.assertFalse(config.remove) self.assertFalse(config.diff)
class ScannerTestCase(unittest.TestCase): maxDiff = None include_star_import = False def setUp(self): self.scanner = Session( include_star_import=self.include_star_import).scanner def assertUnimportEqual( self, source, expected_names=[], expected_imports=[], ): self.scanner.scan(source) self.assertEqual(expected_names, self.scanner.names) self.assertEqual(expected_imports, self.scanner.imports) self.scanner.clear()
def main(argv=None): namespace = parser.parse_args(argv) namespace.check = namespace.check or not any( [value for key, value in vars(namespace).items()][5:-1]) session = Session( config_file=namespace.config, include_star_import=namespace.include_star_import, ) include_list, exclude_list = [], [] if namespace.include: include_list.append(namespace.include) if hasattr(session.config, "include"): include_list.append(session.config.include or "") if namespace.exclude: exclude_list.append(namespace.exclude) if hasattr(session.config, "exclude"): exclude_list.append(session.config.exclude or "") include = re.compile("|".join(include_list)).pattern exclude = re.compile("|".join(exclude_list)).pattern _any_unimport = False for source_path in namespace.sources: for py_path in session._list_paths(source_path, include, exclude): if namespace.check: session.scanner.run_visit(source=session._read(py_path)[0]) unused_imports = list(session.scanner.get_unused_imports()) show(unused_imports, py_path) if not (not _any_unimport and not unused_imports): _any_unimport = True session.scanner.clear() if namespace.diff or namespace.permission: exists_diff = print_if_exists(session.diff_file(py_path)) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(py_path)).yellow}' [Y/n/q] ? >" ).lower() if action == "q": break elif action == "y" or action == "": namespace.remove = True if namespace.remove: source = session._read(py_path)[0] refactor_source = session.refactor_file(py_path, apply=True) if refactor_source != source: print(f"Refactoring '{Color(str(py_path)).green}'") if not _any_unimport and namespace.check: print( Color( "✨ Congratulations there is no unused import in your project. ✨" ).green)
def setUp(self): self.session = Session(include_star_import=self.include_star_import)
def main(argv: Optional[Sequence[str]] = None) -> int: parser = argparse.ArgumentParser( prog="unimport", description=C.DESCRIPTION, epilog="Get rid of all unused imports 🥳", ) exclusive_group = parser.add_mutually_exclusive_group(required=False) parser.add_argument( "sources", default=[Path(".")], nargs="*", help="files and folders to find the unused imports.", action="store", type=Path, ) parser.add_argument( "-c", "--config", default=".", help="read configuration from PATH.", metavar="PATH", action="store", type=Path, ) parser.add_argument( "--include", help="file include pattern.", metavar="include", action="store", default=[], type=lambda value: [value], ) parser.add_argument( "--exclude", help="file exclude pattern.", metavar="exclude", action="store", default=[], type=lambda value: [value], ) parser.add_argument( "--gitignore", action="store_true", help="exclude .gitignore patterns. if present.", ) parser.add_argument( "--include-star-import", action="store_true", help="Include star imports during scanning and refactor.", ) parser.add_argument( "--show-error", action="store_true", help="Show or don't show errors captured during static analysis.", ) parser.add_argument( "-d", "--diff", action="store_true", help="Prints a diff of all the changes unimport would make to a file.", ) exclusive_group.add_argument( "-r", "--remove", action="store_true", help="remove unused imports automatically.", ) exclusive_group.add_argument( "-p", "--permission", action="store_true", help="Refactor permission after see diff.", ) parser.add_argument( "--requirements", action="store_true", help= "Include requirements.txt file, You can use it with all other arguments", ) parser.add_argument( "--check", action="store_true", help="Prints which file the unused imports are in.", ) parser.add_argument( "-v", "--version", action="version", version=f"Unimport {C.VERSION}", help="Prints version of unimport", ) argv = argv if argv is not None else sys.argv[1:] args = parser.parse_args(argv) session = Session( config_file=args.config, include_star_import=args.include_star_import, show_error=args.show_error, ) args.remove = args.remove or session.config.remove # type: ignore args.diff = any( (args.diff, args.permission, session.config.diff)) # type: ignore args.check = args.check or not any((args.diff, args.remove)) args.requirements = args.requirements or session.config.requirements # type: ignore args.gitignore = args.gitignore or session.config.gitignore # type: ignore args.sources.extend(session.config.sources) # type: ignore args.include.extend(session.config.include) # type: ignore args.exclude.extend(session.config.exclude) # type: ignore if args.gitignore: args.exclude.extend(get_exclude_list_from_gitignore()) include = re.compile("|".join(args.include)).pattern exclude = re.compile("|".join(args.exclude)).pattern unused_modules = set() packages: Set[str] = set() for source_path in args.sources: for py_path in session.list_paths(source_path, include, exclude): session.scanner.scan(source=session.read(py_path)[0]) unused_imports = session.scanner.unused_imports unused_modules.update({imp.name for imp in unused_imports}) packages.update( get_used_packages(session.scanner.imports, session.scanner.unused_imports)) if args.check: show(unused_imports, py_path) session.scanner.clear() if args.diff: exists_diff = print_if_exists(session.diff_file(py_path)) if args.permission and exists_diff: action = input( f"Apply suggested changes to '{color.paint(str(py_path), color.YELLOW)}' [Y/n/q] ? >" ).lower() if action == "q": return 1 elif actiontobool(action): args.remove = True if args.remove and session.refactor_file(py_path, apply=True)[1]: print( f"Refactoring '{color.paint(str(py_path), color.GREEN)}'") if not unused_modules and args.check: print( color.paint( "✨ Congratulations there is no unused import in your project. ✨", color.GREEN, )) if args.requirements and packages: for requirements in Path(".").glob("requirements*.txt"): splitlines_requirements = requirements.read_text().splitlines() result = splitlines_requirements.copy() for index, requirement in enumerate(splitlines_requirements): module_name = package_name_from_metadata( requirement.split("==")[0]) if module_name is None: if args.show_error: print( color.paint(requirement + " not found", color.RED)) continue if module_name not in packages: result.remove(requirement) if args.check: print( f"{color.paint(requirement, color.CYAN)} at " f"{color.paint(requirements.as_posix(), color.CYAN)}:{color.paint(str(index + 1), color.CYAN)}" ) if args.diff: exists_diff = print_if_exists( tuple( difflib.unified_diff( splitlines_requirements, result, fromfile=requirements.as_posix(), ))) if args.permission and exists_diff: action = input( f"Apply suggested changes to '{color.paint(requirements.as_posix(), color.CYAN)}' [Y/n/q] ? >" ).lower() if action == "q": return 1 if actiontobool(action): args.remove = True if args.remove: requirements.write_text("".join(result)) print( f"Refactoring '{color.paint(requirements.as_posix(), color.CYAN)}'" ) if unused_modules: return 1 else: return 0
def setUp(self): self.scanner = Session( include_star_import=self.include_star_import).scanner
def main(argv: Optional[List[str]] = None) -> None: namespace = parser.parse_args(argv) namespace.check = namespace.check or not any( [namespace.diff, namespace.remove, namespace.permission] ) namespace.diff = namespace.diff or namespace.permission session = Session( config_file=namespace.config, include_star_import=namespace.include_star_import, show_error=namespace.show_error, ) include_list = [] exclude_list = [] if namespace.include: include_list.append(namespace.include) if hasattr(session.config, "include"): include_list.append(session.config.include) # type: ignore if namespace.exclude: exclude_list.append(namespace.exclude) if hasattr(session.config, "exclude"): exclude_list.append(session.config.exclude) # type: ignore include = re.compile("|".join(include_list)).pattern exclude = re.compile("|".join(exclude_list)).pattern is_unused_module = False unused_modules = set() for source_path in namespace.sources: for py_path in session.list_paths(source_path, include, exclude): session.scanner.scan(source=session.read(py_path)[0]) unused_imports = session.scanner.unused_imports if not is_unused_module and unused_imports: is_unused_module = True if unused_imports: unused_modules.update( { imp.module.__name__.split(".")[0] # type: ignore for imp in unused_imports if imp.module } ) if namespace.check: show(unused_imports, py_path) session.scanner.clear() if namespace.diff: exists_diff = print_if_exists(session.diff_file(py_path)) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(py_path)).yellow}' [Y/n/q] ? >" ).lower() if action == "q": return elif action == "y" or action == "": namespace.remove = True if namespace.remove: source = session.read(py_path)[0] refactor_source = session.refactor_file(py_path, apply=True) if refactor_source != source: print(f"Refactoring '{Color(str(py_path)).green}'") if not is_unused_module and namespace.check: print( Color( "✨ Congratulations there is no unused import in your project. ✨" ).green ) if namespace.requirements and unused_modules: requirements_path = Path("requirements.txt") if not requirements_path.exists(): return result = "" source = requirements_path.read_text() for index, requirement in enumerate(source.splitlines()): if requirement.split("==")[0] not in unused_modules: result += f"{requirement}\n" else: if namespace.check and requirement: print( f"{Color(requirement).cyan} at " f"{Color(str(requirements_path)).cyan}:{Color(str(index + 1)).cyan}" ) if namespace.diff: exists_diff = print_if_exists( tuple( difflib.unified_diff( source.splitlines(), result.splitlines(), fromfile=str(requirements_path), ) ) ) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(requirements_path)).cyan}' [Y/n] ? >" ).lower() if action == "y" or action == "": namespace.remove = True if namespace.remove: requirements_path.write_text(result) print(f"Refactoring '{Color(str(requirements_path)).cyan}'")
def setUp(self): self.session = Session( include_star_import=self.include_star_import, show_error=self.show_error, )
def setUp(self): self.config_toml = Session(config_file=pyproject).config self.config_cfg = Session(config_file=setup_cfg).config
def test_config_file_none(self): none_config = Session(config_file=None).config self.assertIsNone(none_config)
def setUp(self): self.include = "test|test2|tests.py" self.exclude = "__init__.py|tests/" self.config_toml = Session( config_file=CONFIG_TOML["config_file"]).config self.config_cfg = Session(config_file=CONFIG_CFG["config_file"]).config
def main(argv: Optional[List[str]] = None) -> int: argv = argv if argv is not None else sys.argv[1:] parser = argparse.ArgumentParser( prog="unimport", description=C.DESCRIPTION, epilog="Get rid of all unused imports 🥳", ) exclusive_group = parser.add_mutually_exclusive_group(required=False) parser.add_argument( "sources", default=[Path(".")], nargs="*", help="files and folders to find the unused imports.", action="store", type=Path, ) parser.add_argument( "-c", "--config", default=".", help="read configuration from PATH.", metavar="PATH", action="store", type=Path, ) parser.add_argument( "--include", help="file include pattern.", metavar="include", action="store", default="", type=str, ) parser.add_argument( "--exclude", help="file exclude pattern.", metavar="exclude", action="store", default="", type=str, ) parser.add_argument( "--include-star-import", action="store_true", help="Include star imports during scanning and refactor.", ) parser.add_argument( "--show-error", action="store_true", help="Show or don't show errors captured during static analysis.", ) parser.add_argument( "-d", "--diff", action="store_true", help="Prints a diff of all the changes unimport would make to a file.", ) exclusive_group.add_argument( "-r", "--remove", action="store_true", help="remove unused imports automatically.", ) exclusive_group.add_argument( "-p", "--permission", action="store_true", help="Refactor permission after see diff.", ) parser.add_argument( "--requirements", action="store_true", help= "Include requirements.txt file, You can use it with all other arguments", ) parser.add_argument( "--check", action="store_true", help="Prints which file the unused imports are in.", ) parser.add_argument( "-v", "--version", action="version", version=f"Unimport {C.VERSION}", help="Prints version of unimport", ) namespace = parser.parse_args(argv) namespace.check = namespace.check or not any( [namespace.diff, namespace.remove, namespace.permission]) namespace.diff = namespace.diff or namespace.permission session = Session( config_file=namespace.config, include_star_import=namespace.include_star_import, show_error=namespace.show_error, ) include_list = [] exclude_list = [] if namespace.include: include_list.append(namespace.include) if hasattr(session.config, "include"): include_list.append(session.config.include) # type: ignore if namespace.exclude: exclude_list.append(namespace.exclude) if hasattr(session.config, "exclude"): exclude_list.append(session.config.exclude) # type: ignore include = re.compile("|".join(include_list)).pattern exclude = re.compile("|".join(exclude_list)).pattern unused_modules = set() for source_path in namespace.sources: for py_path in session.list_paths(source_path, include, exclude): session.scanner.scan(source=session.read(py_path)[0]) unused_imports = session.scanner.unused_imports if unused_imports: unused_modules.update({ imp.module.__name__.split(".")[0] # type: ignore for imp in unused_imports if imp.module }) if namespace.check: show(unused_imports, py_path) session.scanner.clear() if namespace.diff: exists_diff = print_if_exists(session.diff_file(py_path)) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(py_path)).yellow}' [Y/n/q] ? >" ).lower() if action == "q": return 1 elif action == "y" or action == "": namespace.remove = True if namespace.remove: source = session.read(py_path)[0] refactor_source = session.refactor_file(py_path, apply=True) if refactor_source != source: print(f"Refactoring '{Color(str(py_path)).green}'") if not unused_modules and namespace.check: print( Color( "✨ Congratulations there is no unused import in your project. ✨" ).green) requirements_path = Path("requirements.txt") if (namespace.requirements and unused_modules and requirements_path.exists()): result = "" source = requirements_path.read_text() for index, requirement in enumerate(source.splitlines()): if requirement.split("==")[0] not in unused_modules: result += f"{requirement}\n" else: if namespace.check and requirement: print( f"{Color(requirement).cyan} at " f"{Color(str(requirements_path)).cyan}:{Color(str(index + 1)).cyan}" ) if namespace.diff: exists_diff = print_if_exists( tuple( difflib.unified_diff( source.splitlines(), result.splitlines(), fromfile=str(requirements_path), ))) if namespace.permission and exists_diff: action = input( f"Apply suggested changes to '{Color(str(requirements_path)).cyan}' [Y/n] ? >" ).lower() if action == "y" or action == "": namespace.remove = True if namespace.remove: requirements_path.write_text(result) print(f"Refactoring '{Color(str(requirements_path)).cyan}'") if unused_modules: return 1 else: return 0