ansible-core 2.19.0b2__py3-none-any.whl → 2.19.0b4__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 (85) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_ansiballz.py +1 -4
  3. ansible/_internal/_json/__init__.py +1 -1
  4. ansible/_internal/_templating/_datatag.py +3 -4
  5. ansible/_internal/_templating/_engine.py +6 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +2 -6
  7. ansible/_internal/_testing.py +26 -0
  8. ansible/cli/__init__.py +3 -2
  9. ansible/cli/arguments/option_helpers.py +10 -3
  10. ansible/cli/doc.py +0 -1
  11. ansible/config/base.yml +5 -23
  12. ansible/config/manager.py +144 -103
  13. ansible/constants.py +1 -63
  14. ansible/errors/__init__.py +6 -2
  15. ansible/executor/module_common.py +11 -7
  16. ansible/executor/task_executor.py +6 -8
  17. ansible/galaxy/api.py +1 -1
  18. ansible/galaxy/collection/__init__.py +3 -3
  19. ansible/inventory/manager.py +1 -0
  20. ansible/module_utils/_internal/_ansiballz.py +4 -30
  21. ansible/module_utils/_internal/_datatag/_tags.py +3 -25
  22. ansible/module_utils/_internal/_deprecator.py +134 -0
  23. ansible/module_utils/_internal/_plugin_info.py +25 -0
  24. ansible/module_utils/_internal/_validation.py +14 -0
  25. ansible/module_utils/ansible_release.py +1 -1
  26. ansible/module_utils/basic.py +64 -17
  27. ansible/module_utils/common/arg_spec.py +8 -3
  28. ansible/module_utils/common/messages.py +40 -23
  29. ansible/module_utils/common/process.py +0 -1
  30. ansible/module_utils/common/respawn.py +0 -7
  31. ansible/module_utils/common/warnings.py +13 -13
  32. ansible/module_utils/datatag.py +13 -13
  33. ansible/module_utils/facts/virtual/linux.py +1 -1
  34. ansible/module_utils/parsing/convert_bool.py +6 -0
  35. ansible/modules/assemble.py +4 -4
  36. ansible/modules/async_status.py +1 -1
  37. ansible/modules/cron.py +3 -5
  38. ansible/modules/dnf5.py +2 -1
  39. ansible/modules/get_url.py +1 -1
  40. ansible/modules/git.py +1 -6
  41. ansible/modules/pip.py +2 -4
  42. ansible/modules/sysvinit.py +3 -3
  43. ansible/playbook/task.py +0 -2
  44. ansible/plugins/__init__.py +18 -8
  45. ansible/plugins/action/__init__.py +7 -15
  46. ansible/plugins/action/gather_facts.py +2 -4
  47. ansible/plugins/action/template.py +3 -0
  48. ansible/plugins/callback/oneline.py +7 -1
  49. ansible/plugins/callback/tree.py +7 -1
  50. ansible/plugins/connection/local.py +1 -1
  51. ansible/plugins/connection/paramiko_ssh.py +9 -2
  52. ansible/plugins/doc_fragments/action_core.py +1 -1
  53. ansible/plugins/filter/core.py +4 -1
  54. ansible/plugins/inventory/__init__.py +2 -2
  55. ansible/plugins/loader.py +197 -132
  56. ansible/plugins/lookup/url.py +2 -2
  57. ansible/plugins/strategy/__init__.py +6 -6
  58. ansible/release.py +1 -1
  59. ansible/template/__init__.py +1 -1
  60. ansible/utils/collection_loader/__init__.py +2 -0
  61. ansible/utils/collection_loader/_collection_meta.py +5 -3
  62. ansible/utils/display.py +137 -71
  63. ansible/utils/plugin_docs.py +2 -1
  64. ansible/utils/py3compat.py +1 -7
  65. ansible/utils/ssh_functions.py +4 -1
  66. ansible/vars/manager.py +18 -10
  67. ansible/vars/plugins.py +4 -4
  68. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/METADATA +3 -2
  69. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/RECORD +82 -79
  70. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/WHEEL +1 -1
  71. ansible_test/_internal/commands/sanity/pylint.py +1 -0
  72. ansible_test/_internal/docker_util.py +4 -3
  73. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +486 -0
  74. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
  75. ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
  76. ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
  77. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
  78. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/entry_points.txt +0 -0
  79. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses}/COPYING +0 -0
  80. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/Apache-License.txt +0 -0
  81. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
  82. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/MIT-license.txt +0 -0
  83. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/PSF-license.txt +0 -0
  84. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
  85. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/top_level.txt +0 -0
