1e7e9171eSGeorge Keishing#!/usr/bin/env python3
20ff2eed2SMichael Walsh
30ff2eed2SMichael Walshr"""
40ff2eed2SMichael WalshThis module provides argument manipulation functions like pop_arg.
50ff2eed2SMichael Walsh"""
60ff2eed2SMichael Walsh
7e635ddc0SGeorge Keishingimport collections
85731818dSPatrick Williams
9*20f38712SPatrick Williamsimport gen_print as gp
10*20f38712SPatrick Williams
110ff2eed2SMichael Walsh
12eefa8d98SMichael Walshdef pop_arg(pop_arg_default=None, *args, **kwargs):
130ff2eed2SMichael Walsh    r"""
14410b1787SMichael Walsh    Pop a named argument from the args/kwargs and return a tuple consisting of the argument value, the
15410b1787SMichael Walsh    modified args and the modified kwargs.
160ff2eed2SMichael Walsh
17410b1787SMichael Walsh    The name of the argument is determined automatically by this function by examining the source code which
18410b1787SMichael Walsh    calls it (see examples below).  If no suitable argument can be found, the default value passed to this
19410b1787SMichael Walsh    function will be returned as the argument value.  This function is useful for wrapper functions that wish
20410b1787SMichael Walsh    to process arguments in some way before calling subordinate function.
210ff2eed2SMichael Walsh
220ff2eed2SMichael Walsh    Examples:
230ff2eed2SMichael Walsh
240ff2eed2SMichael Walsh    Given this code:
250ff2eed2SMichael Walsh
260ff2eed2SMichael Walsh    def func1(*args, **kwargs):
270ff2eed2SMichael Walsh
280ff2eed2SMichael Walsh        last_name, args, kwargs = pop_arg('Doe', *args, **kwargs)
290ff2eed2SMichael Walsh        some_function(last_name.capitalize(), *args, **kwargs)
300ff2eed2SMichael Walsh
310ff2eed2SMichael Walsh    Consider this call to func1:
320ff2eed2SMichael Walsh
330ff2eed2SMichael Walsh    func1('Johnson', ssn='111-11-1111')
340ff2eed2SMichael Walsh
350ff2eed2SMichael Walsh    The pop_arg in func1 would return the following:
360ff2eed2SMichael Walsh
370ff2eed2SMichael Walsh        'Johnson', [], {'ssn': "111-11-1111"}
380ff2eed2SMichael Walsh
39410b1787SMichael Walsh    Notice that the 'args' value returned is an empty list. Since last_name was assumed to be the first
40410b1787SMichael Walsh    positional argument, it was popped from args.
410ff2eed2SMichael Walsh
420ff2eed2SMichael Walsh    Now consider this call to func1:
430ff2eed2SMichael Walsh
440ff2eed2SMichael Walsh    func1(last_name='Johnson', ssn='111-11-1111')
450ff2eed2SMichael Walsh
46410b1787SMichael Walsh    The pop_arg in func1 would return the same last_name value as in the previous example.  The only
47410b1787SMichael Walsh    difference being that the last_name value was popped from kwargs rather than from args.
480ff2eed2SMichael Walsh
490ff2eed2SMichael Walsh    Description of argument(s):
50eefa8d98SMichael Walsh    pop_arg_default                 The value to return if the named argument is not present in args/kwargs.
51410b1787SMichael Walsh    args                            The positional arguments passed to the calling function.
52410b1787SMichael Walsh    kwargs                          The keyword arguments passed to the calling function.
530ff2eed2SMichael Walsh    """
540ff2eed2SMichael Walsh
550ff2eed2SMichael Walsh    # Retrieve the argument name by examining the source code.
560ff2eed2SMichael Walsh    arg_name = gp.get_arg_name(None, arg_num=-3, stack_frame_ix=2)
570ff2eed2SMichael Walsh    if arg_name in kwargs:
580ff2eed2SMichael Walsh        arg_value = kwargs.pop(arg_name)
590ff2eed2SMichael Walsh    else:
600ff2eed2SMichael Walsh        # Convert args from a tuple to a list.
610ff2eed2SMichael Walsh        args = list(args)
620ff2eed2SMichael Walsh        if args:
630ff2eed2SMichael Walsh            arg_value = args.pop(0)
640ff2eed2SMichael Walsh        else:
65eefa8d98SMichael Walsh            arg_value = pop_arg_default
660ff2eed2SMichael Walsh
670ff2eed2SMichael Walsh    return arg_value, args, kwargs
68c28deec7SMichael Walsh
69c28deec7SMichael Walsh
70c28deec7SMichael Walshdef source_to_object(value):
71c28deec7SMichael Walsh    r"""
72410b1787SMichael Walsh    Evaluate string value as python source code and return the resulting object.
73c28deec7SMichael Walsh
74410b1787SMichael Walsh    If value is NOT a string or can not be interpreted as a python source object definition, simply return
75410b1787SMichael Walsh    value.
76c28deec7SMichael Walsh
77410b1787SMichael Walsh    The idea is to convert python object definition source code (e.g. for lists, dictionaries, tuples, etc.)
78410b1787SMichael Walsh    into an object.
79c28deec7SMichael Walsh
80c28deec7SMichael Walsh    Example:
81c28deec7SMichael Walsh
82410b1787SMichael Walsh    Note that this first example is a special case in that it is a short-cut for specifying a
83410b1787SMichael Walsh    collections.OrderedDict.
84c28deec7SMichael Walsh
85c28deec7SMichael Walsh    result = source_to_object("[('one', 1), ('two', 2), ('three', 3)]")
86c28deec7SMichael Walsh
87c28deec7SMichael Walsh    The result is a collections.OrderedDict object:
88c28deec7SMichael Walsh
89c28deec7SMichael Walsh    result:
90c28deec7SMichael Walsh      [one]:                     1
91c28deec7SMichael Walsh      [two]:                     2
92c28deec7SMichael Walsh      [three]:                   3
93c28deec7SMichael Walsh
94c28deec7SMichael Walsh    This is a short-cut for the long form shown here:
95c28deec7SMichael Walsh
96c28deec7SMichael Walsh    result = source_to_object("collections.OrderedDict([
97c28deec7SMichael Walsh        ('one', 1),
98c28deec7SMichael Walsh        ('two', 2),
99c28deec7SMichael Walsh        ('three', 3)])")
100c28deec7SMichael Walsh
101410b1787SMichael Walsh    Also note that support for this special-case short-cut precludes the possibility of interpreting such a
102410b1787SMichael Walsh    string as a list of tuples.
103c28deec7SMichael Walsh
104c28deec7SMichael Walsh    Example:
105c28deec7SMichael Walsh
106c28deec7SMichael Walsh    In this example, the result will be a list:
107c28deec7SMichael Walsh
108c28deec7SMichael Walsh    result = source_to_object("[1, 2, 3]")
109c28deec7SMichael Walsh
110c28deec7SMichael Walsh    result:
111c28deec7SMichael Walsh      result[0]:                 1
112c28deec7SMichael Walsh      result[1]:                 2
113c28deec7SMichael Walsh      result[2]:                 3
114c28deec7SMichael Walsh
115c28deec7SMichael Walsh    Example:
116c28deec7SMichael Walsh
117410b1787SMichael Walsh    In this example, the value passed to this function is not a string, so it is simply returned.
118c28deec7SMichael Walsh
119c28deec7SMichael Walsh    result = source_to_object(1)
120c28deec7SMichael Walsh
121c28deec7SMichael Walsh    More examples:
122c28deec7SMichael Walsh    result = source_to_object("dict(one=1, two=2, three=3)")
123c28deec7SMichael Walsh    result = source_to_object("{'one':1, 'two':2, 'three':3}")
124c28deec7SMichael Walsh    result = source_to_object(True)
125c28deec7SMichael Walsh    etc.
126c28deec7SMichael Walsh
127c28deec7SMichael Walsh    Description of argument(s):
128410b1787SMichael Walsh    value                           If value is a string, it will be evaluated as a python statement.  If the
129410b1787SMichael Walsh                                    statement is valid, the resulting object will be returned.  In all other
130410b1787SMichael Walsh                                    cases, the value will simply be returned.
131c28deec7SMichael Walsh    """
132c28deec7SMichael Walsh
133c28deec7SMichael Walsh    if type(value) not in gp.get_string_types():
134c28deec7SMichael Walsh        return value
135c28deec7SMichael Walsh
136410b1787SMichael Walsh    # Strip white space prior to attempting to interpret the string as python code.
137c28deec7SMichael Walsh    value = value.strip()
138c28deec7SMichael Walsh
139410b1787SMichael Walsh    # Try special case of collections.OrderedDict which accepts a list of tuple pairs.
1403af6087cSMichael Walsh    if value.startswith("[("):
141c28deec7SMichael Walsh        try:
142c28deec7SMichael Walsh            return eval("collections.OrderedDict(" + value + ")")
143c28deec7SMichael Walsh        except (TypeError, NameError, ValueError):
144c28deec7SMichael Walsh            pass
145c28deec7SMichael Walsh
146c28deec7SMichael Walsh    try:
147c28deec7SMichael Walsh        return eval(value)
148c28deec7SMichael Walsh    except (NameError, SyntaxError):
149c28deec7SMichael Walsh        pass
150c28deec7SMichael Walsh
151c28deec7SMichael Walsh    return value
152c28deec7SMichael Walsh
153c28deec7SMichael Walsh
154c28deec7SMichael Walshdef args_to_objects(args):
155c28deec7SMichael Walsh    r"""
156c28deec7SMichael Walsh    Run source_to_object() on each element in args and return the result.
157c28deec7SMichael Walsh
158c28deec7SMichael Walsh    Description of argument(s):
159410b1787SMichael Walsh    args                            A type of dictionary, list, set, tuple or simple object whose elements
160410b1787SMichael Walsh                                    are to be converted via a call to source_to_object().
161c28deec7SMichael Walsh    """
162c28deec7SMichael Walsh
163c28deec7SMichael Walsh    type_of_dict = gp.is_dict(args)
164c28deec7SMichael Walsh    if type_of_dict:
165c28deec7SMichael Walsh        if type_of_dict == gp.dict_type():
166c28deec7SMichael Walsh            return {k: source_to_object(v) for (k, v) in args.items()}
167c28deec7SMichael Walsh        elif type_of_dict == gp.ordered_dict_type():
168c28deec7SMichael Walsh            return collections.OrderedDict((k, v) for (k, v) in args.items())
169c28deec7SMichael Walsh        elif type_of_dict == gp.dot_dict_type():
170c28deec7SMichael Walsh            return DotDict((k, v) for (k, v) in args.items())
171c28deec7SMichael Walsh        elif type_of_dict == gp.normalized_dict_type():
172c28deec7SMichael Walsh            return NormalizedDict((k, v) for (k, v) in args.items())
173c28deec7SMichael Walsh    # Assume args is list, tuple or set.
174c28deec7SMichael Walsh    if type(args) in (list, set):
175c28deec7SMichael Walsh        return [source_to_object(arg) for arg in args]
176c28deec7SMichael Walsh    elif type(args) is tuple:
177c28deec7SMichael Walsh        return tuple([source_to_object(arg) for arg in args])
178c28deec7SMichael Walsh
179c28deec7SMichael Walsh    return source_to_object(args)
180