blob: d779b4896d175373ab630db953918b1582bc44fa [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
George Keishinge635ddc2022-12-08 07:38:02 -06007import collections
Patrick Williams57318182022-12-08 06:18:26 -06008
Patrick Williams20f38712022-12-08 06:18:26 -06009import gen_print as gp
10
Michael Walsh0ff2eed2019-03-12 16:21:47 -050011
Michael Walsheefa8d92019-11-20 13:42:38 -060012def pop_arg(pop_arg_default=None, *args, **kwargs):
Michael Walsh0ff2eed2019-03-12 16:21:47 -050013 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050014 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.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050016
Michael Walsh410b1782019-10-22 15:56:18 -050017 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.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050021
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
Michael Walsh410b1782019-10-22 15:56:18 -050039 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.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050041
42 Now consider this call to func1:
43
44 func1(last_name='Johnson', ssn='111-11-1111')
45
Michael Walsh410b1782019-10-22 15:56:18 -050046 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.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050048
49 Description of argument(s):
Michael Walsheefa8d92019-11-20 13:42:38 -060050 pop_arg_default The value to return if the named argument is not present in args/kwargs.
Michael Walsh410b1782019-10-22 15:56:18 -050051 args The positional arguments passed to the calling function.
52 kwargs The keyword arguments passed to the calling function.
Michael Walsh0ff2eed2019-03-12 16:21:47 -050053 """
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:
Michael Walsheefa8d92019-11-20 13:42:38 -060065 arg_value = pop_arg_default
Michael Walsh0ff2eed2019-03-12 16:21:47 -050066
67 return arg_value, args, kwargs
Michael Walshc28deec2019-05-17 15:35:51 -050068
69
70def source_to_object(value):
71 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050072 Evaluate string value as python source code and return the resulting object.
Michael Walshc28deec2019-05-17 15:35:51 -050073
Michael Walsh410b1782019-10-22 15:56:18 -050074 If value is NOT a string or can not be interpreted as a python source object definition, simply return
75 value.
Michael Walshc28deec2019-05-17 15:35:51 -050076
Michael Walsh410b1782019-10-22 15:56:18 -050077 The idea is to convert python object definition source code (e.g. for lists, dictionaries, tuples, etc.)
78 into an object.
Michael Walshc28deec2019-05-17 15:35:51 -050079
80 Example:
81
Michael Walsh410b1782019-10-22 15:56:18 -050082 Note that this first example is a special case in that it is a short-cut for specifying a
83 collections.OrderedDict.
Michael Walshc28deec2019-05-17 15:35:51 -050084
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
Michael Walsh410b1782019-10-22 15:56:18 -0500101 Also note that support for this special-case short-cut precludes the possibility of interpreting such a
102 string as a list of tuples.
Michael Walshc28deec2019-05-17 15:35:51 -0500103
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
Michael Walsh410b1782019-10-22 15:56:18 -0500117 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 -0500118
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):
Michael Walsh410b1782019-10-22 15:56:18 -0500128 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.
Michael Walshc28deec2019-05-17 15:35:51 -0500131 """
132
133 if type(value) not in gp.get_string_types():
134 return value
135
Michael Walsh410b1782019-10-22 15:56:18 -0500136 # Strip white space prior to attempting to interpret the string as python code.
Michael Walshc28deec2019-05-17 15:35:51 -0500137 value = value.strip()
138
Michael Walsh410b1782019-10-22 15:56:18 -0500139 # Try special case of collections.OrderedDict which accepts a list of tuple pairs.
Michael Walsh3af60872019-08-01 11:13:18 -0500140 if value.startswith("[("):
Michael Walshc28deec2019-05-17 15:35:51 -0500141 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):
Michael Walsh410b1782019-10-22 15:56:18 -0500159 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().
Michael Walshc28deec2019-05-17 15:35:51 -0500161 """
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)