duty 1.1.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {duty-1.1.0 → duty-1.3.0}/PKG-INFO +8 -6
- {duty-1.1.0 → duty-1.3.0}/README.md +3 -3
- {duty-1.1.0 → duty-1.3.0}/pyproject.toml +10 -49
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/safety.py +12 -1
- {duty-1.1.0 → duty-1.3.0}/src/duty/collection.py +1 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/context.py +1 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/debug.py +4 -1
- {duty-1.1.0 → duty-1.3.0}/src/duty/decorator.py +3 -4
- {duty-1.1.0 → duty-1.3.0}/src/duty/validation.py +30 -10
- duty-1.1.0/tests/__init__.py +0 -7
- duty-1.1.0/tests/conftest.py +0 -1
- duty-1.1.0/tests/fixtures/arguments.py +0 -6
- duty-1.1.0/tests/fixtures/basic.py +0 -6
- duty-1.1.0/tests/fixtures/booleans.py +0 -6
- duty-1.1.0/tests/fixtures/code.py +0 -6
- duty-1.1.0/tests/fixtures/list.py +0 -11
- duty-1.1.0/tests/fixtures/multiple.py +0 -11
- duty-1.1.0/tests/fixtures/precedence.py +0 -6
- duty-1.1.0/tests/fixtures/validation.py +0 -46
- duty-1.1.0/tests/test_cli.py +0 -247
- duty-1.1.0/tests/test_collection.py +0 -70
- duty-1.1.0/tests/test_context.py +0 -99
- duty-1.1.0/tests/test_decorator.py +0 -26
- duty-1.1.0/tests/test_running.py +0 -114
- duty-1.1.0/tests/test_validation.py +0 -147
- {duty-1.1.0 → duty-1.3.0}/LICENSE +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/__init__.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/__main__.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/__init__.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/_io.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/autoflake.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/black.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/blacken_docs.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/coverage.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/flake8.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/interrogate.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/isort.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/mkdocs.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/mypy.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/pytest.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/ruff.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/callables/ssort.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/cli.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/exceptions.py +0 -0
- {duty-1.1.0 → duty-1.3.0}/src/duty/py.typed +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: duty
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: A simple task runner.
|
|
5
|
-
Keywords: task-runner
|
|
6
|
-
Author-Email:
|
|
5
|
+
Keywords: task-runner,task,runner,cross-platform
|
|
6
|
+
Author-Email: =?utf-8?q?Timoth=C3=A9e_Mazzucotelli?= <dev@pawamoy.fr>
|
|
7
7
|
License: ISC
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
19
|
Classifier: Topic :: Documentation
|
|
19
20
|
Classifier: Topic :: Software Development
|
|
20
21
|
Classifier: Topic :: Utilities
|
|
@@ -28,16 +29,17 @@ Project-URL: Discussions, https://github.com/pawamoy/duty/discussions
|
|
|
28
29
|
Project-URL: Gitter, https://gitter.im/duty/community
|
|
29
30
|
Project-URL: Funding, https://github.com/sponsors/pawamoy
|
|
30
31
|
Requires-Python: >=3.8
|
|
32
|
+
Requires-Dist: eval-type-backport; python_version < "3.10"
|
|
31
33
|
Requires-Dist: failprint!=1.0.0,>=0.11
|
|
32
34
|
Description-Content-Type: text/markdown
|
|
33
35
|
|
|
34
36
|
# duty
|
|
35
37
|
|
|
36
38
|
[](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
|
|
37
|
-
[](https://pawamoy.github.io/duty/)
|
|
38
40
|
[](https://pypi.org/project/duty/)
|
|
39
|
-
[](https://gitter.im
|
|
41
|
+
[](https://gitpod.io/#https://github.com/pawamoy/duty)
|
|
42
|
+
[](https://app.gitter.im/#/room/#duty:gitter.im)
|
|
41
43
|
|
|
42
44
|
A simple task runner.
|
|
43
45
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# duty
|
|
2
2
|
|
|
3
3
|
[](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
|
|
4
|
-
[](https://pawamoy.github.io/duty/)
|
|
5
5
|
[](https://pypi.org/project/duty/)
|
|
6
|
-
[](https://gitter.im
|
|
6
|
+
[](https://gitpod.io/#https://github.com/pawamoy/duty)
|
|
7
|
+
[](https://app.gitter.im/#/room/#duty:gitter.im)
|
|
8
8
|
|
|
9
9
|
A simple task runner.
|
|
10
10
|
|
|
@@ -8,7 +8,7 @@ build-backend = "pdm.backend"
|
|
|
8
8
|
name = "duty"
|
|
9
9
|
description = "A simple task runner."
|
|
10
10
|
authors = [
|
|
11
|
-
{ name = "Timothée Mazzucotelli", email = "pawamoy
|
|
11
|
+
{ name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr" },
|
|
12
12
|
]
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.8"
|
|
@@ -30,15 +30,17 @@ classifiers = [
|
|
|
30
30
|
"Programming Language :: Python :: 3.10",
|
|
31
31
|
"Programming Language :: Python :: 3.11",
|
|
32
32
|
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
33
34
|
"Topic :: Documentation",
|
|
34
35
|
"Topic :: Software Development",
|
|
35
36
|
"Topic :: Utilities",
|
|
36
37
|
"Typing :: Typed",
|
|
37
38
|
]
|
|
38
39
|
dependencies = [
|
|
40
|
+
"eval-type-backport; python_version < '3.10'",
|
|
39
41
|
"failprint>=0.11,!=1.0.0",
|
|
40
42
|
]
|
|
41
|
-
version = "1.
|
|
43
|
+
version = "1.3.0"
|
|
42
44
|
|
|
43
45
|
[project.license]
|
|
44
46
|
text = "ISC"
|
|
@@ -56,58 +58,17 @@ Funding = "https://github.com/sponsors/pawamoy"
|
|
|
56
58
|
[project.scripts]
|
|
57
59
|
duty = "duty.cli:main"
|
|
58
60
|
|
|
59
|
-
[tool.pdm]
|
|
60
|
-
plugins = [
|
|
61
|
-
"pdm-multirun",
|
|
62
|
-
]
|
|
63
|
-
|
|
64
61
|
[tool.pdm.version]
|
|
65
62
|
source = "scm"
|
|
66
63
|
|
|
67
64
|
[tool.pdm.build]
|
|
68
65
|
package-dir = "src"
|
|
69
66
|
editable-backend = "editables"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
ci-quality = [
|
|
73
|
-
"duty[docs,quality,typing,security]",
|
|
74
|
-
]
|
|
75
|
-
ci-tests = [
|
|
76
|
-
"duty[tests]",
|
|
77
|
-
]
|
|
78
|
-
docs = [
|
|
79
|
-
"black>=23.9",
|
|
80
|
-
"markdown-callouts>=0.3",
|
|
81
|
-
"markdown-exec>=1.7",
|
|
82
|
-
"mkdocs>=1.5",
|
|
83
|
-
"mkdocs-coverage>=1.0",
|
|
84
|
-
"mkdocs-gen-files>=0.5",
|
|
85
|
-
"mkdocs-git-committers-plugin-2>=1.2",
|
|
86
|
-
"mkdocs-literate-nav>=0.6",
|
|
87
|
-
"mkdocs-material>=9.4",
|
|
88
|
-
"mkdocs-minify-plugin>=0.7",
|
|
89
|
-
"mkdocstrings[python]>=0.23",
|
|
90
|
-
"tomli>=2.0; python_version < '3.11'",
|
|
67
|
+
source-includes = [
|
|
68
|
+
"share",
|
|
91
69
|
]
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
]
|
|
97
|
-
quality = [
|
|
98
|
-
"ruff>=0.0",
|
|
99
|
-
]
|
|
100
|
-
tests = [
|
|
101
|
-
"pytest>=7.4",
|
|
102
|
-
"pytest-cov>=4.1",
|
|
103
|
-
"pytest-randomly>=3.15",
|
|
104
|
-
"pytest-xdist>=3.3",
|
|
105
|
-
]
|
|
106
|
-
typing = [
|
|
107
|
-
"mypy>=1.5",
|
|
108
|
-
"types-markdown>=3.5",
|
|
109
|
-
"types-pyyaml>=6.0",
|
|
110
|
-
]
|
|
111
|
-
security = [
|
|
112
|
-
"safety>=2.3",
|
|
70
|
+
|
|
71
|
+
[tool.pdm.build.wheel-data]
|
|
72
|
+
data = [
|
|
73
|
+
{ path = "share/**/*", relative-to = "." },
|
|
113
74
|
]
|
|
@@ -51,7 +51,18 @@ def check(
|
|
|
51
51
|
if isinstance(requirements, (list, tuple, set)):
|
|
52
52
|
requirements = "\n".join(requirements)
|
|
53
53
|
packages = list(read_requirements(StringIO(cast(str, requirements))))
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
# TODO: Safety 3 support, merge once support for v2 is dropped.
|
|
56
|
+
check_kwargs = {"packages": packages, "ignore_vulns": ignore_vulns}
|
|
57
|
+
try:
|
|
58
|
+
from safety.auth.cli_utils import build_client_session
|
|
59
|
+
|
|
60
|
+
client_session, _ = build_client_session()
|
|
61
|
+
check_kwargs["session"] = client_session
|
|
62
|
+
except ImportError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
vulns, db_full = check(**check_kwargs)
|
|
55
66
|
remediations = calculate_remediations(vulns, db_full)
|
|
56
67
|
output_report = SafetyFormatter(formatter).render_vulnerabilities(
|
|
57
68
|
announcements=[],
|
|
@@ -37,6 +37,8 @@ class Environment:
|
|
|
37
37
|
"""Python interpreter name."""
|
|
38
38
|
interpreter_version: str
|
|
39
39
|
"""Python interpreter version."""
|
|
40
|
+
interpreter_path: str
|
|
41
|
+
"""Path to Python executable."""
|
|
40
42
|
platform: str
|
|
41
43
|
"""Operating System."""
|
|
42
44
|
packages: list[Package]
|
|
@@ -83,6 +85,7 @@ def get_debug_info() -> Environment:
|
|
|
83
85
|
return Environment(
|
|
84
86
|
interpreter_name=py_name,
|
|
85
87
|
interpreter_version=py_version,
|
|
88
|
+
interpreter_path=sys.executable,
|
|
86
89
|
platform=platform.platform(),
|
|
87
90
|
variables=[Variable(var, val) for var in variables if (val := os.getenv(var))],
|
|
88
91
|
packages=[Package(pkg, get_version(pkg)) for pkg in packages],
|
|
@@ -93,7 +96,7 @@ def print_debug_info() -> None:
|
|
|
93
96
|
"""Print debug/environment information."""
|
|
94
97
|
info = get_debug_info()
|
|
95
98
|
print(f"- __System__: {info.platform}")
|
|
96
|
-
print(f"- __Python__: {info.interpreter_name} {info.interpreter_version}")
|
|
99
|
+
print(f"- __Python__: {info.interpreter_name} {info.interpreter_version} ({info.interpreter_path})")
|
|
97
100
|
print("- __Environment variables__:")
|
|
98
101
|
for var in info.variables:
|
|
99
102
|
print(f" - `{var.name}`: `{var.value}`")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Module containing the decorator provided to users."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import inspect
|
|
@@ -62,13 +63,11 @@ def create_duty(
|
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
@overload
|
|
65
|
-
def duty(**kwargs: Any) -> Callable[[Callable], Duty]: # type: ignore[
|
|
66
|
-
... # pragma: no cover
|
|
66
|
+
def duty(**kwargs: Any) -> Callable[[Callable], Duty]: ... # type: ignore[overload-overlap]
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
@overload
|
|
70
|
-
def duty(func: Callable) -> Duty:
|
|
71
|
-
... # pragma: no cover
|
|
70
|
+
def duty(func: Callable) -> Duty: ...
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
def duty(*args: Any, **kwargs: Any) -> Callable | Duty:
|
|
@@ -9,9 +9,21 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import sys
|
|
11
11
|
import textwrap
|
|
12
|
+
from contextlib import suppress
|
|
12
13
|
from functools import cached_property
|
|
13
14
|
from inspect import Parameter, Signature, signature
|
|
14
|
-
from typing import Any, Callable, Sequence
|
|
15
|
+
from typing import Any, Callable, ForwardRef, Sequence, Union, get_args, get_origin
|
|
16
|
+
|
|
17
|
+
# TODO: Update once support for Python 3.9 is dropped.
|
|
18
|
+
if sys.version_info < (3, 10):
|
|
19
|
+
from eval_type_backport import eval_type_backport as eval_type
|
|
20
|
+
|
|
21
|
+
union_types = (Union,)
|
|
22
|
+
else:
|
|
23
|
+
from types import UnionType
|
|
24
|
+
from typing import _eval_type as eval_type # type: ignore[attr-defined]
|
|
25
|
+
|
|
26
|
+
union_types = (Union, UnionType)
|
|
15
27
|
|
|
16
28
|
|
|
17
29
|
def to_bool(value: str) -> bool:
|
|
@@ -40,6 +52,12 @@ def cast_arg(arg: Any, annotation: Any) -> Any:
|
|
|
40
52
|
return arg
|
|
41
53
|
if annotation is bool:
|
|
42
54
|
annotation = to_bool
|
|
55
|
+
if get_origin(annotation) in union_types:
|
|
56
|
+
for sub_annotation in get_args(annotation):
|
|
57
|
+
if sub_annotation is type(None):
|
|
58
|
+
continue
|
|
59
|
+
with suppress(Exception):
|
|
60
|
+
return cast_arg(arg, sub_annotation)
|
|
43
61
|
try:
|
|
44
62
|
return annotation(arg)
|
|
45
63
|
except Exception: # noqa: BLE001
|
|
@@ -65,11 +83,9 @@ class ParamsCaster:
|
|
|
65
83
|
Returns:
|
|
66
84
|
The position of the variable positional parameter.
|
|
67
85
|
"""
|
|
68
|
-
pos
|
|
69
|
-
for param in self.params_list:
|
|
86
|
+
for pos, param in enumerate(self.params_list):
|
|
70
87
|
if param.kind is Parameter.VAR_POSITIONAL:
|
|
71
88
|
return pos
|
|
72
|
-
pos += 1
|
|
73
89
|
return -1
|
|
74
90
|
|
|
75
91
|
@cached_property
|
|
@@ -175,6 +191,7 @@ def _get_params_caster(func: Callable, *args: Any, **kwargs: Any) -> ParamsCaste
|
|
|
175
191
|
if exec_globals[name] is annotations:
|
|
176
192
|
eval_str = True
|
|
177
193
|
del exec_globals[name]
|
|
194
|
+
break
|
|
178
195
|
exec_globals["__context_above"] = {}
|
|
179
196
|
|
|
180
197
|
# Don't keep first parameter: context.
|
|
@@ -187,12 +204,15 @@ def _get_params_caster(func: Callable, *args: Any, **kwargs: Any) -> ParamsCaste
|
|
|
187
204
|
param.name,
|
|
188
205
|
param.kind,
|
|
189
206
|
default=param.default,
|
|
190
|
-
annotation=
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
207
|
+
annotation=(
|
|
208
|
+
eval_type(
|
|
209
|
+
ForwardRef(param.annotation) if isinstance(param.annotation, str) else param.annotation,
|
|
210
|
+
exec_globals,
|
|
211
|
+
{},
|
|
212
|
+
)
|
|
213
|
+
if param.annotation is not Parameter.empty
|
|
214
|
+
else type(param.default)
|
|
215
|
+
),
|
|
196
216
|
)
|
|
197
217
|
for param in params
|
|
198
218
|
]
|
duty-1.1.0/tests/__init__.py
DELETED
duty-1.1.0/tests/conftest.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Configuration for the pytest test suite."""
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
def no_params(ctx):
|
|
2
|
-
pass # pragma: no cover
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def pos_or_kw_param(ctx, a: int):
|
|
6
|
-
pass # pragma: no cover
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def pos_or_kw_params(ctx, a: int, b: int):
|
|
10
|
-
pass # pragma: no cover
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def varpos_param(ctx, *a: int):
|
|
14
|
-
pass # pragma: no cover
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def pos_and_varpos_param(ctx, a: int, *b: int):
|
|
18
|
-
pass # pragma: no cover
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def kwonly_param(ctx, *a: int, b: int):
|
|
22
|
-
pass # pragma: no cover
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def varkw_param(ctx, a: int, **b: int):
|
|
26
|
-
pass # pragma: no cover
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def varkw_no_annotation(ctx, **a):
|
|
30
|
-
pass # pragma: no cover
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def posonly_marker(ctx, a: int, /, b: int):
|
|
34
|
-
pass # pragma: no cover
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def kwonly_marker(ctx, a: int, *, b: int):
|
|
38
|
-
pass # pragma: no cover
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def only_markers(ctx, a: int, /, b: int, *, c: int):
|
|
42
|
-
pass # pragma: no cover
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def full(ctx, a: int, /, b: int, *c: int, d: int, e: int = 0, **f: int):
|
|
46
|
-
pass # pragma: no cover
|
duty-1.1.0/tests/test_cli.py
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
"""Tests for the `cli` module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from duty import cli, debug
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_no_duty(capsys: pytest.CaptureFixture) -> None:
|
|
11
|
-
"""Run no duties.
|
|
12
|
-
|
|
13
|
-
Parameters:
|
|
14
|
-
capsys: Pytest fixture to capture output.
|
|
15
|
-
"""
|
|
16
|
-
assert cli.main([]) == 1
|
|
17
|
-
captured = capsys.readouterr()
|
|
18
|
-
assert "Available duties" in captured.out
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_show_help(capsys: pytest.CaptureFixture) -> None:
|
|
22
|
-
"""Show help.
|
|
23
|
-
|
|
24
|
-
Parameters:
|
|
25
|
-
capsys: Pytest fixture to capture output.
|
|
26
|
-
"""
|
|
27
|
-
assert cli.main(["-h"]) == 0
|
|
28
|
-
captured = capsys.readouterr()
|
|
29
|
-
assert "duty" in captured.out
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def test_show_help_for_given_duties(capsys: pytest.CaptureFixture) -> None:
|
|
33
|
-
"""Show help for given duties.
|
|
34
|
-
|
|
35
|
-
Parameters:
|
|
36
|
-
capsys: Pytest fixture to capture output.
|
|
37
|
-
"""
|
|
38
|
-
assert cli.main(["-d", "tests/fixtures/basic.py", "-h", "hello"]) == 0
|
|
39
|
-
captured = capsys.readouterr()
|
|
40
|
-
assert "hello" in captured.out
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_show_help_unknown_duty(capsys: pytest.CaptureFixture) -> None:
|
|
44
|
-
"""Show help for an unknown duty.
|
|
45
|
-
|
|
46
|
-
Parameters:
|
|
47
|
-
capsys: Pytest fixture to capture output.
|
|
48
|
-
"""
|
|
49
|
-
assert cli.main(["-d", "tests/fixtures/basic.py", "-h", "not-here"]) == 0
|
|
50
|
-
captured = capsys.readouterr()
|
|
51
|
-
assert "Unknown duty" in captured.out
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_select_duties() -> None:
|
|
55
|
-
"""Run a duty."""
|
|
56
|
-
assert cli.main(["-d", "tests/fixtures/basic.py", "hello"]) == 0
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_unknown_duty() -> None:
|
|
60
|
-
"""Don't run an unknown duty."""
|
|
61
|
-
assert cli.main(["-d", "tests/fixtures/basic.py", "byebye"]) == 1
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def test_incorrect_arguments() -> None:
|
|
65
|
-
"""Use incorrect arguments."""
|
|
66
|
-
assert cli.main(["-d", "tests/fixtures/basic.py", "hello=1"]) == 1
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# we use 300 because it's slightly above the valid maximum 255
|
|
70
|
-
@pytest.mark.parametrize("code", range(-100, 300, 7))
|
|
71
|
-
def test_duty_failure(code: int) -> None:
|
|
72
|
-
"""Check exit code.
|
|
73
|
-
|
|
74
|
-
Parameters:
|
|
75
|
-
code: Code to match.
|
|
76
|
-
"""
|
|
77
|
-
assert cli.main(["-d", "tests/fixtures/code.py", "exit_with", f"code={code}"]) == code
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_multiple_duties(capfd: pytest.CaptureFixture) -> None:
|
|
81
|
-
"""Run multiple duties.
|
|
82
|
-
|
|
83
|
-
Parameters:
|
|
84
|
-
capfd: Pytest fixture to capture output.
|
|
85
|
-
"""
|
|
86
|
-
assert cli.main(["-d", "tests/fixtures/multiple.py", "first_duty", "second_duty"]) == 0
|
|
87
|
-
captured = capfd.readouterr()
|
|
88
|
-
assert "first" in captured.out
|
|
89
|
-
assert "second" in captured.out
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def test_duty_arguments(capfd: pytest.CaptureFixture) -> None:
|
|
93
|
-
"""Run duty with arguments.
|
|
94
|
-
|
|
95
|
-
Parameters:
|
|
96
|
-
capfd: Pytest fixture to capture output.
|
|
97
|
-
"""
|
|
98
|
-
assert cli.main(["-d", "tests/fixtures/arguments.py", "say_hello", "cat=fabric"]) == 0
|
|
99
|
-
captured = capfd.readouterr()
|
|
100
|
-
assert "cat fabric" in captured.out
|
|
101
|
-
assert "dog dog" in captured.out
|
|
102
|
-
|
|
103
|
-
assert cli.main(["-d", "tests/fixtures/arguments.py", "say_hello", "dog=paramiko", "cat=invoke"]) == 0
|
|
104
|
-
captured = capfd.readouterr()
|
|
105
|
-
assert "cat invoke" in captured.out
|
|
106
|
-
assert "dog paramiko" in captured.out
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def test_list_duties(capsys: pytest.CaptureFixture) -> None:
|
|
110
|
-
"""List duties.
|
|
111
|
-
|
|
112
|
-
Parameters:
|
|
113
|
-
capsys: Pytest fixture to capture output.
|
|
114
|
-
"""
|
|
115
|
-
assert cli.main(["-d", "tests/fixtures/list.py", "-l"]) == 0
|
|
116
|
-
captured = capsys.readouterr()
|
|
117
|
-
assert "Tong..." in captured.out
|
|
118
|
-
assert "DEUM!" in captured.out
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def test_global_options() -> None:
|
|
122
|
-
"""Test global options."""
|
|
123
|
-
assert cli.main(["-d", "tests/fixtures/code.py", "-z", "exit_with", "1"]) == 0
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def test_global_and_local_options() -> None:
|
|
127
|
-
"""Test global and local options."""
|
|
128
|
-
assert cli.main(["-d", "tests/fixtures/code.py", "-z", "exit_with", "-Z", "1"]) == 1
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def test_options_precedence() -> None:
|
|
132
|
-
"""Test options precedence."""
|
|
133
|
-
# @duty(nofail=True) is overridden by ctx.run(nofail=False)
|
|
134
|
-
assert cli.main(["-d", "tests/fixtures/precedence.py", "precedence"]) == 1
|
|
135
|
-
|
|
136
|
-
# ctx.run(nofail=False) is overridden by local option -z
|
|
137
|
-
assert cli.main(["-d", "tests/fixtures/precedence.py", "precedence", "-z"]) == 0
|
|
138
|
-
|
|
139
|
-
# ctx.run(nofail=False) is overridden by global option -z
|
|
140
|
-
assert cli.main(["-d", "tests/fixtures/precedence.py", "-z", "precedence"]) == 0
|
|
141
|
-
|
|
142
|
-
# global option -z is overridden by local option -z
|
|
143
|
-
assert cli.main(["-d", "tests/fixtures/precedence.py", "-z", "precedence", "-Z"]) == 1
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# test options precedence (CLI option, env var, ctx.run, @duty
|
|
147
|
-
# test positional arguments
|
|
148
|
-
# test extra keyword arguments
|
|
149
|
-
# test complete (global options + local options + multi duties + positional args + keyword args + extra keyword args)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
@pytest.mark.parametrize(
|
|
153
|
-
("param", "expected"),
|
|
154
|
-
[
|
|
155
|
-
("", 1),
|
|
156
|
-
("n", 1),
|
|
157
|
-
("N", 1),
|
|
158
|
-
("no", 1),
|
|
159
|
-
("NO", 1),
|
|
160
|
-
("false", 1),
|
|
161
|
-
("FALSE", 1),
|
|
162
|
-
("off", 1),
|
|
163
|
-
("OFF", 1),
|
|
164
|
-
("zero=", 1),
|
|
165
|
-
("zero=0", 1),
|
|
166
|
-
("zero=n", 1),
|
|
167
|
-
("zero=N", 1),
|
|
168
|
-
("zero=no", 1),
|
|
169
|
-
("zero=NO", 1),
|
|
170
|
-
("zero=false", 1),
|
|
171
|
-
("zero=FALSE", 1),
|
|
172
|
-
("zero=off", 1),
|
|
173
|
-
("zero=OFF", 1),
|
|
174
|
-
("y", 0),
|
|
175
|
-
("Y", 0),
|
|
176
|
-
("yes", 0),
|
|
177
|
-
("YES", 0),
|
|
178
|
-
("on", 0),
|
|
179
|
-
("ON", 0),
|
|
180
|
-
("true", 0),
|
|
181
|
-
("TRUE", 0),
|
|
182
|
-
("anything else", 0),
|
|
183
|
-
("-1", 0),
|
|
184
|
-
("1", 0),
|
|
185
|
-
("zero=y", 0),
|
|
186
|
-
("zero=Y", 0),
|
|
187
|
-
("zero=yes", 0),
|
|
188
|
-
("zero=YES", 0),
|
|
189
|
-
("zero=on", 0),
|
|
190
|
-
("zero=ON", 0),
|
|
191
|
-
("zero=true", 0),
|
|
192
|
-
("zero=TRUE", 0),
|
|
193
|
-
("zero=anything else", 0),
|
|
194
|
-
("zero=-1", 0),
|
|
195
|
-
("zero=1", 0),
|
|
196
|
-
],
|
|
197
|
-
)
|
|
198
|
-
def test_cast_bool_parameter(param: str, expected: int) -> None:
|
|
199
|
-
"""Test parameters casting as boolean.
|
|
200
|
-
|
|
201
|
-
Parameters:
|
|
202
|
-
param: Pytest parametrization fixture.
|
|
203
|
-
expected: Pytest parametrization fixture.
|
|
204
|
-
"""
|
|
205
|
-
assert cli.main(["-d", "tests/fixtures/booleans.py", "boolean", param]) == expected
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def test_invalid_params(capsys: pytest.CaptureFixture) -> None:
|
|
209
|
-
"""Check that invalid parameters are early and correctly detected.
|
|
210
|
-
|
|
211
|
-
Parameters:
|
|
212
|
-
capsys: Pytest fixture to capture output.
|
|
213
|
-
"""
|
|
214
|
-
assert cli.main(["-d", "tests/fixtures/booleans.py", "boolean", "zore=off"]) == 1
|
|
215
|
-
captured = capsys.readouterr()
|
|
216
|
-
assert "unexpected keyword argument 'zore'" in captured.err
|
|
217
|
-
|
|
218
|
-
assert cli.main(["-d", "tests/fixtures/code.py", "exit_with"]) == 1
|
|
219
|
-
captured = capsys.readouterr()
|
|
220
|
-
assert "missing 1 required positional argument: 'code'" in captured.err
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def test_show_version(capsys: pytest.CaptureFixture) -> None:
|
|
224
|
-
"""Show version.
|
|
225
|
-
|
|
226
|
-
Parameters:
|
|
227
|
-
capsys: Pytest fixture to capture output.
|
|
228
|
-
"""
|
|
229
|
-
with pytest.raises(SystemExit):
|
|
230
|
-
cli.main(["-V"])
|
|
231
|
-
captured = capsys.readouterr()
|
|
232
|
-
assert debug.get_version() in captured.out
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def test_show_debug_info(capsys: pytest.CaptureFixture) -> None:
|
|
236
|
-
"""Show debug information.
|
|
237
|
-
|
|
238
|
-
Parameters:
|
|
239
|
-
capsys: Pytest fixture to capture output.
|
|
240
|
-
"""
|
|
241
|
-
with pytest.raises(SystemExit):
|
|
242
|
-
cli.main(["--debug-info"])
|
|
243
|
-
captured = capsys.readouterr().out.lower()
|
|
244
|
-
assert "python" in captured
|
|
245
|
-
assert "system" in captured
|
|
246
|
-
assert "environment" in captured
|
|
247
|
-
assert "packages" in captured
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"""Tests for the `collection` module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from duty.collection import Collection, Duty
|
|
8
|
-
from duty.decorator import duty as decorate
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def none(*args, **kwargs) -> None: # noqa: ANN002, ANN003, ARG001, D103
|
|
12
|
-
... # pragma: no cover
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_instantiate_duty() -> None:
|
|
16
|
-
"""Instantiate a duty."""
|
|
17
|
-
assert Duty("name", "description", none)
|
|
18
|
-
assert Duty("name", "description", none, pre=["0", "1"], post=["2"])
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_dont_get_duty() -> None:
|
|
22
|
-
"""Don't find a duty."""
|
|
23
|
-
collection = Collection()
|
|
24
|
-
with pytest.raises(KeyError):
|
|
25
|
-
collection.get("hello")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def test_register_aliases() -> None:
|
|
29
|
-
"""Register a duty and its aliases."""
|
|
30
|
-
duty = decorate(none, name="hello", aliases=["HELLO", "_hello_", ".hello."]) # type: ignore[call-overload]
|
|
31
|
-
collection = Collection()
|
|
32
|
-
collection.add(duty)
|
|
33
|
-
assert collection.get("hello")
|
|
34
|
-
assert collection.get("HELLO")
|
|
35
|
-
assert collection.get("_hello_")
|
|
36
|
-
assert collection.get(".hello.")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_replace_name_and_set_alias() -> None:
|
|
40
|
-
"""Replace underscores by dashes in duties names."""
|
|
41
|
-
collection = Collection()
|
|
42
|
-
collection.add(decorate(none, name="snake_case")) # type: ignore[call-overload]
|
|
43
|
-
assert collection.get("snake_case") is collection.get("snake-case")
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def test_clear_collection() -> None:
|
|
47
|
-
"""Check that duties and their aliases are correctly cleared from a collection."""
|
|
48
|
-
collection = Collection()
|
|
49
|
-
collection.add(decorate(none, name="duty_1")) # type: ignore[call-overload]
|
|
50
|
-
collection.clear()
|
|
51
|
-
with pytest.raises(KeyError):
|
|
52
|
-
collection.get("duty-1")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_add_duty_to_multiple_collections() -> None:
|
|
56
|
-
"""Check what happens when adding the same duty to multiple collections."""
|
|
57
|
-
collection1 = Collection()
|
|
58
|
-
collection2 = Collection()
|
|
59
|
-
|
|
60
|
-
duty = decorate(none, name="duty") # type: ignore[call-overload]
|
|
61
|
-
|
|
62
|
-
collection1.add(duty)
|
|
63
|
-
collection2.add(duty)
|
|
64
|
-
|
|
65
|
-
duty1 = collection1.get("duty")
|
|
66
|
-
duty2 = collection2.get("duty")
|
|
67
|
-
|
|
68
|
-
assert duty1 is not duty2
|
|
69
|
-
assert duty1.collection is collection1
|
|
70
|
-
assert duty2.collection is collection2
|
duty-1.1.0/tests/test_context.py
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""Tests for the `context` module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections import namedtuple
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from duty import context
|
|
11
|
-
from duty.exceptions import DutyFailure
|
|
12
|
-
|
|
13
|
-
RunResult = namedtuple("RunResult", "code output") # noqa: PYI024
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_allow_overrides(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
17
|
-
"""Test the `allow_overrides` option.
|
|
18
|
-
|
|
19
|
-
Parameters:
|
|
20
|
-
monkeypatch: A Pytest fixture to monkeypatch objects.
|
|
21
|
-
"""
|
|
22
|
-
ctx = context.Context({"a": 1}, {"a": 2})
|
|
23
|
-
records = []
|
|
24
|
-
monkeypatch.setattr(context, "failprint_run", lambda _, **opts: RunResult(records.append(opts), "")) # type: ignore[func-returns-value]
|
|
25
|
-
ctx.run("")
|
|
26
|
-
ctx.run("", allow_overrides=False)
|
|
27
|
-
ctx.run("", allow_overrides=True)
|
|
28
|
-
ctx.run("", allow_overrides=False, a=3)
|
|
29
|
-
assert records[0]["a"] == 2
|
|
30
|
-
assert records[1]["a"] == 1
|
|
31
|
-
assert records[2]["a"] == 2
|
|
32
|
-
assert records[3]["a"] == 3
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def test_options_context_manager(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
36
|
-
"""Test changing options using the context manager.
|
|
37
|
-
|
|
38
|
-
Parameters:
|
|
39
|
-
monkeypatch: A Pytest fixture to monkeypatch objects.
|
|
40
|
-
"""
|
|
41
|
-
ctx = context.Context({"a": 1}, {"a": 2})
|
|
42
|
-
records = []
|
|
43
|
-
monkeypatch.setattr(context, "failprint_run", lambda _, **opts: RunResult(records.append(opts), "")) # type: ignore[func-returns-value]
|
|
44
|
-
|
|
45
|
-
with ctx.options(a=3):
|
|
46
|
-
ctx.run("") # should be overridden by 2
|
|
47
|
-
with ctx.options(a=4, allow_overrides=False):
|
|
48
|
-
ctx.run("") # should be 4
|
|
49
|
-
ctx.run("", allow_overrides=True) # should be 2
|
|
50
|
-
ctx.run("", allow_overrides=False) # should be 3
|
|
51
|
-
|
|
52
|
-
assert records[0]["a"] == 2
|
|
53
|
-
assert records[1]["a"] == 4
|
|
54
|
-
assert records[2]["a"] == 2
|
|
55
|
-
assert records[3]["a"] == 3
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def test_workdir(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
59
|
-
"""Test the `workdir` option.
|
|
60
|
-
|
|
61
|
-
Parameters:
|
|
62
|
-
monkeypatch: A Pytest fixture to monkeypatch objects.
|
|
63
|
-
"""
|
|
64
|
-
ctx = context.Context({})
|
|
65
|
-
monkeypatch.setattr(context, "failprint_run", lambda _: RunResult(len(Path.cwd().parts), ""))
|
|
66
|
-
records = []
|
|
67
|
-
with pytest.raises(DutyFailure) as failure:
|
|
68
|
-
ctx.run("")
|
|
69
|
-
records.append(failure.value.code)
|
|
70
|
-
with pytest.raises(DutyFailure) as failure:
|
|
71
|
-
ctx.run("", workdir="..")
|
|
72
|
-
records.append(failure.value.code)
|
|
73
|
-
assert records[0] == records[1] + 1
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def test_workdir_as_context_manager(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
77
|
-
"""Test the `workdir` option as a context manager, and the `cd` context manager.
|
|
78
|
-
|
|
79
|
-
Parameters:
|
|
80
|
-
monkeypatch: A Pytest fixture to monkeypatch objects.
|
|
81
|
-
"""
|
|
82
|
-
ctx = context.Context({})
|
|
83
|
-
monkeypatch.setattr(context, "failprint_run", lambda _: RunResult(len(Path.cwd().parts), ""))
|
|
84
|
-
records = []
|
|
85
|
-
with pytest.raises(DutyFailure) as failure, ctx.options(workdir=".."):
|
|
86
|
-
ctx.run("")
|
|
87
|
-
records.append(failure.value.code)
|
|
88
|
-
with pytest.raises(DutyFailure) as failure, ctx.cd("../.."):
|
|
89
|
-
ctx.run("")
|
|
90
|
-
records.append(failure.value.code)
|
|
91
|
-
with pytest.raises(DutyFailure) as failure, ctx.cd(".."), ctx.options(workdir="../.."):
|
|
92
|
-
ctx.run("")
|
|
93
|
-
records.append(failure.value.code)
|
|
94
|
-
with pytest.raises(DutyFailure) as failure, ctx.cd("../../.."):
|
|
95
|
-
ctx.run("", workdir="..")
|
|
96
|
-
records.append(failure.value.code)
|
|
97
|
-
|
|
98
|
-
base = records[0]
|
|
99
|
-
assert records == [base, base - 1, base - 2, base - 3]
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"""Tests for the `decorator` module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import inspect
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
from duty.context import Context
|
|
10
|
-
from duty.decorator import duty as decorate
|
|
11
|
-
from duty.exceptions import DutyFailure
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_accept_one_posarg_when_decorating() -> None:
|
|
15
|
-
"""Accept only one positional argument when decorating."""
|
|
16
|
-
with pytest.raises(ValueError, match="accepts only one positional argument"):
|
|
17
|
-
decorate(0, 1) # type: ignore[call-overload]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_skipping() -> None:
|
|
21
|
-
"""Wrap function that must be skipped."""
|
|
22
|
-
duty = decorate(lambda ctx: ctx.run("false"), skip_if=True) # type: ignore[call-overload]
|
|
23
|
-
# no DutyFailure raised
|
|
24
|
-
assert duty.run() is None
|
|
25
|
-
with pytest.raises(DutyFailure):
|
|
26
|
-
assert inspect.unwrap(duty)(Context({}))
|
duty-1.1.0/tests/test_running.py
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
"""Tests about running duties."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import NoReturn
|
|
6
|
-
from unittest.mock import NonCallableMock
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from duty.collection import Collection, Duty
|
|
11
|
-
from duty.decorator import duty as decorate
|
|
12
|
-
from duty.exceptions import DutyFailure
|
|
13
|
-
|
|
14
|
-
INTERRUPT_CODE = 130
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_run_duty() -> None:
|
|
18
|
-
"""Run a duty."""
|
|
19
|
-
duty = Duty("name", "description", lambda ctx: 1)
|
|
20
|
-
assert duty.run() is None # type: ignore[func-returns-value]
|
|
21
|
-
assert duty(duty.context) is None # type: ignore[func-returns-value]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def test_run_pre_post_duties_lambdas() -> None:
|
|
25
|
-
"""Run pre- and post- duties as lambdas."""
|
|
26
|
-
pre_calls = []
|
|
27
|
-
post_calls = []
|
|
28
|
-
|
|
29
|
-
duty = Duty(
|
|
30
|
-
"name",
|
|
31
|
-
"description",
|
|
32
|
-
lambda ctx: None,
|
|
33
|
-
pre=[lambda ctx: pre_calls.append(True)],
|
|
34
|
-
post=[lambda ctx: post_calls.append(True)],
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
duty.run()
|
|
38
|
-
|
|
39
|
-
assert pre_calls[0] is True
|
|
40
|
-
assert post_calls[0] is True
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_run_pre_post_duties_instances() -> None:
|
|
44
|
-
"""Run pre- and post- duties as duties."""
|
|
45
|
-
pre_calls = []
|
|
46
|
-
post_calls = []
|
|
47
|
-
|
|
48
|
-
pre_duty = Duty("pre", "", lambda ctx: pre_calls.append(True))
|
|
49
|
-
post_duty = Duty("post", "", lambda ctx: post_calls.append(True))
|
|
50
|
-
|
|
51
|
-
duty = Duty(
|
|
52
|
-
name="name",
|
|
53
|
-
description="description",
|
|
54
|
-
function=lambda ctx: None,
|
|
55
|
-
pre=[pre_duty],
|
|
56
|
-
post=[post_duty],
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
duty.run()
|
|
60
|
-
|
|
61
|
-
assert pre_calls[0] is True
|
|
62
|
-
assert post_calls[0] is True
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_run_pre_post_duties_refs() -> None:
|
|
66
|
-
"""Run pre- and post- duties as duties references."""
|
|
67
|
-
pre_calls = []
|
|
68
|
-
post_calls = []
|
|
69
|
-
|
|
70
|
-
collection = Collection()
|
|
71
|
-
collection.add(decorate(lambda ctx: pre_calls.append(True), name="pre")) # type: ignore[call-overload]
|
|
72
|
-
collection.add(decorate(lambda ctx: post_calls.append(True), name="post")) # type: ignore[call-overload]
|
|
73
|
-
|
|
74
|
-
duty = Duty("name", "description", lambda ctx: None, collection=collection, pre=["pre"], post=["post"])
|
|
75
|
-
duty.run()
|
|
76
|
-
|
|
77
|
-
assert pre_calls[0] is True
|
|
78
|
-
assert post_calls[0] is True
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def test_dont_run_other_pre_post_duties() -> None:
|
|
82
|
-
"""Don't run other types of pre- and post- duties."""
|
|
83
|
-
pre_duty = NonCallableMock()
|
|
84
|
-
post_duty = NonCallableMock()
|
|
85
|
-
|
|
86
|
-
duty = Duty("name", "description", lambda ctx: 0, pre=[pre_duty], post=[post_duty])
|
|
87
|
-
duty.run()
|
|
88
|
-
|
|
89
|
-
assert not pre_duty.called
|
|
90
|
-
assert not post_duty.called
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def test_code_when_keyboard_interrupt() -> None:
|
|
94
|
-
"""Return a code 130 on keyboard interruption."""
|
|
95
|
-
|
|
96
|
-
def interrupt() -> NoReturn:
|
|
97
|
-
raise KeyboardInterrupt
|
|
98
|
-
|
|
99
|
-
with pytest.raises(DutyFailure) as excinfo:
|
|
100
|
-
Duty("name", "description", lambda ctx: ctx.run(interrupt)).run()
|
|
101
|
-
assert excinfo.value.code == INTERRUPT_CODE
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def test_dont_raise_duty_failure() -> None:
|
|
105
|
-
"""Don't raise a duty failure on success."""
|
|
106
|
-
duty = Duty("n", "d", lambda ctx: ctx.run(lambda: 0))
|
|
107
|
-
assert not duty.run() # type: ignore[func-returns-value]
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def test_cant_find_duty_without_collection() -> None:
|
|
111
|
-
"""Check that we can't find a duty with its name without a collection."""
|
|
112
|
-
duty = decorate(lambda ctx: None, name="duty1", post=["duty2"]) # type: ignore[call-overload]
|
|
113
|
-
with pytest.raises(RuntimeError):
|
|
114
|
-
duty.run()
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
"""Tests for the `validation` module."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from inspect import Parameter
|
|
6
|
-
from typing import Any, Callable
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from duty.validation import _get_params_caster, cast_arg, to_bool
|
|
11
|
-
from tests.fixtures import validation as valfix
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@pytest.mark.parametrize(
|
|
15
|
-
("value", "expected"),
|
|
16
|
-
[
|
|
17
|
-
("y", True),
|
|
18
|
-
("Y", True),
|
|
19
|
-
("yes", True),
|
|
20
|
-
("YES", True),
|
|
21
|
-
("on", True),
|
|
22
|
-
("ON", True),
|
|
23
|
-
("true", True),
|
|
24
|
-
("TRUE", True),
|
|
25
|
-
("anything else", True),
|
|
26
|
-
("-1", True),
|
|
27
|
-
("1", True),
|
|
28
|
-
("", False),
|
|
29
|
-
("n", False),
|
|
30
|
-
("N", False),
|
|
31
|
-
("no", False),
|
|
32
|
-
("NO", False),
|
|
33
|
-
("false", False),
|
|
34
|
-
("FALSE", False),
|
|
35
|
-
("off", False),
|
|
36
|
-
("OFF", False),
|
|
37
|
-
],
|
|
38
|
-
)
|
|
39
|
-
def test_bool_casting(value: str, expected: bool) -> None:
|
|
40
|
-
"""Check that we correctly cast string values to booleans.
|
|
41
|
-
|
|
42
|
-
Parameters:
|
|
43
|
-
value: The value to cast.
|
|
44
|
-
expected: The expected result.
|
|
45
|
-
"""
|
|
46
|
-
assert to_bool(value) == expected
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class CustomType1:
|
|
50
|
-
"""Dummy type to test type-casting."""
|
|
51
|
-
|
|
52
|
-
def __init__(self, value: str): # noqa: D107
|
|
53
|
-
self.value = value
|
|
54
|
-
|
|
55
|
-
def __eq__(self, other: object):
|
|
56
|
-
return self.value == other.value # type: ignore[attr-defined]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class CustomType2:
|
|
60
|
-
"""Dummy type to test type-casting."""
|
|
61
|
-
|
|
62
|
-
def __init__(self, value, extra): # noqa: ANN001,D107
|
|
63
|
-
... # pragma: no cover
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@pytest.mark.parametrize(
|
|
67
|
-
("arg", "annotation", "expected"),
|
|
68
|
-
[
|
|
69
|
-
("hello", Parameter.empty, "hello"),
|
|
70
|
-
("off", bool, False),
|
|
71
|
-
("on", bool, True),
|
|
72
|
-
("1", int, 1),
|
|
73
|
-
("1", float, 1.0),
|
|
74
|
-
("fie", str, "fie"),
|
|
75
|
-
("fih", CustomType1, CustomType1("fih")),
|
|
76
|
-
("foh", CustomType2, "foh"),
|
|
77
|
-
],
|
|
78
|
-
)
|
|
79
|
-
def test_cast_arg(arg: str, annotation: Any, expected: Any) -> None:
|
|
80
|
-
"""Check that arguments are properly casted given an annotation.
|
|
81
|
-
|
|
82
|
-
Parameters:
|
|
83
|
-
arg: The argument value to cast.
|
|
84
|
-
annotation: The annotation to use.
|
|
85
|
-
expected: The expected result.
|
|
86
|
-
"""
|
|
87
|
-
assert cast_arg(arg, annotation) == expected
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
_parametrization = [
|
|
91
|
-
(valfix.no_params, (), {}, (), {}),
|
|
92
|
-
(valfix.pos_or_kw_param, ("1",), {}, (1,), {}),
|
|
93
|
-
(valfix.pos_or_kw_param, (), {"a": "1"}, (), {"a": 1}),
|
|
94
|
-
(valfix.pos_or_kw_params, ("1", "2"), {}, (1, 2), {}),
|
|
95
|
-
(valfix.pos_or_kw_params, ("1",), {"b": "2"}, (1,), {"b": 2}),
|
|
96
|
-
(valfix.pos_or_kw_params, (), {"a": "1", "b": "2"}, (), {"a": 1, "b": 2}),
|
|
97
|
-
(valfix.varpos_param, (), {}, (), {}),
|
|
98
|
-
(valfix.varpos_param, ("1", "2"), {}, (1, 2), {}),
|
|
99
|
-
(valfix.pos_and_varpos_param, ("1",), {}, (1,), {}),
|
|
100
|
-
(valfix.pos_and_varpos_param, ("1", "2"), {}, (1, 2), {}),
|
|
101
|
-
(valfix.pos_and_varpos_param, ("1", "2", "3"), {}, (1, 2, 3), {}),
|
|
102
|
-
(valfix.kwonly_param, (), {"b": "1"}, (), {"b": 1}),
|
|
103
|
-
(valfix.kwonly_param, ("2",), {"b": "1"}, (2,), {"b": 1}),
|
|
104
|
-
(valfix.kwonly_param, ("2", "3"), {"b": "1"}, (2, 3), {"b": 1}),
|
|
105
|
-
(valfix.varkw_param, ("1",), {}, (1,), {}),
|
|
106
|
-
(valfix.varkw_param, ("1",), {"b": "2"}, (1,), {"b": 2}),
|
|
107
|
-
(valfix.varkw_param, ("1",), {"b": "2", "c": "3"}, (1,), {"b": 2, "c": 3}),
|
|
108
|
-
(valfix.varkw_no_annotation, (), {"a": "1"}, (), {"a": "1"}),
|
|
109
|
-
(valfix.posonly_marker, ("1", "2"), {}, (1, 2), {}),
|
|
110
|
-
(valfix.posonly_marker, ("1",), {"b": "2"}, (1,), {"b": 2}),
|
|
111
|
-
(valfix.kwonly_marker, ("1",), {"b": "2"}, (1,), {"b": 2}),
|
|
112
|
-
(valfix.kwonly_marker, (), {"a": "1", "b": "2"}, (), {"a": 1, "b": 2}),
|
|
113
|
-
(valfix.only_markers, ("1",), {"b": "2", "c": "3"}, (1,), {"b": 2, "c": 3}),
|
|
114
|
-
(valfix.only_markers, ("1", "2"), {"c": "3"}, (1, 2), {"c": 3}),
|
|
115
|
-
(valfix.full, ("1", "2", "3", "4"), {"d": "5", "e": "6", "f": "7"}, (1, 2, 3, 4), {"d": 5, "e": 6, "f": 7}),
|
|
116
|
-
]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
@pytest.mark.parametrize(
|
|
120
|
-
("func", "args", "kwargs", "expected_args", "expected_kwargs"),
|
|
121
|
-
_parametrization,
|
|
122
|
-
)
|
|
123
|
-
def test_params_caster(func: Callable, args: tuple, kwargs: dict, expected_args: tuple, expected_kwargs: dict) -> None:
|
|
124
|
-
"""Test the whole parameters casting helper class.
|
|
125
|
-
|
|
126
|
-
Parameters:
|
|
127
|
-
func: The function to work with.
|
|
128
|
-
args: The positional arguments to cast.
|
|
129
|
-
kwargs: The keyword arguments to cast.
|
|
130
|
-
expected_args: The expected positional arguments result.
|
|
131
|
-
expected_kwargs: The expected keyword arguments result.
|
|
132
|
-
"""
|
|
133
|
-
caster = _get_params_caster(func, *args, **kwargs)
|
|
134
|
-
new_args, new_kwargs = caster.cast(*args, **kwargs)
|
|
135
|
-
assert new_args == expected_args
|
|
136
|
-
assert new_kwargs == expected_kwargs
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def test_casting_based_on_default_value_type() -> None:
|
|
140
|
-
"""Test that we cast according to the default value type when there is no annotation."""
|
|
141
|
-
|
|
142
|
-
def func(ctx, a=0): # noqa: ANN202,ARG001,ANN001
|
|
143
|
-
...
|
|
144
|
-
|
|
145
|
-
caster = _get_params_caster(func, a="1")
|
|
146
|
-
_, kwargs = caster.cast(a="1")
|
|
147
|
-
assert kwargs == {"a": 1}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|