@@ -822,12 +822,12 @@ class StrategyBase:
822
822
  """
823
823
  if handle_stats_and_callbacks:
824
824
  display.deprecated(
825
- "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"
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",
831
831
  )
832
832
  display.debug("loading included file: %s" % included_file._filename)
833
833
  try:
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b2'
20
+ __version__ = '2.19.0b4'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -388,7 +388,7 @@ def generate_ansible_template_vars(path: str, fullpath: str | None = None, dest_
388
388
  value=ansible_managed,
389
389
  msg="The `ansible_managed` variable is deprecated.",
390
390
  help_text="Define and use a custom variable instead.",
391
- removal_version='2.23',
391
+ version='2.23',
392
392
  )
393
393
 
394
394
  temp_vars = dict(
@@ -13,6 +13,8 @@ import typing as t
13
13
  class _EncryptedStringProtocol(t.Protocol):
14
14
  """Protocol representing an `EncryptedString`, since it cannot be imported here."""
15
15
 
16
+ # DTFIX-FUTURE: collapse this with the one in config, once we can
17
+
16
18
  def _decrypt(self) -> str: ...
17
19
 
18
20
 
@@ -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):
ansible/utils/display.py CHANGED
@@ -18,7 +18,6 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import dataclasses
21
- import datetime
22
21
 
23
22
  try:
24
23
  import curses
@@ -50,9 +49,10 @@ from functools import wraps
50
49
  from struct import unpack, pack
51
50
 
52
51
  from ansible import constants as C
52
+ from ansible.constants import config
53
53
  from ansible.errors import AnsibleAssertionError, AnsiblePromptInterrupt, AnsiblePromptNoninteractive, AnsibleError
54
54
  from ansible._internal._errors import _utils
55
- from ansible.module_utils._internal import _ambient_context, _plugin_exec_context
55
+ from ansible.module_utils._internal import _ambient_context, _deprecator
56
56
  from ansible.module_utils.common.text.converters import to_bytes, to_text
57
57
  from ansible._internal._datatag._tags import TrustedAsTemplate
58
58
  from ansible.module_utils.common.messages import ErrorSummary, WarningSummary, DeprecationSummary, Detail, SummaryBase, PluginInfo
@@ -76,8 +76,6 @@ _LIBC.wcswidth.argtypes = (ctypes.c_wchar_p, ctypes.c_int)
76
76
  # Max for c_int
77
77
  _MAX_INT = 2 ** (ctypes.sizeof(ctypes.c_int) * 8 - 1) - 1
78
78
 
79
- _UNSET = t.cast(t.Any, object())
80
-
81
79
  MOVE_TO_BOL = b'\r'
82
80
  CLEAR_TO_EOL = b'\x1b[K'
83
81
 
@@ -555,7 +553,7 @@ class Display(metaclass=Singleton):
555
553
  msg: str,
556
554
  version: str | None = None,
557
555
  removed: bool = False,
558
- date: str | datetime.date | None = None,
556
+ date: str | None = None,
559
557
  collection_name: str | None = None,
560
558
  ) -> str:
561
559
  """Return a deprecation message and help text for non-display purposes (e.g., exception messages)."""
@@ -570,7 +568,7 @@ class Display(metaclass=Singleton):
570
568
  version=version,
571
569
  removed=removed,
572
570
  date=date,
573
- plugin=_plugin_exec_context.PluginExecContext.get_current_plugin_info(),
571
+ deprecator=PluginInfo._from_collection_name(collection_name),
574
572
  )
575
573
 
576
574
  if removed:
@@ -582,57 +580,63 @@ class Display(metaclass=Singleton):
582
580
 
583
581
  def _get_deprecation_message_with_plugin_info(
584
582
  self,
583
+ *,
585
584
  msg: str,
586
- version: str | None = None,
585
+ version: str | None,
587
586
  removed: bool = False,
588
- date: str | datetime.date | None = None,
589
- plugin: PluginInfo | None = None,
587
+ date: str | None,
588
+ deprecator: PluginInfo | None,
590
589
  ) -> str:
591
590
  """Internal use only. Return a deprecation message and help text for display."""
592
- msg = msg.strip()
593
-
594
- if msg and msg[-1] not in ['!', '?', '.']:
595
- msg += '.'
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
596
592
 
597
593
  if removed:
598
594
  removal_fragment = 'This feature was removed'
599
- help_text = 'Please update your playbooks.'
600
595
  else:
601
596
  removal_fragment = 'This feature will be removed'
602
- help_text = ''
603
597
 
604
- if plugin:
605
- from_fragment = f'from the {self._describe_plugin_info(plugin)}'
598
+ if not deprecator or deprecator.type == _deprecator.INDETERMINATE_DEPRECATOR.type:
599
+ collection = None
600
+ plugin_fragment = ''
601
+ elif deprecator.type == _deprecator.PluginInfo._COLLECTION_ONLY_TYPE:
602
+ collection = deprecator.resolved_name
603
+ plugin_fragment = ''
606
604
  else:
607
- from_fragment = ''
605
+ parts = deprecator.resolved_name.split('.')
606
+ plugin_name = parts[-1]
607
+ # DTFIX-RELEASE: normalize 'modules' -> 'module' before storing it so we can eliminate the normalization here
608
+ plugin_type = "module" if deprecator.type in ("module", "modules") else f'{deprecator.type} plugin'
608
609
 
609
- if date:
610
- when = 'in a release after {0}.'.format(date)
611
- elif version:
612
- when = 'in version {0}.'.format(version)
613
- else:
614
- when = 'in a future release.'
610
+ collection = '.'.join(parts[:2]) if len(parts) > 2 else None
611
+ plugin_fragment = f'{plugin_type} {plugin_name!r}'
615
612
 
616
- message_text = ' '.join(f for f in [msg, removal_fragment, from_fragment, when, help_text] if f)
613
+ if collection and plugin_fragment:
614
+ plugin_fragment += ' in'
617
615
 
618
- return message_text
616
+ if collection == 'ansible.builtin':
617
+ collection_fragment = 'ansible-core'
618
+ elif collection:
619
+ collection_fragment = f'collection {collection!r}'
620
+ else:
621
+ collection_fragment = ''
619
622
 
620
- @staticmethod
621
- def _describe_plugin_info(plugin_info: PluginInfo) -> str:
622
- """Return a brief description of the plugin info, including name(s) and type."""
623
- name = repr(plugin_info.resolved_name)
624
- clarification = f' (requested as {plugin_info.requested_name!r})' if plugin_info.requested_name != plugin_info.resolved_name else ''
625
-
626
- if plugin_info.type in ("module", "modules"):
627
- # DTFIX-RELEASE: pluginloader or AnsiblePlugin needs a "type desc" property that doesn't suffer from legacy "inconsistencies" like this
628
- plugin_type = "module"
629
- elif plugin_info.type == "collection":
630
- # not a real plugin type, but used for tombstone errors generated by plugin loader
631
- plugin_type = plugin_info.type
623
+ if not collection:
624
+ when_fragment = 'in the future' if not removed else ''
625
+ elif date:
626
+ when_fragment = f'in a release after {date}'
627
+ elif version:
628
+ when_fragment = f'version {version}'
632
629
  else:
633
- plugin_type = f'{plugin_info.type} plugin'
630
+ when_fragment = 'in a future release' if not removed else ''
634
631
 
635
- return f'{name} {plugin_type}{clarification}'
632
+ if plugin_fragment or collection_fragment:
633
+ from_fragment = 'from'
634
+ else:
635
+ from_fragment = ''
636
+
637
+ deprecation_msg = ' '.join(f for f in [removal_fragment, from_fragment, plugin_fragment, collection_fragment, when_fragment] if f) + '.'
638
+
639
+ return _join_sentences(msg, deprecation_msg)
636
640
 
637
641
  def _wrap_message(self, msg: str, wrap_text: bool) -> str:
638
642
  if wrap_text and self._wrap_stderr:
@@ -661,20 +665,24 @@ class Display(metaclass=Singleton):
661
665
  msg: str,
662
666
  version: str | None = None,
663
667
  removed: bool = False,
664
- date: str | datetime.date | None = None,
665
- collection_name: str | None = _UNSET,
668
+ date: str | None = None,
669
+ collection_name: str | None = None,
666
670
  *,
671
+ deprecator: PluginInfo | None = None,
667
672
  help_text: str | None = None,
668
673
  obj: t.Any = None,
669
674
  ) -> None:
670
- """Display a deprecation warning message, if enabled."""
671
- # deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
672
- # if collection_name is not _UNSET:
673
- # self.deprecated('The `collection_name` argument to `deprecated` is deprecated.', version='2.27')
674
-
675
+ """
676
+ Display a deprecation warning message, if enabled.
677
+ Most callers do not need to provide `collection_name` or `deprecator` -- but provide only one if needed.
678
+ Specify `version` or `date`, but not both.
679
+ If `date` is a string, it must be in the form `YYYY-MM-DD`.
680
+ """
675
681
  # DTFIX-RELEASE: are there any deprecation calls where the feature is switching from enabled to disabled, rather than being removed entirely?
676
682
  # DTFIX-RELEASE: are there deprecated features which should going through deferred deprecation instead?
677
683
 
684
+ _skip_stackwalk = True
685
+
678
686
  self._deprecated_with_plugin_info(
679
687
  msg=msg,
680
688
  version=version,
@@ -682,32 +690,36 @@ class Display(metaclass=Singleton):
682
690
  date=date,
683
691
  help_text=help_text,
684
692
  obj=obj,
685
- plugin=_plugin_exec_context.PluginExecContext.get_current_plugin_info(),
693
+ deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
686
694
  )
687
695
 
688
696
  def _deprecated_with_plugin_info(
689
697
  self,
698
+ *,
690
699
  msg: str,
691
- version: str | None = None,
700
+ version: str | None,
692
701
  removed: bool = False,
693
- date: str | datetime.date | None = None,
694
- *,
695
- help_text: str | None = None,
696
- obj: t.Any = None,
697
- plugin: PluginInfo | None = None,
702
+ date: str | None,
703
+ help_text: str | None,
704
+ obj: t.Any,
705
+ deprecator: PluginInfo | None,
698
706
  ) -> None:
699
707
  """
