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
pull/74354/head
Sam Doran 4 years ago committed by GitHub
parent 6e56e72d99
commit 2cbfd1e350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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``. * 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 <ansible.module_utils.common.arg_spec.ArgumentSpecValidator>` for performing argument spec validation, the following private methods in :class:`AnsibleModule <ansible.module_utils.basic.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() <ansible.module_utils.common.arg_spec.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() <ansible.module_utils.common.arg_spec.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. * **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. * 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. * 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. * 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 Modules
======= =======

@ -25,3 +25,50 @@ To use this functionality, include ``import ansible.module_utils.basic`` in your
.. automodule:: ansible.module_utils.basic .. automodule:: ansible.module_utils.basic
:members: :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

@ -50,29 +50,45 @@ from ansible.module_utils.errors import (
class ValidationResult: class ValidationResult:
"""Result of argument spec validation. """Result of argument spec validation.
:param parameters: Terms to be validated and coerced to the correct type. This is the object returned by :func:`ArgumentSpecValidator.validate()
:type parameters: dict <ansible.module_utils.common.arg_spec.ArgumentSpecValidator.validate()>`
containing the validated parameters and any errors.
""" """
def __init__(self, parameters): def __init__(self, parameters):
"""
:arg parameters: Terms to be validated and coerced to the correct type.
:type parameters: dict
"""
self._no_log_values = set() 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._unsupported_parameters = set()
self._validated_parameters = deepcopy(parameters) self._validated_parameters = deepcopy(parameters)
self._deprecations = [] self._deprecations = []
self._warnings = [] self._warnings = []
self.errors = AnsibleValidationErrorMultiple() 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 @property
def validated_parameters(self): def validated_parameters(self):
"""Validated and coerced parameters."""
return self._validated_parameters return self._validated_parameters
@property @property
def unsupported_parameters(self): def unsupported_parameters(self):
""":class:`set` of unsupported parameter names."""
return self._unsupported_parameters return self._unsupported_parameters
@property @property
def error_messages(self): def error_messages(self):
""":class:`list` of all error messages from each exception in :attr:`errors`."""
return self.errors.messages return self.errors.messages
@ -80,40 +96,42 @@ class ArgumentSpecValidator:
"""Argument spec validation class """Argument spec validation class
Creates a validator based on the ``argument_spec`` that can be used to Creates a validator based on the ``argument_spec`` that can be used to
validate a number of parameters using the ``validate()`` method. validate a number of parameters using the :meth:`validate` method.
"""
:param argument_spec: Specification of valid parameters and their type. May def __init__(self, argument_spec,
mutually_exclusive=None,
required_together=None,
required_one_of=None,
required_if=None,
required_by=None,
):
"""
:arg argument_spec: Specification of valid parameters and their type. May
include nested argument specs. include nested argument specs.
:type argument_spec: dict :type argument_spec: dict[str, dict]
:param mutually_exclusive: List or list of lists of terms that should not :kwarg mutually_exclusive: List or list of lists of terms that should not
be provided together. be provided together.
:type mutually_exclusive: list, optional :type mutually_exclusive: list[str] or list[list[str]]
:param required_together: List of lists of terms that are required together. :kwarg required_together: List of lists of terms that are required together.
:type required_together: list, optional :type required_together: list[list[str]]
:param required_one_of: List of lists of terms, one of which in each list :kwarg required_one_of: List of lists of terms, one of which in each list
is required. is required.
:type required_one_of: list, optional :type required_one_of: list[list[str]]
:param required_if: List of lists of ``[parameter, value, [parameters]]`` where :kwarg required_if: List of lists of ``[parameter, value, [parameters]]`` where
one of [parameters] is required if ``parameter`` == ``value``. one of ``[parameters]`` is required if ``parameter == value``.
:type required_if: list, optional :type required_if: list
:param required_by: Dictionary of parameter names that contain a list of :kwarg required_by: Dictionary of parameter names that contain a list of
parameters required by each key in the dictionary. parameters required by each key in the dictionary.
:type required_by: dict, optional :type required_by: dict[str, list[str]]
""" """
def __init__(self, argument_spec,
mutually_exclusive=None,
required_together=None,
required_one_of=None,
required_if=None,
required_by=None,
):
self._mutually_exclusive = mutually_exclusive self._mutually_exclusive = mutually_exclusive
self._required_together = required_together self._required_together = required_together
self._required_one_of = required_one_of self._required_one_of = required_one_of
@ -130,13 +148,29 @@ class ArgumentSpecValidator:
self._valid_parameter_names.update([key]) self._valid_parameter_names.update([key])
def validate(self, parameters, *args, **kwargs): def validate(self, parameters, *args, **kwargs):
"""Validate module parameters against argument spec. Returns a """Validate ``parameters`` against argument spec.
ValidationResult object.
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.
:arg parameters: Parameters to validate against the argument spec
:type parameters: dict[str, dict]
:return: :class:`ValidationResult` containing validated parameters.
Error messages in the ValidationResult may contain no_log values and should be :Simple Example:
sanitized before logging or displaying.
:Example: .. code-block:: text
argument_spec = {
'name': {'type': 'str'},
'age': {'type': 'int'},
}
parameters = {
'name': 'bo',
'age': '42',
}
validator = ArgumentSpecValidator(argument_spec) validator = ArgumentSpecValidator(argument_spec)
result = validator.validate(parameters) result = validator.validate(parameters)
@ -145,15 +179,6 @@ class ArgumentSpecValidator:
sys.exit("Validation failed: {0}".format(", ".join(result.error_messages)) sys.exit("Validation failed: {0}".format(", ".join(result.error_messages))
valid_params = result.validated_parameters valid_params = result.validated_parameters
:param argument_spec: Specification of parameters, type, and valid values
:type argument_spec: dict
:param parameters: Parameters provided to the role
:type parameters: dict
:return: Object containing validated parameters.
:rtype: ValidationResult
""" """
result = ValidationResult(parameters) result = ValidationResult(parameters)
@ -238,6 +263,12 @@ class ArgumentSpecValidator:
class ModuleArgumentSpecValidator(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): def __init__(self, *args, **kwargs):
super(ModuleArgumentSpecValidator, self).__init__(*args, **kwargs) super(ModuleArgumentSpecValidator, self).__init__(*args, **kwargs)

@ -131,7 +131,7 @@ def _get_type_validator(wanted):
of the wanted type. of the wanted type.
""" """
# Use one our our builtin validators. # Use one of our builtin validators.
if not callable(wanted): if not callable(wanted):
if wanted is None: if wanted is None:
# Default type for parameters # Default type for parameters
@ -249,9 +249,18 @@ def _list_deprecations(argument_spec, parameters, prefix=''):
:arg parameters: Dictionary of parameters :arg parameters: Dictionary of parameters
:returns: List of dictionaries containing a message and version in which :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 = [] deprecations = []
@ -293,9 +302,7 @@ def _list_no_log_values(argument_spec, params):
:arg argument_spec: An argument spec dictionary :arg argument_spec: An argument spec dictionary
:arg params: Dictionary of all parameters :arg params: Dictionary of all parameters
:returns: Set of strings that should be hidden from output:: :returns: :class:`set` of strings that should be hidden from output:
{'secret_dict_value', 'secret_list_item_one', 'secret_list_item_two', 'secret_string'}
""" """
no_log_values = set() 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 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 element is the ``value`` parameter and the second value is a new
container to copy the elements of ``value`` into once iterated. container to copy the elements of ``value`` into once iterated.
:returns: if ``value`` is a scalar, returns ``value`` with two exceptions: :returns: if ``value`` is a scalar, returns ``value`` with two exceptions:
1. :class:`~datetime.datetime` objects which are changed into a string representation. 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 2. objects which are in ``no_log_strings`` are replaced with a placeholder
so that no sensitive data is leaked. so that no sensitive data is leaked.
If ``value`` is a container type, returns a new empty container. If ``value`` is a container type, returns a new empty container.
``deferred_removals`` is added to as a side-effect of this function. ``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. Modifies parameters directly.
:param argument_spec: Argument spec :arg argument_spec: Argument spec
:type argument_spec: dict :type argument_spec: dict
:param parameters: Parameters to evaluate :arg parameters: Parameters to evaluate
:type parameters: dict :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 :type set_default: bool
:returns: Set of strings that should not be logged. :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): 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)): if isinstance(value, (text_type, binary_type)):
return value 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 functions are returned. If any parameter fails to validate, it will not
be in the returned parameters. be in the returned parameters.
:param argument_spec: Argument spec :arg argument_spec: Argument spec
:type argument_spec: dict :type argument_spec: dict
:param parameters: Parameters :arg parameters: Parameters
:type parameters: dict :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 :type prefix: str
:param options_context: List of contexts? :kwarg options_context: List of contexts?
:type options_context: list :type options_context: list
:returns: Two item tuple containing validated and coerced parameters :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): 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: if options_context is None:
options_context = [] options_context = []
@ -811,15 +824,15 @@ def set_fallbacks(argument_spec, parameters):
def sanitize_keys(obj, no_log_strings, ignore_keys=frozenset()): 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, 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 we make use of ``deferred_removals`` to avoid hitting maximum recursion depth in cases of
large data structures. large data structures.
:param obj: The container object to sanitize. Non-container objects are returned unmodified. :arg 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. :arg 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. :kwarg ignore_keys: A set of string values of keys to not sanitize.
:returns: An object with sanitized keys. :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): def remove_values(value, no_log_strings):
""" Remove strings in no_log_strings from value. If value is a container """Remove strings in ``no_log_strings`` from value.
type, then remove a lot more.
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 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 <https://github.com/ansible/ansible/issues/24560>`_).
""" """
deferred_removals = deque() deferred_removals = deque()

