ansible-core 2.19.0b1__py3-none-any.whl → 2.19.0b3__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 (92) hide show
  1. ansible/_internal/_ansiballz.py +1 -4
  2. ansible/_internal/_collection_proxy.py +47 -0
  3. ansible/_internal/_errors/_handler.py +4 -4
  4. ansible/_internal/_json/__init__.py +47 -4
  5. ansible/_internal/_json/_profiles/_legacy.py +2 -3
  6. ansible/_internal/_templating/_datatag.py +3 -4
  7. ansible/_internal/_templating/_engine.py +6 -1
  8. ansible/_internal/_templating/_jinja_bits.py +4 -4
  9. ansible/_internal/_templating/_jinja_plugins.py +7 -17
  10. ansible/cli/__init__.py +12 -5
  11. ansible/cli/arguments/option_helpers.py +4 -1
  12. ansible/cli/doc.py +14 -8
  13. ansible/config/base.yml +17 -20
  14. ansible/config/manager.py +2 -2
  15. ansible/constants.py +0 -62
  16. ansible/errors/__init__.py +6 -2
  17. ansible/executor/module_common.py +11 -7
  18. ansible/executor/process/worker.py +31 -26
  19. ansible/executor/task_executor.py +38 -31
  20. ansible/executor/task_queue_manager.py +62 -52
  21. ansible/executor/task_result.py +168 -72
  22. ansible/galaxy/api.py +1 -1
  23. ansible/galaxy/collection/__init__.py +3 -3
  24. ansible/inventory/manager.py +2 -1
  25. ansible/module_utils/_internal/_ansiballz.py +4 -30
  26. ansible/module_utils/_internal/_datatag/_tags.py +3 -25
  27. ansible/module_utils/_internal/_deprecator.py +134 -0
  28. ansible/module_utils/_internal/_plugin_info.py +25 -0
  29. ansible/module_utils/_internal/_validation.py +14 -0
  30. ansible/module_utils/ansible_release.py +1 -1
  31. ansible/module_utils/basic.py +68 -23
  32. ansible/module_utils/common/arg_spec.py +8 -3
  33. ansible/module_utils/common/messages.py +40 -23
  34. ansible/module_utils/common/process.py +0 -1
  35. ansible/module_utils/common/respawn.py +0 -7
  36. ansible/module_utils/common/warnings.py +13 -13
  37. ansible/module_utils/datatag.py +13 -13
  38. ansible/modules/async_status.py +1 -1
  39. ansible/modules/dnf5.py +1 -1
  40. ansible/modules/get_url.py +1 -1
  41. ansible/parsing/utils/jsonify.py +40 -0
  42. ansible/parsing/yaml/objects.py +16 -5
  43. ansible/playbook/included_file.py +25 -12
  44. ansible/playbook/task.py +0 -2
  45. ansible/plugins/__init__.py +18 -8
  46. ansible/plugins/action/__init__.py +6 -14
  47. ansible/plugins/action/gather_facts.py +2 -4
  48. ansible/plugins/callback/__init__.py +173 -86
  49. ansible/plugins/callback/default.py +79 -79
  50. ansible/plugins/callback/junit.py +20 -19
  51. ansible/plugins/callback/minimal.py +17 -17
  52. ansible/plugins/callback/oneline.py +23 -16
  53. ansible/plugins/callback/tree.py +13 -6
  54. ansible/plugins/connection/local.py +1 -1
  55. ansible/plugins/connection/paramiko_ssh.py +9 -2
  56. ansible/plugins/doc_fragments/action_core.py +1 -1
  57. ansible/plugins/filter/core.py +12 -2
  58. ansible/plugins/inventory/__init__.py +2 -2
  59. ansible/plugins/loader.py +194 -130
  60. ansible/plugins/lookup/url.py +2 -2
  61. ansible/plugins/strategy/__init__.py +76 -82
  62. ansible/plugins/strategy/free.py +4 -4
  63. ansible/plugins/strategy/linear.py +11 -9
  64. ansible/plugins/test/core.py +1 -1
  65. ansible/release.py +1 -1
  66. ansible/template/__init__.py +8 -6
  67. ansible/utils/collection_loader/_collection_meta.py +5 -3
  68. ansible/utils/display.py +141 -79
  69. ansible/utils/py3compat.py +1 -7
  70. ansible/utils/ssh_functions.py +4 -1
  71. ansible/utils/vars.py +23 -0
  72. ansible/vars/clean.py +1 -1
  73. ansible/vars/manager.py +18 -27
  74. ansible/vars/plugins.py +4 -4
  75. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
  76. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +89 -85
  77. ansible_test/_internal/commands/sanity/pylint.py +1 -0
  78. ansible_test/_internal/docker_util.py +4 -3
  79. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +475 -0
  80. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
  81. ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
  82. ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
  83. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
  84. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
  85. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
  86. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
  87. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
  88. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
  89. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
  90. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
  91. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
  92. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
