blob: 306af3844772bf9399727308df423af70428b5e1 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walsh0ff2eed2019-03-12 16:21:47 -05002
3r"""
4This module provides argument manipulation functions like pop_arg.
5"""
6
Patrick Williams57318182022-12-08 06:18:26 -06007import gen_print as gp
George Keishinge635ddc2022-12-08 07:38:02 -06008import collections
Patrick Williams57318182022-12-08 06:18:26 -06009
Michael Walsh0ff2eed2019-03-12 16:21:47 -050010
Michael Walsheefa8d92019-11-20 13:42:38 -060011def pop_arg(pop_arg_default=None, *args, **kwargs):
Michael Walsh0ff2eed2019-03-12 16:21:47 -050012 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050013 Pop a named argument from the args/kwargs and return a tuple consisting of the argument value, the
14 modified args and the modified kwargs.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050015
Michael Walsh410b1782019-10-22 15:56:18 -050016 The name of the argument is determined automatically by this function by examining the source code which
17 calls it (see examples below). If no suitable argument can be found, the default value passed to this
18 function will be returned as the argument value. This function is useful for wrapper functions that wish
19 to process arguments in some way before calling subordinate function.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050020
21 Examples:
22
23 Given this code:
24
25 def func1(*args, **kwargs):
26
27 last_name, args, kwargs = pop_arg('Doe', *args, **kwargs)
28 some_function(last_name.capitalize(), *args, **kwargs)
29
30 Consider this call to func1:
31
32 func1('Johnson', ssn='111-11-1111')
33
34 The pop_arg in func1 would return the following:
35
36 'Johnson', [], {'ssn': "111-11-1111"}
37
Michael Walsh410b1782019-10-22 15:56:18 -050038 Notice that the 'args' value returned is an empty list. Since last_name was assumed to be the first
39 positional argument, it was popped from args.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050040
41 Now consider this call to func1:
42
43 func1(last_name='Johnson', ssn='111-11-1111')
44
Michael Walsh410b1782019-10-22 15:56:18 -050045 The pop_arg in func1 would return the same last_name value as in the previous example. The only
46 difference being that the last_name value was popped from kwargs rather than from args.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050047
48 Description of argument(s):
Michael Walsheefa8d92019-11-20 13:42:38 -060049 pop_arg_default The value to return if the named argument is not present in args/kwargs.
Michael Walsh410b1782019-10-22 15:56:18 -050050 args The positional arguments passed to the calling function.
51 kwargs The keyword arguments passed to the calling function.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050052 """
53
54 # Retrieve the argument name by examining the source code.
55 arg_name = gp.get_arg_name(None, arg_num=-3, stack_frame_ix=2)
56 if arg_name in kwargs:
57 arg_value = kwargs.pop(arg_name)
58 else:
59 # Convert args from a tuple to a list.
60 args = list(args)
61 if args:
62 arg_value = args.pop(0)
63 else:
Michael Walsheefa8d92019-11-20 13:42:38 -060064 arg_value = pop_arg_default
Michael Walsh0ff2eed2019-03-12 16:21:47 -050065
66 return arg_value, args, kwargs
Michael Walshc28deec2019-05-17 15:35:51 -050067
68
69def source_to_object(value):
70 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050071 Evaluate string value as python source code and return the resulting object.
Michael Walshc28deec2019-05-17 15:35:51 -050072
Michael Walsh410b1782019-10-22 15:56:18 -050073 If value is NOT a string or can not be interpreted as a python source object definition, simply return
74 value.
Michael Walshc28deec2019-05-17 15:35:51 -050075
Michael Walsh410b1782019-10-22 15:56:18 -050076 The idea is to convert python object definition source code (e.g. for lists, dictionaries, tuples, etc.)
77 into an object.
Michael Walshc28deec2019-05-17 15:35:51 -050078
79 Example:
80
Michael Walsh410b1782019-10-22 15:56:18 -050081 Note that this first example is a special case in that it is a short-cut for specifying a
82 collections.OrderedDict.
Michael Walshc28deec2019-05-17 15:35:51 -050083
84 result = source_to_object("[('one', 1), ('two', 2), ('three', 3)]")
85
86 The result is a collections.OrderedDict object:
87
88 result:
89 [one]: 1
90 [two]: 2
91 [three]: 3
92
93 This is a short-cut for the long form shown here:
94
95 result = source_to_object("collections.OrderedDict([
96 ('one', 1),
97 ('two', 2),
98 ('three', 3)])")
99
Michael Walsh410b1782019-10-22 15:56:18 -0500100 Also note that support for this special-case short-cut precludes the possibility of interpreting such a
101 string as a list of tuples.
Michael Walshc28deec2019-05-17 15:35:51 -0500102
103 Example:
104
105 In this example, the result will be a list:
106
107 result = source_to_object("[1, 2, 3]")
108
109 result:
110 result[0]: 1
111 result[1]: 2
112 result[2]: 3
113
114 Example:
115
Michael Walsh410b1782019-10-22 15:56:18 -0500116 In this example, the value passed to this function is not a string, so it is simply returned.
Michael Walshc28deec2019-05-17 15:35:51 -0500117
118 result = source_to_object(1)
119
120 More examples:
121 result = source_to_object("dict(one=1, two=2, three=3)")
122 result = source_to_object("{'one':1, 'two':2, 'three':3}")
123 result = source_to_object(True)
124 etc.
125
126 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500127 value If value is a string, it will be evaluated as a python statement. If the
128 statement is valid, the resulting object will be returned. In all other
129 cases, the value will simply be returned.
Michael Walshc28deec2019-05-17 15:35:51 -0500130 """
131
132 if type(value) not in gp.get_string_types():
133 return value
134
Michael Walsh410b1782019-10-22 15:56:18 -0500135 # Strip white space prior to attempting to interpret the string as python code.
Michael Walshc28deec2019-05-17 15:35:51 -0500136 value = value.strip()
137
Michael Walsh410b1782019-10-22 15:56:18 -0500138 # Try special case of collections.OrderedDict which accepts a list of tuple pairs.
Michael Walsh3af60872019-08-01 11:13:18 -0500139 if value.startswith("[("):
Michael Walshc28deec2019-05-17 15:35:51 -0500140 try:
141 return eval("collections.OrderedDict(" + value + ")")
142 except (TypeError, NameError, ValueError):
143 pass
144
145 try:
146 return eval(value)
147 except (NameError, SyntaxError):
148 pass
149
150 return value
151
152
153def args_to_objects(args):
154 r"""
155 Run source_to_object() on each element in args and return the result.
156
157 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500158 args A type of dictionary, list, set, tuple or simple object whose elements
159 are to be converted via a call to source_to_object().
Michael Walshc28deec2019-05-17 15:35:51 -0500160 """
161
162 type_of_dict = gp.is_dict(args)
163 if type_of_dict:
164 if type_of_dict == gp.dict_type():
165 return {k: source_to_object(v) for (k, v) in args.items()}
166 elif type_of_dict == gp.ordered_dict_type():
167 return collections.OrderedDict((k, v) for (k, v) in args.items())
168 elif type_of_dict == gp.dot_dict_type():
169 return DotDict((k, v) for (k, v) in args.items())
170 elif type_of_dict == gp.normalized_dict_type():
171 return NormalizedDict((k, v) for (k, v) in args.items())
172 # Assume args is list, tuple or set.
173 if type(args) in (list, set):
174 return [source_to_object(arg) for arg in args]
175 elif type(args) is tuple:
176 return tuple([source_to_object(arg) for arg in args])
177
178 return source_to_object(args)