ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b5__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.
Files changed (163) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_collection_proxy.py +1 -1
  3. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  4. ansible/_internal/_errors/_captured.py +25 -30
  5. ansible/_internal/_errors/_error_factory.py +89 -0
  6. ansible/_internal/_errors/_error_utils.py +240 -0
  7. ansible/_internal/_errors/_task_timeout.py +28 -0
  8. ansible/_internal/_event_formatting.py +127 -0
  9. ansible/_internal/_json/__init__.py +5 -5
  10. ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
  11. ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
  12. ansible/_internal/_json/_profiles/_legacy.py +3 -11
  13. ansible/_internal/_ssh/__init__.py +0 -0
  14. ansible/_internal/_ssh/_agent_launch.py +91 -0
  15. ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
  16. ansible/_internal/_templating/__init__.py +5 -3
  17. ansible/_internal/_templating/_datatag.py +2 -1
  18. ansible/_internal/_templating/_engine.py +3 -4
  19. ansible/_internal/_templating/_jinja_bits.py +21 -16
  20. ansible/_internal/_templating/_jinja_common.py +18 -27
  21. ansible/_internal/_templating/_jinja_plugins.py +31 -3
  22. ansible/_internal/_templating/_lazy_containers.py +5 -5
  23. ansible/_internal/_templating/_transform.py +20 -19
  24. ansible/_internal/_templating/_utils.py +1 -1
  25. ansible/_internal/_yaml/_dumper.py +1 -1
  26. ansible/_internal/_yaml/_errors.py +7 -7
  27. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  29. ansible/cli/__init__.py +5 -82
  30. ansible/cli/arguments/option_helpers.py +2 -3
  31. ansible/cli/doc.py +84 -28
  32. ansible/cli/inventory.py +1 -1
  33. ansible/compat/importlib_resources.py +9 -12
  34. ansible/config/base.yml +22 -0
  35. ansible/errors/__init__.py +96 -49
  36. ansible/executor/module_common.py +8 -10
  37. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  38. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  39. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  40. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  41. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  42. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  43. ansible/executor/powershell/module_manifest.py +52 -0
  44. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  45. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  46. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  47. ansible/executor/process/worker.py +38 -113
  48. ansible/executor/task_executor.py +26 -61
  49. ansible/executor/task_result.py +2 -4
  50. ansible/galaxy/collection/__init__.py +1 -4
  51. ansible/inventory/manager.py +1 -1
  52. ansible/module_utils/_internal/__init__.py +0 -3
  53. ansible/module_utils/_internal/_ambient_context.py +3 -3
  54. ansible/module_utils/_internal/_ansiballz.py +4 -2
  55. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  56. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  57. ansible/module_utils/_internal/_deprecator.py +66 -48
  58. ansible/module_utils/_internal/_errors.py +88 -17
  59. ansible/module_utils/_internal/_event_utils.py +61 -0
  60. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  61. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  62. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  63. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  64. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  65. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  66. ansible/module_utils/_internal/_plugin_info.py +1 -1
  67. ansible/module_utils/_internal/_stack.py +22 -0
  68. ansible/module_utils/_internal/_text_utils.py +6 -0
  69. ansible/module_utils/_internal/_traceback.py +11 -8
  70. ansible/module_utils/ansible_release.py +1 -1
  71. ansible/module_utils/basic.py +49 -15
  72. ansible/module_utils/common/arg_spec.py +2 -2
  73. ansible/module_utils/common/collections.py +6 -0
  74. ansible/module_utils/common/json.py +2 -2
  75. ansible/module_utils/common/text/converters.py +3 -3
  76. ansible/module_utils/common/validation.py +1 -1
  77. ansible/module_utils/common/warnings.py +80 -23
  78. ansible/module_utils/common/yaml.py +1 -1
  79. ansible/module_utils/datatag.py +5 -2
  80. ansible/module_utils/facts/system/distribution.py +16 -3
  81. ansible/module_utils/facts/virtual/linux.py +1 -1
  82. ansible/module_utils/service.py +2 -9
  83. ansible/modules/apt_repository.py +7 -29
  84. ansible/modules/async_status.py +13 -11
  85. ansible/modules/async_wrapper.py +5 -5
  86. ansible/modules/dnf5.py +14 -22
  87. ansible/modules/hostname.py +0 -1
  88. ansible/modules/service.py +3 -9
  89. ansible/parsing/ajson.py +3 -5
  90. ansible/parsing/dataloader.py +4 -4
  91. ansible/parsing/mod_args.py +1 -1
  92. ansible/parsing/plugin_docs.py +2 -2
  93. ansible/parsing/utils/yaml.py +3 -3
  94. ansible/parsing/vault/__init__.py +4 -4
  95. ansible/playbook/playbook_include.py +1 -1
  96. ansible/playbook/taggable.py +0 -3
  97. ansible/plugins/__init__.py +0 -25
  98. ansible/plugins/action/__init__.py +8 -31
  99. ansible/plugins/action/add_host.py +1 -1
  100. ansible/plugins/action/assemble.py +8 -16
  101. ansible/plugins/action/async_status.py +7 -2
  102. ansible/plugins/action/copy.py +8 -7
  103. ansible/plugins/action/gather_facts.py +8 -8
  104. ansible/plugins/action/package.py +5 -8
  105. ansible/plugins/action/script.py +8 -15
  106. ansible/plugins/action/service.py +3 -7
  107. ansible/plugins/action/template.py +3 -8
  108. ansible/plugins/action/unarchive.py +5 -15
  109. ansible/plugins/action/uri.py +9 -20
  110. ansible/plugins/callback/__init__.py +4 -6
  111. ansible/plugins/callback/junit.py +4 -2
  112. ansible/plugins/connection/local.py +2 -2
  113. ansible/plugins/connection/ssh.py +17 -9
  114. ansible/plugins/connection/winrm.py +5 -2
  115. ansible/plugins/doc_fragments/constructed.py +2 -2
  116. ansible/plugins/filter/core.py +13 -6
  117. ansible/plugins/filter/encryption.py +4 -4
  118. ansible/plugins/inventory/__init__.py +11 -10
  119. ansible/plugins/inventory/script.py +1 -1
  120. ansible/plugins/list.py +69 -16
  121. ansible/plugins/loader.py +7 -7
  122. ansible/plugins/lookup/csvfile.py +16 -71
  123. ansible/plugins/lookup/first_found.py +2 -1
  124. ansible/plugins/shell/__init__.py +56 -2
  125. ansible/plugins/shell/powershell.py +66 -9
  126. ansible/plugins/shell/sh.py +9 -5
  127. ansible/plugins/test/core.py +21 -15
  128. ansible/plugins/test/finished.yml +1 -1
  129. ansible/plugins/test/uri.py +2 -5
  130. ansible/release.py +1 -1
  131. ansible/template/__init__.py +30 -2
  132. ansible/utils/display.py +103 -128
  133. ansible/utils/hashing.py +0 -1
  134. ansible/utils/listify.py +6 -4
  135. ansible/utils/unsafe_proxy.py +1 -1
  136. ansible/vars/hostvars.py +1 -1
  137. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
  138. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
  139. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  140. ansible_test/_data/completion/docker.txt +3 -3
  141. ansible_test/_data/completion/remote.txt +1 -0
  142. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  143. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  144. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  145. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  146. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  147. ansible_test/_internal/util.py +20 -0
  148. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  149. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  150. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  151. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  152. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  153. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  154. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  155. ansible/_internal/_errors/_utils.py +0 -310
  156. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  157. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
  158. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  159. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  160. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  161. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  162. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  163. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -31,7 +31,7 @@ from ansible import errors
