ansible-core 2.19.4rc1__py3-none-any.whl → 2.20.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.

Potentially problematic release.


This version of ansible-core might be problematic. Click here for more details.

Files changed (215) hide show
  1. ansible/_internal/__init__.py +1 -4
  2. ansible/_internal/_ansiballz/_builder.py +1 -3
  3. ansible/_internal/_collection_proxy.py +7 -9
  4. ansible/_internal/_json/__init__.py +3 -4
  5. ansible/_internal/_templating/_engine.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  7. ansible/_internal/_wrapt.py +105 -301
  8. ansible/cli/__init__.py +11 -10
  9. ansible/cli/adhoc.py +1 -2
  10. ansible/cli/arguments/option_helpers.py +1 -1
  11. ansible/cli/config.py +5 -6
  12. ansible/cli/doc.py +67 -67
  13. ansible/cli/galaxy.py +15 -24
  14. ansible/cli/inventory.py +0 -1
  15. ansible/cli/playbook.py +0 -1
  16. ansible/cli/pull.py +0 -1
  17. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  18. ansible/config/base.yml +1 -25
  19. ansible/config/manager.py +0 -2
  20. ansible/executor/play_iterator.py +42 -20
  21. ansible/executor/playbook_executor.py +0 -9
  22. ansible/executor/task_executor.py +26 -18
  23. ansible/executor/task_queue_manager.py +1 -3
  24. ansible/galaxy/api.py +33 -80
  25. ansible/galaxy/collection/__init__.py +11 -21
  26. ansible/galaxy/dependency_resolution/__init__.py +10 -9
  27. ansible/galaxy/dependency_resolution/dataclasses.py +86 -70
  28. ansible/galaxy/dependency_resolution/providers.py +54 -134
  29. ansible/galaxy/dependency_resolution/versioning.py +2 -4
  30. ansible/galaxy/role.py +1 -33
  31. ansible/inventory/manager.py +2 -3
  32. ansible/keyword_desc.yml +0 -3
  33. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  34. ansible/module_utils/_internal/_no_six.py +86 -0
  35. ansible/module_utils/_text.py +28 -8
  36. ansible/module_utils/ansible_release.py +2 -2
  37. ansible/module_utils/basic.py +26 -23
  38. ansible/module_utils/common/_collections_compat.py +11 -2
  39. ansible/module_utils/common/collections.py +8 -3
  40. ansible/module_utils/common/dict_transformations.py +1 -2
  41. ansible/module_utils/common/network.py +4 -2
  42. ansible/module_utils/common/parameters.py +32 -41
  43. ansible/module_utils/common/text/converters.py +109 -23
  44. ansible/module_utils/common/text/formatters.py +6 -2
  45. ansible/module_utils/common/validation.py +11 -9
  46. ansible/module_utils/connection.py +8 -3
  47. ansible/module_utils/facts/hardware/linux.py +23 -7
  48. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  49. ansible/module_utils/facts/hardware/sunos.py +2 -1
  50. ansible/module_utils/facts/packages.py +6 -2
  51. ansible/module_utils/facts/system/distribution.py +2 -1
  52. ansible/module_utils/facts/system/env.py +6 -3
  53. ansible/module_utils/facts/system/local.py +3 -1
  54. ansible/module_utils/parsing/convert_bool.py +6 -2
  55. ansible/module_utils/service.py +2 -3
  56. ansible/module_utils/six/__init__.py +11 -6
  57. ansible/module_utils/yumdnf.py +0 -5
  58. ansible/modules/apt.py +18 -13
  59. ansible/modules/apt_repository.py +1 -1
  60. ansible/modules/assemble.py +5 -9
  61. ansible/modules/blockinfile.py +39 -23
  62. ansible/modules/cron.py +26 -35
  63. ansible/modules/deb822_repository.py +83 -12
  64. ansible/modules/dnf.py +3 -7
  65. ansible/modules/dnf5.py +4 -6
  66. ansible/modules/expect.py +0 -3
  67. ansible/modules/find.py +1 -2
  68. ansible/modules/get_url.py +1 -1
  69. ansible/modules/git.py +4 -5
  70. ansible/modules/include_vars.py +1 -1
  71. ansible/modules/known_hosts.py +7 -1
  72. ansible/modules/lineinfile.py +71 -63
  73. ansible/modules/package_facts.py +1 -1
  74. ansible/modules/pip.py +8 -2
  75. ansible/modules/replace.py +6 -6
  76. ansible/modules/service.py +3 -4
  77. ansible/modules/stat.py +20 -0
  78. ansible/modules/uri.py +9 -10
  79. ansible/modules/user.py +1 -2
  80. ansible/modules/wait_for.py +2 -2
  81. ansible/modules/wait_for_connection.py +2 -1
  82. ansible/modules/yum_repository.py +1 -16
  83. ansible/parsing/dataloader.py +24 -31
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +0 -60
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +0 -7
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +102 -36
  92. ansible/playbook/play_context.py +4 -0
  93. ansible/playbook/role/__init__.py +10 -65
  94. ansible/playbook/role/definition.py +3 -4
  95. ansible/playbook/role/include.py +2 -3
  96. ansible/playbook/role/metadata.py +1 -12
  97. ansible/playbook/role/requirement.py +1 -2
  98. ansible/playbook/role_include.py +1 -2
  99. ansible/playbook/taggable.py +16 -5
  100. ansible/playbook/task.py +11 -50
  101. ansible/plugins/action/__init__.py +20 -19
  102. ansible/plugins/action/add_host.py +1 -2
  103. ansible/plugins/action/fetch.py +3 -5
  104. ansible/plugins/action/group_by.py +1 -2
  105. ansible/plugins/action/include_vars.py +20 -22
  106. ansible/plugins/action/script.py +1 -3
  107. ansible/plugins/action/template.py +1 -2
  108. ansible/plugins/action/uri.py +4 -2
  109. ansible/plugins/cache/__init__.py +1 -0
  110. ansible/plugins/callback/__init__.py +13 -6
  111. ansible/plugins/connection/__init__.py +3 -7
  112. ansible/plugins/connection/local.py +2 -3
  113. ansible/plugins/connection/psrp.py +0 -2
  114. ansible/plugins/connection/ssh.py +2 -7
  115. ansible/plugins/connection/winrm.py +0 -2
  116. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  117. ansible/plugins/filter/core.py +4 -5
  118. ansible/plugins/filter/encryption.py +3 -27
  119. ansible/plugins/filter/mathstuff.py +1 -2
  120. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  121. ansible/plugins/filter/to_yaml.yml +29 -12
  122. ansible/plugins/inventory/__init__.py +1 -2
  123. ansible/plugins/inventory/toml.py +3 -6
  124. ansible/plugins/inventory/yaml.py +1 -2
  125. ansible/plugins/loader.py +3 -4
  126. ansible/plugins/lookup/password.py +1 -2
  127. ansible/plugins/lookup/subelements.py +2 -3
  128. ansible/plugins/lookup/url.py +1 -1
  129. ansible/plugins/lookup/varnames.py +1 -2
  130. ansible/plugins/shell/__init__.py +9 -4
  131. ansible/plugins/shell/powershell.py +8 -24
  132. ansible/plugins/strategy/__init__.py +5 -2
  133. ansible/plugins/test/core.py +4 -1
  134. ansible/plugins/test/falsy.yml +1 -1
  135. ansible/plugins/test/regex.yml +18 -6
  136. ansible/plugins/test/truthy.yml +1 -1
  137. ansible/release.py +2 -2
  138. ansible/template/__init__.py +3 -7
  139. ansible/utils/collection_loader/_collection_config.py +5 -0
  140. ansible/utils/collection_loader/_collection_finder.py +11 -14
  141. ansible/utils/context_objects.py +7 -4
  142. ansible/utils/display.py +7 -6
  143. ansible/utils/encrypt.py +0 -5
  144. ansible/utils/helpers.py +6 -2
  145. ansible/utils/jsonrpc.py +7 -3
  146. ansible/utils/plugin_docs.py +49 -38
  147. ansible/utils/ssh_functions.py +0 -19
  148. ansible/utils/unsafe_proxy.py +7 -7
  149. ansible/vars/clean.py +2 -3
  150. ansible/vars/manager.py +28 -22
  151. ansible/vars/plugins.py +1 -31
  152. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/METADATA +4 -4
  153. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/RECORD +213 -214
  154. ansible_test/_data/completion/docker.txt +7 -7
  155. ansible_test/_data/completion/network.txt +0 -1
  156. ansible_test/_data/completion/remote.txt +4 -4
  157. ansible_test/_data/requirements/ansible-test.txt +1 -1
  158. ansible_test/_data/requirements/ansible.txt +1 -1
  159. ansible_test/_data/requirements/sanity.ansible-doc.txt +2 -2
  160. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  161. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  162. ansible_test/_data/requirements/sanity.import.txt +1 -1
  163. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
  164. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  165. ansible_test/_data/requirements/sanity.pylint.txt +6 -6
  166. ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -1
  167. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  168. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  169. ansible_test/_internal/cache.py +2 -5
  170. ansible_test/_internal/cli/compat.py +1 -1
  171. ansible_test/_internal/commands/coverage/combine.py +1 -3
  172. ansible_test/_internal/commands/integration/__init__.py +3 -7
  173. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  174. ansible_test/_internal/commands/integration/coverage.py +1 -3
  175. ansible_test/_internal/commands/integration/filters.py +5 -10
  176. ansible_test/_internal/commands/sanity/pylint.py +11 -0
  177. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  178. ansible_test/_internal/commands/units/__init__.py +1 -13
  179. ansible_test/_internal/compat/packaging.py +2 -2
  180. ansible_test/_internal/compat/yaml.py +2 -2
  181. ansible_test/_internal/completion.py +2 -5
  182. ansible_test/_internal/config.py +2 -7
  183. ansible_test/_internal/coverage_util.py +1 -1
  184. ansible_test/_internal/delegation.py +2 -0
  185. ansible_test/_internal/docker_util.py +1 -1
  186. ansible_test/_internal/host_profiles.py +6 -11
  187. ansible_test/_internal/provider/__init__.py +2 -5
  188. ansible_test/_internal/provisioning.py +2 -5
  189. ansible_test/_internal/pypi_proxy.py +1 -1
  190. ansible_test/_internal/python_requirements.py +1 -1
  191. ansible_test/_internal/target.py +2 -6
  192. ansible_test/_internal/thread.py +1 -4
  193. ansible_test/_internal/util.py +9 -14
  194. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  195. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +48 -45
  196. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +9 -7
  197. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +51 -37
  198. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  199. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  200. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  201. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  202. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  203. ansible_test/_util/target/common/constants.py +2 -2
  204. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  205. ansible/utils/py3compat.py +0 -27
  206. ansible_test/_data/pytest/config/legacy.ini +0 -4
  207. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/WHEEL +0 -0
  208. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/entry_points.txt +0 -0
  209. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/COPYING +0 -0
  210. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  211. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  212. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  213. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  214. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  215. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
