1#!/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"
165