antsibull-nox 0.1.0__py3-none-any.whl → 0.2.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.
@@ -11,89 +11,12 @@ Utility code for scripts in data.
11
11
  from __future__ import annotations
12
12
 
13
13
  import json
14
- import sys
15
14
  import typing as t
16
15
  from pathlib import Path
17
16
 
18
17
  import nox
19
18
 
20
19
 
21
- def setup() -> tuple[list[str], dict[str, t.Any]]:
22
- """
23
- Fetch list of paths and potential extra configuration.
24
-
25
- First thing to call in an extra sanity check script in data/.
26
- """
27
- if len(sys.argv) == 3 and sys.argv[1] == "--data":
28
- # Preferred way: load information from JSON file
29
- path = sys.argv[2]
30
- try:
31
- with open(path, "rb") as f:
32
- data = json.load(f)
33
- except Exception as exc:
34
- raise ValueError(f"Error while reading JSON from {path}") from exc
35
- try:
36
- paths = get_list_of_strings(data, "paths")
37
- except ValueError as exc:
38
- raise ValueError(f"Invalid JSON content in {path}: {exc}") from exc
39
- data.pop("paths")
40
- return paths, data
41
- if len(sys.argv) >= 2:
42
- # It's also possible to pass a list of paths on the command line, to simplify
43
- # testing these scripts.
44
- return sys.argv[1:], {}
45
- # Alternatively one can pass a list of files from stdin, for example by piping
46
- # the output of 'git ls-files' into this script. This is also for testing these
47
- # scripts.
48
- return sys.stdin.read().splitlines(), {}
49
-
50
-
51
- def get_list_of_strings(
52
- data: dict[str, t.Any],
53
- key: str,
54
- *,
55
- default: list[str] | None = None,
56
- ) -> list[str]:
57
- """
58
- Retrieves a list of strings from key ``key`` of the JSON object ``data``.
59
-
60
- If ``default`` is set to a list, a missing key results in this value being returned.
61
- """
62
- sentinel = object()
63
- value = data.get(key, sentinel)
64
- if value is sentinel:
65
- if default is not None:
66
- return default
67
- raise ValueError(f"{key!r} is not a present")
68
- if not isinstance(value, list):
69
- raise ValueError(f"{key!r} is not a list, but {type(key)}")
70
- if not all(isinstance(entry, str) for entry in value):
71
- raise ValueError(f"{key!r} is not a list of strings")
72
- return t.cast(list[str], value)
73
-
74
-
75
- def get_bool(
76
- data: dict[str, t.Any],
77
- key: str,
78
- *,
79
- default: bool | None = None,
80
- ) -> bool:
81
- """
82
- Retrieves a boolean from key ``key`` of the JSON object ``data``.
83
-
84
- If ``default`` is set to a boolean, a missing key results in this value being returned.
85
- """
86
- sentinel = object()
87
- value = data.get(key, sentinel)
88
- if value is sentinel:
89
- if default is not None:
90
- return default
91
- raise ValueError(f"{key!r} is not a present")
92
- if not isinstance(value, bool):
93
- raise ValueError(f"{key!r} is not a bool, but {type(key)}")
94
- return value
95
-
96
-
97
20
  def prepare_data_script(
98
21
  session: nox.Session,
99
22
  *,
@@ -0,0 +1,235 @@
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
+ Interpret config.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import typing as t
14
+
15
+ from .ansible import AnsibleCoreVersion
16
+ from .collection import CollectionSource, setup_collection_sources
17
+ from .config import ActionGroup as ConfigActionGroup
18
+ from .config import (
19
+ Config,
20
+ DevelLikeBranch,
21
+ Sessions,
22
+ )
23
+ from .sessions import (
24
+ ActionGroup,
25
+ add_all_ansible_test_sanity_test_sessions,
26
+ add_all_ansible_test_unit_test_sessions,
27
+ add_ansible_lint,
28
+ add_ansible_test_integration_sessions_default_container,
29
+ add_build_import_check,
30
+ add_docs_check,
31
+ add_extra_checks,
32
+ add_license_check,
33
+ add_lint_sessions,
34
+ add_matrix_generator,
35
+ )
36
+ from .utils import Version
37
+
38
+
39
+ def _interpret_config(config: Config) -> None:
40
+ if config.collection_sources:
41
+ setup_collection_sources(
42
+ {
43
+ name: CollectionSource(name=name, source=source.source)
44
+ for name, source in config.collection_sources.items()
45
+ }
46
+ )
47
+
48
+
49
+ def _convert_action_groups(
50
+ action_groups: list[ConfigActionGroup] | None,
51
+ ) -> list[ActionGroup] | None:
52
+ if action_groups is None:
53
+ return None
54
+ return [
55
+ ActionGroup(
56
+ name=action_group.name,
57
+ pattern=action_group.pattern,
58
+ doc_fragment=action_group.doc_fragment,
59
+ exclusions=action_group.exclusions,
60
+ )
61
+ for action_group in action_groups
62
+ ]
63
+
64
+
65
+ def _convert_devel_like_branches(
66
+ devel_like_branches: list[DevelLikeBranch] | None,
67
+ ) -> list[tuple[str | None, str]] | None:
68
+ if devel_like_branches is None:
69
+ return None
70
+ return [(branch.repository, branch.branch) for branch in devel_like_branches]
71
+
72
+
73
+ def _convert_except_versions(
74
+ except_versions: list[AnsibleCoreVersion] | None,
75
+ ) -> list[AnsibleCoreVersion | str] | None:
76
+ return t.cast(t.Optional[list[AnsibleCoreVersion | str]], except_versions)
77
+
78
+
79
+ def _convert_core_python_versions(
80
+ core_python_versions: dict[AnsibleCoreVersion | str, list[Version]] | None,
81
+ ) -> dict[str | AnsibleCoreVersion, list[str | Version]] | None:
82
+ return t.cast(
83
+ t.Optional[dict[str | AnsibleCoreVersion, list[str | Version]]],
84
+ core_python_versions,
85
+ )
86
+
87
+
88
+ def _add_sessions(sessions: Sessions) -> None:
89
+ if sessions.lint:
90
+ add_lint_sessions(
91
+ make_lint_default=sessions.lint.default,
92
+ extra_code_files=sessions.lint.extra_code_files,
93
+ run_isort=sessions.lint.run_isort,
94
+ isort_config=sessions.lint.isort_config,
95
+ isort_package=sessions.lint.isort_package,
96
+ run_black=sessions.lint.run_black,
97
+ run_black_modules=sessions.lint.run_black_modules,
98
+ black_config=sessions.lint.black_config,
99
+ black_package=sessions.lint.black_package,
100
+ run_flake8=sessions.lint.run_flake8,
101
+ flake8_config=sessions.lint.flake8_config,
102
+ flake8_package=sessions.lint.flake8_package,
103
+ run_pylint=sessions.lint.run_pylint,
104
+ pylint_rcfile=sessions.lint.pylint_rcfile,
105
+ pylint_modules_rcfile=sessions.lint.pylint_modules_rcfile,
106
+ pylint_package=sessions.lint.pylint_package,
107
+ pylint_ansible_core_package=sessions.lint.pylint_ansible_core_package,
108
+ pylint_extra_deps=sessions.lint.pylint_extra_deps,
109
+ run_yamllint=sessions.lint.run_yamllint,
110
+ yamllint_config=sessions.lint.yamllint_config,
111
+ yamllint_config_plugins=sessions.lint.yamllint_config_plugins,
112
+ yamllint_config_plugins_examples=sessions.lint.yamllint_config_plugins_examples,
113
+ yamllint_package=sessions.lint.yamllint_package,
114
+ run_mypy=sessions.lint.run_mypy,
115
+ mypy_config=sessions.lint.mypy_config,
116
+ mypy_package=sessions.lint.mypy_package,
117
+ mypy_ansible_core_package=sessions.lint.mypy_ansible_core_package,
118
+ mypy_extra_deps=sessions.lint.mypy_extra_deps,
119
+ )
120
+ if sessions.docs_check:
121
+ add_docs_check(
122
+ make_docs_check_default=sessions.docs_check.default,
123
+ antsibull_docs_package=sessions.docs_check.antsibull_docs_package,
124
+ ansible_core_package=sessions.docs_check.ansible_core_package,
125
+ validate_collection_refs=sessions.docs_check.validate_collection_refs,
126
+ extra_collections=sessions.docs_check.extra_collections,
127
+ )
128
+ if sessions.license_check:
129
+ add_license_check(
130
+ make_license_check_default=sessions.license_check.default,
131
+ run_reuse=sessions.license_check.run_reuse,
132
+ reuse_package=sessions.license_check.reuse_package,
133
+ run_license_check=sessions.license_check.run_license_check,
134
+ license_check_extra_ignore_paths=(
135
+ sessions.license_check.license_check_extra_ignore_paths
136
+ ),
137
+ )
138
+ if sessions.extra_checks:
139
+ add_extra_checks(
140
+ make_extra_checks_default=sessions.extra_checks.default,
141
+ run_no_unwanted_files=sessions.extra_checks.run_no_unwanted_files,
142
+ no_unwanted_files_module_extensions=(
143
+ sessions.extra_checks.no_unwanted_files_module_extensions
144
+ ),
145
+ no_unwanted_files_other_extensions=(
146
+ sessions.extra_checks.no_unwanted_files_other_extensions
147
+ ),
148
+ no_unwanted_files_yaml_extensions=(
149
+ sessions.extra_checks.no_unwanted_files_yaml_extensions
150
+ ),
151
+ no_unwanted_files_skip_paths=(
152
+ sessions.extra_checks.no_unwanted_files_skip_paths
153
+ ),
154
+ no_unwanted_files_skip_directories=(
155
+ sessions.extra_checks.no_unwanted_files_skip_directories
156
+ ),
157
+ no_unwanted_files_yaml_directories=(
158
+ sessions.extra_checks.no_unwanted_files_yaml_directories
159
+ ),
160
+ no_unwanted_files_allow_symlinks=(
161
+ sessions.extra_checks.no_unwanted_files_allow_symlinks
162
+ ),
163
+ run_action_groups=sessions.extra_checks.run_action_groups,
164
+ action_groups_config=_convert_action_groups(
165
+ sessions.extra_checks.action_groups_config
166
+ ),
167
+ )
168
+ if sessions.build_import_check:
169
+ add_build_import_check(
170
+ make_build_import_check_default=sessions.build_import_check.default,
171
+ ansible_core_package=sessions.build_import_check.ansible_core_package,
172
+ run_galaxy_importer=sessions.build_import_check.run_galaxy_importer,
173
+ galaxy_importer_package=sessions.build_import_check.galaxy_importer_package,
174
+ galaxy_importer_config_path=sessions.build_import_check.galaxy_importer_config_path,
175
+ )
176
+ if sessions.ansible_test_sanity:
177
+ add_all_ansible_test_sanity_test_sessions(
178
+ default=sessions.ansible_test_sanity.default,
179
+ include_devel=sessions.ansible_test_sanity.include_devel,
180
+ include_milestone=sessions.ansible_test_sanity.include_milestone,
181
+ add_devel_like_branches=_convert_devel_like_branches(
182
+ sessions.ansible_test_sanity.add_devel_like_branches
183
+ ),
184
+ min_version=sessions.ansible_test_sanity.min_version,
185
+ max_version=sessions.ansible_test_sanity.max_version,
186
+ except_versions=_convert_except_versions(
187
+ sessions.ansible_test_sanity.except_versions
188
+ ),
189
+ )
190
+ if sessions.ansible_test_units:
191
+ add_all_ansible_test_unit_test_sessions(
192
+ default=sessions.ansible_test_units.default,
193
+ include_devel=sessions.ansible_test_units.include_devel,
194
+ include_milestone=sessions.ansible_test_units.include_milestone,
195
+ add_devel_like_branches=_convert_devel_like_branches(
196
+ sessions.ansible_test_units.add_devel_like_branches
197
+ ),
198
+ min_version=sessions.ansible_test_units.min_version,
199
+ max_version=sessions.ansible_test_units.max_version,
200
+ except_versions=_convert_except_versions(
201
+ sessions.ansible_test_units.except_versions
202
+ ),
203
+ )
204
+ if sessions.ansible_test_integration_w_default_container:
205
+ cfg = sessions.ansible_test_integration_w_default_container
206
+ add_ansible_test_integration_sessions_default_container(
207
+ default=cfg.default,
208
+ include_devel=cfg.include_devel,
209
+ include_milestone=(cfg.include_milestone),
210
+ add_devel_like_branches=_convert_devel_like_branches(
211
+ cfg.add_devel_like_branches
212
+ ),
213
+ min_version=cfg.min_version,
214
+ max_version=cfg.max_version,
215
+ except_versions=_convert_except_versions(cfg.except_versions),
216
+ core_python_versions=_convert_core_python_versions(
217
+ cfg.core_python_versions
218
+ ),
219
+ controller_python_versions_only=cfg.controller_python_versions_only,
220
+ )
221
+ if sessions.ansible_lint:
222
+ add_ansible_lint(
223
+ make_ansible_lint_default=sessions.ansible_lint.default,
224
+ ansible_lint_package=sessions.ansible_lint.ansible_lint_package,
225
+ strict=sessions.ansible_lint.strict,
226
+ )
227
+ add_matrix_generator()
228
+
229
+
230
+ def interpret_config(config: Config) -> None:
231
+ """
232
+ Interpret the config file's contents.
233
+ """
234
+ _interpret_config(config)
235
+ _add_sessions(config.sessions)
antsibull_nox/paths.py CHANGED
@@ -191,8 +191,27 @@ def create_temp_directory(basename: str) -> Path:
191
191
  return path
192
192
 
193
193
 
194
+ def copy_directory_tree_into(source: Path, destination: Path) -> None:
195
+ """
196
+ Copy the directory tree from ``source`` into the tree at ``destination``.
197
+
198
+ If ``destination`` does not yet exist, it will be created first.
199
+ """
200
+ if not source.is_dir():
201
+ return
202
+ destination.mkdir(parents=True, exist_ok=True)
203
+ for root, _, files in source.walk():
204
+ path = destination / root.relative_to(source)
205
+ path.mkdir(exist_ok=True)
206
+ for file in files:
207
+ dest = path / file
208
+ remove_path(dest)
209
+ shutil.copy2(root / file, dest, follow_symlinks=False)
210
+
211
+
194
212
  __all__ = [
195
213
  "copy_collection",
214
+ "copy_directory_tree_into",
196
215
  "create_temp_directory",
197
216
  "filter_paths",
198
217
  "find_data_directory",
@@ -0,0 +1,81 @@
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
+ Python version utilities.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import functools
14
+ import shutil
15
+ import subprocess
16
+ from pathlib import Path
17
+
18
+ from .utils import Version
19
+
20
+ # The following contains Python version candidates
21
+ _PYTHON_VERSIONS_TO_TRY: tuple[Version, ...] = tuple(
22
+ Version.parse(v)
23
+ for v in [
24
+ # Python 2:
25
+ "2.6",
26
+ "2.7",
27
+ # Python 3:
28
+ "3.5",
29
+ "3.6",
30
+ "3.7",
31
+ "3.8",
32
+ "3.9",
33
+ "3.10",
34
+ "3.11",
35
+ "3.12",
36
+ "3.13",
37
+ "3.14",
38
+ # "3.15",
39
+ # "3.16",
40
+ ]
41
+ )
42
+
43
+
44
+ @functools.cache
45
+ def get_installed_python_versions() -> dict[Version, Path]:
46
+ """
47
+ Return a map of supported Python versions for which interpreters exist and are on the path,
48
+ mapped to an executable.
49
+ """
50
+ result = {}
51
+
52
+ # Look for pythonX.Y binaries
53
+ for candidate in _PYTHON_VERSIONS_TO_TRY:
54
+ if exe := shutil.which(f"python{candidate}"):
55
+ result[candidate] = Path(exe)
56
+
57
+ # Look for python, python2, python3 binaries and determine their version
58
+ for executable in ("python", "python2", "python3"):
59
+ exe = shutil.which(executable)
60
+ if exe:
61
+ script = "import platform; print('.'.join(platform.python_version().split('.')[:2]))"
62
+ exe_result = subprocess.run(
63
+ [exe, "-c", script],
64
+ check=False,
65
+ text=True,
66
+ capture_output=True,
67
+ )
68
+ if exe_result.returncode == 0 and exe_result.stdout:
69
+ try:
70
+ version = Version.parse(exe_result.stdout.strip())
71
+ except (AttributeError, ValueError):
72
+ continue
73
+ else:
74
+ result.setdefault(version, Path(exe))
75
+
76
+ return result
77
+
78
+
79
+ __all__ = [
80
+ "get_installed_python_versions",
81
+ ]