700
708
  This is the internal pre-proxy half of the `deprecated` implementation.
701
709
  Any logic that must occur on workers needs to be implemented here.
702
710
  """
711
+ _skip_stackwalk = True
712
+
703
713
  if removed:
704
- raise AnsibleError(self._get_deprecation_message_with_plugin_info(
714
+ formatted_msg = self._get_deprecation_message_with_plugin_info(
705
715
  msg=msg,
706
716
  version=version,
707
717
  removed=removed,
708
718
  date=date,
709
- plugin=plugin,
710
- ))
719
+ deprecator=deprecator,
720
+ )
721
+
722
+ raise AnsibleError(formatted_msg)
711
723
 
712
724
  if source_context := _utils.SourceContext.from_value(obj):
713
725
  formatted_source_context = str(source_context)
@@ -723,8 +735,8 @@ class Display(metaclass=Singleton):
723
735
  ),
724
736
  ),
725
737
  version=version,
726
- date=str(date) if isinstance(date, datetime.date) else date,
727
- plugin=plugin,
738
+ date=date,
739
+ deprecator=deprecator,
728
740
  formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
729
741
  )
730
742
 
@@ -1225,20 +1237,74 @@ def _get_message_lines(message: str, help_text: str | None, formatted_source_con
1225
1237
  return message_lines
1226
1238
 
1227
1239
 
1240
+ def _join_sentences(first: str | None, second: str | None) -> str:
1241
+ """Join two sentences together."""
1242
+ first = (first or '').strip()
1243
+ second = (second or '').strip()
1244
+
1245
+ if first and first[-1] not in ('!', '?', '.'):
1246
+ first += '.'
1247
+
1248
+ if second and second[-1] not in ('!', '?', '.'):
1249
+ second += '.'
1250
+
1251
+ if first and not second:
1252
+ return first
1253
+
1254
+ if not first and second:
1255
+ return second
1256
+
1257
+ return ' '.join((first, second))
1258
+
1259
+
1228
1260
  def format_message(summary: SummaryBase) -> str:
1229
- details: t.Sequence[Detail]
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,
1270
+ version=summary.version,
1271
+ date=summary.date,
1272
+ deprecator=summary.deprecator,
1273
+ )
1230
1274
 
1231
- if isinstance(summary, DeprecationSummary):
1232
- details = [detail if idx else dataclasses.replace(
1275
+ detail_list[0] = dataclasses.replace(
1233
1276
  detail,
1234
- msg=_display._get_deprecation_message_with_plugin_info(
1235
- msg=detail.msg,
1236
- version=summary.version,
1237
- date=summary.date,
1238
- plugin=summary.plugin,
1239
- ),
1240
- ) for idx, detail in enumerate(summary.details)]
1241
- else:
1242
- details = summary.details
1277
+ msg=deprecation_msg,
1278
+ help_text=detail.help_text,
1279
+ )
1280
+
1281
+ details = detail_list
1243
1282
 
1244
1283
  return _format_error_details(details, summary.formatted_traceback)
1284
+
1285
+
1286
+ def _report_config_warnings(deprecator: PluginInfo) -> None:
1287
+ """Called by config to report warnings/deprecations collected during a config parse."""
1288
+ while config._errors:
1289
+ msg, exception = config._errors.pop()
1290
+ _display.error_as_warning(msg=msg, exception=exception)
1291
+
1292
+ while config.WARNINGS:
1293
+ warn = config.WARNINGS.pop()
1294
+ _display.warning(warn)
1295
+
1296
+ while config.DEPRECATED:
1297
+ # tuple with name and options
1298
+ dep = config.DEPRECATED.pop(0)
1299
+ msg = config.get_deprecated_msg_from_config(dep[1]).replace("\t", "")
1300
+
1301
+ _display.deprecated( # pylint: disable=ansible-deprecated-unnecessary-collection-name,ansible-invalid-deprecated-version
1302
+ msg=f"{dep[0]} option. {msg}",
1303
+ version=dep[1]['version'],
1304
+ deprecator=deprecator,
1305
+ )
1306
+
1307
+
1308
+ # emit any warnings or deprecations
1309
+ # in the event config fails before display is up, we'll lose warnings -- but that's OK, since everything is broken anyway
1310
+ _report_config_warnings(_deprecator.ANSIBLE_CORE_DEPRECATOR)
@@ -154,7 +154,8 @@ def add_fragments(doc, filename, fragment_loader, is_module=False):
154
154
  unknown_fragments.append(fragment_slug)
155
155
  continue
156
156
 
157
- fragment_yaml = getattr(fragment_class, fragment_var, None)
157
+ # trust-tagged source propagates to loaded values; expressions and templates in config require trust
158
+ fragment_yaml = _tags.TrustedAsTemplate().tag(getattr(fragment_class, fragment_var, None))
158
159
  if fragment_yaml is None:
159
160
  if fragment_var != 'DOCUMENTATION':
160
161
  # if it's asking for something specific that's missing, that's an error
@@ -6,7 +6,6 @@
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import inspect
10
9
  import os
11
10
 
12
11
  from ansible.utils.display import Display
@@ -19,13 +18,8 @@ def __getattr__(name):
19
18
  if name != 'environ':
20
19
  raise AttributeError(name)
21
20
 
22
- caller = inspect.stack()[1]
23
-
24
21
  display.deprecated(
25
- (
26
- 'ansible.utils.py3compat.environ is deprecated in favor of os.environ. '
27
- f'Accessed by {caller.filename} line number {caller.lineno}'
28
- ),
22
+ msg='ansible.utils.py3compat.environ is deprecated in favor of os.environ.',
29
23
  version='2.20',
30
24
  )
31
25
 
@@ -56,7 +56,10 @@ def set_default_transport():
56
56
 
57
57
  # deal with 'smart' connection .. one time ..
58
58
  if C.DEFAULT_TRANSPORT == 'smart':
59
- display.deprecated("The 'smart' option for connections is deprecated. Set the connection plugin directly instead.", version='2.20')
59
+ display.deprecated(
60
+ msg="The 'smart' option for connections is deprecated. Set the connection plugin directly instead.",
61
+ version='2.20',
62
+ )
60
63
 
61
64
  # see if SSH can support ControlPersist if not use paramiko
62
65
  if not check_for_controlpersist('ssh') and paramiko is not None:
ansible/vars/manager.py CHANGED
@@ -25,6 +25,8 @@ from collections import defaultdict
25
25
  from collections.abc import Mapping, MutableMapping
26
26
 
27
27
  from ansible import constants as C
28
+ from ansible.module_utils._internal import _deprecator
29
+ from ansible.module_utils._internal._datatag import _tags
28
30
  from ansible.errors import (AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleFileNotFound,
29
31
  AnsibleAssertionError, AnsibleValueOmittedError)
30
32
  from ansible.inventory.host import Host
@@ -32,7 +34,6 @@ from ansible.inventory.helpers import sort_groups, get_group_vars
32
34
  from ansible.inventory.manager import InventoryManager
33
35
  from ansible.module_utils.datatag import native_type_name
34
36
  from ansible.module_utils.six import text_type
35
- from ansible.module_utils.datatag import deprecate_value
36
37
  from ansible.parsing.dataloader import DataLoader
37
38
  from ansible._internal._templating._engine import TemplateEngine
38
39
  from ansible.plugins.loader import cache_loader
@@ -50,8 +51,12 @@ if t.TYPE_CHECKING:
50
51
  display = Display()
51
52
 
52
53
  # deprecated: description='enable top-level facts deprecation' core_version='2.20'
53
- # _DEPRECATE_TOP_LEVEL_FACT_MSG = sys.intern('Top-level facts are deprecated, use `ansible_facts` instead.')
54
- # _DEPRECATE_TOP_LEVEL_FACT_REMOVAL_VERSION = sys.intern('2.22')
54
+ # _DEPRECATE_TOP_LEVEL_FACT_TAG = _tags.Deprecated(
55
+ # msg='Top-level facts are deprecated.',
56
+ # version='2.24',
57
+ # deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,
58
+ # help_text='Use `ansible_facts` instead.',
59
+ # )
55
60
 
56
61
 
57
62
  def _deprecate_top_level_fact(value: t.Any) -> t.Any:
@@ -61,7 +66,7 @@ def _deprecate_top_level_fact(value: t.Any) -> t.Any:
61
66
  Unique tag instances are required to achieve the correct de-duplication within a top-level templating operation.
62
67
  """