31
31
  from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
32
32
  from ansible._internal._templating._jinja_common import Marker, UndefinedMarker
33
33
  from ansible.module_utils.parsing.convert_bool import boolean
34
- from ansible.plugins import accept_args_markers
34
+ from ansible.template import accept_args_markers
35
35
  from ansible.parsing.vault import is_encrypted_file, VaultHelper, VaultLib
36
36
  from ansible.utils.display import Display
37
37
  from ansible.utils.version import SemanticVersion
@@ -49,37 +49,41 @@ def timedout(result):
49
49
  """ Test if task result yields a time out"""
50
50
  if not isinstance(result, MutableMapping):
51
51
  raise errors.AnsibleFilterError("The 'timedout' test expects a dictionary")
52
- return result.get('timedout', False) and bool(result['timedout'].get('period', False))
52
+
53
+ return bool(result.get('timedout') and bool(result['timedout'].get('period')))
53
54
 
54
55
 
55
56
  def failed(result):
56
57
  """ Test if task result yields failed """
57
58
  if not isinstance(result, MutableMapping):
58
59
  raise errors.AnsibleFilterError("The 'failed' test expects a dictionary")
59
- return result.get('failed', False)
60
+
61
+ return bool(result.get('failed'))
60
62
 
61
63
 
62
64
  def success(result):
63
65
  """ Test if task result yields success """
64
- return not failed(result)
66
+ return not bool(failed(result))
65
67
 
66
68
 
67
69
  def unreachable(result):
68
70
  """ Test if task result yields unreachable """
69
71
  if not isinstance(result, MutableMapping):
70
72
  raise errors.AnsibleFilterError("The 'unreachable' test expects a dictionary")
71
- return result.get('unreachable', False)
73
+
74
+ return bool(result.get('unreachable'))
72
75
 
