antsibull-nox 0.2.0__py3-none-any.whl → 0.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.
Files changed (34) hide show
  1. antsibull_nox/__init__.py +7 -51
  2. antsibull_nox/_pydantic.py +98 -0
  3. antsibull_nox/ansible.py +15 -0
  4. antsibull_nox/cli.py +132 -0
  5. antsibull_nox/collection/__init__.py +2 -2
  6. antsibull_nox/collection/data.py +12 -0
  7. antsibull_nox/collection/install.py +194 -79
  8. antsibull_nox/collection/search.py +136 -34
  9. antsibull_nox/config.py +51 -2
  10. antsibull_nox/data/action-groups.py +2 -2
  11. antsibull_nox/data/antsibull-nox-lint-config.py +29 -0
  12. antsibull_nox/data/file-yamllint.py +138 -0
  13. antsibull_nox/data/license-check.py +5 -1
  14. antsibull_nox/data/plugin-yamllint.py +54 -24
  15. antsibull_nox/init.py +83 -0
  16. antsibull_nox/interpret_config.py +29 -8
  17. antsibull_nox/lint_config.py +113 -0
  18. antsibull_nox/sessions/__init__.py +70 -0
  19. antsibull_nox/sessions/ansible_lint.py +60 -0
  20. antsibull_nox/sessions/ansible_test.py +559 -0
  21. antsibull_nox/sessions/build_import_check.py +147 -0
  22. antsibull_nox/sessions/collections.py +145 -0
  23. antsibull_nox/sessions/docs_check.py +78 -0
  24. antsibull_nox/sessions/extra_checks.py +127 -0
  25. antsibull_nox/sessions/license_check.py +73 -0
  26. antsibull_nox/sessions/lint.py +694 -0
  27. antsibull_nox/sessions/utils.py +206 -0
  28. {antsibull_nox-0.2.0.dist-info → antsibull_nox-0.4.0.dist-info}/METADATA +2 -2
  29. antsibull_nox-0.4.0.dist-info/RECORD +41 -0
  30. antsibull_nox-0.4.0.dist-info/entry_points.txt +2 -0
  31. antsibull_nox/sessions.py +0 -1712
  32. antsibull_nox-0.2.0.dist-info/RECORD +0 -25
  33. {antsibull_nox-0.2.0.dist-info → antsibull_nox-0.4.0.dist-info}/WHEEL +0 -0
  34. {antsibull_nox-0.2.0.dist-info → antsibull_nox-0.4.0.dist-info}/licenses/LICENSES/GPL-3.0-or-later.txt +0 -0
