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