mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-27 07:01:22 -07:00
Move type checking methods out of basic.py and add unit tests (#53687)
* Move check_type_str() out of basic.py * Move check_type_list() out of basic.py * Move safe_eval() out of basic.py * Move check_type_dict() out of basic.py * Move json importing code to common location * Move check_type_bool() out of basic.py * Move _check_type_int() out of basic.py * Move _check_type_float() out of basic.py * Move _check_type_path() out of basic.py * Move _check_type_raw() out of basic.py * Move _check_type_bytes() out of basic.py * Move _check_type_bits() out of basic.py * Create text.formatters.py Move human_to_bytes, bytes_to_human, and _lenient_lowercase out of basic.py into text.formatters.py Change references in modules to point to function at new location * Move _check_type_jsonarg() out of basic.py * Rename json related functions and put them in common.text.converters Move formatters.py to common.text.formatters.py and update references in modules. * Rework check_type_str() Add allow_conversion option to make the function more self-contained. Move the messaging back to basic.py since those error messages are more relevant to using this function in the context of AnsibleModule and not when using the function in isolation. * Add unit tests for type checking functions * Change _lenient_lowercase to lenient_lowercase per feedback
This commit is contained in:
parent
bb61d7527f
commit
ff88bd82b5
26 changed files with 957 additions and 326 deletions
|
@ -1,13 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018 Ansible Project
|
||||
# Copyright (c) 2019 Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
import os
|
||||
import re
|
||||
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible.module_utils.common._json_compat import json
|
||||
from ansible.module_utils.common.collections import is_iterable
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import jsonify
|
||||
from ansible.module_utils.common.text.formatters import human_to_bytes
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.module_utils.pycompat24 import literal_eval
|
||||
from ansible.module_utils.six import (
|
||||
binary_type,
|
||||
integer_types,
|
||||
string_types,
|
||||
text_type,
|
||||
)
|
||||
|
||||
|
||||
def count_terms(terms, module_parameters):
|
||||
|
@ -27,8 +40,9 @@ def count_terms(terms, module_parameters):
|
|||
|
||||
|
||||
def check_mutually_exclusive(terms, module_parameters):
|
||||
"""Check mutually exclusive terms against argument parameters. Accepts
|
||||
a single list or list of lists that are groups of terms that should be
|
||||
"""Check mutually exclusive terms against argument parameters
|
||||
|
||||
Accepts a single list or list of lists that are groups of terms that should be
|
||||
mutually exclusive with one another
|
||||
|
||||
:arg terms: List of mutually exclusive module parameters
|
||||
|
@ -56,7 +70,9 @@ def check_mutually_exclusive(terms, module_parameters):
|
|||
|
||||
def check_required_one_of(terms, module_parameters):
|
||||
"""Check each list of terms to ensure at least one exists in the given module
|
||||
parameters. Accepts a list of lists or tuples.
|
||||
parameters
|
||||
|
||||
Accepts a list of lists or tuples
|
||||
|
||||
:arg terms: List of lists of terms to check. For each list of terms, at
|
||||
least one is required.
|
||||
|
@ -84,7 +100,9 @@ def check_required_one_of(terms, module_parameters):
|
|||
|
||||
def check_required_together(terms, module_parameters):
|
||||
"""Check each list of terms to ensure every parameter in each list exists
|
||||
in the given module parameters. Accepts a list of lists or tuples.
|
||||
in the given module parameters
|
||||
|
||||
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
|
||||
|
@ -114,8 +132,9 @@ def check_required_together(terms, module_parameters):
|
|||
|
||||
def check_required_by(requirements, module_parameters):
|
||||
"""For each key in requirements, check the corresponding list to see if they
|
||||
exist in module_parameters. Accepts a single string or list of values for
|
||||
each key.
|
||||
exist in module_parameters
|
||||
|
||||
Accepts a single string or list of values for each key
|
||||
|
||||
:arg requirements: Dictionary of requirements
|
||||
:arg module_parameters: Dictionary of module parameters
|
||||
|
@ -149,9 +168,9 @@ def check_required_by(requirements, module_parameters):
|
|||
|
||||
def check_required_arguments(argument_spec, module_parameters):
|
||||
"""Check all paramaters in argument_spec and return a list of parameters
|
||||
that are required by not present in module_parameters.
|
||||
that are required but not present in module_parameters
|
||||
|
||||
Raises AnsibleModuleParameterException if the check fails.
|
||||
Raises TypeError if the check fails
|
||||
|
||||
:arg argument_spec: Argument spec dicitionary containing all parameters
|
||||
and their specification
|
||||
|
@ -177,9 +196,9 @@ def check_required_arguments(argument_spec, module_parameters):
|
|||
|
||||
|
||||
def check_required_if(requirements, module_parameters):
|
||||
"""Check parameters that are conditionally required.
|
||||
"""Check parameters that are conditionally required
|
||||
|
||||
Raises TypeError if the check fails.
|
||||
Raises 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
|
||||
|
@ -262,6 +281,8 @@ def check_missing_parameters(module_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
|
||||
|
||||
:arg module_paramaters: Dictionary of module parameters
|
||||
:arg required_parameters: List of parameters to look for in the given module
|
||||
parameters
|
||||
|
@ -281,3 +302,244 @@ def check_missing_parameters(module_parameters, required_parameters=None):
|
|||
raise TypeError(to_native(msg))
|
||||
|
||||
return missing_params
|
||||
|
||||
|
||||
def safe_eval(value, locals=None, include_exceptions=False):
|
||||
# do not allow method calls to modules
|
||||
if not isinstance(value, string_types):
|
||||
# already templated to a datavaluestructure, perhaps?
|
||||
if include_exceptions:
|
||||
return (value, None)
|
||||
return value
|
||||
if re.search(r'\w\.\w+\(', value):
|
||||
if include_exceptions:
|
||||
return (value, None)
|
||||
return value
|
||||
# do not allow imports
|
||||
if re.search(r'import \w+', value):
|
||||
if include_exceptions:
|
||||
return (value, None)
|
||||
return value
|
||||
try:
|
||||
result = literal_eval(value)
|
||||
if include_exceptions:
|
||||
return (result, None)
|
||||
else:
|
||||
return result
|
||||
except Exception as e:
|
||||
if include_exceptions:
|
||||
return (value, e)
|
||||
return value
|
||||
|
||||
|
||||
def check_type_str(value, allow_conversion=True):
|
||||
"""Verify that the value is a string or convert to a string.
|
||||
|
||||
Since unexpected changes can sometimes happen when converting to a string,
|
||||
``allow_conversion`` controls whether or not the value will be converted or a
|
||||
TypeError will be raised if the value is not a string and would be converted
|
||||
|
||||
:arg value: Value to validate or convert to a string
|
||||
:arg allow_conversion: Whether to convert the string and return it or raise
|
||||
a TypeError
|
||||
|
||||
:returns: Original value if it is a string, the value converted to a string
|
||||
if allow_conversion=True, or raises a TypeError if allow_conversion=False.
|
||||
"""
|
||||
if isinstance(value, string_types):
|
||||
return value
|
||||
|
||||
if allow_conversion:
|
||||
return to_native(value, errors='surrogate_or_strict')
|
||||
|
||||
msg = "'{0!r}' is not a string and conversion is not allowed".format(value)
|
||||
raise TypeError(to_native(msg))
|
||||
|
||||
|
||||
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.
|
||||
|
||||
: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
|
||||
comma-delimited string.
|
||||
"""
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
|
||||
if isinstance(value, string_types):
|
||||
return value.split(",")
|
||||
elif isinstance(value, int) or isinstance(value, float):
|
||||
return [str(value)]
|
||||
|
||||
raise TypeError('%s cannot be converted to a list' % type(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
|
||||
|
||||
:arg value: Dict or string to convert to a dict. Accepts 'k1=v2, k2=v2'.
|
||||
|
||||
:returns: value converted to a dictionary
|
||||
"""
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
|
||||
if isinstance(value, string_types):
|
||||
if value.startswith("{"):
|
||||
try:
|
||||
return json.loads(value)
|
||||
except Exception:
|
||||
(result, exc) = safe_eval(value, dict(), include_exceptions=True)
|
||||
if exc is not None:
|
||||
raise TypeError('unable to evaluate string as dictionary')
|
||||
return result
|
||||
elif '=' in value:
|
||||
fields = []
|
||||
field_buffer = []
|
||||
in_quote = False
|
||||
in_escape = False
|
||||
for c in value.strip():
|
||||
if in_escape:
|
||||
field_buffer.append(c)
|
||||
in_escape = False
|
||||
elif c == '\\':
|
||||
in_escape = True
|
||||
elif not in_quote and c in ('\'', '"'):
|
||||
in_quote = c
|
||||
elif in_quote and in_quote == c:
|
||||
in_quote = False
|
||||
elif not in_quote and c in (',', ' '):
|
||||
field = ''.join(field_buffer)
|
||||
if field:
|
||||
fields.append(field)
|
||||
field_buffer = []
|
||||
else:
|
||||
field_buffer.append(c)
|
||||
|
||||
field = ''.join(field_buffer)
|
||||
if field:
|
||||
fields.append(field)
|
||||
return dict(x.split("=", 1) for x in fields)
|
||||
else:
|
||||
raise TypeError("dictionary requested, could not parse JSON or key=value")
|
||||
|
||||
raise TypeError('%s cannot be converted to a dict' % type(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
|
||||
|
||||
: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'
|
||||
|
||||
:returns: Boolean True or False
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
|
||||
if isinstance(value, string_types) or isinstance(value, (int, float)):
|
||||
return boolean(value)
|
||||
|
||||
raise TypeError('%s cannot be converted to a bool' % type(value))
|
||||
|
||||
|
||||
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
|
||||
|
||||
:arg value: String or int to convert of verify
|
||||
|
||||
:return: Int of given value
|
||||
"""
|
||||
if isinstance(value, integer_types):
|
||||
return value
|
||||
|
||||
if isinstance(value, string_types):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
raise TypeError('%s cannot be converted to an int' % type(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
|
||||
|
||||
:arg value: Float, int, str, or bytes to verify or convert and return.
|
||||
|
||||
:returns: Float of given value.
|
||||
"""
|
||||
if isinstance(value, float):
|
||||
return value
|
||||
|
||||
if isinstance(value, (binary_type, text_type, int)):
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
raise TypeError('%s cannot be converted to a float' % type(value))
|
||||
|
||||
|
||||
def check_type_path(value,):
|
||||
"""Verify the provided value is a string or convert it to a string,
|
||||
then return the expanded path
|
||||
"""
|
||||
value = check_type_str(value)
|
||||
return os.path.expanduser(os.path.expandvars(value))
|
||||
|
||||
|
||||
def check_type_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
|
||||
"""
|
||||
try:
|
||||
return human_to_bytes(value)
|
||||
except ValueError:
|
||||
raise TypeError('%s cannot be converted to a Byte value' % type(value))
|
||||
|
||||
|
||||
def check_type_bits(value):
|
||||
"""Convert a human-readable string value to bits
|
||||
|
||||
Raises TypeError if unable to covert the value
|
||||
"""
|
||||
try:
|
||||
return human_to_bytes(value, isbits=True)
|
||||
except ValueError:
|
||||
raise TypeError('%s cannot be converted to a Bit value' % type(value))
|
||||
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
if isinstance(value, (text_type, binary_type)):
|
||||
return value.strip()
|
||||
elif isinstance(value, (list, tuple, dict)):
|
||||
return jsonify(value)
|
||||
raise TypeError('%s cannot be converted to a json string' % type(value))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue