Skip to content

Commit 1736196

Browse files
authored
Add mypy, ruff, and refurb. (#34)
1 parent 23d8e0d commit 1736196

17 files changed

+165
-94
lines changed

.pre-commit-config.yaml

+24-26
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,14 @@ repos:
99
- id: debug-statements
1010
- id: end-of-file-fixer
1111
- repo: https://github.com/pre-commit/pygrep-hooks
12-
rev: v1.9.0 # Use the ref you want to point at
12+
rev: v1.9.0
1313
hooks:
1414
- id: python-check-blanket-noqa
1515
- id: python-check-mock-methods
1616
- id: python-no-eval
1717
- id: python-no-log-warn
1818
- id: python-use-type-annotations
1919
- id: text-unicode-replacement-char
20-
- repo: https://github.com/asottile/pyupgrade
21-
rev: v3.3.1
22-
hooks:
23-
- id: pyupgrade
24-
args: [--py37-plus]
2520
- repo: https://github.com/asottile/reorder_python_imports
2621
rev: v3.9.0
2722
hooks:
@@ -40,27 +35,15 @@ repos:
4035
rev: 22.12.0
4136
hooks:
4237
- id: black
43-
- repo: https://github.com/PyCQA/flake8
44-
rev: 5.0.4
38+
- repo: https://github.com/charliermarsh/ruff-pre-commit
39+
rev: v0.0.215
4540
hooks:
46-
- id: flake8
47-
types: [python]
48-
additional_dependencies: [
49-
flake8-alfred,
50-
flake8-bugbear,
51-
flake8-builtins,
52-
flake8-comprehensions,
53-
flake8-docstrings,
54-
flake8-eradicate,
55-
flake8-print,
56-
flake8-pytest-style,
57-
flake8-todo,
58-
flake8-typing-imports,
59-
flake8-unused-arguments,
60-
pep8-naming,
61-
pydocstyle,
62-
Pygments,
63-
]
41+
- id: ruff
42+
- repo: https://github.com/dosisod/refurb
43+
rev: v1.9.1
44+
hooks:
45+
- id: refurb
46+
args: [--ignore, FURB126]
6447
- repo: https://github.com/econchick/interrogate
6548
rev: 1.5.0
6649
hooks:
@@ -79,6 +62,21 @@ repos:
7962
rev: v2.2.2
8063
hooks:
8164
- id: codespell
65+
- repo: https://github.com/pre-commit/mirrors-mypy
66+
rev: 'v0.991'
67+
hooks:
68+
- id: mypy
69+
args: [
70+
--no-strict-optional,
71+
--ignore-missing-imports,
72+
]
73+
additional_dependencies: [
74+
attrs>=21.3.0,
75+
click,
76+
types-PyYAML,
77+
types-setuptools
78+
]
79+
pass_filenames: false
8280
- repo: https://github.com/mgedmin/check-manifest
8381
rev: "0.49"
8482
hooks:

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask-r) and
88
## 0.3.0 - 2023-xx-xx
99

1010
- {pull}`33` deprecates INI configurations and aligns the plugin with pytask v0.3.
11+
- {pull}`34` adds mypy, ruff and refurb.
1112

1213
## 0.2.0 - 2022-04-16
1314

pyproject.toml

+55
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,58 @@ build-backend = "setuptools.build_meta"
55

66
[tool.setuptools_scm]
77
write_to = "src/pytask_r/_version.py"
8+
9+
10+
[tool.mypy]
11+
files = ["src", "tests"]
12+
check_untyped_defs = true
13+
disallow_any_generics = true
14+
disallow_incomplete_defs = true
15+
disallow_untyped_defs = true
16+
no_implicit_optional = true
17+
warn_redundant_casts = true
18+
warn_unused_ignores = true
19+
20+
21+
[[tool.mypy.overrides]]
22+
module = "tests.*"
23+
disallow_untyped_defs = false
24+
ignore_errors = true
25+
26+
27+
[tool.ruff]
28+
target-version = "py37"
29+
select = ["ALL"]
30+
fix = true
31+
extend-ignore = [
32+
# Numpy docstyle
33+
"D107",
34+
"D203",
35+
"D212",
36+
"D213",
37+
"D402",
38+
"D413",
39+
"D415",
40+
"D416",
41+
"D417",
42+
# Others.
43+
"D404", # Do not start module docstring with "This".
44+
"RET504", # unnecessary variable assignment before return.
45+
"S101", # raise errors for asserts.
46+
"B905", # strict parameter for zip that was implemented in py310.
47+
"I", # ignore isort
48+
"ANN101", # type annotating self
49+
"ANN102", # type annotating cls
50+
"FBT", # flake8-boolean-trap
51+
"EM", # flake8-errmsg
52+
"ANN401", # flake8-annotate typing.Any
53+
"PD", # pandas-vet
54+
]
55+
56+
57+
[tool.ruff.per-file-ignores]
58+
"tests/*" = ["D", "ANN"]
59+
60+
61+
[tool.ruff.pydocstyle]
62+
convention = "numpy"

