1#!/usr/bin/env python3
2
3r"""
4This module provides argument manipulation functions like pop_arg.
5"""
6
7import collections
8
9import gen_print as gp
10
11
12def pop_arg(pop_arg_default=None, *args, **kwargs):
13    r"""
14    Pop a named argument from the args/kwargs and return a tuple consisting of the argument value, the
15    modified args and the modified kwargs.
16
17    The name of the argument is determined automatically by this function by examining the source code which
18    calls it (see examples below).  If no suitable argument can be found, the default value passed to this
19    function will be returned as the argument value.  This function is useful for wrapper functions that wish
20    to process arguments in some way before calling subordinate function.
21
22    Examples:
23
24    Given this code:
25
26    def func1(*args, **kwargs):
27
28        last_name, args, kwargs = pop_arg('Doe', *args, **kwargs)
29        some_function(last_name.capitalize(), *args, **kwargs)
30
31    Consider this call to func1:
32
33    func1('Johnson', ssn='111-11-1111')
34
35    The pop_arg in func1 would return the following:
36
37        'Johnson', [], {'ssn': "111-11-1111"}
38
39    Notice that the 'args' value returned is an empty list. Since last_name was assumed to be the first
40    positional argument, it was popped from args.
41
42    Now consider this call to func1:
43
44    func1(last_name='Johnson', ssn='111-11-1111')
45
46    The pop_arg in func1 would return the same last_name value as in the previous example.  The only
47    difference being that the last_name value was popped from kwargs rather than from args.
48
49    Description of argument(s):
50    pop_arg_default                 The value to return if the named argument is not present in args/kwargs.
51    args                            The positional arguments passed to the calling function.
52    kwargs                          The keyword arguments passed to the calling function.
53    """
54
55    # Retrieve the argument name by examining the source code.
56    arg_name = gp.get_arg_name(None, arg_num=-3, stack_frame_ix=2)
57    if arg_name in kwargs:
58        arg_value = kwargs.pop(arg_name)
59    else:
60        # Convert args from a tuple to a list.
61        args = list(args)
62        if args:
63            arg_value = args.pop(0)
64        else:
65            arg_value = pop_arg_default
66
67    return arg_value, args, kwargs
68
69
70def source_to_object(value):
71    r"""
72    Evaluate string value as python source code and return the resulting object.
73
74    If value is NOT a string or can not be interpreted as a python source object definition, simply return
75    value.
76
77    The idea is to convert python object definition source code (e.g. for lists, dictionaries, tuples, etc.)
78    into an object.
79
80    Example:
81
82    Note that this first example is a special case in that it is a short-cut for specifying a
83    collections.OrderedDict.
84
85    result = source_to_object("[('one', 1), ('two', 2), ('three', 3)]")
86
87    The result is a collections.OrderedDict object:
88
89    result:
90      [one]:                     1
91      [two]:                     2
92      [three]:                   3
93
94    This is a short-cut for the long form shown here:
95
96    result = source_to_object("collections.OrderedDict([
97        ('one', 1),
98        ('two', 2),
99        ('three', 3)])")
100
101    Also note that support for this special-case short-cut precludes the possibility of interpreting such a
102    string as a list of tuples.
103
104    Example:
105
106    In this example, the result will be a list:
107
108    result = source_to_object("[1, 2, 3]")
109
110    result:
111      result[0]:                 1
112      result[1]:                 2
113      result[2]:                 3
114
115    Example:
116
117    In this example, the value passed to this function is not a string, so it is simply returned.
118
119    result = source_to_object(1)
120
121    More examples:
122    result = source_to_object("dict(one=1, two=2, three=3)")
123    result = source_to_object("{'one':1, 'two':2, 'three':3}")
124    result = source_to_object(True)
125    etc.
126
127    Description of argument(s):
128    value                           If value is a string, it will be evaluated as a python statement.  If the
129                                    statement is valid, the resulting object will be returned.  In all other
130                                    cases, the value will simply be returned.
131    """
132
133    if type(value) not in gp.get_string_types():
134        return value
135
136    # Strip white space prior to attempting to interpret the string as python code.
137    value = value.strip()
138
139    # Try special case of collections.OrderedDict which accepts a list of tuple pairs.
140    if value.startswith("[("):
141        try:
142            return eval("collections.OrderedDict(" + value + ")")
143        except (TypeError, NameError, ValueError):
144            pass
145
146    try:
147        return eval(value)
148    except (NameError, SyntaxError):
149        pass
150
151    return value
152
153
154def args_to_objects(args):
155    r"""
156    Run source_to_object() on each element in args and return the result.
157
158    Description of argument(s):
159    args                            A type of dictionary, list, set, tuple or simple object whose elements
160                                    are to be converted via a call to source_to_object().
161    """
162
163    type_of_dict = gp.is_dict(args)
164    if type_of_dict:
165        if type_of_dict == gp.dict_type():
166            return {k: source_to_object(v) for (k, v) in args.items()}
167        elif type_of_dict == gp.ordered_dict_type():
168            return collections.OrderedDict((k, v) for (k, v) in args.items())
169        elif type_of_dict == gp.dot_dict_type():
170            return DotDict((k, v) for (k, v) in args.items())
171        elif type_of_dict == gp.normalized_dict_type():
172            return NormalizedDict((k, v) for (k, v) in args.items())
173    # Assume args is list, tuple or set.
174    if type(args) in (list, set):
175        return [source_to_object(arg) for arg in args]
176    elif type(args) is tuple:
177        return tuple([source_to_object(arg) for arg in args])
178
179    return source_to_object(args)
180