blob: 8434a40cbc09142bcfb93e2c3853acb5b2b52653 [file] [log] [blame]
Michael Walsh264bc142017-11-13 11:18:12 -06001#!/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"