New wrap_utils.py for help writing wrapper functions.
Change-Id: I6f3f3063fdee96d75d9fc015e2ec337377be372c
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/wrap_utils.py b/lib/wrap_utils.py
new file mode 100755
index 0000000..8434a40
--- /dev/null
+++ b/lib/wrap_utils.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+
+r"""
+This module provides functions which are useful for writing python wrapper
+functions (i.e. in this context, a wrapper function is one whose aim is to
+call some other function on the caller's behalf but to provide some additional
+functionality over and above what the base function provides).
+"""
+
+import sys
+import inspect
+
+
+def create_wrapper_def_and_call(base_func_name,
+ wrap_func_name):
+
+ r"""
+ Return a wrapper function definition line and a base function call line.
+
+ This is a utility for helping to create wrapper functions.
+
+ For example, if there existed a function with the following definition
+ line:
+ def sprint_foo_bar(headers=1):
+
+ And the user wished to write a print_foo_bar wrapper function, they could
+ call create_wrapper_def_and_call as follows:
+
+ func_def_line, call_line = create_wrapper_def_and_call("sprint_foo_bar",
+ "print_foo_bar")
+
+ They would get the following results:
+ func_def_line def print_foo_bar(headers=1):
+ call_line sprint_foo_bar(headers=headers)
+
+ The func_def_line is suitable as the definition line for the wrapper
+ function. The call_line is suitable for use in the new wrapper function
+ wherever it wishes to call the base function. By explicitly specifying
+ each parm in the definition and the call line, we allow the caller of the
+ wrapper function to refer to any given parm by name rather than having to
+ specify parms positionally.
+
+ Description of argument(s):
+ base_func_name The name of the base function around which
+ a wrapper is being created.
+ wrap_func_name The name of the wrapper function being
+ created.
+ """
+
+ # Get caller's module name. Note: that for the present we've hard-coded
+ # the stack_frame_ix value because we expect a call stack to this function
+ # to be something like this:
+ # caller
+ # create_print_wrapper_funcs
+ # create_func_def_string
+ # create_wrapper_def_and_call
+ stack_frame_ix = 3
+ frame = inspect.stack()[stack_frame_ix]
+ module = inspect.getmodule(frame[0])
+ mod_name = module.__name__
+
+ # Get a reference to the base function.
+ base_func = getattr(sys.modules[mod_name], base_func_name)
+ # Get the argument specification for the base function.
+ base_arg_spec = inspect.getargspec(base_func)
+ base_arg_list = base_arg_spec[0]
+ num_args = len(base_arg_list)
+ # Get the variable argument specification for the base function.
+ var_args = base_arg_spec[1]
+ if var_args is None:
+ var_args = []
+ else:
+ var_args = ["*" + var_args]
+ if base_arg_spec[3] is None:
+ base_default_list = []
+ else:
+ base_default_list = list(base_arg_spec[3])
+ num_defaults = len(base_default_list)
+ num_non_defaults = num_args - num_defaults
+
+ # Create base_arg_default_string which is a reconstruction of the base
+ # function's argument list.
+ # Example base_arg_default_string:
+ # headers, last=2, first=[1]
+ # First, create a new list where each entry is of the form "arg=default".
+ base_arg_default_list = list(base_arg_list)
+ for ix in range(num_non_defaults, len(base_arg_default_list)):
+ base_default_ix = ix - num_non_defaults
+ if type(base_default_list[base_default_ix]) is str:
+ default_string = "'" + base_default_list[base_default_ix] + "'"
+ # Convert "\n" to "\\n".
+ default_string = default_string.replace("\n", "\\n")
+ else:
+ default_string = str(base_default_list[base_default_ix])
+ base_arg_default_list[ix] += "=" + default_string
+ base_arg_default_string = ', '.join(base_arg_default_list + var_args)
+
+ # Create the argument string which can be used to call the base function.
+ # Example call_arg_string:
+ # headers=headers, last=last, first=first
+ call_arg_string = ', '.join([val + "=" + val for val in base_arg_list] +
+ var_args)
+
+ # Compose the result values.
+ func_def_line = "def " + wrap_func_name + "(" + base_arg_default_string +\
+ "):"
+ call_line = base_func_name + "(" + call_arg_string + ")"
+
+ return func_def_line, call_line
+
+
+def create_func_def_string(base_func_name,
+ wrap_func_name,
+ func_body_template,
+ replace_dict):
+
+ r"""
+ Create and return a complete function definition as a string. The caller
+ may run "exec" on the resulting string to create the desired function.
+
+ Description of argument(s):
+ base_func_name The name of the base function around which
+ a wrapper is being created.
+ wrap_func_name The name of the wrapper function being
+ created.
+ func_body_template A function body in the form of a list.
+ Each list element represents one line of a
+ function This is a template in so far as
+ text substitions will be done on it to
+ arrive at a valid function definition.
+ This template should NOT contain the
+ function definition line (e.g. "def
+ func1():"). create_func_def_string will
+ pre-pend the definition line. The
+ template should also contain the text
+ "<call_line>" which is to be replaced by
+ text which will call the base function
+ with appropriate arguments.
+ replace_dict A dictionary indicating additional text
+ replacements to be done. For example, if
+ the template contains a "<sub1>" (be sure
+ to include the angle brackets), and the
+ dictionary contains a key/value pair of
+ 'sub1'/'replace1', then all instances of
+ "<sub1>" will be replaced by "replace1".
+ """
+
+ # Create the initial function definition list as a copy of the template.
+ func_def = list(func_body_template)
+ # Call create_wrapper_def_and_call to get func_def_line and call_line.
+ func_def_line, call_line = create_wrapper_def_and_call(base_func_name,
+ wrap_func_name)
+ # Insert the func_def_line composed by create_wrapper_def_and_call is the
+ # first list entry.
+ func_def.insert(0, func_def_line)
+ # Make sure the replace_dict has a 'call_line'/call_line pair so that any
+ # '<call_line>' text gets replaced as intended.
+ replace_dict['call_line'] = call_line
+
+ # Do the replacements.
+ for key, value in replace_dict.items():
+ func_def = [w.replace("<" + key + ">", value) for w in func_def]
+
+ return '\n'.join(func_def) + "\n"