Пример #1
0
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)
Пример #2
0
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,
            """\

            """,
        )
Пример #3
0
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)))
Пример #4
0
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))
Пример #5
0
 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)
Пример #6
0
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()
Пример #7
0
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)
Пример #8
0
 def setUp(self):
     self.session = Session(include_star_import=self.include_star_import)
Пример #9
0
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
Пример #10
0
 def setUp(self):
     self.scanner = Session(
         include_star_import=self.include_star_import).scanner
Пример #11
0
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}'")
Пример #12
0
 def setUp(self):
     self.session = Session(
         include_star_import=self.include_star_import,
         show_error=self.show_error,
     )
Пример #13
0
 def setUp(self):
     self.config_toml = Session(config_file=pyproject).config
     self.config_cfg = Session(config_file=setup_cfg).config
Пример #14
0
 def test_config_file_none(self):
     none_config = Session(config_file=None).config
     self.assertIsNone(none_config)
Пример #15
0
 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
Пример #16
0
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