Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replaced os.path with pathlib #182

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
7 changes: 4 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import codecs
import os
import re

from pathlib import Path


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()


Expand Down
9 changes: 5 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
# 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()


#####
Expand All @@ -23,7 +24,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)

Expand All @@ -43,7 +44,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"]
Expand Down
46 changes: 26 additions & 20 deletions src/interrogate/badge_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
"""
from __future__ import annotations

import os
import sys

from importlib import resources
from typing import Union
from pathlib import Path
from typing import TYPE_CHECKING, Union
from xml.dom import minidom


if TYPE_CHECKING:
from os import PathLike

try:
import cairosvg
except ImportError: # pragma: no cover
Expand Down Expand Up @@ -80,8 +83,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
Expand All @@ -96,10 +99,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:
Expand All @@ -110,16 +113,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

Expand Down Expand Up @@ -183,7 +184,9 @@ 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
Expand All @@ -203,14 +206,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
Expand Down Expand Up @@ -260,11 +265,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
Expand All @@ -290,9 +295,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)
Expand Down
13 changes: 10 additions & 3 deletions src/interrogate/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Copyright 2020-2024 Lynn Root
"""CLI entrypoint into `interrogate`."""

import os
import sys

from pathlib import Path
from typing import List, Optional, Pattern, Tuple, Union

import click
Expand Down Expand Up @@ -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]",
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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
Expand Down
27 changes: 15 additions & 12 deletions src/interrogate/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@
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
except ImportError:
import tomli as tomllib # type: ignore
import tomli as tomllib


# TODO: idea: break out InterrogateConfig into two classes: one for
Expand Down Expand Up @@ -86,17 +88,17 @@ 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.
If no directory in the tree contains a marker that would specify it's the
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"
Expand All @@ -114,7 +116,9 @@ 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"
Expand All @@ -125,7 +129,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.
Expand All @@ -134,8 +138,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()
Expand Down Expand Up @@ -221,7 +224,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
Expand Down
Loading