antsibull_nox/__init__.py CHANGED
@@ -10,71 +10,27 @@ Antsibull Nox Helper.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- from .collection import CollectionSource, setup_collection_sources
14
- from .config import load_config_from_toml
15
- from .interpret_config import interpret_config
16
- from .sessions import (
17
- ActionGroup,
18
- add_all_ansible_test_sanity_test_sessions,
19
- add_all_ansible_test_unit_test_sessions,
20
- add_ansible_lint,
21
- add_ansible_test_integration_sessions_default_container,
22
- add_ansible_test_sanity_test_session,
23
- add_ansible_test_session,
24
- add_ansible_test_unit_test_session,
25
- add_build_import_check,
26
- add_docs_check,
27
- add_extra_checks,
28
- add_license_check,
29
- add_lint_sessions,
30
- add_matrix_generator,
13
+ from .config import (
14
+ CONFIG_FILENAME,
15
+ load_config_from_toml,
31
16
  )
17
+ from .interpret_config import interpret_config
18
+ from .sessions.ansible_test import add_ansible_test_session
32
19
 
33
- __version__ = "0.2.0"
34
-
35
-
36
- def setup(
37
- *,
38
- collection_sources: dict[str, str | CollectionSource] | None = None,
39
- ) -> None:
40
- """
41
- Set-up antsibull-nox.
42
- """
43
- if collection_sources:
44
- setup_collection_sources(
45
- {
46
- name: CollectionSource.parse(name, source)
47
- for name, source in collection_sources.items()
48
- }
49
- )
20
+ __version__ = "0.4.0"
50
21
 
51
22
 
52
23
  def load_antsibull_nox_toml() -> None:
53
24
  """
54
25
  Load and interpret antsibull-nox.toml config file.
55
26
  """
56
- config = load_config_from_toml("antsibull-nox.toml")
27
+ config = load_config_from_toml(CONFIG_FILENAME)
57
28
  interpret_config(config)
58
29
 
59
30
 
60
31
  # pylint:disable=duplicate-code
61
32
  __all__ = (
62
33
  "__version__",
63
- "ActionGroup",
64
- "add_build_import_check",
65
- "add_docs_check",
66
- "add_extra_checks",
67
- "add_license_check",
68
- "add_lint_sessions",
69
34
  "add_ansible_test_session",
70
- "add_ansible_test_sanity_test_session",
71
- "add_all_ansible_test_sanity_test_sessions",
72
- "add_ansible_test_unit_test_session",
73
- "add_all_ansible_test_unit_test_sessions",
74
- "add_ansible_test_integration_sessions_default_container",
75
- "add_ansible_lint",
76
- "add_matrix_generator",
77
- "CollectionSource",
78
- "setup",
79
35
  "load_antsibull_nox_toml",
80
36
  )
@@ -0,0 +1,98 @@
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: Ansible Project, 2024
6
+
7
+ # ========================================================================
8
+ # **Vendored** from antsibull-core.
9
+ #
10
+ # I vendored this to avoid depending on antsibull-core only for this.
11
+ # TBH, most of this should be part of pydantic anyway. See
12
+ # https://github.com/pydantic/pydantic/discussions/2652 for a discussion
13
+ # on this.
14
+ #
15
+ # pylint: disable=missing-function-docstring
16
+ # ========================================================================
17
+
18
+ """
19
+ Helpers for pydantic.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import typing as t
25
+ from collections.abc import Callable, Collection
26
+
27
+ import pydantic as p
28
+
29
+ if t.TYPE_CHECKING:
30
+ from typing_extensions import TypeGuard
31
+
32
+
33
+ def _is_basemodel(a_type: t.Any) -> TypeGuard[type[p.BaseModel]]:
34
+ try:
35
+ return issubclass(a_type, p.BaseModel)
36
+ except TypeError:
37
+ # If inspect.isclass(a_type) is checked first, no TypeError happens for
38
+ # Python 3.11+.
39
+
40
+ # On Python 3.9 and 3.10, issubclass(dict[int, int], p.BaseModel) raises
41
+ # "TypeError: issubclass() arg 1 must be a class".
42
+ # (https://github.com/pydantic/pydantic/discussions/5970)
43
+ return False
44
+
45
+
46
+ def _modify_config(
47
+ cls: type[p.BaseModel],
48
+ processed_classes: set[type[p.BaseModel]],
49
+ change_config: Callable[[p.ConfigDict], bool],
50
+ ) -> bool:
51
+ if cls in processed_classes:
52
+ return False
53
+ change = False
54
+ for field_info in cls.model_fields.values():
55
+ if _is_basemodel(field_info.annotation):
56
+ change |= _modify_config(
57
+ field_info.annotation,
58
+ processed_classes,
59
+ change_config,
60
+ )
61
+ for subcls in t.get_args(field_info.annotation):
62
+ if _is_basemodel(subcls):
63
+ change |= _modify_config(subcls, processed_classes, change_config)
64
+ change |= change_config(cls.model_config)
65
+ if change:
66
+ cls.model_rebuild(force=True)
67
+ processed_classes.add(cls)
68
+ return change
69
+
70
+
71
+ def set_extras(
72
+ models: type[p.BaseModel] | Collection[type[p.BaseModel]],
73
+ value: t.Literal["allow", "ignore", "forbid"],
74
+ ) -> None:
75
+ def change_config(model_config: p.ConfigDict) -> bool:
76
+ if model_config.get("extra") == value:
77
+ return False
78
+ model_config["extra"] = value
79
+ return True
80
+
81
+ processed_classes: set[type[p.BaseModel]] = set()
82
+ if isinstance(models, Collection):
83
+ for cls in models:
84
+ _modify_config(cls, processed_classes, change_config)
85
+ else:
86
+ _modify_config(models, processed_classes, change_config)
87
+
88
+
89
+ def forbid_extras(models: type[p.BaseModel] | Collection[type[p.BaseModel]]) -> None:
90
+ set_extras(models, "forbid")
91
+
92
+
93
+ def get_formatted_error_messages(error: p.ValidationError) -> list[str]:
94
+ def format_error(err) -> str:
95
+ location = " -> ".join(str(loc) for loc in err["loc"])
96
+ return f'{location}: {err["msg"]}'
97
+
98
+ return [format_error(err) for err in error.errors()]
antsibull_nox/ansible.py CHANGED
@@ -253,8 +253,23 @@ def get_supported_core_versions(
253
253
  return result
254
254
 
255
255
 
256
+ def parse_ansible_core_version(
257
+ version: str | AnsibleCoreVersion,
258
+ ) -> AnsibleCoreVersion:
259
+ """
260
+ Coerce a string or a AnsibleCoreVersion to a AnsibleCoreVersion.
261
+ """
262
+ if version in ("devel", "milestone"):
263
+ # For some reason mypy doesn't notice that
264
+ return t.cast(AnsibleCoreVersion, version)
265
+ if isinstance(version, Version):
266
+ return version
267
+ return Version.parse(version)
268
+
269
+
256
270
  __all__ = [
257
271
  "AnsibleCoreInfo",
258
272
  "get_ansible_core_info",
259
273
  "get_ansible_core_package_name",
274
+ "parse_ansible_core_version",
260
275
  ]
antsibull_nox/cli.py ADDED
@@ -0,0 +1,132 @@
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
+ # PYTHON_ARGCOMPLETE_OK
8
+
9
+ """Entrypoint to the antsibull-docs script."""
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import os
15
+ import os.path
16
+ import sys
17
+ from collections.abc import Callable
18
+
19
+ from . import __version__
20
+ from .init import create_initial_config as _create_initial_config
21
+ from .lint_config import lint_config as _lint_config
22
+
23
+ try:
24
+ import argcomplete
25
+
26
+ HAS_ARGCOMPLETE = True
27
+ except ImportError:
28
+ HAS_ARGCOMPLETE = False
29
+
30
+
31
+ def lint_config(_: argparse.Namespace) -> int:
32
+ """
33
+ Lint antsibull-nox config file.
34
+ """
35
+ errors = _lint_config()
36
+ for error in errors:
37
+ print(error)
38
+ return 0 if len(errors) == 0 else 3
39
+
40
+
41
+ def create_initial_config(_: argparse.Namespace) -> int:
42
+ """
43
+ Create noxfile.py and antsibull-nox.toml.
44
+ """
45
+ try:
46
+ _create_initial_config()
47
+ except Exception as exc: # pylint: disable=broad-exception-caught
48
+ print(f"Error: {exc}", file=sys.stderr)
49
+ return 3
50
+ return 0
51
+
52
+
53
+ # Mapping from command line subcommand names to functions which implement those.
54
+ # The functions need to take a single argument, the processed list of args.
55
+ ARGS_MAP: dict[str, Callable[[argparse.Namespace], int]] = {
56
+ "lint-config": lint_config,
57
+ "init": create_initial_config,
58
+ }
59
+
60
+
61
+ class InvalidArgumentError(Exception):
62
+ """
63
+ Error while parsing arguments.
64
+ """
65
+
66
+
67
+ def parse_args(program_name: str, args: list[str]) -> argparse.Namespace:
68
+ """
69
+ Parse the command line arguments.
70
+ """
71
+
72
+ toplevel_parser = argparse.ArgumentParser(
73
+ prog=program_name,
74
+ description="Script to manage generated documentation for ansible",
75
+ )
76
+ toplevel_parser.add_argument(
77
+ "--version",
78
+ action="version",
79
+ version=__version__,
80
+ help="Print the antsibull-nox version",
81
+ )
82
+ subparsers = toplevel_parser.add_subparsers(
83
+ title="Subcommands", dest="command", help="for help use: `SUBCOMMANDS -h`"
84
+ )
85
+ subparsers.required = True
86
+
87
+ subparsers.add_parser(
88
+ "lint-config",
89
+ description="Lint antsibull-nox configuration file",
90
+ )
91
+
92
+ subparsers.add_parser(
93
+ "init",
94
+ description="Create noxfile and antsibull-nox configuration file",
95
+ )
96
+
97
+ # This must come after all parser setup
98
+ if HAS_ARGCOMPLETE:
99
+ argcomplete.autocomplete(toplevel_parser)
100
+
101
+ parsed_args: argparse.Namespace = toplevel_parser.parse_args(args)
102
+ return parsed_args
103
+
104
+
105
+ def run(args: list[str]) -> int:
106
+ """
107
+ Run the program.
108
+ """
109
+ program_name = os.path.basename(args[0])
110
+ try:
111
+ parsed_args: argparse.Namespace = parse_args(program_name, args[1:])
112
+ except InvalidArgumentError as e:
113
+ print(e, file=sys.stderr)
114
+ return 2
115
+
116
+ return ARGS_MAP[parsed_args.command](parsed_args)
117
+
118
+
119
+ def main() -> int:
120
+ """
121
+ Entrypoint called from the script.
122
+
123
+ Return codes:
124
+ :0: Success
125
+ :1: Unhandled error. See the Traceback for more information.
126
+ :2: There was a problem with the command line arguments
127
+ """
128
+ return run(sys.argv)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ sys.exit(main())
@@ -21,7 +21,7 @@ from .install import (
21
21
  setup_collections,
22
22
  setup_current_tree,
23
23
  )
24
- from .search import CollectionList, load_collection_data_from_disk
24
+ from .search import GALAXY_YML, CollectionList, load_collection_data_from_disk
25
25
 
26
26
 
27
27
  def force_collection_version(path: Path, *, version: str) -> bool:
@@ -31,7 +31,7 @@ def force_collection_version(path: Path, *, version: str) -> bool:
31
31
  Returns ``True`` if the version was changed, and ``False`` if the version
32
32
  was already set to this value.
33
33
  """
34
- galaxy_yml = path / "galaxy.yml"
34
+ galaxy_yml = path / GALAXY_YML
35
35
  try:
36
36
  data = load_yaml_file(galaxy_yml)
37
37
  except Exception as exc:
@@ -10,6 +10,8 @@ Data types for collections.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import base64
14
+ import hashlib
13
15
  from dataclasses import dataclass
14
16
  from pathlib import Path
15
17
 
@@ -98,6 +100,16 @@ class CollectionSource:
98
100
  )
99
101
  return source
100
102
 
103
+ def identifier(self) -> str:
104
+ """
105
+ Compute a source identifier.
106
+ """
107
+ hasher = hashlib.sha256()
108
+ hasher.update(self.name.encode("utf-8"))
109
+ hasher.update(b"::")
110
+ hasher.update(self.source.encode("utf-8"))
111
+ return base64.b32encode(hasher.digest())[:16].decode("ascii")
112
+
101
113
 
102
114
  __all__ = [
103
115
  "CollectionData",