src/pytask_r/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""This module contains the main namespace of pytask-r."""
12
from __future__ import annotations
23

34
try:

src/pytask_r/collect.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
from pathlib import Path
77
from types import FunctionType
8+
from typing import Any
89

910
from pytask import depends_on
1011
from pytask import has_mark
@@ -13,6 +14,7 @@
1314
from pytask import parse_nodes
1415
from pytask import produces
1516
from pytask import remove_marks
17+
from pytask import Session
1618
from pytask import Task
1719
from pytask_r.serialization import SERIALIZERS
1820
from pytask_r.shared import r
@@ -26,7 +28,9 @@ def run_r_script(script: Path, options: list[str], serialized: Path) -> None:
2628

2729

2830
@hookimpl
29-
def pytask_collect_task(session, path, name, obj):
31+
def pytask_collect_task(
32+
session: Session, path: Path, name: str, obj: Any
33+
) -> Task | None:
3034
"""Perform some checks."""
3135
__tracebackhide__ = True
3236

@@ -62,7 +66,7 @@ def pytask_collect_task(session, path, name, obj):
6266
task = Task(
6367
base_name=name,
6468
path=path,
65-
function=_copy_func(run_r_script),
69+
function=_copy_func(run_r_script), # type: ignore[arg-type]
6670
depends_on=dependencies,
6771
produces=products,
6872
markers=markers,
@@ -85,28 +89,33 @@ def pytask_collect_task(session, path, name, obj):
8589
)
8690

8791
return task
92+
return None
8893

8994

90-
def _parse_r_mark(mark, default_options, default_serializer, default_suffix):
95+
def _parse_r_mark(
96+
mark: Mark,
97+
default_options: list[str] | None,
98+
default_serializer: str,
99+
default_suffix: str,
100+
) -> Mark:
91101
"""Parse a Julia mark."""
92102
script, options, serializer, suffix = r(**mark.kwargs)
93103

94104
parsed_kwargs = {}
95-
for arg_name, value, default in [
105+
for arg_name, value, default in (
96106
("script", script, None),
97107
("options", options, default_options),
98108
("serializer", serializer, default_serializer),
99-
]:
100-
parsed_kwargs[arg_name] = value if value else default
109+
):
110+
parsed_kwargs[arg_name] = value or default
101111

102-
if (
103-
isinstance(parsed_kwargs["serializer"], str)
112+
proposed_suffix = (
113+
SERIALIZERS[parsed_kwargs["serializer"]]["suffix"]
114+
if isinstance(parsed_kwargs["serializer"], str)
104115
and parsed_kwargs["serializer"] in SERIALIZERS
105-
):
106-
proposed_suffix = SERIALIZERS[parsed_kwargs["serializer"]]["suffix"]
107-
else:
108-
proposed_suffix = default_suffix
109-
parsed_kwargs["suffix"] = suffix if suffix else proposed_suffix
116+
else default_suffix
117+
)
118+
parsed_kwargs["suffix"] = suffix or proposed_suffix # type: ignore[assignment]
110119

111120
mark = Mark("r", (), parsed_kwargs)
112121
return mark

src/pytask_r/config.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""Configure pytask."""
22
from __future__ import annotations
33

4+
from typing import Any
5+
46
from pytask import hookimpl
57
from pytask_r.serialization import SERIALIZERS
68

79

810
@hookimpl
9-
def pytask_parse_config(config):
11+
def pytask_parse_config(config: dict[str, Any]) -> None:
1012
"""Register the r marker."""
1113
config["markers"]["r"] = "Tasks which are executed with Rscript."
1214
config["r_serializer"] = config.get("r_serializer", "json")
@@ -23,7 +25,6 @@ def _parse_value_or_whitespace_option(value: str | None) -> None | str | list[st
2325
"""Parse option which can hold a single value or values separated by new lines."""
2426
if value is None:
2527
return None
26-
elif isinstance(value, list):
28+
if isinstance(value, list):
2729
return list(map(str, value))
28-
else:
29-
raise ValueError(f"'r_options' is {value} and not a list.")
30+
raise ValueError(f"'r_options' is {value} and not a list.")

src/pytask_r/execute.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
@hookimpl
18-
def pytask_execute_task_setup(task):
18+
def pytask_execute_task_setup(task: Task) -> None:
1919
"""Perform some checks when a task marked with the r marker is executed."""
2020
marks = get_marks(task, "r")
2121
if marks:

src/pytask_r/parametrize.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""Parametrize tasks."""
22
from __future__ import annotations
33

4+
from typing import Any
5+
46
import pytask
57
from pytask import hookimpl
68

79

810
@hookimpl
9-
def pytask_parametrize_kwarg_to_marker(obj, kwargs):
11+
def pytask_parametrize_kwarg_to_marker(obj: Any, kwargs: dict[Any, Any]) -> None:
1012
"""Attach parametrized r arguments to the function with a marker."""
11-
if callable(obj):
12-
if "r" in kwargs:
13-
pytask.mark.r(**kwargs.pop("r"))(obj)
13+
if callable(obj) and "r" in kwargs:
14+
pytask.mark.r(**kwargs.pop("r"))(obj)

src/pytask_r/plugin.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Register hook specifications and implementations."""
22
from __future__ import annotations
33

4+
from pluggy import PluginManager
45
from pytask import hookimpl
56
from pytask_r import collect
67
from pytask_r import config
@@ -9,7 +10,7 @@
910

1011

1112
@hookimpl
12-
def pytask_add_hooks(pm):
13+
def pytask_add_hooks(pm: PluginManager) -> None:
1314
"""Register hook implementations."""
1415
pm.register(collect)
1516
pm.register(config)

src/pytask_r/serialization.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@ def create_file_name(task: Task, suffix: str) -> str:
5050

5151

5252
def serialize_keyword_arguments(
53-
serializer: str | Callable[dict[str, Any], str],
53+
serializer: str | Callable[..., str],
5454
path_to_serialized: Path,
5555
kwargs: dict[str, Any],
5656
) -> None:
57+
"""Serialize keyword arguments."""
5758
if callable(serializer):
5859
serializer_func = serializer
5960
elif isinstance(serializer, str) and serializer in SERIALIZERS:
60-
serializer_func = SERIALIZERS[serializer]["serializer"]
61+
serializer_func = SERIALIZERS[serializer][
62+
"serializer"
63+
] # type: ignore[assignment]
6164
else:
6265
raise ValueError(f"Serializer {serializer!r} is not known.")
6366

src/pytask_r/shared.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
"""This module contains shared functions."""
12
from __future__ import annotations
23

34
from pathlib import Path
5+
from typing import Any
46
from typing import Callable
57
from typing import Iterable
68
from typing import Sequence
@@ -39,17 +41,9 @@ def r(
3941
return script, options, serializer, suffix
4042

4143

42-
def _to_list(scalar_or_iter):
44+
def _to_list(scalar_or_iter: Any) -> list[Any]:
4345
"""Convert scalars and iterables to list.
4446
45-
Parameters
46-
----------
47-
scalar_or_iter : str or list
48-
49-
Returns
50-
-------
51-
list
52-
5347
Examples
5448
--------
5549
>>> _to_list("a")

tests/test_collect.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
from pytask_r.serialization import SERIALIZERS
1010

1111

12-
@pytest.mark.unit
12+
@pytest.mark.unit()
1313
@pytest.mark.parametrize(
14-
"args, kwargs, expectation, expected",
14+
("args", "kwargs", "expectation", "expected"),
1515
[
1616
(
1717
(),
@@ -43,9 +43,16 @@ def test_r(args, kwargs, expectation, expected):
4343
assert result == expected
4444

4545

46-
@pytest.mark.unit
46+
@pytest.mark.unit()
4747
@pytest.mark.parametrize(
48-
"mark, default_options, default_serializer, default_suffix, expectation, expected",
48+
(
49+
"mark",
50+
"default_options",
51+
"default_serializer",
52+
"default_suffix",
53+
"expectation",
54+
"expected",
55+
),
4956
[
5057
(
5158
Mark("r", (), {"script": "script.r"}),

tests/test_config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pytask import main
55

66

7-
@pytest.mark.end_to_end
7+
@pytest.mark.end_to_end()
88
def test_marker_is_configured(tmp_path):
99
session = main({"paths": tmp_path})
1010

0 commit comments

Comments
 (0)