ansible-core 2.19.3rc1__py3-none-any.whl → 2.20.0b2__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 (201) 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 +70 -68
  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/collections/list.py +4 -2
  19. ansible/config/base.yml +1 -25
  20. ansible/config/manager.py +0 -2
  21. ansible/executor/play_iterator.py +42 -20
  22. ansible/executor/playbook_executor.py +0 -9
  23. ansible/executor/task_executor.py +26 -18
  24. ansible/executor/task_queue_manager.py +1 -3
  25. ansible/galaxy/api.py +33 -80
  26. ansible/galaxy/collection/__init__.py +4 -17
  27. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  28. ansible/galaxy/dependency_resolution/providers.py +24 -118
  29. ansible/galaxy/role.py +1 -33
  30. ansible/inventory/manager.py +2 -3
  31. ansible/keyword_desc.yml +0 -3
  32. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  33. ansible/module_utils/_internal/_no_six.py +86 -0
  34. ansible/module_utils/_text.py +28 -8
  35. ansible/module_utils/ansible_release.py +2 -2
  36. ansible/module_utils/basic.py +26 -23
  37. ansible/module_utils/common/_collections_compat.py +11 -2
  38. ansible/module_utils/common/collections.py +8 -3
  39. ansible/module_utils/common/dict_transformations.py +1 -2
  40. ansible/module_utils/common/network.py +4 -2
  41. ansible/module_utils/common/parameters.py +32 -41
  42. ansible/module_utils/common/text/converters.py +109 -23
  43. ansible/module_utils/common/text/formatters.py +6 -2
  44. ansible/module_utils/common/validation.py +11 -9
  45. ansible/module_utils/connection.py +8 -3
  46. ansible/module_utils/facts/hardware/linux.py +23 -7
  47. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  48. ansible/module_utils/facts/hardware/sunos.py +2 -1
  49. ansible/module_utils/facts/packages.py +6 -2
  50. ansible/module_utils/facts/system/distribution.py +2 -1
  51. ansible/module_utils/facts/system/env.py +6 -3
  52. ansible/module_utils/facts/system/local.py +3 -1
  53. ansible/module_utils/parsing/convert_bool.py +6 -2
  54. ansible/module_utils/service.py +2 -3
  55. ansible/module_utils/six/__init__.py +19 -6
  56. ansible/module_utils/yumdnf.py +0 -5
  57. ansible/modules/apt.py +18 -13
  58. ansible/modules/apt_repository.py +1 -1
  59. ansible/modules/assemble.py +5 -9
  60. ansible/modules/blockinfile.py +39 -23
  61. ansible/modules/cron.py +26 -35
  62. ansible/modules/deb822_repository.py +83 -12
  63. ansible/modules/dnf.py +3 -7
  64. ansible/modules/dnf5.py +4 -6
  65. ansible/modules/expect.py +0 -3
  66. ansible/modules/find.py +1 -2
  67. ansible/modules/get_url.py +1 -1
  68. ansible/modules/git.py +4 -5
  69. ansible/modules/include_vars.py +1 -1
  70. ansible/modules/known_hosts.py +7 -1
  71. ansible/modules/lineinfile.py +71 -63
  72. ansible/modules/package_facts.py +1 -1
  73. ansible/modules/pip.py +8 -2
  74. ansible/modules/replace.py +6 -6
  75. ansible/modules/service.py +3 -4
  76. ansible/modules/stat.py +20 -0
  77. ansible/modules/uri.py +9 -10
  78. ansible/modules/user.py +1 -2
  79. ansible/modules/wait_for.py +2 -2
  80. ansible/modules/wait_for_connection.py +2 -1
  81. ansible/modules/yum_repository.py +1 -16
  82. ansible/parsing/dataloader.py +24 -31
  83. ansible/parsing/mod_args.py +3 -0
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +1 -63
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +15 -20
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +105 -49
  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 +51 -55
  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 +6 -3
  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.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/METADATA +3 -3
  153. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/RECORD +199 -200
  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/sanity.changelog.txt +1 -1
  159. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  161. ansible_test/_internal/cache.py +2 -5
  162. ansible_test/_internal/cli/compat.py +1 -1
  163. ansible_test/_internal/commands/coverage/combine.py +1 -3
  164. ansible_test/_internal/commands/integration/__init__.py +3 -7
  165. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  166. ansible_test/_internal/commands/integration/coverage.py +1 -3
  167. ansible_test/_internal/commands/integration/filters.py +5 -10
  168. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  169. ansible_test/_internal/commands/units/__init__.py +1 -13
  170. ansible_test/_internal/completion.py +2 -5
  171. ansible_test/_internal/config.py +2 -7
  172. ansible_test/_internal/coverage_util.py +1 -1
  173. ansible_test/_internal/delegation.py +2 -0
  174. ansible_test/_internal/docker_util.py +1 -1
  175. ansible_test/_internal/host_profiles.py +6 -11
  176. ansible_test/_internal/provider/__init__.py +2 -5
  177. ansible_test/_internal/provisioning.py +2 -5
  178. ansible_test/_internal/pypi_proxy.py +1 -1
  179. ansible_test/_internal/target.py +2 -6
  180. ansible_test/_internal/thread.py +1 -4
  181. ansible_test/_internal/util.py +9 -14
  182. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  183. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +40 -27
  184. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  188. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  189. ansible_test/_util/target/common/constants.py +2 -2
  190. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  191. ansible/utils/py3compat.py +0 -27
  192. ansible_test/_data/pytest/config/legacy.ini +0 -4
  193. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/WHEEL +0 -0
  194. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/entry_points.txt +0 -0
  195. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/COPYING +0 -0
  196. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  197. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  198. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  199. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  200. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  201. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.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-0 python=3.13,3.9,3.10,3.11,3.12,3.14
