ansible-core 2.19.0b2__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 (70) hide show
  1. ansible/_internal/_ansiballz.py +1 -4
  2. ansible/_internal/_templating/_datatag.py +3 -4
  3. ansible/_internal/_templating/_engine.py +6 -1
  4. ansible/_internal/_templating/_jinja_plugins.py +2 -6
  5. ansible/cli/__init__.py +3 -2
  6. ansible/cli/arguments/option_helpers.py +4 -1
  7. ansible/cli/doc.py +0 -1
  8. ansible/config/manager.py +2 -2
  9. ansible/constants.py +0 -62
  10. ansible/errors/__init__.py +6 -2
  11. ansible/executor/module_common.py +11 -7
  12. ansible/executor/task_executor.py +6 -8
  13. ansible/galaxy/api.py +1 -1
  14. ansible/galaxy/collection/__init__.py +3 -3
  15. ansible/module_utils/_internal/_ansiballz.py +4 -30
  16. ansible/module_utils/_internal/_datatag/_tags.py +3 -25
  17. ansible/module_utils/_internal/_deprecator.py +134 -0
  18. ansible/module_utils/_internal/_plugin_info.py +25 -0
  19. ansible/module_utils/_internal/_validation.py +14 -0
  20. ansible/module_utils/ansible_release.py +1 -1
  21. ansible/module_utils/basic.py +64 -17
  22. ansible/module_utils/common/arg_spec.py +8 -3
  23. ansible/module_utils/common/messages.py +40 -23
  24. ansible/module_utils/common/process.py +0 -1
  25. ansible/module_utils/common/respawn.py +0 -7
  26. ansible/module_utils/common/warnings.py +13 -13
  27. ansible/module_utils/datatag.py +13 -13
  28. ansible/modules/async_status.py +1 -1
  29. ansible/modules/dnf5.py +1 -1
  30. ansible/modules/get_url.py +1 -1
  31. ansible/playbook/task.py +0 -2
  32. ansible/plugins/__init__.py +18 -8
  33. ansible/plugins/action/__init__.py +6 -14
  34. ansible/plugins/action/gather_facts.py +2 -4
  35. ansible/plugins/callback/oneline.py +7 -1
  36. ansible/plugins/callback/tree.py +7 -1
  37. ansible/plugins/connection/local.py +1 -1
  38. ansible/plugins/connection/paramiko_ssh.py +9 -2
  39. ansible/plugins/doc_fragments/action_core.py +1 -1
  40. ansible/plugins/filter/core.py +4 -1
  41. ansible/plugins/inventory/__init__.py +2 -2
  42. ansible/plugins/loader.py +194 -130
  43. ansible/plugins/lookup/url.py +2 -2
  44. ansible/plugins/strategy/__init__.py +6 -6
  45. ansible/release.py +1 -1
  46. ansible/template/__init__.py +1 -1
  47. ansible/utils/collection_loader/_collection_meta.py +5 -3
  48. ansible/utils/display.py +133 -71
  49. ansible/utils/py3compat.py +1 -7
  50. ansible/utils/ssh_functions.py +4 -1
  51. ansible/vars/manager.py +18 -10
  52. ansible/vars/plugins.py +4 -4
  53. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
  54. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +67 -65
  55. ansible_test/_internal/commands/sanity/pylint.py +1 -0
  56. ansible_test/_internal/docker_util.py +4 -3
  57. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +475 -0
  58. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
  59. ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
  60. ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
  61. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
  62. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
  63. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
  64. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
  65. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
  66. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
  67. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
  68. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
  69. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
  70. {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,6 @@ def _ansiballz_main(
42
42
  module_fqn: str,
43
43
  params: str,
44
44
  profile: str,
45
- plugin_info_dict: dict[str, object],
46
45
  date_time: datetime.datetime,
47
46
  coverage_config: str | None,
48
47
  coverage_output: str | None,
@@ -142,7 +141,6 @@ def _ansiballz_main(
142
141
  run_module(
143
142
  json_params=json_params,
144
143
  profile=profile,
145
- plugin_info_dict=plugin_info_dict,
146
144
  module_fqn=module_fqn,
147
145
  modlib_path=modlib_path,
148
146
  coverage_config=coverage_config,
@@ -230,13 +228,12 @@ def _ansiballz_main(
230
228
  run_module(
231
229
  json_params=json_params,
232
230
  profile=profile,
233
- plugin_info_dict=plugin_info_dict,
234
231
  module_fqn=module_fqn,
235
232
  modlib_path=modlib_path,
236
233
  )
237
234
 
238
235
  else:
239
- print('WARNING: Unknown debug command. Doing nothing.')
236
+ print(f'FATAL: Unknown debug command {command!r}. Doing nothing.')
240
237
 
241
238
  #
242
239
  # See comments in the debug() method for information on debugging
@@ -12,7 +12,6 @@ from ansible.utils.display import Display
12
12
  from ._access import NotifiableAccessContextBase
13
13
  from ._utils import TemplateContext
14
14
 
15
-
16
15
  display = Display()
17
16
 
18
17
 
@@ -57,10 +56,10 @@ class DeprecatedAccessAuditContext(NotifiableAccessContextBase):
57
56
  display._deprecated_with_plugin_info(
58
57
  msg=msg,
59
58
  help_text=item.deprecated.help_text,
60
- version=item.deprecated.removal_version,
61
- date=item.deprecated.removal_date,
59
+ version=item.deprecated.version,
60
+ date=item.deprecated.date,
62
61
  obj=item.template,
63
- plugin=item.deprecated.plugin,
62
+ deprecator=item.deprecated.deprecator,
64
63
  )
65
64
 
66
65
  return result
@@ -566,7 +566,12 @@ class TemplateEngine:
566
566
  )
567
567
 
568
568
  if _TemplateConfig.allow_broken_conditionals:
569
- _display.deprecated(msg=msg, obj=conditional, help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT, version='2.23')
569
+ _display.deprecated(
570
+ msg=msg,
571
+ obj=conditional,
572
+ help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT,
573
+ version='2.23',
574
+ )
570
575
 
571
576
  return bool_result
572
577
 
@@ -9,7 +9,6 @@ import functools
9
9
  import typing as t
10
10
 
11
11
  from ansible.module_utils._internal._ambient_context import AmbientContextBase
12
- from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
13
12
  from ansible.module_utils.common.collections import is_sequence
14
13
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
15
14
  from ansible._internal._datatag._tags import TrustedAsTemplate
@@ -111,7 +110,7 @@ class JinjaPluginIntercept(c.MutableMapping):
111
110
  return first_marker
112
111
 
113
112
  try:
114
- with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers), PluginExecContext(executing_plugin=instance):
113
+ with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
115
114
  return instance.j2_function(*lazify_container_args(args), **lazify_container_kwargs(kwargs))
116
115
  except MarkerError as ex:
117
116
  return ex.source
@@ -212,10 +211,7 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
212
211
  wantlist = lookup_kwargs.pop('wantlist', False)
213
212
  errors = lookup_kwargs.pop('errors', 'strict')
214
213
 
215
- with (
216
- JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers),
217
- PluginExecContext(executing_plugin=instance),
218
- ):
214
+ with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
219
215
  try:
220
216
  if _TemplateConfig.allow_embedded_templates:
221
217
  # for backwards compat, only trust constant templates in lookup terms
ansible/cli/__init__.py CHANGED
@@ -10,7 +10,6 @@ import os
10
10
  import signal
11
11
  import sys
12
12
 
13
-
14
13
  # We overload the ``ansible`` adhoc command to provide the functionality for
15
14
  # ``SSH_ASKPASS``. This code is here, and not in ``adhoc.py`` to bypass
16
15
  # unnecessary code. The program provided to ``SSH_ASKPASS`` can only be invoked
@@ -106,6 +105,7 @@ except Exception as ex:
106
105
 
107
106
 
108
107
  from ansible import context
108
+ from ansible.utils import display as _display
109
109
  from ansible.cli.arguments import option_helpers as opt_help
110
110
  from ansible.inventory.manager import InventoryManager
111
111
  from ansible.module_utils.six import string_types
@@ -122,6 +122,7 @@ from ansible.utils.collection_loader import AnsibleCollectionConfig
122
122
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
123
123
  from ansible.utils.path import unfrackpath
124
124
  from ansible.vars.manager import VariableManager
125
+ from ansible.module_utils._internal import _deprecator
125
126
 
126
127
  try:
127
128
  import argcomplete
@@ -257,7 +258,7 @@ class CLI(ABC):
257
258
  else:
258
259
  display.v(u"No config file found; using defaults")
259
260
 
260
- C.handle_config_noise(display)
261
+ _display._report_config_warnings(_deprecator.ANSIBLE_CORE_DEPRECATOR)
261
262
 
262
263
  @staticmethod
263
264
  def split_vault_id(vault_id):
@@ -56,7 +56,10 @@ class DeprecatedArgument:
56
56
 
57
57
  from ansible.utils.display import Display
58
58
 
59
- Display().deprecated(f'The {option!r} argument is deprecated.', version=self.version)
59
+ Display().deprecated( # pylint: disable=ansible-invalid-deprecated-version
60
+ msg=f'The {option!r} argument is deprecated.',
61
+ version=self.version,
62
+ )
60
63
 
61
64
 
62
65
  class ArgumentParser(argparse.ArgumentParser):
ansible/cli/doc.py CHANGED
@@ -1335,7 +1335,6 @@ class DocCLI(CLI, RoleMixin):
1335
1335
  'This was unintentionally allowed when plugin attributes were added, '
1336
1336
  'but the feature does not map well to role argument specs.',
1337
1337
  version='2.20',
1338
- collection_name='ansible.builtin',
1339
1338
  )
1340
1339
  text.append("")
1341
1340
  text.append(_format("ATTRIBUTES:", 'bold'))
ansible/config/manager.py CHANGED
@@ -480,9 +480,9 @@ class ConfigManager(object):
480
480
  else:
481
481
  ret = self._plugins.get(plugin_type, {}).get(name, {})
482
482
 
483
- if ignore_private:
483
+ if ignore_private: # ignore 'test' config entries, they should not change runtime behaviors
484
484
  for cdef in list(ret.keys()):
485
- if cdef.startswith('_'):
485
+ if cdef.startswith('_Z_'):
486
486
  del ret[cdef]
487
487
  return ret
488
488
 
ansible/constants.py CHANGED
@@ -10,9 +10,7 @@ from string import ascii_letters, digits
10
10
 
11
11
  from ansible.config.manager import ConfigManager
12
12
  from ansible.module_utils.common.text.converters import to_text
13
- from ansible.module_utils.common.collections import Sequence
14
13
  from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
15
- from ansible.release import __version__
16
14
  from ansible.utils.fqcn import add_internal_fqcns
17
15
 
18
16
  # initialize config manager/config data to read/store global settings
@@ -20,68 +18,11 @@ from ansible.utils.fqcn import add_internal_fqcns
20
18
  config = ConfigManager()
21
19
 
22
20
 
23
- def _warning(msg):
24
- """ display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
25
- try:
26
- from ansible.utils.display import Display
27
- Display().warning(msg)
28
- except Exception:
29
- import sys
30
- sys.stderr.write(' [WARNING] %s\n' % (msg))
31
-
32
-
33
- def _deprecated(msg, version):
34
- """ display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
35
- try:
36
- from ansible.utils.display import Display
37
- Display().deprecated(msg, version=version)
38
- except Exception:
39
- import sys
40
- sys.stderr.write(' [DEPRECATED] %s, to be removed in %s\n' % (msg, version))
41
-
42
-
43
- def handle_config_noise(display=None):
44
-
45
- if display is not None:
46
- w = display.warning
47
- d = display.deprecated
48
- else:
49
- w = _warning
50
- d = _deprecated
51
-
52
- while config.WARNINGS:
53
- warn = config.WARNINGS.pop()
54
- w(warn)
55
-
56
- while config.DEPRECATED:
57
- # tuple with name and options
58
- dep = config.DEPRECATED.pop(0)
59
- msg = config.get_deprecated_msg_from_config(dep[1])
60
- # use tabs only for ansible-doc?
61
- msg = msg.replace("\t", "")
62
- d(f"{dep[0]} option. {msg}", version=dep[1]['version'])
63
-
64
-
65
21
  def set_constant(name, value, export=vars()):
66
22
  """ sets constants and returns resolved options dict """
67
23
  export[name] = value
68
24
 
69
25
 
70
- class _DeprecatedSequenceConstant(Sequence):
71
- def __init__(self, value, msg, version):
72
- self._value = value
73
- self._msg = msg
74
- self._version = version
75
-
76
- def __len__(self):
77
- _deprecated(self._msg, self._version)
78
- return len(self._value)
79
-
80
- def __getitem__(self, y):
81
- _deprecated(self._msg, self._version)
82
- return self._value[y]
83
-
84
-
85
26
  # CONSTANTS ### yes, actual ones
86
27
 
87
28
  # The following are hard-coded action names
@@ -245,6 +186,3 @@ MAGIC_VARIABLE_MAPPING = dict(
245
186
  # POPULATE SETTINGS FROM CONFIG ###
246
187
  for setting in config.get_configuration_definitions():
247
188
  set_constant(setting, config.get_config_value(setting, variables=vars()))
248
-
249
- # emit any warnings or deprecations
250
- handle_config_noise()
@@ -18,6 +18,9 @@ from ..module_utils.datatag import native_type_name
18
18
  from ansible._internal._datatag import _tags
19
19
  from .._internal._errors import _utils
20
20
 
21
+ if t.TYPE_CHECKING:
22
+ from ansible.plugins import loader as _t_loader
23
+
21
24
 
22
25
  class ExitCode(enum.IntEnum):
23
26
  SUCCESS = 0 # used by TQM, must be bit-flag safe
@@ -374,8 +377,9 @@ class _AnsibleActionDone(AnsibleAction):
374
377
  class AnsiblePluginError(AnsibleError):
375
378
  """Base class for Ansible plugin-related errors that do not need AnsibleError contextual data."""
376
379
 
377
- def __init__(self, message=None, plugin_load_context=None):
378
- super(AnsiblePluginError, self).__init__(message)
380
+ def __init__(self, message: str | None = None, plugin_load_context: _t_loader.PluginLoadContext | None = None, help_text: str | None = None) -> None:
381
+ super(AnsiblePluginError, self).__init__(message, help_text=help_text)
382
+
379
383
  self.plugin_load_context = plugin_load_context
380
384
 
381
385
 
@@ -39,7 +39,6 @@ from io import BytesIO
39
39
  from ansible._internal import _locking
40
40
  from ansible._internal._datatag import _utils
41
41
  from ansible.module_utils._internal import _dataclass_validation
42
- from ansible.module_utils.common.messages import PluginInfo
43
42
  from ansible.module_utils.common.yaml import yaml_load
44
43
  from ansible._internal._datatag._tags import Origin
45
44
  from ansible.module_utils.common.json import Direction, get_module_encoder
@@ -56,6 +55,7 @@ from ansible.template import Templar
56
55
  from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
57
56
  from ansible.module_utils._internal import _json, _ansiballz
58
57
  from ansible.module_utils import basic as _basic
58
+ from ansible.module_utils.common import messages as _messages
59
59
 
60
60
  if t.TYPE_CHECKING:
61
61
  from ansible import template as _template
@@ -434,7 +434,13 @@ class ModuleUtilLocatorBase:
434
434
  else:
435
435
  msg += '.'
436
436
 
437
- display.deprecated(msg, removal_version, removed, removal_date, self._collection_name)
437
+ display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
438
+ msg=msg,
439
+ version=removal_version,
440
+ removed=removed,
441
+ date=removal_date,
442
+ deprecator=_messages.PluginInfo._from_collection_name(self._collection_name),
443
+ )
438
444
  if 'redirect' in routing_entry:
439
445
  self.redirected = True
440
446
  source_pkg = '.'.join(name_parts)
@@ -944,7 +950,6 @@ class _CachedModule:
944
950
  def _find_module_utils(
945
951
  *,
946
952
  module_name: str,
947
- plugin: PluginInfo,
948
953
  b_module_data: bytes,
949
954
  module_path: str,
950
955
  module_args: dict[object, object],
@@ -1020,7 +1025,9 @@ def _find_module_utils(
1020
1025
  # People should start writing collections instead of modules in roles so we
1021
1026
  # may never fix this
1022
1027
  display.debug('ANSIBALLZ: Could not determine module FQN')
1023
- remote_module_fqn = 'ansible.modules.%s' % module_name
1028
+ # FIXME: add integration test to validate that builtins and legacy modules with the same name are tracked separately by the caching mechanism
1029
+ # FIXME: surrogate FQN should be unique per source path- role-packaged modules with name collisions can still be aliased
1030
+ remote_module_fqn = 'ansible.legacy.%s' % module_name
1024
1031
 
1025
1032
  if module_substyle == 'python':
1026
1033
  date_time = datetime.datetime.now(datetime.timezone.utc)
@@ -1126,7 +1133,6 @@ def _find_module_utils(
1126
1133
  module_fqn=remote_module_fqn,
1127
1134
  params=encoded_params,
1128
1135
  profile=module_metadata.serialization_profile,
1129
- plugin_info_dict=dataclasses.asdict(plugin),
1130
1136
  date_time=date_time,
1131
1137
  coverage_config=coverage_config,
1132
1138
  coverage_output=coverage_output,
@@ -1236,7 +1242,6 @@ def _extract_interpreter(b_module_data):
1236
1242
  def modify_module(
1237
1243
  *,
1238
1244
  module_name: str,
1239
- plugin: PluginInfo,
1240
1245
  module_path,
1241
1246
  module_args,
1242
1247
  templar,
@@ -1277,7 +1282,6 @@ def modify_module(
1277
1282
 
1278
1283
  module_bits = _find_module_utils(
1279
1284
  module_name=module_name,
1280
- plugin=plugin,
1281
1285
  b_module_data=b_module_data,
1282
1286
  module_path=module_path,
1283
1287
  module_args=module_args,
@@ -22,8 +22,7 @@ from ansible.errors import (
22
22
  )
23
23
  from ansible.executor.task_result import _RawTaskResult
24
24
  from ansible._internal._datatag import _utils
25
- from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
26
- from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary
25
+ from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary, PluginInfo
27
26
  from ansible.module_utils.datatag import native_type_name
28
27
  from ansible._internal._datatag._tags import TrustedAsTemplate
29
28
  from ansible.module_utils.parsing.convert_bool import boolean
@@ -640,8 +639,8 @@ class TaskExecutor:
640
639
  if self._task.timeout:
641
640
  old_sig = signal.signal(signal.SIGALRM, task_timeout)
642
641
  signal.alarm(self._task.timeout)
643
- with PluginExecContext(self._handler):
644
- result = self._handler.run(task_vars=vars_copy)
642
+
643
+ result = self._handler.run(task_vars=vars_copy)
645
644
 
646
645
  # DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
647
646
  except (AnsibleActionFail, AnsibleActionSkip) as e:
@@ -844,13 +843,12 @@ class TaskExecutor:
844
843
  if not isinstance(deprecation, DeprecationSummary):
845
844
  # translate non-DeprecationMessageDetail message dicts
846
845
  try:
847
- if deprecation.pop('collection_name', ...) is not ...:
846
+ if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
848
847
  # deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
848
+ # CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
849
849
  # self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
850
- pass
850
+ deprecation.update(deprecator=PluginInfo._from_collection_name(collection_name))
851
851
 
852
- # DTFIX-RELEASE: when plugin isn't set, do it at the boundary where we receive the module/action results
853
- # that may even allow us to never set it in modules/actions directly and to populate it at the boundary
854
852
  deprecation = DeprecationSummary(
855
853
  details=(
856
854
  Detail(msg=deprecation.pop('msg')),
ansible/galaxy/api.py CHANGED
@@ -138,7 +138,7 @@ def g_connect(versions):
138
138
  'The v2 Ansible Galaxy API is deprecated and no longer supported. '
139
139
  'Ensure that you have configured the ansible-galaxy CLI to utilize an '
140
140
  'updated and supported version of Ansible Galaxy.',
141
- version='2.20'
141
+ version='2.20',
142
142
  )
143
143
 
144
144
  return method(self, *args, **kwargs)
@@ -201,9 +201,9 @@ class CollectionSignatureError(Exception):
201
201
 
202
202
  # FUTURE: expose actual verify result details for a collection on this object, maybe reimplement as dataclass on py3.8+
203
203
  class CollectionVerifyResult:
204
- def __init__(self, collection_name): # type: (str) -> None
205
- self.collection_name = collection_name # type: str
206
- self.success = True # type: bool
204
+ def __init__(self, collection_name: str) -> None:
205
+ self.collection_name = collection_name
206
+ self.success = True
207
207
 
208
208
 
209
209
  def verify_local_collection(local_collection, remote_collection, artifacts_manager):
@@ -6,7 +6,6 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import atexit
9
- import dataclasses
10
9
  import importlib.util
11
10
  import json
12
11
  import os
@@ -15,17 +14,14 @@ import sys
15
14
  import typing as t
16
15
 
17
16
  from . import _errors
18
- from ._plugin_exec_context import PluginExecContext, HasPluginInfo
19
17
  from .. import basic
20
18
  from ..common.json import get_module_encoder, Direction
21
- from ..common.messages import PluginInfo
22
19
 
23
20
 
24
21
  def run_module(
25
22
  *,
26
23
  json_params: bytes,
27
24
  profile: str,
28
- plugin_info_dict: dict[str, object],
29
25
  module_fqn: str,
30
26
  modlib_path: str,
31
27
  init_globals: dict[str, t.Any] | None = None,
@@ -38,7 +34,6 @@ def run_module(
38
34
  _run_module(
39
35
  json_params=json_params,
40
36
  profile=profile,
41
- plugin_info_dict=plugin_info_dict,
42
37
  module_fqn=module_fqn,
43
38
  modlib_path=modlib_path,
44
39
  init_globals=init_globals,
@@ -80,7 +75,6 @@ def _run_module(
80
75
  *,
81
76
  json_params: bytes,
82
77
  profile: str,
83
- plugin_info_dict: dict[str, object],
84
78
  module_fqn: str,
85
79
  modlib_path: str,
86
80
  init_globals: dict[str, t.Any] | None = None,
@@ -92,12 +86,11 @@ def _run_module(
92
86
  init_globals = init_globals or {}
93
87
  init_globals.update(_module_fqn=module_fqn, _modlib_path=modlib_path)
94
88
 
95
- with PluginExecContext(_ModulePluginWrapper(PluginInfo._from_dict(plugin_info_dict))):
96
- # Run the module. By importing it as '__main__', it executes as a script.
97
- runpy.run_module(mod_name=module_fqn, init_globals=init_globals, run_name='__main__', alter_sys=True)
89
+ # Run the module. By importing it as '__main__', it executes as a script.
90
+ runpy.run_module(mod_name=module_fqn, init_globals=init_globals, run_name='__main__', alter_sys=True)
98
91
 
99
- # An Ansible module must print its own results and exit. If execution reaches this point, that did not happen.
100
- raise RuntimeError('New-style module did not handle its own exit.')
92
+ # An Ansible module must print its own results and exit. If execution reaches this point, that did not happen.
93
+ raise RuntimeError('New-style module did not handle its own exit.')
101
94
 
102
95
 
103
96
  def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
@@ -112,22 +105,3 @@ def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
112
105
  print(json.dumps(result, cls=encoder)) # pylint: disable=ansible-bad-function
113
106
 
114
107
  sys.exit(1) # pylint: disable=ansible-bad-function
115
-
116
-
117
- @dataclasses.dataclass(frozen=True)
118
- class _ModulePluginWrapper(HasPluginInfo):
119
- """Modules aren't plugin instances; this adapter implements the `HasPluginInfo` protocol to allow `PluginExecContext` infra to work with modules."""
120
-
121
- plugin: PluginInfo
122
-
123
- @property
124
- def _load_name(self) -> str:
125
- return self.plugin.requested_name
126
-
127
- @property
128
- def ansible_name(self) -> str:
129
- return self.plugin.resolved_name
130
-
131
- @property
132
- def plugin_type(self) -> str:
133
- return self.plugin.type
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- import datetime
5
4
  import typing as t
6
5
 
7
6
  from ansible.module_utils.common import messages as _messages
@@ -12,27 +11,6 @@ from ansible.module_utils._internal import _datatag
12
11
  class Deprecated(_datatag.AnsibleDatatagBase):
13
12
  msg: str
14
13
  help_text: t.Optional[str] = None
15
- removal_date: t.Optional[datetime.date] = None
16
- removal_version: t.Optional[str] = None
17
- plugin: t.Optional[_messages.PluginInfo] = None
18
-
19
- @classmethod
20
- def _from_dict(cls, d: t.Dict[str, t.Any]) -> Deprecated:
21
- source = d
22
- removal_date = source.get('removal_date')
23
-
24
- if removal_date is not None:
25
- source = source.copy()
26
- source['removal_date'] = datetime.date.fromisoformat(removal_date)
27
-
28
- return cls(**source)
29
-
30
- def _as_dict(self) -> t.Dict[str, t.Any]:
31
- # deprecated: description='no-args super() with slotted dataclass requires 3.14+' python_version='3.13'
32
- # see: https://github.com/python/cpython/pull/124455
33
- value = super(Deprecated, self)._as_dict()
34
-
35
- if self.removal_date is not None:
36
- value['removal_date'] = self.removal_date.isoformat()
37
-
38
- return value
14
+ date: t.Optional[str] = None
15
+ version: t.Optional[str] = None
16
+ deprecator: t.Optional[_messages.PluginInfo] = None
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import re
5
+ import pathlib
6
+ import sys
7
+ import typing as t
8
+
9
+ from ansible.module_utils.common.messages import PluginInfo
10
+
11
+ _ansible_module_base_path: t.Final = pathlib.Path(sys.modules['ansible'].__file__).parent
12
+ """Runtime-detected base path of the `ansible` Python package to distinguish between Ansible-owned and external code."""
13
+
14
+ ANSIBLE_CORE_DEPRECATOR: t.Final = PluginInfo._from_collection_name('ansible.builtin')
15
+ """Singleton `PluginInfo` instance for ansible-core callers where the plugin can/should not be identified in messages."""
16
+
17
+ INDETERMINATE_DEPRECATOR: t.Final = PluginInfo(resolved_name='indeterminate', type='indeterminate')
18
+ """Singleton `PluginInfo` instance for indeterminate deprecator."""
19
+
20
+ _DEPRECATOR_PLUGIN_TYPES = frozenset(
21
+ {
22
+ 'action',
23
+ 'become',
24
+ 'cache',
25
+ 'callback',
26
+ 'cliconf',
27
+ 'connection',
28
+ # doc_fragments - no code execution
29
+ # filter - basename inadequate to identify plugin
30
+ 'httpapi',
31
+ 'inventory',
32
+ 'lookup',
33
+ 'module', # only for collections
34
+ 'netconf',
35
+ 'shell',
36
+ 'strategy',
37
+ 'terminal',
38
+ # test - basename inadequate to identify plugin
39
+ 'vars',
40
+ }
41
+ )
42
+ """Plugin types which are valid for identifying a deprecator for deprecation purposes."""
43
+
44
+ _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES = frozenset(
45
+ {
46
+ 'filter',
47
+ 'test',
48
+ }
49
+ )
50
+ """Plugin types for which basename cannot be used to identify the plugin name."""
51
+
52
+
53
+ def get_best_deprecator(*, deprecator: PluginInfo | None = None, collection_name: str | None = None) -> PluginInfo:
54
+ """Return the best-available `PluginInfo` for the caller of this method."""
55
+ _skip_stackwalk = True
56
+
57
+ if deprecator and collection_name:
58
+ raise ValueError('Specify only one of `deprecator` or `collection_name`.')
59
+
60
+ return deprecator or PluginInfo._from_collection_name(collection_name) or get_caller_plugin_info() or INDETERMINATE_DEPRECATOR
61
+
62
+
63
+ def get_caller_plugin_info() -> PluginInfo | None:
64
+ """Try to get `PluginInfo` for the caller of this method, ignoring marked infrastructure stack frames."""
65
+ _skip_stackwalk = True
66
+
67
+ if frame_info := next((frame_info for frame_info in inspect.stack() if '_skip_stackwalk' not in frame_info.frame.f_locals), None):
68
+ return _path_as_core_plugininfo(frame_info.filename) or _path_as_collection_plugininfo(frame_info.filename)
69
+
70
+ return None # pragma: nocover
71
+
72
+
73
+ def _path_as_core_plugininfo(path: str) -> PluginInfo | None:
74
+ """Return a `PluginInfo` instance if the provided `path` refers to a core plugin."""
75
+ try:
76
+ relpath = str(pathlib.Path(path).relative_to(_ansible_module_base_path))
77
+ except ValueError:
78
+ return None # not ansible-core
79
+
80
+ namespace = 'ansible.builtin'
81
+
82
+ if match := re.match(r'plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', relpath):
83
+ plugin_name = match.group("plugin_name")
84
+ plugin_type = match.group("plugin_type")
85
+
86
+ if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
87
+ # The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
88
+ # We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin.
89
+ # Callers in this case need to identify the deprecating plugin name, otherwise only ansible-core will be reported.
90
+ # Reporting ansible-core is never wrong, it just may be missing an additional detail (plugin name) in the "on behalf of" case.
91
+ return ANSIBLE_CORE_DEPRECATOR
92
+ elif match := re.match(r'modules/(?P<module_name>\w+)', relpath):
93
+ # AnsiballZ Python package for core modules
94
+ plugin_name = match.group("module_name")
95
+ plugin_type = "module"
96
+ elif match := re.match(r'legacy/(?P<module_name>\w+)', relpath):
97
+ # AnsiballZ Python package for non-core library/role modules
98
+ namespace = 'ansible.legacy'
99
+
100
+ plugin_name = match.group("module_name")
101
+ plugin_type = "module"
102
+ else:
103
+ return ANSIBLE_CORE_DEPRECATOR # non-plugin core path, safe to use ansible-core for the same reason as the non-deprecator plugin type case above
104
+
105
+ name = f'{namespace}.{plugin_name}'
106
+
107
+ return PluginInfo(resolved_name=name, type=plugin_type)
108
+
109
+
110
+ def _path_as_collection_plugininfo(path: str) -> PluginInfo | None:
111
+ """Return a `PluginInfo` instance if the provided `path` refers to a collection plugin."""
112
+ if not (match := re.search(r'/ansible_collections/(?P<ns>\w+)/(?P<coll>\w+)/plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', path)):
113
+ return None
114
+
115
+ plugin_type = match.group('plugin_type')
116
+
117
+ if plugin_type in _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES:
118
+ # We're able to detect the namespace, collection and plugin type -- but we have no way to identify the plugin name currently.
119
+ # To keep things simple we'll fall back to just identifying the namespace and collection.
120
+ # In the future we could improve the detection and/or make it easier for a caller to identify the plugin name.
121
+ return PluginInfo._from_collection_name('.'.join((match.group('ns'), match.group('coll'))))
122
+
123
+ if plugin_type == 'modules':
124
+ plugin_type = 'module'
125
+
126
+ if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
127
+ # The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
128
+ # We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin.
129
+ # Callers in this case need to identify the deprecator to avoid ambiguity, since it could be the same collection or another collection.
130
+ return INDETERMINATE_DEPRECATOR
131
+
132
+ name = '.'.join((match.group('ns'), match.group('coll'), match.group('plugin_name')))
133
+
134
+ return PluginInfo(resolved_name=name, type=plugin_type)