blob: b51844b4892b8d80775c1e21f6bfbfd25e8cdb03 [file] [log] [blame]
#!/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]
keyword_args = base_arg_spec[2]
if keyword_args is None:
keyword_args = []
else:
keyword_args = ["**" + keyword_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 isinstance(base_default_list[base_default_ix], 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 + keyword_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 + keyword_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 substitutions 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"