- base image=quay.io/ansible/base-test-container:8.2.0 python=3.13,3.8,3.9,3.10,3.11,3.12
2
- default image=quay.io/ansible/default-test-container:11.6.0 python=3.13,3.8,3.9,3.10,3.11,3.12 context=collection
3
- default image=quay.io/ansible/ansible-core-test-container:11.6.0 python=3.13,3.8,3.9,3.10,3.11,3.12 context=ansible-core
4
- alpine321 image=quay.io/ansible/alpine321-test-container:9.1.0 python=3.12 cgroup=none audit=none
5
- fedora41 image=quay.io/ansible/fedora41-test-container:9.0.0 python=3.13 cgroup=v2-only
6
- ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:9.0.0 python=3.10
7
- ubuntu2404 image=quay.io/ansible/ubuntu2404-test-container:9.0.0 python=3.12
1
+ base image=quay.io/ansible/base-test-container:v2.20-2 python=3.14,3.9,3.10,3.11,3.12,3.13
2
+ default image=quay.io/ansible/default-test-container:v2.20-4 python=3.14,3.9,3.10,3.11,3.12,3.13 context=collection
3
+ default image=quay.io/ansible/ansible-core-test-container:v2.20-4 python=3.14,3.9,3.10,3.11,3.12,3.13 context=ansible-core
4
+ alpine322 image=quay.io/ansible/alpine-test-container:3.22-v2.20-1 python=3.12 cgroup=none audit=none
5
+ fedora42 image=quay.io/ansible/fedora-test-container:42-v2.20-1 python=3.13 cgroup=v2-only
6
+ ubuntu2204 image=quay.io/ansible/ubuntu-test-container:22.04-v2.20-1 python=3.10
7
+ ubuntu2404 image=quay.io/ansible/ubuntu-test-container:24.04-v2.20-1 python=3.12
@@ -1 +0,0 @@
1
- ios/csr1000v collection=cisco.ios connection=ansible.netcommon.network_cli provider=aws arch=x86_64
@@ -1,13 +1,13 @@
1
- alpine/3.21 python=3.12 become=doas_sudo provider=aws arch=x86_64
1
+ alpine/3.22 python=3.12 become=doas_sudo provider=aws arch=x86_64
2
2
  alpine become=doas_sudo provider=aws arch=x86_64
