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