ansible-core 2.19.2__py3-none-any.whl → 2.20.0b1__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.
- ansible/_internal/__init__.py +1 -4
- ansible/_internal/_ansiballz/_builder.py +1 -3
- ansible/_internal/_collection_proxy.py +7 -9
- ansible/_internal/_display_utils.py +145 -0
- ansible/_internal/_json/__init__.py +3 -4
- ansible/_internal/_templating/_engine.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +1 -2
- ansible/_internal/_wrapt.py +105 -301
- ansible/cli/__init__.py +11 -10
- ansible/cli/adhoc.py +1 -2
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/config.py +5 -6
- ansible/cli/doc.py +67 -67
- ansible/cli/galaxy.py +15 -24
- ansible/cli/inventory.py +0 -1
- ansible/cli/playbook.py +0 -1
- ansible/cli/pull.py +0 -1
- ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
- ansible/config/base.yml +1 -25
- ansible/config/manager.py +0 -2
- ansible/executor/play_iterator.py +42 -20
- ansible/executor/playbook_executor.py +0 -9
- ansible/executor/powershell/async_watchdog.ps1 +24 -4
- ansible/executor/task_executor.py +32 -22
- ansible/executor/task_queue_manager.py +1 -3
- ansible/galaxy/api.py +33 -80
- ansible/galaxy/collection/__init__.py +4 -17
- ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
- ansible/galaxy/dependency_resolution/providers.py +1 -2
- ansible/galaxy/role.py +1 -33
- ansible/inventory/manager.py +2 -3
- ansible/keyword_desc.yml +0 -3
- ansible/module_utils/_internal/_datatag/__init__.py +2 -10
- ansible/module_utils/_internal/_no_six.py +86 -0
- ansible/module_utils/_text.py +28 -8
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/basic.py +27 -24
- ansible/module_utils/common/_collections_compat.py +11 -2
- ansible/module_utils/common/collections.py +8 -3
- ansible/module_utils/common/dict_transformations.py +1 -2
- ansible/module_utils/common/network.py +4 -2
- ansible/module_utils/common/parameters.py +32 -41
- ansible/module_utils/common/text/converters.py +109 -23
- ansible/module_utils/common/text/formatters.py +6 -2
- ansible/module_utils/common/validation.py +11 -9
- ansible/module_utils/connection.py +8 -3
- ansible/module_utils/facts/hardware/linux.py +23 -7
- ansible/module_utils/facts/hardware/netbsd.py +1 -1
- ansible/module_utils/facts/hardware/sunos.py +2 -1
- ansible/module_utils/facts/packages.py +6 -2
- ansible/module_utils/facts/system/distribution.py +2 -1
- ansible/module_utils/facts/system/env.py +6 -3
- ansible/module_utils/facts/system/local.py +3 -1
- ansible/module_utils/parsing/convert_bool.py +6 -2
- ansible/module_utils/service.py +2 -3
- ansible/module_utils/six/__init__.py +11 -6
- ansible/module_utils/urls.py +6 -2
- ansible/module_utils/yumdnf.py +0 -5
- ansible/modules/apt.py +18 -13
- ansible/modules/apt_repository.py +1 -1
- ansible/modules/assemble.py +5 -9
- ansible/modules/blockinfile.py +39 -23
- ansible/modules/cron.py +26 -35
- ansible/modules/deb822_repository.py +83 -12
- ansible/modules/dnf.py +3 -7
- ansible/modules/dnf5.py +4 -6
- ansible/modules/expect.py +0 -3
- ansible/modules/find.py +1 -2
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +4 -5
- ansible/modules/include_vars.py +1 -1
- ansible/modules/lineinfile.py +71 -63
- ansible/modules/package_facts.py +1 -1
- ansible/modules/pip.py +8 -2
- ansible/modules/replace.py +6 -6
- ansible/modules/service.py +3 -4
- ansible/modules/stat.py +20 -0
- ansible/modules/uri.py +9 -10
- ansible/modules/user.py +1 -2
- ansible/modules/wait_for.py +2 -2
- ansible/modules/wait_for_connection.py +2 -1
- ansible/modules/yum_repository.py +1 -16
- ansible/parsing/dataloader.py +24 -31
- ansible/parsing/mod_args.py +3 -0
- ansible/parsing/vault/__init__.py +1 -2
- ansible/playbook/base.py +8 -56
- ansible/playbook/block.py +0 -60
- ansible/playbook/collectionsearch.py +1 -2
- ansible/playbook/handler.py +1 -7
- ansible/playbook/helpers.py +0 -7
- ansible/playbook/included_file.py +1 -1
- ansible/playbook/play.py +103 -37
- ansible/playbook/play_context.py +4 -0
- ansible/playbook/role/__init__.py +10 -65
- ansible/playbook/role/definition.py +3 -4
- ansible/playbook/role/include.py +2 -3
- ansible/playbook/role/metadata.py +1 -12
- ansible/playbook/role/requirement.py +1 -2
- ansible/playbook/role_include.py +1 -2
- ansible/playbook/taggable.py +16 -5
- ansible/playbook/task.py +51 -55
- ansible/plugins/action/__init__.py +20 -19
- ansible/plugins/action/add_host.py +1 -2
- ansible/plugins/action/fetch.py +2 -4
- ansible/plugins/action/group_by.py +1 -2
- ansible/plugins/action/include_vars.py +20 -22
- ansible/plugins/action/script.py +1 -3
- ansible/plugins/action/template.py +1 -2
- ansible/plugins/action/uri.py +4 -2
- ansible/plugins/cache/__init__.py +1 -0
- ansible/plugins/callback/__init__.py +13 -6
- ansible/plugins/connection/__init__.py +3 -7
- ansible/plugins/connection/local.py +2 -3
- ansible/plugins/connection/psrp.py +0 -2
- ansible/plugins/connection/ssh.py +2 -7
- ansible/plugins/connection/winrm.py +0 -2
- ansible/plugins/doc_fragments/result_format_callback.py +15 -0
- ansible/plugins/filter/core.py +4 -5
- ansible/plugins/filter/encryption.py +3 -27
- ansible/plugins/filter/mathstuff.py +1 -2
- ansible/plugins/filter/to_nice_yaml.yml +31 -3
- ansible/plugins/filter/to_yaml.yml +29 -12
- ansible/plugins/inventory/__init__.py +1 -2
- ansible/plugins/inventory/script.py +2 -1
- ansible/plugins/inventory/toml.py +3 -6
- ansible/plugins/inventory/yaml.py +1 -2
- ansible/plugins/list.py +10 -3
- ansible/plugins/loader.py +6 -6
- ansible/plugins/lookup/password.py +1 -2
- ansible/plugins/lookup/subelements.py +2 -3
- ansible/plugins/lookup/url.py +1 -1
- ansible/plugins/lookup/varnames.py +1 -2
- ansible/plugins/shell/__init__.py +9 -4
- ansible/plugins/shell/powershell.py +8 -24
- ansible/plugins/strategy/__init__.py +6 -3
- ansible/plugins/test/core.py +4 -1
- ansible/plugins/test/regex.yml +18 -6
- ansible/release.py +2 -2
- ansible/template/__init__.py +3 -7
- ansible/utils/collection_loader/_collection_config.py +5 -0
- ansible/utils/collection_loader/_collection_finder.py +11 -14
- ansible/utils/context_objects.py +7 -4
- ansible/utils/display.py +28 -167
- ansible/utils/encrypt.py +0 -5
- ansible/utils/helpers.py +6 -2
- ansible/utils/jsonrpc.py +7 -3
- ansible/utils/plugin_docs.py +49 -38
- ansible/utils/ssh_functions.py +0 -19
- ansible/utils/unsafe_proxy.py +7 -7
- ansible/vars/clean.py +2 -3
- ansible/vars/manager.py +27 -20
- ansible/vars/plugins.py +1 -31
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/METADATA +3 -3
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/RECORD +200 -200
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/network.txt +0 -1
- ansible_test/_data/completion/remote.txt +4 -4
- ansible_test/_data/requirements/ansible-test.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +1 -1
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_internal/cache.py +2 -5
- ansible_test/_internal/cli/compat.py +1 -1
- ansible_test/_internal/commands/coverage/combine.py +1 -3
- ansible_test/_internal/commands/integration/__init__.py +3 -7
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +1 -3
- ansible_test/_internal/commands/integration/filters.py +5 -10
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
- ansible_test/_internal/commands/units/__init__.py +1 -13
- ansible_test/_internal/completion.py +2 -5
- ansible_test/_internal/config.py +2 -7
- ansible_test/_internal/coverage_util.py +1 -1
- ansible_test/_internal/delegation.py +2 -0
- ansible_test/_internal/docker_util.py +1 -1
- ansible_test/_internal/host_profiles.py +6 -11
- ansible_test/_internal/provider/__init__.py +2 -5
- ansible_test/_internal/provisioning.py +2 -5
- ansible_test/_internal/pypi_proxy.py +1 -1
- ansible_test/_internal/target.py +2 -6
- ansible_test/_internal/thread.py +1 -4
- ansible_test/_internal/util.py +9 -14
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +30 -27
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
- ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
- ansible_test/_util/target/common/constants.py +2 -2
- ansible_test/_util/target/setup/bootstrap.sh +0 -6
- ansible/utils/py3compat.py +0 -27
- ansible_test/_data/pytest/config/legacy.ini +0 -4
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
base image=quay.io/ansible/base-test-container:
|
|
2
|
-
default image=quay.io/ansible/default-test-container:
|
|
3
|
-
default image=quay.io/ansible/ansible-core-test-container:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ubuntu2204 image=quay.io/ansible/
|
|
7
|
-
ubuntu2404 image=quay.io/ansible/
|
|
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.
|
|
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/
|
|
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.
|
|
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.
|
|
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
|
|
2
|
+
coverage == 7.10.6 ; python_version >= '3.9' and python_version <= '3.14'
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# edit "sanity.pep8.in" and generate with: hacking/update-sanity-requirements.py --test pep8
|
|
2
|
-
pycodestyle==2.
|
|
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.
|
|
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.
|
|
7
|
-
pylint==3.3.
|
|
6
|
+
platformdirs==4.4.0
|
|
7
|
+
pylint==3.3.8
|
|
8
8
|
PyYAML==6.0.2
|
|
9
|
-
tomlkit==0.13.
|
|
9
|
+
tomlkit==0.13.3
|
ansible_test/_internal/cache.py
CHANGED
|
@@ -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 = '
|
|
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] #
|
|
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[
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
ansible_test/_internal/config.py
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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[
|
|
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.
|
|
73
|
+
image = 'quay.io/ansible/pypi-test-container:3.5.0'
|
|
74
74
|
port = 3141
|
|
75
75
|
|
|
76
76
|
run_support_container(
|
ansible_test/_internal/target.py
CHANGED
|
@@ -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)
|
ansible_test/_internal/thread.py
CHANGED
|
@@ -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
|
|
ansible_test/_internal/util.py
CHANGED
|
@@ -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[...,
|
|
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.
|