2
+ default image=quay.io/ansible/default-test-container:v2.20-0 python=3.13,3.9,3.10,3.11,3.12,3.14 context=collection
3
+ default image=quay.io/ansible/ansible-core-test-container:v2.20-0 python=3.13,3.9,3.10,3.11,3.12,3.14 context=ansible-core
4
+ alpine322 image=quay.io/ansible/alpine-test-container:3.22-v2.20-0 python=3.12 cgroup=none audit=none
5
+ fedora42 image=quay.io/ansible/fedora-test-container:42-v2.20-0 python=3.13 cgroup=v2-only
6
+ ubuntu2204 image=quay.io/ansible/ubuntu-test-container:22.04-v2.20-0 python=3.10
7
+ ubuntu2404 image=quay.io/ansible/ubuntu-test-container:24.04-v2.20-0 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.6 ; python_version >= '3.9' and python_version <= '3.14'
@@ -6,4 +6,4 @@ PyYAML==6.0.2
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,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==3.3.11
3
3
  dill==0.4.0
4
4
  isort==6.0.1
5
5
  mccabe==0.7.0
6
- platformdirs==4.3.8
7
- pylint==3.3.7
6
+ platformdirs==4.4.0
7
+ pylint==3.3.8
8
8
  PyYAML==6.0.2
9
- tomlkit==0.13.2
9
+ tomlkit==0.13.3
@@ -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
 