@ -76,7 +76,7 @@ def check_mutually_exclusive(terms, parameters, options_context=None):
:arg terms: List of mutually exclusive parameters :arg terms: List of mutually exclusive parameters
:arg parameters: 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.
""" """
results = [] 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 :arg terms: List of lists of terms to check. For each list of terms, at
least one is required. least one is required.
:arg parameters: Dictionary of parameters :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 = [] results = []
@ -132,16 +134,16 @@ def check_required_one_of(terms, parameters, options_context=None):
def check_required_together(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 """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 :arg terms: List of lists of terms to check. Each list should include
parameters that are all required when at least one is specified parameters that are all required when at least one is specified
in the parameters. in the parameters.
:arg parameters: 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.
""" """
results = [] results = []
@ -166,14 +168,14 @@ def check_required_together(terms, parameters, options_context=None):
def check_required_by(requirements, 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 """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 requirements: Dictionary of requirements
:arg parameters: Dictionary of parameters :arg parameters: Dictionary of parameters
:returns: Empty dictionary or raises TypeError if the :returns: Empty dictionary or raises :class:`TypeError` if the
""" """
result = {} result = {}
@ -203,16 +205,16 @@ def check_required_by(requirements, parameters, options_context=None):
def check_required_arguments(argument_spec, 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 """Check all parameters in argument_spec and return a list of parameters
that are required but not present in 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 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 = [] missing = []
@ -236,32 +238,38 @@ def check_required_arguments(argument_spec, parameters, options_context=None):
def check_required_if(requirements, parameters, options_context=None): def check_required_if(requirements, parameters, options_context=None):
"""Check parameters that are conditionally required """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 :arg requirements: List of lists specifying a parameter, value, parameters
required when the given parameter is the specified value, and optionally required when the given parameter is the specified value, and optionally
a boolean indicating any or all parameters are required. a boolean indicating any or all parameters are required.
Example: :Example:
.. code-block:: python
required_if=[ required_if=[
['state', 'present', ('path',), True], ['state', 'present', ('path',), True],
['someint', 99, ('bool_param', 'string_param')], ['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. 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: Each return dictionary contains the following keys:
:key missing: List of parameters that are required but missing :key missing: List of parameters that are required but missing
:key requires: 'any' or 'all' :key requires: 'any' or 'all'
:key paramater: Parameter name that has the requirement :key parameter: Parameter name that has the requirement
:key value: Original value of the paramater :key value: Original value of the parameter
:key requirements: Original required parameters :key requirements: Original required parameters
Example: :Example:
.. code-block:: python
[ [
{ {
'parameter': 'someint', '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 """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. 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 parameters: Dictionary of parameters
:arg required_parameters: List of parameters to look for in the given module :arg required_parameters: List of parameters to look for in the given parameters.
parameters
:returns: Empty list or raises TypeError if the check fails. :returns: Empty list or raises :class:`TypeError` if the check fails.
""" """
missing_params = [] missing_params = []
if required_parameters is None: 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): def check_type_list(value):
"""Verify that the value is a list or convert to a list """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 A comma separated string will be split into a list. Raises a :class:`TypeError`
unable to convert to a list. if unable to convert to a list.
:arg value: Value to validate or 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 :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. comma-delimited string.
""" """
if isinstance(value, list): if isinstance(value, list):
@ -397,9 +404,9 @@ def check_type_list(value):
def check_type_dict(value): def check_type_dict(value):
"""Verify that value is a dict or convert it to a dict and return it. """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 :returns: value converted to a dictionary
""" """
@ -451,7 +458,7 @@ def check_type_dict(value):
def check_type_bool(value): def check_type_bool(value):
"""Verify that the value is a bool or convert it to a bool and return it. """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: :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' '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 """Verify that the value is an integer and return it or convert the value
to an integer and return it 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 :arg value: String or int to convert of verify
:return: Int of given value :return: int of given value
""" """
if isinstance(value, integer_types): if isinstance(value, integer_types):
return value return value
@ -492,11 +499,11 @@ def check_type_int(value):
def check_type_float(value): def check_type_float(value):
"""Verify that value is a float or convert it to a float and return it """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): if isinstance(value, float):
return value return value
@ -519,15 +526,14 @@ def check_type_path(value,):
def check_type_raw(value): def check_type_raw(value):
"""Returns the raw value """Returns the raw value"""
"""
return value return value
def check_type_bytes(value): def check_type_bytes(value):
"""Convert a human-readable string value to bytes """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: try:
return human_to_bytes(value) return human_to_bytes(value)
@ -538,9 +544,9 @@ def check_type_bytes(value):
def check_type_bits(value): def check_type_bits(value):
"""Convert a human-readable string bits value to bits in integer. """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: try:
return human_to_bytes(value, isbits=True) 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 """Return a jsonified string. Sometimes the controller turns a json string
into a dict/list so transform it back into json here 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)): if isinstance(value, (text_type, binary_type)):

