From 2cbfd1e350cbe1ca195d33306b5a9628667ddda8 Mon Sep 17 00:00:00 2001 From: Sam Doran Date: Tue, 20 Apr 2021 15:40:53 -0400 Subject: [PATCH] Add porting guide and documentation for changes to argument spec validation (#74268) * Add ArgumentSpecValidator to docs * Improve docs for ArgumentSpecValidator * Document removal of private methods * Update module_utils documentation - Add docs for argument spec classes as well as validation and parameters files. - preserve the order in the source for errors.py - document DEFAULT_TYPE_VALIDATORS so it can be referenced elsewhere - fix automodule directive for validation.py * Update docs in arg_spec and paremeters - This improves the generated documentation. * Document breaking changes in porting guide. * Update formatting in porting guide and add a Deprecated section * Fine tune module_utils documentation * Move instance docstring to the __init__ method Remove optional description since it fails the sanity test and I am not 100% it is valid anyway. * Remoe incorrect parameter from docstring This was changed a while ago but wasn't removed from the docstring. * Use attr rather than attribute The py:attribute: domain only exists in newer Sphinx >= 3.1. * Improve documentation on exceptions * Final pass - use args/kwargs instead of param - fix formatting errors that didn't display examples correctly - format TypeErrors so they are referenced as classes - specify complex types --- .../porting_guide_core_2.11.rst | 65 ++++++++-- .../rst/reference_appendices/module_utils.rst | 47 +++++++ lib/ansible/module_utils/common/arg_spec.py | 117 +++++++++++------- lib/ansible/module_utils/common/parameters.py | 68 ++++++---- lib/ansible/module_utils/common/validation.py | 102 ++++++++------- lib/ansible/module_utils/errors.py | 11 ++ 6 files changed, 284 insertions(+), 126 deletions(-) diff --git a/docs/docsite/rst/porting_guides/porting_guide_core_2.11.rst b/docs/docsite/rst/porting_guides/porting_guide_core_2.11.rst index ec55443a78b..582a2fb02a8 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_core_2.11.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_core_2.11.rst @@ -29,22 +29,71 @@ Command Line * The ``ansible-galaxy login`` command has been removed, as the underlying API it used for GitHub auth has been shut down. Publishing roles or collections to Galaxy with ``ansible-galaxy`` now requires that a Galaxy API token be passed to the CLI using a token file (default location ``~/.ansible/galaxy_token``) or (insecurely) with the ``--token`` argument to ``ansible-galaxy``. -Other: +Deprecated +========== + +The constant ``ansible.module_utils.basic._CHECK_ARGUMENT_TYPES_DISPATCHER`` is deprecated. Use :const:`ansible.module_utils.common.parameters.DEFAULT_TYPE_VALIDATORS` instead. + + +Breaking Changes +================ + +Changes to ``AnsibleModule`` +---------------------------- + +With the move to :class:`ArgumentSpecValidator ` for performing argument spec validation, the following private methods in :class:`AnsibleModule ` have been removed: + + - ``_check_argument_types()`` + - ``_check_argument_values()`` + - ``_check_arguments()`` + - ``_check_mutually_exclusive()`` --> :func:`ansible.module_utils.common.validation.check_mutually_exclusive` + - ``_check_required_arguments()`` --> :func:`ansible.module_utils.common.validation.check_required_arguments` + - ``_check_required_by()`` --> :func:`ansible.module_utils.common.validation.check_required_by` + - ``_check_required_if()`` --> :func:`ansible.module_utils.common.validation.check_required_if` + - ``_check_required_one_of()`` --> :func:`ansible.module_utils.common.validation.check_required_one_of` + - ``_check_required_together()`` --> :func:`ansible.module_utils.common.validation.check_required_together` + - ``_check_type_bits()`` --> :func:`ansible.module_utils.common.validation.check_type_bits` + - ``_check_type_bool()`` --> :func:`ansible.module_utils.common.validation.check_type_bool` + - ``_check_type_bytes()`` --> :func:`ansible.module_utils.common.validation.check_type_bytes` + - ``_check_type_dict()`` --> :func:`ansible.module_utils.common.validation.check_type_dict` + - ``_check_type_float()`` --> :func:`ansible.module_utils.common.validation.check_type_float` + - ``_check_type_int()`` --> :func:`ansible.module_utils.common.validation.check_type_int` + - ``_check_type_jsonarg()`` --> :func:`ansible.module_utils.common.validation.check_type_jsonarg` + - ``_check_type_list()`` --> :func:`ansible.module_utils.common.validation.check_type_list` + - ``_check_type_path()`` --> :func:`ansible.module_utils.common.validation.check_type_path` + - ``_check_type_raw()`` --> :func:`ansible.module_utils.common.validation.check_type_raw` + - ``_check_type_str()`` --> :func:`ansible.module_utils.common.validation.check_type_str` + - ``_count_terms()`` --> :func:`ansible.module_utils.common.validation.count_terms` + - ``_get_wanted_type()`` + - ``_handle_aliases()`` + - ``_handle_no_log_values()`` + - ``_handle_options()`` + - ``_set_defaults()`` + - ``_set_fallbacks()`` + +Modules or plugins using these private methods should use the public functions in :mod:`ansible.module_utils.common.validation` or :meth:`ArgumentSpecValidator.validate() ` if no public function was listed above. + + +Changes to :mod:`ansible.module_utils.common.parameters` +-------------------------------------------------------- + +The following functions in :mod:`ansible.module_utils.common.parameters` are now private and should not be used directly. Use :meth:`ArgumentSpecValidator.validate() ` instead. + + - ``list_no_log_values`` + - ``list_deprecations`` + - ``handle_aliases`` + + +Other ====== * **Upgrading**: If upgrading from ``ansible < 2.10`` or from ``ansible-base`` and using pip, you must ``pip uninstall ansible`` or ``pip uninstall ansible-base`` before installing ``ansible-core`` to avoid conflicts. * Python 3.8 on the controller node is a soft requirement for this release. ``ansible-core`` 2.11 still works with the same versions of Python that ``ansible-base`` 2.10 worked with, however 2.11 emits a warning when running on a controller node with a Python version less than 3.8. This warning can be disabled by setting ``ANSIBLE_CONTROLLER_PYTHON_WARNING=False`` in your environment. ``ansible-core`` 2.12 will require Python 3.8 or greater. -* The configuration system now validates the ``choices`` field, so any settings that violate it and were ignored in 2.10 cause an error in 2.11. For example, `ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH=0` now causes an error (valid choices are ``ignore``, ``warn`` or ``error``). +* The configuration system now validates the ``choices`` field, so any settings that violate it and were ignored in 2.10 cause an error in 2.11. For example, ``ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH=0`` now causes an error (valid choices are ``ignore``, ``warn`` or ``error``). * The ``ansible-galaxy`` command now uses ``resolvelib`` for resolving dependencies. In most cases this should not make a user-facing difference beyond being more performant, but we note it here for posterity and completeness. * If you import Python ``module_utils`` into any modules you maintain, you may now mark the import as optional during the module payload build by wrapping the ``import`` statement in a ``try`` or ``if`` block. This allows modules to use ``module_utils`` that may not be present in all versions of Ansible or a collection, and to perform arbitrary recovery or fallback actions during module runtime. -Deprecated -========== - -No notable changes - - Modules ======= diff --git a/docs/docsite/rst/reference_appendices/module_utils.rst b/docs/docsite/rst/reference_appendices/module_utils.rst index 7fa4620c1e0..29cf23106e4 100644 --- a/docs/docsite/rst/reference_appendices/module_utils.rst +++ b/docs/docsite/rst/reference_appendices/module_utils.rst @@ -25,3 +25,50 @@ To use this functionality, include ``import ansible.module_utils.basic`` in your .. automodule:: ansible.module_utils.basic :members: + + +Argument Spec +--------------------- + +Classes and functions for validating parameters against an argument spec. + +ArgumentSpecValidator +===================== + +.. autoclass:: ansible.module_utils.common.arg_spec.ArgumentSpecValidator + :members: + +ValidationResult +================ + +.. autoclass:: ansible.module_utils.common.arg_spec.ValidationResult + :members: + :member-order: bysource + :private-members: _no_log_values # This only works in sphinx >= 3.2. Otherwise it shows all private members with doc strings. + +Parameters +========== + +.. automodule:: ansible.module_utils.common.parameters + :members: + + .. py:data:: DEFAULT_TYPE_VALIDATORS + + :class:`dict` of type names, such as ``'str'``, and the default function + used to check that type, :func:`~ansible.module_utils.common.validation.check_type_str` in this case. + +Validation +========== + +Standalone functions for validating various parameter types. + +.. automodule:: ansible.module_utils.common.validation + :members: + + +Errors +------ + +.. automodule:: ansible.module_utils.errors + :members: + :member-order: bysource diff --git a/lib/ansible/module_utils/common/arg_spec.py b/lib/ansible/module_utils/common/arg_spec.py index c4d4a247ed5..781f69487da 100644 --- a/lib/ansible/module_utils/common/arg_spec.py +++ b/lib/ansible/module_utils/common/arg_spec.py @@ -50,29 +50,45 @@ from ansible.module_utils.errors import ( class ValidationResult: """Result of argument spec validation. - :param parameters: Terms to be validated and coerced to the correct type. - :type parameters: dict - + This is the object returned by :func:`ArgumentSpecValidator.validate() + ` + containing the validated parameters and any errors. """ def __init__(self, parameters): + """ + :arg parameters: Terms to be validated and coerced to the correct type. + :type parameters: dict + """ self._no_log_values = set() + """:class:`set` of values marked as ``no_log`` in the argument spec. This + is a temporary holding place for these values and may move in the future. + """ + self._unsupported_parameters = set() self._validated_parameters = deepcopy(parameters) self._deprecations = [] self._warnings = [] self.errors = AnsibleValidationErrorMultiple() + """ + :class:`~ansible.module_utils.errors.AnsibleValidationErrorMultiple` containing all + :class:`~ansible.module_utils.errors.AnsibleValidationError` objects if there were + any failures during validation. + """ @property def validated_parameters(self): + """Validated and coerced parameters.""" return self._validated_parameters @property def unsupported_parameters(self): + """:class:`set` of unsupported parameter names.""" return self._unsupported_parameters @property def error_messages(self): + """:class:`list` of all error messages from each exception in :attr:`errors`.""" return self.errors.messages @@ -80,30 +96,7 @@ class ArgumentSpecValidator: """Argument spec validation class Creates a validator based on the ``argument_spec`` that can be used to - validate a number of parameters using the ``validate()`` method. - - :param argument_spec: Specification of valid parameters and their type. May - include nested argument specs. - :type argument_spec: dict - - :param mutually_exclusive: List or list of lists of terms that should not - be provided together. - :type mutually_exclusive: list, optional - - :param required_together: List of lists of terms that are required together. - :type required_together: list, optional - - :param required_one_of: List of lists of terms, one of which in each list - is required. - :type required_one_of: list, optional - - :param required_if: List of lists of ``[parameter, value, [parameters]]`` where - one of [parameters] is required if ``parameter`` == ``value``. - :type required_if: list, optional - - :param required_by: Dictionary of parameter names that contain a list of - parameters required by each key in the dictionary. - :type required_by: dict, optional + validate a number of parameters using the :meth:`validate` method. """ def __init__(self, argument_spec, @@ -114,6 +107,31 @@ class ArgumentSpecValidator: required_by=None, ): + """ + :arg argument_spec: Specification of valid parameters and their type. May + include nested argument specs. + :type argument_spec: dict[str, dict] + + :kwarg mutually_exclusive: List or list of lists of terms that should not + be provided together. + :type mutually_exclusive: list[str] or list[list[str]] + + :kwarg required_together: List of lists of terms that are required together. + :type required_together: list[list[str]] + + :kwarg required_one_of: List of lists of terms, one of which in each list + is required. + :type required_one_of: list[list[str]] + + :kwarg required_if: List of lists of ``[parameter, value, [parameters]]`` where + one of ``[parameters]`` is required if ``parameter == value``. + :type required_if: list + + :kwarg required_by: Dictionary of parameter names that contain a list of + parameters required by each key in the dictionary. + :type required_by: dict[str, list[str]] + """ + self._mutually_exclusive = mutually_exclusive self._required_together = required_together self._required_one_of = required_one_of @@ -130,30 +148,37 @@ class ArgumentSpecValidator: self._valid_parameter_names.update([key]) def validate(self, parameters, *args, **kwargs): - """Validate module parameters against argument spec. Returns a - ValidationResult object. + """Validate ``parameters`` against argument spec. - Error messages in the ValidationResult may contain no_log values and should be - sanitized before logging or displaying. + Error messages in the :class:`ValidationResult` may contain no_log values and should be + sanitized with :func:`~ansible.module_utils.common.parameters.sanitize_keys` before logging or displaying. - :Example: + :arg parameters: Parameters to validate against the argument spec + :type parameters: dict[str, dict] - validator = ArgumentSpecValidator(argument_spec) - result = validator.validate(parameters) + :return: :class:`ValidationResult` containing validated parameters. - if result.error_messages: - sys.exit("Validation failed: {0}".format(", ".join(result.error_messages)) + :Simple Example: - valid_params = result.validated_parameters + .. code-block:: text - :param argument_spec: Specification of parameters, type, and valid values - :type argument_spec: dict + argument_spec = { + 'name': {'type': 'str'}, + 'age': {'type': 'int'}, + } - :param parameters: Parameters provided to the role - :type parameters: dict + parameters = { + 'name': 'bo', + 'age': '42', + } + + validator = ArgumentSpecValidator(argument_spec) + result = validator.validate(parameters) + + if result.error_messages: + sys.exit("Validation failed: {0}".format(", ".join(result.error_messages)) - :return: Object containing validated parameters. - :rtype: ValidationResult + valid_params = result.validated_parameters """ result = ValidationResult(parameters) @@ -238,6 +263,12 @@ class ArgumentSpecValidator: class ModuleArgumentSpecValidator(ArgumentSpecValidator): + """Argument spec validation class used by :class:`AnsibleModule`. + + This is not meant to be used outside of :class:`AnsibleModule`. Use + :class:`ArgumentSpecValidator` instead. + """ + def __init__(self, *args, **kwargs): super(ModuleArgumentSpecValidator, self).__init__(*args, **kwargs) diff --git a/lib/ansible/module_utils/common/parameters.py b/lib/ansible/module_utils/common/parameters.py index 972db2d3faf..2624bb50927 100644 --- a/lib/ansible/module_utils/common/parameters.py +++ b/lib/ansible/module_utils/common/parameters.py @@ -131,7 +131,7 @@ def _get_type_validator(wanted): of the wanted type. """ - # Use one our our builtin validators. + # Use one of our builtin validators. if not callable(wanted): if wanted is None: # Default type for parameters @@ -249,9 +249,18 @@ def _list_deprecations(argument_spec, parameters, prefix=''): :arg parameters: Dictionary of parameters :returns: List of dictionaries containing a message and version in which - the deprecated parameter will be removed, or an empty list:: + the deprecated parameter will be removed, or an empty list. - [{'msg': "Param 'deptest' is deprecated. See the module docs for more information", 'version': '2.9'}] + :Example return: + + .. code-block:: python + + [ + { + 'msg': "Param 'deptest' is deprecated. See the module docs for more information", + 'version': '2.9' + } + ] """ deprecations = [] @@ -293,9 +302,7 @@ def _list_no_log_values(argument_spec, params): :arg argument_spec: An argument spec dictionary :arg params: Dictionary of all parameters - :returns: Set of strings that should be hidden from output:: - - {'secret_dict_value', 'secret_list_item_one', 'secret_list_item_two', 'secret_string'} + :returns: :class:`set` of strings that should be hidden from output: """ no_log_values = set() @@ -374,10 +381,13 @@ def _remove_values_conditions(value, no_log_strings, deferred_removals): a container type. The format of each entry is a 2-tuple where the first element is the ``value`` parameter and the second value is a new container to copy the elements of ``value`` into once iterated. + :returns: if ``value`` is a scalar, returns ``value`` with two exceptions: + 1. :class:`~datetime.datetime` objects which are changed into a string representation. - 2. objects which are in no_log_strings are replaced with a placeholder - so that no sensitive data is leaked. + 2. objects which are in ``no_log_strings`` are replaced with a placeholder + so that no sensitive data is leaked. + If ``value`` is a container type, returns a new empty container. ``deferred_removals`` is added to as a side-effect of this function. @@ -458,13 +468,13 @@ def _set_defaults(argument_spec, parameters, set_default=True): Modifies parameters directly. - :param argument_spec: Argument spec + :arg argument_spec: Argument spec :type argument_spec: dict - :param parameters: Parameters to evaluate + :arg parameters: Parameters to evaluate :type parameters: dict - :param set_default: Whether or not to set the default values + :kwarg set_default: Whether or not to set the default values :type set_default: bool :returns: Set of strings that should not be logged. @@ -491,7 +501,7 @@ def _set_defaults(argument_spec, parameters, set_default=True): def _sanitize_keys_conditions(value, no_log_strings, ignore_keys, deferred_removals): - """ Helper method to sanitize_keys() to build deferred_removals and avoid deep recursion. """ + """ Helper method to :func:`sanitize_keys` to build ``deferred_removals`` and avoid deep recursion. """ if isinstance(value, (text_type, binary_type)): return value @@ -564,16 +574,16 @@ def _validate_argument_types(argument_spec, parameters, prefix='', options_conte functions are returned. If any parameter fails to validate, it will not be in the returned parameters. - :param argument_spec: Argument spec + :arg argument_spec: Argument spec :type argument_spec: dict - :param parameters: Parameters + :arg parameters: Parameters :type parameters: dict - :param prefix: Name of the parent key that contains the spec. Used in the error message + :kwarg prefix: Name of the parent key that contains the spec. Used in the error message :type prefix: str - :param options_context: List of contexts? + :kwarg options_context: List of contexts? :type options_context: list :returns: Two item tuple containing validated and coerced parameters @@ -680,7 +690,10 @@ def _validate_argument_values(argument_spec, parameters, options_context=None, e def _validate_sub_spec(argument_spec, parameters, prefix='', options_context=None, errors=None, no_log_values=None, unsupported_parameters=None): - """Validate sub argument spec. This function is recursive.""" + """Validate sub argument spec. + + This function is recursive. + """ if options_context is None: options_context = [] @@ -811,15 +824,15 @@ def set_fallbacks(argument_spec, parameters): def sanitize_keys(obj, no_log_strings, ignore_keys=frozenset()): - """ Sanitize the keys in a container object by removing no_log values from key names. + """Sanitize the keys in a container object by removing ``no_log`` values from key names. - This is a companion function to the `remove_values()` function. Similar to that function, - we make use of deferred_removals to avoid hitting maximum recursion depth in cases of + This is a companion function to the :func:`remove_values` function. Similar to that function, + we make use of ``deferred_removals`` to avoid hitting maximum recursion depth in cases of large data structures. - :param obj: The container object to sanitize. Non-container objects are returned unmodified. - :param no_log_strings: A set of string values we do not want logged. - :param ignore_keys: A set of string values of keys to not sanitize. + :arg obj: The container object to sanitize. Non-container objects are returned unmodified. + :arg no_log_strings: A set of string values we do not want logged. + :kwarg ignore_keys: A set of string values of keys to not sanitize. :returns: An object with sanitized keys. """ @@ -855,12 +868,13 @@ def sanitize_keys(obj, no_log_strings, ignore_keys=frozenset()): def remove_values(value, no_log_strings): - """ Remove strings in no_log_strings from value. If value is a container - type, then remove a lot more. + """Remove strings in ``no_log_strings`` from value. + + If value is a container type, then remove a lot more. - Use of deferred_removals exists, rather than a pure recursive solution, + Use of ``deferred_removals`` exists, rather than a pure recursive solution, because of the potential to hit the maximum recursion depth when dealing with - large amounts of data (see issue #24560). + large amounts of data (see `issue #24560 `_). """ deferred_removals = deque() diff --git a/lib/ansible/module_utils/common/validation.py b/lib/ansible/module_utils/common/validation.py index d8c74e0232c..7bb1add330b 100644 --- a/lib/ansible/module_utils/common/validation.py +++ b/lib/ansible/module_utils/common/validation.py @@ -76,7 +76,7 @@ def check_mutually_exclusive(terms, parameters, options_context=None): :arg terms: List of mutually exclusive parameters :arg parameters: Dictionary of parameters - :returns: Empty list or raises TypeError if the check fails. + :returns: Empty list or raises :class:`TypeError` if the check fails. """ results = [] @@ -107,8 +107,10 @@ def check_required_one_of(terms, parameters, options_context=None): :arg terms: List of lists of terms to check. For each list of terms, at least one is required. :arg parameters: Dictionary of parameters + :kwarg options_context: List of strings of parent key names if ``terms`` are + in a sub spec. - :returns: Empty list or raises TypeError if the check fails. + :returns: Empty list or raises :class:`TypeError` if the check fails. """ results = [] @@ -132,16 +134,16 @@ def check_required_one_of(terms, parameters, options_context=None): def check_required_together(terms, parameters, options_context=None): """Check each list of terms to ensure every parameter in each list exists - in the given parameters + in the given parameters. - Accepts a list of lists or tuples + Accepts a list of lists or tuples. :arg terms: List of lists of terms to check. Each list should include parameters that are all required when at least one is specified in the parameters. :arg parameters: Dictionary of parameters - :returns: Empty list or raises TypeError if the check fails. + :returns: Empty list or raises :class:`TypeError` if the check fails. """ results = [] @@ -166,14 +168,14 @@ def check_required_together(terms, parameters, options_context=None): def check_required_by(requirements, parameters, options_context=None): """For each key in requirements, check the corresponding list to see if they - exist in parameters + exist in parameters. - Accepts a single string or list of values for each key + Accepts a single string or list of values for each key. :arg requirements: Dictionary of requirements :arg parameters: Dictionary of parameters - :returns: Empty dictionary or raises TypeError if the + :returns: Empty dictionary or raises :class:`TypeError` if the """ result = {} @@ -203,16 +205,16 @@ def check_required_by(requirements, parameters, options_context=None): def check_required_arguments(argument_spec, parameters, options_context=None): - """Check all paramaters in argument_spec and return a list of parameters - that are required but not present in parameters + """Check all parameters in argument_spec and return a list of parameters + that are required but not present in parameters. - Raises TypeError if the check fails + Raises :class:`TypeError` if the check fails - :arg argument_spec: Argument spec dicitionary containing all parameters + :arg argument_spec: Argument spec dictionary containing all parameters and their specification - :arg module_paramaters: Dictionary of parameters + :arg parameters: Dictionary of parameters - :returns: Empty list or raises TypeError if the check fails. + :returns: Empty list or raises :class:`TypeError` if the check fails. """ missing = [] @@ -236,32 +238,38 @@ def check_required_arguments(argument_spec, parameters, options_context=None): def check_required_if(requirements, parameters, options_context=None): """Check parameters that are conditionally required - Raises TypeError if the check fails + Raises :class:`TypeError` if the check fails :arg requirements: List of lists specifying a parameter, value, parameters required when the given parameter is the specified value, and optionally a boolean indicating any or all parameters are required. - Example: - required_if=[ - ['state', 'present', ('path',), True], - ['someint', 99, ('bool_param', 'string_param')], - ] + :Example: + + .. code-block:: python + + required_if=[ + ['state', 'present', ('path',), True], + ['someint', 99, ('bool_param', 'string_param')], + ] - :arg module_paramaters: Dictionary of parameters + :arg parameters: Dictionary of parameters - :returns: Empty list or raises TypeError if the check fails. + :returns: Empty list or raises :class:`TypeError` if the check fails. The results attribute of the exception contains a list of dictionaries. - Each dictionary is the result of evaluting each item in requirements. + Each dictionary is the result of evaluating each item in requirements. Each return dictionary contains the following keys: :key missing: List of parameters that are required but missing :key requires: 'any' or 'all' - :key paramater: Parameter name that has the requirement - :key value: Original value of the paramater + :key parameter: Parameter name that has the requirement + :key value: Original value of the parameter :key requirements: Original required parameters - Example: + :Example: + + .. code-block:: python + [ { 'parameter': 'someint', @@ -321,13 +329,12 @@ def check_missing_parameters(parameters, required_parameters=None): """This is for checking for required params when we can not check via argspec because we need more information than is simply given in the argspec. - Raises TypeError if any required parameters are missing + Raises :class:`TypeError` if any required parameters are missing - :arg module_paramaters: Dictionary of parameters - :arg required_parameters: List of parameters to look for in the given module - parameters + :arg parameters: Dictionary of parameters + :arg required_parameters: List of parameters to look for in the given parameters. - :returns: Empty list or raises TypeError if the check fails. + :returns: Empty list or raises :class:`TypeError` if the check fails. """ missing_params = [] if required_parameters is None: @@ -374,13 +381,13 @@ def check_type_str(value, allow_conversion=True, param=None, prefix=''): def check_type_list(value): """Verify that the value is a list or convert to a list - A comma separated string will be split into a list. Rases a TypeError if - unable to convert to a list. + A comma separated string will be split into a list. Raises a :class:`TypeError` + if unable to convert to a list. :arg value: Value to validate or convert to a list :returns: Original value if it is already a list, single item list if a - float, int or string without commas, or a multi-item list if a + float, int, or string without commas, or a multi-item list if a comma-delimited string. """ if isinstance(value, list): @@ -397,9 +404,9 @@ def check_type_list(value): def check_type_dict(value): """Verify that value is a dict or convert it to a dict and return it. - Raises TypeError if unable to convert to a dict + Raises :class:`TypeError` if unable to convert to a dict - :arg value: Dict or string to convert to a dict. Accepts 'k1=v2, k2=v2'. + :arg value: Dict or string to convert to a dict. Accepts ``k1=v2, k2=v2``. :returns: value converted to a dictionary """ @@ -451,7 +458,7 @@ def check_type_dict(value): def check_type_bool(value): """Verify that the value is a bool or convert it to a bool and return it. - Raises TypeError if unable to convert to a bool + Raises :class:`TypeError` if unable to convert to a bool :arg value: String, int, or float to convert to bool. Valid booleans include: '1', 'on', 1, '0', 0, 'n', 'f', 'false', 'true', 'y', 't', 'yes', 'no', 'off' @@ -471,11 +478,11 @@ def check_type_int(value): """Verify that the value is an integer and return it or convert the value to an integer and return it - Raises TypeError if unable to convert to an int + Raises :class:`TypeError` if unable to convert to an int :arg value: String or int to convert of verify - :return: Int of given value + :return: int of given value """ if isinstance(value, integer_types): return value @@ -492,11 +499,11 @@ def check_type_int(value): def check_type_float(value): """Verify that value is a float or convert it to a float and return it - Raises TypeError if unable to convert to a float + Raises :class:`TypeError` if unable to convert to a float - :arg value: Float, int, str, or bytes to verify or convert and return. + :arg value: float, int, str, or bytes to verify or convert and return. - :returns: Float of given value. + :returns: float of given value. """ if isinstance(value, float): return value @@ -519,15 +526,14 @@ def check_type_path(value,): def check_type_raw(value): - """Returns the raw value - """ + """Returns the raw value""" return value def check_type_bytes(value): """Convert a human-readable string value to bytes - Raises TypeError if unable to covert the value + Raises :class:`TypeError` if unable to covert the value """ try: return human_to_bytes(value) @@ -538,9 +544,9 @@ def check_type_bytes(value): def check_type_bits(value): """Convert a human-readable string bits value to bits in integer. - Example: check_type_bits('1Mb') returns integer 1048576. + Example: ``check_type_bits('1Mb')`` returns integer 1048576. - Raises TypeError if unable to covert the value. + Raises :class:`TypeError` if unable to covert the value. """ try: return human_to_bytes(value, isbits=True) @@ -552,7 +558,7 @@ def check_type_jsonarg(value): """Return a jsonified string. Sometimes the controller turns a json string into a dict/list so transform it back into json here - Raises TypeError if unable to covert the value + Raises :class:`TypeError` if unable to covert the value """ if isinstance(value, (text_type, binary_type)): diff --git a/lib/ansible/module_utils/errors.py b/lib/ansible/module_utils/errors.py index 953c78dce21..3274b85b632 100644 --- a/lib/ansible/module_utils/errors.py +++ b/lib/ansible/module_utils/errors.py @@ -16,9 +16,11 @@ class AnsibleValidationError(Exception): def __init__(self, message): super(AnsibleValidationError, self).__init__(message) self.error_message = message + """The error message passed in when the exception was raised.""" @property def msg(self): + """The error message passed in when the exception was raised.""" return self.args[0] @@ -27,6 +29,7 @@ class AnsibleValidationErrorMultiple(AnsibleValidationError): def __init__(self, errors=None): self.errors = errors[:] if errors else [] + """:class:`list` of :class:`AnsibleValidationError` objects""" def __getitem__(self, key): return self.errors[key] @@ -39,16 +42,24 @@ class AnsibleValidationErrorMultiple(AnsibleValidationError): @property def msg(self): + """The first message from the first error in ``errors``.""" return self.errors[0].args[0] @property def messages(self): + """:class:`list` of each error message in ``errors``.""" return [err.msg for err in self.errors] def append(self, error): + """Append a new error to ``self.errors``. + + Only :class:`AnsibleValidationError` should be added. + """ + self.errors.append(error) def extend(self, errors): + """Append each item in ``errors`` to ``self.errors``. Only :class:`AnsibleValidationError` should be added.""" self.errors.extend(errors)