diff --git a/lib/ansible/config/manager.py b/lib/ansible/config/manager.py index e4e7b6a8e95..4bbd9cbf9d0 100644 --- a/lib/ansible/config/manager.py +++ b/lib/ansible/config/manager.py @@ -17,6 +17,7 @@ from collections.abc import Mapping, Sequence from jinja2.nativetypes import NativeEnvironment from ansible.errors import AnsibleOptionsError, AnsibleError, AnsibleRequiredOptionError +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_text, to_bytes, to_native from ansible.module_utils.common.yaml import yaml_load from ansible.module_utils.six import string_types @@ -232,15 +233,13 @@ def find_ini_config_file(warnings=None): # Note: In this case, warnings does nothing warnings = set() - # A value that can never be a valid path so that we can tell if ANSIBLE_CONFIG was set later - # We can't use None because we could set path to None. - SENTINEL = object - potential_paths = [] + # A value that can never be a valid path so that we can tell if ANSIBLE_CONFIG was set later + # We can't use None because we could set path to None. # Environment setting - path_from_env = os.getenv("ANSIBLE_CONFIG", SENTINEL) - if path_from_env is not SENTINEL: + path_from_env = os.getenv("ANSIBLE_CONFIG", Sentinel) + if path_from_env is not Sentinel: path_from_env = unfrackpath(path_from_env, follow=False) if os.path.isdir(to_bytes(path_from_env)): path_from_env = os.path.join(path_from_env, "ansible.cfg") diff --git a/lib/ansible/galaxy/collection/__init__.py b/lib/ansible/galaxy/collection/__init__.py index b2c83ee8c30..829f7aa19d2 100644 --- a/lib/ansible/galaxy/collection/__init__.py +++ b/lib/ansible/galaxy/collection/__init__.py @@ -126,13 +126,13 @@ from ansible.galaxy.dependency_resolution.dataclasses import ( from ansible.galaxy.dependency_resolution.versioning import meets_requirements from ansible.plugins.loader import get_all_plugin_loaders from ansible.module_utils.common.file import S_IRWU_RG_RO, S_IRWXU_RXG_RXO, S_IXANY +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.collections import is_sequence from ansible.module_utils.common.yaml import yaml_dump from ansible.utils.collection_loader import AnsibleCollectionRef from ansible.utils.display import Display from ansible.utils.hashing import secure_hash, secure_hash_s -from ansible.utils.sentinel import Sentinel display = Display() diff --git a/lib/ansible/galaxy/collection/concrete_artifact_manager.py b/lib/ansible/galaxy/collection/concrete_artifact_manager.py index 06c1cf6f93b..7ff6b31a10e 100644 --- a/lib/ansible/galaxy/collection/concrete_artifact_manager.py +++ b/lib/ansible/galaxy/collection/concrete_artifact_manager.py @@ -33,10 +33,10 @@ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_ from ansible.module_utils.api import retry_with_delays_and_condition from ansible.module_utils.api import generate_jittered_backoff from ansible.module_utils.common.process import get_bin_path +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.yaml import yaml_load from ansible.module_utils.urls import open_url from ansible.utils.display import Display -from ansible.utils.sentinel import Sentinel import yaml diff --git a/lib/ansible/galaxy/token.py b/lib/ansible/galaxy/token.py index 573d1b3a56c..eb06a34181f 100644 --- a/lib/ansible/galaxy/token.py +++ b/lib/ansible/galaxy/token.py @@ -30,6 +30,7 @@ from urllib.error import HTTPError from ansible import constants as C from ansible.galaxy.api import GalaxyError from ansible.galaxy.user_agent import user_agent +from ansible.module_utils.common.sentinel import Sentinel as NoTokenSentinel from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.yaml import yaml_dump, yaml_load from ansible.module_utils.urls import open_url @@ -38,12 +39,6 @@ from ansible.utils.display import Display display = Display() -class NoTokenSentinel(object): - """ Represents an ansible.cfg server with not token defined (will ignore cmdline and GALAXY_TOKEN_PATH. """ - def __new__(cls, *args, **kwargs): - return cls - - class KeycloakToken(object): '''A token granted by a Keycloak server. diff --git a/lib/ansible/module_utils/common/sentinel.py b/lib/ansible/module_utils/common/sentinel.py new file mode 100644 index 00000000000..0fdbf4ce318 --- /dev/null +++ b/lib/ansible/module_utils/common/sentinel.py @@ -0,0 +1,66 @@ +# Copyright (c) 2019 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + + +class Sentinel: + """ + Object which can be used to mark whether an entry as being special + + A sentinel value demarcates a value or marks an entry as having a special meaning. In C, the + Null byte is used as a sentinel for the end of a string. In Python, None is often used as + a Sentinel in optional parameters to mean that the parameter was not set by the user. + + You should use None as a Sentinel value any Python code where None is not a valid entry. If + None is a valid entry, though, then you need to create a different value, which is the purpose + of this class. + + Example of using Sentinel as a default parameter value:: + + def confirm_big_red_button(tristate=Sentinel): + if tristate is Sentinel: + print('You must explicitly press the big red button to blow up the base') + elif tristate is True: + print('Countdown to destruction activated') + elif tristate is False: + print('Countdown stopped') + elif tristate is None: + print('Waiting for more input') + + Example of using Sentinel to tell whether a dict which has a default value has been changed:: + + values = {'one': Sentinel, 'two': Sentinel} + defaults = {'one': 1, 'two': 2} + + # [.. Other code which does things including setting a new value for 'one' ..] + values['one'] = None + # [..] + + print('You made changes to:') + for key, value in values.items(): + if value is Sentinel: + continue + print('%s: %s' % (key, value) + """ + + def __new__(cls): + """ + Return the cls itself. This makes both equality and identity True for comparing the class + to an instance of the class, preventing common usage errors. + + Preferred usage:: + + a = Sentinel + if a is Sentinel: + print('Sentinel value') + + However, these are True as well, eliminating common usage errors:: + + if Sentinel is Sentinel(): + print('Sentinel value') + + if Sentinel == Sentinel(): + print('Sentinel value') + """ + return cls diff --git a/lib/ansible/modules/file.py b/lib/ansible/modules/file.py index ba901404a20..e2c15333132 100644 --- a/lib/ansible/modules/file.py +++ b/lib/ansible/modules/file.py @@ -239,7 +239,7 @@ from grp import getgrnam, getgrgid from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_bytes, to_native - +from ansible.module_utils.common.sentinel import Sentinel # There will only be a single AnsibleModule object per module module = None @@ -257,11 +257,6 @@ class ParameterError(AnsibleModuleError): pass -class Sentinel(object): - def __new__(cls, *args, **kwargs): - return cls - - def _ansible_excepthook(exc_type, exc_value, tb): # Using an exception allows us to catch it if the calling code knows it can recover if issubclass(exc_type, AnsibleModuleError): diff --git a/lib/ansible/parsing/mod_args.py b/lib/ansible/parsing/mod_args.py index cfa9574ede4..b4956bc3f98 100644 --- a/lib/ansible/parsing/mod_args.py +++ b/lib/ansible/parsing/mod_args.py @@ -20,12 +20,12 @@ from __future__ import annotations import ansible.constants as C from ansible.errors import AnsibleParserError, AnsibleError, AnsibleAssertionError from ansible.module_utils.six import string_types +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_text from ansible.parsing.splitter import parse_kv, split_args from ansible.plugins.loader import module_loader, action_loader from ansible.template import Templar from ansible.utils.fqcn import add_internal_fqcns -from ansible.utils.sentinel import Sentinel # modules formated for user msg diff --git a/lib/ansible/playbook/attribute.py b/lib/ansible/playbook/attribute.py index bf3975548ab..ee797c27ef4 100644 --- a/lib/ansible/playbook/attribute.py +++ b/lib/ansible/playbook/attribute.py @@ -17,7 +17,7 @@ from __future__ import annotations -from ansible.utils.sentinel import Sentinel +from ansible.module_utils.common.sentinel import Sentinel _CONTAINERS = frozenset(('list', 'dict', 'set')) diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py index 46483efda2d..729344a2055 100644 --- a/lib/ansible/playbook/base.py +++ b/lib/ansible/playbook/base.py @@ -19,13 +19,13 @@ from ansible import context from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError from ansible.module_utils.six import string_types from ansible.module_utils.parsing.convert_bool import boolean +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_text from ansible.parsing.dataloader import DataLoader from ansible.playbook.attribute import Attribute, FieldAttribute, ConnectionFieldAttribute, NonInheritableFieldAttribute from ansible.plugins.loader import module_loader, action_loader from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, AnsibleCollectionRef from ansible.utils.display import Display -from ansible.utils.sentinel import Sentinel from ansible.utils.vars import combine_vars, isidentifier, get_unique_id display = Display() diff --git a/lib/ansible/playbook/block.py b/lib/ansible/playbook/block.py index 9c82ed27900..fa8c13ee49d 100644 --- a/lib/ansible/playbook/block.py +++ b/lib/ansible/playbook/block.py @@ -19,6 +19,7 @@ from __future__ import annotations import ansible.constants as C from ansible.errors import AnsibleParserError +from ansible.module_utils.common.sentinel import Sentinel from ansible.playbook.attribute import NonInheritableFieldAttribute from ansible.playbook.base import Base from ansible.playbook.conditional import Conditional @@ -28,7 +29,6 @@ from ansible.playbook.helpers import load_list_of_tasks from ansible.playbook.notifiable import Notifiable from ansible.playbook.role import Role from ansible.playbook.taggable import Taggable -from ansible.utils.sentinel import Sentinel class Block(Base, Conditional, CollectionSearch, Taggable, Notifiable, Delegatable): diff --git a/lib/ansible/playbook/role/__init__.py b/lib/ansible/playbook/role/__init__.py index c37f4be6dbe..05e6bef044a 100644 --- a/lib/ansible/playbook/role/__init__.py +++ b/lib/ansible/playbook/role/__init__.py @@ -24,6 +24,7 @@ from types import MappingProxyType from ansible import constants as C from ansible.errors import AnsibleError, AnsibleParserError, AnsibleAssertionError +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.six import binary_type, text_type from ansible.playbook.attribute import FieldAttribute @@ -37,7 +38,6 @@ from ansible.playbook.taggable import Taggable from ansible.plugins.loader import add_all_plugin_dirs from ansible.utils.collection_loader import AnsibleCollectionConfig from ansible.utils.path import is_subpath -from ansible.utils.sentinel import Sentinel from ansible.utils.vars import combine_vars __all__ = ['Role', 'hash_params'] diff --git a/lib/ansible/playbook/taggable.py b/lib/ansible/playbook/taggable.py index fa1d5b203f3..3b481270a65 100644 --- a/lib/ansible/playbook/taggable.py +++ b/lib/ansible/playbook/taggable.py @@ -19,9 +19,9 @@ from __future__ import annotations from ansible.errors import AnsibleError from ansible.module_utils.six import string_types +from ansible.module_utils.common.sentinel import Sentinel from ansible.playbook.attribute import FieldAttribute from ansible.template import Templar -from ansible.utils.sentinel import Sentinel def _flatten_tags(tags: list) -> list: diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 431501b4af5..d42e1df77ea 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -19,6 +19,7 @@ from __future__ import annotations from ansible import constants as C from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.six import string_types from ansible.parsing.mod_args import ModuleArgsParser @@ -36,7 +37,7 @@ from ansible.playbook.role import Role from ansible.playbook.taggable import Taggable from ansible.utils.collection_loader import AnsibleCollectionConfig from ansible.utils.display import Display -from ansible.utils.sentinel import Sentinel + from ansible.utils.vars import isidentifier __all__ = ['Task'] diff --git a/lib/ansible/playbook/task_include.py b/lib/ansible/playbook/task_include.py index 4f354cae5fc..94f57e53264 100644 --- a/lib/ansible/playbook/task_include.py +++ b/lib/ansible/playbook/task_include.py @@ -19,10 +19,10 @@ from __future__ import annotations import ansible.constants as C from ansible.errors import AnsibleParserError +from ansible.module_utils.common.sentinel import Sentinel from ansible.playbook.block import Block from ansible.playbook.task import Task from ansible.utils.display import Display -from ansible.utils.sentinel import Sentinel __all__ = ['TaskInclude'] diff --git a/lib/ansible/plugins/lookup/config.py b/lib/ansible/plugins/lookup/config.py index 093c1a50036..b31cb057efa 100644 --- a/lib/ansible/plugins/lookup/config.py +++ b/lib/ansible/plugins/lookup/config.py @@ -84,10 +84,10 @@ import ansible.plugins.loader as plugin_loader from ansible import constants as C from ansible.errors import AnsibleError, AnsibleLookupError, AnsibleOptionsError +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.six import string_types from ansible.plugins.lookup import LookupBase -from ansible.utils.sentinel import Sentinel class MissingSetting(AnsibleOptionsError): diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 1d8af833616..70073224a46 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -41,6 +41,7 @@ from ansible.executor.process.worker import WorkerProcess from ansible.executor.task_result import TaskResult from ansible.executor.task_queue_manager import CallbackSend, DisplaySend, PromptSend from ansible.module_utils.six import string_types +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.connection import Connection, ConnectionError from ansible.playbook.conditional import Conditional @@ -53,7 +54,6 @@ from ansible.template import Templar from ansible.utils.display import Display from ansible.utils.fqcn import add_internal_fqcns from ansible.utils.unsafe_proxy import wrap_var -from ansible.utils.sentinel import Sentinel from ansible.utils.vars import combine_vars from ansible.vars.clean import strip_internal_keys, module_response_deepcopy diff --git a/lib/ansible/utils/sentinel.py b/lib/ansible/utils/sentinel.py index 0fdbf4ce318..6aa21a17a21 100644 --- a/lib/ansible/utils/sentinel.py +++ b/lib/ansible/utils/sentinel.py @@ -1,66 +1,8 @@ -# Copyright (c) 2019 Ansible Project +# -*- coding: utf-8 -*- +# Copyright: Contributors to the Ansible project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import annotations - -class Sentinel: - """ - Object which can be used to mark whether an entry as being special - - A sentinel value demarcates a value or marks an entry as having a special meaning. In C, the - Null byte is used as a sentinel for the end of a string. In Python, None is often used as - a Sentinel in optional parameters to mean that the parameter was not set by the user. - - You should use None as a Sentinel value any Python code where None is not a valid entry. If - None is a valid entry, though, then you need to create a different value, which is the purpose - of this class. - - Example of using Sentinel as a default parameter value:: - - def confirm_big_red_button(tristate=Sentinel): - if tristate is Sentinel: - print('You must explicitly press the big red button to blow up the base') - elif tristate is True: - print('Countdown to destruction activated') - elif tristate is False: - print('Countdown stopped') - elif tristate is None: - print('Waiting for more input') - - Example of using Sentinel to tell whether a dict which has a default value has been changed:: - - values = {'one': Sentinel, 'two': Sentinel} - defaults = {'one': 1, 'two': 2} - - # [.. Other code which does things including setting a new value for 'one' ..] - values['one'] = None - # [..] - - print('You made changes to:') - for key, value in values.items(): - if value is Sentinel: - continue - print('%s: %s' % (key, value) - """ - - def __new__(cls): - """ - Return the cls itself. This makes both equality and identity True for comparing the class - to an instance of the class, preventing common usage errors. - - Preferred usage:: - - a = Sentinel - if a is Sentinel: - print('Sentinel value') - - However, these are True as well, eliminating common usage errors:: - - if Sentinel is Sentinel(): - print('Sentinel value') - - if Sentinel == Sentinel(): - print('Sentinel value') - """ - return cls +# For Backward compatibility +from ansible.module_utils.common.sentinel import Sentinel # pylint: disable=unused-import diff --git a/test/units/galaxy/test_collection.py b/test/units/galaxy/test_collection.py index 63bc55dae3e..bfabd81d9ee 100644 --- a/test/units/galaxy/test_collection.py +++ b/test/units/galaxy/test_collection.py @@ -22,13 +22,13 @@ from ansible.cli.galaxy import GalaxyCLI from ansible.config import manager from ansible.errors import AnsibleError from ansible.galaxy import api, collection, token +from ansible.module_utils.common.sentinel import Sentinel from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.file import S_IRWU_RG_RO import builtins from ansible.utils import context_objects as co from ansible.utils.display import Display from ansible.utils.hashing import secure_hash_s -from ansible.utils.sentinel import Sentinel @pytest.fixture(autouse='function') diff --git a/test/units/parsing/test_mod_args.py b/test/units/parsing/test_mod_args.py index 0bb0c954da8..472b5465e87 100644 --- a/test/units/parsing/test_mod_args.py +++ b/test/units/parsing/test_mod_args.py @@ -7,9 +7,9 @@ from __future__ import annotations import pytest from ansible.errors import AnsibleParserError +from ansible.module_utils.common.sentinel import Sentinel from ansible.parsing.mod_args import ModuleArgsParser from ansible.plugins.loader import init_plugin_loader -from ansible.utils.sentinel import Sentinel class TestModArgsDwim: