From cd06660ad354d433fdd968041d0eb29a513d3bc0 Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 14:48:00 +0200 Subject: [PATCH 01/11] Updated docs conf to use pathlib --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a1ee09c..5d0877d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ import codecs -import os import re +from pathlib import Path def read(*parts): @@ -8,8 +8,8 @@ def read(*parts): Build an absolute path from *parts* and and return the contents of the resulting file. Assume UTF-8 encoding. """ - here = os.path.abspath(os.path.dirname(__file__)) - with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: + here = Path(__file__).parent.absolute() + with codecs.open(str(here.joinpath(*parts)), "rb", "utf-8") as f: return f.read() From 257428ca34b67363262b6602588e1e11af975f80 Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 14:48:07 +0200 Subject: [PATCH 02/11] Updated setup.py to use pathlib --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index aeeb025..0a8aa4e 100644 --- a/setup.py +++ b/setup.py @@ -2,13 +2,13 @@ # Copyright 2020 Lynn Root import codecs -import os import re +from pathlib import Path from setuptools import find_packages, setup -HERE = os.path.abspath(os.path.dirname(__file__)) +HERE = Path(__file__).parent.absolute() ##### @@ -23,7 +23,7 @@ def read(*filenames, **kwargs): sep = kwargs.get("sep", "\n") buf = [] for fl in filenames: - with codecs.open(os.path.join(HERE, fl), "rb", encoding) as f: + with codecs.open(HERE / fl, "rb", encoding) as f: buf.append(f.read()) return sep.join(buf) @@ -43,7 +43,7 @@ def find_meta(meta): NAME = "interrogate" PACKAGE_NAME = "interrogate" PACKAGES = find_packages(where="src") -META_PATH = os.path.join("src", PACKAGE_NAME, "__init__.py") +META_PATH = Path("src") / PACKAGE_NAME / "__init__.py" META_FILE = read(META_PATH) KEYWORDS = ["documentation", "coverage", "quality"] From b275221b12bdd1f1ec2d16b3e7e337c9bce5a5ae Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 14:57:39 +0200 Subject: [PATCH 03/11] Made badge_gen use pathlib --- src/interrogate/badge_gen.py | 43 +++++++++++++++++++----------------- tests/unit/test_badge_gen.py | 32 +++++++++++++-------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/interrogate/badge_gen.py b/src/interrogate/badge_gen.py index 8a9479c..7720277 100644 --- a/src/interrogate/badge_gen.py +++ b/src/interrogate/badge_gen.py @@ -5,13 +5,15 @@ """ from __future__ import annotations -import os import sys +from pathlib import Path from importlib import resources -from typing import Union +from typing import TYPE_CHECKING, Union from xml.dom import minidom +if TYPE_CHECKING: + from os import PathLike try: import cairosvg @@ -80,8 +82,8 @@ def save_badge( - badge: str, output: str, output_format: str | None = None -) -> str: + badge: str, output: PathLike[str] | str, output_format: str | None = None +) -> Path: """Save badge to the specified path. .. versionadded:: 1.4.0 new ``output_format`` keyword argument @@ -96,10 +98,10 @@ def save_badge( if output_format is None: output_format = "svg" - if output_format == "svg": - with open(output, "w") as f: - f.write(badge) + output = Path(output) + if output_format == "svg": + output.write_text(badge) return output if cairosvg is None: @@ -110,16 +112,14 @@ def save_badge( # need to write the badge as an svg first in order to convert it to # another format - tmp_output_file = f"{os.path.splitext(output)[0]}.tmp.svg" + tmp_output_file = Path(f"{output.parent / output.stem}.tmp.svg") try: - with open(tmp_output_file, "w") as f: - f.write(badge) - + tmp_output_file.write_text(badge) cairosvg.svg2png(url=tmp_output_file, write_to=output, scale=2) finally: try: - os.remove(tmp_output_file) + tmp_output_file.unlink() except Exception: # pragma: no cover pass @@ -183,7 +183,7 @@ def get_badge(result: float, color: str, style: str | None = None) -> str: return tmpl -def should_generate_badge(output: str, color: str, result: float) -> bool: +def should_generate_badge(output: PathLike[str] | str, color: str, result: float) -> bool: """Detect if existing badge needs updating. This is to help avoid unnecessary newline updates. See @@ -203,14 +203,16 @@ def should_generate_badge(output: str, color: str, result: float) -> bool: :return: Whether or not the badge SVG file should be generated. :rtype: bool """ - if not os.path.exists(output): + output = Path(output) + + if not output.exists(): return True - if not output.endswith(".svg"): + if output.suffix != ".svg": return True try: - badge = minidom.parse(output) + badge = minidom.parse(str(output)) except Exception: # an exception might happen when a file is not an SVG file but has # `.svg` extension (perhaps a png image was generated with the wrong @@ -260,11 +262,11 @@ def get_color(result: float) -> str: def create( - output: str, + output: PathLike[str] | str, result: InterrogateResults, output_format: str | None = None, output_style: str | None = None, -) -> str: +) -> Path: """Create a status badge. The badge file will only be written if it doesn't exist, or if the @@ -290,9 +292,10 @@ def create( """ if output_format is None: output_format = "svg" - if os.path.isdir(output): + output = Path(output) + if output.is_dir(): filename = DEFAULT_FILENAME + "." + output_format - output = os.path.join(output, filename) + output /= filename result_perc = result.perc_covered color = get_color(result_perc) diff --git a/tests/unit/test_badge_gen.py b/tests/unit/test_badge_gen.py index 4ecca83..f6430e6 100644 --- a/tests/unit/test_badge_gen.py +++ b/tests/unit/test_badge_gen.py @@ -1,7 +1,7 @@ # Copyright 2020-2024 Lynn Root """Unit tests for interrogate/badge_gen.py module""" -import os +from pathlib import Path import sys import pytest @@ -9,8 +9,8 @@ from interrogate import badge_gen -HERE = os.path.abspath(os.path.join(os.path.abspath(__file__), os.path.pardir)) -FIXTURES = os.path.join(HERE, "fixtures") +HERE = Path(__file__).parent +FIXTURES = HERE / "fixtures" IS_WINDOWS = sys.platform in ("cygwin", "win32") @@ -18,9 +18,9 @@ @pytest.mark.parametrize( "out_format,out_file,exp_called_with", ( - (None, "fixtures/my_badge.svg", "fixtures/my_badge.svg"), - ("svg", "fixtures/my_badge.svg", "fixtures/my_badge.svg"), - ("png", "fixtures/my_badge.png", "fixtures/my_badge.tmp.svg"), + (None, Path("fixtures/my_badge.svg"), Path("fixtures/my_badge.svg")), + ("svg", Path("fixtures/my_badge.svg"), Path("fixtures/my_badge.svg")), + ("png", Path("fixtures/my_badge.png"), Path("fixtures/my_badge.tmp.svg")), ), ) def test_save_badge( @@ -30,20 +30,19 @@ def test_save_badge( mock_cairosvg = mocker.Mock() monkeypatch.setattr(badge_gen, "cairosvg", mock_cairosvg) - mock_open = mocker.mock_open() - m = mocker.patch("interrogate.badge_gen.open", mock_open) - mock_rm = mocker.patch("interrogate.badge_gen.os.remove", mocker.Mock()) + mock_write_text = mocker.patch.object(Path, "write_text") + mock_unlink = mocker.patch.object(Path, "unlink") badge_contents = "<svg>foo</svg>" actual = badge_gen.save_badge(badge_contents, out_file, out_format) assert out_file == actual - m.assert_called_once_with(exp_called_with, "w") + mock_write_text.assert_called_once_with(badge_contents) if out_format == "png": mock_cairosvg.svg2png.assert_called_once_with( url=exp_called_with, write_to=out_file, scale=2 ) - mock_rm.assert_called_once_with(exp_called_with) + mock_unlink.assert_called_once() @pytest.mark.skipif(not IS_WINDOWS, reason="windows-only tests") @@ -74,7 +73,7 @@ def test_get_badge(): """SVG badge is templated as expected.""" actual = badge_gen.get_badge(99.9, "#4c1") actual = actual.replace("\n", "").replace("\r", "") - expected_fixture = os.path.join(FIXTURES, "default-style", "99.svg") + expected_fixture = FIXTURES / "default-style" / "99.svg" with open(expected_fixture) as f: expected = f.read() expected = expected.replace("\n", "").replace("\r", "") @@ -95,7 +94,7 @@ def test_get_badge(): ) def test_should_generate(fixture, color, result, expected): """Only return True if existing badge needs updating""" - output = os.path.join(FIXTURES, "default-style", fixture) + output = FIXTURES / "default-style" / fixture actual = badge_gen.should_generate_badge(output, color, result) assert actual is expected @@ -105,7 +104,7 @@ def test_should_generate_xml_error(mocker, monkeypatch): mock_minidom_parse = mocker.Mock() mock_minidom_parse.side_effect = Exception("fuuuu") monkeypatch.setattr(badge_gen.minidom, "parse", mock_minidom_parse) - output = os.path.join(FIXTURES, "default-style", "99.svg") + output = FIXTURES / "default-style" / "99.svg" actual = badge_gen.should_generate_badge(output, "#123456", 99.9) assert actual is True @@ -168,7 +167,7 @@ def test_create( tmpdir, ): """Status badges are created according to interrogation results.""" - monkeypatch.setattr(badge_gen.os.path, "isdir", lambda x: is_dir) + monkeypatch.setattr(badge_gen.Path, "is_dir", lambda x: is_dir) output = tmpdir.mkdir("output") if not is_dir: output = output.join("badge.svg") @@ -191,8 +190,7 @@ def test_create( if style is None: style = "default-style" - expected_fixture = os.path.join(style, expected_fixture) - expected_fixture = os.path.join(FIXTURES, expected_fixture) + expected_fixture = FIXTURES / style / expected_fixture with open(expected_fixture, flag) as f: expected_contents = f.read() if out_format is None: From 7cbdd3bb247aa25e4b88a5bb1a8bbdc06b51232b Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 15:27:10 +0200 Subject: [PATCH 04/11] Made cli use pathlib --- src/interrogate/cli.py | 13 ++++++++++--- src/interrogate/coverage.py | 3 ++- tests/functional/test_cli.py | 35 +++++++++++++++-------------------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/interrogate/cli.py b/src/interrogate/cli.py index 86b9084..a00fd7b 100644 --- a/src/interrogate/cli.py +++ b/src/interrogate/cli.py @@ -1,7 +1,7 @@ # Copyright 2020-2024 Lynn Root """CLI entrypoint into `interrogate`.""" -import os +from pathlib import Path import sys from typing import List, Optional, Pattern, Tuple, Union @@ -218,6 +218,13 @@ @click.option( "-o", "--output", + type=click.Path( + exists=False, + file_okay=True, + dir_okay=True, + writable=True, + resolve_path=True, + ), default=None, metavar="FILE", help="Write output to a given FILE. [default: stdout]", @@ -313,7 +320,7 @@ help="Read configuration from `pyproject.toml` or `setup.cfg`.", ) def main( - paths: Optional[List[str]], + paths: Optional[List[Path]], verbose: int, quiet: bool, fail_under: Union[int, float], @@ -389,7 +396,7 @@ def main( ), ) if not paths: - paths = [os.path.abspath(os.getcwd())] + paths = [Path.cwd()] # NOTE: this will need to be fixed if we want to start supporting # --whitelist-regex on filenames. This otherwise assumes you diff --git a/src/interrogate/coverage.py b/src/interrogate/coverage.py index 4c1226b..abb8cbb 100644 --- a/src/interrogate/coverage.py +++ b/src/interrogate/coverage.py @@ -7,6 +7,7 @@ import decimal import fnmatch import os +from pathlib import Path import sys from typing import Final, Iterator @@ -119,7 +120,7 @@ class InterrogateCoverage: def __init__( self, - paths: list[str], + paths: list[Path], conf: config.InterrogateConfig | None = None, excluded: tuple[str] | None = None, extensions: tuple[str] | None = None, diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py index b03a555..4adb7e4 100644 --- a/tests/functional/test_cli.py +++ b/tests/functional/test_cli.py @@ -1,8 +1,8 @@ # Copyright 2020-2024 Lynn Root """Functional tests for the CLI and implicitly interrogate/visit.py.""" -import os import sys +from pathlib import Path import pytest @@ -11,9 +11,9 @@ from interrogate import cli, config -HERE = os.path.abspath(os.path.join(os.path.abspath(__file__), os.path.pardir)) -SAMPLE_DIR = os.path.join(HERE, "sample") -FIXTURES = os.path.join(HERE, "fixtures") +HERE = Path(__file__).parent +SAMPLE_DIR = HERE / "sample" +FIXTURES = HERE / "fixtures" IS_WINDOWS = sys.platform in ("cygwin", "win32") @@ -28,7 +28,7 @@ def runner(monkeypatch): def test_run_no_paths(runner, monkeypatch, tmpdir): """Assume current working directory if no paths are given.""" - monkeypatch.setattr(os, "getcwd", lambda: SAMPLE_DIR) + monkeypatch.setattr(Path, "cwd", lambda: SAMPLE_DIR) result = runner.invoke(cli.main, []) @@ -68,16 +68,16 @@ def test_run_no_paths(runner, monkeypatch, tmpdir): # whitelist regex (["-w", "^get$"], 50.0, 1), # exclude file - (["-e", os.path.join(SAMPLE_DIR, "partial.py")], 62.2, 1), + (["-e", SAMPLE_DIR / "partial.py"], 62.2, 1), # exclude file which doesn't exist - (["-e", os.path.join(SAMPLE_DIR, "does.not.exist")], 51.4, 1), + (["-e", SAMPLE_DIR / "does.not.exist"], 51.4, 1), # fail under (["-f", "40"], 51.4, 0), ), ) def test_run_shortflags(flags, exp_result, exp_exit_code, runner): """Test CLI with single short flags""" - cli_inputs = flags + [SAMPLE_DIR] + cli_inputs = flags + [str(SAMPLE_DIR)] result = runner.invoke(cli.main, cli_inputs) exp_partial_output = f"actual: {exp_result:.1f}%" @@ -102,14 +102,14 @@ def test_run_shortflags(flags, exp_result, exp_exit_code, runner): (["--ignore-regex", "^get$"], 51.4, 1), (["--ext", "pyi"], 63.1, 1), (["--whitelist-regex", "^get$"], 50.0, 1), - (["--exclude", os.path.join(SAMPLE_DIR, "partial.py")], 62.2, 1), + (["--exclude", SAMPLE_DIR / "partial.py"], 62.2, 1), (["--fail-under", "40"], 51.4, 0), (["--style", "google"], 54.1, 1), ), ) def test_run_longflags(flags, exp_result, exp_exit_code, runner): """Test CLI with single long flags""" - cli_inputs = flags + [SAMPLE_DIR] + cli_inputs = flags + [str(SAMPLE_DIR)] result = runner.invoke(cli.main, cli_inputs) exp_partial_output = f"actual: {exp_result:.1f}%" @@ -127,7 +127,7 @@ def test_run_longflags(flags, exp_result, exp_exit_code, runner): ) def test_run_multiple_flags(flags, exp_result, exp_exit_code, runner): """Test CLI with a hodge-podge of flags""" - cli_inputs = flags + [SAMPLE_DIR] + cli_inputs = flags + [str(SAMPLE_DIR)] result = runner.invoke(cli.main, cli_inputs) exp_partial_output = f"actual: {exp_result:.1f}%" @@ -138,11 +138,8 @@ def test_run_multiple_flags(flags, exp_result, exp_exit_code, runner): @pytest.mark.parametrize("quiet", (True, False)) def test_generate_badge(quiet, runner, tmp_path): """Test expected SVG output when creating a status badge.""" - expected_output_path = os.path.join(FIXTURES, "expected_badge.svg") - with open(expected_output_path) as f: - expected_output = f.read() - - expected_output = expected_output.replace("\n", "") + expected_output_path = FIXTURES / "expected_badge.svg" + expected_output = expected_output_path.read_text().replace("\n", "") tmpdir = tmp_path / "testing" tmpdir.mkdir() @@ -152,7 +149,7 @@ def test_generate_badge(quiet, runner, tmp_path): 0, "--generate-badge", str(tmpdir), - SAMPLE_DIR, + str(SAMPLE_DIR), ] if quiet: cli_inputs.append("--quiet") @@ -164,9 +161,7 @@ def test_generate_badge(quiet, runner, tmp_path): else: assert str(expected_path) in result.output - with open(str(expected_path)) as f: - actual_output = f.read() - actual_output = actual_output.replace("\n", "") + actual_output = expected_path.read_text().replace("\n", "") assert expected_output == actual_output From c5ab0e8944f4fd14fa4f5826d0265bf7fe3c115a Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 15:39:44 +0200 Subject: [PATCH 05/11] Made config use pathlib --- src/interrogate/config.py | 22 +++++++++++----------- tests/unit/test_config.py | 14 +++++++------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/interrogate/config.py b/src/interrogate/config.py index 13dc0ac..4a80f88 100644 --- a/src/interrogate/config.py +++ b/src/interrogate/config.py @@ -7,16 +7,17 @@ from __future__ import annotations import configparser -import os -import pathlib import re from collections.abc import Sequence -from typing import Any +from pathlib import Path +from typing import TYPE_CHECKING, Any import attr import click +if TYPE_CHECKING: + from os import PathLike try: import tomllib @@ -86,7 +87,7 @@ def _check_style(self, attribute: str, value: str) -> None: ) -def find_project_root(srcs: Sequence[str]) -> pathlib.Path: +def find_project_root(srcs: Sequence[PathLike[str] | str]) -> Path: """Return a directory containing .git, .hg, or pyproject.toml. That directory can be one of the directories passed in `srcs` or their common parent. @@ -94,9 +95,9 @@ def find_project_root(srcs: Sequence[str]) -> pathlib.Path: project root, the root of the file system is returned. """ if not srcs: - return pathlib.Path("/").resolve() + return Path("/").resolve() - common_base = min(pathlib.Path(src).resolve() for src in srcs) + common_base = min(Path(src).resolve() for src in srcs) if common_base.is_dir(): # Append a fake file so `parents` below returns `common_base_dir`, too. common_base /= "fake-file" @@ -114,7 +115,7 @@ def find_project_root(srcs: Sequence[str]) -> pathlib.Path: return directory -def find_project_config(path_search_start: Sequence[str]) -> str | None: +def find_project_config(path_search_start: Sequence[PathLike[str] | str]) -> str | None: """Find the absolute filepath to a pyproject.toml if it exists.""" project_root = find_project_root(path_search_start) pyproject_toml = project_root / "pyproject.toml" @@ -125,7 +126,7 @@ def find_project_config(path_search_start: Sequence[str]) -> str | None: return str(setup_cfg) if setup_cfg.is_file() else None -def parse_pyproject_toml(path_config: str) -> dict[str, Any]: +def parse_pyproject_toml(path_config: PathLike[str] | str) -> dict[str, Any]: """Parse ``pyproject.toml`` file and return relevant parts for Interrogate. :param str path_config: Path to ``pyproject.toml`` file. @@ -134,8 +135,7 @@ def parse_pyproject_toml(path_config: str) -> dict[str, Any]: :raise OSError: an I/O-related error when opening ``pyproject.toml``. :raise tomllib.TOMLDecodeError: unable to load ``pyproject.toml``. """ - with open(path_config, "rb") as f: - pyproject_toml = tomllib.load(f) + pyproject_toml = tomllib.loads(Path(path_config).read_text()) config = pyproject_toml.get("tool", {}).get("interrogate", {}) return { k.replace("--", "").replace("-", "_"): v for k, v in config.items() @@ -221,7 +221,7 @@ def read_config_file( if not value: paths = ctx.params.get("paths") if not paths: - paths = (os.path.abspath(os.getcwd()),) + paths = (Path.cwd(),) value = find_project_config(paths) if value is None: return None diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index df574e8..2b397da 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -2,7 +2,7 @@ """Unit tests for interrogate/config.py module""" import configparser -import pathlib +from pathlib import Path import click import pytest @@ -34,10 +34,10 @@ def test_find_project_root(srcs, patch_func, expected, monkeypatch): """Return expected directory of project root.""" with monkeypatch.context() as mp: - expected = pathlib.Path(expected) + expected = Path(expected) if patch_func: - mp.setattr(config.pathlib.Path, patch_func, lambda x: True) - mp.setattr(config.pathlib.Path, "resolve", lambda x: x) + mp.setattr(config.Path, patch_func, lambda x: True) + mp.setattr(config.Path, "resolve", lambda x: x) actual = config.find_project_root(srcs) @@ -47,15 +47,15 @@ def test_find_project_root(srcs, patch_func, expected, monkeypatch): @pytest.mark.parametrize( "is_file,expected", ( - (True, str(pathlib.Path("/usr/src/pyproject.toml"))), + (True, str(Path("/usr/src/pyproject.toml"))), (False, None), ), ) def test_find_project_config(is_file, expected, mocker, monkeypatch): """Return absolute path if pyproject.toml or setup.cfg is detected.""" with monkeypatch.context() as mp: - mp.setattr(config.pathlib.Path, "is_file", lambda x: is_file) - mp.setattr(config.pathlib.Path, "resolve", lambda x: x) + mp.setattr(config.Path, "is_file", lambda x: is_file) + mp.setattr(config.Path, "resolve", lambda x: x) actual = config.find_project_config(("/usr/src/app",)) assert expected == actual From a0260632bcd2a8e811fdff4c28256142912b5386 Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 20:24:12 +0200 Subject: [PATCH 06/11] Replaced os.path with pathlib in misc files --- src/interrogate/utils.py | 8 ++++---- src/interrogate/visit.py | 4 ++-- tests/unit/test_badge_gen.py | 25 +++++++++++++------------ tests/unit/test_utils.py | 8 ++++---- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/interrogate/utils.py b/src/interrogate/utils.py index d8cb942..ad1d594 100644 --- a/src/interrogate/utils.py +++ b/src/interrogate/utils.py @@ -6,11 +6,11 @@ import contextlib import functools import os -import pathlib import re import shutil import sys +from pathlib import Path from typing import IO, Any, Final, Iterator, Sequence import colorama @@ -57,7 +57,7 @@ def smart_open( :type fmode: ``str`` or ``None`` """ if filename and filename != "-": - fh = open(filename, fmode) + fh = Path(filename).open(fmode) else: fh = sys.stdout @@ -68,7 +68,7 @@ def smart_open( fh.close() -def get_common_base(files: Sequence[str | pathlib.Path]) -> str: +def get_common_base(files: Sequence[str | Path]) -> str: """Find the common parent base path for a list of files. For example, ``["/usr/src/app", "/usr/src/tests", "/usr/src/app2"]`` @@ -79,7 +79,7 @@ def get_common_base(files: Sequence[str | pathlib.Path]) -> str: :return: Common parent path. :rtype: str """ - commonbase = pathlib.Path(os.path.commonprefix(files)) + commonbase = Path(os.path.commonprefix(files)) # commonprefix may return an invalid path, e.g. for "/usr/foobar" # and "/usr/foobaz", it will return "/usr/fooba", so we'll need to # find its parent directory if that's the case. diff --git a/src/interrogate/visit.py b/src/interrogate/visit.py index e9aa906..e9e4e3d 100644 --- a/src/interrogate/visit.py +++ b/src/interrogate/visit.py @@ -3,8 +3,8 @@ from __future__ import annotations import ast -import os +from pathlib import Path from typing import Union import attr @@ -71,7 +71,7 @@ def _has_doc(node: DocumentableNode) -> bool: def _visit_helper(self, node: DocumentableNode) -> None: """Recursively visit AST node for docstrings.""" if not hasattr(node, "name"): - node_name = os.path.basename(self.filename) + node_name = Path(self.filename).name else: node_name = node.name diff --git a/tests/unit/test_badge_gen.py b/tests/unit/test_badge_gen.py index f6430e6..1c05b40 100644 --- a/tests/unit/test_badge_gen.py +++ b/tests/unit/test_badge_gen.py @@ -74,9 +74,7 @@ def test_get_badge(): actual = badge_gen.get_badge(99.9, "#4c1") actual = actual.replace("\n", "").replace("\r", "") expected_fixture = FIXTURES / "default-style" / "99.svg" - with open(expected_fixture) as f: - expected = f.read() - expected = expected.replace("\n", "").replace("\r", "") + expected = expected_fixture.read_text().replace("\n", "").replace("\r", "") assert expected == actual @@ -182,18 +180,21 @@ def test_create( str(output), mock_result, out_format, output_style=style ) - flag = "rb" if out_format == "png" else "r" - with open(actual, flag) as f: - actual_contents = f.read() - if out_format is None: - actual_contents = actual_contents.replace("\n", "") + actual_contents = ( + actual.read_bytes() if out_format == "png" else actual.read_text() + ) + if out_format is None: + actual_contents = actual_contents.replace("\n", "") if style is None: style = "default-style" expected_fixture = FIXTURES / style / expected_fixture - with open(expected_fixture, flag) as f: - expected_contents = f.read() - if out_format is None: - expected_contents = expected_contents.replace("\n", "") + expected_contents = ( + expected_fixture.read_bytes() + if out_format == "png" + else expected_fixture.read_text() + ) + if out_format is None: + expected_contents = expected_contents.replace("\n", "") assert expected_contents == actual_contents diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 68c897b..c121069 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -37,13 +37,13 @@ def test_parse_regex(value, exp): def test_smart_open(filename, mocker): """Handles both opening a file and stdout in the same manner.""" m_open = mocker.mock_open() - mock_open = mocker.patch("interrogate.utils.open", m_open) + mock_open = mocker.patch("interrogate.utils.Path.open", m_open) with utils.smart_open(filename, fmode="r") as act_ret: pass if filename and filename != "-": - mock_open.assert_called_once_with(filename, "r") + mock_open.assert_called_once_with("r") assert act_ret.closed else: mock_open.assert_not_called() @@ -67,7 +67,7 @@ def test_smart_open(filename, mocker): def test_get_common_base(files, side_effect, expected, mocker, monkeypatch): """Return common base of a set of files/directories, if any.""" mock_exists = mocker.Mock(side_effect=side_effect) - monkeypatch.setattr(utils.pathlib.Path, "exists", mock_exists) + monkeypatch.setattr(utils.Path, "exists", mock_exists) actual = utils.get_common_base(files) assert expected == actual @@ -100,7 +100,7 @@ def test_get_common_base_windows( ): """Return common base of a set of files/directories, if any.""" mock_exists = mocker.Mock(side_effect=side_effect) - monkeypatch.setattr(utils.pathlib.Path, "exists", mock_exists) + monkeypatch.setattr(utils.Path, "exists", mock_exists) actual = utils.get_common_base(files) assert expected == actual From 3b3c7640718aa379d7759450ae6f9b02c4f136ba Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 22:29:38 +0200 Subject: [PATCH 07/11] Made most of coverage use pathlib --- src/interrogate/coverage.py | 49 +++++++++++----------- tests/functional/test_coverage.py | 70 ++++++++++++++----------------- 2 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/interrogate/coverage.py b/src/interrogate/coverage.py index abb8cbb..fb2489d 100644 --- a/src/interrogate/coverage.py +++ b/src/interrogate/coverage.py @@ -5,12 +5,11 @@ import ast import decimal -import fnmatch import os from pathlib import Path -import sys -from typing import Final, Iterator +import sys +from typing import TYPE_CHECKING, Final, Iterator import attr import click @@ -18,6 +17,9 @@ from interrogate import config, utils, visit +if TYPE_CHECKING: + from os import PathLike + tabulate.PRESERVE_WHITESPACE = True @@ -120,16 +122,16 @@ class InterrogateCoverage: def __init__( self, - paths: list[Path], + paths: list[PathLike[str] | str], conf: config.InterrogateConfig | None = None, excluded: tuple[str] | None = None, - extensions: tuple[str] | None = None, + extensions: tuple[PathLike[str] | str] | None = None, ): - self.paths = paths - self.extensions = set(extensions or set()) + self.paths = list(map(Path, paths)) + self.extensions = {f".{ext.lstrip('.')}" for ext in extensions or ()} self.extensions.add(".py") self.config = conf or config.InterrogateConfig() - self.excluded = excluded or () + self.excluded = tuple(map(str, excluded or ())) self.common_base = "" self._add_common_exclude() self.skipped_file_count = 0 @@ -139,30 +141,29 @@ def _add_common_exclude(self) -> None: """Ignore common directories by default""" for path in self.paths: self.excluded = self.excluded + tuple( # type: ignore - os.path.join(path, i) for i in self.COMMON_EXCLUDE + str(path / i) for i in self.COMMON_EXCLUDE ) def _filter_files(self, files: list[str]) -> Iterator[str]: """Filter files that are explicitly excluded.""" for f in files: - has_valid_ext = any([f.endswith(ext) for ext in self.extensions]) + has_valid_ext = any([f.suffix == ext for ext in self.extensions]) if not has_valid_ext: continue if self.config.ignore_init_module: - basename = os.path.basename(f) - if basename == "__init__.py": + if f.name == "__init__.py": continue - if any(fnmatch.fnmatch(f, exc + "*") for exc in self.excluded): + if any(f.match(exc + "*") for exc in self.excluded): continue - yield f + yield str(f) def get_filenames_from_paths(self) -> list[str]: """Find all files to measure for docstring coverage.""" filenames = [] for path in self.paths: - if os.path.isfile(path): + if path.is_file(): has_valid_ext = any( - [path.endswith(ext) for ext in self.VALID_EXT] + [path.suffix == ext for ext in self.VALID_EXT] ) if not has_valid_ext: msg = ( @@ -172,14 +173,14 @@ def get_filenames_from_paths(self) -> list[str]: ) click.echo(msg, err=True) return sys.exit(1) - filenames.append(path) + filenames.append(str(path)) continue for root, dirs, fs in os.walk(path): - full_paths = [os.path.join(root, f) for f in fs] + full_paths = [Path(root) / f for f in fs] filenames.extend(self._filter_files(full_paths)) if not filenames: - p = ", ".join(self.paths) + p = ", ".join(map(str, self.paths)) msg = ( f"E: No Python or Python-like files found to interrogate in " f"'{p}'." @@ -485,12 +486,12 @@ def _sort_results(results: InterrogateResults) -> InterrogateResults: def _get_header_base(self) -> str: """Get common base directory for header of verbose output.""" - base = self.common_base - if os.path.isfile(base): - base = os.path.dirname(base) + base = Path(self.common_base) + if base.is_file(): + base = base.parent if sys.platform in ("cygwin", "win32"): # pragma: no cover - return base + "\\" - return base + "/" + return f"{base}\\" + return f"{base}/" def _print_omitted_file_count(self, results: InterrogateResults) -> None: """Print # of files omitted due to 100% coverage and --omit-covered. diff --git a/tests/functional/test_coverage.py b/tests/functional/test_coverage.py index 4401828..377df87 100644 --- a/tests/functional/test_coverage.py +++ b/tests/functional/test_coverage.py @@ -2,6 +2,7 @@ """Functional tests for interrogate/coverage.py.""" import os +from pathlib import Path import sys import pytest @@ -9,9 +10,9 @@ from interrogate import config, coverage -HERE = os.path.abspath(os.path.join(os.path.abspath(__file__), os.path.pardir)) -SAMPLE_DIR = os.path.join(HERE, "sample") -FIXTURES = os.path.join(HERE, "fixtures") +HERE = Path(__file__).parent +SAMPLE_DIR = HERE / "sample" +FIXTURES = HERE / "fixtures" IS_WINDOWS = sys.platform in ("cygwin", "win32") @@ -26,14 +27,14 @@ def patch_term_width(monkeypatch): ( ( [ - os.path.join(SAMPLE_DIR, "empty.py"), + SAMPLE_DIR / "empty.py", ], {}, (1, 0, 1, "0.0"), ), ( [ - os.path.join(SAMPLE_DIR, "empty.py"), + SAMPLE_DIR / "empty.py", ], {"ignore_module": True}, (0, 0, 0, "100.0"), @@ -45,31 +46,31 @@ def patch_term_width(monkeypatch): {}, (74, 38, 36, "51.4"), ), - ([os.path.join(SAMPLE_DIR, "partial.py")], {}, (29, 10, 19, "34.5")), + ([SAMPLE_DIR / "partial.py"], {}, (29, 10, 19, "34.5")), ( [ - os.path.join(SAMPLE_DIR, "full.py"), + SAMPLE_DIR / "full.py", ], {"ignore_nested_functions": True}, (28, 26, 2, "92.9"), ), ( [ - os.path.join(SAMPLE_DIR, "partial.py"), + SAMPLE_DIR / "partial.py", ], {"ignore_nested_functions": True}, (27, 9, 18, "33.3"), ), ( [ - os.path.join(SAMPLE_DIR, "full.py"), + SAMPLE_DIR / "full.py", ], {"ignore_overloaded_functions": True}, (25, 23, 2, "92.0"), ), ( [ - os.path.join(SAMPLE_DIR, "partial.py"), + SAMPLE_DIR / "partial.py", ], {"ignore_overloaded_functions": True}, (25, 10, 15, "40.0"), @@ -91,7 +92,7 @@ def test_coverage_simple(paths, conf, exp_results, mocker): def test_coverage_errors(capsys): """Exit when no Python files are found.""" - path = os.path.join(SAMPLE_DIR, "ignoreme.txt") + path = SAMPLE_DIR / "ignoreme.txt" interrogate_coverage = coverage.InterrogateCoverage(paths=[path]) with pytest.raises(SystemExit, match="1"): @@ -132,11 +133,10 @@ def test_print_results(level, exp_fixture_file, capsys, monkeypatch): ) captured = capsys.readouterr() - expected_fixture = os.path.join(FIXTURES, exp_fixture_file) + expected_fixture = FIXTURES / exp_fixture_file if IS_WINDOWS: - expected_fixture = os.path.join(FIXTURES, "windows", exp_fixture_file) - with open(expected_fixture) as f: - expected_out = f.read() + expected_fixture = FIXTURES / "windows" / exp_fixture_file + expected_out = expected_fixture.read_text() assert expected_out in captured.out assert "omitted due to complete coverage" not in captured.out @@ -166,11 +166,10 @@ def test_print_results_omit_covered( ) captured = capsys.readouterr() - expected_fixture = os.path.join(FIXTURES, exp_fixture_file) + expected_fixture = FIXTURES / exp_fixture_file if IS_WINDOWS: - expected_fixture = os.path.join(FIXTURES, "windows", exp_fixture_file) - with open(expected_fixture) as f: - expected_out = f.read() + expected_fixture = FIXTURES / "windows" / exp_fixture_file + expected_out = expected_fixture.read_text() assert expected_out in captured.out @@ -180,7 +179,7 @@ def test_print_results_omit_none(level, capsys, monkeypatch): """Output of test results by verbosity, no fully covered files.""" interrogate_config = config.InterrogateConfig(omit_covered_files=True) interrogate_coverage = coverage.InterrogateCoverage( - paths=[os.path.join(SAMPLE_DIR, "child_sample")], + paths=[SAMPLE_DIR / "child_sample"], conf=interrogate_config, ) results = interrogate_coverage.get_coverage() @@ -198,7 +197,7 @@ def test_print_results_omit_all_summary(capsys, monkeypatch): omit_covered_files=True, docstring_style="google" ) interrogate_coverage = coverage.InterrogateCoverage( - paths=[os.path.join(SAMPLE_DIR, "full.py")], conf=interrogate_config + paths=[SAMPLE_DIR / "full.py"], conf=interrogate_config ) results = interrogate_coverage.get_coverage() interrogate_coverage.print_results( @@ -207,11 +206,10 @@ def test_print_results_omit_all_summary(capsys, monkeypatch): captured = capsys.readouterr() exp_fixture_file = "expected_summary_skip_covered_all.txt" - expected_fixture = os.path.join(FIXTURES, exp_fixture_file) + expected_fixture = FIXTURES / exp_fixture_file if IS_WINDOWS: - expected_fixture = os.path.join(FIXTURES, "windows", exp_fixture_file) - with open(expected_fixture) as f: - expected_out = f.read() + expected_fixture = FIXTURES / "windows" / exp_fixture_file + expected_out = expected_fixture.read_text() assert expected_out in captured.out @@ -222,7 +220,7 @@ def test_print_results_omit_all_detailed(capsys, monkeypatch): omit_covered_files=True, docstring_style="google" ) interrogate_coverage = coverage.InterrogateCoverage( - paths=[os.path.join(SAMPLE_DIR, "full.py")], conf=interrogate_config + paths=[SAMPLE_DIR / "full.py"], conf=interrogate_config ) results = interrogate_coverage.get_coverage() interrogate_coverage.print_results( @@ -260,18 +258,17 @@ def test_print_results_ignore_module( ) captured = capsys.readouterr() - expected_fixture = os.path.join(FIXTURES, exp_fixture_file) + expected_fixture = FIXTURES / exp_fixture_file if IS_WINDOWS: - expected_fixture = os.path.join(FIXTURES, "windows", exp_fixture_file) - with open(expected_fixture) as f: - expected_out = f.read() + expected_fixture = FIXTURES / "windows" / exp_fixture_file + expected_out = expected_fixture.read_text() assert expected_out in captured.out def test_print_results_single_file(capsys, monkeypatch): """Results for a single file should still list the filename.""" - single_file = os.path.join(SAMPLE_DIR, "full.py") + single_file = SAMPLE_DIR / "full.py" conf = {"docstring_style": "google"} conf = config.InterrogateConfig(**conf) interrogate_coverage = coverage.InterrogateCoverage( @@ -283,17 +280,14 @@ def test_print_results_single_file(capsys, monkeypatch): ) captured = capsys.readouterr() - expected_fixture = os.path.join( - FIXTURES, "expected_detailed_single_file.txt" - ) + expected_fixture = FIXTURES / "expected_detailed_single_file.txt" if IS_WINDOWS: - expected_fixture = os.path.join( - FIXTURES, "windows", "expected_detailed_single_file.txt" + expected_fixture = ( + FIXTURES / "windows" / "expected_detailed_single_file.txt" ) - with open(expected_fixture) as f: - expected_out = f.read() + expected_out = expected_fixture.read_text() assert expected_out in captured.out # I don't want to deal with path mocking out just to get tests to run From 98b9501d2fe00d01e913a4347ad6e61a2353163c Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 23:42:43 +0200 Subject: [PATCH 08/11] Made remaining coverage code use pathlib --- src/interrogate/coverage.py | 72 ++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/interrogate/coverage.py b/src/interrogate/coverage.py index fb2489d..f09f35b 100644 --- a/src/interrogate/coverage.py +++ b/src/interrogate/coverage.py @@ -64,7 +64,7 @@ class InterrogateFileResult(BaseInterrogateResult): :param list[visit.CovNode] nodes: visited AST nodes. """ - filename: str = attr.ib(default=None) + filename: Path = attr.ib(default=None) ignore_module: bool = attr.ib(default=False) nodes: list[visit.CovNode] = attr.ib(repr=False, default=None) @@ -125,7 +125,7 @@ def __init__( paths: list[PathLike[str] | str], conf: config.InterrogateConfig | None = None, excluded: tuple[str] | None = None, - extensions: tuple[PathLike[str] | str] | None = None, + extensions: tuple[str] | None = None, ): self.paths = list(map(Path, paths)) self.extensions = {f".{ext.lstrip('.')}" for ext in extensions or ()} @@ -144,7 +144,7 @@ def _add_common_exclude(self) -> None: str(path / i) for i in self.COMMON_EXCLUDE ) - def _filter_files(self, files: list[str]) -> Iterator[str]: + def _filter_files(self, files: list[Path]) -> Iterator[Path]: """Filter files that are explicitly excluded.""" for f in files: has_valid_ext = any([f.suffix == ext for ext in self.extensions]) @@ -155,9 +155,9 @@ def _filter_files(self, files: list[str]) -> Iterator[str]: continue if any(f.match(exc + "*") for exc in self.excluded): continue - yield str(f) + yield f - def get_filenames_from_paths(self) -> list[str]: + def get_filenames_from_paths(self) -> list[Path]: """Find all files to measure for docstring coverage.""" filenames = [] for path in self.paths: @@ -173,7 +173,7 @@ def get_filenames_from_paths(self) -> list[str]: ) click.echo(msg, err=True) return sys.exit(1) - filenames.append(str(path)) + filenames.append(path) continue for root, dirs, fs in os.walk(path): full_paths = [Path(root) / f for f in fs] @@ -246,14 +246,13 @@ def _set_google_style(self, nodes: list[visit.CovNode]) -> None: setattr(node.parent, "covered", True) def _get_file_coverage( - self, filename: str + self, filename: Path ) -> InterrogateFileResult | None: """Get coverage results for a particular file.""" - with open(filename, encoding="utf-8") as f: - source_tree = f.read() + source_tree = filename.read_text(encoding="utf-8") parsed_tree = ast.parse(source_tree) - visitor = visit.CoverageVisitor(filename=filename, config=self.config) + visitor = visit.CoverageVisitor(filename=str(filename), config=self.config) visitor.visit(parsed_tree) filtered_nodes = self._filter_nodes(visitor.nodes) @@ -278,7 +277,7 @@ def _get_file_coverage( results.combine() return results - def _get_coverage(self, filenames: list[str]) -> InterrogateResults: + def _get_coverage(self, filenames: list[Path]) -> InterrogateResults: """Get coverage results.""" results = InterrogateResults() file_results = [] @@ -304,27 +303,27 @@ def get_coverage(self) -> InterrogateResults: filenames = self.get_filenames_from_paths() return self._get_coverage(filenames) - def _get_filename(self, filename: str) -> str: + def _get_filename(self, filename: Path) -> str: """Get filename for output information. If only one file is being interrogated, then ``self.common_base`` and ``filename`` will be the same. Therefore, take the file - ``os.path.basename`` as the return ``filename``. + ``Path.name`` as the return ``filename``. """ - if filename == self.common_base: - return os.path.basename(filename) - return filename[len(self.common_base) + 1 :] + if str(filename) == self.common_base: + return filename.name + return str(filename)[len(self.common_base) + 1 :] def _get_detailed_row( - self, node: visit.CovNode, filename: str + self, node: visit.CovNode, filename: Path ) -> list[str]: """Generate a row of data for the detailed view.""" - filename = self._get_filename(filename) + filename_ = self._get_filename(filename) if node.node_type == "Module": if self.config.ignore_module: - return [filename, ""] - name = f"{filename} (module)" + return [filename_, ""] + name = f"{filename_} (module)" else: name = node.path.split(":")[-1] name = f"{name} (L{node.lineno})" @@ -461,21 +460,36 @@ def _sort_results(results: InterrogateResults) -> InterrogateResults: all_filenames_map = {r.filename: r for r in results.file_results} all_dirs = sorted( { - os.path.dirname(r.filename) + r.filename.parent for r in results.file_results - if os.path.dirname(r.filename) != "" + if r.filename.parent } ) - sorted_results = [] + # cnt = 0 + + # def log(obj: object) -> None: + # nonlocal cnt + # cnt += 1 + # pth = Path.home() / "dev/download/interrogate/log.txt" + # with pth.open("a") as f: + # f.write(f"{cnt} {obj!r}\n") + + # log(all_filenames_map) # 1 + # log(all_filenames_map_paths) # 2 + # log(all_dirs) # 3 + # log(all_dirs_paths) # 4 + + sorted_results: list[Path] = [] while all_dirs: current_dir = all_dirs.pop(0) - files = [] - for p in os.listdir(current_dir): - path = os.path.join(current_dir, p) - if path in all_filenames_map.keys(): - files.append(path) - files = sorted(files) + files = sorted( + [ + path + for path in current_dir.iterdir() + if path in all_filenames_map + ] + ) sorted_results.extend(files) sorted_res = [] From 90c9dd8949f617dcc5aedc89bf9aff9159cf8c78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 21:49:10 +0000 Subject: [PATCH 09/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/conf.py | 1 + setup.py | 1 + src/interrogate/badge_gen.py | 7 +++++-- src/interrogate/cli.py | 2 +- src/interrogate/config.py | 5 ++++- src/interrogate/coverage.py | 11 +++++++---- tests/functional/test_cli.py | 1 + tests/functional/test_coverage.py | 3 ++- tests/unit/test_badge_gen.py | 9 +++++++-- tests/unit/test_config.py | 1 + 10 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5d0877d..04f8999 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,6 @@ import codecs import re + from pathlib import Path diff --git a/setup.py b/setup.py index 0a8aa4e..fcad016 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import codecs import re + from pathlib import Path from setuptools import find_packages, setup diff --git a/src/interrogate/badge_gen.py b/src/interrogate/badge_gen.py index 7720277..cecb91a 100644 --- a/src/interrogate/badge_gen.py +++ b/src/interrogate/badge_gen.py @@ -6,12 +6,13 @@ from __future__ import annotations import sys -from pathlib import Path from importlib import resources +from pathlib import Path from typing import TYPE_CHECKING, Union from xml.dom import minidom + if TYPE_CHECKING: from os import PathLike @@ -183,7 +184,9 @@ def get_badge(result: float, color: str, style: str | None = None) -> str: return tmpl -def should_generate_badge(output: PathLike[str] | str, color: str, result: float) -> bool: +def should_generate_badge( + output: PathLike[str] | str, color: str, result: float +) -> bool: """Detect if existing badge needs updating. This is to help avoid unnecessary newline updates. See diff --git a/src/interrogate/cli.py b/src/interrogate/cli.py index a00fd7b..efffcd0 100644 --- a/src/interrogate/cli.py +++ b/src/interrogate/cli.py @@ -1,9 +1,9 @@ # Copyright 2020-2024 Lynn Root """CLI entrypoint into `interrogate`.""" -from pathlib import Path import sys +from pathlib import Path from typing import List, Optional, Pattern, Tuple, Union import click diff --git a/src/interrogate/config.py b/src/interrogate/config.py index 4a80f88..89f8b76 100644 --- a/src/interrogate/config.py +++ b/src/interrogate/config.py @@ -16,6 +16,7 @@ import attr import click + if TYPE_CHECKING: from os import PathLike @@ -115,7 +116,9 @@ def find_project_root(srcs: Sequence[PathLike[str] | str]) -> Path: return directory -def find_project_config(path_search_start: Sequence[PathLike[str] | str]) -> str | None: +def find_project_config( + path_search_start: Sequence[PathLike[str] | str], +) -> str | None: """Find the absolute filepath to a pyproject.toml if it exists.""" project_root = find_project_root(path_search_start) pyproject_toml = project_root / "pyproject.toml" diff --git a/src/interrogate/coverage.py b/src/interrogate/coverage.py index f09f35b..7d1cd44 100644 --- a/src/interrogate/coverage.py +++ b/src/interrogate/coverage.py @@ -6,9 +6,9 @@ import ast import decimal import os -from pathlib import Path - import sys + +from pathlib import Path from typing import TYPE_CHECKING, Final, Iterator import attr @@ -17,6 +17,7 @@ from interrogate import config, utils, visit + if TYPE_CHECKING: from os import PathLike @@ -252,7 +253,9 @@ def _get_file_coverage( source_tree = filename.read_text(encoding="utf-8") parsed_tree = ast.parse(source_tree) - visitor = visit.CoverageVisitor(filename=str(filename), config=self.config) + visitor = visit.CoverageVisitor( + filename=str(filename), config=self.config + ) visitor.visit(parsed_tree) filtered_nodes = self._filter_nodes(visitor.nodes) @@ -474,7 +477,7 @@ def _sort_results(results: InterrogateResults) -> InterrogateResults: # pth = Path.home() / "dev/download/interrogate/log.txt" # with pth.open("a") as f: # f.write(f"{cnt} {obj!r}\n") - + # log(all_filenames_map) # 1 # log(all_filenames_map_paths) # 2 # log(all_dirs) # 3 diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py index 4adb7e4..3864479 100644 --- a/tests/functional/test_cli.py +++ b/tests/functional/test_cli.py @@ -2,6 +2,7 @@ """Functional tests for the CLI and implicitly interrogate/visit.py.""" import sys + from pathlib import Path import pytest diff --git a/tests/functional/test_coverage.py b/tests/functional/test_coverage.py index 377df87..0a7b2ce 100644 --- a/tests/functional/test_coverage.py +++ b/tests/functional/test_coverage.py @@ -2,9 +2,10 @@ """Functional tests for interrogate/coverage.py.""" import os -from pathlib import Path import sys +from pathlib import Path + import pytest from interrogate import config, coverage diff --git a/tests/unit/test_badge_gen.py b/tests/unit/test_badge_gen.py index 1c05b40..3fbe26f 100644 --- a/tests/unit/test_badge_gen.py +++ b/tests/unit/test_badge_gen.py @@ -1,9 +1,10 @@ # Copyright 2020-2024 Lynn Root """Unit tests for interrogate/badge_gen.py module""" -from pathlib import Path import sys +from pathlib import Path + import pytest from interrogate import badge_gen @@ -20,7 +21,11 @@ ( (None, Path("fixtures/my_badge.svg"), Path("fixtures/my_badge.svg")), ("svg", Path("fixtures/my_badge.svg"), Path("fixtures/my_badge.svg")), - ("png", Path("fixtures/my_badge.png"), Path("fixtures/my_badge.tmp.svg")), + ( + "png", + Path("fixtures/my_badge.png"), + Path("fixtures/my_badge.tmp.svg"), + ), ), ) def test_save_badge( diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 2b397da..474c63d 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -2,6 +2,7 @@ """Unit tests for interrogate/config.py module""" import configparser + from pathlib import Path import click From ab493c897dd139559867db6029d0f7eb25ae2d34 Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Wed, 22 May 2024 23:54:20 +0200 Subject: [PATCH 10/11] Removed unused import --- tests/functional/test_coverage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional/test_coverage.py b/tests/functional/test_coverage.py index 0a7b2ce..fa4891e 100644 --- a/tests/functional/test_coverage.py +++ b/tests/functional/test_coverage.py @@ -1,7 +1,6 @@ # Copyright 2020-2024 Lynn Root """Functional tests for interrogate/coverage.py.""" -import os import sys from pathlib import Path From 6c6d0206f2fdd815b82f182bcbb1ad74d8836a10 Mon Sep 17 00:00:00 2001 From: trag1c <trag1cdev@yahoo.com> Date: Thu, 23 May 2024 00:01:56 +0200 Subject: [PATCH 11/11] Made mypy happy (hopefully) --- src/interrogate/config.py | 2 +- src/interrogate/coverage.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/interrogate/config.py b/src/interrogate/config.py index 89f8b76..582aec0 100644 --- a/src/interrogate/config.py +++ b/src/interrogate/config.py @@ -23,7 +23,7 @@ try: import tomllib except ImportError: - import tomli as tomllib # type: ignore + import tomli as tomllib # TODO: idea: break out InterrogateConfig into two classes: one for diff --git a/src/interrogate/coverage.py b/src/interrogate/coverage.py index 7d1cd44..45434bf 100644 --- a/src/interrogate/coverage.py +++ b/src/interrogate/coverage.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: + from collections.abc import Iterable from os import PathLike @@ -123,7 +124,7 @@ class InterrogateCoverage: def __init__( self, - paths: list[PathLike[str] | str], + paths: Iterable[PathLike[str] | str], conf: config.InterrogateConfig | None = None, excluded: tuple[str] | None = None, extensions: tuple[str] | None = None, @@ -141,7 +142,7 @@ def __init__( def _add_common_exclude(self) -> None: """Ignore common directories by default""" for path in self.paths: - self.excluded = self.excluded + tuple( # type: ignore + self.excluded = self.excluded + tuple( str(path / i) for i in self.COMMON_EXCLUDE )