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
@@ -26,6 +26,7 @@ import sys
26
26
  import threading
27
27
  import time
28
28
  import typing as t
29
+ import collections.abc as _c
29
30
 
30
31
  from collections import deque
31
32
 
@@ -35,13 +36,13 @@ from ansible import context
35
36
  from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleParserError, AnsibleTemplateError
36
37
  from ansible.executor.play_iterator import IteratingStates, PlayIterator
37
38
  from ansible.executor.process.worker import WorkerProcess
38
- from ansible.executor.task_result import TaskResult
39
+ from ansible.executor.task_result import _RawTaskResult, _WireTaskResult
39
40
  from ansible.executor.task_queue_manager import CallbackSend, DisplaySend, PromptSend, TaskQueueManager
40
- from ansible.module_utils.six import string_types
41
41
  from ansible.module_utils.common.text.converters import to_text
42
42
  from ansible.module_utils.connection import Connection, ConnectionError
43
43
  from ansible.playbook.handler import Handler
44
44
  from ansible.playbook.helpers import load_list_of_blocks
45
+ from ansible.playbook.included_file import IncludedFile
45
46
  from ansible.playbook.task import Task
46
47
  from ansible.playbook.task_include import TaskInclude
47
48
  from ansible.plugins import loader as plugin_loader
@@ -89,7 +90,9 @@ def _get_item_vars(result, task):
89
90
  return item_vars
90
91
 
91
92
 
92
- def results_thread_main(strategy):
93
+ def results_thread_main(strategy: StrategyBase) -> None:
94
+ value: object
95
+
93
96
  while True:
94
97
  try:
95
98
  result = strategy._final_q.get()
@@ -99,13 +102,10 @@ def results_thread_main(strategy):
99
102
  dmethod = getattr(display, result.method)
100
103
  dmethod(*result.args, **result.kwargs)
101
104
  elif isinstance(result, CallbackSend):
102
- for arg in result.args:
103
- if isinstance(arg, TaskResult):
104
- strategy.normalize_task_result(arg)
105
- break
106
- strategy._tqm.send_callback(result.method_name, *result.args, **result.kwargs)
107
- elif isinstance(result, TaskResult):
108
- strategy.normalize_task_result(result)
105
+ task_result = strategy._convert_wire_task_result_to_raw(result.wire_task_result)
106
+ strategy._tqm.send_callback(result.method_name, task_result)
107
+ elif isinstance(result, _WireTaskResult):
108
+ result = strategy._convert_wire_task_result_to_raw(result)
109
109
  with strategy._results_lock:
110
110
  strategy._results.append(result)
111
111
  elif isinstance(result, PromptSend):
@@ -137,7 +137,7 @@ def results_thread_main(strategy):
137
137
  def debug_closure(func):
138
138
  """Closure to wrap ``StrategyBase._process_pending_results`` and invoke the task debugger"""
139
139
  @functools.wraps(func)
