duty 1.2.0__py3-none-any.whl → 1.4.0__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.
- duty/callables/__init__.py +32 -24
- duty/callables/build.py +76 -0
- duty/callables/git_changelog.py +178 -0
- duty/callables/griffe.py +227 -0
- duty/callables/twine.py +284 -0
- duty/cli.py +2 -0
- duty/context.py +6 -1
- duty/debug.py +4 -1
- duty/tools/__init__.py +48 -0
- duty/tools/_autoflake.py +138 -0
- duty/tools/_base.py +66 -0
- duty/tools/_black.py +184 -0
- duty/tools/_blacken_docs.py +115 -0
- duty/tools/_build.py +84 -0
- duty/tools/_coverage.py +721 -0
- duty/tools/_flake8.py +230 -0
- duty/tools/_git_changelog.py +186 -0
- duty/tools/_griffe.py +226 -0
- duty/tools/_interrogate.py +160 -0
- duty/tools/_isort.py +579 -0
- duty/tools/_mkdocs.py +271 -0
- duty/tools/_mypy.py +502 -0
- duty/tools/_pytest.py +483 -0
- duty/tools/_ruff.py +451 -0
- duty/tools/_safety.py +97 -0
- duty/tools/_ssort.py +44 -0
- duty/tools/_twine.py +289 -0
- duty/validation.py +24 -6
- {duty-1.2.0.dist-info → duty-1.4.0.dist-info}/METADATA +8 -5
- duty-1.4.0.dist-info/RECORD +53 -0
- {duty-1.2.0.dist-info → duty-1.4.0.dist-info}/WHEEL +1 -1
- duty-1.2.0.dist-info/RECORD +0 -30
- {duty-1.2.0.dist-info → duty-1.4.0.dist-info}/entry_points.txt +0 -0
- {duty-1.2.0.dist-info → duty-1.4.0.dist-info}/licenses/LICENSE +0 -0
duty/tools/_ruff.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Callable for [Ruff](https://github.com/charliermarsh/ruff)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
|
|
10
|
+
from duty.tools._base import Tool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@lru_cache(maxsize=None)
|
|
14
|
+
def _find_ruff() -> str:
|
|
15
|
+
from ruff.__main__ import find_ruff_bin
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
return find_ruff_bin()
|
|
19
|
+
except FileNotFoundError:
|
|
20
|
+
paths = os.environ["PATH"]
|
|
21
|
+
for path in paths.split(os.pathsep):
|
|
22
|
+
ruff = os.path.join(path, "ruff")
|
|
23
|
+
if os.path.exists(ruff):
|
|
24
|
+
return ruff
|
|
25
|
+
py_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
|
|
26
|
+
pypackages_bin = os.path.join("__pypackages__", py_version, "bin")
|
|
27
|
+
ruff = os.path.join(pypackages_bin, "ruff")
|
|
28
|
+
if os.path.exists(ruff):
|
|
29
|
+
return ruff
|
|
30
|
+
return "ruff"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ruff(Tool): # noqa: N801
|
|
34
|
+
"""Call [Ruff](https://github.com/charliermarsh/ruff)."""
|
|
35
|
+
|
|
36
|
+
cli_name = "ruff"
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def check(
|
|
40
|
+
cls,
|
|
41
|
+
*files: str,
|
|
42
|
+
config: str | None = None,
|
|
43
|
+
fix: bool | None = None,
|
|
44
|
+
show_source: bool | None = None,
|
|
45
|
+
show_fixes: bool | None = None,
|
|
46
|
+
diff: bool | None = None,
|
|
47
|
+
watch: bool | None = None,
|
|
48
|
+
fix_only: bool | None = None,
|
|
49
|
+
output_format: str | None = None,
|
|
50
|
+
statistics: bool | None = None,
|
|
51
|
+
add_noqa: bool | None = None,
|
|
52
|
+
show_files: bool | None = None,
|
|
53
|
+
show_settings: bool | None = None,
|
|
54
|
+
select: list[str] | None = None,
|
|
55
|
+
ignore: list[str] | None = None,
|
|
56
|
+
extend_select: list[str] | None = None,
|
|
57
|
+
per_file_ignores: dict[str, list[str]] | None = None,
|
|
58
|
+
fixable: list[str] | None = None,
|
|
59
|
+
unfixable: list[str] | None = None,
|
|
60
|
+
exclude: list[str] | None = None,
|
|
61
|
+
extend_exclude: list[str] | None = None,
|
|
62
|
+
respect_gitignore: bool | None = None,
|
|
63
|
+
force_exclude: bool | None = None,
|
|
64
|
+
no_cache: bool | None = None,
|
|
65
|
+
isolated: bool | None = None,
|
|
66
|
+
cache_dir: str | None = None,
|
|
67
|
+
stdin_filename: str | None = None,
|
|
68
|
+
exit_zero: bool | None = None,
|
|
69
|
+
exit_non_zero_on_fix: bool | None = None,
|
|
70
|
+
verbose: bool = False,
|
|
71
|
+
quiet: bool = False,
|
|
72
|
+
silent: bool = False,
|
|
73
|
+
) -> ruff:
|
|
74
|
+
"""Run Ruff on the given files or directories.
|
|
75
|
+
|
|
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
|
|
105
|
+
verbose: Enable verbose logging.
|
|
106
|
+
quiet: Print lint violations, but nothing else.
|
|
107
|
+
silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
|
|
108
|
+
"""
|
|
109
|
+
cli_args = ["check", *files]
|
|
110
|
+
|
|
111
|
+
if fix:
|
|
112
|
+
cli_args.append("--fix")
|
|
113
|
+
|
|
114
|
+
if show_source:
|
|
115
|
+
cli_args.append("--show-source")
|
|
116
|
+
|
|
117
|
+
if show_fixes:
|
|
118
|
+
cli_args.append("--show-fixes")
|
|
119
|
+
|
|
120
|
+
if diff:
|
|
121
|
+
cli_args.append("--diff")
|
|
122
|
+
|
|
123
|
+
if watch:
|
|
124
|
+
cli_args.append("--watch")
|
|
125
|
+
|
|
126
|
+
if fix_only:
|
|
127
|
+
cli_args.append("--fix-only")
|
|
128
|
+
|
|
129
|
+
if output_format:
|
|
130
|
+
cli_args.append("--format")
|
|
131
|
+
cli_args.append(output_format)
|
|
132
|
+
|
|
133
|
+
if config:
|
|
134
|
+
cli_args.append("--config")
|
|
135
|
+
cli_args.append(config)
|
|
136
|
+
|
|
137
|
+
if statistics:
|
|
138
|
+
cli_args.append("--statistics")
|
|
139
|
+
|
|
140
|
+
if add_noqa:
|
|
141
|
+
cli_args.append("--add-noqa")
|
|
142
|
+
|
|
143
|
+
if show_files:
|
|
144
|
+
cli_args.append("--show-files")
|
|
145
|
+
|
|
146
|
+
if show_settings:
|
|
147
|
+
cli_args.append("--show-settings")
|
|
148
|
+
|
|
149
|
+
if select:
|
|
150
|
+
cli_args.append("--select")
|
|
151
|
+
cli_args.append(",".join(select))
|
|
152
|
+
|
|
153
|
+
if ignore:
|
|
154
|
+
cli_args.append("--ignore")
|
|
155
|
+
cli_args.append(",".join(ignore))
|
|
156
|
+
|
|
157
|
+
if extend_select:
|
|
158
|
+
cli_args.append("--extend-select")
|
|
159
|
+
cli_args.append(",".join(extend_select))
|
|
160
|
+
|
|
161
|
+
if per_file_ignores:
|
|
162
|
+
cli_args.append("--per-file-ignores")
|
|
163
|
+
cli_args.append(
|
|
164
|
+
" ".join(f"{path}:{','.join(codes)}" for path, codes in per_file_ignores.items()),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if fixable:
|
|
168
|
+
cli_args.append("--fixable")
|
|
169
|
+
cli_args.append(",".join(fixable))
|
|
170
|
+
|
|
171
|
+
if unfixable:
|
|
172
|
+
cli_args.append("--unfixable")
|
|
173
|
+
cli_args.append(",".join(unfixable))
|
|
174
|
+
|
|
175
|
+
if exclude:
|
|
176
|
+
cli_args.append("--exclude")
|
|
177
|
+
cli_args.append(",".join(exclude))
|
|
178
|
+
|
|
179
|
+
if extend_exclude:
|
|
180
|
+
cli_args.append("--extend-exclude")
|
|
181
|
+
cli_args.append(",".join(extend_exclude))
|
|
182
|
+
|
|
183
|
+
if respect_gitignore:
|
|
184
|
+
cli_args.append("--respect-gitignore")
|
|
185
|
+
|
|
186
|
+
if force_exclude:
|
|
187
|
+
cli_args.append("--force-exclude")
|
|
188
|
+
|
|
189
|
+
if no_cache:
|
|
190
|
+
cli_args.append("--no-cache")
|
|
191
|
+
|
|
192
|
+
if isolated:
|
|
193
|
+
cli_args.append("--isolated")
|
|
194
|
+
|
|
195
|
+
if cache_dir:
|
|
196
|
+
cli_args.append("--cache-dir")
|
|
197
|
+
cli_args.append(cache_dir)
|
|
198
|
+
|
|
199
|
+
if stdin_filename:
|
|
200
|
+
cli_args.append("--stdin-filename")
|
|
201
|
+
cli_args.append(stdin_filename)
|
|
202
|
+
|
|
203
|
+
if exit_zero:
|
|
204
|
+
cli_args.append("--exit-zero")
|
|
205
|
+
|
|
206
|
+
if exit_non_zero_on_fix:
|
|
207
|
+
cli_args.append("--exit-non-zero-on-fix")
|
|
208
|
+
|
|
209
|
+
if verbose:
|
|
210
|
+
cli_args.append("--verbose")
|
|
211
|
+
|
|
212
|
+
if quiet:
|
|
213
|
+
cli_args.append("--quiet")
|
|
214
|
+
|
|
215
|
+
if silent:
|
|
216
|
+
cli_args.append("--silent")
|
|
217
|
+
|
|
218
|
+
return cls(cli_args)
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def format(
|
|
222
|
+
cls,
|
|
223
|
+
*files: str,
|
|
224
|
+
config: str | None = None,
|
|
225
|
+
check: bool | None = None,
|
|
226
|
+
diff: bool | None = None,
|
|
227
|
+
target_version: str | None = None,
|
|
228
|
+
preview: bool | None = None,
|
|
229
|
+
exclude: list[str] | None = None,
|
|
230
|
+
extend_exclude: list[str] | None = None,
|
|
231
|
+
respect_gitignore: bool | None = None,
|
|
232
|
+
force_exclude: bool | None = None,
|
|
233
|
+
no_cache: bool | None = None,
|
|
234
|
+
isolated: bool | None = None,
|
|
235
|
+
cache_dir: str | None = None,
|
|
236
|
+
stdin_filename: str | None = None,
|
|
237
|
+
verbose: bool = False,
|
|
238
|
+
quiet: bool = False,
|
|
239
|
+
silent: bool = False,
|
|
240
|
+
) -> ruff:
|
|
241
|
+
"""Run Ruff formatter on the given files or directories.
|
|
242
|
+
|
|
243
|
+
Parameters:
|
|
244
|
+
check: Avoid writing any formatted files back; instead, exit with a non-zero status code if any files would have been modified, and zero otherwise
|
|
245
|
+
config: Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
|
|
246
|
+
diff: Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
|
|
247
|
+
target_version: The minimum Python version that should be supported [possible values: py37, py38, py39, py310, py311, py312]
|
|
248
|
+
preview: Enable preview mode; enables unstable formatting
|
|
249
|
+
exclude: List of paths, used to omit files and/or directories from analysis
|
|
250
|
+
extend_exclude: Like --exclude, but adds additional files and directories on top of those already excluded
|
|
251
|
+
respect_gitignore: Respect file exclusions via `.gitignore` and other standard ignore files
|
|
252
|
+
force_exclude: Enforce exclusions, even for paths passed to Ruff directly on the command-line
|
|
253
|
+
no_cache: Disable cache reads
|
|
254
|
+
isolated: Ignore all configuration files
|
|
255
|
+
cache_dir: Path to the cache directory [env: RUFF_CACHE_DIR=]
|
|
256
|
+
stdin_filename: The name of the file when passing it through stdin
|
|
257
|
+
verbose: Enable verbose logging.
|
|
258
|
+
quiet: Print lint violations, but nothing else.
|
|
259
|
+
silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
|
|
260
|
+
"""
|
|
261
|
+
cli_args = ["format", *files]
|
|
262
|
+
|
|
263
|
+
if check:
|
|
264
|
+
cli_args.append("--check")
|
|
265
|
+
|
|
266
|
+
if diff:
|
|
267
|
+
cli_args.append("--diff")
|
|
268
|
+
|
|
269
|
+
if config:
|
|
270
|
+
cli_args.append("--config")
|
|
271
|
+
cli_args.append(config)
|
|
272
|
+
|
|
273
|
+
if target_version:
|
|
274
|
+
cli_args.append("--target-version")
|
|
275
|
+
cli_args.append(target_version)
|
|
276
|
+
|
|
277
|
+
if preview:
|
|
278
|
+
cli_args.append("--preview")
|
|
279
|
+
|
|
280
|
+
if exclude:
|
|
281
|
+
cli_args.append("--exclude")
|
|
282
|
+
cli_args.append(",".join(exclude))
|
|
283
|
+
|
|
284
|
+
if extend_exclude:
|
|
285
|
+
cli_args.append("--extend-exclude")
|
|
286
|
+
cli_args.append(",".join(extend_exclude))
|
|
287
|
+
|
|
288
|
+
if respect_gitignore:
|
|
289
|
+
cli_args.append("--respect-gitignore")
|
|
290
|
+
|
|
291
|
+
if force_exclude:
|
|
292
|
+
cli_args.append("--force-exclude")
|
|
293
|
+
|
|
294
|
+
if no_cache:
|
|
295
|
+
cli_args.append("--no-cache")
|
|
296
|
+
|
|
297
|
+
if isolated:
|
|
298
|
+
cli_args.append("--isolated")
|
|
299
|
+
|
|
300
|
+
if cache_dir:
|
|
301
|
+
cli_args.append("--cache-dir")
|
|
302
|
+
cli_args.append(cache_dir)
|
|
303
|
+
|
|
304
|
+
if stdin_filename:
|
|
305
|
+
cli_args.append("--stdin-filename")
|
|
306
|
+
cli_args.append(stdin_filename)
|
|
307
|
+
|
|
308
|
+
if verbose:
|
|
309
|
+
cli_args.append("--verbose")
|
|
310
|
+
|
|
311
|
+
if quiet:
|
|
312
|
+
cli_args.append("--quiet")
|
|
313
|
+
|
|
314
|
+
if silent:
|
|
315
|
+
cli_args.append("--silent")
|
|
316
|
+
|
|
317
|
+
return cls(cli_args)
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def rule(
|
|
321
|
+
cls,
|
|
322
|
+
*,
|
|
323
|
+
output_format: str | None = None,
|
|
324
|
+
verbose: bool = False,
|
|
325
|
+
quiet: bool = False,
|
|
326
|
+
silent: bool = False,
|
|
327
|
+
) -> ruff:
|
|
328
|
+
"""Explain a rule.
|
|
329
|
+
|
|
330
|
+
Parameters:
|
|
331
|
+
output_format: Output format (default: pretty, possible values: text, json, pretty).
|
|
332
|
+
verbose: Enable verbose logging.
|
|
333
|
+
quiet: Print lint violations, but nothing else.
|
|
334
|
+
silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
|
|
335
|
+
"""
|
|
336
|
+
cli_args = ["rule"]
|
|
337
|
+
|
|
338
|
+
if output_format:
|
|
339
|
+
cli_args.append("--format")
|
|
340
|
+
cli_args.append(output_format)
|
|
341
|
+
|
|
342
|
+
if verbose:
|
|
343
|
+
cli_args.append("--verbose")
|
|
344
|
+
|
|
345
|
+
if quiet:
|
|
346
|
+
cli_args.append("--quiet")
|
|
347
|
+
|
|
348
|
+
if silent:
|
|
349
|
+
cli_args.append("--silent")
|
|
350
|
+
|
|
351
|
+
return cls(cli_args)
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def config(
|
|
355
|
+
cls,
|
|
356
|
+
*,
|
|
357
|
+
verbose: bool = False,
|
|
358
|
+
quiet: bool = False,
|
|
359
|
+
silent: bool = False,
|
|
360
|
+
) -> ruff:
|
|
361
|
+
"""List or describe the available configuration options.
|
|
362
|
+
|
|
363
|
+
Parameters:
|
|
364
|
+
verbose: Enable verbose logging.
|
|
365
|
+
quiet: Print lint violations, but nothing else.
|
|
366
|
+
silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
|
|
367
|
+
"""
|
|
368
|
+
cli_args = ["config"]
|
|
369
|
+
|
|
370
|
+
if verbose:
|
|
371
|
+
cli_args.append("--verbose")
|
|
372
|
+
|
|
373
|
+
if quiet:
|
|
374
|
+
cli_args.append("--quiet")
|
|
375
|
+
|
|
376
|
+
if silent:
|
|
377
|
+
cli_args.append("--silent")
|
|
378
|
+
|
|
379
|
+
return cls(cli_args)
|
|
380
|
+
|
|
381
|
+
@classmethod
|
|
382
|
+
def linter(
|
|
383
|
+
cls,
|
|
384
|
+
*,
|
|
385
|
+
output_format: str | None = None,
|
|
386
|
+
verbose: bool = False,
|
|
387
|
+
quiet: bool = False,
|
|
388
|
+
silent: bool = False,
|
|
389
|
+
) -> ruff:
|
|
390
|
+
"""List all supported upstream linters.
|
|
391
|
+
|
|
392
|
+
Parameters:
|
|
393
|
+
output_format: Output format [default: pretty] [possible values: text, json, pretty].
|
|
394
|
+
verbose: Enable verbose logging.
|
|
395
|
+
quiet: Print lint violations, but nothing else.
|
|
396
|
+
silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
|
|
397
|
+
"""
|
|
398
|
+
cli_args = ["linter"]
|
|
399
|
+
|
|
400
|
+
if output_format:
|
|
401
|
+
cli_args.append("--format")
|
|
402
|
+
cli_args.append(output_format)
|
|
403
|
+
|
|
404
|
+
if verbose:
|
|
405
|
+
cli_args.append("--verbose")
|
|
406
|
+
|
|
407
|
+
if quiet:
|
|
408
|
+
cli_args.append("--quiet")
|
|
409
|
+
|
|
410
|
+
if silent:
|
|
411
|
+
cli_args.append("--silent")
|
|
412
|
+
|
|
413
|
+
return cls(cli_args)
|
|
414
|
+
|
|
415
|
+
@classmethod
|
|
416
|
+
def clean(
|
|
417
|
+
cls,
|
|
418
|
+
*,
|
|
419
|
+
verbose: bool = False,
|
|
420
|
+
quiet: bool = False,
|
|
421
|
+
silent: bool = False,
|
|
422
|
+
) -> ruff:
|
|
423
|
+
"""Clear any caches in the current directory and any subdirectories.
|
|
424
|
+
|
|
425
|
+
Parameters:
|
|
426
|
+
verbose: Enable verbose logging.
|
|
427
|
+
quiet: Print lint violations, but nothing else.
|
|
428
|
+
silent: Disable all logging (but still exit with status code "1" upon detecting lint violations).
|
|
429
|
+
"""
|
|
430
|
+
cli_args = ["clean"]
|
|
431
|
+
|
|
432
|
+
if verbose:
|
|
433
|
+
cli_args.append("--verbose")
|
|
434
|
+
|
|
435
|
+
if quiet:
|
|
436
|
+
cli_args.append("--quiet")
|
|
437
|
+
|
|
438
|
+
if silent:
|
|
439
|
+
cli_args.append("--silent")
|
|
440
|
+
|
|
441
|
+
return cls(cli_args)
|
|
442
|
+
|
|
443
|
+
def __call__(self) -> int:
|
|
444
|
+
process = subprocess.run(
|
|
445
|
+
[_find_ruff(), *self.cli_args], # noqa: S603
|
|
446
|
+
capture_output=True,
|
|
447
|
+
text=True,
|
|
448
|
+
check=False,
|
|
449
|
+
)
|
|
450
|
+
print(process.stdout) # noqa: T201
|
|
451
|
+
return process.returncode
|
duty/tools/_safety.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Callable for [Safety](https://github.com/pyupio/safety)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
from io import StringIO
|
|
8
|
+
from typing import Literal, Sequence, cast
|
|
9
|
+
|
|
10
|
+
from duty.tools._base import Tool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class safety(Tool): # noqa: N801
|
|
14
|
+
"""Call [Safety](https://github.com/pyupio/safety)."""
|
|
15
|
+
|
|
16
|
+
cli_name = "safety"
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def check(
|
|
20
|
+
cls,
|
|
21
|
+
requirements: str | Sequence[str],
|
|
22
|
+
*,
|
|
23
|
+
ignore_vulns: dict[str, str] | None = None,
|
|
24
|
+
formatter: Literal["json", "bare", "text"] = "text",
|
|
25
|
+
full_report: bool = True,
|
|
26
|
+
) -> safety:
|
|
27
|
+
"""Run the safety check command.
|
|
28
|
+
|
|
29
|
+
This function makes sure we load the original, unpatched version of safety.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
requirements: Python "requirements" (list of pinned dependencies).
|
|
33
|
+
ignore_vulns: Vulnerabilities to ignore.
|
|
34
|
+
formatter: Report format.
|
|
35
|
+
full_report: Whether to output a full report.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Success/failure.
|
|
39
|
+
"""
|
|
40
|
+
return cls(py_args=dict(locals()))
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def cli_command(self) -> str:
|
|
44
|
+
raise ValueError("This command cannot be translated to a CLI command.")
|
|
45
|
+
|
|
46
|
+
def __call__(self) -> bool:
|
|
47
|
+
requirements = self.py_args["requirements"]
|
|
48
|
+
ignore_vulns = self.py_args["ignore_vulns"]
|
|
49
|
+
formatter = self.py_args["formatter"]
|
|
50
|
+
full_report = self.py_args["full_report"]
|
|
51
|
+
|
|
52
|
+
# set default parameter values
|
|
53
|
+
ignore_vulns = ignore_vulns or {}
|
|
54
|
+
|
|
55
|
+
# undo possible patching
|
|
56
|
+
# see https://github.com/pyupio/safety/issues/348
|
|
57
|
+
for module in sys.modules:
|
|
58
|
+
if module.startswith("safety.") or module == "safety":
|
|
59
|
+
del sys.modules[module]
|
|
60
|
+
|
|
61
|
+
importlib.invalidate_caches()
|
|
62
|
+
|
|
63
|
+
# reload original, unpatched safety
|
|
64
|
+
from safety.formatter import SafetyFormatter
|
|
65
|
+
from safety.safety import calculate_remediations, check
|
|
66
|
+
from safety.util import read_requirements
|
|
67
|
+
|
|
68
|
+
# check using safety as a library
|
|
69
|
+
if isinstance(requirements, (list, tuple, set)):
|
|
70
|
+
requirements = "\n".join(requirements)
|
|
71
|
+
packages = list(read_requirements(StringIO(cast(str, requirements))))
|
|
72
|
+
|
|
73
|
+
# TODO: Safety 3 support, merge once support for v2 is dropped.
|
|
74
|
+
check_kwargs = {"packages": packages, "ignore_vulns": ignore_vulns}
|
|
75
|
+
try:
|
|
76
|
+
from safety.auth.cli_utils import build_client_session
|
|
77
|
+
|
|
78
|
+
client_session, _ = build_client_session()
|
|
79
|
+
check_kwargs["session"] = client_session
|
|
80
|
+
except ImportError:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
vulns, db_full = check(**check_kwargs)
|
|
84
|
+
remediations = calculate_remediations(vulns, db_full)
|
|
85
|
+
output_report = SafetyFormatter(formatter).render_vulnerabilities(
|
|
86
|
+
announcements=[],
|
|
87
|
+
vulnerabilities=vulns,
|
|
88
|
+
remediations=remediations,
|
|
89
|
+
full=full_report,
|
|
90
|
+
packages=packages,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# print report, return status
|
|
94
|
+
if vulns:
|
|
95
|
+
print(output_report) # noqa: T201
|
|
96
|
+
return False
|
|
97
|
+
return True
|
duty/tools/_ssort.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Callable for [ssort](https://github.com/bwhmather/ssort)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from duty.tools._base import Tool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ssort(Tool): # noqa: N801
|
|
11
|
+
"""Call [ssort](https://github.com/bwhmather/ssort)."""
|
|
12
|
+
|
|
13
|
+
cli_name = "ssort"
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
*files: str,
|
|
18
|
+
diff: bool | None = None,
|
|
19
|
+
check: bool | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Run `ssort`.
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
*files: Files to format.
|
|
25
|
+
diff: Prints a diff of all changes ssort would make to a file.
|
|
26
|
+
check: Check the file for unsorted statements. Returns 0 if nothing needs to be changed. Otherwise returns 1.
|
|
27
|
+
"""
|
|
28
|
+
cli_args = list(files)
|
|
29
|
+
|
|
30
|
+
if diff:
|
|
31
|
+
cli_args.append("--diff")
|
|
32
|
+
|
|
33
|
+
if check:
|
|
34
|
+
cli_args.append("--check")
|
|
35
|
+
|
|
36
|
+
def __call__(self) -> int:
|
|
37
|
+
from ssort._main import main as run_ssort
|
|
38
|
+
|
|
39
|
+
old_sys_argv = sys.argv
|
|
40
|
+
sys.argv = ["ssort", *self.cli_args]
|
|
41
|
+
try:
|
|
42
|
+
return run_ssort()
|
|
43
|
+
finally:
|
|
44
|
+
sys.argv = old_sys_argv
|