@@ -53,9 +53,7 @@ try:
53
53
  except ImportError:
54
54
  HAS_SYSLOG = False
55
55
 
56
- # deprecated: description='types.EllipsisType is available in Python 3.10+' python_version='3.9'
57
- if t.TYPE_CHECKING:
58
- from builtins import ellipsis
56
+ _UNSET = t.cast(t.Any, object())
59
57
 
60
58
  try:
61
59
  from systemd import journal, daemon as systemd_daemon
@@ -77,7 +75,7 @@ except ImportError:
77
75
  # Python2 & 3 way to get NoneType
78
76
  NoneType = type(None)
79
77
 
80
- from ._internal import _traceback, _errors, _debugging
78
+ from ._internal import _traceback, _errors, _debugging, _deprecator
81
79
 
82
80
  from .common.text.converters import (
83
81
  to_native,
@@ -341,7 +339,7 @@ def _load_params():
341
339
  except Exception as ex:
342
340
  raise Exception("Failed to decode JSON module parameters.") from ex
343
341
 
344
- if (ansible_module_args := params.get('ANSIBLE_MODULE_ARGS', ...)) is ...:
342
+ if (ansible_module_args := params.get('ANSIBLE_MODULE_ARGS', _UNSET)) is _UNSET:
345
343
  raise Exception("ANSIBLE_MODULE_ARGS not provided.")
346
344
 
347
345
  global _PARSED_MODULE_ARGS
@@ -511,16 +509,31 @@ class AnsibleModule(object):
511
509
  warn(warning)
512
510
  self.log('[WARNING] %s' % warning)
513
511
 
514
- def deprecate(self, msg, version=None, date=None, collection_name=None):
515
- if version is not None and date is not None:
516
- raise AssertionError("implementation error -- version and date must not both be set")
517
- deprecate(msg, version=version, date=date)
518
- # For compatibility, we accept that neither version nor date is set,
519
- # and treat that the same as if version would not have been set
520
- if date is not None:
521
- self.log('[DEPRECATION WARNING] %s %s' % (msg, date))
522
- else:
523
- self.log('[DEPRECATION WARNING] %s %s' % (msg, version))
512
+ def deprecate(
513
+ self,
514
+ msg: str,
515
+ version: str | None = None,
516
+ date: str | None = None,
517
+ collection_name: str | None = None,
518
+ *,
519
+ deprecator: _messages.PluginInfo | None = None,
520
+ help_text: str | None = None,
521
+ ) -> None:
522
+ """
523
+ Record a deprecation warning to be returned with the module result.
524
+ Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
525
+ Specify `version` or `date`, but not both.
526
+ If `date` is a string, it must be in the form `YYYY-MM-DD`.
527
+ """
528
+ _skip_stackwalk = True
529
+
530
+ deprecate( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
531
+ msg=msg,
532
+ version=version,
533
+ date=date,
534
+ deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
535
+ help_text=help_text,
536
+ )
524
537
 
525
538
  def load_file_common_arguments(self, params, path=None):
526
539
  """
@@ -1406,6 +1419,7 @@ class AnsibleModule(object):
1406
1419
  self.cleanup(path)
1407
1420
 
1408
1421
  def _return_formatted(self, kwargs):
1422
+ _skip_stackwalk = True
1409
1423
 
1410
1424
  self.add_path_info(kwargs)
1411
1425
 
@@ -1413,6 +1427,13 @@ class AnsibleModule(object):
1413
1427
  kwargs['invocation'] = {'module_args': self.params}
1414
1428
 
1415
1429
  if 'warnings' in kwargs:
1430
+ self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name
1431
+ msg='Passing `warnings` to `exit_json` or `fail_json` is deprecated.',
1432
+ version='2.23',
1433
+ help_text='Use `AnsibleModule.warn` instead.',
1434
+ deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,
1435
+ )
1436
+
1416
1437
  if isinstance(kwargs['warnings'], list):
1417
1438
  for w in kwargs['warnings']:
1418
1439
  self.warn(w)
@@ -1424,17 +1445,38 @@ class AnsibleModule(object):
1424
1445
  kwargs['warnings'] = warnings
1425
1446
 
1426
1447
  if 'deprecations' in kwargs:
1448
+ self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name
1449
+ msg='Passing `deprecations` to `exit_json` or `fail_json` is deprecated.',
1450
+ version='2.23',
1451
+ help_text='Use `AnsibleModule.deprecate` instead.',
1452
+ deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,
1453
+ )
1454
+
1427
1455
  if isinstance(kwargs['deprecations'], list):
1428
1456
  for d in kwargs['deprecations']:
1429
- if isinstance(d, SEQUENCETYPE) and len(d) == 2:
1430
- self.deprecate(d[0], version=d[1])
1457
+ if isinstance(d, (KeysView, Sequence)) and len(d) == 2:
1458
+ self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-invalid-deprecated-version
1459
+ msg=d[0],
1460
+ version=d[1],
1461
+ deprecator=_deprecator.get_best_deprecator(),
1462
+ )
1431
1463
  elif isinstance(d, Mapping):
1432
- self.deprecate(d['msg'], version=d.get('version'), date=d.get('date'),
1433
- collection_name=d.get('collection_name'))
1464
+ self.deprecate( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
1465
+ msg=d['msg'],
1466
+ version=d.get('version'),
1467
+ date=d.get('date'),
1468
+ deprecator=_deprecator.get_best_deprecator(collection_name=d.get('collection_name')),
1469
+ )
1434
1470
  else:
1435
- self.deprecate(d) # pylint: disable=ansible-deprecated-no-version
1471
+ self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-deprecated-no-version
1472
+ msg=d,
1473
+ deprecator=_deprecator.get_best_deprecator(),
1474
+ )
1436
1475
  else:
1437
- self.deprecate(kwargs['deprecations']) # pylint: disable=ansible-deprecated-no-version
1476
+ self.deprecate( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-deprecated-no-version
1477
+ msg=kwargs['deprecations'],
1478
+ deprecator=_deprecator.get_best_deprecator(),
1479
+ )
1438
1480
 
1439
1481
  deprecations = get_deprecations()
1440
1482
  if deprecations:
@@ -1454,12 +1496,13 @@ class AnsibleModule(object):
1454
1496
 
1455
1497
  def exit_json(self, **kwargs) -> t.NoReturn:
1456
1498
  """ return from the module, without error """
1499
+ _skip_stackwalk = True
1457
1500
 
1458
1501
  self.do_cleanup_files()
1459
1502
  self._return_formatted(kwargs)
1460
1503
  sys.exit(0)
1461
1504
 
1462
- def fail_json(self, msg: str, *, exception: BaseException | str | ellipsis | None = ..., **kwargs) -> t.NoReturn:
1505
+ def fail_json(self, msg: str, *, exception: BaseException | str | None = _UNSET, **kwargs) -> t.NoReturn:
1463
1506
  """
1464
1507
  Return from the module with an error message and optional exception/traceback detail.
1465
1508
  A traceback will only be included in the result if error traceback capturing has been enabled.
@@ -1475,6 +1518,8 @@ class AnsibleModule(object):
1475
1518
  When `exception` is not specified, a formatted traceback will be retrieved from the current exception.
1476
1519
  If no exception is pending, the current call stack will be used instead.
1477
1520
  """
1521
+ _skip_stackwalk = True
1522
+
1478
1523
  msg = str(msg) # coerce to str instead of raising an error due to an invalid type
1479
1524
 
1480
1525
  kwargs.update(
@@ -1498,7 +1543,7 @@ class AnsibleModule(object):
1498
1543
 
1499
1544
  if isinstance(exception, str):
1500
1545
  formatted_traceback = exception
1501
- elif exception is ... and (current_exception := t.cast(t.Optional[BaseException], sys.exc_info()[1])):
1546
+ elif exception is _UNSET and (current_exception := t.cast(t.Optional[BaseException], sys.exc_info()[1])):
1502
1547
  formatted_traceback = _traceback.maybe_extract_traceback(current_exception, _traceback.TracebackEvent.ERROR)
1503
1548
  else:
1504
1549
  formatted_traceback = _traceback.maybe_capture_traceback(_traceback.TracebackEvent.ERROR)
@@ -22,6 +22,7 @@ from ansible.module_utils.common.parameters import (
22
22
 
23
23
  from ansible.module_utils.common.text.converters import to_native
24
24
  from ansible.module_utils.common.warnings import deprecate, warn
25
+ from ansible.module_utils.common import messages as _messages
25
26
 
26
27
  from ansible.module_utils.common.validation import (
27
28
  check_mutually_exclusive,
@@ -300,9 +301,13 @@ class ModuleArgumentSpecValidator(ArgumentSpecValidator):
300
301
  result = super(ModuleArgumentSpecValidator, self).validate(parameters)
301
302
 
302
303
  for d in result._deprecations:
303
- deprecate(d['msg'],
304
- version=d.get('version'), date=d.get('date'),
305
- collection_name=d.get('collection_name'))
304
+ # DTFIX-FUTURE: pass an actual deprecator instead of one derived from collection_name
305
+ deprecate( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
306
+ msg=d['msg'],
307
+ version=d.get('version'),
308
+ date=d.get('date'),
309
+ deprecator=_messages.PluginInfo._from_collection_name(d.get('collection_name')),
310
+ )
306
311
 
307
312
  for w in result._warnings:
308
313
  warn('Both option {option} and its alias {alias} are set.'.format(option=w['option'], alias=w['alias']))
@@ -13,7 +13,7 @@ import dataclasses as _dataclasses
13
13
  # deprecated: description='typing.Self exists in Python 3.11+' python_version='3.10'
14
14
  from ..compat import typing as _t
15
15
 
16
- from ansible.module_utils._internal import _datatag
16
+ from ansible.module_utils._internal import _datatag, _validation
17
17
 
18
18
  if _sys.version_info >= (3, 10):
19
19
  # Using slots for reduced memory usage and improved performance.
@@ -27,13 +27,27 @@ else:
27
27
  class PluginInfo(_datatag.AnsibleSerializableDataclass):
28
28
  """Information about a loaded plugin."""
29
29
 
30
- requested_name: str
31
- """The plugin name as requested, before resolving, which may be partially or fully qualified."""
32
30
  resolved_name: str
33
31
  """The resolved canonical plugin name; always fully-qualified for collection plugins."""
34
32
  type: str
35
33
  """The plugin type."""
36
34
 
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
+
38
+ @classmethod
39
+ def _from_collection_name(cls, collection_name: str | None) -> _t.Self | None:
40
+ """Returns an instance with the special `collection` type to refer to a non-plugin or ambiguous caller within a collection."""
41
+ if not collection_name:
42
+ return None
43
+
44
+ _validation.validate_collection_name(collection_name)
45
+
46
+ return cls(
47
+ resolved_name=collection_name,
48
+ type=cls._COLLECTION_ONLY_TYPE,
49
+ )
50
+
37
51
 
38
52
  @_dataclasses.dataclass(**_dataclass_kwargs)
39
53
  class Detail(_datatag.AnsibleSerializableDataclass):
@@ -75,34 +89,37 @@ class WarningSummary(SummaryBase):
75
89
  class DeprecationSummary(WarningSummary):
76
90
  """Deprecation summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""
77
91
 
78
- version: _t.Optional[str] = None
79
- date: _t.Optional[str] = None
80
- plugin: _t.Optional[PluginInfo] = None
81
-
82
- @property
83
- def collection_name(self) -> _t.Optional[str]:
84
- if not self.plugin:
85
- return None
86
-
87
- parts = self.plugin.resolved_name.split('.')
88
-
89
- if len(parts) < 2:
90
- return None
91
-
92
- collection_name = '.'.join(parts[:2])
92
+ deprecator: _t.Optional[PluginInfo] = None
93
+ """
94
+ The identifier for the content which is being deprecated.
95
+ """
93
96
 
94
- # deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
95
- # from ansible.module_utils.datatag import deprecate_value
96
- # collection_name = deprecate_value(collection_name, 'The `collection_name` property is deprecated.', removal_version='2.27')
97
+ date: _t.Optional[str] = None
98
+ """
99
+ The date after which a new release of `deprecator` will remove the feature described by `msg`.
100
+ Ignored if `deprecator` is not provided.
101
+ """
97
102
 
98
- return collection_name
103
+ version: _t.Optional[str] = None
104
+ """
105
+ The version of `deprecator` which will remove the feature described by `msg`.
106
+ Ignored if `deprecator` is not provided.
107
+ Ignored if `date` is provided.
108
+ """
99
109
 
100
110
  def _as_simple_dict(self) -> _t.Dict[str, _t.Any]:
101
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
+
102
119
  result = self._as_dict()
103
120
  result.update(
104
121
  msg=self._format(),
105
- collection_name=self.collection_name,
122
+ collection_name=collection_name,
106
123
  )
107
124
 
108
125
  return result
@@ -29,7 +29,6 @@ def get_bin_path(arg, opt_dirs=None, required=None):
29
29
  deprecate(
30
30
  msg="The `required` parameter in `get_bin_path` API is deprecated.",
31
31
  version="2.21",
32
- collection_name="ansible.builtin",
33
32
  )
34
33
 
35
34
  paths = []
@@ -3,14 +3,12 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- import dataclasses
7
6
  import os
8
7
  import pathlib
9
8
  import subprocess
10
9
  import sys
11
10
  import typing as t
12
11
 
13
- from ansible.module_utils._internal import _plugin_exec_context
14
12
  from ansible.module_utils.common.text.converters import to_bytes
15
13
 
16
14
  _ANSIBLE_PARENT_PATH = pathlib.Path(__file__).parents[3]
@@ -99,7 +97,6 @@ if __name__ == '__main__':
99
97
 
100
98
  json_params = {json_params!r}
101
99
  profile = {profile!r}
102
- plugin_info_dict = {plugin_info_dict!r}
103
100
  module_fqn = {module_fqn!r}
104
101
  modlib_path = {modlib_path!r}
105
102
 
@@ -110,19 +107,15 @@ if __name__ == '__main__':
110
107
  _ansiballz.run_module(
111
108
  json_params=json_params,
112
109
  profile=profile,
113
- plugin_info_dict=plugin_info_dict,
114
110
  module_fqn=module_fqn,
115
111
  modlib_path=modlib_path,
116
112
  init_globals=dict(_respawned=True),
117
113
  )
118
114
  """
119
115
 
120
- plugin_info = _plugin_exec_context.PluginExecContext.get_current_plugin_info()
121
-
122
116
  respawn_code = respawn_code_template.format(
123
117
  json_params=basic._ANSIBLE_ARGS,
124
118
  profile=basic._ANSIBLE_PROFILE,
125
- plugin_info_dict=dataclasses.asdict(plugin_info),
126
119
  module_fqn=module_fqn,
127
120
  modlib_path=modlib_path,
128
121
  )
@@ -4,15 +4,12 @@
4
4
 
5
5
  from __future__ import annotations as _annotations
6
6
 
7
- import datetime as _datetime
8
7
  import typing as _t
9
8
 
10
- from ansible.module_utils._internal import _traceback, _plugin_exec_context
9
+ from ansible.module_utils._internal import _traceback, _deprecator
11
10
  from ansible.module_utils.common import messages as _messages
12
11
  from ansible.module_utils import _internal
13
12
 
14
- _UNSET = _t.cast(_t.Any, ...)
15
-
16
13
 
17
14
  def warn(warning: str) -> None:
18
15
  """Record a warning to be returned with the module result."""
@@ -28,22 +25,23 @@ def warn(warning: str) -> None:
28
25
  def deprecate(
29
26
  msg: str,
30
27
  version: str | None = None,
31
- date: str | _datetime.date | None = None,
32
- collection_name: str | None = _UNSET,
28
+ date: str | None = None,
29
+ collection_name: str | None = None,
33
30
  *,
31
+ deprecator: _messages.PluginInfo | None = None,
34
32
  help_text: str | None = None,
35
33
  obj: object | None = None,
36
34
  ) -> None:
37
35
  """
38
- Record a deprecation warning to be returned with the module result.
36
+ Record a deprecation warning.
39
37
  The `obj` argument is only useful in a controller context; it is ignored for target-side callers.
38
+ Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
39
+ Specify `version` or `date`, but not both.
40
+ If `date` is a string, it must be in the form `YYYY-MM-DD`.
40
41
  """
41
- if isinstance(date, _datetime.date):
42
- date = str(date)
42
+ _skip_stackwalk = True
43
43
 
44
- # deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
45
- # if collection_name is not _UNSET:
46
- # deprecate('The `collection_name` argument to `deprecate` is deprecated.', version='2.27')
44
+ deprecator = _deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name)
47
45
 
48
46
  if _internal.is_controller:
49
47
  _display = _internal.import_controller_module('ansible.utils.display').Display()
@@ -53,6 +51,8 @@ def deprecate(
53
51
  date=date,
54
52
  help_text=help_text,
55
53
  obj=obj,
54
+ # skip passing collection_name; get_best_deprecator already accounted for it when present
55
+ deprecator=deprecator,
56
56
  )
57
57
 
58
58
  return
@@ -64,7 +64,7 @@ def deprecate(
64
64
  formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
65
65
  version=version,
66
66
  date=date,
67
- plugin=_plugin_exec_context.PluginExecContext.get_current_plugin_info(),
67
+ deprecator=deprecator,
68
68
  )] = None
69
69
 
70
70
 
@@ -1,11 +1,11 @@
1
1
  """Public API for data tagging."""
2
2
  from __future__ import annotations as _annotations
3
3
 
4
- import datetime as _datetime
5
4
  import typing as _t
6
5
 
7
- from ._internal import _plugin_exec_context, _datatag
6
+ from ._internal import _datatag, _deprecator
8
7
  from ._internal._datatag import _tags
8
+ from .common import messages as _messages
9
9
 
10
10
  _T = _t.TypeVar('_T')
11
11
 
@@ -14,28 +14,28 @@ def deprecate_value(
14
14
  value: _T,
15
15
  msg: str,
16
16
  *,
17
+ version: str | None = None,
18
+ date: str | None = None,
19
+ collection_name: str | None = None,
20
+ deprecator: _messages.PluginInfo | None = None,
17
21
  help_text: str | None = None,
18
- removal_date: str | _datetime.date | None = None,
19
- removal_version: str | None = None,
20
22
  ) -> _T:
21
23
  """
22
24
  Return `value` tagged with the given deprecation details.
23
25
  The types `None` and `bool` cannot be deprecated and are returned unmodified.
24
26
  Raises a `TypeError` if `value` is not a supported type.
25
- If `removal_date` is a string, it must be in the form `YYYY-MM-DD`.
26
- This function is only supported in contexts where an Ansible plugin/module is executing.
27
+ Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
28
+ Specify `version` or `date`, but not both.
29
+ If `date` is provided, it should be in the form `YYYY-MM-DD`.
27
30
  """
28
- if isinstance(removal_date, str):
29
- # The `fromisoformat` method accepts other ISO 8601 formats than `YYYY-MM-DD` starting with Python 3.11.
30
- # That should be considered undocumented behavior of `deprecate_value` rather than an intentional feature.
31
- removal_date = _datetime.date.fromisoformat(removal_date)
31
+ _skip_stackwalk = True
32
32
 
33
33
  deprecated = _tags.Deprecated(
34
34
  msg=msg,
35
35
  help_text=help_text,
36
- removal_date=removal_date,
37
- removal_version=removal_version,
38
- plugin=_plugin_exec_context.PluginExecContext.get_current_plugin_info(),
36
+ date=date,
37
+ version=version,
38
+ deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
39
39
  )
40
40
 
41
41
  return deprecated.tag(value)
@@ -68,7 +68,7 @@ EXAMPLES = r"""
68
68
  ansible.builtin.async_status:
69
69
  jid: '{{ dnf_sleeper.ansible_job_id }}'
70
70
  register: job_result
71
- until: job_result.finished
71
+ until: job_result is finished
72
72
  retries: 100
73
73
  delay: 10
74
74
 
ansible/modules/dnf5.py CHANGED
@@ -797,7 +797,7 @@ class Dnf5Module(YumDnf):
797
797
  if self.module.check_mode:
798
798
  if results:
799
799
  msg = "Check mode: No changes made, but would have if not in check mode"
800
- else:
800
+ elif changed:
801
801
  transaction.download()
802
802
  if not self.download_only:
803
803
  transaction.set_description("ansible dnf5 module")
@@ -87,7 +87,7 @@ options:
87
87
  - 'If a checksum is passed to this parameter, the digest of the
88
88
  destination file will be calculated after it is downloaded to ensure
89
89
  its integrity and verify that the transfer completed successfully.
90
- Format: <algorithm>:<checksum|url>, for example C(checksum="sha256:D98291AC[...]B6DC7B97",
90
+ Format: <algorithm>:<checksum|url>, for example C(checksum="sha256:D98291AC[...]B6DC7B97"),
91
91
  C(checksum="sha256:http://example.com/path/sha256sum.txt").'
92
92
  - If you worry about portability, only the sha1 algorithm is available
93
93
  on all platforms and python versions.
@@ -0,0 +1,40 @@
1
+ # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
2
+ #
3
+ # This file is part of Ansible
4
+ #
5
+ # Ansible is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # Ansible is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+
22
+ from ansible.utils.display import Display
23
+
24
+ Display().deprecated(f'{__name__!r} is deprecated.', version='2.23', help_text='Call `json.dumps` directly instead.')
25
+
26
+
27
+ def jsonify(result, format=False):
28
+ """Format JSON output."""
29
+
30
+ if result is None:
31
+ return "{}"
32
+
33
+ indent = None
34
+ if format:
35
+ indent = 4
36
+
37
+ try:
38
+ return json.dumps(result, sort_keys=True, indent=indent, ensure_ascii=False)
39
+ except UnicodeDecodeError:
40
+ return json.dumps(result, sort_keys=True, indent=indent)
@@ -8,25 +8,36 @@ from ansible.module_utils._internal import _datatag
8
8
  from ansible.module_utils.common.text import converters as _converters
9
9
  from ansible.parsing import vault as _vault
10
10
 
11
+ _UNSET = _t.cast(_t.Any, object())
12
+
11
13
 
12
14
  class _AnsibleMapping(dict):
13
15
  """Backwards compatibility type."""
14
16
 
15
- def __new__(cls, value):
16
- return _datatag.AnsibleTagHelper.tag_copy(value, dict(value))
17
+ def __new__(cls, value=_UNSET, /, **kwargs):
18
+ if value is _UNSET:
19
+ return dict(**kwargs)
20
+
21
+ return _datatag.AnsibleTagHelper.tag_copy(value, dict(value, **kwargs))
17
22
 
18
23
 
19
24
  class _AnsibleUnicode(str):
20
25
  """Backwards compatibility type."""
21
26
 
22
- def __new__(cls, value):
23
- return _datatag.AnsibleTagHelper.tag_copy(value, str(value))
27
+ def __new__(cls, object=_UNSET, **kwargs):
28
+ if object is _UNSET:
29
+ return str(**kwargs)
30
+
31
+ return _datatag.AnsibleTagHelper.tag_copy(object, str(object, **kwargs))
24
32
 
25
33
 
26
34
  class _AnsibleSequence(list):
27
35
  """Backwards compatibility type."""
28
36
 
29
- def __new__(cls, value):
37
+ def __new__(cls, value=_UNSET, /):
38
+ if value is _UNSET:
39
+ return list()
40
+
30
41
  return _datatag.AnsibleTagHelper.tag_copy(value, list(value))
31
42
 
32
43
 
@@ -21,34 +21,42 @@ import os
21
21
 
22
22
  from ansible import constants as C
23
23
  from ansible.errors import AnsibleError
24
+ from ansible.executor.task_result import _RawTaskResult
25
+ from ansible.inventory.host import Host
24
26
  from ansible.module_utils.common.text.converters import to_text
27
+ from ansible.parsing.dataloader import DataLoader
25
28
  from ansible.playbook.handler import Handler
26
29
  from ansible.playbook.task_include import TaskInclude
27
30
  from ansible.playbook.role_include import IncludeRole
28
31
  from ansible._internal._templating._engine import TemplateEngine
29
32
  from ansible.utils.display import Display
33
+ from ansible.vars.manager import VariableManager
30
34
 
31
35
  display = Display()
32
36
 
33
37
 
34
38
  class IncludedFile:
35
39
 
36
- def __init__(self, filename, args, vars, task, is_role=False):
40
+ def __init__(self, filename, args, vars, task, is_role: bool = False) -> None:
37
41
  self._filename = filename
38
42
  self._args = args
39
43
  self._vars = vars
40
44
  self._task = task
41
- self._hosts = []
45
+ self._hosts: list[Host] = []
42
46
  self._is_role = is_role
43
- self._results = []
47
+ self._results: list[_RawTaskResult] = []
44
48
 
45
- def add_host(self, host):
49
+ def add_host(self, host: Host) -> None:
46
50
  if host not in self._hosts:
47
51
  self._hosts.append(host)
48
52
  return
53
+
49
54
  raise ValueError()
50
55
 
51
56
  def __eq__(self, other):
57
+ if not isinstance(other, IncludedFile):
58
+ return False
59
+
52
60
  return (other._filename == self._filename and
53
61
  other._args == self._args and
54
62
  other._vars == self._vars and
@@ -59,23 +67,28 @@ class IncludedFile:
59
67
  return "%s (args=%s vars=%s): %s" % (self._filename, self._args, self._vars, self._hosts)
60
68
 
61
69
  @staticmethod
62
- def process_include_results(results, iterator, loader, variable_manager):
63
- included_files = []
64
- task_vars_cache = {}
70
+ def process_include_results(
71
+ results: list[_RawTaskResult],
72
+ iterator,
73
+ loader: DataLoader,
74
+ variable_manager: VariableManager,
75
+ ) -> list[IncludedFile]:
76
+ included_files: list[IncludedFile] = []
77
+ task_vars_cache: dict[tuple, dict] = {}
65
78
 
66
79
  for res in results:
67
80
 
68
- original_host = res._host
69
- original_task = res._task
81
+ original_host = res.host
82
+ original_task = res.task
70
83
 
71
84
  if original_task.action in C._ACTION_ALL_INCLUDES:
72
85
 
73
86
  if original_task.loop:
74
- if 'results' not in res._result:
87
+ if 'results' not in res._return_data:
75
88
  continue
76
- include_results = res._result['results']
89
+ include_results = res._loop_results
77
90
  else:
78
- include_results = [res._result]
91
+ include_results = [res._return_data]
79
92
 
80
93
  for include_result in include_results:
81
94
  # if the task result was skipped or failed, continue