73
76
 
74
77
  def reachable(result):
75
78
  """ Test if task result yields reachable """
76
- return not unreachable(result)
79
+ return bool(not unreachable(result))
77
80
 
78
81
 
79
82
  def changed(result):
80
83
  """ Test if task result yields changed """
81
84
  if not isinstance(result, MutableMapping):
82
85
  raise errors.AnsibleFilterError("The 'changed' test expects a dictionary")
86
+
83
87
  if 'changed' not in result:
84
88
  changed = False
85
89
  if (
@@ -88,29 +92,31 @@ def changed(result):
88
92
  isinstance(result['results'][0], MutableMapping)
89
93
  ):
90
94
  for res in result['results']:
91
- if res.get('changed', False):
95
+ if res.get('changed'):
92
96
  changed = True
93
97
  break
94
98
  else:
95
- changed = result.get('changed', False)
96
- return changed
99
+ changed = result.get('changed')
100
+
101
+ return bool(changed)
97
102
 
98
103
 
99
104
  def skipped(result):
100
105
  """ Test if task result yields skipped """
101
106
  if not isinstance(result, MutableMapping):
102
107
  raise errors.AnsibleFilterError("The 'skipped' test expects a dictionary")
103
- return result.get('skipped', False)
108
+
109
+ return bool(result.get('skipped'))
104
110
 
105
111
 
106
112
  def started(result):
107
113
  """ Test if async task has started """
108
114
  if not isinstance(result, MutableMapping):
109
115
  raise errors.AnsibleFilterError("The 'started' test expects a dictionary")
116
+
110
117
  if 'started' in result:
111
118
  # For async tasks, return status
112
- # NOTE: The value of started is 0 or 1, not False or True :-/
113
- return result.get('started', 0) == 1
119
+ return bool(result.get('started'))
114
120
  else:
115
121
  # For non-async tasks, warn user, but return as if started
116
122
  display.warning("The 'started' test expects an async task, but a non-async task was tested")
@@ -121,10 +127,10 @@ def finished(result):
121
127
  """ Test if async task has finished """
122
128
  if not isinstance(result, MutableMapping):
123
129
  raise errors.AnsibleFilterError("The 'finished' test expects a dictionary")
130
+
124
131
  if 'finished' in result:
125
132
  # For async tasks, return status
126
- # NOTE: The value of finished is 0 or 1, not False or True :-/
127
- return result.get('finished', 0) == 1
133
+ return bool(result.get('finished'))
128
134
  else:
129
135
  # For non-async tasks, warn user, but return as if finished
130
136
  display.warning("The 'finished' test expects an async task, but a non-async task was tested")
@@ -170,7 +176,7 @@ def vaulted_file(value):
170
176
  with open(to_bytes(value), 'rb') as f:
171
177
  return is_encrypted_file(f)
172
178
  except (OSError, IOError) as e:
173
- raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault", orig_exc=e)
179
+ raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault.") from e
174
180
 
175
181
 
176
182
  def match(value, pattern='', ignorecase=False, multiline=False):
@@ -5,7 +5,7 @@ DOCUMENTATION:
5
5
  short_description: Did async task finish
6
6
  description:
7
7
  - Used to test if an async task has finished, it will also work with normal tasks but will issue a warning.
8
- - This test checks for the existence of a C(finished) key in the input dictionary and that it is V(1) if present
8
+ - This test checks for the existence of a C(finished) key in the input dictionary and that it is V(True) if present
9
9
  options:
10
10
  _input:
11
11
  description: registered result from an Ansible task
@@ -20,11 +20,8 @@ def is_url(value, schemes=None):
20
20
 
21
21
  isit = is_uri(value, schemes)
22
22
  if isit:
23
- try:
24
- x = urlparse(value)
25
- isit = bool(x.netloc or x.scheme == 'file')
26
- except Exception as e:
27
- isit = False
23
+ x = urlparse(value)
24
+ isit = bool(x.netloc or x.scheme == 'file')
28
25
  return isit
29
26
 
30
27
 
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b4'
20
+ __version__ = '2.19.0b5'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -98,7 +98,7 @@ class Templar:
98
98
  @property
99
99
  def basedir(self) -> str:
100
100
  """The basedir from DataLoader."""
101
- # DTFIX-RELEASE: come up with a better way to handle this so it can be deprecated
101
+ # DTFIX-FUTURE: come up with a better way to handle this so it can be deprecated
102
102
  return self._engine.basedir
103
103
 
104
104
  @property
@@ -381,7 +381,7 @@ def generate_ansible_template_vars(path: str, fullpath: str | None = None, dest_
381
381
  )
382
382
 
383
383
  ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
384
- # DTFIX-RELEASE: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
384
+ # DTFIX7: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
385
385
  ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