3
- fedora/41 python=3.13 become=sudo provider=aws arch=x86_64
3
+ fedora/42 python=3.13 become=sudo provider=aws arch=x86_64
4
4
  fedora become=sudo provider=aws arch=x86_64
5
5
  freebsd/13.5 python=3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
6
- freebsd/14.2 python=3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
6
+ freebsd/14.3 python=3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
7
7
  freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
8
8
  macos/15.3 python=3.13 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
9
9
  macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
10
- rhel/9.5 python=3.9,3.12 become=sudo provider=aws arch=x86_64
10
+ rhel/9.6 python=3.9,3.12 become=sudo provider=aws arch=x86_64
11
11
  rhel/10.0 python=3.12 become=sudo provider=aws arch=x86_64
12
12
  rhel become=sudo provider=aws arch=x86_64
13
13
  ubuntu/22.04 python=3.10 become=sudo provider=aws arch=x86_64
@@ -1,2 +1,2 @@
1
1
  # The test-constraints sanity test verifies this file, but changes must be made manually to keep it in up-to-date.
2
- coverage == 7.6.1 ; python_version >= '3.8' and python_version <= '3.13'
2
+ coverage == 7.10.7 ; python_version >= '3.9' and python_version <= '3.14'
@@ -12,4 +12,4 @@ packaging
12
12
  # NOTE: Ref: https://github.com/sarugaku/resolvelib/issues/69
13
13
  # NOTE: When updating the upper bound, also update the latest version used
14
14
  # NOTE: in the ansible-galaxy-collection test suite.
15
- resolvelib >= 0.5.3, < 2.0.0 # dependency resolver used by ansible-galaxy
15
+ resolvelib >= 0.8.0, < 2.0.0 # dependency resolver used by ansible-galaxy
@@ -1,5 +1,5 @@
1
1
  # edit "sanity.ansible-doc.in" and generate with: hacking/update-sanity-requirements.py --test ansible-doc
2
2
  Jinja2==3.1.6
3
- MarkupSafe==3.0.2
3
+ MarkupSafe==3.0.3
4
4
  packaging==25.0
5
- PyYAML==6.0.2
5
+ PyYAML==6.0.3
@@ -2,8 +2,8 @@
2
2
  antsibull-changelog==0.29.0
3
3
  docutils==0.18.1
4
4
  packaging==25.0
5
- PyYAML==6.0.2
5
+ PyYAML==6.0.3
6
6
  rstcheck==5.0.0
7
7
  semantic-version==2.10.0
8
8
  types-docutils==0.18.3
9
- typing_extensions==4.13.2
9
+ typing_extensions==4.15.0
@@ -1,4 +1,4 @@
1
1
  # edit "sanity.import.plugin.in" and generate with: hacking/update-sanity-requirements.py --test import.plugin
2
2
  Jinja2==3.1.6
3
- MarkupSafe==3.0.2
4
- PyYAML==6.0.2
3
+ MarkupSafe==3.0.3
4
+ PyYAML==6.0.3
@@ -1,2 +1,2 @@
1
1
  # edit "sanity.import.in" and generate with: hacking/update-sanity-requirements.py --test import
2
- PyYAML==6.0.2
2
+ PyYAML==6.0.3
@@ -1,2 +1,2 @@
1
1
  # edit "sanity.integration-aliases.in" and generate with: hacking/update-sanity-requirements.py --test integration-aliases
2
- PyYAML==6.0.2
2
+ PyYAML==6.0.3
@@ -1,2 +1,2 @@
1
1
  # edit "sanity.pep8.in" and generate with: hacking/update-sanity-requirements.py --test pep8
