From 43c0132caa1859fec830f26812cb895dfa96609b Mon Sep 17 00:00:00 2001 From: Matt Davis <6775756+nitzmahone@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:50:09 -0700 Subject: [PATCH] adjust PluginInfo to use PluginType enum (#85277) * normalization fixups Co-authored-by: Matt Clay --- .../_internal/_templating/_transform.py | 6 ++ .../_internal/_datatag/__init__.py | 24 +++++++- .../module_utils/_internal/_deprecator.py | 60 +++++++++---------- .../_internal/_json/_profiles/__init__.py | 1 + .../module_utils/_internal/_messages.py | 28 ++++++++- .../module_utils/_internal/_plugin_info.py | 15 ++++- lib/ansible/utils/display.py | 14 ++--- .../lookup_plugins/synthetic_plugin_info.py | 4 +- .../targets/protomatter/tasks/main.yml | 4 +- .../module_common/test_recursive_finder.py | 1 + .../module_utils/_internal/test_deprecator.py | 35 +++++------ .../module_utils/datatag/test_datatag.py | 11 +++- test/units/parsing/vault/test_vault.py | 1 + test/units/utils/test_display.py | 12 ++-- 14 files changed, 137 insertions(+), 79 deletions(-) diff --git a/lib/ansible/_internal/_templating/_transform.py b/lib/ansible/_internal/_templating/_transform.py index 6293341bb23..c812b43da5f 100644 --- a/lib/ansible/_internal/_templating/_transform.py +++ b/lib/ansible/_internal/_templating/_transform.py @@ -21,6 +21,11 @@ def plugin_info(value: _messages.PluginInfo) -> dict[str, str]: return dataclasses.asdict(value) +def plugin_type(value: _messages.PluginType) -> str: + """Render PluginType as a string.""" + return value.value + + def error_summary(value: _messages.ErrorSummary) -> str: """Render ErrorSummary as a formatted traceback for backward-compatibility with pre-2.19 TaskResult.exception.""" if _traceback._is_traceback_enabled(_traceback.TracebackEvent.ERROR): @@ -56,6 +61,7 @@ def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker: _type_transform_mapping: dict[type, t.Callable[[t.Any], t.Any]] = { _captured.CapturedErrorSummary: error_summary, _messages.PluginInfo: plugin_info, + _messages.PluginType: plugin_type, _messages.ErrorSummary: error_summary, _messages.WarningSummary: warning_summary, _messages.DeprecationSummary: deprecation_summary, diff --git a/lib/ansible/module_utils/_internal/_datatag/__init__.py b/lib/ansible/module_utils/_internal/_datatag/__init__.py index 5fba8623a32..e09a9bb195f 100644 --- a/lib/ansible/module_utils/_internal/_datatag/__init__.py +++ b/lib/ansible/module_utils/_internal/_datatag/__init__.py @@ -5,6 +5,7 @@ import collections.abc as c import copy import dataclasses import datetime +import enum import inspect import sys @@ -216,7 +217,7 @@ class AnsibleTagHelper: return value -class AnsibleSerializable(metaclass=abc.ABCMeta): +class AnsibleSerializable: __slots__ = _NO_INSTANCE_STORAGE _known_type_map: t.ClassVar[t.Dict[str, t.Type['AnsibleSerializable']]] = {} @@ -274,6 +275,27 @@ class AnsibleSerializable(metaclass=abc.ABCMeta): return f'{name}({arg_string})' +class AnsibleSerializableEnum(AnsibleSerializable, enum.Enum): + """Base class for serializable enumerations.""" + + def _as_dict(self) -> t.Dict[str, t.Any]: + return dict(value=self.value) + + @classmethod + def _from_dict(cls, d: t.Dict[str, t.Any]) -> t.Self: + return cls(d['value'].lower()) + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f'<{self.__class__.__name__}.{self.name}>' + + @staticmethod + def _generate_next_value_(name, start, count, last_values): + return name.lower() + + class AnsibleSerializableWrapper(AnsibleSerializable, t.Generic[_T], metaclass=abc.ABCMeta): __slots__ = ('_value',) diff --git a/lib/ansible/module_utils/_internal/_deprecator.py b/lib/ansible/module_utils/_internal/_deprecator.py index f31942a1938..e730b71c2be 100644 --- a/lib/ansible/module_utils/_internal/_deprecator.py +++ b/lib/ansible/module_utils/_internal/_deprecator.py @@ -5,7 +5,7 @@ import pathlib import sys import typing as t -from ansible.module_utils._internal import _stack, _messages, _validation +from ansible.module_utils._internal import _stack, _messages, _validation, _plugin_info def deprecator_from_collection_name(collection_name: str | None) -> _messages.PluginInfo | None: @@ -19,7 +19,7 @@ def deprecator_from_collection_name(collection_name: str | None) -> _messages.Pl return _messages.PluginInfo( resolved_name=collection_name, - type=_COLLECTION_ONLY_TYPE, + type=None, ) @@ -54,7 +54,7 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None: if match := re.match(r'plugins/(?P\w+)/(?P\w+)', relpath): plugin_name = match.group("plugin_name") - plugin_type = match.group("plugin_type") + plugin_type = _plugin_info.normalize_plugin_type(match.group("plugin_type")) if plugin_type not in _DEPRECATOR_PLUGIN_TYPES: # The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code. @@ -65,13 +65,13 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None: elif match := re.match(r'modules/(?P\w+)', relpath): # AnsiballZ Python package for core modules plugin_name = match.group("module_name") - plugin_type = "module" + plugin_type = _messages.PluginType.MODULE elif match := re.match(r'legacy/(?P\w+)', relpath): # AnsiballZ Python package for non-core library/role modules namespace = 'ansible.legacy' plugin_name = match.group("module_name") - plugin_type = "module" + plugin_type = _messages.PluginType.MODULE else: 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 @@ -85,7 +85,7 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None: if not (match := re.search(r'/ansible_collections/(?P\w+)/(?P\w+)/plugins/(?P\w+)/(?P\w+)', path)): return None - plugin_type = match.group('plugin_type') + plugin_type = _plugin_info.normalize_plugin_type(match.group('plugin_type')) if plugin_type in _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES: # We're able to detect the namespace, collection and plugin type -- but we have no way to identify the plugin name currently. @@ -93,9 +93,6 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None: # In the future we could improve the detection and/or make it easier for a caller to identify the plugin name. return deprecator_from_collection_name('.'.join((match.group('ns'), match.group('coll')))) - if plugin_type == 'modules': - plugin_type = 'module' - if plugin_type not in _DEPRECATOR_PLUGIN_TYPES: # The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code. # We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin. @@ -107,46 +104,43 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None: return _messages.PluginInfo(resolved_name=name, type=plugin_type) -_COLLECTION_ONLY_TYPE: t.Final = 'collection' -"""Ersatz placeholder plugin type for use by a `PluginInfo` instance that references only a collection.""" - _ANSIBLE_MODULE_BASE_PATH: t.Final = pathlib.Path(sys.modules['ansible'].__file__).parent """Runtime-detected base path of the `ansible` Python package to distinguish between Ansible-owned and external code.""" ANSIBLE_CORE_DEPRECATOR: t.Final = deprecator_from_collection_name('ansible.builtin') """Singleton `PluginInfo` instance for ansible-core callers where the plugin can/should not be identified in messages.""" -INDETERMINATE_DEPRECATOR: t.Final = _messages.PluginInfo(resolved_name='indeterminate', type='indeterminate') +INDETERMINATE_DEPRECATOR: t.Final = _messages.PluginInfo(resolved_name=None, type=None) """Singleton `PluginInfo` instance for indeterminate deprecator.""" _DEPRECATOR_PLUGIN_TYPES: t.Final = frozenset( { - 'action', - 'become', - 'cache', - 'callback', - 'cliconf', - 'connection', - # doc_fragments - no code execution - # filter - basename inadequate to identify plugin - 'httpapi', - 'inventory', - 'lookup', - 'module', # only for collections - 'netconf', - 'shell', - 'strategy', - 'terminal', - # test - basename inadequate to identify plugin - 'vars', + _messages.PluginType.ACTION, + _messages.PluginType.BECOME, + _messages.PluginType.CACHE, + _messages.PluginType.CALLBACK, + _messages.PluginType.CLICONF, + _messages.PluginType.CONNECTION, + # DOC_FRAGMENTS - no code execution + # FILTER - basename inadequate to identify plugin + _messages.PluginType.HTTPAPI, + _messages.PluginType.INVENTORY, + _messages.PluginType.LOOKUP, + _messages.PluginType.MODULE, # only for collections + _messages.PluginType.NETCONF, + _messages.PluginType.SHELL, + _messages.PluginType.STRATEGY, + _messages.PluginType.TERMINAL, + # TEST - basename inadequate to identify plugin + _messages.PluginType.VARS, } ) """Plugin types which are valid for identifying a deprecator for deprecation purposes.""" _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES: t.Final = frozenset( { - 'filter', - 'test', + _messages.PluginType.FILTER, + _messages.PluginType.TEST, } ) """Plugin types for which basename cannot be used to identify the plugin name.""" diff --git a/lib/ansible/module_utils/_internal/_json/_profiles/__init__.py b/lib/ansible/module_utils/_internal/_json/_profiles/__init__.py index 73fb1f5f47e..25163175b4c 100644 --- a/lib/ansible/module_utils/_internal/_json/_profiles/__init__.py +++ b/lib/ansible/module_utils/_internal/_json/_profiles/__init__.py @@ -87,6 +87,7 @@ For controller-to-module, type behavior is profile dependent. _common_module_response_types: frozenset[type[AnsibleSerializable]] = frozenset( { _messages.PluginInfo, + _messages.PluginType, _messages.Event, _messages.EventChain, _messages.ErrorSummary, diff --git a/lib/ansible/module_utils/_internal/_messages.py b/lib/ansible/module_utils/_internal/_messages.py index 7c1634f12d6..c03fc687e4b 100644 --- a/lib/ansible/module_utils/_internal/_messages.py +++ b/lib/ansible/module_utils/_internal/_messages.py @@ -8,6 +8,7 @@ A future release will remove the provisional status. from __future__ import annotations as _annotations import dataclasses as _dataclasses +import enum as _enum import sys as _sys import typing as _t @@ -21,14 +22,37 @@ else: _dataclass_kwargs = dict(frozen=True) +class PluginType(_datatag.AnsibleSerializableEnum): + """Enum of Ansible plugin types.""" + + ACTION = _enum.auto() + BECOME = _enum.auto() + CACHE = _enum.auto() + CALLBACK = _enum.auto() + CLICONF = _enum.auto() + CONNECTION = _enum.auto() + DOC_FRAGMENTS = _enum.auto() + FILTER = _enum.auto() + HTTPAPI = _enum.auto() + INVENTORY = _enum.auto() + LOOKUP = _enum.auto() + MODULE = _enum.auto() + NETCONF = _enum.auto() + SHELL = _enum.auto() + STRATEGY = _enum.auto() + TERMINAL = _enum.auto() + TEST = _enum.auto() + VARS = _enum.auto() + + @_dataclasses.dataclass(**_dataclass_kwargs) class PluginInfo(_datatag.AnsibleSerializableDataclass): """Information about a loaded plugin.""" - resolved_name: str + resolved_name: _t.Optional[str] """The resolved canonical plugin name; always fully-qualified for collection plugins.""" - type: str + type: _t.Optional[PluginType] """The plugin type.""" diff --git a/lib/ansible/module_utils/_internal/_plugin_info.py b/lib/ansible/module_utils/_internal/_plugin_info.py index a4d5776746c..2efb636b7e1 100644 --- a/lib/ansible/module_utils/_internal/_plugin_info.py +++ b/lib/ansible/module_utils/_internal/_plugin_info.py @@ -21,5 +21,18 @@ def get_plugin_info(value: HasPluginInfo) -> _messages.PluginInfo: """Utility method that returns a `PluginInfo` from an object implementing the `HasPluginInfo` protocol.""" return _messages.PluginInfo( resolved_name=value.ansible_name, - type=value.plugin_type, + type=normalize_plugin_type(value.plugin_type), ) + + +def normalize_plugin_type(value: str) -> _messages.PluginType | None: + """Normalize value and return it as a PluginType, or None if the value does match any known plugin type.""" + value = value.lower() + + if value == 'modules': + value = 'module' + + try: + return _messages.PluginType(value) + except ValueError: + return None diff --git a/lib/ansible/utils/display.py b/lib/ansible/utils/display.py index dbd9d2e4160..cbdebe2fda4 100644 --- a/lib/ansible/utils/display.py +++ b/lib/ansible/utils/display.py @@ -603,20 +603,18 @@ class Display(metaclass=Singleton): else: removal_fragment = 'This feature will be removed' - if not deprecator or deprecator.type == _deprecator.INDETERMINATE_DEPRECATOR.type: - collection = None - plugin_fragment = '' - elif deprecator.type == _deprecator._COLLECTION_ONLY_TYPE: - collection = deprecator.resolved_name + if not deprecator or not deprecator.type: + # indeterminate has no resolved_name or type + # collections have a resolved_name but no type + collection = deprecator.resolved_name if deprecator else None plugin_fragment = '' else: parts = deprecator.resolved_name.split('.') plugin_name = parts[-1] - # DTFIX1: normalize 'modules' -> 'module' before storing it so we can eliminate the normalization here - plugin_type = "module" if deprecator.type in ("module", "modules") else f'{deprecator.type} plugin' + plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin' collection = '.'.join(parts[:2]) if len(parts) > 2 else None - plugin_fragment = f'{plugin_type} {plugin_name!r}' + plugin_fragment = f'{plugin_type_name} {plugin_name!r}' if collection and plugin_fragment: plugin_fragment += ' in' diff --git a/test/integration/targets/protomatter/lookup_plugins/synthetic_plugin_info.py b/test/integration/targets/protomatter/lookup_plugins/synthetic_plugin_info.py index 9daf8b98278..5b8ae55dc12 100644 --- a/test/integration/targets/protomatter/lookup_plugins/synthetic_plugin_info.py +++ b/test/integration/targets/protomatter/lookup_plugins/synthetic_plugin_info.py @@ -7,6 +7,6 @@ from ansible.plugins.lookup import LookupBase class LookupModule(LookupBase): def run(self, terms, variables=None, **kwargs): return [_messages.PluginInfo( - resolved_name='resolved_name', - type='type', + resolved_name='ns.col.module', + type=_messages.PluginType.MODULE, )] diff --git a/test/integration/targets/protomatter/tasks/main.yml b/test/integration/targets/protomatter/tasks/main.yml index 41a834ed207..4931deeeb2a 100644 --- a/test/integration/targets/protomatter/tasks/main.yml +++ b/test/integration/targets/protomatter/tasks/main.yml @@ -43,8 +43,8 @@ vars: some_var: Hello expected_plugin_info: - resolved_name: resolved_name - type: type + resolved_name: ns.col.module + type: module - name: test the python_literal_eval filter assert: diff --git a/test/units/executor/module_common/test_recursive_finder.py b/test/units/executor/module_common/test_recursive_finder.py index 05fb23fcf31..01d26fe2524 100644 --- a/test/units/executor/module_common/test_recursive_finder.py +++ b/test/units/executor/module_common/test_recursive_finder.py @@ -48,6 +48,7 @@ MODULE_UTILS_BASIC_FILES = frozenset(('ansible/__init__.py', 'ansible/module_utils/_internal/_patches/_socket_patch.py', 'ansible/module_utils/_internal/_patches/_sys_intern_patch.py', 'ansible/module_utils/_internal/_patches/__init__.py', + 'ansible/module_utils/_internal/_plugin_info.py', 'ansible/module_utils/_internal/_stack.py', 'ansible/module_utils/_internal/_text_utils.py', 'ansible/module_utils/common/collections.py', diff --git a/test/units/module_utils/_internal/test_deprecator.py b/test/units/module_utils/_internal/test_deprecator.py index 76f9f45546a..958d6c631dc 100644 --- a/test/units/module_utils/_internal/test_deprecator.py +++ b/test/units/module_utils/_internal/test_deprecator.py @@ -33,26 +33,25 @@ def do_stuff(): return super().exec_module(module) -@pytest.mark.parametrize("python_fq_name,expected_resolved_name,expected_plugin_type", ( +@pytest.mark.parametrize("python_fq_name,expected_plugin_info", ( # legacy module callers - ('ansible.legacy.blah', 'ansible.legacy.blah', 'module'), + ('ansible.legacy.blah', _messages.PluginInfo(resolved_name='ansible.legacy.blah', type=_messages.PluginType.MODULE)), # core callers - ('ansible.modules.ping', 'ansible.builtin.ping', 'module'), - ('ansible.plugins.filters.core', _deprecator.ANSIBLE_CORE_DEPRECATOR.resolved_name, _deprecator.ANSIBLE_CORE_DEPRECATOR.type), - ('ansible.plugins.tests.core', _deprecator.ANSIBLE_CORE_DEPRECATOR.resolved_name, _deprecator.ANSIBLE_CORE_DEPRECATOR.type), - ('ansible.nonplugin_something', _deprecator.ANSIBLE_CORE_DEPRECATOR.resolved_name, _deprecator.ANSIBLE_CORE_DEPRECATOR.type), + ('ansible.modules.ping', _messages.PluginInfo(resolved_name='ansible.builtin.ping', type=_messages.PluginType.MODULE)), + ('ansible.plugins.filter.core', _deprecator.ANSIBLE_CORE_DEPRECATOR), + ('ansible.plugins.test.core', _deprecator.ANSIBLE_CORE_DEPRECATOR), + ('ansible.nonplugin_something', _deprecator.ANSIBLE_CORE_DEPRECATOR), # collections plugin callers - ('ansible_collections.foo.bar.plugins.modules.module_thing', 'foo.bar.module_thing', 'module'), - ('ansible_collections.foo.bar.plugins.filter.somefilter', 'foo.bar', _deprecator._COLLECTION_ONLY_TYPE), - ('ansible_collections.foo.bar.plugins.test.sometest', 'foo.bar', _deprecator._COLLECTION_ONLY_TYPE), + ('ansible_collections.foo.bar.plugins.modules.module_thing', _messages.PluginInfo(resolved_name='foo.bar.module_thing', type=_messages.PluginType.MODULE)), + ('ansible_collections.foo.bar.plugins.filter.somefilter', _messages.PluginInfo(resolved_name='foo.bar', type=None)), + ('ansible_collections.foo.bar.plugins.test.sometest', _messages.PluginInfo(resolved_name='foo.bar', type=None)), # indeterminate callers (e.g. collection module_utils- must specify since they might be calling on behalf of another - ('ansible_collections.foo.bar.plugins.module_utils.something', - _deprecator.INDETERMINATE_DEPRECATOR.resolved_name, _deprecator.INDETERMINATE_DEPRECATOR.type), + ('ansible_collections.foo.bar.plugins.module_utils.something', _deprecator.INDETERMINATE_DEPRECATOR), # other callers - ('something.else', None, None), - ('ansible_collections.foo.bar.nonplugin_something', None, None), + ('something.else', None), + ('ansible_collections.foo.bar.nonplugin_something', None), )) -def test_get_caller_plugin_info(python_fq_name: str, expected_resolved_name: str, expected_plugin_type: str): +def test_get_caller_plugin_info(python_fq_name: str, expected_plugin_info: _messages.PluginInfo): """Validates the expected `PluginInfo` values received from various types of core/non-core/collection callers.""" # invoke a standalone fake loader that generates a Python module with the specified FQ python name (converted to a corresponding __file__ entry) that # pretends as if it called `get_caller_plugin_info()` and returns its result @@ -64,10 +63,4 @@ def test_get_caller_plugin_info(python_fq_name: str, expected_resolved_name: str pi: _messages.PluginInfo = mod.do_stuff() - if not expected_resolved_name and not expected_plugin_type: - assert pi is None - return - - assert pi is not None - assert pi.resolved_name == expected_resolved_name - assert pi.type == expected_plugin_type + assert pi == expected_plugin_info diff --git a/test/units/module_utils/datatag/test_datatag.py b/test/units/module_utils/datatag/test_datatag.py index 9d88561d0c1..20ffdd00bdf 100644 --- a/test/units/module_utils/datatag/test_datatag.py +++ b/test/units/module_utils/datatag/test_datatag.py @@ -5,6 +5,7 @@ import collections.abc as c import copy import dataclasses import datetime +import enum import inspect import json @@ -26,6 +27,7 @@ from ansible.module_utils._internal import _messages from ansible.module_utils._internal._datatag import ( AnsibleSerializable, + AnsibleSerializableEnum, AnsibleSingletonTagBase, AnsibleTaggedObject, NotTaggableError, @@ -83,7 +85,8 @@ message_instances = [ _messages.ErrorSummary(event=_messages.Event(msg="bla", formatted_traceback="tb")), _messages.WarningSummary(event=_messages.Event(msg="bla", formatted_source_context="sc", formatted_traceback="tb")), _messages.DeprecationSummary(event=_messages.Event(msg="bla", formatted_source_context="sc", formatted_traceback="tb"), version="1.2.3"), - _messages.PluginInfo(resolved_name='a.b.c', type='module'), + _messages.PluginInfo(resolved_name='a.b.c', type=_messages.PluginType.MODULE), + _messages.PluginType.MODULE, ] @@ -97,7 +100,7 @@ def assert_round_trip(original_value, round_tripped_value, via_copy=False): return # singleton values should rehydrate as the shared singleton instance, all others should be a new instance - if isinstance(original_value, AnsibleSingletonTagBase): + if isinstance(original_value, (AnsibleSingletonTagBase, enum.Enum)): assert original_value is round_tripped_value else: assert original_value is not round_tripped_value @@ -483,6 +486,8 @@ class TestDatatagTarget(AutoParamSupport): excluded_type_names = { AnsibleTaggedObject.__name__, # base class, cannot be abstract AnsibleSerializableDataclass.__name__, # base class, cannot be abstract + AnsibleSerializable.__name__, # base class, cannot be abstract + AnsibleSerializableEnum.__name__, # base class, cannot be abstract # these types are all controller-only, so it's easier to have static type names instead of importing them 'JinjaConstTemplate', # serialization not required '_EncryptedSource', # serialization not required @@ -643,7 +648,7 @@ class TestDatatagTarget(AutoParamSupport): """Assert that __slots__ are properly defined on the given serializable type.""" if value in (AnsibleSerializable, AnsibleTaggedObject): expect_slots = True # non-dataclass base types have no attributes, but still use slots - elif issubclass(value, (int, bytes, tuple)): + elif issubclass(value, (int, bytes, tuple, enum.Enum)): # non-empty slots are not supported by these variable-length data types # see: https://docs.python.org/3/reference/datamodel.html expect_slots = False diff --git a/test/units/parsing/vault/test_vault.py b/test/units/parsing/vault/test_vault.py index fa705dda8a3..9f83f3092fd 100644 --- a/test/units/parsing/vault/test_vault.py +++ b/test/units/parsing/vault/test_vault.py @@ -837,6 +837,7 @@ class TestVaultLib(unittest.TestCase): def test_verify_encrypted_string_methods(): """Verify all relevant methods on `str` are implemented on `EncryptedString`.""" str_methods_to_not_implement = { + '__class__', '__dir__', '__getattribute__', # generated by Python, identical to the one on `str` in Python 3.13+, but different on earlier versions '__getnewargs__', diff --git a/test/units/utils/test_display.py b/test/units/utils/test_display.py index 5e6f51d843a..b5a7b4da0a5 100644 --- a/test/units/utils/test_display.py +++ b/test/units/utils/test_display.py @@ -182,14 +182,14 @@ Origin: /some/path A_DATE = datetime.date(2025, 1, 1) CORE = deprecator_from_collection_name('ansible.builtin') -CORE_MODULE = _messages.PluginInfo(resolved_name='ansible.builtin.ping', type='module') -CORE_PLUGIN = _messages.PluginInfo(resolved_name='ansible.builtin.debug', type='action') +CORE_MODULE = _messages.PluginInfo(resolved_name='ansible.builtin.ping', type=_messages.PluginType.MODULE) +CORE_PLUGIN = _messages.PluginInfo(resolved_name='ansible.builtin.debug', type=_messages.PluginType.ACTION) COLL = deprecator_from_collection_name('ns.col') -COLL_MODULE = _messages.PluginInfo(resolved_name='ns.col.ping', type='module') -COLL_PLUGIN = _messages.PluginInfo(resolved_name='ns.col.debug', type='action') +COLL_MODULE = _messages.PluginInfo(resolved_name='ns.col.ping', type=_messages.PluginType.MODULE) +COLL_PLUGIN = _messages.PluginInfo(resolved_name='ns.col.debug', type=_messages.PluginType.ACTION) INDETERMINATE = _deprecator.INDETERMINATE_DEPRECATOR -LEGACY_MODULE = _messages.PluginInfo(resolved_name='ping', type='module') -LEGACY_PLUGIN = _messages.PluginInfo(resolved_name='debug', type='action') +LEGACY_MODULE = _messages.PluginInfo(resolved_name='ping', type=_messages.PluginType.MODULE) +LEGACY_PLUGIN = _messages.PluginInfo(resolved_name='debug', type=_messages.PluginType.ACTION) @pytest.mark.parametrize('kwargs, expected', (