386
386
  ansible_managed = trust_as_template(ansible_managed)
387
387
  ansible_managed = _module_utils_datatag.deprecate_value(
@@ -427,3 +427,31 @@ def is_trusted_as_template(value: object) -> bool:
427
427
  This function should not be needed for production code, but may be useful in unit tests.
428
428
  """
429
429
  return isinstance(value, _TRUSTABLE_TYPES) and _tags.TrustedAsTemplate.is_tagged_on(value)
430
+
431
+
432
+ _TCallable = _t.TypeVar('_TCallable', bound=_t.Callable)
433
+
434
+
435
+ def accept_args_markers(plugin: _TCallable) -> _TCallable:
436
+ """
437
+ A decorator to mark a Jinja plugin as capable of handling `Marker` values for its top-level arguments.
438
+ Non-decorated plugin invocation is skipped when a top-level argument is a `Marker`, with the first such value substituted as the plugin result.
439
+ This ensures that only plugins which understand `Marker` instances for top-level arguments will encounter them.
440
+ """
441
+ plugin.accept_args_markers = True
442
+
443
+ return plugin
444
+
445
+
446
+ def accept_lazy_markers(plugin: _TCallable) -> _TCallable:
447
+ """
448
+ A decorator to mark a Jinja plugin as capable of handling `Marker` values retrieved from lazy containers.
449
+ Non-decorated plugins will trigger a `MarkerError` exception when attempting to retrieve a `Marker` from a lazy container.
450
+ This ensures that only plugins which understand lazy retrieval of `Marker` instances will encounter them.
451
+ """
452
+ plugin.accept_lazy_markers = True
453
+
454
+ return plugin
455
+
456
+
457
+ get_first_marker_arg = _jinja_common.get_first_marker_arg
ansible/utils/display.py CHANGED
@@ -51,13 +51,14 @@ from struct import unpack, pack
51
51
  from ansible import constants as C
52
52
  from ansible.constants import config
53
53
  from ansible.errors import AnsibleAssertionError, AnsiblePromptInterrupt, AnsiblePromptNoninteractive, AnsibleError
54
- from ansible._internal._errors import _utils
55
- from ansible.module_utils._internal import _ambient_context, _deprecator
54
+ from ansible._internal._errors import _error_utils, _error_factory
55
+ from ansible._internal import _event_formatting
56
+ from ansible.module_utils._internal import _ambient_context, _deprecator, _messages
56
57
  from ansible.module_utils.common.text.converters import to_bytes, to_text
58
+ from ansible.module_utils.datatag import deprecator_from_collection_name
57
59
  from ansible._internal._datatag._tags import TrustedAsTemplate
58
- from ansible.module_utils.common.messages import ErrorSummary, WarningSummary, DeprecationSummary, Detail, SummaryBase, PluginInfo
59
60
  from ansible.module_utils.six import text_type
60
- from ansible.module_utils._internal import _traceback
61
+ from ansible.module_utils._internal import _traceback, _errors
61
62
  from ansible.utils.color import stringc
62
63
  from ansible.utils.multiprocessing import context as multiprocessing_context
63
64
  from ansible.utils.singleton import Singleton
@@ -90,6 +91,9 @@ def _is_controller_traceback_enabled(event: _traceback.TracebackEvent) -> bool:
90
91
  if 'never' in flag_values:
91
92
  return False
92
93
 
94
+ if _traceback.TracebackEvent.DEPRECATED_VALUE.name.lower() in flag_values:
95
+ flag_values.add(_traceback.TracebackEvent.DEPRECATED.name.lower()) # DEPRECATED_VALUE implies DEPRECATED
96
+
93
97
  return event.name.lower() in flag_values
94
98
 
95
99
 
@@ -433,6 +437,10 @@ class Display(metaclass=Singleton):
433
437
  if not isinstance(msg, str):
434
438
  raise TypeError(f'Display message must be str, not: {msg.__class__.__name__}')
435
439
 
440
+ # Convert Windows newlines to Unix newlines.
441
+ # Some environments, such as Azure Pipelines, render `\r` as an additional `\n`.
442
+ msg = msg.replace('\r\n', '\n')
443
+
436
444
  nocolor = msg
437
445
 
438
446
  if not log_only:
@@ -568,7 +576,7 @@ class Display(metaclass=Singleton):
568
576
  version=version,
569
577
  removed=removed,
570
578
  date=date,
571
- deprecator=PluginInfo._from_collection_name(collection_name),
579
+ deprecator=deprecator_from_collection_name(collection_name),
572
580
  )
573
581
 
574
582
  if removed:
@@ -585,10 +593,10 @@ class Display(metaclass=Singleton):
585
593
  version: str | None,
586
594
  removed: bool = False,
587
595
  date: str | None,
588
- deprecator: PluginInfo | None,
596
+ deprecator: _messages.PluginInfo | None,
589
597
  ) -> str:
590
598
  """Internal use only. Return a deprecation message and help text for display."""
591
- # DTFIX-RELEASE: 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
599
+ # 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
592
600
 
593
601
  if removed:
594
602
  removal_fragment = 'This feature was removed'
@@ -598,13 +606,13 @@ class Display(metaclass=Singleton):
598
606
  if not deprecator or deprecator.type == _deprecator.INDETERMINATE_DEPRECATOR.type:
599
607
  collection = None
600
608
  plugin_fragment = ''
601
- elif deprecator.type == _deprecator.PluginInfo._COLLECTION_ONLY_TYPE:
609
+ elif deprecator.type == _deprecator._COLLECTION_ONLY_TYPE:
602
610
  collection = deprecator.resolved_name
603
611
  plugin_fragment = ''
604
612
  else:
605
613
  parts = deprecator.resolved_name.split('.')
606
614
  plugin_name = parts[-1]
607
- # DTFIX-RELEASE: normalize 'modules' -> 'module' before storing it so we can eliminate the normalization here
615
+ # DTFIX1: normalize 'modules' -> 'module' before storing it so we can eliminate the normalization here
608
616
  plugin_type = "module" if deprecator.type in ("module", "modules") else f'{deprecator.type} plugin'
609
617
 
610
618
  collection = '.'.join(parts[:2]) if len(parts) > 2 else None
@@ -668,7 +676,7 @@ class Display(metaclass=Singleton):
668
676
  date: str | None = None,
669
677
  collection_name: str | None = None,
670
678
  *,
671
- deprecator: PluginInfo | None = None,
679
+ deprecator: _messages.PluginInfo | None = None,
672
680
  help_text: str | None = None,
673
681
  obj: t.Any = None,
674
682
  ) -> None:
@@ -678,8 +686,8 @@ class Display(metaclass=Singleton):
678
686
  Specify `version` or `date`, but not both.
679
687
  If `date` is a string, it must be in the form `YYYY-MM-DD`.
680
688
  """
681
- # DTFIX-RELEASE: are there any deprecation calls where the feature is switching from enabled to disabled, rather than being removed entirely?
682
- # DTFIX-RELEASE: are there deprecated features which should going through deferred deprecation instead?
689
+ # DTFIX3: are there any deprecation calls where the feature is switching from enabled to disabled, rather than being removed entirely?
690
+ # DTFIX3: are there deprecated features which should going through deferred deprecation instead?
683
691
 
684
692
  _skip_stackwalk = True
685
693
 
@@ -691,6 +699,7 @@ class Display(metaclass=Singleton):
691
699
  help_text=help_text,
692
700
  obj=obj,
693
701
  deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
702
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.DEPRECATED),
694
703
  )
