blob: 124d8c8c2c0d703e60b2736325e52e3c6034fcff [file] [log] [blame]
Michael Walsh264bc142017-11-13 11:18:12 -06001#!/usr/bin/env python
2
3r"""
Michael Walsh410b1782019-10-22 15:56:18 -05004This 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).
Michael Walsh264bc142017-11-13 11:18:12 -06007"""
8
9import sys
10import inspect
11
12
13def create_wrapper_def_and_call(base_func_name,
14 wrap_func_name):
Michael Walsh264bc142017-11-13 11:18:12 -060015 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
Michael Walsh410b1782019-10-22 15:56:18 -050020 For example, if there existed a function with the following definition line:
Michael Walsh264bc142017-11-13 11:18:12 -060021 def sprint_foo_bar(headers=1):
22
Michael Walsh410b1782019-10-22 15:56:18 -050023 And the user wished to write a print_foo_bar wrapper function, they could call
24 create_wrapper_def_and_call as follows:
Michael Walsh264bc142017-11-13 11:18:12 -060025
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
Michael Walsh410b1782019-10-22 15:56:18 -050033 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.
Michael Walsh264bc142017-11-13 11:18:12 -060037
38 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050039 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.
Michael Walsh264bc142017-11-13 11:18:12 -060041 """
42
Michael Walsh410b1782019-10-22 15:56:18 -050043 # 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:
Michael Walsh264bc142017-11-13 11:18:12 -060045 # 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]
Michael Walsh2dd46fb2018-10-30 13:10:21 -050066 keyword_args = base_arg_spec[2]
67 if keyword_args is None:
68 keyword_args = []
69 else:
70 keyword_args = ["**" + keyword_args]
Michael Walsh264bc142017-11-13 11:18:12 -060071 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
Michael Walsh410b1782019-10-22 15:56:18 -050078 # Create base_arg_default_string which is a reconstruction of the base function's argument list.
Michael Walsh264bc142017-11-13 11:18:12 -060079 # 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
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -050085 if isinstance(base_default_list[base_default_ix], str):
Michael Walsh264bc142017-11-13 11:18:12 -060086 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
Michael Walsh2dd46fb2018-10-30 13:10:21 -050092 base_arg_default_string =\
93 ', '.join(base_arg_default_list + var_args + keyword_args)
Michael Walsh264bc142017-11-13 11:18:12 -060094
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
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -050098 call_arg_string = ', '.join([val + "=" + val for val in base_arg_list]
Michael Walsh2dd46fb2018-10-30 13:10:21 -050099 + var_args + keyword_args)
Michael Walsh264bc142017-11-13 11:18:12 -0600100
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):
Michael Walsh264bc142017-11-13 11:18:12 -0600113 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500114 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.
Michael Walsh264bc142017-11-13 11:18:12 -0600116
117 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500118 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".
Michael Walsh264bc142017-11-13 11:18:12 -0600133 """
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)
Michael Walsh410b1782019-10-22 15:56:18 -0500140 # Insert the func_def_line composed by create_wrapper_def_and_call is the first list entry.
Michael Walsh264bc142017-11-13 11:18:12 -0600141 func_def.insert(0, func_def_line)
Michael Walsh410b1782019-10-22 15:56:18 -0500142 # Make sure the replace_dict has a 'call_line'/call_line pair so that any '<call_line>' text gets
143 # replaced as intended.
Michael Walsh264bc142017-11-13 11:18:12 -0600144 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"