@ -16,9 +16,11 @@ class AnsibleValidationError(Exception):
def __init__(self, message): def __init__(self, message):
super(AnsibleValidationError, self).__init__(message) super(AnsibleValidationError, self).__init__(message)
self.error_message = message self.error_message = message
"""The error message passed in when the exception was raised."""
@property @property
def msg(self): def msg(self):
"""The error message passed in when the exception was raised."""
return self.args[0] return self.args[0]
@ -27,6 +29,7 @@ class AnsibleValidationErrorMultiple(AnsibleValidationError):
def __init__(self, errors=None): def __init__(self, errors=None):
self.errors = errors[:] if errors else [] self.errors = errors[:] if errors else []
""":class:`list` of :class:`AnsibleValidationError` objects"""
def __getitem__(self, key): def __getitem__(self, key):
return self.errors[key] return self.errors[key]
@ -39,16 +42,24 @@ class AnsibleValidationErrorMultiple(AnsibleValidationError):
@property @property
def msg(self): def msg(self):
"""The first message from the first error in ``errors``."""
return self.errors[0].args[0] return self.errors[0].args[0]
@property @property
def messages(self): def messages(self):
""":class:`list` of each error message in ``errors``."""
return [err.msg for err in self.errors] return [err.msg for err in self.errors]
def append(self, error): def append(self, error):
"""Append a new error to ``self.errors``.
Only :class:`AnsibleValidationError` should be added.
"""
self.errors.append(error) self.errors.append(error)
def extend(self, errors): def extend(self, errors):
"""Append each item in ``errors`` to ``self.errors``. Only :class:`AnsibleValidationError` should be added."""
self.errors.extend(errors) self.errors.extend(errors)

Loading…
Cancel
Save