695
704
 
696
705
  def _deprecated_with_plugin_info(
@@ -702,7 +711,8 @@ class Display(metaclass=Singleton):
702
711
  date: str | None,
703
712
  help_text: str | None,
704
713
  obj: t.Any,
705
- deprecator: PluginInfo | None,
714
+ deprecator: _messages.PluginInfo | None,
715
+ formatted_traceback: str | None = None,
706
716
  ) -> None:
707
717
  """
708
718
  This is the internal pre-proxy half of the `deprecated` implementation.
@@ -721,23 +731,21 @@ class Display(metaclass=Singleton):
721
731
 
722
732
  raise AnsibleError(formatted_msg)
723
733
 
724
- if source_context := _utils.SourceContext.from_value(obj):
734
+ if source_context := _error_utils.SourceContext.from_value(obj):
725
735
  formatted_source_context = str(source_context)
726
736
  else:
727
737
  formatted_source_context = None
728
738
 
729
- deprecation = DeprecationSummary(
730
- details=(
731
- Detail(
732
- msg=msg,
733
- formatted_source_context=formatted_source_context,
734
- help_text=help_text,
735
- ),
739
+ deprecation = _messages.DeprecationSummary(
740
+ event=_messages.Event(
741
+ msg=msg,
742
+ formatted_source_context=formatted_source_context,
743
+ help_text=help_text,
744
+ formatted_traceback=formatted_traceback,
736
745
  ),
737
746
  version=version,
738
747
  date=date,
739
748
  deprecator=deprecator,
740
- formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
741
749
  )
742
750
 
743
751
  if warning_ctx := _DeferredWarningContext.current(optional=True):
@@ -747,7 +755,7 @@ class Display(metaclass=Singleton):
747
755
  self._deprecated(deprecation)
748
756
 
749
757
  @_proxy
750
- def _deprecated(self, warning: DeprecationSummary) -> None:
758
+ def _deprecated(self, warning: _messages.DeprecationSummary) -> None:
751
759
  """Internal implementation detail, use `deprecated` instead."""
752
760
 
753
761
  # This is the post-proxy half of the `deprecated` implementation.
@@ -758,10 +766,10 @@ class Display(metaclass=Singleton):
758
766
 
759
767
  self.warning('Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg.')
760
768
 
761
- msg = format_message(warning)
769
+ msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.DEPRECATED))
762
770
  msg = f'[DEPRECATION WARNING]: {msg}'
763
771
 
764
- # DTFIX-RELEASE: what should we do with wrap_message?
772
+ # DTFIX3: what should we do with wrap_message?
765
773
  msg = self._wrap_message(msg=msg, wrap_text=True)
766
774
 
767
775
  if self._deduplicate(msg, self._deprecations):
@@ -778,47 +786,46 @@ class Display(metaclass=Singleton):
778
786
  obj: t.Any = None
779
787
  ) -> None:
780
788
  """Display a warning message."""
789
+ _skip_stackwalk = True
781
790
 
782
791
  # This is the pre-proxy half of the `warning` implementation.
783
792
  # Any logic that must occur on workers needs to be implemented here.
784
793
 
785
- if source_context := _utils.SourceContext.from_value(obj):
794
+ if source_context := _error_utils.SourceContext.from_value(obj):
786
795
  formatted_source_context = str(source_context)
787
796
  else:
788
797
  formatted_source_context = None
789
798
 
790
- warning = WarningSummary(
791
- details=(
792
- Detail(
793
- msg=msg,
794
- help_text=help_text,
795
- formatted_source_context=formatted_source_context,
796
- ),
799
+ warning = _messages.WarningSummary(
800
+ event=_messages.Event(
801
+ msg=msg,
802
+ help_text=help_text,
803
+ formatted_source_context=formatted_source_context,
804
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
797
805
  ),
798
- formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.WARNING),
799
806
  )
800
807
 
801
808
  if warning_ctx := _DeferredWarningContext.current(optional=True):
802
809
  warning_ctx.capture(warning)
803
- # DTFIX-RELEASE: what to do about propagating wrap_text?
810
+ # DTFIX3: what to do about propagating wrap_text?
804
811
  return
805
812
 
806
813
  self._warning(warning, wrap_text=not formatted)
807
814
 
808
815
  @_proxy
809
- def _warning(self, warning: WarningSummary, wrap_text: bool) -> None:
816
+ def _warning(self, warning: _messages.WarningSummary, wrap_text: bool) -> None:
810
817
  """Internal implementation detail, use `warning` instead."""
811
818
 
812
819
  # This is the post-proxy half of the `warning` implementation.
813
820
  # Any logic that must occur in the primary controller process needs to be implemented here.
814
821
 
815
- msg = format_message(warning)
822
+ msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
816
823
  msg = f"[WARNING]: {msg}"
817
824
 
818
825
  if self._deduplicate(msg, self._warns):
819
826
  return
820
827
 
821
- # DTFIX-RELEASE: what should we do with wrap_message?
828
+ # DTFIX3: what should we do with wrap_message?
822
829
  msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
823
830
 
824
831
  self.display(msg, color=C.config.get_config_value('COLOR_WARN'), stderr=True, caplevel=-2)
@@ -870,17 +877,39 @@ class Display(metaclass=Singleton):
870
877
  (out, err) = cmd.communicate()
871
878
  self.display(u"%s\n" % to_text(out), color=color)
872
879
 
873
- def error_as_warning(self, msg: str | None, exception: BaseException) -> None:
880
+ def error_as_warning(
881
+ self,
882
+ msg: str | None,
883
+ exception: BaseException,
884
+ *,
885
+ help_text: str | None = None,
886
+ obj: t.Any = None,
887
+ ) -> None:
874
888
  """Display an exception as a warning."""
889
+ _skip_stackwalk = True
875
890
 
876
- error = _utils._create_error_summary(exception, _traceback.TracebackEvent.WARNING)
891
+ event = _error_factory.ControllerEventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
877
892
 
878
893
  if msg:
879
- error = dataclasses.replace(error, details=(Detail(msg=msg),) + error.details)
894
+ if source_context := _error_utils.SourceContext.from_value(obj):
895
+ formatted_source_context = str(source_context)
896
+ else:
897
+ formatted_source_context = None
898
+
899
+ event = _messages.Event(
900
+ msg=msg,
901
+ help_text=help_text,
902
+ formatted_source_context=formatted_source_context,
903
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
904
+ chain=_messages.EventChain(
905
+ msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
906
+ traceback_reason=_errors.TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING,
907
+ event=event,
908
+ ),
909
+ )
880
910
 
881
- warning = WarningSummary(
882
- details=error.details,
883
- formatted_traceback=error.formatted_traceback,
911
+ warning = _messages.WarningSummary(
912
+ event=event,
884
913
  )
885
914
 
886
915
  if warning_ctx := _DeferredWarningContext.current(optional=True):
@@ -891,32 +920,41 @@ class Display(metaclass=Singleton):
891
920
 
892
921
  def error(self, msg: str | BaseException, wrap_text: bool = True, stderr: bool = True) -> None:
893
922
  """Display an error message."""
923
+ _skip_stackwalk = True
894
924
 
895
925
  # This is the pre-proxy half of the `error` implementation.
896
926
  # Any logic that must occur on workers needs to be implemented here.
897
927
 
898
928
  if isinstance(msg, BaseException):
899
- error = _utils._create_error_summary(msg, _traceback.TracebackEvent.ERROR)
929
+ event = _error_factory.ControllerEventFactory.from_exception(msg, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR))
930
+
900
931
  wrap_text = False
901
932
  else:
902
- error = ErrorSummary(details=(Detail(msg=msg),), formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.ERROR))
933
+ event = _messages.Event(
934
+ msg=msg,
935
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.ERROR),
936
+ )
937
+
938
+ error = _messages.ErrorSummary(
939
+ event=event,
940
+ )
903
941
 
904
942
  self._error(error, wrap_text=wrap_text, stderr=stderr)
905
943
 
906
944
  @_proxy
907
- def _error(self, error: ErrorSummary, wrap_text: bool, stderr: bool) -> None:
945
+ def _error(self, error: _messages.ErrorSummary, wrap_text: bool, stderr: bool) -> None:
908
946
  """Internal implementation detail, use `error` instead."""
909
947
 
910
948
  # This is the post-proxy half of the `error` implementation.
911
949
  # Any logic that must occur in the primary controller process needs to be implemented here.
912
950
 
913
- msg = format_message(error)
951
+ msg = _format_message(error, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR))
914
952
  msg = f'[ERROR]: {msg}'
915
953
 
916
954
  if self._deduplicate(msg, self._errors):
917
955
  return
918
956
 
919
- # DTFIX-RELEASE: what should we do with wrap_message?
957
+ # DTFIX3: what should we do with wrap_message?
920
958
  msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
921
959
 
922
960
  self.display(msg, color=C.config.get_config_value('COLOR_ERROR'), stderr=stderr, caplevel=-1)
@@ -1146,9 +1184,9 @@ class _DeferredWarningContext(_ambient_context.AmbientContextBase):
1146
1184
 
1147
1185
  def __init__(self, *, variables: dict[str, object]) -> None:
1148
1186
  self._variables = variables # DTFIX-FUTURE: move this to an AmbientContext-derived TaskContext (once it exists)
1149
- self._deprecation_warnings: list[DeprecationSummary] = []
1150
- self._warnings: list[WarningSummary] = []
1151
- self._seen: set[WarningSummary] = set()
1187
+ self._deprecation_warnings: list[_messages.DeprecationSummary] = []
1188
+ self._warnings: list[_messages.WarningSummary] = []
1189
+ self._seen: set[_messages.WarningSummary] = set()
1152
1190
 
1153
1191
  @classmethod
1154
1192
  def deprecation_warnings_enabled(cls) -> bool:
@@ -1161,82 +1199,29 @@ class _DeferredWarningContext(_ambient_context.AmbientContextBase):
1161
1199
 
1162
1200
  return C.config.get_config_value('DEPRECATION_WARNINGS', variables=variables)
1163
1201
 
1164
- def capture(self, warning: WarningSummary) -> None:
1202
+ def capture(self, warning: _messages.WarningSummary) -> None:
1165
1203
  """Add the warning/deprecation to the context if it has not already been seen by this context."""
1166
1204
  if warning in self._seen:
1167
1205
  return
1168
1206
 
1169
1207
  self._seen.add(warning)
1170
1208
 
1171
- if isinstance(warning, DeprecationSummary):
1209
+ if isinstance(warning, _messages.DeprecationSummary):
1172
1210
  self._deprecation_warnings.append(warning)
1173
1211
  else:
1174
1212
  self._warnings.append(warning)
1175
1213
 
1176
- def get_warnings(self) -> list[WarningSummary]:
1214
+ def get_warnings(self) -> list[_messages.WarningSummary]:
1177
1215
  """Return a list of the captured non-deprecation warnings."""
1178
1216
  # DTFIX-FUTURE: return a read-only list proxy instead
1179
1217
  return self._warnings
1180
1218
 
1181
- def get_deprecation_warnings(self) -> list[DeprecationSummary]:
1219
+ def get_deprecation_warnings(self) -> list[_messages.DeprecationSummary]:
1182
1220
  """Return a list of the captured deprecation warnings."""
1183
1221
  # DTFIX-FUTURE: return a read-only list proxy instead
1184
1222
  return self._deprecation_warnings
1185
1223
 
1186
1224
 
1187
- def _format_error_details(details: t.Sequence[Detail], formatted_tb: str | None = None) -> str:
1188
- details = _utils._collapse_error_details(details)
1189
-
1190
- message_lines: list[str] = []
1191
-
1192
- if len(details) > 1:
1193
- message_lines.append(_utils._dedupe_and_concat_message_chain([md.msg for md in details]))
1194
- message_lines.append('')
1195
-
1196
- for idx, edc in enumerate(details):
1197
- if idx:
1198
- message_lines.extend((
1199
- '',
1200
- '<<< caused by >>>',
1201
- '',
1202
- ))
1203
-
1204
- message_lines.extend(_get_message_lines(edc.msg, edc.help_text, edc.formatted_source_context))
1205
-
1206
- message_lines = [f'{line}\n' for line in message_lines]
1207
-
1208
- if formatted_tb:
1209
- message_lines.append('\n')
1210
- message_lines.append(formatted_tb)
1211
-
1212
- msg = "".join(message_lines).strip()
1213
-
1214
- if '\n' in msg:
1215
- msg += '\n\n'
1216
- else:
1217
- msg += '\n'
1218
-
1219
- return msg
1220
-
1221
-
1222
- def _get_message_lines(message: str, help_text: str | None, formatted_source_context: str | None) -> list[str]:
1223
- """Return a list of error/warning message lines constructed from the given message, help text and source context."""
1224
-
1225
- if help_text and not formatted_source_context and '\n' not in message and '\n' not in help_text:
1226
- return [f'{message} {help_text}'] # prefer a single-line message with help text when there is no source context
1227
-
1228
- message_lines = [message]
1229
-
1230
- if formatted_source_context:
1231
- message_lines.append(formatted_source_context)
1232
-
1233
- if help_text:
1234
- message_lines.append('')
1235
- message_lines.append(help_text)
1236
-
1237
- return message_lines
1238
-
1239
-
1240
1225
  def _join_sentences(first: str | None, second: str | None) -> str:
1241
1226
  """Join two sentences together."""
1242
1227
  first = (first or '').strip()
@@ -1257,33 +1242,23 @@ def _join_sentences(first: str | None, second: str | None) -> str:
1257
1242
  return ' '.join((first, second))
1258
1243
 
1259
1244
 
1260
- def format_message(summary: SummaryBase) -> str:
1261
- details: c.Sequence[Detail] = summary.details
1262
-
1263
- if isinstance(summary, DeprecationSummary) and details:
1264
- # augment the first detail element for deprecations to include additional diagnostic info and help text
1265
- detail_list = list(details)
1266
- detail = detail_list[0]
1267
-
1268
- deprecation_msg = _display._get_deprecation_message_with_plugin_info(
1269
- msg=detail.msg,
1245
+ def _format_message(summary: _messages.SummaryBase, include_traceback: bool) -> str:
1246
+ if isinstance(summary, _messages.DeprecationSummary):
1247
+ deprecation_message = _display._get_deprecation_message_with_plugin_info(
1248
+ msg=summary.event.msg,
1270
1249
  version=summary.version,
1271
1250
  date=summary.date,
1272
1251
  deprecator=summary.deprecator,
1273
1252
  )
1274
1253
 
1275
- detail_list[0] = dataclasses.replace(
1276
- detail,
1277
- msg=deprecation_msg,
1278
- help_text=detail.help_text,
1279
- )
1280
-
1281
- details = detail_list
1254
+ event = dataclasses.replace(summary.event, msg=deprecation_message)
1255
+ else:
1256
+ event = summary.event
1282
1257
 
1283
- return _format_error_details(details, summary.formatted_traceback)
1258
+ return _event_formatting.format_event(event, include_traceback)
1284
1259
 
1285
1260
 
1286
- def _report_config_warnings(deprecator: PluginInfo) -> None:
1261
+ def _report_config_warnings(deprecator: _messages.PluginInfo) -> None:
1287
1262
  """Called by config to report warnings/deprecations collected during a config parse."""
1288
1263
  while config._errors:
1289
1264
  msg, exception = config._errors.pop()
ansible/utils/hashing.py CHANGED
@@ -59,7 +59,6 @@ def secure_hash(filename, hash_func=sha1):
59
59
  return digest.hexdigest()
60
60
 
61
61
 
62
- # The checksum algorithm must match with the algorithm in ShellModule.checksum() method
63
62
  checksum = secure_hash
64
63
  checksum_s = secure_hash_s
65
64