Skip to content

Commit

Permalink
Merge pull request #726 from carmenbianca/mypy
Browse files Browse the repository at this point in the history
Type checking with mypy
  • Loading branch information
carmenbianca authored Jun 19, 2023
2 parents 2644787 + bf40ad4 commit 970e22f
Show file tree
Hide file tree
Showing 26 changed files with 446 additions and 266 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ jobs:
poetry run isort --check src/ tests/
poetry run black .
mypy:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: |
pip install poetry
poetry install --no-interaction
- name: Test typing with mypy
run: |
poetry run mypy
prettier:
runs-on: ubuntu-20.04
container: node:latest
Expand Down
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ repos:
- id: isort
name: isort (pyi)
types: [pyi]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.2.0
hooks:
- id: mypy
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Next, you'll find the following commands handy:
- `poetry run reuse`
- `poetry run pytest`
- `poetry run pylint src`
- `poetry run mypy`
- `make docs`

## Development conventions
Expand Down
49 changes: 48 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pre-commit = "^2.9.0"
pylint = "^2.12.2"
pytest = ">=6.0.0"
pytest-cov = ">=2.10.0"
mypy = "^1.0"

[tool.poetry.scripts]
reuse = 'reuse._main:main'
Expand All @@ -94,3 +95,27 @@ line_length = 80

[tool.pytest.ini_options]
addopts = "--doctest-modules"

[tool.mypy]
files = [
"src/reuse/**.py",
"tests/**.py",
]
exclude = [
'^_build\.py$',
'^conf\.py$',
]

[[tool.mypy.overrides]]
module = "reuse.*"
disallow_untyped_defs = true
disallow_incomplete_defs = true

[[tool.mypy.overrides]]
module = [
"binaryornot.check",
"boolean.boolean",
"license_expression",
"pkg_resources",
]
ignore_missing_imports = true
16 changes: 8 additions & 8 deletions src/reuse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from dataclasses import dataclass, field
from enum import Enum, auto
from importlib.metadata import PackageNotFoundError, version
from typing import NamedTuple, Optional, Set, Type
from typing import Any, Dict, NamedTuple, Optional, Set, Type

from boolean.boolean import Expression

Expand Down Expand Up @@ -109,25 +109,25 @@ class ReuseInfo:
source_path: Optional[str] = None
source_type: Optional[SourceType] = None

def _check_nonexistent(self, **kwargs) -> None:
def _check_nonexistent(self, **kwargs: Any) -> None:
nonexistent_attributes = set(kwargs) - set(self.__dict__)
if nonexistent_attributes:
raise KeyError(
f"The following attributes do not exist in"
f" {self.__class__}: {', '.join(nonexistent_attributes)}"
)

def copy(self, **kwargs) -> Type["ReuseInfo"]:
def copy(self, **kwargs: Any) -> "ReuseInfo":
"""Return a copy of ReuseInfo, replacing the values of attributes with
the values from *kwargs*.
"""
self._check_nonexistent(**kwargs)
new_kwargs = {}
for key, value in self.__dict__.items():
new_kwargs[key] = kwargs.get(key, value)
return self.__class__(**new_kwargs)
return self.__class__(**new_kwargs) # type: ignore

