blob: b51844b4892b8d80775c1e21f6bfbfd25e8cdb03 [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):
Michael Walsh264bc142017-11-13 11:18:12 -060016 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]
Michael Walsh2dd46fb2018-10-30 13:10:21 -050073 keyword_args = base_arg_spec[2]
74 if keyword_args is None:
75 keyword_args = []
76 else:
77 keyword_args = ["**" + keyword_args]
Michael Walsh264bc142017-11-13 11:18:12 -060078 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
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -050093 if isinstance(base_default_list[base_default_ix], str):
Michael Walsh264bc142017-11-13 11:18:12 -060094 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
Michael Walsh2dd46fb2018-10-30 13:10:21 -0500100 base_arg_default_string =\
101 ', '.join(base_arg_default_list + var_args + keyword_args)
Michael Walsh264bc142017-11-13 11:18:12 -0600102
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
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500106 call_arg_string = ', '.join([val + "=" + val for val in base_arg_list]
Michael Walsh2dd46fb2018-10-30 13:10:21 -0500107 + var_args + keyword_args)
Michael Walsh264bc142017-11-13 11:18:12 -0600108
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):
Michael Walsh264bc142017-11-13 11:18:12 -0600121 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
George Keishingcb3b9b62018-09-14 12:11:19 -0500133 text substitutions will be done on it to
Michael Walsh264bc142017-11-13 11:18:12 -0600134 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"