duty 1.4.1__tar.gz → 1.4.3__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.
Files changed (99) hide show
  1. {duty-1.4.1 → duty-1.4.3}/CHANGELOG.md +16 -0
  2. {duty-1.4.1 → duty-1.4.3}/CONTRIBUTING.md +2 -3
  3. {duty-1.4.1 → duty-1.4.3}/PKG-INFO +5 -9
  4. {duty-1.4.1 → duty-1.4.3}/README.md +2 -6
  5. {duty-1.4.1 → duty-1.4.3}/config/ruff.toml +1 -1
  6. {duty-1.4.1 → duty-1.4.3}/duties.py +5 -3
  7. {duty-1.4.1 → duty-1.4.3}/mkdocs.yml +4 -2
  8. {duty-1.4.1 → duty-1.4.3}/pyproject.toml +32 -4
  9. {duty-1.4.1 → duty-1.4.3}/scripts/gen_credits.py +6 -6
  10. {duty-1.4.1 → duty-1.4.3}/scripts/make +42 -62
  11. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/blacken_docs.py +9 -1
  12. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/ruff.py +32 -32
  13. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/safety.py +4 -1
  14. {duty-1.4.1 → duty-1.4.3}/src/duty/collection.py +2 -2
  15. {duty-1.4.1 → duty-1.4.3}/src/duty/context.py +5 -2
  16. {duty-1.4.1 → duty-1.4.3}/src/duty/decorator.py +3 -1
  17. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_blacken_docs.py +10 -1
  18. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_ruff.py +33 -33
  19. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_safety.py +4 -1
  20. {duty-1.4.1 → duty-1.4.3}/src/duty/validation.py +5 -2
  21. {duty-1.4.1 → duty-1.4.3}/tests/test_collection.py +1 -1
  22. {duty-1.4.1 → duty-1.4.3}/tests/test_context.py +5 -1
  23. {duty-1.4.1 → duty-1.4.3}/tests/test_validation.py +2 -2
  24. duty-1.4.1/devdeps.txt +0 -32
  25. {duty-1.4.1 → duty-1.4.3}/CODE_OF_CONDUCT.md +0 -0
  26. {duty-1.4.1 → duty-1.4.3}/LICENSE +0 -0
  27. {duty-1.4.1 → duty-1.4.3}/config/coverage.ini +0 -0
  28. {duty-1.4.1 → duty-1.4.3}/config/git-changelog.toml +0 -0
  29. {duty-1.4.1 → duty-1.4.3}/config/mypy.ini +0 -0
  30. {duty-1.4.1 → duty-1.4.3}/config/pytest.ini +0 -0
  31. {duty-1.4.1 → duty-1.4.3}/config/vscode/launch.json +0 -0
  32. {duty-1.4.1 → duty-1.4.3}/config/vscode/settings.json +0 -0
  33. {duty-1.4.1 → duty-1.4.3}/config/vscode/tasks.json +0 -0
  34. {duty-1.4.1 → duty-1.4.3}/docs/.overrides/main.html +0 -0
  35. {duty-1.4.1 → duty-1.4.3}/docs/.overrides/partials/comments.html +0 -0
  36. {duty-1.4.1 → duty-1.4.3}/docs/changelog.md +0 -0
  37. {duty-1.4.1 → duty-1.4.3}/docs/code_of_conduct.md +0 -0
  38. {duty-1.4.1 → duty-1.4.3}/docs/contributing.md +0 -0
  39. {duty-1.4.1 → duty-1.4.3}/docs/credits.md +0 -0
  40. {duty-1.4.1 → duty-1.4.3}/docs/css/material.css +0 -0
  41. {duty-1.4.1 → duty-1.4.3}/docs/css/mkdocstrings.css +0 -0
  42. {duty-1.4.1 → duty-1.4.3}/docs/demo.svg +0 -0
  43. {duty-1.4.1 → duty-1.4.3}/docs/gen_credits.py +0 -0
  44. {duty-1.4.1 → duty-1.4.3}/docs/index.md +0 -0
  45. {duty-1.4.1 → duty-1.4.3}/docs/js/feedback.js +0 -0
  46. {duty-1.4.1 → duty-1.4.3}/docs/license.md +0 -0
  47. {duty-1.4.1 → duty-1.4.3}/docs/usage.md +0 -0
  48. {duty-1.4.1 → duty-1.4.3}/scripts/gen_ref_nav.py +0 -0
  49. {duty-1.4.1 → duty-1.4.3}/src/duty/__init__.py +0 -0
  50. {duty-1.4.1 → duty-1.4.3}/src/duty/__main__.py +0 -0
  51. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/__init__.py +0 -0
  52. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/_io.py +0 -0
  53. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/autoflake.py +0 -0
  54. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/black.py +0 -0
  55. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/build.py +0 -0
  56. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/coverage.py +0 -0
  57. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/flake8.py +0 -0
  58. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/git_changelog.py +0 -0
  59. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/griffe.py +0 -0
  60. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/interrogate.py +0 -0
  61. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/isort.py +0 -0
  62. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/mkdocs.py +0 -0
  63. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/mypy.py +0 -0
  64. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/pytest.py +0 -0
  65. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/ssort.py +0 -0
  66. {duty-1.4.1 → duty-1.4.3}/src/duty/callables/twine.py +0 -0
  67. {duty-1.4.1 → duty-1.4.3}/src/duty/cli.py +0 -0
  68. {duty-1.4.1 → duty-1.4.3}/src/duty/debug.py +0 -0
  69. {duty-1.4.1 → duty-1.4.3}/src/duty/exceptions.py +0 -0
  70. {duty-1.4.1 → duty-1.4.3}/src/duty/py.typed +0 -0
  71. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/__init__.py +0 -0
  72. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_autoflake.py +0 -0
  73. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_base.py +0 -0
  74. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_black.py +0 -0
  75. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_build.py +0 -0
  76. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_coverage.py +0 -0
  77. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_flake8.py +0 -0
  78. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_git_changelog.py +0 -0
  79. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_griffe.py +0 -0
  80. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_interrogate.py +0 -0
  81. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_isort.py +0 -0
  82. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_mkdocs.py +0 -0
  83. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_mypy.py +0 -0
  84. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_pytest.py +0 -0
  85. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_ssort.py +0 -0
  86. {duty-1.4.1 → duty-1.4.3}/src/duty/tools/_twine.py +0 -0
  87. {duty-1.4.1 → duty-1.4.3}/tests/__init__.py +0 -0
  88. {duty-1.4.1 → duty-1.4.3}/tests/conftest.py +0 -0
  89. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/arguments.py +0 -0
  90. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/basic.py +0 -0
  91. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/booleans.py +0 -0
  92. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/code.py +0 -0
  93. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/list.py +0 -0
  94. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/multiple.py +0 -0
  95. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/precedence.py +0 -0
  96. {duty-1.4.1 → duty-1.4.3}/tests/fixtures/validation.py +0 -0
  97. {duty-1.4.1 → duty-1.4.3}/tests/test_cli.py +0 -0
  98. {duty-1.4.1 → duty-1.4.3}/tests/test_decorator.py +0 -0
  99. {duty-1.4.1 → duty-1.4.3}/tests/test_running.py +0 -0