def union(self, value) -> Type["ReuseInfo"]:
def union(self, value: "ReuseInfo") -> "ReuseInfo":
"""Return a new instance of ReuseInfo where all Set attributes are equal
to the union of the set in *self* and the set in *value*.
Expand All @@ -147,16 +147,16 @@ def union(self, value) -> Type["ReuseInfo"]:
new_kwargs[key] = attr_val.union(other_val)
else:
new_kwargs[key] = attr_val
return self.__class__(**new_kwargs)
return self.__class__(**new_kwargs) # type: ignore

def contains_copyright_or_licensing(self) -> bool:
"""Either *spdx_expressions* or *copyright_lines* is non-empty."""
return bool(self.spdx_expressions or self.copyright_lines)

def __bool__(self):
def __bool__(self) -> bool:
return any(self.__dict__.values())

def __or__(self, value) -> Type["ReuseInfo"]:
def __or__(self, value: "ReuseInfo") -> "ReuseInfo":
return self.union(value)


Expand Down
7 changes: 4 additions & 3 deletions src/reuse/_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@
"""Formatting functions primarily for the CLI."""

from textwrap import fill, indent
from typing import Iterator

WIDTH = 78
INDENT = 2


def fill_paragraph(text, width=WIDTH, indent_width=0):
def fill_paragraph(text: str, width: int = WIDTH, indent_width: int = 0) -> str:
"""Wrap a single paragraph."""
return indent(
fill(text.strip(), width=width - indent_width), indent_width * " "
)


def fill_all(text, width=WIDTH, indent_width=0):
def fill_all(text: str, width: int = WIDTH, indent_width: int = 0) -> str:
"""Wrap all paragraphs."""
return "\n\n".join(
fill_paragraph(paragraph, width=width, indent_width=indent_width)
for paragraph in split_into_paragraphs(text)
)


def split_into_paragraphs(text):
def split_into_paragraphs(text: str) -> Iterator[str]:
"""Yield all paragraphs in a text. A paragraph is a piece of text
surrounded by empty lines.
"""
Expand Down
5 changes: 3 additions & 2 deletions src/reuse/_licenses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@

import json
import os
from typing import Dict, List, Tuple

_BASE_DIR = os.path.dirname(__file__)
_RESOURCES_DIR = os.path.join(_BASE_DIR, "resources")
_LICENSES = os.path.join(_RESOURCES_DIR, "licenses.json")
_EXCEPTIONS = os.path.join(_RESOURCES_DIR, "exceptions.json")


def _load_license_list(file_name):
def _load_license_list(file_name: str) -> Tuple[List[int], Dict[str, Dict]]:
"""Return the licenses list version tuple and a mapping of licenses
id->name loaded from a JSON file
from https://github.com/spdx/license-list-data
Expand All @@ -33,7 +34,7 @@ def _load_license_list(file_name):
return version, licenses_map


def _load_exception_list(file_name):
def _load_exception_list(file_name: str) -> Tuple[List[int], Dict[str, Dict]]:
"""Return the exceptions list version tuple and a mapping of
exceptions id->name loaded from a JSON file
from https://github.com/spdx/license-list-data
Expand Down
20 changes: 10 additions & 10 deletions src/reuse/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import logging
import sys
from gettext import gettext as _
from typing import List
from typing import IO, Callable, List, Optional, Type, cast

from . import (
__REUSE_version__,
Expand Down Expand Up @@ -238,14 +238,14 @@ def parser() -> argparse.ArgumentParser:


def add_command( # pylint: disable=too-many-arguments,redefined-builtin
subparsers,
subparsers: argparse._SubParsersAction,
name: str,
add_arguments_func,
run_func,
formatter_class=None,
description: str = None,
help: str = None,
aliases: list = None,
add_arguments_func: Callable[[argparse.ArgumentParser], None],
run_func: Callable[[argparse.Namespace, Project, IO[str]], int],
formatter_class: Optional[Type[argparse.HelpFormatter]] = None,
description: Optional[str] = None,
help: Optional[str] = None,
aliases: Optional[List[str]] = None,
) -> None:
"""Add a subparser for a command."""
if formatter_class is None:
Expand All @@ -262,10 +262,10 @@ def add_command( # pylint: disable=too-many-arguments,redefined-builtin
subparser.set_defaults(parser=subparser)


def main(args: List[str] = None, out=sys.stdout) -> int:
def main(args: Optional[List[str]] = None, out: IO[str] = sys.stdout) -> int:
"""Main entry function."""
if args is None:
args = sys.argv[1:]
args = cast(List[str], sys.argv[1:])

main_parser = parser()
parsed_args = main_parser.parse_args(args)
Expand Down
Loading

0 comments on commit 970e22f

Please sign in to comment.