ansible-core 2.19.2rc1__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.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/METADATA +3 -3
- {ansible_core-2.19.2rc1.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.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/top_level.txt +0 -0
ansible/_internal/__init__.py
CHANGED
|
@@ -30,10 +30,7 @@ def import_controller_module(module_name: str, /) -> t.Any:
|
|
|
30
30
|
return importlib.import_module(module_name)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def experimental(obj: _T) -> _T:
|
|
33
|
+
def experimental[T](obj: T) -> T:
|
|
37
34
|
"""
|
|
38
35
|
Decorator for experimental types and methods outside the `_internal` package which accept or expose internal types.
|
|
39
36
|
As with internal APIs, these are subject to change at any time without notice.
|
|
@@ -9,8 +9,6 @@ from ansible.module_utils._internal._ansiballz import _extensions
|
|
|
9
9
|
from ansible.module_utils._internal._ansiballz._extensions import _debugpy, _pydevd, _coverage
|
|
10
10
|
from ansible.constants import config
|
|
11
11
|
|
|
12
|
-
_T = t.TypeVar('_T')
|
|
13
|
-
|
|
14
12
|
|
|
15
13
|
class ExtensionManager:
|
|
16
14
|
"""AnsiballZ extension manager."""
|
|
@@ -101,7 +99,7 @@ class ExtensionManager:
|
|
|
101
99
|
)
|
|
102
100
|
|
|
103
101
|
@classmethod
|
|
104
|
-
def _get_options(cls, name: str, config_type: type[
|
|
102
|
+
def _get_options[T](cls, name: str, config_type: type[T], task_vars: dict[str, object]) -> T | None:
|
|
105
103
|
"""Parse configuration from the named environment variable as the specified type, or None if not configured."""
|
|
106
104
|
if (value := config.get_config_value(name, variables=task_vars)) is None:
|
|
107
105
|
return None
|
|
@@ -3,26 +3,24 @@ from __future__ import annotations as _annotations
|
|
|
3
3
|
import collections.abc as _c
|
|
4
4
|
import typing as _t
|
|
5
5
|
|
|
6
|
-
_T_co = _t.TypeVar('_T_co', covariant=True)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
class SequenceProxy(_c.Sequence[_T_co]):
|
|
7
|
+
class SequenceProxy[T](_c.Sequence[T]):
|
|
10
8
|
"""A read-only sequence proxy."""
|
|
11
9
|
|
|
12
10
|
# DTFIX5: needs unit test coverage
|
|
13
11
|
|
|
14
12
|
__slots__ = ('__value',)
|
|
15
13
|
|
|
16
|
-
def __init__(self, value: _c.Sequence[
|
|
14
|
+
def __init__(self, value: _c.Sequence[T]) -> None:
|
|
17
15
|
self.__value = value
|
|
18
16
|
|
|
19
17
|
@_t.overload
|
|
20
|
-
def __getitem__(self, index: int) ->
|
|
18
|
+
def __getitem__(self, index: int) -> T: ...
|
|
21
19
|
|
|
22
20
|
@_t.overload
|
|
23
|
-
def __getitem__(self, index: slice) -> _c.Sequence[
|
|
21
|
+
def __getitem__(self, index: slice) -> _c.Sequence[T]: ...
|
|
24
22
|
|
|
25
|
-
def __getitem__(self, index: int | slice) ->
|
|
23
|
+
def __getitem__(self, index: int | slice) -> T | _c.Sequence[T]:
|
|
26
24
|
if isinstance(index, slice):
|
|
27
25
|
return self.__class__(self.__value[index])
|
|
28
26
|
|
|
@@ -34,10 +32,10 @@ class SequenceProxy(_c.Sequence[_T_co]):
|
|
|
34
32
|
def __contains__(self, item: object) -> bool:
|
|
35
33
|
return item in self.__value
|
|
36
34
|
|
|
37
|
-
def __iter__(self) -> _t.Iterator[
|
|
35
|
+
def __iter__(self) -> _t.Iterator[T]:
|
|
38
36
|
yield from self.__value
|
|
39
37
|
|
|
40
|
-
def __reversed__(self) -> _c.Iterator[
|
|
38
|
+
def __reversed__(self) -> _c.Iterator[T]:
|
|
41
39
|
return reversed(self.__value)
|
|
42
40
|
|
|
43
41
|
def index(self, *args) -> int:
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
|
|
5
|
+
from ansible.module_utils._internal import _ambient_context, _messages
|
|
6
|
+
from . import _event_formatting
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DeferredWarningContext(_ambient_context.AmbientContextBase):
|
|
10
|
+
"""
|
|
11
|
+
Calls to `Display.warning()` and `Display.deprecated()` within this context will cause the resulting warnings to be captured and not displayed.
|
|
12
|
+
The intended use is for task-initiated warnings to be recorded with the task result, which makes them visible to registered results, callbacks, etc.
|
|
13
|
+
The active display callback is responsible for communicating any warnings to the user.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# DTFIX-FUTURE: once we start implementing nested scoped contexts for our own bookkeeping, this should be an interface facade that forwards to the nearest
|
|
17
|
+
# context that actually implements the warnings collection capability
|
|
18
|
+
|
|
19
|
+
def __init__(self, *, variables: dict[str, object]) -> None:
|
|
20
|
+
self._variables = variables # DTFIX-FUTURE: move this to an AmbientContext-derived TaskContext (once it exists)
|
|
21
|
+
self._deprecation_warnings: list[_messages.DeprecationSummary] = []
|
|
22
|
+
self._warnings: list[_messages.WarningSummary] = []
|
|
23
|
+
self._seen: set[_messages.WarningSummary] = set()
|
|
24
|
+
|
|
25
|
+
def capture(self, warning: _messages.WarningSummary) -> None:
|
|
26
|
+
"""Add the warning/deprecation to the context if it has not already been seen by this context."""
|
|
27
|
+
if warning in self._seen:
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
self._seen.add(warning)
|
|
31
|
+
|
|
32
|
+
if isinstance(warning, _messages.DeprecationSummary):
|
|
33
|
+
self._deprecation_warnings.append(warning)
|
|
34
|
+
else:
|
|
35
|
+
self._warnings.append(warning)
|
|
36
|
+
|
|
37
|
+
def get_warnings(self) -> list[_messages.WarningSummary]:
|
|
38
|
+
"""Return a list of the captured non-deprecation warnings."""
|
|
39
|
+
# DTFIX-FUTURE: return a read-only list proxy instead
|
|
40
|
+
return self._warnings
|
|
41
|
+
|
|
42
|
+
def get_deprecation_warnings(self) -> list[_messages.DeprecationSummary]:
|
|
43
|
+
"""Return a list of the captured deprecation warnings."""
|
|
44
|
+
# DTFIX-FUTURE: return a read-only list proxy instead
|
|
45
|
+
return self._deprecation_warnings
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def format_message(summary: _messages.SummaryBase, include_traceback: bool) -> str:
|
|
49
|
+
if isinstance(summary, _messages.DeprecationSummary):
|
|
50
|
+
deprecation_message = get_deprecation_message_with_plugin_info(
|
|
51
|
+
msg=summary.event.msg,
|
|
52
|
+
version=summary.version,
|
|
53
|
+
date=summary.date,
|
|
54
|
+
deprecator=summary.deprecator,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
event = dataclasses.replace(summary.event, msg=deprecation_message)
|
|
58
|
+
else:
|
|
59
|
+
event = summary.event
|
|
60
|
+
|
|
61
|
+
return _event_formatting.format_event(event, include_traceback)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_deprecation_message_with_plugin_info(
|
|
65
|
+
*,
|
|
66
|
+
msg: str,
|
|
67
|
+
version: str | None,
|
|
68
|
+
removed: bool = False,
|
|
69
|
+
date: str | None,
|
|
70
|
+
deprecator: _messages.PluginInfo | None,
|
|
71
|
+
) -> str:
|
|
72
|
+
"""Internal use only. Return a deprecation message and help text for display."""
|
|
73
|
+
# DTFIX-FUTURE: the logic for omitting date/version doesn't apply to the payload, so it shows up in vars in some cases when it should not
|
|
74
|
+
|
|
75
|
+
if removed:
|
|
76
|
+
removal_fragment = 'This feature was removed'
|
|
77
|
+
else:
|
|
78
|
+
removal_fragment = 'This feature will be removed'
|
|
79
|
+
|
|
80
|
+
if not deprecator or not deprecator.type:
|
|
81
|
+
# indeterminate has no resolved_name or type
|
|
82
|
+
# collections have a resolved_name but no type
|
|
83
|
+
collection = deprecator.resolved_name if deprecator else None
|
|
84
|
+
plugin_fragment = ''
|
|
85
|
+
elif deprecator.resolved_name == 'ansible.builtin':
|
|
86
|
+
# core deprecations from base classes (the API) have no plugin name, only 'ansible.builtin'
|
|
87
|
+
plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin'
|
|
88
|
+
|
|
89
|
+
collection = deprecator.resolved_name
|
|
90
|
+
plugin_fragment = f'the {plugin_type_name} API'
|
|
91
|
+
else:
|
|
92
|
+
parts = deprecator.resolved_name.split('.')
|
|
93
|
+
plugin_name = parts[-1]
|
|
94
|
+
plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin'
|
|
95
|
+
|
|
96
|
+
collection = '.'.join(parts[:2]) if len(parts) > 2 else None
|
|
97
|
+
plugin_fragment = f'{plugin_type_name} {plugin_name!r}'
|
|
98
|
+
|
|
99
|
+
if collection and plugin_fragment:
|
|
100
|
+
plugin_fragment += ' in'
|
|
101
|
+
|
|
102
|
+
if collection == 'ansible.builtin':
|
|
103
|
+
collection_fragment = 'ansible-core'
|
|
104
|
+
elif collection:
|
|
105
|
+
collection_fragment = f'collection {collection!r}'
|
|
106
|
+
else:
|
|
107
|
+
collection_fragment = ''
|
|
108
|
+
|
|
109
|
+
if not collection:
|
|
110
|
+
when_fragment = 'in the future' if not removed else ''
|
|
111
|
+
elif date:
|
|
112
|
+
when_fragment = f'in a release after {date}'
|
|
113
|
+
elif version:
|
|
114
|
+
when_fragment = f'version {version}'
|
|
115
|
+
else:
|
|
116
|
+
when_fragment = 'in a future release' if not removed else ''
|
|
117
|
+
|
|
118
|
+
if plugin_fragment or collection_fragment:
|
|
119
|
+
from_fragment = 'from'
|
|
120
|
+
else:
|
|
121
|
+
from_fragment = ''
|
|
122
|
+
|
|
123
|
+
deprecation_msg = ' '.join(f for f in [removal_fragment, from_fragment, plugin_fragment, collection_fragment, when_fragment] if f) + '.'
|
|
124
|
+
|
|
125
|
+
return join_sentences(msg, deprecation_msg)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def join_sentences(first: str | None, second: str | None) -> str:
|
|
129
|
+
"""Join two sentences together."""
|
|
130
|
+
first = (first or '').strip()
|
|
131
|
+
second = (second or '').strip()
|
|
132
|
+
|
|
133
|
+
if first and first[-1] not in ('!', '?', '.'):
|
|
134
|
+
first += '.'
|
|
135
|
+
|
|
136
|
+
if second and second[-1] not in ('!', '?', '.'):
|
|
137
|
+
second += '.'
|
|
138
|
+
|
|
139
|
+
if first and not second:
|
|
140
|
+
return first
|
|
141
|
+
|
|
142
|
+
if not first and second:
|
|
143
|
+
return second
|
|
144
|
+
|
|
145
|
+
return ' '.join((first, second))
|
|
@@ -24,7 +24,6 @@ from ansible._internal._templating import _transform
|
|
|
24
24
|
from ansible.module_utils import _internal
|
|
25
25
|
from ansible.module_utils._internal import _datatag
|
|
26
26
|
|
|
27
|
-
_T = t.TypeVar('_T')
|
|
28
27
|
_sentinel = object()
|
|
29
28
|
|
|
30
29
|
|
|
@@ -115,7 +114,7 @@ class AnsibleVariableVisitor:
|
|
|
115
114
|
if func := getattr(super(), '__exit__', None):
|
|
116
115
|
func(*args, **kwargs)
|
|
117
116
|
|
|
118
|
-
def visit(self, value:
|
|
117
|
+
def visit[T](self, value: T) -> T:
|
|
119
118
|
"""
|
|
120
119
|
Enforces Ansible's variable type system restrictions before a var is accepted in inventory. Also, conditionally implements template trust
|
|
121
120
|
compatibility, depending on the plugin's declared understanding (or lack thereof). This always recursively copies inputs to fully isolate
|
|
@@ -143,7 +142,7 @@ class AnsibleVariableVisitor:
|
|
|
143
142
|
|
|
144
143
|
return self._visit(None, key) # key=None prevents state tracking from seeing the key as value
|
|
145
144
|
|
|
146
|
-
def _visit(self, key: t.Any, value:
|
|
145
|
+
def _visit[T](self, key: t.Any, value: T) -> T:
|
|
147
146
|
"""Internal implementation to recursively visit a data structure's contents."""
|
|
148
147
|
self._current = key # supports StateTrackingMixIn
|
|
149
148
|
|
|
@@ -168,7 +167,7 @@ class AnsibleVariableVisitor:
|
|
|
168
167
|
value = value._native_copy()
|
|
169
168
|
value_type = type(value)
|
|
170
169
|
|
|
171
|
-
result:
|
|
170
|
+
result: T
|
|
172
171
|
|
|
173
172
|
# DTFIX-FUTURE: Visitor generally ignores dict/mapping keys by default except for debugging and schema-aware checking.
|
|
174
173
|
# It could be checking keys destined for variable storage to apply more strict rules about key shape and type.
|
|
@@ -193,7 +193,7 @@ class TemplateEngine:
|
|
|
193
193
|
return self._variables
|
|
194
194
|
|
|
195
195
|
@available_variables.setter
|
|
196
|
-
def available_variables(self, variables: dict[str, t.Any]) -> None:
|
|
196
|
+
def available_variables(self, variables: dict[str, t.Any] | ChainMap[str, t.Any]) -> None:
|
|
197
197
|
self._variables = variables
|
|
198
198
|
|
|
199
199
|
def resolve_variable_expression(
|
|
@@ -29,7 +29,6 @@ from ._utils import LazyOptions, TemplateContext
|
|
|
29
29
|
|
|
30
30
|
_display = Display()
|
|
31
31
|
|
|
32
|
-
_TCallable = t.TypeVar("_TCallable", bound=t.Callable)
|
|
33
32
|
_ITERATOR_TYPES: t.Final = (c.Iterator, c.ItemsView, c.KeysView, c.ValuesView, range)
|
|
34
33
|
|
|
35
34
|
|
|
@@ -174,7 +173,7 @@ class _DirectCall:
|
|
|
174
173
|
_marker_attr: t.Final[str] = "_directcall"
|
|
175
174
|
|
|
176
175
|
@classmethod
|
|
177
|
-
def mark(cls, src:
|
|
176
|
+
def mark[T: t.Callable](cls, src: T) -> T:
|
|
178
177
|
setattr(src, cls._marker_attr, True)
|
|
179
178
|
return src
|
|
180
179
|
|