@@ -5,6 +5,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  <!-- insertion marker -->
8
+ ## [1.4.3](https://github.com/pawamoy/duty/releases/tag/1.4.3) - 2024-10-17
9
+
10
+ <small>[Compare with 1.4.2](https://github.com/pawamoy/duty/compare/1.4.2...1.4.3)</small>
11
+
12
+ ### Build
13
+
14
+ - Drop support for Python 3.8 ([4f5d6ec](https://github.com/pawamoy/duty/commit/4f5d6ecbb0a84e5c42cab4d584239f16e8397d86) by Timothée Mazzucotelli).
15
+
16
+ ## [1.4.2](https://github.com/pawamoy/duty/releases/tag/1.4.2) - 2024-09-10
17
+
18
+ <small>[Compare with 1.4.1](https://github.com/pawamoy/duty/compare/1.4.1...1.4.2)</small>
19
+
20
+ ### Bug Fixes
21
+
22
+ - Add missing (new) `check_only` argument to blacken-docs ([1a6dc99](https://github.com/pawamoy/duty/commit/1a6dc995dc0caa47d8734a9feb54eb766652cadc) by Timothée Mazzucotelli). [Issue-22](https://github.com/pawamoy/duty/issues/22)
23
+
8
24
  ## [1.4.1](https://github.com/pawamoy/duty/releases/tag/1.4.1) - 2024-08-15
9
25
 
10
26
  <small>[Compare with 1.4.0](https://github.com/pawamoy/duty/compare/1.4.0...1.4.1)</small>
@@ -23,12 +23,11 @@ make setup
23
23
  > You can install it with:
24
24
  >
25
25
  > ```bash
26
- > python3 -m pip install --user pipx
27
- > pipx install uv
26
+ > curl -LsSf https://astral.sh/uv/install.sh | sh
28
27
  > ```
29
28
  >
30
29
  > Now you can try running `make setup` again,
31
- > or simply `uv install`.
30
+ > or simply `uv sync`.
32
31
 
33
32
  You now have the dependencies installed.
34
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: duty
3
- Version: 1.4.1
3
+ Version: 1.4.3
4
4
  Summary: A simple task runner.
5
5
  Keywords: task-runner,task,runner,cross-platform
6
6
  Author-Email: =?utf-8?q?Timoth=C3=A9e_Mazzucotelli?= <dev@pawamoy.fr>
@@ -10,12 +10,12 @@ Classifier: Intended Audience :: Developers
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3 :: Only
13
- Classifier: Programming Language :: Python :: 3.8
14
13
  Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
19
  Classifier: Topic :: Documentation
20
20
  Classifier: Topic :: Software Development
21
21
  Classifier: Topic :: Utilities
@@ -28,7 +28,7 @@ Project-URL: Issues, https://github.com/pawamoy/duty/issues
28
28
  Project-URL: Discussions, https://github.com/pawamoy/duty/discussions
29
29
  Project-URL: Gitter, https://gitter.im/duty/community
30
30
  Project-URL: Funding, https://github.com/sponsors/pawamoy
31
- Requires-Python: >=3.8
31
+ Requires-Python: >=3.9
32
32
  Requires-Dist: eval-type-backport; python_version < "3.10"
33
33
  Requires-Dist: failprint!=1.0.0,>=0.11
34
34
  Requires-Dist: typing-extensions>=4.0; python_version < "3.11"
@@ -39,7 +39,6 @@ Description-Content-Type: text/markdown
39
39
  [![ci](https://github.com/pawamoy/duty/workflows/ci/badge.svg)](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
40
40
  [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://pawamoy.github.io/duty/)
41
41
  [![pypi version](https://img.shields.io/pypi/v/duty.svg)](https://pypi.org/project/duty/)
42
- [![gitpod](https://img.shields.io/badge/gitpod-workspace-708FCC.svg?style=flat)](https://gitpod.io/#https://github.com/pawamoy/duty)
43
42
  [![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#duty:gitter.im)
44
43
 
45
44
  A simple task runner.
@@ -50,17 +49,14 @@ Inspired by [Invoke](https://github.com/pyinvoke/invoke).
50
49
 
51
50
  ## Installation
52
51
 
53
- With `pip`:
54
-
55
52
  ```bash
56
53
  pip install duty
57
54
  ```
58
55
 
59
- With [`pipx`](https://github.com/pipxproject/pipx):
56
+ With [`uv`](https://docs.astral.sh/uv/):
60
57
 
61
58
  ```bash
62
- python3.8 -m pip install --user pipx
63
- pipx install duty
59
+ uv tool install duty
64
60
  ```
65
61
 
66
62
  ## Quick start
@@ -3,7 +3,6 @@
3
3
  [![ci](https://github.com/pawamoy/duty/workflows/ci/badge.svg)](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
4
4
  [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://pawamoy.github.io/duty/)
5
5
  [![pypi version](https://img.shields.io/pypi/v/duty.svg)](https://pypi.org/project/duty/)
6
- [![gitpod](https://img.shields.io/badge/gitpod-workspace-708FCC.svg?style=flat)](https://gitpod.io/#https://github.com/pawamoy/duty)
7
6
  [![gitter](https://badges.gitter.im/join%20chat.svg)](https://app.gitter.im/#/room/#duty:gitter.im)
8
7
 
9
8
  A simple task runner.
@@ -14,17 +13,14 @@ Inspired by [Invoke](https://github.com/pyinvoke/invoke).
14
13
 
15
14
  ## Installation
16
15
 
17
- With `pip`:
18
-
19
16
  ```bash
20
17
  pip install duty
21
18
  ```
22
19
 
23
- With [`pipx`](https://github.com/pipxproject/pipx):
20
+ With [`uv`](https://docs.astral.sh/uv/):
24
21
 
25
22
  ```bash
26
- python3.8 -m pip install --user pipx
27
- pipx install duty
23
+ uv tool install duty
28
24
  ```
29
25
 
30
26
  ## Quick start
@@ -1,4 +1,4 @@
1
- target-version = "py38"
1
+ target-version = "py39"
2
2
  line-length = 120
3
3
 
4
4
  [lint]
@@ -7,11 +7,13 @@ import sys
7
7
  from contextlib import contextmanager
8
8
  from importlib.metadata import version as pkgversion
9
9
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Iterator
10
+ from typing import TYPE_CHECKING
11
11
 
12
12
  from duty import duty, tools
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from collections.abc import Iterator
16
+
15
17
  from duty.context import Context
16
18
 
17
19
 
@@ -53,8 +55,8 @@ def changelog(ctx: Context, bump: str = "") -> None:
53
55
  ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog")
54
56
 
55
57
 
56
- @duty(pre=["check_quality", "check_types", "check_docs", "check_dependencies", "check-api"])
57
- def check(ctx: Context) -> None: # noqa: ARG001
58
+ @duty(pre=["check-quality", "check-types", "check-docs", "check-api"])
59
+ def check(ctx: Context) -> None:
58
60
  """Check it all!"""
59
61
 
60
62
 
@@ -129,13 +129,15 @@ plugins:
129
129
  show_root_heading: true
130
130
  show_root_full_path: false
131
131
  show_signature_annotations: true
132
+ show_source: true
132
133
  show_symbol_type_heading: true
133
134
  show_symbol_type_toc: true
134
135
  signature_crossrefs: true
135
136
  summary: true
136
- - git-committers:
137
+ - git-revision-date-localized:
137
138
  enabled: !ENV [DEPLOY, false]
138
- repository: pawamoy/duty
139
+ enable_creation_date: true
140
+ type: timeago
139
141
  - minify:
140
142
  minify_html: !ENV [DEPLOY, false]
141
143
  - group:
@@ -11,7 +11,7 @@ authors = [
11
11
  { name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr" },
12
12
  ]
13
13
  readme = "README.md"
14
- requires-python = ">=3.8"
14
+ requires-python = ">=3.9"
15
15
  keywords = [
16
16
  "task-runner",
17
17
  "task",
@@ -25,12 +25,12 @@ classifiers = [
25
25
  "Programming Language :: Python",
26
26
  "Programming Language :: Python :: 3",
27
27
  "Programming Language :: Python :: 3 :: Only",
28
- "Programming Language :: Python :: 3.8",
29
28
  "Programming Language :: Python :: 3.9",
30
29
  "Programming Language :: Python :: 3.10",
31
30
  "Programming Language :: Python :: 3.11",
32
31
  "Programming Language :: Python :: 3.12",
33
32
  "Programming Language :: Python :: 3.13",
33
+ "Programming Language :: Python :: 3.14",
34
34
  "Topic :: Documentation",
35
35
  "Topic :: Software Development",
36
36
  "Topic :: Utilities",
@@ -41,7 +41,7 @@ dependencies = [
41
41
  "failprint>=0.11,!=1.0.0",
42
42
  "typing-extensions>=4.0; python_version < '3.11'",
43
43
  ]
44
- version = "1.4.1"
44
+ version = "1.4.3"
45
45
 
46
46
  [project.license]
47
47
  text = "ISC"
@@ -74,7 +74,6 @@ source-includes = [
74
74
  "scripts",
75
75
  "share",
76
76
  "tests",
77
- "devdeps.txt",
78
77
  "duties.py",
79
78
  "mkdocs.yml",
80
79
  "*.md",
@@ -85,3 +84,32 @@ source-includes = [
85
84
  data = [
86
85
  { path = "share/**/*", relative-to = "." },
87
86
  ]
87
+
88
+ [tool.uv]
89
+ dev-dependencies = [
90
+ "editables>=0.5",
91
+ "build>=1.2",
92
+ "git-changelog>=2.5",
93
+ "twine>=5.1",
94
+ "duty>=1.4",
95
+ "ruff>=0.4",
96
+ "pytest>=8.2",
97
+ "pytest-cov>=5.0",
98
+ "pytest-randomly>=3.15",
99
+ "pytest-xdist>=3.6",
100
+ "mypy>=1.10",
101
+ "types-markdown>=3.6",
102
+ "types-pyyaml>=6.0",
103
+ "black>=24.4",
104
+ "markdown-callouts>=0.4",
105
+ "markdown-exec>=1.8",
106
+ "mkdocs>=1.6",
107
+ "mkdocs-coverage>=1.0",
108
+ "mkdocs-gen-files>=0.5",
109
+ "mkdocs-git-revision-date-localized-plugin>=1.2",
110
+ "mkdocs-literate-nav>=0.6",
111
+ "mkdocs-material>=9.5",
112
+ "mkdocs-minify-plugin>=0.8",
113
+ "mkdocstrings[python]>=0.25",
114
+ "tomli>=2.0; python_version < '3.11'",
115
+ ]
@@ -5,17 +5,18 @@ from __future__ import annotations
5
5
  import os
6
6
  import sys
7
7
  from collections import defaultdict
8
+ from collections.abc import Iterable
8
9
  from importlib.metadata import distributions
9
10
  from itertools import chain
10
11
  from pathlib import Path
11
12
  from textwrap import dedent
12
- from typing import Dict, Iterable, Union
13
+ from typing import Union
13
14
 
14
15
  from jinja2 import StrictUndefined
15
16
  from jinja2.sandbox import SandboxedEnvironment
16
17
  from packaging.requirements import Requirement
17
18
 
18
- # TODO: Remove once support for Python 3.10 is dropped.
19
+ # YORE: EOL 3.10: Replace block with line 2.
19
20
  if sys.version_info >= (3, 11):
20
21
  import tomllib
21
22
  else:
@@ -26,11 +27,10 @@ with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file:
26
27
  pyproject = tomllib.load(pyproject_file)
27
28
  project = pyproject["project"]
28
29
  project_name = project["name"]
29
- with project_dir.joinpath("devdeps.txt").open() as devdeps_file:
30
- devdeps = [line.strip() for line in devdeps_file if line.strip() and not line.strip().startswith(("-e", "#"))]
30
+ devdeps = [dep for dep in pyproject["tool"]["uv"]["dev-dependencies"] if not dep.startswith("-e")]
31
31
 
32
- PackageMetadata = Dict[str, Union[str, Iterable[str]]]
33
- Metadata = Dict[str, PackageMetadata]
32
+ PackageMetadata = dict[str, Union[str, Iterable[str]]]
33
+ Metadata = dict[str, PackageMetadata]
34
34
 
35
35
 
36
36
  def _merge_fields(metadata: dict) -> PackageMetadata:
@@ -9,12 +9,10 @@ import subprocess
9
9
  import sys
10
10
  from contextlib import contextmanager
11
11
  from pathlib import Path
12
+ from textwrap import dedent
12
13
  from typing import Any, Iterator
13
14
 
14
- PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.8 3.9 3.10 3.11 3.12 3.13").split()
15
-
16
- exe = ""
17
- prefix = ""
15
+ PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split()
18
16
 
19
17
 
20
18
  def shell(cmd: str, capture_output: bool = False, **kwargs: Any) -> str | None:
@@ -37,17 +35,13 @@ def environ(**kwargs: str) -> Iterator[None]:
37
35
  os.environ.update(original)
38
36
 
39
37
 
40
- def uv_install() -> None:
38
+ def uv_install(venv: Path) -> None:
41
39
  """Install dependencies using uv."""
42
- uv_opts = ""
43
- if "UV_RESOLUTION" in os.environ:
44
- uv_opts = f"--resolution={os.getenv('UV_RESOLUTION')}"
45
- requirements = shell(f"uv pip compile {uv_opts} pyproject.toml devdeps.txt", capture_output=True)
46
- shell("uv pip install -r -", input=requirements, text=True)
47
- if "CI" not in os.environ:
48
- shell("uv pip install --no-deps -e .")
49
- else:
50
- shell("uv pip install --no-deps .")
40
+ with environ(UV_PROJECT_ENVIRONMENT=str(venv), PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"):
41
+ if "CI" in os.environ:
42
+ shell("uv sync --no-editable")
43
+ else:
44
+ shell("uv sync")
51
45
 
52
46
 
53
47
  def setup() -> None:
@@ -59,7 +53,7 @@ def setup() -> None:
59
53
  default_venv = Path(".venv")
60
54
  if not default_venv.exists():
61
55
  shell("uv venv --python python")
62
- uv_install()
56
+ uv_install(default_venv)
63
57
 
64
58
  if PYTHON_VERSIONS:
65
59
  for version in PYTHON_VERSIONS:
@@ -67,39 +61,22 @@ def setup() -> None:
67
61
  venv_path = Path(f".venvs/{version}")
68
62
  if not venv_path.exists():
69
63
  shell(f"uv venv --python {version} {venv_path}")
70
- with environ(VIRTUAL_ENV=str(venv_path.resolve())):
71
- uv_install()
72
-
73
-
74
- def activate(path: str) -> None:
75
- """Activate a virtual environment."""
76
- global exe, prefix # noqa: PLW0603
77
-
78
- if (bin := Path(path, "bin")).exists():
79
- activate_script = bin / "activate_this.py"
80
- elif (scripts := Path(path, "Scripts")).exists():
81
- activate_script = scripts / "activate_this.py"
82
- exe = ".exe"
83
- prefix = f"{path}/Scripts/"
84
- else:
85
- raise ValueError(f"make: activate: Cannot find activation script in {path}")
86
-
87
- if not activate_script.exists():
88
- raise ValueError(f"make: activate: Cannot find activation script in {path}")
89
-
90
- exec(activate_script.read_text(), {"__file__": str(activate_script)}) # noqa: S102
64
+ with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())):
65
+ uv_install(venv_path)
91
66
 
92
67
 
93
- def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None:
68
+ def run(version: str, cmd: str, *args: str, no_sync: bool = False, **kwargs: Any) -> None:
94
69
  """Run a command in a virtual environment."""
95
70
  kwargs = {"check": True, **kwargs}
71
+ uv_run = ["uv", "run"]
72
+ if no_sync:
73
+ uv_run.append("--no-sync")
96
74
  if version == "default":
97
- activate(".venv")
98
- subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
75
+ with environ(UV_PROJECT_ENVIRONMENT=".venv"):
76
+ subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
99
77
  else:
100
- activate(f".venvs/{version}")
101
- os.environ["MULTIRUN"] = "1"
102
- subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
78
+ with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"):
79
+ subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
103
80
 
104
81
 
105
82
  def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
@@ -124,10 +101,10 @@ def clean() -> None:
124
101
  for path in paths_to_clean:
125
102
  shell(f"rm -rf {path}")
126
103
 
127
- cache_dirs = [".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"]
128
- for dirpath in Path(".").rglob("*"):
129
- if any(dirpath.match(pattern) for pattern in cache_dirs) and not (dirpath.match(".venv") or dirpath.match(".venvs")):
130
- shutil.rmtree(path, ignore_errors=True)
104
+ cache_dirs = {".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"}
105
+ for dirpath in Path(".").rglob("*/"):
106
+ if dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs:
107
+ shutil.rmtree(dirpath, ignore_errors=True)
131
108
 
132
109
 
133
110
  def vscode() -> None:
@@ -143,22 +120,25 @@ def main() -> int:
143
120
  if len(args) > 1:
144
121
  run("default", "duty", "--help", args[1])
145
122
  else:
146
- print("Available commands") # noqa: T201
147
- print(" help Print this help. Add task name to print help.") # noqa: T201
148
- print(" setup Setup all virtual environments (install dependencies).") # noqa: T201
149
- print(" run Run a command in the default virtual environment.") # noqa: T201
150
- print(" multirun Run a command for all configured Python versions.") # noqa: T201
151
- print(" allrun Run a command in all virtual environments.") # noqa: T201
152
- print(" 3.x Run a command in the virtual environment for Python 3.x.") # noqa: T201
153
- print(" clean Delete build artifacts and cache files.") # noqa: T201
154
- print(" vscode Configure VSCode to work on this project.") # noqa: T201
155
- try:
156
- run("default", "python", "-V", capture_output=True)
157
- except (subprocess.CalledProcessError, ValueError):
158
- pass
159
- else:
160
- print("\nAvailable tasks") # noqa: T201
161
- run("default", "duty", "--list")
123
+ print(
124
+ dedent(
125
+ """
126
+ Available commands
127
+ help Print this help. Add task name to print help.
128
+ setup Setup all virtual environments (install dependencies).
129
+ run Run a command in the default virtual environment.
130
+ multirun Run a command for all configured Python versions.
131
+ allrun Run a command in all virtual environments.
132
+ 3.x Run a command in the virtual environment for Python 3.x.
133
+ clean Delete build artifacts and cache files.
134
+ vscode Configure VSCode to work on this project.
135
+ """
136
+ ),
137
+ flush=True,
138
+ ) # noqa: T201
139
+ if os.path.exists(".venv"):
140
+ print("\nAvailable tasks", flush=True) # noqa: T201
141
+ run("default", "duty", "--list", no_sync=True)
162
142
  return 0
163
143
 
164
144
  while args:
@@ -4,10 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from pathlib import Path
7
- from typing import Pattern, Sequence
7
+ from re import Pattern
8
+ from typing import TYPE_CHECKING
8
9
 
9
10
  from failprint.lazy import lazy
10
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
11
15
 
12
16
  @lazy(name="blacken_docs")
13
17
  def run(
@@ -24,6 +28,7 @@ def run(
24
28
  magic_trailing_comma: bool = True,
25
29
  python_cell_magics: set[str] | None = None,
26
30
  preview: bool = False,
31
+ check_only: bool = False,
27
32
  ) -> int:
28
33
  """Run `blacken-docs`.
29
34
 
@@ -44,6 +49,8 @@ def run(
44
49
  Useful for formatting cells with custom python magics.
45
50
  preview: Enable potentially disruptive style changes that may be added
46
51
  to Black's main functionality in the next major release.
52
+ check_only: Don't modify files but indicate when changes are necessary
53
+ with a message and non-zero return code.
47
54
 
48
55
  Returns:
49
56
  Success/failure.
@@ -80,5 +87,6 @@ def run(
80
87
  black_mode,
81
88
  skip_errors=skip_errors,
82
89
  rst_literal_blocks=rst_literal_blocks,
90
+ check_only=check_only,
83
91
  )
84
92
  return retv
@@ -1,16 +1,16 @@
1
- """Callable for [Ruff](https://github.com/charliermarsh/ruff)."""
1
+ """Callable for [Ruff](https://github.com/astral-sh/ruff)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
6
  import subprocess
7
7
  import sys
8
- from functools import lru_cache
8
+ from functools import cache
9
9
 
10
10
  from failprint.lazy import lazy
11
11
 
12
12
 
13
- @lru_cache(maxsize=None)
13
+ @cache
14
14
  def _find_ruff() -> str:
15
15
  from ruff.__main__ import find_ruff_bin
16
16
 
@@ -90,34 +90,34 @@ def check(
90
90
  """Run Ruff on the given files or directories.
91
91
 
92
92
  Parameters:
93
- fix: Attempt to automatically fix lint violations
94
- config: Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
95
- show_source: Show violations with source code
96
- show_fixes: Show an enumeration of all autofixed lint violations
97
- diff: Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
98
- watch: Run in watch mode by re-running whenever files change
99
- fix_only: Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
100
- output_format: Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
101
- statistics: Show counts for every rule with at least one violation
102
- add_noqa: Enable automatic additions of `noqa` directives to failing lines
103
- show_files: See the files Ruff will be run against with the current settings
104
- show_settings: See the settings Ruff will use to lint a given Python file
105
- select: Comma-separated list of rule codes to enable (or ALL, to enable all rules)
106
- ignore: Comma-separated list of rule codes to disable
107
- extend_select: Like --select, but adds additional rule codes on top of the selected ones
108
- per_file_ignores: List of mappings from file pattern to code to exclude
109
- fixable: List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
110
- unfixable: List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
111
- exclude: List of paths, used to omit files and/or directories from analysis
112
- extend_exclude: Like --exclude, but adds additional files and directories on top of those already excluded
113
- respect_gitignore: Respect file exclusions via `.gitignore` and other standard ignore files
114
- force_exclude: Enforce exclusions, even for paths passed to Ruff directly on the command-line
115
- no_cache: Disable cache reads
116
- isolated: Ignore all configuration files
117
- cache_dir: Path to the cache directory [env: RUFF_CACHE_DIR=]
118
- stdin_filename: The name of the file when passing it through stdin
119
- exit_zero: Exit with status code "0", even upon detecting lint violations
120
- exit_non_zero_on_fix: Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain
93
+ fix: Attempt to automatically fix lint violations.
94
+ config: Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
95
+ show_source: Show violations with source code.
96
+ show_fixes: Show an enumeration of all autofixed lint violations.
97
+ diff: Avoid writing any fixed files back; instead, output a diff for each changed file to stdout.
98
+ watch: Run in watch mode by re-running whenever files change.
99
+ fix_only: Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`.
100
+ output_format: Output serialization format for violations (env: RUFF_FORMAT=) (possible values: text, json, junit, grouped, github, gitlab, pylint).
101
+ statistics: Show counts for every rule with at least one violation.
102
+ add_noqa: Enable automatic additions of `noqa` directives to failing lines.
103
+ show_files: See the files Ruff will be run against with the current settings.
104
+ show_settings: See the settings Ruff will use to lint a given Python file.
105
+ select: Comma-separated list of rule codes to enable (or ALL, to enable all rules).
106
+ ignore: Comma-separated list of rule codes to disable.
107
+ extend_select: Like --select, but adds additional rule codes on top of the selected ones.
108
+ per_file_ignores: List of mappings from file pattern to code to exclude.
109
+ fixable: List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`).
110
+ unfixable: List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`).
111
+ exclude: List of paths, used to omit files and/or directories from analysis.
112
+ extend_exclude: Like --exclude, but adds additional files and directories on top of those already excluded.
113
+ respect_gitignore: Respect file exclusions via `.gitignore` and other standard ignore files.
114
+ force_exclude: Enforce exclusions, even for paths passed to Ruff directly on the command-line.
115
+ no_cache: Disable cache reads.
116
+ isolated: Ignore all configuration files.
117
+ cache_dir: Path to the cache directory (env: RUFF_CACHE_DIR=).
118
+ stdin_filename: The name of the file when passing it through stdin.
119
+ exit_zero: Exit with status code "0", even upon detecting lint violations.
120
+ exit_non_zero_on_fix: Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain.
121
121
  verbose: Enable verbose logging.
122
122
  quiet: Print lint violations, but nothing else.
123
123
  silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
@@ -368,7 +368,7 @@ def linter(
368
368
  """List all supported upstream linters.
369
369
 
370
370
  Parameters:
371
- output_format: Output format [default: pretty] [possible values: text, json, pretty].
371
+ output_format: Output format (default: pretty) (possible values: text, json, pretty).
372
372
  verbose: Enable verbose logging.
373
373
  quiet: Print lint violations, but nothing else.
374
374
  silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
@@ -5,10 +5,13 @@ from __future__ import annotations
5
5
  import importlib
6
6
  import sys
7
7
  from io import StringIO
8
- from typing import Literal, Sequence, cast
8
+ from typing import TYPE_CHECKING, Literal, cast
9
9
 
10
10
  from failprint.lazy import lazy
11
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
12
15
 
13
16
  @lazy(name="safety.check")
14
17
  def check(
@@ -6,11 +6,11 @@ import inspect
6
6
  import sys
7
7
  from copy import deepcopy
8
8
  from importlib import util as importlib_util
9
- from typing import Any, Callable, ClassVar, List, Union
9
+ from typing import Any, Callable, ClassVar, Union
10
10
 
11
11
  from duty.context import Context
12
12
 
13
- DutyListType = List[Union[str, Callable, "Duty"]]
13
+ DutyListType = list[Union[str, Callable, "Duty"]]
14
14
  default_duties_file = "duties.py"
15
15
 
16
16
 
@@ -4,14 +4,17 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
  from contextlib import contextmanager, suppress
7
- from typing import Any, Callable, Iterator, List, Union
7
+ from typing import TYPE_CHECKING, Any, Callable, Union
8
8
 
9
9
  from failprint.runners import run as failprint_run
10
10
 
11
11
  from duty.exceptions import DutyFailure
12
12
  from duty.tools import Tool
13
13
 
14
- CmdType = Union[str, List[str], Callable]
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterator
16
+
17
+ CmdType = Union[str, list[str], Callable]
15
18
 
16
19
 
17
20
  class Context:
@@ -4,11 +4,13 @@ from __future__ import annotations
4
4
 
5
5
  import inspect
6
6
  from functools import wraps
7
- from typing import TYPE_CHECKING, Any, Callable, Iterable, overload
7
+ from typing import TYPE_CHECKING, Any, Callable, overload
8
8
 
9
9
  from duty.collection import Duty, DutyListType
10
10
 
11
11
  if TYPE_CHECKING:
12
+ from collections.abc import Iterable
13
+
12
14
  from duty.context import Context
13
15
 
14
16
 
@@ -4,10 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from pathlib import Path
7
- from typing import Pattern, Sequence
7
+ from re import Pattern
8
+ from typing import TYPE_CHECKING
8
9
 
9
10
  from duty.tools._base import Tool
10
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
11
15
 
12
16
  class blacken_docs(Tool): # noqa: N801
13
17
  """Call [blacken-docs](https://github.com/adamchainz/blacken-docs)."""
@@ -29,6 +33,7 @@ class blacken_docs(Tool): # noqa: N801
29
33
  magic_trailing_comma: bool = True,
30
34
  python_cell_magics: set[str] | None = None,
31
35
  preview: bool = False,
36
+ check_only: bool = False,
32
37
  ) -> None:
33
38
  """Run `blacken-docs`.
34
39
 
@@ -49,6 +54,8 @@ class blacken_docs(Tool): # noqa: N801
49
54
  Useful for formatting cells with custom python magics.
50
55
  preview: Enable potentially disruptive style changes that may be added
51
56
  to Black's main functionality in the next major release.
57
+ check_only: Don't modify files but indicate when changes are necessary
58
+ with a message and non-zero return code.
52
59
 
53
60
  Returns:
54
61
  Success/failure.
@@ -77,6 +84,7 @@ class blacken_docs(Tool): # noqa: N801
77
84
  preview = self.py_args["preview"]
78
85
  skip_errors = self.py_args["skip_errors"]
79
86
  rst_literal_blocks = self.py_args["rst_literal_blocks"]
87
+ check_only = self.py_args["check_only"]
80
88
 
81
89
  # Build filepaths.
82
90
  exts = ("md", "py") if exts is None else tuple(ext.lstrip(".") for ext in exts)
@@ -111,5 +119,6 @@ class blacken_docs(Tool): # noqa: N801
111
119
  black_mode,
112
120
  skip_errors=skip_errors,
113
121
  rst_literal_blocks=rst_literal_blocks,
122
+ check_only=check_only,
114
123
  )
115
124
  return retv
@@ -1,16 +1,16 @@
1
- """Callable for [Ruff](https://github.com/charliermarsh/ruff)."""
1
+ """Callable for [Ruff](https://github.com/astral-sh/ruff)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
6
  import subprocess
7
7
  import sys
8
- from functools import lru_cache
8
+ from functools import cache
9
9
 
10
10
  from duty.tools._base import Tool
11
11
 
12
12
 
13
- @lru_cache(maxsize=None)
13
+ @cache
14
14
  def _find_ruff() -> str:
15
15
  from ruff.__main__ import find_ruff_bin
16
16
 
@@ -31,7 +31,7 @@ def _find_ruff() -> str:
31
31
 
32
32
 
33
33
  class ruff(Tool): # noqa: N801
34
- """Call [Ruff](https://github.com/charliermarsh/ruff)."""
34
+ """Call [Ruff](https://github.com/astral-sh/ruff)."""
35
35
 
36
36
  cli_name = "ruff"
37
37
 
@@ -74,34 +74,34 @@ class ruff(Tool): # noqa: N801
74
74
  """Run Ruff on the given files or directories.
75
75
 
76
76
  Parameters:
77
- fix: Attempt to automatically fix lint violations
78
- config: Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
79
- show_source: Show violations with source code
80
- show_fixes: Show an enumeration of all autofixed lint violations
81
- diff: Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
82
- watch: Run in watch mode by re-running whenever files change
83
- fix_only: Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
84
- output_format: Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
85
- statistics: Show counts for every rule with at least one violation
86
- add_noqa: Enable automatic additions of `noqa` directives to failing lines
87
- show_files: See the files Ruff will be run against with the current settings
88
- show_settings: See the settings Ruff will use to lint a given Python file
89
- select: Comma-separated list of rule codes to enable (or ALL, to enable all rules)
90
- ignore: Comma-separated list of rule codes to disable
91
- extend_select: Like --select, but adds additional rule codes on top of the selected ones
92
- per_file_ignores: List of mappings from file pattern to code to exclude
93
- fixable: List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
94
- unfixable: List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
95
- exclude: List of paths, used to omit files and/or directories from analysis
96
- extend_exclude: Like --exclude, but adds additional files and directories on top of those already excluded
97
- respect_gitignore: Respect file exclusions via `.gitignore` and other standard ignore files
98
- force_exclude: Enforce exclusions, even for paths passed to Ruff directly on the command-line
99
- no_cache: Disable cache reads
100
- isolated: Ignore all configuration files
101
- cache_dir: Path to the cache directory [env: RUFF_CACHE_DIR=]
102
- stdin_filename: The name of the file when passing it through stdin
103
- exit_zero: Exit with status code "0", even upon detecting lint violations
104
- exit_non_zero_on_fix: Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain
77
+ fix: Attempt to automatically fix lint violations.
78
+ config: Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
79
+ show_source: Show violations with source code.
80
+ show_fixes: Show an enumeration of all autofixed lint violations.
81
+ diff: Avoid writing any fixed files back; instead, output a diff for each changed file to stdout.
82
+ watch: Run in watch mode by re-running whenever files change.
83
+ fix_only: Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`.
84
+ output_format: Output serialization format for violations (env: RUFF_FORMAT=) (possible values: text, json, junit, grouped, github, gitlab, pylint).
85
+ statistics: Show counts for every rule with at least one violation.
86
+ add_noqa: Enable automatic additions of `noqa` directives to failing lines.
87
+ show_files: See the files Ruff will be run against with the current settings.
88
+ show_settings: See the settings Ruff will use to lint a given Python file.
89
+ select: Comma-separated list of rule codes to enable (or ALL, to enable all rules).
90
+ ignore: Comma-separated list of rule codes to disable.
91
+ extend_select: Like --select, but adds additional rule codes on top of the selected ones.
92
+ per_file_ignores: List of mappings from file pattern to code to exclude.
93
+ fixable: List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`).
94
+ unfixable: List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`).
95
+ exclude: List of paths, used to omit files and/or directories from analysis.
96
+ extend_exclude: Like --exclude, but adds additional files and directories on top of those already excluded.
97
+ respect_gitignore: Respect file exclusions via `.gitignore` and other standard ignore files.
98
+ force_exclude: Enforce exclusions, even for paths passed to Ruff directly on the command-line.
99
+ no_cache: Disable cache reads.
100
+ isolated: Ignore all configuration files.
101
+ cache_dir: Path to the cache directory (env: RUFF_CACHE_DIR=).
102
+ stdin_filename: The name of the file when passing it through stdin.
103
+ exit_zero: Exit with status code "0", even upon detecting lint violations.
104
+ exit_non_zero_on_fix: Exit with a non-zero status code if any files were modified via autofix, even if no lint violations remain.
105
105
  verbose: Enable verbose logging.
106
106
  quiet: Print lint violations, but nothing else.
107
107
  silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
@@ -390,7 +390,7 @@ class ruff(Tool): # noqa: N801
390
390
  """List all supported upstream linters.
391
391
 
392
392
  Parameters:
393
- output_format: Output format [default: pretty] [possible values: text, json, pretty].
393
+ output_format: Output format (default: pretty) (possible values: text, json, pretty).
394
394
  verbose: Enable verbose logging.
395
395
  quiet: Print lint violations, but nothing else.
396
396
  silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
@@ -5,10 +5,13 @@ from __future__ import annotations
5
5
  import importlib
6
6
  import sys
7
7
  from io import StringIO
8
- from typing import Literal, Sequence, cast
8
+ from typing import TYPE_CHECKING, Literal, cast
9
9
 
10
10
  from duty.tools._base import Tool
11
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
12
15
 
13
16
  class safety(Tool): # noqa: N801
14
17
  """Call [Safety](https://github.com/pyupio/safety)."""
@@ -12,9 +12,12 @@ import textwrap
12
12
  from contextlib import suppress
13
13
  from functools import cached_property, partial
14
14
  from inspect import Parameter, Signature, signature
15
- from typing import Any, Callable, ForwardRef, Sequence, Union, get_args, get_origin
15
+ from typing import TYPE_CHECKING, Any, Callable, ForwardRef, Union, get_args, get_origin
16
16
 
17
- # TODO: Update once support for Python 3.9 is dropped.
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
20
+ # YORE: EOL 3.9: Replace block with lines 6-13.
18
21
  if sys.version_info < (3, 10):
19
22
  from eval_type_backport import eval_type_backport as eval_type
20
23
 
@@ -8,7 +8,7 @@ from duty.collection import Collection, Duty
8
8
  from duty.decorator import duty as decorate
9
9
 
10
10
 
11
- def none(*args, **kwargs) -> None: # noqa: ANN002, ANN003, ARG001, D103
11
+ def none(*args, **kwargs) -> None: # noqa: ANN002, ANN003, D103
12
12
  ... # pragma: no cover
13
13
 
14
14
 
@@ -96,4 +96,8 @@ def test_workdir_as_context_manager(monkeypatch: pytest.MonkeyPatch) -> None:
96
96
  records.append(failure.value.code)
97
97
 
98
98
  base = records[0]
99
- assert records == [base, base - 1, base - 2, base - 3]
99
+
100
+ # If the repository is checked out near the root of the filesystem, the working directory will
101
+ # eventually be the root, so cap the lowest depth at 1.
102
+ expected_depths = [max(1, base - offset) for offset in range(len(records))]
103
+ assert records == expected_depths
@@ -139,7 +139,7 @@ def test_params_caster(func: Callable, args: tuple, kwargs: dict, expected_args:
139
139
  def test_casting_based_on_default_value_type() -> None:
140
140
  """Test that we cast according to the default value type when there is no annotation."""
141
141
 
142
- def func(ctx, a=0): # noqa: ANN202,ARG001,ANN001
142
+ def func(ctx, a=0): # noqa: ANN202, ANN001
143
143
  ...
144
144
 
145
145
  caster = _get_params_caster(func, a="1")
@@ -150,7 +150,7 @@ def test_casting_based_on_default_value_type() -> None:
150
150
  def test_validating_modern_annotations() -> None:
151
151
  """Test modern type annotations in function signatures."""
152
152
 
153
- def func(ctx, a: int | None = None): # noqa: ANN202,ARG001,ANN001
153
+ def func(ctx, a: int | None = None): # noqa: ANN202, ANN001
154
154
  ...
155
155
 
156
156
  caster = _get_params_caster(func, a=1)
duty-1.4.1/devdeps.txt DELETED
@@ -1,32 +0,0 @@
1
- # dev
2
- editables>=0.5
3
-
4
- # maintenance
5
- build>=1.2
6
- git-changelog>=2.5
7
- twine>=5.0; python_version < '3.13'
8
-
9
- # ci
10
- duty>=1.4
11
- ruff>=0.4
12
- pytest>=8.2
13
- pytest-cov>=5.0
14
- pytest-randomly>=3.15
15
- pytest-xdist>=3.6
16
- mypy>=1.10
17
- types-markdown>=3.6
18
- types-pyyaml>=6.0
19
-
20
- # docs
21
- black>=24.4
22
- markdown-callouts>=0.4
23
- markdown-exec>=1.8
24
- mkdocs>=1.6
25
- mkdocs-coverage>=1.0
26
- mkdocs-gen-files>=0.5
27
- mkdocs-git-committers-plugin-2>=2.3
28
- mkdocs-literate-nav>=0.6
29
- mkdocs-material>=9.5
30
- mkdocs-minify-plugin>=0.8
31
- mkdocstrings[python]>=0.25
32
- tomli>=2.0; python_version < '3.11'
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
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
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes