duty 1.3.0__py3-none-any.whl → 1.4.1__py3-none-any.whl

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.
@@ -0,0 +1,284 @@
1
+ """Callable for [Twine](https://github.com/pypa/twine)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from failprint.lazy import lazy
6
+
7
+
8
+ def run(*args: str, version: bool = False, no_color: bool = False) -> None:
9
+ """Run `twine`.
10
+
11
+ Parameters:
12
+ *args: CLI arguments.
13
+ version: Show program's version number and exit.
14
+ no_color: Disable colored output.
15
+ """
16
+ from twine.cli import dispatch as twine
17
+
18
+ cli_args = []
19
+
20
+ # --version and --no-color must appear first.
21
+ if version:
22
+ cli_args.append("--version")
23
+
24
+ if no_color:
25
+ cli_args.append("--no-color")
26
+
27
+ cli_args.extend(args)
28
+
29
+ twine(cli_args)
30
+
31
+
32
+ @lazy(name="twine.check")
33
+ def check(
34
+ *dists: str,
35
+ strict: bool = False,
36
+ version: bool = False,
37
+ no_color: bool = False,
38
+ ) -> None:
39
+ """Checks whether your distribution's long description will render correctly on PyPI.
40
+
41
+ Parameters:
42
+ dists: The distribution files to check, usually `dist/*`.
43
+ strict: Fail on warnings.
44
+ version: Show program's version number and exit.
45
+ no_color: Disable colored output.
46
+ """
47
+ cli_args = list(dists)
48
+
49
+ if strict is True:
50
+ cli_args.append("--strict")
51
+
52
+ run("check", *cli_args, version=version, no_color=no_color)
53
+
54
+
55
+ @lazy(name="twine.register")
56
+ def register(
57
+ package: str,
58
+ *,
59
+ repository: str = "pypi",
60
+ repository_url: str | None = None,
61
+ attestations: bool = False,
62
+ sign: bool = False,
63
+ sign_with: str | None = None,
64
+ identity: str | None = None,
65
+ username: str | None = None,
66
+ password: str | None = None,
67
+ non_interactive: bool = False,
68
+ comment: str | None = None,
69
+ config_file: str | None = None,
70
+ skip_existing: bool = False,
71
+ cert: str | None = None,
72
+ client_cert: str | None = None,
73
+ verbose: bool = False,
74
+ disable_progress_bar: bool = False,
75
+ version: bool = False,
76
+ no_color: bool = False,
77
+ ) -> None:
78
+ """Pre-register a name with a repository before uploading a distribution.
79
+
80
+ Pre-registration is not supported on PyPI, so the register command
81
+ is only necessary if you are using a different repository that requires it.
82
+
83
+ Parameters:
84
+ package: File from which we read the package metadata.
85
+ repository: The repository (package index) to upload the package to.
86
+ Should be a section in the config file (default: `pypi`).
87
+ Can also be set via `TWINE_REPOSITORY` environment variable.
88
+ repository_url: The repository (package index) URL to upload the package to. This overrides `--repository`.
89
+ Can also be set via `TWINE_REPOSITORY_URL` environment variable.
90
+ attestations: Upload each file's associated attestations.
91
+ sign: Sign files to upload using GPG.
92
+ sign_with: GPG program used to sign uploads (default: `gpg`).
93
+ identity: GPG identity used to sign files.
94
+ username: The username to authenticate to the repository (package index) as.
95
+ Can also be set via `TWINE_USERNAME` environment variable.
96
+ password: The password to authenticate to the repository (package index) with.
97
+ Can also be set via `TWINE_PASSWORD` environment variable.
98
+ non_interactive: Do not interactively prompt for username/password if the required credentials are missing.
99
+ Can also be set via `TWINE_NON_INTERACTIVE` environment variable.
100
+ comment: The comment to include with the distribution file.
101
+ config_file: The `.pypirc` config file to use.
102
+ skip_existing: Continue uploading files if one already exists.
103
+ Only valid when uploading to PyPI. Other implementations may not support this.
104
+ cert: Path to alternate CA bundle (can also be set via `TWINE_CERT` environment variable).
105
+ client_cert: Path to SSL client certificate, a single file containing the private key and the certificate in PEM format.
106
+ verbose: Show verbose output.
107
+ disable_progress_bar: Disable the progress bar.
108
+ version: Show program's version number and exit.
109
+ no_color: Disable colored output.
110
+ """
111
+ cli_args = [package]
112
+
113
+ if repository:
114
+ cli_args.append("--repository")
115
+ cli_args.append(repository)
116
+
117
+ if repository_url:
118
+ cli_args.append("--repository-url")
119
+ cli_args.append(repository_url)
120
+
121
+ if attestations:
122
+ cli_args.append("--attestations")
123
+
124
+ if sign:
125
+ cli_args.append("--sign")
126
+
127
+ if sign_with:
128
+ cli_args.append("--sign-with")
129
+ cli_args.append(sign_with)
130
+
131
+ if identity:
132
+ cli_args.append("--identity")
133
+ cli_args.append(identity)
134
+
135
+ if username:
136
+ cli_args.append("--username")
137
+ cli_args.append(username)
138
+
139
+ if password:
140
+ cli_args.append("--password")
141
+ cli_args.append(password)
142
+
143
+ if non_interactive:
144
+ cli_args.append("--non-interactive")
145
+
146
+ if comment:
147
+ cli_args.append("--repository")
148
+
149
+ if config_file:
150
+ cli_args.append("--config-file")
151
+ cli_args.append(config_file)
152
+
153
+ if skip_existing:
154
+ cli_args.append("--skip-existing")
155
+
156
+ if cert:
157
+ cli_args.append("--cert")
158
+ cli_args.append(cert)
159
+
160
+ if client_cert:
161
+ cli_args.append("--client-cert")
162
+ cli_args.append(client_cert)
163
+
164
+ if verbose:
165
+ cli_args.append("--verbose")
166
+
167
+ if disable_progress_bar:
168
+ cli_args.append("--disable-progress-bar")
169
+
170
+ run("check", *cli_args, version=version, no_color=no_color)
171
+
172
+
173
+ @lazy(name="twine.upload")
174
+ def upload(
175
+ *dists: str,
176
+ repository: str = "pypi",
177
+ repository_url: str | None = None,
178
+ attestations: bool = False,
179
+ sign: bool = False,
180
+ sign_with: str | None = None,
181
+ identity: str | None = None,
182
+ username: str | None = None,
183
+ password: str | None = None,
184
+ non_interactive: bool = False,
185
+ comment: str | None = None,
186
+ config_file: str | None = None,
187
+ skip_existing: bool = False,
188
+ cert: str | None = None,
189
+ client_cert: str | None = None,
190
+ verbose: bool = False,
191
+ disable_progress_bar: bool = False,
192
+ version: bool = False,
193
+ no_color: bool = False,
194
+ ) -> None:
195
+ """Uploads one or more distributions to a repository.
196
+
197
+ Parameters:
198
+ dists: The distribution files to check, usually `dist/*`.
199
+ repository: The repository (package index) to upload the package to.
200
+ Should be a section in the config file (default: `pypi`).
201
+ Can also be set via `TWINE_REPOSITORY` environment variable.
202
+ repository_url: The repository (package index) URL to upload the package to. This overrides `--repository`.
203
+ Can also be set via `TWINE_REPOSITORY_URL` environment variable.
204
+ attestations: Upload each file's associated attestations.
205
+ sign: Sign files to upload using GPG.
206
+ sign_with: GPG program used to sign uploads (default: `gpg`).
207
+ identity: GPG identity used to sign files.
208
+ username: The username to authenticate to the repository (package index) as.
209
+ Can also be set via `TWINE_USERNAME` environment variable.
210
+ password: The password to authenticate to the repository (package index) with.
211
+ Can also be set via `TWINE_PASSWORD` environment variable.
212
+ non_interactive: Do not interactively prompt for username/password if the required credentials are missing.
213
+ Can also be set via `TWINE_NON_INTERACTIVE` environment variable.
214
+ comment: The comment to include with the distribution file.
215
+ config_file: The `.pypirc` config file to use.
216
+ skip_existing: Continue uploading files if one already exists.
217
+ Only valid when uploading to PyPI. Other implementations may not support this.
218
+ cert: Path to alternate CA bundle (can also be set via `TWINE_CERT` environment variable).
219
+ client_cert: Path to SSL client certificate, a single file containing the private key and the certificate in PEM format.
220
+ verbose: Show verbose output.
221
+ disable_progress_bar: Disable the progress bar.
222
+ version: Show program's version number and exit.
223
+ no_color: Disable colored output.
224
+ """
225
+ cli_args = list(dists)
226
+
227
+ if repository:
228
+ cli_args.append("--repository")
229
+ cli_args.append(repository)
230
+
231
+ if repository_url:
232
+ cli_args.append("--repository-url")
233
+ cli_args.append(repository_url)
234
+
235
+ if attestations:
236
+ cli_args.append("--attestations")
237
+
238
+ if sign:
239
+ cli_args.append("--sign")
240
+
241
+ if sign_with:
242
+ cli_args.append("--sign-with")
243
+ cli_args.append(sign_with)
244
+
245
+ if identity:
246
+ cli_args.append("--identity")
247
+ cli_args.append(identity)
248
+
249
+ if username:
250
+ cli_args.append("--username")
251
+ cli_args.append(username)
252
+
253
+ if password:
254
+ cli_args.append("--password")
255
+ cli_args.append(password)
256
+
257
+ if non_interactive:
258
+ cli_args.append("--non-interactive")
259
+
260
+ if comment:
261
+ cli_args.append("--repository")
262
+
263
+ if config_file:
264
+ cli_args.append("--config-file")
265
+ cli_args.append(config_file)
266
+
267
+ if skip_existing:
268
+ cli_args.append("--skip-existing")
269
+
270
+ if cert:
271
+ cli_args.append("--cert")
272
+ cli_args.append(cert)
273
+
274
+ if client_cert:
275
+ cli_args.append("--client-cert")
276
+ cli_args.append(client_cert)
277
+
278
+ if verbose:
279
+ cli_args.append("--verbose")
280
+
281
+ if disable_progress_bar:
282
+ cli_args.append("--disable-progress-bar")
283
+
284
+ run("upload", *cli_args, version=version, no_color=no_color)
duty/cli.py CHANGED
@@ -214,6 +214,8 @@ def parse_commands(arg_lists: list[list[str]], global_opts: dict[str, Any], coll
214
214
  for arg_list in arg_lists:
215
215
  duty = collection.get(arg_list[0])
216
216
  opts, remainder = parse_options(duty, arg_list[1:])
217
+ if remainder and remainder[0] == "--":
218
+ remainder = remainder[1:]
217
219
  duty.options_override = {**global_opts, **opts}
218
220
  commands.append((duty, *parse_args(duty, remainder)))
219
221
  return commands
duty/context.py CHANGED
@@ -3,12 +3,13 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from contextlib import contextmanager
6
+ from contextlib import contextmanager, suppress
7
7
  from typing import Any, Callable, Iterator, List, Union
8
8
 
9
9
  from failprint.runners import run as failprint_run
10
10
 
11
11
  from duty.exceptions import DutyFailure
12
+ from duty.tools import Tool
12
13
 
13
14
  CmdType = Union[str, List[str], Callable]
14
15
 
@@ -67,6 +68,10 @@ class Context:
67
68
  final_options = dict(self._options)
68
69
  final_options.update(options)
69
70
 
71
+ if "command" not in final_options and isinstance(cmd, Tool):
72
+ with suppress(ValueError):
73
+ final_options["command"] = cmd.cli_command
74
+
70
75
  allow_overrides = final_options.pop("allow_overrides", True)
71
76
  workdir = final_options.pop("workdir", None)
72
77
 
duty/decorator.py CHANGED
@@ -63,7 +63,7 @@ def create_duty(
63
63
 
64
64
 
65
65
  @overload
66
- def duty(**kwargs: Any) -> Callable[[Callable], Duty]: ... # type: ignore[overload-overlap]
66
+ def duty(**kwargs: Any) -> Callable[[Callable], Duty]: ...
67
67
 
68
68
 
69
69
  @overload
duty/tools/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ """Module containing callables for many tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from failprint.lazy import lazy
6
+
7
+ from duty.tools._autoflake import autoflake
8
+ from duty.tools._base import LazyStderr, LazyStdout, Tool
9
+ from duty.tools._black import black
10
+ from duty.tools._blacken_docs import blacken_docs
11
+ from duty.tools._build import build
12
+ from duty.tools._coverage import coverage
13
+ from duty.tools._flake8 import flake8
14
+ from duty.tools._git_changelog import git_changelog
15
+ from duty.tools._griffe import griffe
16
+ from duty.tools._interrogate import interrogate
17
+ from duty.tools._isort import isort
18
+ from duty.tools._mkdocs import mkdocs
19
+ from duty.tools._mypy import mypy
20
+ from duty.tools._pytest import pytest
21
+ from duty.tools._ruff import ruff
22
+ from duty.tools._safety import safety
23
+ from duty.tools._ssort import ssort
24
+ from duty.tools._twine import twine
25
+
26
+ __all__ = [
27
+ "Tool",
28
+ "LazyStdout",
29
+ "LazyStderr",
30
+ "lazy",
31
+ "autoflake",
32
+ "black",
33
+ "blacken_docs",
34
+ "build",
35
+ "coverage",
36
+ "flake8",
37
+ "git_changelog",
38
+ "griffe",
39
+ "interrogate",
40
+ "isort",
41
+ "mkdocs",
42
+ "mypy",
43
+ "pytest",
44
+ "ruff",
45
+ "safety",
46
+ "ssort",
47
+ "twine",
48
+ ]
@@ -0,0 +1,138 @@
1
+ """Callable for [autoflake](https://github.com/PyCQA/autoflake)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from duty.tools._base import LazyStderr, LazyStdout, Tool
6
+
7
+
8
+ class autoflake(Tool): # noqa: N801
9
+ """Call [autoflake](https://github.com/PyCQA/autoflake)."""
10
+
11
+ cli_name = "autoflake"
12
+
13
+ def __init__(
14
+ self,
15
+ *files: str,
16
+ config: str | None = None,
17
+ check: bool | None = None,
18
+ check_diff: bool | None = None,
19
+ imports: list[str] | None = None,
20
+ remove_all_unused_imports: bool | None = None,
21
+ recursive: bool | None = None,
22
+ jobs: int | None = None,
23
+ exclude: list[str] | None = None,
24
+ expand_star_imports: bool | None = None,
25
+ ignore_init_module_imports: bool | None = None,
26
+ remove_duplicate_keys: bool | None = None,
27
+ remove_unused_variables: bool | None = None,
28
+ remove_rhs_for_unused_variables: bool | None = None,
29
+ ignore_pass_statements: bool | None = None,
30
+ ignore_pass_after_docstring: bool | None = None,
31
+ quiet: bool | None = None,
32
+ verbose: bool | None = None,
33
+ stdin_display_name: str | None = None,
34
+ in_place: bool | None = None,
35
+ stdout: bool | None = None,
36
+ ) -> None:
37
+ r"""Run `autoflake`.
38
+
39
+ Parameters:
40
+ *files: Files to format.
41
+ config: Explicitly set the config file instead of auto determining based on file location.
42
+ check: Return error code if changes are needed.
43
+ check_diff: Return error code if changes are needed, also display file diffs.
44
+ imports: By default, only unused standard library imports are removed; specify a comma-separated list of additional modules/packages.
45
+ remove_all_unused_imports: Remove all unused imports (not just those from the standard library).
46
+ recursive: Drill down directories recursively.
47
+ jobs: Number of parallel jobs; match CPU count if value is 0 (default: 0).
48
+ exclude: Exclude file/directory names that match these comma-separated globs.
49
+ expand_star_imports: Expand wildcard star imports with undefined names; this only triggers if there is only one star import in the file; this is skipped if there are any uses of `__all__` or `del` in the file.
50
+ ignore_init_module_imports: Exclude `__init__.py` when removing unused imports.
51
+ remove_duplicate_keys: Remove all duplicate keys in objects.
52
+ remove_unused_variables: Remove unused variables.
53
+ remove_rhs_for_unused_variables: Remove RHS of statements when removing unused variables (unsafe).
54
+ ignore_pass_statements: Ignore all pass statements.
55
+ ignore_pass_after_docstring: Ignore pass statements after a newline ending on `\"\"\"`.
56
+ quiet: Suppress output if there are no issues.
57
+ verbose: Print more verbose logs (you can repeat `-v` to make it more verbose).
58
+ stdin_display_name: The name used when processing input from stdin.
59
+ in_place: Make changes to files instead of printing diffs.
60
+ stdout: Print changed text to stdout. defaults to true when formatting stdin, or to false otherwise.
61
+ """
62
+ cli_args = list(files)
63
+
64
+ if check:
65
+ cli_args.append("--check")
66
+
67
+ if check_diff:
68
+ cli_args.append("--check-diff")
69
+
70
+ if imports:
71
+ cli_args.append("--imports")
72
+ cli_args.append(",".join(imports))
73
+
74
+ if remove_all_unused_imports:
75
+ cli_args.append("--remove-all-unused-imports")
76
+
77
+ if recursive:
78
+ cli_args.append("--recursive")
79
+
80
+ if jobs:
81
+ cli_args.append("--jobs")
82
+ cli_args.append(str(jobs))
83
+
84
+ if exclude:
85
+ cli_args.append("--exclude")
86
+ cli_args.append(",".join(exclude))
87
+
88
+ if expand_star_imports:
89
+ cli_args.append("--expand-star-imports")
90
+
91
+ if ignore_init_module_imports:
92
+ cli_args.append("--ignore-init-module-imports")
93
+
94
+ if remove_duplicate_keys:
95
+ cli_args.append("--remove-duplicate-keys")
96
+
97
+ if remove_unused_variables:
98
+ cli_args.append("--remove-unused-variables")
99
+
100
+ if remove_rhs_for_unused_variables:
101
+ cli_args.append("remove-rhs-for-unused-variables")
102
+
103
+ if ignore_pass_statements:
104
+ cli_args.append("--ignore-pass-statements")
105
+
106
+ if ignore_pass_after_docstring:
107
+ cli_args.append("--ignore-pass-after-docstring")
108
+
109
+ if quiet:
110
+ cli_args.append("--quiet")
111
+
112
+ if verbose:
113
+ cli_args.append("--verbose")
114
+
115
+ if stdin_display_name:
116
+ cli_args.append("--stdin-display-name")
117
+ cli_args.append(stdin_display_name)
118
+
119
+ if config:
120
+ cli_args.append("--config")
121
+ cli_args.append(config)
122
+
123
+ if in_place:
124
+ cli_args.append("--in-place")
125
+
126
+ if stdout:
127
+ cli_args.append("--stdout")
128
+
129
+ super().__init__(cli_args)
130
+
131
+ def __call__(self) -> int:
132
+ from autoflake import _main as run_autoflake
133
+
134
+ return run_autoflake(
135
+ self.cli_args,
136
+ standard_out=LazyStdout(),
137
+ standard_error=LazyStderr(),
138
+ )
duty/tools/_base.py ADDED
@@ -0,0 +1,66 @@
1
+ """Utilities for creating tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shlex
6
+ import sys
7
+ from io import StringIO
8
+ from typing import Any
9
+
10
+ if sys.version_info >= (3, 11):
11
+ from typing import Self
12
+ else:
13
+ from typing_extensions import Self
14
+
15
+
16
+ class LazyStdout(StringIO):
17
+ """Lazy stdout buffer.
18
+
19
+ Can be used when tools' main entry-points
20
+ expect a file-like object for stdout.
21
+ """
22
+
23
+ def write(self, value: str) -> int:
24
+ return sys.stdout.write(value)
25
+
26
+ def __repr__(self) -> str:
27
+ return "stdout"
28
+
29
+
30
+ class LazyStderr(StringIO):
31
+ """Lazy stderr buffer.
32
+
33
+ Can be used when tools' main entry-points
34
+ expect a file-like object for stderr.
35
+ """
36
+
37
+ def write(self, value: str) -> int:
38
+ return sys.stderr.write(value)
39
+
40
+ def __repr__(self) -> str:
41
+ return "stderr"
42
+
43
+
44
+ class Tool:
45
+ """Base class for tools."""
46
+
47
+ cli_name: str = ""
48
+
49
+ def __init__(
50
+ self,
51
+ cli_args: list[str] | None = None,
52
+ py_args: dict[str, Any] | None = None,
53
+ ) -> None:
54
+ self.cli_args: list[str] = cli_args or []
55
+ self.py_args: dict[str, Any] = py_args or {}
56
+
57
+ def add_args(self, *args: str) -> Self:
58
+ """Add arguments."""
59
+ self.cli_args.extend(args)
60
+ return self
61
+
62
+ @property
63
+ def cli_command(self) -> str:
64
+ if not self.cli_name:
65
+ raise ValueError("This tool does not provide a CLI.")
66
+ return shlex.join([self.cli_name, *self.cli_args])