63
68
  # deprecated: description='enable top-level facts deprecation' core_version='2.20'
64
- # return deprecate_value(value, _DEPRECATE_TOP_LEVEL_FACT_MSG, removal_version=_DEPRECATE_TOP_LEVEL_FACT_REMOVAL_VERSION)
69
+ # return _DEPRECATE_TOP_LEVEL_FACT_TAG.tag(value)
65
70
  return value
66
71
 
67
72
 
@@ -96,6 +101,13 @@ class VariableManager:
96
101
  _ALLOWED = frozenset(['plugins_by_group', 'groups_plugins_play', 'groups_plugins_inventory', 'groups_inventory',
97
102
  'all_plugins_play', 'all_plugins_inventory', 'all_inventory'])
98
103
 
104
+ _PLAY_HOSTS_DEPRECATED_TAG = _tags.Deprecated(
105
+ msg='The `play_hosts` magic variable is deprecated.',
106
+ version='2.23',
107
+ deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,
108
+ help_text='Use `ansible_play_batch` instead.',
109
+ )
110
+
99
111
  def __init__(self, loader: DataLoader | None = None, inventory: InventoryManager | None = None, version_info: dict[str, str] | None = None) -> None:
100
112
  self._nonpersistent_fact_cache: defaultdict[str, dict] = defaultdict(dict)