2
- pycodestyle==2.13.0
2
+ pycodestyle==2.14.0
@@ -1,9 +1,9 @@
1
1
  # edit "sanity.pylint.in" and generate with: hacking/update-sanity-requirements.py --test pylint
2
- astroid==3.3.10
2
+ astroid==4.0.1
3
3
  dill==0.4.0
4
- isort==6.0.1
4
+ isort==7.0.0
5
5
  mccabe==0.7.0
6
- platformdirs==4.3.8
7
- pylint==3.3.7
8
- PyYAML==6.0.2
9
- tomlkit==0.13.2
6
+ platformdirs==4.5.0
7
+ pylint==4.0.2
8
+ PyYAML==6.0.3
9
+ tomlkit==0.13.3
@@ -1,3 +1,3 @@
1
1
  # edit "sanity.runtime-metadata.in" and generate with: hacking/update-sanity-requirements.py --test runtime-metadata
2
- PyYAML==6.0.2
2
+ PyYAML==6.0.3
3
3
  voluptuous==0.15.2
@@ -1,6 +1,6 @@
1
1
  # edit "sanity.validate-modules.in" and generate with: hacking/update-sanity-requirements.py --test validate-modules
2
2
  antsibull-docs-parser==1.0.0
3
3
  Jinja2==3.1.6
4
- MarkupSafe==3.0.2
5
- PyYAML==6.0.2
4
+ MarkupSafe==3.0.3
5
+ PyYAML==6.0.3
6
6
  voluptuous==0.15.2
@@ -1,4 +1,4 @@
1
1
  # edit "sanity.yamllint.in" and generate with: hacking/update-sanity-requirements.py --test yamllint
2
2
  pathspec==0.12.1
3
- PyYAML==6.0.2
3
+ PyYAML==6.0.3
4
4
  yamllint==1.37.1
@@ -3,14 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import collections.abc as c
6
- import typing as t
7
6
 
8
7
  from .config import (
9
8
  CommonConfig,
10
9
  )
11
10
 
12
- TValue = t.TypeVar('TValue')
13
-
14
11
 
15
12
  class CommonCache:
16
13
  """Common cache."""
@@ -18,14 +15,14 @@ class CommonCache:
18
15
  def __init__(self, args: CommonConfig) -> None:
19
16
  self.args = args
20
17
 
21
- def get(self, key: str, factory: c.Callable[[], TValue]) -> TValue:
18
+ def get[TValue](self, key: str, factory: c.Callable[[], TValue]) -> TValue:
22
19
  """Return the value from the cache identified by the given key, using the specified factory method if it is not found."""
23
20
  if key not in self.args.cache:
24
21
  self.args.cache[key] = factory()
25
22
 
26
23
  return self.args.cache[key]
27
24
 
28
- def get_with_args(self, key: str, factory: c.Callable[[CommonConfig], TValue]) -> TValue:
25
+ def get_with_args[TValue](self, key: str, factory: c.Callable[[CommonConfig], TValue]) -> TValue:
29
26
  """Return the value from the cache identified by the given key, using the specified factory method (which accepts args) if it is not found."""
30
27
  if key not in self.args.cache:
31
28
  self.args.cache[key] = factory(self.args)
@@ -69,7 +69,7 @@ def controller_python(version: t.Optional[str]) -> t.Optional[str]:
69
69
 
70
70
  def get_fallback_remote_controller() -> str:
71
71
  """Return the remote fallback platform for the controller."""
72
- platform = 'freebsd' # lower cost than RHEL and macOS
72
+ platform = 'fedora' # Fedora is lower cost than other remotes and always supports a recent Python version
73
73
  candidates = [item for item in filter_completion(remote_completion()).values() if item.controller_supported and item.platform == platform]
74
74
  fallback = sorted(candidates, key=lambda value: str_to_version(value.version), reverse=True)[0]
75
75
  return fallback.name
@@ -63,8 +63,6 @@ from . import (
63
63
  PathChecker,
64
64
  )
65
65
 
66
- TValue = t.TypeVar('TValue')
67
-
68
66
 
69
67
  def command_coverage_combine(args: CoverageCombineConfig) -> None:
70
68
  """Patch paths in coverage files and merge into a single file."""
@@ -287,7 +285,7 @@ def _get_coverage_targets(args: CoverageCombineConfig, walk_func: c.Callable) ->
287
285
  return sources
288
286
 
289
287
 
