antsibull-nox 0.2.0__py3-none-any.whl → 0.3.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.
@@ -0,0 +1,627 @@
1
+ # Author: Felix Fontein <felix@fontein.de>
2
+ # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
3
+ # https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ # SPDX-License-Identifier: GPL-3.0-or-later
5
+ # SPDX-FileCopyrightText: 2025, Ansible Project
6
+
7
+ """
8
+ Create nox lint sessions.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import shlex
15
+ from pathlib import Path
16
+
17
+ import nox
18
+
19
+ from ..paths import (
20
+ filter_paths,
21
+ list_all_files,
22
+ )
23
+ from .collections import (
24
+ CollectionSetup,
25
+ prepare_collections,
26
+ )
27
+ from .utils import (
28
+ IN_CI,
29
+ compose_description,
30
+ install,
31
+ run_bare_script,
32
+ )
33
+
34
+ CODE_FILES = [
35
+ "plugins",
36
+ "tests/unit",
37
+ ]
38
+
39
+ MODULE_PATHS = [
40
+ "plugins/modules/",
41
+ "plugins/module_utils/",
42
+ "tests/unit/plugins/modules/",
43
+ "tests/unit/plugins/module_utils/",
44
+ ]
45
+
46
+
47
+ def add_lint(
48
+ *,
49
+ make_lint_default: bool,
50
+ has_formatters: bool,
51
+ has_codeqa: bool,
52
+ has_yamllint: bool,
53
+ has_typing: bool,
54
+ has_config_lint: bool,
55
+ ) -> None:
56
+ """
57
+ Add nox meta session for linting.
58
+ """
59
+
60
+ def lint(session: nox.Session) -> None: # pylint: disable=unused-argument
61
+ pass # this session is deliberately empty
62
+
63
+ dependent_sessions = []
64
+ if has_formatters:
65
+ dependent_sessions.append("formatters")
66
+ if has_codeqa:
67
+ dependent_sessions.append("codeqa")
68
+ if has_yamllint:
69
+ dependent_sessions.append("yamllint")
70
+ if has_typing:
71
+ dependent_sessions.append("typing")
72
+ if has_config_lint:
73
+ dependent_sessions.append("antsibull-nox-config")
74
+
75
+ lint.__doc__ = compose_description(
76
+ prefix={
77
+ "one": "Meta session for triggering the following session:",
78
+ "other": "Meta session for triggering the following sessions:",
79
+ },
80
+ programs={
81
+ "formatters": has_formatters,
82
+ "codeqa": has_codeqa,
83
+ "yamllint": has_yamllint,
84
+ "typing": has_typing,
85
+ "antsibull-nox-config": has_config_lint,
86
+ },
87
+ )
88
+ nox.session(
89
+ name="lint",
90
+ default=make_lint_default,
91
+ requires=dependent_sessions,
92
+ )(lint)
93
+
94
+
95
+ def add_formatters(
96
+ *,
97
+ extra_code_files: list[str],
98
+ # isort:
99
+ run_isort: bool,
100
+ isort_config: str | os.PathLike | None,
101
+ isort_package: str,
102
+ # black:
103
+ run_black: bool,
104
+ run_black_modules: bool | None,
105
+ black_config: str | os.PathLike | None,
106
+ black_package: str,
107
+ ) -> None:
108
+ """
109
+ Add nox session for formatters.
110
+ """
111
+ if run_black_modules is None:
112
+ run_black_modules = run_black
113
+ run_check = IN_CI
114
+
115
+ def compose_dependencies() -> list[str]:
116
+ deps = []
117
+ if run_isort:
118
+ deps.append(isort_package)
119
+ if run_black or run_black_modules:
120
+ deps.append(black_package)
121
+ return deps
122
+
123
+ def execute_isort(session: nox.Session) -> None:
124
+ command: list[str] = [
125
+ "isort",
126
+ ]
127
+ if run_check:
128
+ command.append("--check")
129
+ if isort_config is not None:
130
+ command.extend(["--settings-file", str(isort_config)])
131
+ command.extend(session.posargs)
132
+ command.extend(filter_paths(CODE_FILES + ["noxfile.py"] + extra_code_files))
133
+ session.run(*command)
134
+
135
+ def execute_black_for(session: nox.Session, paths: list[str]) -> None:
136
+ if not paths:
137
+ return
138
+ command = ["black"]
139
+ if run_check:
140
+ command.append("--check")
141
+ if black_config is not None:
142
+ command.extend(["--config", str(black_config)])
143
+ command.extend(session.posargs)
144
+ command.extend(paths)
145
+ session.run(*command)
146
+
147
+ def execute_black(session: nox.Session) -> None:
148
+ if run_black and run_black_modules:
149
+ execute_black_for(
150
+ session, filter_paths(CODE_FILES + ["noxfile.py"] + extra_code_files)
151
+ )
152
+ return
153
+ if run_black:
154
+ paths = (
155
+ filter_paths(
156
+ CODE_FILES,
157
+ remove=MODULE_PATHS,
158
+ extensions=[".py"],
159
+ )
160
+ + ["noxfile.py"]
161
+ + extra_code_files
162
+ )
163
+ execute_black_for(session, paths)
164
+ if run_black_modules:
165
+ paths = filter_paths(
166
+ CODE_FILES,
167
+ restrict=MODULE_PATHS,
168
+ extensions=[".py"],
169
+ )
170
+ execute_black_for(session, paths)
171
+
172
+ def formatters(session: nox.Session) -> None:
173
+ install(session, *compose_dependencies())
174
+ if run_isort:
175
+ execute_isort(session)
176
+ if run_black or run_black_modules:
177
+ execute_black(session)
178
+
179
+ formatters.__doc__ = compose_description(
180
+ prefix={
181
+ "one": "Run code formatter:",
182
+ "other": "Run code formatters:",
183
+ },
184
+ programs={
185
+ "isort": run_isort,
186
+ "black": run_black,
187
+ },
188
+ )
189
+ nox.session(name="formatters", default=False)(formatters)
190
+
191
+
192
+ def add_codeqa( # noqa: C901
193
+ *,
194
+ extra_code_files: list[str],
195
+ # flake8:
196
+ run_flake8: bool,
197
+ flake8_config: str | os.PathLike | None,
198
+ flake8_package: str,
199
+ # pylint:
200
+ run_pylint: bool,
201
+ pylint_rcfile: str | os.PathLike | None,
202
+ pylint_modules_rcfile: str | os.PathLike | None,
203
+ pylint_package: str,
204
+ pylint_ansible_core_package: str | None,
205
+ pylint_extra_deps: list[str],
206
+ ) -> None:
207
+ """
208
+ Add nox session for codeqa.
209
+ """
210
+
211
+ def compose_dependencies() -> list[str]:
212
+ deps = []
213
+ if run_flake8:
214
+ deps.append(flake8_package)
215
+ if run_pylint:
216
+ deps.append(pylint_package)
217
+ if pylint_ansible_core_package is not None:
218
+ deps.append(pylint_ansible_core_package)
219
+ if os.path.isdir("tests/unit"):
220
+ deps.append("pytest")
221
+ if os.path.isfile("tests/unit/requirements.txt"):
222
+ deps.extend(["-r", "tests/unit/requirements.txt"])
223
+ for extra_dep in pylint_extra_deps:
224
+ deps.extend(shlex.split(extra_dep))
225
+ return deps
226
+
227
+ def execute_flake8(session: nox.Session) -> None:
228
+ command: list[str] = [
229
+ "flake8",
230
+ ]
231
+ if flake8_config is not None:
232
+ command.extend(["--config", str(flake8_config)])
233
+ command.extend(session.posargs)
234
+ command.extend(filter_paths(CODE_FILES + ["noxfile.py"] + extra_code_files))
235
+ session.run(*command)
236
+
237
+ def execute_pylint_impl(
238
+ session: nox.Session,
239
+ prepared_collections: CollectionSetup,
240
+ config: os.PathLike | str | None,
241
+ paths: list[str],
242
+ ) -> None:
243
+ command = ["pylint"]
244
+ if config is not None:
245
+ command.extend(
246
+ [
247
+ "--rcfile",
248
+ os.path.join(prepared_collections.current_collection.path, config),
249
+ ]
250
+ )
251
+ command.extend(["--source-roots", "."])
252
+ command.extend(session.posargs)
253
+ command.extend(prepared_collections.prefix_current_paths(paths))
254
+ session.run(*command)
255
+
256
+ def execute_pylint(
257
+ session: nox.Session, prepared_collections: CollectionSetup
258
+ ) -> None:
259
+ if pylint_modules_rcfile is not None and pylint_modules_rcfile != pylint_rcfile:
260
+ # Only run pylint twice when using different configurations
261
+ module_paths = filter_paths(
262
+ CODE_FILES, restrict=MODULE_PATHS, extensions=[".py"]
263
+ )
264
+ other_paths = filter_paths(
265
+ CODE_FILES, remove=MODULE_PATHS, extensions=[".py"]
266
+ )
267
+ else:
268
+ # Otherwise run it only once using the general configuration
269
+ module_paths = []
270
+ other_paths = filter_paths(CODE_FILES)
271
+
272
+ with session.chdir(prepared_collections.current_place):
273
+ if module_paths:
274
+ execute_pylint_impl(
275
+ session,
276
+ prepared_collections,
277
+ pylint_modules_rcfile or pylint_rcfile,
278
+ module_paths,
279
+ )
280
+
281
+ if other_paths:
282
+ execute_pylint_impl(
283
+ session, prepared_collections, pylint_rcfile, other_paths
284
+ )
285
+
286
+ def codeqa(session: nox.Session) -> None:
287
+ install(session, *compose_dependencies())
288
+ prepared_collections: CollectionSetup | None = None
289
+ if run_pylint:
290
+ prepared_collections = prepare_collections(
291
+ session,
292
+ install_in_site_packages=False,
293
+ extra_deps_files=["tests/unit/requirements.yml"],
294
+ )
295
+ if not prepared_collections:
296
+ session.warn("Skipping pylint...")
297
+ if run_flake8:
298
+ execute_flake8(session)
299
+ if run_pylint and prepared_collections:
300
+ execute_pylint(session, prepared_collections)
301
+
302
+ codeqa.__doc__ = compose_description(
303
+ prefix={
304
+ "other": "Run code QA:",
305
+ },
306
+ programs={
307
+ "flake8": run_flake8,
308
+ "pylint": run_pylint,
309
+ },
310
+ )
311
+ nox.session(name="codeqa", default=False)(codeqa)
312
+
313
+
314
+ def add_yamllint(
315
+ *,
316
+ run_yamllint: bool,
317
+ yamllint_config: str | os.PathLike | None,
318
+ yamllint_config_plugins: str | os.PathLike | None,
319
+ yamllint_config_plugins_examples: str | os.PathLike | None,
320
+ yamllint_package: str,
321
+ ) -> None:
322
+ """
323
+ Add yamllint session for linting YAML files and plugin/module docs.
324
+ """
325
+
326
+ def compose_dependencies() -> list[str]:
327
+ deps = []
328
+ if run_yamllint:
329
+ deps.append(yamllint_package)
330
+ return deps
331
+
332
+ def to_str(config: str | os.PathLike | None) -> str | None:
333
+ return str(config) if config else None
334
+
335
+ def execute_yamllint(session: nox.Session) -> None:
336
+ # Run yamllint
337
+ all_files = list_all_files()
338
+ cwd = Path.cwd()
339
+ all_yaml_filenames = [
340
+ str(file.relative_to(cwd))
341
+ for file in all_files
342
+ if file.name.lower().endswith((".yml", ".yaml"))
343
+ ]
344
+ if not all_yaml_filenames:
345
+ session.warn("Skipping yamllint since no YAML file was found...")
346
+ return
347
+
348
+ command = ["yamllint"]
349
+ if yamllint_config is not None:
350
+ command.extend(
351
+ [
352
+ "-c",
353
+ str(yamllint_config),
354
+ ]
355
+ )
356
+ command.append("--strict")
357
+ command.append("--")
358
+ command.extend(all_yaml_filenames)
359
+ command.extend(session.posargs)
360
+ session.run(*command)
361
+
362
+ def execute_plugin_yamllint(session: nox.Session) -> None:
363
+ # Run yamllint
364
+ all_files = list_all_files()
365
+ cwd = Path.cwd()
366
+ plugins_dir = cwd / "plugins"
367
+ ignore_dirs = [
368
+ plugins_dir / "action",
369
+ plugins_dir / "module_utils",
370
+ plugins_dir / "plugin_utils",
371
+ ]
372
+ all_plugin_files = [
373
+ file
374
+ for file in all_files
375
+ if file.is_relative_to(plugins_dir)
376
+ and file.name.lower().endswith((".py", ".yml", ".yaml"))
377
+ and not any(file.is_relative_to(dir) for dir in ignore_dirs)
378
+ ]
379
+ if not all_plugin_files:
380
+ session.warn(
381
+ "Skipping yamllint for modules/plugins since"
382
+ " no appropriate Python file was found..."
383
+ )
384
+ return
385
+ run_bare_script(
386
+ session,
387
+ "plugin-yamllint",
388
+ use_session_python=True,
389
+ files=all_plugin_files,
390
+ extra_data={
391
+ "config": to_str(yamllint_config_plugins or yamllint_config),
392
+ "config_examples": to_str(
393
+ yamllint_config_plugins_examples
394
+ or yamllint_config_plugins
395
+ or yamllint_config
396
+ ),
397
+ },
398
+ )
399
+
400
+ def yamllint(session: nox.Session) -> None:
401
+ install(session, *compose_dependencies())
402
+ if run_yamllint:
403
+ execute_yamllint(session)
404
+ execute_plugin_yamllint(session)
405
+
406
+ yamllint.__doc__ = compose_description(
407
+ prefix={
408
+ "one": "Run YAML checker:",
409
+ "other": "Run YAML checkers:",
410
+ },
411
+ programs={
412
+ "yamllint": run_yamllint,
413
+ },
414
+ )
415
+ nox.session(name="yamllint", default=False)(yamllint)
416
+
417
+
418
+ def add_typing(
419
+ *,
420
+ extra_code_files: list[str],
421
+ run_mypy: bool,
422
+ mypy_config: str | os.PathLike | None,
423
+ mypy_package: str,
424
+ mypy_ansible_core_package: str | None,
425
+ mypy_extra_deps: list[str],
426
+ ) -> None:
427
+ """
428
+ Add nox session for typing.
429
+ """
430
+
431
+ def compose_dependencies() -> list[str]:
432
+ deps = []
433
+ if run_mypy:
434
+ deps.append(mypy_package)
435
+ if mypy_ansible_core_package is not None:
436
+ deps.append(mypy_ansible_core_package)
437
+ if os.path.isdir("tests/unit"):
438
+ deps.append("pytest")
439
+ if os.path.isfile("tests/unit/requirements.txt"):
440
+ deps.extend(["-r", "tests/unit/requirements.txt"])
441
+ for extra_dep in mypy_extra_deps:
442
+ deps.extend(shlex.split(extra_dep))
443
+ return deps
444
+
445
+ def execute_mypy(
446
+ session: nox.Session, prepared_collections: CollectionSetup
447
+ ) -> None:
448
+ # Run mypy
449
+ with session.chdir(prepared_collections.current_place):
450
+ command = ["mypy"]
451
+ if mypy_config is not None:
452
+ command.extend(
453
+ [
454
+ "--config-file",
455
+ os.path.join(
456
+ prepared_collections.current_collection.path, mypy_config
457
+ ),
458
+ ]
459
+ )
460
+ command.append("--namespace-packages")
461
+ command.append("--explicit-package-bases")
462
+ command.extend(session.posargs)
463
+ command.extend(
464
+ prepared_collections.prefix_current_paths(CODE_FILES + extra_code_files)
465
+ )
466
+ session.run(
467
+ *command, env={"MYPYPATH": str(prepared_collections.current_place)}
468
+ )
469
+
470
+ def typing(session: nox.Session) -> None:
471
+ install(session, *compose_dependencies())
472
+ prepared_collections = prepare_collections(
473
+ session,
474
+ install_in_site_packages=False,
475
+ extra_deps_files=["tests/unit/requirements.yml"],
476
+ )
477
+ if not prepared_collections:
478
+ session.warn("Skipping mypy...")
479
+ if run_mypy and prepared_collections:
480
+ execute_mypy(session, prepared_collections)
481
+
482
+ typing.__doc__ = compose_description(
483
+ prefix={
484
+ "one": "Run type checker:",
485
+ "other": "Run type checkers:",
486
+ },
487
+ programs={
488
+ "mypy": run_mypy,
489
+ },
490
+ )
491
+ nox.session(name="typing", default=False)(typing)
492
+
493
+
494
+ def add_config_lint(
495
+ *,
496
+ run_antsibullnox_config_lint: bool,
497
+ ):
498
+ """
499
+ Add nox session for antsibull-nox config linting.
500
+ """
501
+
502
+ def antsibull_nox_config(session: nox.Session) -> None:
503
+ if run_antsibullnox_config_lint:
504
+ run_bare_script(
505
+ session,
506
+ "antsibull-nox-lint-config",
507
+ )
508
+
509
+ session.run("antsibull-nox", "lint-config")
510
+
511
+ antsibull_nox_config.__doc__ = "Lint antsibull-nox config"
512
+ nox.session(name="antsibull-nox-config", python=False, default=False)(
513
+ antsibull_nox_config
514
+ )
515
+
516
+
517
+ def add_lint_sessions(
518
+ *,
519
+ make_lint_default: bool = True,
520
+ extra_code_files: list[str] | None = None,
521
+ # isort:
522
+ run_isort: bool = True,
523
+ isort_config: str | os.PathLike | None = None,
524
+ isort_package: str = "isort",
525
+ # black:
526
+ run_black: bool = True,
527
+ run_black_modules: bool | None = None,
528
+ black_config: str | os.PathLike | None = None,
529
+ black_package: str = "black",
530
+ # flake8:
531
+ run_flake8: bool = True,
532
+ flake8_config: str | os.PathLike | None = None,
533
+ flake8_package: str = "flake8",
534
+ # pylint:
535
+ run_pylint: bool = True,
536
+ pylint_rcfile: str | os.PathLike | None = None,
537
+ pylint_modules_rcfile: str | os.PathLike | None = None,
538
+ pylint_package: str = "pylint",
539
+ pylint_ansible_core_package: str | None = "ansible-core",
540
+ pylint_extra_deps: list[str] | None = None,
541
+ # yamllint:
542
+ run_yamllint: bool = False,
543
+ yamllint_config: str | os.PathLike | None = None,
544
+ yamllint_config_plugins: str | os.PathLike | None = None,
545
+ yamllint_config_plugins_examples: str | os.PathLike | None = None,
546
+ yamllint_package: str = "yamllint",
547
+ # mypy:
548
+ run_mypy: bool = True,
549
+ mypy_config: str | os.PathLike | None = None,
550
+ mypy_package: str = "mypy",
551
+ mypy_ansible_core_package: str | None = "ansible-core",
552
+ mypy_extra_deps: list[str] | None = None,
553
+ # antsibull-nox config lint:
554
+ run_antsibullnox_config_lint: bool = True,
555
+ ) -> None:
556
+ """
557
+ Add nox sessions for linting.
558
+ """
559
+ has_formatters = run_isort or run_black or run_black_modules or False
560
+ has_codeqa = run_flake8 or run_pylint
561
+ has_yamllint = run_yamllint
562
+ has_typing = run_mypy
563
+ has_config_lint = run_antsibullnox_config_lint
564
+
565
+ add_lint(
566
+ has_formatters=has_formatters,
567
+ has_codeqa=has_codeqa,
568
+ has_yamllint=has_yamllint,
569
+ has_typing=has_typing,
570
+ has_config_lint=has_config_lint,
571
+ make_lint_default=make_lint_default,
572
+ )
573
+
574
+ if has_formatters:
575
+ add_formatters(
576
+ extra_code_files=extra_code_files or [],
577
+ run_isort=run_isort,
578
+ isort_config=isort_config,
579
+ isort_package=isort_package,
580
+ run_black=run_black,
581
+ run_black_modules=run_black_modules,
582
+ black_config=black_config,
583
+ black_package=black_package,
584
+ )
585
+
586
+ if has_codeqa:
587
+ add_codeqa(
588
+ extra_code_files=extra_code_files or [],
589
+ run_flake8=run_flake8,
590
+ flake8_config=flake8_config,
591
+ flake8_package=flake8_package,
592
+ run_pylint=run_pylint,
593
+ pylint_rcfile=pylint_rcfile,
594
+ pylint_modules_rcfile=pylint_modules_rcfile,
595
+ pylint_package=pylint_package,
596
+ pylint_ansible_core_package=pylint_ansible_core_package,
597
+ pylint_extra_deps=pylint_extra_deps or [],
598
+ )
599
+
600
+ if has_yamllint:
601
+ add_yamllint(
602
+ run_yamllint=run_yamllint,
603
+ yamllint_config=yamllint_config,
604
+ yamllint_config_plugins=yamllint_config_plugins,
605
+ yamllint_config_plugins_examples=yamllint_config_plugins_examples,
606
+ yamllint_package=yamllint_package,
607
+ )
608
+
609
+ if has_typing:
610
+ add_typing(
611
+ extra_code_files=extra_code_files or [],
612
+ run_mypy=run_mypy,
613
+ mypy_config=mypy_config,
614
+ mypy_package=mypy_package,
615
+ mypy_ansible_core_package=mypy_ansible_core_package,
616
+ mypy_extra_deps=mypy_extra_deps or [],
617
+ )
618
+
619
+ if has_config_lint:
620
+ add_config_lint(
621
+ run_antsibullnox_config_lint=run_antsibullnox_config_lint,
622
+ )
623
+
624
+
625
+ __all__ = [
626
+ "add_lint_sessions",
627
+ ]