1""" 2Utility functions to modify other functions 3""" 4 5 6import logging 7import types 8 9import salt.utils.args 10from salt.exceptions import SaltInvocationError 11 12log = logging.getLogger(__name__) 13 14 15def namespaced_function(function, global_dict, defaults=None, preserve_context=False): 16 """ 17 Redefine (clone) a function under a different globals() namespace scope 18 19 preserve_context: 20 Allow keeping the context taken from orignal namespace, 21 and extend it with globals() taken from 22 new targetted namespace. 23 """ 24 if defaults is None: 25 defaults = function.__defaults__ 26 27 if preserve_context: 28 _global_dict = function.__globals__.copy() 29 _global_dict.update(global_dict) 30 global_dict = _global_dict 31 new_namespaced_function = types.FunctionType( 32 function.__code__, 33 global_dict, 34 name=function.__name__, 35 argdefs=defaults, 36 closure=function.__closure__, 37 ) 38 new_namespaced_function.__dict__.update(function.__dict__) 39 return new_namespaced_function 40 41 42def alias_function(fun, name, doc=None): 43 """ 44 Copy a function 45 """ 46 alias_fun = types.FunctionType( 47 fun.__code__, 48 fun.__globals__, 49 str(name), 50 fun.__defaults__, 51 fun.__closure__, 52 ) 53 alias_fun.__dict__.update(fun.__dict__) 54 55 if doc and isinstance(doc, str): 56 alias_fun.__doc__ = doc 57 else: 58 orig_name = fun.__name__ 59 alias_msg = "\nThis function is an alias of ``{}``.\n".format(orig_name) 60 alias_fun.__doc__ = alias_msg + (fun.__doc__ or "") 61 62 return alias_fun 63 64 65def parse_function(function_arguments): 66 """ 67 Helper function to parse function_arguments (module.run format) 68 into args and kwargs. 69 This function is similar to salt.utils.data.repack_dictlist, except that this 70 handles mixed (i.e. dict and non-dict) arguments in the input list. 71 72 :param list function_arguments: List of items and dicts with kwargs. 73 74 :rtype: dict 75 :return: Dictionary with ``args`` and ``kwargs`` keyword. 76 """ 77 function_args = [] 78 function_kwargs = {} 79 for item in function_arguments: 80 if isinstance(item, dict): 81 function_kwargs.update(item) 82 else: 83 function_args.append(item) 84 return {"args": function_args, "kwargs": function_kwargs} 85 86 87def call_function(salt_function, *args, **kwargs): 88 """ 89 Calls a function from the specified module. 90 91 :param function salt_function: Function reference to call 92 :return: The result of the function call 93 """ 94 argspec = salt.utils.args.get_function_argspec(salt_function) 95 # function_kwargs is initialized to a dictionary of keyword arguments the function to be run accepts 96 function_kwargs = dict( 97 zip( 98 argspec.args[ 99 -len(argspec.defaults or []) : 100 ], # pylint: disable=incompatible-py3-code 101 argspec.defaults or [], 102 ) 103 ) 104 # expected_args is initialized to a list of positional arguments that the function to be run accepts 105 expected_args = argspec.args[ 106 : len(argspec.args or []) - len(argspec.defaults or []) 107 ] 108 function_args, kw_to_arg_type = [], {} 109 for funcset in reversed(args or []): 110 if not isinstance(funcset, dict): 111 # We are just receiving a list of args to the function to be run, so just append 112 # those to the arg list that we will pass to the func. 113 function_args.append(funcset) 114 else: 115 for kwarg_key in funcset.keys(): 116 # We are going to pass in a keyword argument. The trick here is to make certain 117 # that if we find that in the *args* list that we pass it there and not as a kwarg 118 if kwarg_key in expected_args: 119 kw_to_arg_type[kwarg_key] = funcset[kwarg_key] 120 else: 121 # Otherwise, we're good and just go ahead and pass the keyword/value pair into 122 # the kwargs list to be run. 123 function_kwargs.update(funcset) 124 function_args.reverse() 125 # Add kwargs passed as kwargs :) 126 function_kwargs.update(kwargs) 127 for arg in expected_args: 128 if arg in kw_to_arg_type: 129 function_args.append(kw_to_arg_type[arg]) 130 _exp_prm = len(argspec.args or []) - len(argspec.defaults or []) 131 _passed_prm = len(function_args) 132 missing = [] 133 if _exp_prm > _passed_prm: 134 for arg in argspec.args[_passed_prm:]: 135 if arg not in function_kwargs: 136 missing.append(arg) 137 else: 138 # Found the expected argument as a keyword 139 # increase the _passed_prm count 140 _passed_prm += 1 141 if missing: 142 raise SaltInvocationError("Missing arguments: {}".format(", ".join(missing))) 143 elif _exp_prm > _passed_prm: 144 raise SaltInvocationError( 145 "Function expects {} positional parameters, got only {}".format( 146 _exp_prm, _passed_prm 147 ) 148 ) 149 150 return salt_function(*function_args, **function_kwargs) 151