290
- def _build_stub_groups(
288
+ def _build_stub_groups[TValue](
291
289
  args: CoverageCombineConfig,
292
290
  sources: list[tuple[str, int]],
293
291
  default_stub_value: c.Callable[[list[str]], dict[str, TValue]],
@@ -41,7 +41,6 @@ from ...target import (
41
41
  walk_integration_targets,
42
42
  IntegrationTarget,
43
43
  walk_internal_targets,
44
- TIntegrationTarget,
45
44
  IntegrationTargetType,
46
45
  )
47
46
 
@@ -50,7 +49,6 @@ from ...config import (
50
49
  NetworkIntegrationConfig,
51
50
  PosixIntegrationConfig,
52
51
  WindowsIntegrationConfig,
53
- TIntegrationConfig,
54
52
  )
55
53
 
56
54
  from ...io import (
@@ -132,8 +130,6 @@ from .coverage import (
132
130
  CoverageManager,
133
131
  )
134
132
 
135
- THostProfile = t.TypeVar('THostProfile', bound=HostProfile)
136
-
137
133
 
138
134
  def generate_dependency_map(integration_targets: list[IntegrationTarget]) -> dict[str, set[IntegrationTarget]]:
139
135
  """Analyze the given list of integration test targets and return a dictionary expressing target names and the targets on which they depend."""
@@ -331,7 +327,7 @@ def integration_test_environment(
331
327
  display.info('Copying %s/ to %s/' % (dir_src, dir_dst), verbosity=2)
332
328
 
333
329
  if not args.explain:
334
- shutil.copytree(to_bytes(dir_src), to_bytes(dir_dst), symlinks=True) # type: ignore[arg-type] # incorrect type stub omits bytes path support
330
+ shutil.copytree(to_bytes(dir_src), to_bytes(dir_dst), symlinks=True) # type: ignore[type-var,arg-type] # type stub omits bytes path support
335
331
 
336
332
  for file_src, file_dst in file_copies:
337
333
  display.info('Copying %s to %s' % (file_src, file_dst), verbosity=2)
@@ -856,7 +852,7 @@ class IntegrationCache(CommonCache):
856
852
  return self.get('dependency_map', lambda: generate_dependency_map(self.integration_targets))
857
853
 
858
854
 
859
- def filter_profiles_for_target(args: IntegrationConfig, profiles: list[THostProfile], target: IntegrationTarget) -> list[THostProfile]:
855
+ def filter_profiles_for_target[T: HostProfile](args: IntegrationConfig, profiles: list[T], target: IntegrationTarget) -> list[T]:
860
856
  """Return a list of profiles after applying target filters."""
861
857
  if target.target_type == IntegrationTargetType.CONTROLLER:
862
858
  profile_filter = get_target_filter(args, [args.controller], True)
@@ -912,7 +908,7 @@ If necessary, context can be controlled by adding entries to the "aliases" file
912
908
  return exclude
913
909
 
914
910
 
915
- def command_integration_filter(
911
+ def command_integration_filter[TIntegrationTarget: IntegrationTarget, TIntegrationConfig: IntegrationConfig](
916
912
  args: TIntegrationConfig,
917
913
  targets: c.Iterable[TIntegrationTarget],
918
914
  ) -> tuple[HostState, tuple[TIntegrationTarget, ...]]:
@@ -32,7 +32,7 @@ class HttptesterProvider(CloudProvider):
32
32
  def __init__(self, args: IntegrationConfig) -> None:
33
33
  super().__init__(args)
34
34
 
35
- self.image = os.environ.get('ANSIBLE_HTTP_TEST_CONTAINER', 'quay.io/ansible/http-test-container:3.3.0')
35
+ self.image = os.environ.get('ANSIBLE_HTTP_TEST_CONTAINER', 'quay.io/ansible/http-test-container:3.5.0')
36
36
 
37
37
  self.uses_docker = True
38
38
 
@@ -79,10 +79,8 @@ from ...inventory import (
79
79
  create_posix_inventory,
80
80
  )
81
81
 
82
- THostConfig = t.TypeVar('THostConfig', bound=HostConfig)
83
82
 
84
-
85
- class CoverageHandler(t.Generic[THostConfig], metaclass=abc.ABCMeta):
83
+ class CoverageHandler[THostConfig: HostConfig](metaclass=abc.ABCMeta):
86
84
  """Base class for configuring hosts for integration test code coverage."""
87
85
 
88
86
  def __init__(self, args: IntegrationConfig, host_state: HostState, inventory_path: str) -> None:
@@ -40,13 +40,8 @@ from ...host_profiles import (
40
40
  HostProfile,
41
41
  )
42
42
 
43
- THostConfig = t.TypeVar('THostConfig', bound=HostConfig)
44
- TPosixConfig = t.TypeVar('TPosixConfig', bound=PosixConfig)
45
- TRemoteConfig = t.TypeVar('TRemoteConfig', bound=RemoteConfig)
46
- THostProfile = t.TypeVar('THostProfile', bound=HostProfile)
47
43
 
48
-
49
- class TargetFilter(t.Generic[THostConfig], metaclass=abc.ABCMeta):
44
+ class TargetFilter[THostConfig: HostConfig](metaclass=abc.ABCMeta):
50
45
  """Base class for target filters."""
51
46
 
52
47
  def __init__(self, args: IntegrationConfig, configs: list[THostConfig], controller: bool) -> None:
@@ -92,7 +87,7 @@ class TargetFilter(t.Generic[THostConfig], metaclass=abc.ABCMeta):
92
87
  exclude.update(skipped)
93
88
  display.warning(f'Excluding {self.host_type} tests marked {marked} {reason}: {", ".join(skipped)}')
94
89
 
95
- def filter_profiles(self, profiles: list[THostProfile], target: IntegrationTarget) -> list[THostProfile]:
90
+ def filter_profiles[THostProfile: HostProfile](self, profiles: list[THostProfile], target: IntegrationTarget) -> list[THostProfile]:
96
91
  """Filter the list of profiles, returning only those which are not skipped for the given target."""
97
92
  del target
98
93
  return profiles
@@ -138,7 +133,7 @@ class TargetFilter(t.Generic[THostConfig], metaclass=abc.ABCMeta):
138
133
  self.skip('unstable', 'which require --allow-unstable or prefixing with "unstable/"', targets, exclude, override)
139
134
 
140
135
 
141
- class PosixTargetFilter(TargetFilter[TPosixConfig]):
136
+ class PosixTargetFilter[TPosixConfig: PosixConfig](TargetFilter[TPosixConfig]):
142
137
  """Target filter for POSIX hosts."""
143
138
 
144
139
  def filter_targets(self, targets: list[IntegrationTarget], exclude: set[str]) -> None:
@@ -169,10 +164,10 @@ class PosixSshTargetFilter(PosixTargetFilter[PosixSshConfig]):
169
164
  """Target filter for POSIX SSH hosts."""
170
165
 
171
166
 
172
- class RemoteTargetFilter(TargetFilter[TRemoteConfig]):
167
+ class RemoteTargetFilter[TRemoteConfig: RemoteConfig](TargetFilter[TRemoteConfig]):
173
168
  """Target filter for remote Ansible Core CI managed hosts."""
174
169
 
175
- def filter_profiles(self, profiles: list[THostProfile], target: IntegrationTarget) -> list[THostProfile]:
170
+ def filter_profiles[THostProfile: HostProfile](self, profiles: list[THostProfile], target: IntegrationTarget) -> list[THostProfile]:
176
171
  """Filter the list of profiles, returning only those which are not skipped for the given target."""
177
172
  profiles = super().filter_profiles(profiles, target)
178
173
 
@@ -301,4 +301,15 @@ class PylintTest(SanitySingleVersion):
301
301
  else:
302
302
  messages = []
303
303
 
304
+ expected_paths = set(paths)
305
+
306
+ unexpected_messages = [message for message in messages if message["path"] not in expected_paths]
307
+ messages = [message for message in messages if message["path"] in expected_paths]
308
+
309
+ for unexpected_message in unexpected_messages:
310
+ display.info(f"Unexpected message: {json.dumps(unexpected_message)}", verbosity=4)
311
+
312
+ if unexpected_messages:
313
+ display.notice(f"Discarded {len(unexpected_messages)} unexpected messages. Use -vvvv to display.")
314
+
304
315
  return messages
@@ -160,11 +160,7 @@ class ValidateModulesTest(SanitySingleVersion):
160
160
  temp_dir = process_scoped_temporary_directory(args)
161
161
 
162
162
  with tarfile.open(path) as file:
163
- # deprecated: description='extractall fallback without filter' python_version='3.11'
164
- if hasattr(tarfile, 'data_filter'):
165
- file.extractall(temp_dir, filter='data') # type: ignore[call-arg]
166
- else:
167
- file.extractall(temp_dir)
163
+ file.extractall(temp_dir, filter='data')
168
164
 
169
165
  cmd.extend([
170
166
  '--original-plugins', temp_dir,
@@ -241,19 +241,7 @@ def command_units(args: UnitsConfig) -> None:
241
241
  sys.exit()
242
242
 
243
243
  for test_context, python, paths, env in test_sets:
244
- # When using pytest-mock, make sure that features introduced in Python 3.8 are available to older Python versions.
245
- # This is done by enabling the mock_use_standalone_module feature, which forces use of mock even when unittest.mock is available.
246
- # Later Python versions have not introduced additional unittest.mock features, so use of mock is not needed as of Python 3.8.
247
- # If future Python versions introduce new unittest.mock features, they will not be available to older Python versions.
248
- # Having the cutoff at Python 3.8 also eases packaging of ansible-core since no supported controller version requires the use of mock.
249
- #
250
- # NOTE: This only affects use of pytest-mock.
251
- # Collection unit tests may directly import mock, which will be provided by ansible-test when it installs requirements using pip.
252
- # Although mock is available for ansible-core unit tests, they should import unittest.mock instead.
253
- if str_to_version(python.version) < (3, 8):
254
- config_name = 'legacy.ini'
255
- else:
256
- config_name = 'default.ini'
244
+ config_name = 'default.ini'
257
245
 
258
246
  cmd = [
259
247
  'pytest',
@@ -12,8 +12,8 @@ try:
12
12
 
13
13
  SpecifierSet: t.Optional[t.Type[specifiers.SpecifierSet]] = specifiers.SpecifierSet
14
14
  Version: t.Optional[t.Type[version.Version]] = version.Version
15
- PACKAGING_IMPORT_ERROR = None
15
+ PACKAGING_IMPORT_ERROR = None # pylint: disable=invalid-name
16
16
  except ImportError as ex:
17
17
  SpecifierSet = None # pylint: disable=invalid-name
18
18
  Version = None # pylint: disable=invalid-name
19
- PACKAGING_IMPORT_ERROR = ex
19
+ PACKAGING_IMPORT_ERROR = ex # pylint: disable=invalid-name
@@ -11,10 +11,10 @@ from functools import (
11
11
  try:
12
12
  import yaml as _yaml
13
13
 
14
- YAML_IMPORT_ERROR = None
14
+ YAML_IMPORT_ERROR = None # pylint: disable=invalid-name
15
15
  except ImportError as ex:
16
16
  yaml_load = None # pylint: disable=invalid-name
17
- YAML_IMPORT_ERROR = ex
17
+ YAML_IMPORT_ERROR = ex # pylint: disable=invalid-name
18
18
  else:
19
19
  try:
20
20
  _SafeLoader: t.Union[t.Type[_yaml.CSafeLoader], t.Type[_yaml.SafeLoader]] = _yaml.CSafeLoader
@@ -250,10 +250,7 @@ class WindowsRemoteCompletionConfig(RemoteCompletionConfig):
250
250
  connection: str = ''
251
251
 
252
252
 
253
- TCompletionConfig = t.TypeVar('TCompletionConfig', bound=CompletionConfig)
254
-
255
-
256
- def load_completion(name: str, completion_type: t.Type[TCompletionConfig]) -> dict[str, TCompletionConfig]:
253
+ def load_completion[TCompletionConfig: CompletionConfig](name: str, completion_type: t.Type[TCompletionConfig]) -> dict[str, TCompletionConfig]:
257
254
  """Load the named completion entries, returning them in dictionary form using the specified completion type."""
258
255
  lines = read_lines_without_comments(os.path.join(ANSIBLE_TEST_DATA_ROOT, 'completion', '%s.txt' % name), remove_blank_lines=True)
259
256
 
@@ -283,7 +280,7 @@ def parse_completion_entry(value: str) -> tuple[str, dict[str, str]]:
283
280
  return name, data
284
281
 
285
282
 
286
- def filter_completion(
283
+ def filter_completion[TCompletionConfig: CompletionConfig](
287
284
  completion: dict[str, TCompletionConfig],
288
285
  controller_only: bool = False,
289
286
  include_defaults: bool = False,
@@ -38,8 +38,6 @@ from .host_configs import (
38
38
  VirtualPythonConfig,
39
39
  )
40
40
 
41
- THostConfig = t.TypeVar('THostConfig', bound=HostConfig)
42
-
43
41
 
44
42
  class TerminateMode(enum.Enum):
45
43
  """When to terminate instances."""
@@ -166,7 +164,7 @@ class EnvironmentConfig(CommonConfig):
166
164
  """Host configuration for the targets."""
167
165
  return self.host_settings.targets
168
166
 
169
- def only_target(self, target_type: t.Type[THostConfig]) -> THostConfig:
167
+ def only_target[THostConfig: HostConfig](self, target_type: t.Type[THostConfig]) -> THostConfig:
170
168
  """
171
169
  Return the host configuration for the target.
172
170
  Requires that there is exactly one target of the specified type.
@@ -183,7 +181,7 @@ class EnvironmentConfig(CommonConfig):
183
181
 
184
182
  return target
185
183
 
186
- def only_targets(self, target_type: t.Type[THostConfig]) -> list[THostConfig]:
184
+ def only_targets[THostConfig: HostConfig](self, target_type: t.Type[THostConfig]) -> list[THostConfig]:
187
185
  """
188
186
  Return a list of target host configurations.
189
187
  Requires that there are one or more targets, all the specified type.
@@ -318,9 +316,6 @@ class IntegrationConfig(TestConfig):
318
316
  return ansible_config_path
319
317
 
320
318
 
321
- TIntegrationConfig = t.TypeVar('TIntegrationConfig', bound=IntegrationConfig)
322
-
323
-
324
319
  class PosixIntegrationConfig(IntegrationConfig):
325
320
  """Configuration for the posix integration command."""
326
321
 
@@ -69,7 +69,7 @@ class CoverageVersion:
69
69
 
70
70
  COVERAGE_VERSIONS = (
71
71
  # IMPORTANT: Keep this in sync with the ansible-test.txt requirements file.
72
- CoverageVersion('7.6.1', 7, (3, 8), (3, 13)),
72
+ CoverageVersion('7.10.7', 7, (3, 9), (3, 14)),
73
73
  )
74
74
  """
75
75
  This tuple specifies the coverage version to use for Python version ranges.
@@ -124,6 +124,8 @@ def delegate(args: CommonConfig, host_state: HostState, exclude: list[str], requ
124
124
  @contextlib.contextmanager
125
125
  def metadata_context(args: EnvironmentConfig) -> t.Generator[None]:
126
126
  """A context manager which exports delegation metadata."""
127
+ os.makedirs(ResultType.TMP.path, exist_ok=True)
128
+
127
129
  with tempfile.NamedTemporaryFile(prefix='metadata-', suffix='.json', dir=ResultType.TMP.path) as metadata_fd:
128
130
  args.metadata_path = os.path.join(ResultType.TMP.relative_path, os.path.basename(metadata_fd.name))
129
131
  args.metadata.to_file(args.metadata_path)
@@ -50,7 +50,7 @@ DOCKER_COMMANDS = [
50
50
  'podman',
51
51
  ]
52
52
 
53
- UTILITY_IMAGE = 'quay.io/ansible/ansible-test-utility-container:3.2.0'
53
+ UTILITY_IMAGE = 'quay.io/ansible/ansible-test-utility-container:3.4.0'
54
54
 
55
55
  # Max number of open files in a docker container.
56
56
  # Passed with --ulimit option to the docker run command.
@@ -144,11 +144,6 @@ from .debugging import (
144
144
  DebuggerSettings,
145
145
  )
146
146
 
147
- TControllerHostConfig = t.TypeVar('TControllerHostConfig', bound=ControllerHostConfig)
148
- THostConfig = t.TypeVar('THostConfig', bound=HostConfig)
149
- TPosixConfig = t.TypeVar('TPosixConfig', bound=PosixConfig)
150
- TRemoteConfig = t.TypeVar('TRemoteConfig', bound=RemoteConfig)
151
-
152
147
 
153
148
  class ControlGroupError(ApplicationError):
154
149
  """Raised when the container host does not have the necessary cgroup support to run a container."""
@@ -239,7 +234,7 @@ class Inventory:
239
234
  display.info(f'>>> Inventory\n{inventory_text}', verbosity=3)
240
235
 
241
236
 
242
- class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
237
+ class HostProfile[THostConfig: HostConfig](metaclass=abc.ABCMeta):
243
238
  """Base class for host profiles."""
244
239
 
245
240
  def __init__(
@@ -299,7 +294,7 @@ class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
299
294
  return f'{self.__class__.__name__}: {self.name}'
300
295
 
301
296
 
302
- class DebuggableProfile(HostProfile[THostConfig], DebuggerProfile, metaclass=abc.ABCMeta):
297
+ class DebuggableProfile[THostConfig: HostConfig](HostProfile[THostConfig], DebuggerProfile, metaclass=abc.ABCMeta):
303
298
  """Base class for profiles remote debugging."""
304
299
 
305
300
  __DEBUGGING_PORT_KEY = 'debugging_port'
@@ -465,7 +460,7 @@ class DebuggableProfile(HostProfile[THostConfig], DebuggerProfile, metaclass=abc
465
460
  )
466
461
 
467
462
 
468
- class PosixProfile(HostProfile[TPosixConfig], metaclass=abc.ABCMeta):
463
+ class PosixProfile[TPosixConfig: PosixConfig](HostProfile[TPosixConfig], metaclass=abc.ABCMeta):
469
464
  """Base class for POSIX host profiles."""
470
465
 
471
466
  @property
@@ -487,7 +482,7 @@ class PosixProfile(HostProfile[TPosixConfig], metaclass=abc.ABCMeta):
487
482
  return python
488
483
 
489
484
 
490
- class ControllerHostProfile(PosixProfile[TControllerHostConfig], DebuggableProfile[TControllerHostConfig], metaclass=abc.ABCMeta):
485
+ class ControllerHostProfile[T: ControllerHostConfig](PosixProfile[T], DebuggableProfile[T], metaclass=abc.ABCMeta):
491
486
  """Base class for profiles usable as a controller."""
492
487
 
493
488
  @abc.abstractmethod
@@ -499,7 +494,7 @@ class ControllerHostProfile(PosixProfile[TControllerHostConfig], DebuggableProfi
499
494
  """Return the working directory for the host."""
500
495
 
501
496
 
502
- class SshTargetHostProfile(HostProfile[THostConfig], metaclass=abc.ABCMeta):
497
+ class SshTargetHostProfile[THostConfig: HostConfig](HostProfile[THostConfig], metaclass=abc.ABCMeta):
503
498
  """Base class for profiles offering SSH connectivity."""
504
499
 
505
500
  @abc.abstractmethod
@@ -507,7 +502,7 @@ class SshTargetHostProfile(HostProfile[THostConfig], metaclass=abc.ABCMeta):
507
502
  """Return SSH connection(s) for accessing the host as a target from the controller."""
508
503
 
509
504
 
510
- class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
505
+ class RemoteProfile[TRemoteConfig: RemoteConfig](SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
511
506
  """Base class for remote instance profiles."""
512
507
 
513
508
  @property
@@ -12,12 +12,12 @@ from ..util import (
12
12
  )
13
13
 
14
14
 
15
- def get_path_provider_classes(provider_type: t.Type[TPathProvider]) -> list[t.Type[TPathProvider]]:
15
+ def get_path_provider_classes[TPathProvider: PathProvider](provider_type: t.Type[TPathProvider]) -> list[t.Type[TPathProvider]]:
16
16
  """Return a list of path provider classes of the given type."""
17
17
  return sorted(get_subclasses(provider_type), key=lambda subclass: (subclass.priority, subclass.__name__))
18
18
 
19
19
 
20
- def find_path_provider(
20
+ def find_path_provider[TPathProvider: PathProvider](
21
21
  provider_type: t.Type[TPathProvider],
22
22
  provider_classes: list[t.Type[TPathProvider]],
23
23
  path: str,
@@ -71,6 +71,3 @@ class PathProvider(metaclass=abc.ABCMeta):
71
71
  @abc.abstractmethod
72
72
  def is_content_root(path: str) -> bool:
73
73
  """Return True if the given path is a content root for this provider."""
74
-
75
-
76
- TPathProvider = t.TypeVar('TPathProvider', bound=PathProvider)
@@ -48,9 +48,6 @@ from .pypi_proxy import (
48
48
  run_pypi_proxy,
49
49
  )
50
50
 
51
- THostProfile = t.TypeVar('THostProfile', bound=HostProfile)
52
- TEnvironmentConfig = t.TypeVar('TEnvironmentConfig', bound=EnvironmentConfig)
53
-
54
51
 
55
52
  class PrimeContainers(ApplicationError):
56
53
  """Exception raised to end execution early after priming containers."""
@@ -91,7 +88,7 @@ class HostState:
91
88
  return list(itertools.chain.from_iterable([target.get_controller_target_connections() for
92
89
  target in self.target_profiles if isinstance(target, SshTargetHostProfile)]))
93
90
 
94
- def targets(self, profile_type: t.Type[THostProfile]) -> list[THostProfile]:
91
+ def targets[THostProfile: HostProfile](self, profile_type: t.Type[THostProfile]) -> list[THostProfile]:
95
92
  """The list of target(s), verified to be of the specified type."""
96
93
  if not self.target_profiles:
97
94
  raise Exception('No target profiles found.')
@@ -101,7 +98,7 @@ class HostState:
101
98
  return t.cast(list[THostProfile], self.target_profiles)
102
99
 
103
100
 
104
- def prepare_profiles(
101
+ def prepare_profiles[TEnvironmentConfig: EnvironmentConfig](
105
102
  args: TEnvironmentConfig,
106
103
  targets_use_pypi: bool = False,
107
104
  skip_setup: bool = False,
@@ -70,7 +70,7 @@ def run_pypi_proxy(args: EnvironmentConfig, targets_use_pypi: bool) -> None:
70
70
  display.warning('Unable to use the PyPI proxy because Docker is not available. Installation of packages using `pip` may fail.')
71
71
  return
72
72
 
73
- image = 'quay.io/ansible/pypi-test-container:3.3.0'
73
+ image = 'quay.io/ansible/pypi-test-container:3.5.0'
74
74
  port = 3141
75
75
 
76
76
  run_support_container(
@@ -433,7 +433,7 @@ def get_venv_packages(python: PythonConfig) -> dict[str, str]:
433
433
  # See: https://github.com/ansible/base-test-container/blob/main/files/installer.py
434
434
 
435
435
  default_packages = dict(
436
- pip='24.2',
436
+ pip='25.2',
437
437
  )
438
438
 
439
439
  override_packages: dict[str, dict[str, str]] = {