@@ -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',
@@ -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.6', 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(
@@ -65,7 +65,7 @@ def walk_completion_targets(targets: c.Iterable[CompletionTarget], prefix: str,
65
65
  return tuple(sorted(matches))
66
66
 
67
67
 
68
- def walk_internal_targets(
68
+ def walk_internal_targets[TCompletionTarget: CompletionTarget](
69
69
  targets: c.Iterable[TCompletionTarget],
70
70
  includes: t.Optional[list[str]] = None,
71
71
  excludes: t.Optional[list[str]] = None,
@@ -87,7 +87,7 @@ def walk_internal_targets(
87
87
  return tuple(sorted(internal_targets, key=lambda sort_target: sort_target.name))
88
88
 
89
89
 
90
- def filter_targets(
90
+ def filter_targets[TCompletionTarget: CompletionTarget](
91
91
  targets: c.Iterable[TCompletionTarget],
92
92
  patterns: list[str],
93
93
  include: bool = True,
@@ -711,7 +711,3 @@ class TargetPatternsNotMatched(ApplicationError):
711
711
  message = 'Target pattern not matched: %s' % self.patterns[0]
712
712
 
713
713
  super().__init__(message)
714
-
715
-
716
- TCompletionTarget = t.TypeVar('TCompletionTarget', bound=CompletionTarget)
717
- TIntegrationTarget = t.TypeVar('TIntegrationTarget', bound=IntegrationTarget)
@@ -11,9 +11,6 @@ import queue
11
11
  import typing as t
12
12
 
13
13
 
14
- TCallable = t.TypeVar('TCallable', bound=t.Callable[..., t.Any])
15
-
16
-
17
14
  class WrappedThread(threading.Thread):
18
15
  """Wrapper around Thread which captures results and exceptions."""
19
16
 
@@ -50,7 +47,7 @@ class WrappedThread(threading.Thread):
50
47
  return result
51
48
 
52
49
 
53
- def mutex(func: TCallable) -> TCallable:
50
+ def mutex[TCallable: t.Callable[..., t.Any]](func: TCallable) -> TCallable:
54
51
  """Enforce exclusive access on a decorated function."""
55
52
  lock = threading.Lock()
56
53
 
@@ -57,11 +57,6 @@ from .constants import (
57
57
  SUPPORTED_PYTHON_VERSIONS,
58
58
  )
59
59
 
60
- C = t.TypeVar('C')
61
- TBase = t.TypeVar('TBase')
62
- TKey = t.TypeVar('TKey')
63
- TValue = t.TypeVar('TValue')
64
-
65
60
  PYTHON_PATHS: dict[str, str] = {}
66
61
 
67
62
  COVERAGE_CONFIG_NAME = 'coveragerc'
@@ -180,7 +175,7 @@ def is_valid_identifier(value: str) -> bool:
180
175
  return value.isidentifier() and not keyword.iskeyword(value)
181
176
 
182
177
 
183
- def cache(func: c.Callable[[], TValue]) -> c.Callable[[], TValue]:
178
+ def cache[TValue](func: c.Callable[[], TValue]) -> c.Callable[[], TValue]:
184
179
  """Enforce exclusive access on a decorated function and cache the result."""
185
180
  storage: dict[None, TValue] = {}
186
181
  sentinel = object()
@@ -313,7 +308,7 @@ def read_lines_without_comments(path: str, remove_blank_lines: bool = False, opt
313
308
  return lines
314
309
 
315
310
 
316
- def exclude_none_values(data: dict[TKey, t.Optional[TValue]]) -> dict[TKey, TValue]:
311
+ def exclude_none_values[TKey, TValue](data: dict[TKey, t.Optional[TValue]]) -> dict[TKey, TValue]:
317
312
  """Return the provided dictionary with any None values excluded."""
318
313
  return dict((key, value) for key, value in data.items() if value is not None)
319
314
 
@@ -1059,7 +1054,7 @@ def format_command_output(stdout: str | None, stderr: str | None) -> str:
1059
1054
  return message
1060
1055
 
1061
1056
 
1062
- def retry(func: t.Callable[..., TValue], ex_type: t.Type[BaseException] = SubprocessError, sleep: int = 10, attempts: int = 10, warn: bool = True) -> TValue:
1057
+ def retry[T](func: t.Callable[..., T], ex_type: t.Type[BaseException] = SubprocessError, sleep: int = 10, attempts: int = 10, warn: bool = True) -> T:
1063
1058
  """Retry the specified function on failure."""
1064
1059
  for dummy in range(1, attempts):
1065
1060
  try:
@@ -1092,7 +1087,7 @@ def parse_to_list_of_dict(pattern: str, value: str) -> list[dict[str, str]]:
1092
1087
  return matched
1093
1088
 
1094
1089
 
1095
- def get_subclasses(class_type: t.Type[C]) -> list[t.Type[C]]:
1090
+ def get_subclasses[C](class_type: t.Type[C]) -> list[t.Type[C]]:
1096
1091
  """Returns a list of types that are concrete subclasses of the given type."""
1097
1092
  subclasses: set[t.Type[C]] = set()
1098
1093
  queue: list[t.Type[C]] = [class_type]
@@ -1168,7 +1163,7 @@ def import_plugins(directory: str, root: t.Optional[str] = None) -> None:
1168
1163
  load_module(module_path, name)
1169
1164
 
1170
1165
 
1171
- def load_plugins(base_type: t.Type[C], database: dict[str, t.Type[C]]) -> None:
1166
+ def load_plugins[C](base_type: t.Type[C], database: dict[str, t.Type[C]]) -> None:
1172
1167
  """
1173
1168
  Load plugins of the specified type and track them in the specified database.
1174
1169
  Only plugins which have already been imported will be loaded.
@@ -1195,19 +1190,19 @@ def sanitize_host_name(name: str) -> str:
1195
1190
  return re.sub('[^A-Za-z0-9]+', '-', name)[:63].strip('-')
1196
1191
 
1197
1192
 
1198
- def get_generic_type(base_type: t.Type, generic_base_type: t.Type[TValue]) -> t.Optional[t.Type[TValue]]:
1193
+ def get_generic_type[TValue](base_type: t.Type, generic_base_type: t.Type[TValue]) -> t.Optional[t.Type[TValue]]:
1199
1194
  """Return the generic type arg derived from the generic_base_type type that is associated with the base_type type, if any, otherwise return None."""
1200
1195
  # noinspection PyUnresolvedReferences
1201
1196
  type_arg = t.get_args(base_type.__orig_bases__[0])[0]
1202
1197
  return None if isinstance(type_arg, generic_base_type) else type_arg
1203
1198
 
1204
1199
 
1205
- def get_type_associations(base_type: t.Type[TBase], generic_base_type: t.Type[TValue]) -> list[tuple[t.Type[TValue], t.Type[TBase]]]:
1200
+ def get_type_associations[TBase, TValue](base_type: t.Type[TBase], generic_base_type: t.Type[TValue]) -> list[tuple[t.Type[TValue], t.Type[TBase]]]:
1206
1201
  """Create and return a list of tuples associating generic_base_type derived types with a corresponding base_type derived type."""
1207
1202
  return [item for item in [(get_generic_type(sc_type, generic_base_type), sc_type) for sc_type in get_subclasses(base_type)] if item[1]]
1208
1203
 
1209
1204
 
1210
- def get_type_map(base_type: t.Type[TBase], generic_base_type: t.Type[TValue]) -> dict[t.Type[TValue], t.Type[TBase]]:
1205
+ def get_type_map[TBase, TValue](base_type: t.Type[TBase], generic_base_type: t.Type[TValue]) -> dict[t.Type[TValue], t.Type[TBase]]:
1211
1206
  """Create and return a mapping of generic_base_type derived types to base_type derived types."""
1212
1207
  return {item[0]: item[1] for item in get_type_associations(base_type, generic_base_type)}
1213
1208
 
@@ -1228,7 +1223,7 @@ def verify_sys_executable(path: str) -> t.Optional[str]:
1228
1223
  return expected_executable
1229
1224
 
1230
1225
 
1231
- def type_guard(sequence: c.Sequence[t.Any], guard_type: t.Type[C]) -> t.TypeGuard[c.Sequence[C]]:
1226
+ def type_guard[C](sequence: c.Sequence[t.Any], guard_type: t.Type[C]) -> t.TypeGuard[c.Sequence[C]]:
1232
1227
  """
1233
1228
  Raises an exception if any item in the given sequence does not match the specified guard type.
1234
1229
  Use with assert so that type checkers are aware of the type guard.