140
- def inner(self, iterator, one_pass=False, max_passes=None):
140
+ def inner(self, iterator: PlayIterator, one_pass: bool = False, max_passes: int | None = None) -> list[_RawTaskResult]:
141
141
  status_to_stats_map = (
142
142
  ('is_failed', 'failures'),
143
143
  ('is_unreachable', 'dark'),
@@ -148,12 +148,12 @@ def debug_closure(func):
148
148
  # We don't know the host yet, copy the previous states, for lookup after we process new results
149
149
  prev_host_states = iterator.host_states.copy()
150
150
 
151
- results = func(self, iterator, one_pass=one_pass, max_passes=max_passes)
152
- _processed_results = []
151
+ results: list[_RawTaskResult] = func(self, iterator, one_pass=one_pass, max_passes=max_passes)
152
+ _processed_results: list[_RawTaskResult] = []
153
153
 
154
154
  for result in results:
155
- task = result._task
156
- host = result._host
155
+ task = result.task
156
+ host = result.host
157
157
  _queued_task_args = self._queued_task_cache.pop((host.name, task._uuid), None)
158
158
  task_vars = _queued_task_args['task_vars']
159
159
  play_context = _queued_task_args['play_context']
@@ -239,7 +239,7 @@ class StrategyBase:
239
239
  # outstanding tasks still in queue
240
240
  self._blocked_hosts: dict[str, bool] = dict()
241
241
 
242
- self._results: deque[TaskResult] = deque()
242
+ self._results: deque[_RawTaskResult] = deque()
243
243
  self._results_lock = threading.Condition(threading.Lock())
244
244
 
245
245
  # create the result processing thread for reading results in the background
@@ -249,7 +249,7 @@ class StrategyBase:
249
249
 
250
250
  # holds the list of active (persistent) connections to be shutdown at
251
251
  # play completion
252
- self._active_connections: dict[str, str] = dict()
252
+ self._active_connections: dict[Host, str] = dict()
253
253
 
254
254
  # Caches for get_host calls, to avoid calling excessively
255
255
  # These values should be set at the top of the ``run`` method of each
@@ -447,39 +447,33 @@ class StrategyBase:
447
447
  for target_host in host_list:
448
448
  _set_host_facts(target_host, always_facts)
449
449
 
450
- def normalize_task_result(self, task_result):
451
- """Normalize a TaskResult to reference actual Host and Task objects
452
- when only given the ``Host.name``, or the ``Task._uuid``
453
-
454
- Only the ``Host.name`` and ``Task._uuid`` are commonly sent back from
455
- the ``TaskExecutor`` or ``WorkerProcess`` due to performance concerns
450
+ def _convert_wire_task_result_to_raw(self, wire_task_result: _WireTaskResult) -> _RawTaskResult:
451
+ """Return a `_RawTaskResult` created from a `_WireTaskResult`."""
452
+ host = self._inventory.get_host(wire_task_result.host_name)
453
+ queue_cache_entry = (host.name, wire_task_result.task_uuid)
456
454
 
457
- Mutates the original object
458
- """
459
-
460
- if isinstance(task_result._host, string_types):
461
- # If the value is a string, it is ``Host.name``
462
- task_result._host = self._inventory.get_host(to_text(task_result._host))
455
+ try:
456
+ found_task = self._queued_task_cache[queue_cache_entry]['task']
457
+ except KeyError:
458
+ # This should only happen due to an implicit task created by the
459
+ # TaskExecutor, restrict this behavior to the explicit use case
460
+ # of an implicit async_status task
461
+ if wire_task_result.task_fields.get('action') != 'async_status':
462
+ raise
463
+
464
+ task = Task()
465
+ else:
466
+ task = found_task.copy(exclude_parent=True, exclude_tasks=True)
467
+ task._parent = found_task._parent
463
468
 
464
- if isinstance(task_result._task, string_types):
465
- # If the value is a string, it is ``Task._uuid``
466
- queue_cache_entry = (task_result._host.name, task_result._task)
467
- try:
468
- found_task = self._queued_task_cache[queue_cache_entry]['task']
469
- except KeyError:
470
- # This should only happen due to an implicit task created by the
471
- # TaskExecutor, restrict this behavior to the explicit use case
472
- # of an implicit async_status task
473
- if task_result._task_fields.get('action') != 'async_status':
474
- raise
475
- original_task = Task()
476
- else:
477
- original_task = found_task.copy(exclude_parent=True, exclude_tasks=True)
478
- original_task._parent = found_task._parent
479
- original_task.from_attrs(task_result._task_fields)
480
- task_result._task = original_task
469
+ task.from_attrs(wire_task_result.task_fields)
481
470
 
482
- return task_result
471
+ return _RawTaskResult(
472
+ host=host,
473
+ task=task,
474
+ return_data=wire_task_result.return_data,
475
+ task_fields=wire_task_result.task_fields,
476
+ )
483
477
 
484
478
  def search_handlers_by_notification(self, notification: str, iterator: PlayIterator) -> t.Generator[Handler, None, None]:
485
479
  handlers = [h for b in reversed(iterator._play.handlers) for h in b.block]
@@ -537,7 +531,7 @@ class StrategyBase:
537
531
  yield handler
538
532
 
539
533
  @debug_closure
540
- def _process_pending_results(self, iterator: PlayIterator, one_pass: bool = False, max_passes: int | None = None) -> list[TaskResult]:
534
+ def _process_pending_results(self, iterator: PlayIterator, one_pass: bool = False, max_passes: int | None = None) -> list[_RawTaskResult]:
541
535
  """
542
536
  Reads results off the final queue and takes appropriate action
543
537
  based on the result (executing callbacks, updating state, etc.).
@@ -553,8 +547,8 @@ class StrategyBase:
553
547
  finally:
554
548
  self._results_lock.release()
555
549
 
556
- original_host = task_result._host
557
- original_task: Task = task_result._task
550
+ original_host = task_result.host
551
+ original_task: Task = task_result.task
558
552
 
559
553
  # all host status messages contain 2 entries: (msg, task_result)
560
554
  role_ran = False
@@ -588,7 +582,7 @@ class StrategyBase:
588
582
  original_host.name,
589
583
  dict(
590
584
  ansible_failed_task=original_task.serialize(),
591
- ansible_failed_result=task_result._result,
585
+ ansible_failed_result=task_result._return_data,
592
586
  ),
593
587
  )
594
588
  else:
@@ -596,7 +590,7 @@ class StrategyBase:
596
590
  else:
597
591
  self._tqm._stats.increment('ok', original_host.name)
598
592
  self._tqm._stats.increment('ignored', original_host.name)
599
- if 'changed' in task_result._result and task_result._result['changed']:
593
+ if task_result.is_changed():
600
594
  self._tqm._stats.increment('changed', original_host.name)
601
595
  self._tqm.send_callback('v2_runner_on_failed', task_result, ignore_errors=ignore_errors)
602
596
  elif task_result.is_unreachable():
@@ -618,9 +612,9 @@ class StrategyBase:
618
612
  if original_task.loop:
619
613
  # this task had a loop, and has more than one result, so
620
614
  # loop over all of them instead of a single result
621
- result_items = task_result._result.get('results', [])
615
+ result_items = task_result._loop_results
622
616
  else:
623
- result_items = [task_result._result]
617
+ result_items = [task_result._return_data]
624
618
 
625
619
  for result_item in result_items:
626
620
  if '_ansible_notify' in result_item and task_result.is_changed():
@@ -665,7 +659,7 @@ class StrategyBase:
665
659
 
666
660
  if 'add_host' in result_item or 'add_group' in result_item:
667
661
  item_vars = _get_item_vars(result_item, original_task)
668
- found_task_vars = self._queued_task_cache.get((original_host.name, task_result._task._uuid))['task_vars']
662
+ found_task_vars = self._queued_task_cache.get((original_host.name, task_result.task._uuid))['task_vars']
669
663
  if item_vars:
670
664
  all_task_vars = combine_vars(found_task_vars, item_vars)
671
665
  else:
@@ -680,17 +674,17 @@ class StrategyBase:
680
674
  original_task._resolve_conditional(original_task.failed_when, all_task_vars))
681
675
 
682
676
  if original_task.loop or original_task.loop_with:
683
- new_item_result = TaskResult(
684
- task_result._host,
685
- task_result._task,
677
+ new_item_result = _RawTaskResult(
678
+ task_result.host,
679
+ task_result.task,
686
680
  result_item,
687
- task_result._task_fields,
681
+ task_result.task_fields,
688
682
  )
689
683
  self._tqm.send_callback('v2_runner_item_on_ok', new_item_result)
690
684
  if result_item.get('changed', False):
691
- task_result._result['changed'] = True
685
+ task_result._return_data['changed'] = True
692
686
  if result_item.get('failed', False):
693
- task_result._result['failed'] = True
687
+ task_result._return_data['failed'] = True
694
688
 
695
689
  if 'ansible_facts' in result_item and original_task.action not in C._ACTION_DEBUG:
696
690
  # if delegated fact and we are delegating facts, we need to change target host for them
@@ -738,13 +732,13 @@ class StrategyBase:
738
732
  else:
739
733
  self._tqm._stats.set_custom_stats(k, data[k], myhost)
740
734
 
741
- if 'diff' in task_result._result:
735
+ if 'diff' in task_result._return_data:
742
736
  if self._diff or getattr(original_task, 'diff', False):
743
737
  self._tqm.send_callback('v2_on_file_diff', task_result)
744
738
 
745
739
  if not isinstance(original_task, TaskInclude):
746
740
  self._tqm._stats.increment('ok', original_host.name)
747
- if 'changed' in task_result._result and task_result._result['changed']:
741
+ if task_result.is_changed():
748
742
  self._tqm._stats.increment('changed', original_host.name)
749
743
 
750
744
  # finally, send the ok for this task
@@ -754,7 +748,7 @@ class StrategyBase:
754
748
  if original_task.register:
755
749
  host_list = self.get_task_hosts(iterator, original_host, original_task)
756
750
 
757
- clean_copy = strip_internal_keys(module_response_deepcopy(task_result._result))
751
+ clean_copy = strip_internal_keys(module_response_deepcopy(task_result._return_data))
758
752
  if 'invocation' in clean_copy:
759
753
  del clean_copy['invocation']
760
754
 
@@ -805,7 +799,7 @@ class StrategyBase:
805
799
 
806
800
  return ret_results
807
801
 
808
- def _copy_included_file(self, included_file):
802
+ def _copy_included_file(self, included_file: IncludedFile) -> IncludedFile:
809
803
  """
810
804
  A proven safe and performant way to create a copy of an included file
811
805
  """
@@ -818,7 +812,7 @@ class StrategyBase:
818
812
 
819
813
  return ti_copy
820
814
 
821
- def _load_included_file(self, included_file, iterator, is_handler=False, handle_stats_and_callbacks=True):
815
+ def _load_included_file(self, included_file: IncludedFile, iterator, is_handler=False, handle_stats_and_callbacks=True):
822
816
  """
823
817
  Loads an included YAML file of tasks, applying the optional set of variables.
824
818
 
@@ -828,12 +822,12 @@ class StrategyBase:
828
822
  """
829
823
  if handle_stats_and_callbacks:
830
824
  display.deprecated(
831
- "Reporting play recap stats and running callbacks functionality for "
832
- "``include_tasks`` in ``StrategyBase._load_included_file`` is deprecated. "
833
- "See ``https://github.com/ansible/ansible/pull/79260`` for guidance on how to "
834
- "move the reporting into specific strategy plugins to account for "
835
- "``include_role`` tasks as well.",
836
- version="2.21"
825
+ msg="Reporting play recap stats and running callbacks functionality for "
826
+ "``include_tasks`` in ``StrategyBase._load_included_file`` is deprecated. "
827
+ "See ``https://github.com/ansible/ansible/pull/79260`` for guidance on how to "
828
+ "move the reporting into specific strategy plugins to account for "
829
+ "``include_role`` tasks as well.",
830
+ version="2.21",
837
831
  )
838
832
  display.debug("loading included file: %s" % included_file._filename)
839
833
  try:
@@ -865,11 +859,11 @@ class StrategyBase:
865
859
  else:
866
860
  reason = to_text(e)
867
861
  if handle_stats_and_callbacks:
868
- for r in included_file._results:
869
- r._result['failed'] = True
862
+ for tr in included_file._results:
863
+ tr._return_data['failed'] = True
870
864
 
871
865
  for host in included_file._hosts:
872
- tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason))
866
+ tr = _RawTaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason), task_fields={})
873
867
  self._tqm._stats.increment('failures', host.name)
874
868
  self._tqm.send_callback('v2_runner_on_failed', tr)
875
869
  raise AnsibleError(reason) from e
@@ -905,7 +899,7 @@ class StrategyBase:
905
899
  def _cond_not_supported_warn(self, task_name):
906
900
  display.warning("%s task does not support when conditional" % task_name)
907
901
 
908
- def _execute_meta(self, task: Task, play_context, iterator, target_host):
902
+ def _execute_meta(self, task: Task, play_context, iterator, target_host: Host):
909
903
  task.resolved_action = 'ansible.builtin.meta' # _post_validate_args is never called for meta actions, so resolved_action hasn't been set
910
904
 
911
905
  # meta tasks store their args in the _raw_params field of args,
@@ -1083,7 +1077,7 @@ class StrategyBase:
1083
1077
  else:
1084
1078
  display.vv(f"META: {header}")
1085
1079
 
1086
- res = TaskResult(target_host, task, result)
1080
+ res = _RawTaskResult(target_host, task, result, {})
1087
1081
  if skipped:
1088
1082
  self._tqm.send_callback('v2_runner_on_skipped', res)
1089
1083
  return [res]
@@ -1103,14 +1097,14 @@ class StrategyBase:
1103
1097
  hosts_left.append(self._inventory.get_host(host))
1104
1098
  return hosts_left
1105
1099
 
1106
- def update_active_connections(self, results):
1100
+ def update_active_connections(self, results: _c.Iterable[_RawTaskResult]) -> None:
1107
1101
  """ updates the current active persistent connections """
1108
1102
  for r in results:
1109
- if 'args' in r._task_fields:
1110
- socket_path = r._task_fields['args'].get('_ansible_socket')
1103
+ if 'args' in r.task_fields:
1104
+ socket_path = r.task_fields['args'].get('_ansible_socket')
1111
1105
  if socket_path:
1112
- if r._host not in self._active_connections:
1113
- self._active_connections[r._host] = socket_path
1106
+ if r.host not in self._active_connections:
1107
+ self._active_connections[r.host] = socket_path
1114
1108
 
1115
1109
 
1116
1110
  class NextAction(object):
@@ -252,11 +252,11 @@ class StrategyModule(StrategyBase):
252
252
  # FIXME: send the error to the callback; don't directly write to display here
253
253
  display.error(ex)
254
254
  for r in included_file._results:
255
- r._result['failed'] = True
256
- r._result['reason'] = str(ex)
257
- self._tqm._stats.increment('failures', r._host.name)
255
+ r._return_data['failed'] = True
256
+ r._return_data['reason'] = str(ex)
257
+ self._tqm._stats.increment('failures', r.host.name)
258
258
  self._tqm.send_callback('v2_runner_on_failed', r)
259
- failed_includes_hosts.add(r._host)
259
+ failed_includes_hosts.add(r.host)
260
260
  continue
261
261
  else:
262
262
  # since we skip incrementing the stats when the task result is
@@ -40,6 +40,8 @@ from ansible.utils.display import Display
40
40
  from ansible.inventory.host import Host
41
41
  from ansible.playbook.task import Task
42
42
  from ansible.executor.play_iterator import PlayIterator
43
+ from ansible.playbook.play_context import PlayContext
44
+ from ansible.executor import task_result as _task_result
43
45
 
44
46
  display = Display()
45
47
 
@@ -92,7 +94,7 @@ class StrategyModule(StrategyBase):
92
94
 
93
95
  return host_tasks
94
96
 
95
- def run(self, iterator, play_context):
97
+ def run(self, iterator, play_context: PlayContext): # type: ignore[override]
96
98
  """
97
99
  The linear strategy is simple - get the next task and queue
98
100
  it for all hosts, then wait for the queue to drain before
@@ -100,7 +102,7 @@ class StrategyModule(StrategyBase):
100
102
  """
101
103
 
102
104
  # iterate over each task, while there is one left to run
103
- result = self._tqm.RUN_OK
105
+ result = int(self._tqm.RUN_OK)
104
106
  work_to_do = True
105
107
 
106
108
  self._set_hosts_cache(iterator._play)
@@ -125,7 +127,7 @@ class StrategyModule(StrategyBase):
125
127
  # flag set if task is set to any_errors_fatal
126
128
  any_errors_fatal = False
127
129
 
128
- results = []
130
+ results: list[_task_result._RawTaskResult] = []
129
131
  for (host, task) in host_tasks:
130
132
  if self._tqm._terminated:
131
133
  break
@@ -285,11 +287,11 @@ class StrategyModule(StrategyBase):
285
287
  # FIXME: send the error to the callback; don't directly write to display here
286
288
  display.error(ex)
287
289
  for r in included_file._results:
288
- r._result['failed'] = True
289
- r._result['reason'] = str(ex)
290
- self._tqm._stats.increment('failures', r._host.name)
290
+ r._return_data['failed'] = True
291
+ r._return_data['reason'] = str(ex)
292
+ self._tqm._stats.increment('failures', r.host.name)
291
293
  self._tqm.send_callback('v2_runner_on_failed', r)
292
- failed_includes_hosts.add(r._host)
294
+ failed_includes_hosts.add(r.host)
293
295
  else:
294
296
  # since we skip incrementing the stats when the task result is
295
297
  # first processed, we do so now for each host in the list
@@ -320,9 +322,9 @@ class StrategyModule(StrategyBase):
320
322
  unreachable_hosts = []
321
323
  for res in results:
322
324
  if res.is_failed():
323
- failed_hosts.append(res._host.name)
325
+ failed_hosts.append(res.host.name)
324
326
  elif res.is_unreachable():
325
- unreachable_hosts.append(res._host.name)
327
+ unreachable_hosts.append(res.host.name)
326
328
 
327
329
  if any_errors_fatal and (failed_hosts or unreachable_hosts):
328
330
  for host in hosts_left:
@@ -49,7 +49,7 @@ 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 result['timedout'].get('period', False)
52
+ return result.get('timedout', False) and bool(result['timedout'].get('period', False))
53
53
 
54
54
 
55
55
  def failed(result):
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b1'
20
+ __version__ = '2.19.0b3'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -28,7 +28,7 @@ if _t.TYPE_CHECKING: # pragma: nocover
28
28
 
29
29
 
30
30
  _display: _t.Final[_Display] = _Display()
31
- _UNSET = _t.cast(_t.Any, ...)
31
+ _UNSET = _t.cast(_t.Any, object())
32
32
  _TTrustable = _t.TypeVar('_TTrustable', bound=str | _io.IOBase | _t.TextIO | _t.BinaryIO)
33
33
  _TRUSTABLE_TYPES = (str, _io.IOBase)
34
34
 
@@ -171,7 +171,8 @@ class Templar:
171
171
  variables=self._engine._variables if available_variables is None else available_variables,
172
172
  )
173
173
 
174
- templar._overrides = self._overrides.merge(context_overrides)
174
+ # backward compatibility: filter out None values from overrides, even though it is a valid value for some of them
175
+ templar._overrides = self._overrides.merge({key: value for key, value in context_overrides.items() if value is not None})
175
176
 
176
177
  if searchpath is not None:
177
178
  templar._engine.environment.loader.searchpath = searchpath
@@ -198,7 +199,7 @@ class Templar:
198
199
  available_variables=self._engine,
199
200
  )
200
201
 
201
- kwargs = dict(
202
+ target_args = dict(
202
203
  searchpath=searchpath,
203
204
  available_variables=available_variables,
204
205
  )
@@ -207,13 +208,14 @@ class Templar:
207
208
  previous_overrides = self._overrides
208
209
 
209
210
  try:
210
- for key, value in kwargs.items():
211
+ for key, value in target_args.items():
211
212
  if value is not None:
212
213
  target = targets[key]
213
214
  original[key] = getattr(target, key)
214
215
  setattr(target, key, value)
215
216
 
216
- self._overrides = self._overrides.merge(context_overrides)
217
+ # backward compatibility: filter out None values from overrides, even though it is a valid value for some of them
218
+ self._overrides = self._overrides.merge({key: value for key, value in context_overrides.items() if value is not None})
217
219
 
218
220
  yield
219
221
  finally:
@@ -386,7 +388,7 @@ def generate_ansible_template_vars(path: str, fullpath: str | None = None, dest_
386
388
  value=ansible_managed,
387
389
  msg="The `ansible_managed` variable is deprecated.",
388
390
  help_text="Define and use a custom variable instead.",
389
- removal_version='2.23',
391
+ version='2.23',
390
392
  )
391
393
 
392
394
  temp_vars = dict(
@@ -24,11 +24,13 @@ def _meta_yml_to_dict(yaml_string_data: bytes | str, content_id):
24
24
  import yaml
25
25
 
26
26
  try:
27
- from yaml import CSafeLoader as SafeLoader
27
+ from yaml import CBaseLoader as BaseLoader
28
28
  except (ImportError, AttributeError):
29
- from yaml import SafeLoader # type: ignore[assignment]
29
+ from yaml import BaseLoader # type: ignore[assignment]
30
30
 
31
- routing_dict = yaml.load(yaml_string_data, Loader=SafeLoader)
31
+ # Using BaseLoader ensures that all scalars are strings.
32
+ # Doing so avoids parsing unquoted versions as floats, dates as datetime.date, etc.
33
+ routing_dict = yaml.load(yaml_string_data, Loader=BaseLoader)
32
34
  if not routing_dict:
33
35
  routing_dict = {}
34
36
  if not isinstance(routing_dict, Mapping):