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 if base_arg_spec[3] is None: 74 base_default_list = [] 75 else: 76 base_default_list = list(base_arg_spec[3]) 77 num_defaults = len(base_default_list) 78 num_non_defaults = num_args - num_defaults 79 80 # Create base_arg_default_string which is a reconstruction of the base 81 # function's argument list. 82 # Example base_arg_default_string: 83 # headers, last=2, first=[1] 84 # First, create a new list where each entry is of the form "arg=default". 85 base_arg_default_list = list(base_arg_list) 86 for ix in range(num_non_defaults, len(base_arg_default_list)): 87 base_default_ix = ix - num_non_defaults 88 if isinstance(base_default_list[base_default_ix], str): 89 default_string = "'" + base_default_list[base_default_ix] + "'" 90 # Convert "\n" to "\\n". 91 default_string = default_string.replace("\n", "\\n") 92 else: 93 default_string = str(base_default_list[base_default_ix]) 94 base_arg_default_list[ix] += "=" + default_string 95 base_arg_default_string = ', '.join(base_arg_default_list + var_args) 96 97 # Create the argument string which can be used to call the base function. 98 # Example call_arg_string: 99 # headers=headers, last=last, first=first 100 call_arg_string = ', '.join([val + "=" + val for val in base_arg_list] 101 + var_args) 102 103 # Compose the result values. 104 func_def_line = "def " + wrap_func_name + "(" + base_arg_default_string +\ 105 "):" 106 call_line = base_func_name + "(" + call_arg_string + ")" 107 108 return func_def_line, call_line 109 110 111def create_func_def_string(base_func_name, 112 wrap_func_name, 113 func_body_template, 114 replace_dict): 115 r""" 116 Create and return a complete function definition as a string. The caller 117 may run "exec" on the resulting string to create the desired function. 118 119 Description of argument(s): 120 base_func_name The name of the base function around which 121 a wrapper is being created. 122 wrap_func_name The name of the wrapper function being 123 created. 124 func_body_template A function body in the form of a list. 125 Each list element represents one line of a 126 function This is a template in so far as 127 text substitions will be done on it to 128 arrive at a valid function definition. 129 This template should NOT contain the 130 function definition line (e.g. "def 131 func1():"). create_func_def_string will 132 pre-pend the definition line. The 133 template should also contain the text 134 "<call_line>" which is to be replaced by 135 text which will call the base function 136 with appropriate arguments. 137 replace_dict A dictionary indicating additional text 138 replacements to be done. For example, if 139 the template contains a "<sub1>" (be sure 140 to include the angle brackets), and the 141 dictionary contains a key/value pair of 142 'sub1'/'replace1', then all instances of 143 "<sub1>" will be replaced by "replace1". 144 """ 145 146 # Create the initial function definition list as a copy of the template. 147 func_def = list(func_body_template) 148 # Call create_wrapper_def_and_call to get func_def_line and call_line. 149 func_def_line, call_line = create_wrapper_def_and_call(base_func_name, 150 wrap_func_name) 151 # Insert the func_def_line composed by create_wrapper_def_and_call is the 152 # first list entry. 153 func_def.insert(0, func_def_line) 154 # Make sure the replace_dict has a 'call_line'/call_line pair so that any 155 # '<call_line>' text gets replaced as intended. 156 replace_dict['call_line'] = call_line 157 158 # Do the replacements. 159 for key, value in replace_dict.items(): 160 func_def = [w.replace("<" + key + ">", value) for w in func_def] 161 162 return '\n'.join(func_def) + "\n" 163