1#!/usr/bin/env python 2 3r""" 4This module provides functions which are useful for writing python wrapper 5functions (i.e. in this context, a wrapper function is one whose aim is to 6call some other function on the caller's behalf but to provide some additional 7functionality over and above what the base function provides). 8""" 9 10import sys 11import inspect 12 13 14def create_wrapper_def_and_call(base_func_name, 15 wrap_func_name): 16 r""" 17 Return a wrapper function definition line and a base function call line. 18 19 This is a utility for helping to create wrapper functions. 20 21 For example, if there existed a function with the following definition 22 line: 23 def sprint_foo_bar(headers=1): 24 25 And the user wished to write a print_foo_bar wrapper function, they could 26 call create_wrapper_def_and_call as follows: 27 28 func_def_line, call_line = create_wrapper_def_and_call("sprint_foo_bar", 29 "print_foo_bar") 30 31 They would get the following results: 32 func_def_line def print_foo_bar(headers=1): 33 call_line sprint_foo_bar(headers=headers) 34 35 The func_def_line is suitable as the definition line for the wrapper 36 function. The call_line is suitable for use in the new wrapper function 37 wherever it wishes to call the base function. By explicitly specifying 38 each parm in the definition and the call line, we allow the caller of the 39 wrapper function to refer to any given parm by name rather than having to 40 specify parms positionally. 41 42 Description of argument(s): 43 base_func_name The name of the base function around which 44 a wrapper is being created. 45 wrap_func_name The name of the wrapper function being 46 created. 47 """ 48 49 # Get caller's module name. Note: that for the present we've hard-coded 50 # the stack_frame_ix value because we expect a call stack to this function 51 # to be something like this: 52 # caller 53 # create_print_wrapper_funcs 54 # create_func_def_string 55 # create_wrapper_def_and_call 56 stack_frame_ix = 3 57 frame = inspect.stack()[stack_frame_ix] 58 module = inspect.getmodule(frame[0]) 59 mod_name = module.__name__ 60 61 # Get a reference to the base function. 62 base_func = getattr(sys.modules[mod_name], base_func_name) 63 # Get the argument specification for the base function. 64 base_arg_spec = inspect.getargspec(base_func) 65 base_arg_list = base_arg_spec[0] 66 num_args = len(base_arg_list) 67 # Get the variable argument specification for the base function. 68 var_args = base_arg_spec[1] 69 if var_args is None: 70 var_args = [] 71 else: 72 var_args = ["*" + var_args] 73 keyword_args = base_arg_spec[2] 74 if keyword_args is None: 75 keyword_args = [] 76 else: 77 keyword_args = ["**" + keyword_args] 78 if base_arg_spec[3] is None: 79 base_default_list = [] 80 else: 81 base_default_list = list(base_arg_spec[3]) 82 num_defaults = len(base_default_list) 83 num_non_defaults = num_args - num_defaults 84 85 # Create base_arg_default_string which is a reconstruction of the base 86 # function's argument list. 87 # Example base_arg_default_string: 88 # headers, last=2, first=[1] 89 # First, create a new list where each entry is of the form "arg=default". 90 base_arg_default_list = list(base_arg_list) 91 for ix in range(num_non_defaults, len(base_arg_default_list)): 92 base_default_ix = ix - num_non_defaults 93 if isinstance(base_default_list[base_default_ix], str): 94 default_string = "'" + base_default_list[base_default_ix] + "'" 95 # Convert "\n" to "\\n". 96 default_string = default_string.replace("\n", "\\n") 97 else: 98 default_string = str(base_default_list[base_default_ix]) 99 base_arg_default_list[ix] += "=" + default_string 100 base_arg_default_string =\ 101 ', '.join(base_arg_default_list + var_args + keyword_args) 102 103 # Create the argument string which can be used to call the base function. 104 # Example call_arg_string: 105 # headers=headers, last=last, first=first 106 call_arg_string = ', '.join([val + "=" + val for val in base_arg_list] 107 + var_args + keyword_args) 108 109 # Compose the result values. 110 func_def_line = "def " + wrap_func_name + "(" + base_arg_default_string +\ 111 "):" 112 call_line = base_func_name + "(" + call_arg_string + ")" 113 114 return func_def_line, call_line 115 116 117def create_func_def_string(base_func_name, 118 wrap_func_name, 119 func_body_template, 120 replace_dict): 121 r""" 122 Create and return a complete function definition as a string. The caller 123 may run "exec" on the resulting string to create the desired function. 124 125 Description of argument(s): 126 base_func_name The name of the base function around which 127 a wrapper is being created. 128 wrap_func_name The name of the wrapper function being 129 created. 130 func_body_template A function body in the form of a list. 131 Each list element represents one line of a 132 function This is a template in so far as 133 text substitutions will be done on it to 134 arrive at a valid function definition. 135 This template should NOT contain the 136 function definition line (e.g. "def 137 func1():"). create_func_def_string will 138 pre-pend the definition line. The 139 template should also contain the text 140 "<call_line>" which is to be replaced by 141 text which will call the base function 142 with appropriate arguments. 143 replace_dict A dictionary indicating additional text 144 replacements to be done. For example, if 145 the template contains a "<sub1>" (be sure 146 to include the angle brackets), and the 147 dictionary contains a key/value pair of 148 'sub1'/'replace1', then all instances of 149 "<sub1>" will be replaced by "replace1". 150 """ 151 152 # Create the initial function definition list as a copy of the template. 153 func_def = list(func_body_template) 154 # Call create_wrapper_def_and_call to get func_def_line and call_line. 155 func_def_line, call_line = create_wrapper_def_and_call(base_func_name, 156 wrap_func_name) 157 # Insert the func_def_line composed by create_wrapper_def_and_call is the 158 # first list entry. 159 func_def.insert(0, func_def_line) 160 # Make sure the replace_dict has a 'call_line'/call_line pair so that any 161 # '<call_line>' text gets replaced as intended. 162 replace_dict['call_line'] = call_line 163 164 # Do the replacements. 165 for key, value in replace_dict.items(): 166 func_def = [w.replace("<" + key + ">", value) for w in func_def] 167 168 return '\n'.join(func_def) + "\n" 169