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

Add type hints (finally) #178

Merged
merged 12 commits into from
May 20, 2024
13 changes: 12 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
branches: [ master ]
# Allow rebuilds via API.
repository_dispatch:
types: rebuild
types: [rebuild]

env:
FORCE_COLOR: "1"
Expand Down Expand Up @@ -51,6 +51,14 @@ jobs:
- name: Run tox targets for ${{ matrix.python-version }}
run: python -Im tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)

- name: Run mypy
run: python -Im tox run -e mypy
if: matrix.python-version == '3.11'

- name: Run mypy
run: python -Im tox run -e mypy
if: matrix.python-version == '3.11'

- name: Check MANIFEST.in
run: python -Im tox run -e manifest
if: matrix.python-version == '3.11'
Expand Down Expand Up @@ -127,6 +135,9 @@ jobs:
- name: "Create a badge"
run: interrogate --config pyproject.toml --generate-badge . src tests
if: runner.os != 'Windows'
env:
# TODO: set for only macos
DYLD_FALLBACK_LIBRARY_PATH: "/opt/homebrew/lib"

docs:
name: Check docs
Expand Down
24 changes: 22 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,31 @@ To generate a **PNG file** instead, install ``interrogate`` with the extras ``[p
**NOTICE:** Additional system libraries/tools may be required in order to generate a PNG file of the coverage badge:

* on Windows, install Visual C++ compiler for Cairo;
* on macOS, install ``cairo`` and ``libffi`` (with Homebrew for example);
* on macOS, install ``cairo`` and ``libffi`` (with Homebrew for example - `see note below <#macos-and-cairo>`_);
* on Linux, install the ``cairo``, ``python3-dev`` and ``libffi-dev`` packages (names may vary depending on distribution).

Refer to the ``cairosvg`` `documentation <https://cairosvg.org/documentation/>`_ for more information.

MacOS and Cairo
^^^^^^^^^^^^^^^

If you get an error when trying to generate a badge like so:

.. code-block:: console

OSError: no library called "cairo-2" was found
no library called "cairo" was found
no library called "libcairo-2" was found


Then first try:

.. code-block:: console

export DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib

And rerun the command.

Usage
=====

Expand Down Expand Up @@ -373,7 +393,7 @@ Configure within your ``pyproject.toml`` (``interrogate`` will automatically det
ignore-regex = ["^get$", "^mock_.*", ".*BaseClass.*"]
ext = []
# possible values: sphinx (default), google
style = sphinx
style = "sphinx"
# possible values: 0 (minimal output), 1 (-v), 2 (-vv)
verbose = 0
quiet = false
Expand Down
3 changes: 3 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif

# Internal variables
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) .

.PHONY: help clean html livehtml linkcheck

help:
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

1.8.0 (UNRELEASED)
------------------

Added
^^^^^

* Finally added type hints

.. short-log

1.7.0 (2024-04-07)
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ fail_under = 95
line-length = 79

[tool.isort]
atomic=true
force_single_line=true
lines_after_imports=2
lines_between_types=1
use_parentheses=true
known_first_party="interrogate"
known_third_party=["attr", "click", "py", "pytest", "setuptools", "tabulate"]

Expand All @@ -53,3 +50,8 @@ quiet = false
whitelist-regex = []
ignore-regex = []
color = true

[tool.mypy]
strict = true
pretty = true
ignore_missing_imports = true
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import os
import re

from setuptools import find_packages
from setuptools import setup
from setuptools import find_packages, setup


HERE = os.path.abspath(os.path.dirname(__file__))
Expand Down Expand Up @@ -80,11 +79,13 @@ def find_meta(meta):
"png": ["cairosvg"],
"docs": ["sphinx", "sphinx-autobuild"],
"tests": ["pytest", "pytest-cov", "pytest-mock", "coverage[toml]"],
"typing": ["mypy", "types-tabulate"],
}
EXTRAS_REQUIRE["dev"] = (
EXTRAS_REQUIRE["png"]
+ EXTRAS_REQUIRE["docs"]
+ EXTRAS_REQUIRE["tests"]
+ EXTRAS_REQUIRE["typing"]
+ ["wheel", "pre-commit"]
)
URL = find_meta("uri")
Expand Down
2 changes: 1 addition & 1 deletion src/interrogate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2020-2021 Lynn Root
# Copyright 2020-2024 Lynn Root
"""Explain yourself! Interrogate a codebase for docstring coverage."""
__author__ = "Lynn Root"
__version__ = "1.7.0"
Expand Down
2 changes: 1 addition & 1 deletion src/interrogate/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2020 Lynn Root
# Copyright 2020-2024 Lynn Root
"""interrogate entrypoint"""

from interrogate import cli
Expand Down
60 changes: 41 additions & 19 deletions src/interrogate/badge_gen.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Copyright 2020 Lynn Root
# Copyright 2020-2024 Lynn Root
"""Module for generating an SVG badge.

Inspired by `coverage-badge <https://github.com/dbrgn/coverage-badge>`_.
"""
from __future__ import annotations

