ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b6__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.
- ansible/_internal/__init__.py +1 -1
- ansible/_internal/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
- ansible/_internal/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +5 -5
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +28 -20
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +36 -5
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +26 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_yaml/_constructor.py +4 -4
- ansible/_internal/_yaml/_dumper.py +26 -18
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +11 -93
- ansible/cli/arguments/option_helpers.py +3 -4
- ansible/cli/console.py +1 -1
- ansible/cli/doc.py +86 -30
- ansible/cli/inventory.py +5 -7
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +46 -0
- ansible/errors/__init__.py +98 -50
- ansible/executor/module_common.py +75 -49
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +40 -115
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/api.py +1 -4
- ansible/galaxy/collection/__init__.py +2 -10
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
- ansible/galaxy/role.py +2 -2
- ansible/inventory/manager.py +1 -1
- ansible/module_utils/_internal/__init__.py +7 -7
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +13 -39
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_datatag/__init__.py +43 -15
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +67 -55
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +22 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +54 -49
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +15 -2
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +95 -71
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/respawn.py +4 -41
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/connection.py +8 -11
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/hardware/linux.py +1 -1
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/caps.py +2 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/system/local.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +2 -2
- ansible/module_utils/service.py +3 -10
- ansible/module_utils/urls.py +4 -4
- ansible/modules/apt_repository.py +17 -39
- ansible/modules/assemble.py +2 -2
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +12 -22
- ansible/modules/command.py +3 -3
- ansible/modules/copy.py +4 -4
- ansible/modules/cron.py +1 -1
- ansible/modules/dnf5.py +14 -22
- ansible/modules/file.py +16 -17
- ansible/modules/find.py +3 -3
- ansible/modules/get_url.py +17 -0
- ansible/modules/git.py +9 -7
- ansible/modules/hostname.py +0 -1
- ansible/modules/known_hosts.py +12 -14
- ansible/modules/package.py +6 -0
- ansible/modules/replace.py +2 -2
- ansible/modules/service.py +3 -9
- ansible/modules/slurp.py +10 -13
- ansible/modules/stat.py +5 -7
- ansible/modules/unarchive.py +6 -6
- ansible/modules/user.py +1 -1
- ansible/modules/wait_for.py +28 -30
- ansible/modules/yum_repository.py +4 -3
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +6 -6
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +10 -14
- ansible/playbook/base.py +7 -2
- ansible/playbook/included_file.py +3 -1
- ansible/playbook/play_context.py +2 -0
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +19 -8
- ansible/playbook/task.py +2 -0
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +8 -31
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/fetch.py +3 -3
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +11 -10
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/cache/__init__.py +17 -19
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/callback/tree.py +5 -5
- ansible/plugins/connection/local.py +6 -6
- ansible/plugins/connection/paramiko_ssh.py +5 -5
- ansible/plugins/connection/ssh.py +25 -15
- ansible/plugins/connection/winrm.py +6 -3
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +32 -27
- ansible/plugins/filter/encryption.py +14 -6
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +73 -19
- ansible/plugins/loader.py +7 -7
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/lookup/template.py +9 -4
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +67 -9
- ansible/plugins/shell/sh.py +10 -5
- ansible/plugins/strategy/__init__.py +3 -3
- ansible/plugins/test/core.py +22 -16
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +38 -54
- ansible/utils/collection_loader/_collection_finder.py +3 -3
- ansible/utils/display.py +124 -138
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +6 -8
- ansible/utils/listify.py +6 -4
- ansible/utils/path.py +5 -7
- ansible/utils/py3compat.py +2 -1
- ansible/utils/ssh_functions.py +3 -2
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- ansible/vars/plugins.py +3 -3
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +224 -204
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/commands/integration/coverage.py +7 -2
- ansible_test/_internal/host_profiles.py +62 -10
- ansible_test/_internal/provisioning.py +10 -4
- ansible_test/_internal/ssh.py +1 -5
- ansible_test/_internal/thread.py +2 -1
- ansible_test/_internal/timeout.py +1 -1
- ansible_test/_internal/util.py +40 -12
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible_test/_util/target/setup/requirements.py +3 -9
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
from __future__ import annotations as _annotations
|
2
|
+
|
3
|
+
import typing as _t
|
4
|
+
|
5
|
+
from ansible.module_utils._internal import _text_utils, _messages
|
6
|
+
|
7
|
+
|
8
|
+
def deduplicate_message_parts(message_parts: list[str]) -> str:
|
9
|
+
"""Format the given list of messages into a brief message, while deduplicating repeated elements."""
|
10
|
+
message_parts = list(reversed(message_parts))
|
11
|
+
|
12
|
+
message = message_parts.pop(0)
|
13
|
+
|
14
|
+
for message_part in message_parts:
|
15
|
+
# avoid duplicate messages where the cause was already concatenated to the exception message
|
16
|
+
if message_part.endswith(message):
|
17
|
+
message = message_part
|
18
|
+
else:
|
19
|
+
message = _text_utils.concat_message(message_part, message)
|
20
|
+
|
21
|
+
return message
|
22
|
+
|
23
|
+
|
24
|
+
def format_event_brief_message(event: _messages.Event) -> str:
|
25
|
+
"""
|
26
|
+
Format an event into a brief message.
|
27
|
+
Help text, contextual information and sub-events will be omitted.
|
28
|
+
"""
|
29
|
+
message_parts: list[str] = []
|
30
|
+
|
31
|
+
while True:
|
32
|
+
message_parts.append(event.msg)
|
33
|
+
|
34
|
+
if not event.chain or not event.chain.follow:
|
35
|
+
break
|
36
|
+
|
37
|
+
event = event.chain.event
|
38
|
+
|
39
|
+
return deduplicate_message_parts(message_parts)
|
40
|
+
|
41
|
+
|
42
|
+
def deprecation_as_dict(deprecation: _messages.DeprecationSummary) -> _t.Dict[str, _t.Any]:
|
43
|
+
"""Returns a dictionary representation of the deprecation object in the format exposed to playbooks."""
|
44
|
+
from ansible.module_utils._internal._deprecator import INDETERMINATE_DEPRECATOR # circular import from messages
|
45
|
+
|
46
|
+
if deprecation.deprecator and deprecation.deprecator != INDETERMINATE_DEPRECATOR:
|
47
|
+
collection_name = '.'.join(deprecation.deprecator.resolved_name.split('.')[:2])
|
48
|
+
else:
|
49
|
+
collection_name = None
|
50
|
+
|
51
|
+
result = dict(
|
52
|
+
msg=format_event_brief_message(deprecation.event),
|
53
|
+
collection_name=collection_name,
|
54
|
+
)
|
55
|
+
|
56
|
+
if deprecation.date:
|
57
|
+
result.update(date=deprecation.date)
|
58
|
+
else:
|
59
|
+
result.update(version=deprecation.version)
|
60
|
+
|
61
|
+
return result
|
@@ -6,7 +6,7 @@ import json
|
|
6
6
|
import typing as t
|
7
7
|
|
8
8
|
from ansible.module_utils import _internal
|
9
|
-
from ansible.module_utils.
|
9
|
+
from ansible.module_utils._internal import _messages
|
10
10
|
from ansible.module_utils._internal._datatag import (
|
11
11
|
AnsibleSerializable,
|
12
12
|
AnsibleSerializableWrapper,
|
@@ -87,7 +87,9 @@ For controller-to-module, type behavior is profile dependent.
|
|
87
87
|
_common_module_response_types: frozenset[type[AnsibleSerializable]] = frozenset(
|
88
88
|
{
|
89
89
|
_messages.PluginInfo,
|
90
|
-
_messages.
|
90
|
+
_messages.PluginType,
|
91
|
+
_messages.Event,
|
92
|
+
_messages.EventChain,
|
91
93
|
_messages.ErrorSummary,
|
92
94
|
_messages.WarningSummary,
|
93
95
|
_messages.DeprecationSummary,
|
@@ -203,11 +205,27 @@ class _JSONSerializationProfile(t.Generic[_T_encoder, _T_decoder]):
|
|
203
205
|
|
204
206
|
@classmethod
|
205
207
|
def handle_key(cls, k: t.Any) -> t.Any:
|
208
|
+
"""Validation/conversion hook before a dict key is serialized. The default implementation only accepts str-typed keys."""
|
209
|
+
# NOTE: Since JSON requires string keys, there is no support for preserving tags on dictionary keys during serialization.
|
210
|
+
|
206
211
|
if not isinstance(k, str): # DTFIX-FUTURE: optimize this to use all known str-derived types in type map / allowed types
|
207
212
|
raise TypeError(f'Key of type {type(k).__name__!r} is not JSON serializable by the {cls.profile_name!r} profile.')
|
208
213
|
|
209
214
|
return k
|
210
215
|
|
216
|
+
@classmethod
|
217
|
+
def _handle_key_str_fallback(cls, k: t.Any) -> t.Any:
|
218
|
+
"""Legacy implementations should use this key handler for backward compatibility with stdlib JSON key conversion quirks."""
|
219
|
+
# DTFIX-FUTURE: optimized exact-type table lookup first
|
220
|
+
|
221
|
+
if isinstance(k, str):
|
222
|
+
return k
|
223
|
+
|
224
|
+
if k is None or isinstance(k, (int, float)):
|
225
|
+
return json.dumps(k)
|
226
|
+
|
227
|
+
raise TypeError(f'Key of type {type(k).__name__!r} is not JSON serializable by the {cls.profile_name!r} profile.')
|
228
|
+
|
211
229
|
@classmethod
|
212
230
|
def default(cls, o: t.Any) -> t.Any:
|
213
231
|
# Preserve the built-in JSON encoder support for subclasses of scalar types.
|
@@ -373,8 +391,8 @@ Future code changes should further restrict bytes to string conversions to elimi
|
|
373
391
|
Additional warnings at other boundaries may be needed to give users an opportunity to resolve the issues before they become errors.
|
374
392
|
"""
|
375
393
|
# DTFIX-FUTURE: add strict UTF8 string encoding checking to serialization profiles (to match the checks performed during deserialization)
|
376
|
-
#
|
377
|
-
# DTFIX-
|
394
|
+
# DTFIX3: the surrogateescape note above isn't quite right, for encoding use surrogatepass, which does work
|
395
|
+
# DTFIX-FUTURE: this config setting should probably be deprecated
|
378
396
|
|
379
397
|
|
380
398
|
def _create_encoding_check_error() -> Exception:
|
@@ -22,6 +22,8 @@ class _Profile(_profiles._JSONSerializationProfile["Encoder", "Decoder"]):
|
|
22
22
|
}
|
23
23
|
)
|
24
24
|
|
25
|
+
cls.handle_key = cls._handle_key_str_fallback # type: ignore[method-assign] # legacy stdlib-compatible key behavior
|
26
|
+
|
25
27
|
|
26
28
|
class Encoder(_profiles.AnsibleProfileJSONEncoder):
|
27
29
|
_profile = _Profile
|
@@ -26,6 +26,8 @@ class _Profile(_profiles._JSONSerializationProfile["Encoder", "Decoder"]):
|
|
26
26
|
_datetime.datetime: cls.serialize_as_isoformat, # legacy _json_encode_fallback behavior *and* legacy parameters.py does this before serialization
|
27
27
|
}
|
28
28
|
|
29
|
+
cls.handle_key = cls._handle_key_str_fallback # type: ignore[method-assign] # legacy stdlib-compatible key behavior
|
30
|
+
|
29
31
|
|
30
32
|
class Encoder(_profiles.AnsibleProfileJSONEncoder):
|
31
33
|
_profile = _Profile
|
@@ -17,7 +17,7 @@ class _Profile(_profiles._JSONSerializationProfile["Encoder", "Decoder"]):
|
|
17
17
|
@classmethod
|
18
18
|
def post_init(cls) -> None:
|
19
19
|
cls.serialize_map = {
|
20
|
-
#
|
20
|
+
# DTFIX5: support serialization of every type that is supported in the Ansible variable type system
|
21
21
|
set: cls.serialize_as_list,
|
22
22
|
tuple: cls.serialize_as_list,
|
23
23
|
_datetime.date: cls.serialize_as_isoformat,
|
@@ -41,6 +41,8 @@ class _Profile(_profiles._JSONSerializationProfile["Encoder", "Decoder"]):
|
|
41
41
|
'__ansible_vault': _functools.partial(cls.unsupported_target_type_error, '__ansible_vault'),
|
42
42
|
}
|
43
43
|
|
44
|
+
cls.handle_key = cls._handle_key_str_fallback # type: ignore[method-assign] # legacy stdlib-compatible key behavior
|
45
|
+
|
44
46
|
|
45
47
|
class Encoder(_profiles.AnsibleProfileJSONEncoder):
|
46
48
|
_profile = _Profile
|
@@ -7,13 +7,12 @@ A future release will remove the provisional status.
|
|
7
7
|
|
8
8
|
from __future__ import annotations as _annotations
|
9
9
|
|
10
|
-
import sys as _sys
|
11
10
|
import dataclasses as _dataclasses
|
11
|
+
import enum as _enum
|
12
|
+
import sys as _sys
|
13
|
+
import typing as _t
|
12
14
|
|
13
|
-
|
14
|
-
from ..compat import typing as _t
|
15
|
-
|
16
|
-
from ansible.module_utils._internal import _datatag, _validation
|
15
|
+
from ansible.module_utils._internal import _datatag, _dataclass_validation
|
17
16
|
|
18
17
|
if _sys.version_info >= (3, 10):
|
19
18
|
# Using slots for reduced memory usage and improved performance.
|
@@ -23,56 +22,79 @@ else:
|
|
23
22
|
_dataclass_kwargs = dict(frozen=True)
|
24
23
|
|
25
24
|
|
25
|
+
class PluginType(_datatag.AnsibleSerializableEnum):
|
26
|
+
"""Enum of Ansible plugin types."""
|
27
|
+
|
28
|
+
ACTION = _enum.auto()
|
29
|
+
BECOME = _enum.auto()
|
30
|
+
CACHE = _enum.auto()
|
31
|
+
CALLBACK = _enum.auto()
|
32
|
+
CLICONF = _enum.auto()
|
33
|
+
CONNECTION = _enum.auto()
|
34
|
+
DOC_FRAGMENTS = _enum.auto()
|
35
|
+
FILTER = _enum.auto()
|
36
|
+
HTTPAPI = _enum.auto()
|
37
|
+
INVENTORY = _enum.auto()
|
38
|
+
LOOKUP = _enum.auto()
|
39
|
+
MODULE = _enum.auto()
|
40
|
+
NETCONF = _enum.auto()
|
41
|
+
SHELL = _enum.auto()
|
42
|
+
STRATEGY = _enum.auto()
|
43
|
+
TERMINAL = _enum.auto()
|
44
|
+
TEST = _enum.auto()
|
45
|
+
VARS = _enum.auto()
|
46
|
+
|
47
|
+
|
26
48
|
@_dataclasses.dataclass(**_dataclass_kwargs)
|
27
49
|
class PluginInfo(_datatag.AnsibleSerializableDataclass):
|
28
50
|
"""Information about a loaded plugin."""
|
29
51
|
|
30
|
-
resolved_name: str
|
52
|
+
resolved_name: _t.Optional[str]
|
31
53
|
"""The resolved canonical plugin name; always fully-qualified for collection plugins."""
|
32
|
-
|
54
|
+
|
55
|
+
type: _t.Optional[PluginType]
|
33
56
|
"""The plugin type."""
|
34
57
|
|
35
|
-
_COLLECTION_ONLY_TYPE: _t.ClassVar[str] = 'collection'
|
36
|
-
"""This is not a real plugin type. It's a placeholder for use by a `PluginInfo` instance which references a collection without a plugin."""
|
37
58
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
59
|
+
@_dataclasses.dataclass(**_dataclass_kwargs)
|
60
|
+
class EventChain(_datatag.AnsibleSerializableDataclass):
|
61
|
+
"""A chain used to link one event to another."""
|
62
|
+
|
63
|
+
_validation_auto_enabled = False
|
43
64
|
|
44
|
-
|
65
|
+
def __post_init__(self): ... # required for deferred dataclass validation
|
45
66
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
67
|
+
msg_reason: str
|
68
|
+
traceback_reason: str
|
69
|
+
event: Event
|
70
|
+
follow: bool = True
|
50
71
|
|
51
72
|
|
52
73
|
@_dataclasses.dataclass(**_dataclass_kwargs)
|
53
|
-
class
|
54
|
-
"""
|
74
|
+
class Event(_datatag.AnsibleSerializableDataclass):
|
75
|
+
"""Base class for an error/warning/deprecation event with optional chain (from an exception __cause__ chain) and an optional traceback."""
|
76
|
+
|
77
|
+
_validation_auto_enabled = False
|
78
|
+
|
79
|
+
def __post_init__(self): ... # required for deferred dataclass validation
|
55
80
|
|
56
81
|
msg: str
|
57
82
|
formatted_source_context: _t.Optional[str] = None
|
83
|
+
formatted_traceback: _t.Optional[str] = None
|
58
84
|
help_text: _t.Optional[str] = None
|
85
|
+
chain: _t.Optional[EventChain] = None
|
86
|
+
events: _t.Optional[_t.Tuple[Event, ...]] = None
|
87
|
+
|
88
|
+
|
89
|
+
_dataclass_validation.inject_post_init_validation(EventChain, EventChain._validation_allow_subclasses)
|
90
|
+
_dataclass_validation.inject_post_init_validation(Event, Event._validation_allow_subclasses)
|
59
91
|
|
60
92
|
|
61
93
|
@_dataclasses.dataclass(**_dataclass_kwargs)
|
62
94
|
class SummaryBase(_datatag.AnsibleSerializableDataclass):
|
63
95
|
"""Base class for an error/warning/deprecation summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""
|
64
96
|
|
65
|
-
|
66
|
-
formatted_traceback: _t.Optional[str] = None
|
67
|
-
|
68
|
-
def _format(self) -> str:
|
69
|
-
"""Returns a string representation of the details."""
|
70
|
-
# DTFIX-RELEASE: eliminate this function and use a common message squashing utility such as get_chained_message on instances of this type
|
71
|
-
return ': '.join(detail.msg for detail in self.details)
|
72
|
-
|
73
|
-
def _post_validate(self) -> None:
|
74
|
-
if not self.details:
|
75
|
-
raise ValueError(f'{type(self).__name__}.details cannot be empty')
|
97
|
+
event: Event
|
76
98
|
|
77
99
|
|
78
100
|
@_dataclasses.dataclass(**_dataclass_kwargs)
|
@@ -106,20 +128,3 @@ class DeprecationSummary(WarningSummary):
|
|
106
128
|
Ignored if `deprecator` is not provided.
|
107
129
|
Ignored if `date` is provided.
|
108
130
|
"""
|
109
|
-
|
110
|
-
def _as_simple_dict(self) -> _t.Dict[str, _t.Any]:
|
111
|
-
"""Returns a dictionary representation of the deprecation object in the format exposed to playbooks."""
|
112
|
-
from ansible.module_utils._internal._deprecator import INDETERMINATE_DEPRECATOR # circular import from messages
|
113
|
-
|
114
|
-
if self.deprecator and self.deprecator != INDETERMINATE_DEPRECATOR:
|
115
|
-
collection_name = '.'.join(self.deprecator.resolved_name.split('.')[:2])
|
116
|
-
else:
|
117
|
-
collection_name = None
|
118
|
-
|
119
|
-
result = self._as_dict()
|
120
|
-
result.update(
|
121
|
-
msg=self._format(),
|
122
|
-
collection_name=collection_name,
|
123
|
-
)
|
124
|
-
|
125
|
-
return result
|
@@ -1,7 +1,5 @@
|
|
1
1
|
"""Patches for builtin `dataclasses` module."""
|
2
2
|
|
3
|
-
# deprecated: description='verify ClassVar support in dataclasses has been fixed in Python before removing this patching code', python_version='3.13'
|
4
|
-
|
5
3
|
from __future__ import annotations
|
6
4
|
|
7
5
|
import dataclasses
|
@@ -26,7 +24,7 @@ class DataclassesIsTypePatch(CallablePatch):
|
|
26
24
|
@dataclasses.dataclass
|
27
25
|
class CheckClassVar:
|
28
26
|
# this is the broken case requiring patching: ClassVar dot-referenced from a module that is not `typing` is treated as an instance field
|
29
|
-
# DTFIX-
|
27
|
+
# DTFIX-FUTURE: file/link CPython bug report, deprecate this patch if/when it's fixed in CPython
|
30
28
|
a_classvar: _ts.ClassVar[int] # type: ignore[name-defined]
|
31
29
|
a_field: int
|
32
30
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import typing as t
|
4
4
|
|
5
|
-
from
|
5
|
+
from . import _messages
|
6
6
|
|
7
7
|
|
8
8
|
class HasPluginInfo(t.Protocol):
|
@@ -21,5 +21,18 @@ def get_plugin_info(value: HasPluginInfo) -> _messages.PluginInfo:
|
|
21
21
|
"""Utility method that returns a `PluginInfo` from an object implementing the `HasPluginInfo` protocol."""
|
22
22
|
return _messages.PluginInfo(
|
23
23
|
resolved_name=value.ansible_name,
|
24
|
-
type=value.plugin_type,
|
24
|
+
type=normalize_plugin_type(value.plugin_type),
|
25
25
|
)
|
26
|
+
|
27
|
+
|
28
|
+
def normalize_plugin_type(value: str) -> _messages.PluginType | None:
|
29
|
+
"""Normalize value and return it as a PluginType, or None if the value does match any known plugin type."""
|
30
|
+
value = value.lower()
|
31
|
+
|
32
|
+
if value == 'modules':
|
33
|
+
value = 'module'
|
34
|
+
|
35
|
+
try:
|
36
|
+
return _messages.PluginType(value)
|
37
|
+
except ValueError:
|
38
|
+
return None
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from __future__ import annotations as _annotations
|
2
|
+
|
3
|
+
import inspect as _inspect
|
4
|
+
import typing as _t
|
5
|
+
|
6
|
+
|
7
|
+
def caller_frame() -> _inspect.FrameInfo | None:
|
8
|
+
"""Return the caller stack frame, skipping any marked with the `_skip_stackwalk` local."""
|
9
|
+
_skip_stackwalk = True
|
10
|
+
|
11
|
+
return next(iter_stack(), None)
|
12
|
+
|
13
|
+
|
14
|
+
def iter_stack() -> _t.Generator[_inspect.FrameInfo]:
|
15
|
+
"""Iterate over stack frames, skipping any marked with the `_skip_stackwalk` local."""
|
16
|
+
_skip_stackwalk = True
|
17
|
+
|
18
|
+
for frame_info in _inspect.stack():
|
19
|
+
if '_skip_stackwalk' in frame_info.frame.f_locals:
|
20
|
+
continue
|
21
|
+
|
22
|
+
yield frame_info
|
@@ -6,9 +6,10 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import enum
|
9
|
-
import inspect
|
10
9
|
import traceback
|
11
10
|
|
11
|
+
from . import _stack
|
12
|
+
|
12
13
|
|
13
14
|
class TracebackEvent(enum.Enum):
|
14
15
|
"""The events for which tracebacks can be enabled."""
|
@@ -16,6 +17,7 @@ class TracebackEvent(enum.Enum):
|
|
16
17
|
ERROR = enum.auto()
|
17
18
|
WARNING = enum.auto()
|
18
19
|
DEPRECATED = enum.auto()
|
20
|
+
DEPRECATED_VALUE = enum.auto() # implies DEPRECATED
|
19
21
|
|
20
22
|
|
21
23
|
def traceback_for() -> list[str]:
|
@@ -28,24 +30,25 @@ def is_traceback_enabled(event: TracebackEvent) -> bool:
|
|
28
30
|
return _is_traceback_enabled(event)
|
29
31
|
|
30
32
|
|
31
|
-
def maybe_capture_traceback(event: TracebackEvent) -> str | None:
|
33
|
+
def maybe_capture_traceback(msg: str, event: TracebackEvent) -> str | None:
|
32
34
|
"""
|
33
35
|
Optionally capture a traceback for the current call stack, formatted as a string, if the specified traceback event is enabled.
|
34
|
-
|
36
|
+
Frames marked with the `_skip_stackwalk` local are omitted.
|
35
37
|
"""
|
38
|
+
_skip_stackwalk = True
|
39
|
+
|
36
40
|
if not is_traceback_enabled(event):
|
37
41
|
return None
|
38
42
|
|
39
43
|
tb_lines = []
|
40
44
|
|
41
|
-
if
|
45
|
+
if frame_info := _stack.caller_frame():
|
42
46
|
# DTFIX-FUTURE: rewrite target-side tracebacks to point at controller-side paths?
|
43
|
-
frames = inspect.getouterframes(current_frame)
|
44
|
-
ignore_frame_count = 2 # ignore this function and its caller
|
45
47
|
tb_lines.append('Traceback (most recent call last):\n')
|
46
|
-
tb_lines.extend(traceback.format_stack(
|
48
|
+
tb_lines.extend(traceback.format_stack(frame_info.frame))
|
49
|
+
tb_lines.append(f'Message: {msg}\n')
|
47
50
|
else:
|
48
|
-
tb_lines.append('
|
51
|
+
tb_lines.append('(frame not found)\n') # pragma: nocover
|
49
52
|
|
50
53
|
return ''.join(tb_lines)
|
51
54
|
|