101
113
  self._vars_cache: defaultdict[str, dict] = defaultdict(dict)
@@ -477,12 +489,8 @@ class VariableManager:
477
489
  variables['ansible_play_hosts'] = [x for x in variables['ansible_play_hosts_all'] if x not in play._removed_hosts]
478
490
  variables['ansible_play_batch'] = [x for x in _hosts if x not in play._removed_hosts]
479
491
 
480
- variables['play_hosts'] = deprecate_value(
481
- value=variables['ansible_play_batch'],
482
- msg='The `play_hosts` magic variable is deprecated.',
483
- removal_version='2.23',
484
- help_text='Use `ansible_play_batch` instead.',
485
- )
492
+ # use a static tag instead of `deprecate_value` to avoid stackwalk in a hot code path
493
+ variables['play_hosts'] = self._PLAY_HOSTS_DEPRECATED_TAG.tag(variables['ansible_play_batch'])
486
494
 
487
495
  # Set options vars
488
496
  for option, option_value in self._options_vars.items():
ansible/vars/plugins.py CHANGED
@@ -34,10 +34,10 @@ def get_plugin_vars(loader, plugin, path, entities):
34
34
  except AttributeError:
35
35
  if hasattr(plugin, 'get_host_vars') or hasattr(plugin, 'get_group_vars'):
36
36
  display.deprecated(
37
- f"The vars plugin {plugin.ansible_name} from {plugin._original_path} is relying "
38
- "on the deprecated entrypoints 'get_host_vars' and 'get_group_vars'. "
39
- "This plugin should be updated to inherit from BaseVarsPlugin and define "
40
- "a 'get_vars' method as the main entrypoint instead.",
37
+ msg=f"The vars plugin {plugin.ansible_name} from {plugin._original_path} is relying "
38
+ "on the deprecated entrypoints 'get_host_vars' and 'get_group_vars'. "
39
+ "This plugin should be updated to inherit from BaseVarsPlugin and define "
40
+ "a 'get_vars' method as the main entrypoint instead.",
41
41
  version="2.20",
42
42
  )
43
43
  try:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ansible-core
3
- Version: 2.19.0b2
3
+ Version: 2.19.0b4
4
4
  Summary: Radically simple IT automation
5
5
  Author: Ansible Project
6
6
  Project-URL: Homepage, https://ansible.com/
@@ -38,6 +38,7 @@ Requires-Dist: PyYAML>=5.1
38
38
  Requires-Dist: cryptography
39
39
  Requires-Dist: packaging
40
40
  Requires-Dist: resolvelib<2.0.0,>=0.5.3
41
+ Dynamic: license-file
41
42
 
42
43
  [![PyPI version](https://img.shields.io/pypi/v/ansible-core.svg)](https://pypi.org/project/ansible-core)
43
44
  [![Docs badge](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.ansible.com/ansible/latest/)