import os
import sys

from importlib import resources
from typing import Union
from xml.dom import minidom


Expand All @@ -16,9 +18,13 @@
except ImportError: # pragma: no cover
cairosvg = None

from interrogate.coverage import InterrogateResults

DEFAULT_FILENAME = "interrogate_badge"
COLORS = {

NumberType = Union[int, float]

DEFAULT_FILENAME: str = "interrogate_badge"
COLORS: dict[str, str] = {
"brightgreen": "#4c1",
"green": "#97CA00",
"yellowgreen": "#a4a61d",
Expand All @@ -28,19 +34,21 @@
"lightgrey": "#9f9f9f",
}

COLOR_RANGES = [
COLOR_RANGES: list[tuple[int, str]] = [
(95, "brightgreen"),
(90, "green"),
(75, "yellowgreen"),
(60, "yellow"),
(40, "orange"),
(0, "red"),
]
SUPPORTED_OUTPUT_FORMATS = ["svg", "png"]
SUPPORTED_OUTPUT_FORMATS: list[str] = ["svg", "png"]
# depending on the character length of the result (e.g. 100, 99.9, 9.9)
# a few values in the svg template need to adjust so it's readable.
# Tuple of values: (svg_width, rect_width, text_x, text_length)
SVG_WIDTH_VALUES = {
SVG_WIDTH_VALUES: dict[
str, dict[str, tuple[int, int, NumberType, NumberType]]
] = {
# integer
"100": {
"plastic": (135, 43, 1140, 330),
Expand Down Expand Up @@ -71,7 +79,9 @@
}


def save_badge(badge, output, output_format=None):
def save_badge(
badge: str, output: str, output_format: str | None = None
) -> str:
"""Save badge to the specified path.

.. versionadded:: 1.4.0 new ``output_format`` keyword argument
Expand Down Expand Up @@ -116,7 +126,9 @@ def save_badge(badge, output, output_format=None):
return output


def _get_badge_measurements(result, style):
def _get_badge_measurements(
result: float, style: str
) -> dict[str, NumberType]:
"""Lookup templated style values based on result number."""
if result == 100:
width_values = SVG_WIDTH_VALUES["100"]
Expand All @@ -133,15 +145,15 @@ def _get_badge_measurements(result, style):
}


def _format_result(result):
def _format_result(result: float) -> str:
"""Format result into string for templating."""
# do not include decimal if it's 100
if result == 100:
return "100"
return f"{result:.1f}"


def get_badge(result, color, style=None):
def get_badge(result: float, color: str, style: str | None = None) -> str:
"""Generate an SVG from template.

:param float result: coverage % result.
Expand All @@ -154,9 +166,9 @@ def get_badge(result, color, style=None):
style = "flat-square-modified"
template_file = f"{style}-style.svg"
badge_template_values = _get_badge_measurements(result, style)
result = _format_result(result)
badge_template_values["result"] = result
badge_template_values["color"] = color
formatted_result = _format_result(result)
badge_template_values["result"] = formatted_result # type: ignore
badge_template_values["color"] = color # type: ignore

if sys.version_info >= (3, 9):
tmpl = (
Expand All @@ -171,7 +183,7 @@ def get_badge(result, color, style=None):
return tmpl


def should_generate_badge(output, color, result):
def should_generate_badge(output: str, color: str, result: float) -> bool:
"""Detect if existing badge needs updating.

This is to help avoid unnecessary newline updates. See
Expand All @@ -186,8 +198,8 @@ def should_generate_badge(output, color, result):
logo doesn't exist.

:param str output: path to output badge file
:param float result: coverage % result.
:param str color: color of badge.
:param float result: coverage % result.
:return: Whether or not the badge SVG file should be generated.
:rtype: bool
"""
Expand Down Expand Up @@ -228,13 +240,13 @@ def should_generate_badge(output, color, result):
for t in texts
if t.hasAttribute("data-interrogate")
]
result = f"{result:.1f}%"
if result in current_results:
formatted_result = f"{result:.1f}%"
if formatted_result in current_results:
return False
return True


def get_color(result):
def get_color(result: float) -> str:
"""Get color for current doc coverage percent.

:param float result: coverage % result
Expand All @@ -247,7 +259,12 @@ def get_color(result):
return COLORS["lightgrey"]


def create(output, result, output_format=None, output_style=None):
def create(
output: str,
result: InterrogateResults,
output_format: str | None = None,
output_style: str | None = None,
) -> str:
"""Create a status badge.

The badge file will only be written if it doesn't exist, or if the
Expand All @@ -263,6 +280,11 @@ def create(output, result, output_format=None, output_style=None):
:param str output: path to output badge file.
:param coverage.InterrogateResults result: results of coverage
interrogation.
:param str output_format: output format of the badge. Options: "svg", "png".
Default: "svg"
:param str output_style: badge styling. Options: "plastic", "social",
"flat", "flat-square", "flat-square-modified", "for-the-badge".
Default: "flat-square-modified"
:return: path to output badge file.
:rtype: str
"""
Expand Down
Loading
Loading