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
             )