Skip to content

Commit db8cfd4

Browse files
Allow passing positional parameters as optional
1 parent aac4991 commit db8cfd4

File tree

3 files changed

+69
-21
lines changed

3 files changed

+69
-21
lines changed

cloudinary_cli/modules/upload_dir.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from cloudinary_cli.utils.api_utils import upload_file
88
from cloudinary_cli.utils.file_utils import get_destination_folder, is_hidden_path
9-
from cloudinary_cli.utils.utils import parse_option_value, logger, run_tasks_concurrently
9+
from cloudinary_cli.utils.utils import parse_option_value, logger, run_tasks_concurrently, group_params
1010

1111

1212
@command("upload_dir", help="""Upload a folder of assets, maintaining the folder structure.""")
@@ -62,8 +62,7 @@ def upload_dir(directory, glob_pattern, include_hidden, optional_parameter, opti
6262

6363
options = {
6464
**defaults,
65-
**{k: v for k, v in optional_parameter},
66-
**{k: parse_option_value(v) for k, v in optional_parameter_parsed},
65+
**group_params(optional_parameter, ((k, parse_option_value(v)) for k, v in optional_parameter_parsed)),
6766
}
6867

6968
uploads = []

cloudinary_cli/utils/utils.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ def load_template(language, template_name):
9595

9696

9797
def parse_option_value(value):
98+
if isinstance(value, list):
99+
return list(map(parse_option_value, value))
100+
98101
if value == "True" or value == "true":
99102
return True
100103
elif value == "False" or value == "false":
@@ -109,20 +112,27 @@ def parse_option_value(value):
109112
return value
110113

111114

112-
def parse_args_kwargs(func, params):
115+
def parse_args_kwargs(func, params=None, kwargs=None):
116+
if params is None:
117+
params = []
118+
if kwargs is None:
119+
kwargs = {}
120+
113121
spec = getfullargspec(func)
114-
n_args = len(spec.args) if spec.args else 0
115-
n_defaults = len(spec.defaults) if spec.defaults else 0
116122

117-
n_req = n_args - n_defaults
118-
if len(params) < n_req:
123+
num_args = len(spec.args) if spec.args else 0
124+
num_defaults = len(spec.defaults) if spec.defaults else 0
125+
126+
num_req = num_args - num_defaults
127+
num_provided_args = len(params)
128+
num_overall_provided = num_provided_args + len([p for p in kwargs.keys() if p in spec.args[num_provided_args:]])
129+
if num_overall_provided < num_req:
119130
func_sig = signature(func)
120-
raise Exception(f"Function '{func.__name__}{func_sig}' requires {n_req} positional arguments")
131+
raise Exception(f"Function '{func.__name__}{func_sig}' requires {num_req} positional arguments")
121132
# consume required args
122-
args = [parse_option_value(p) for p in params[:n_req]]
123-
kwargs = {}
133+
args = [parse_option_value(p) for p in params[:num_req]]
124134

125-
for p in params[n_req:]:
135+
for p in params[num_req:]:
126136
if '=' not in p:
127137
# named/positional with default value args passed as positional
128138
args.append(parse_option_value(p))
@@ -207,17 +217,31 @@ def get_command_params(
207217
if not callable(func):
208218
raise Exception(f"{params[0]} is not callable.")
209219

210-
args, kwargs = parse_args_kwargs(func, params[1:])
220+
kwargs = group_params(optional_parameter, ((k, parse_option_value(v)) for k, v in optional_parameter_parsed))
211221

212-
kwargs = {
213-
**kwargs,
214-
**{k: v for k, v in optional_parameter},
215-
**{k: parse_option_value(v) for k, v in optional_parameter_parsed},
216-
}
222+
args, kwargs = parse_args_kwargs(func, params[1:], kwargs)
217223

218224
return func, args, kwargs
219225

220226

227+
def group_params(*params):
228+
"""
229+
Groups parameters (which are passed as list of tuples) by keys. Duplicate keys' values are combined into lists.
230+
231+
:param params: the list of parameters to group
232+
:return: dict
233+
"""
234+
res = {}
235+
for param in params:
236+
for k, v in param:
237+
if k in res:
238+
res[k] = (res[k] if isinstance(res[k], list) else [res[k]]) + [v]
239+
continue
240+
res[k] = v
241+
242+
return res
243+
244+
221245
def print_help_and_exit():
222246
"""
223247
Prints help for the current command and exits.

test/test_utils.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22

33
from cloudinary_cli.utils.utils import parse_option_value, parse_args_kwargs, whitelist_keys, merge_responses, \
4-
normalize_list_params, chunker
4+
normalize_list_params, chunker, group_params
55

66

77
class UtilsTest(unittest.TestCase):
@@ -13,8 +13,14 @@ def test_parse_option_value(self):
1313
self.assertListEqual(["test", "123"], parse_option_value('["test","123"]'))
1414
self.assertDictEqual({"foo": "bar"}, parse_option_value('{"foo":"bar"}'))
1515
self.assertDictEqual({"an": "object", "or": "dict"}, parse_option_value('{"an":"object","or":"dict"}'))
16-
self.assertListEqual(["this", "will", "be", "read", "as",
17-
"a", "list"], parse_option_value('["this","will","be","read","as","a","list"]'))
16+
self.assertListEqual(
17+
["this", "will", "be", "read", "as","a", "list"],
18+
parse_option_value('["this","will","be","read","as","a","list"]')
19+
)
20+
self.assertListEqual(
21+
[True, False, 123, '0', ['test', '123']],
22+
parse_option_value(["True", "false", "123", "0", '["test","123"]'])
23+
)
1824

1925
def test_parse_option_value_converts_int_to_str(self):
2026
""" should convert a parsed 0 to a str """
@@ -47,6 +53,25 @@ def test_parse_args_kwargs(self):
4753
self.assertListEqual(["a1", "a2"], args)
4854
self.assertEqual(0, len(kwargs))
4955

56+
# should allow passing positional as optional
57+
args, kwargs = parse_args_kwargs(_only_args_test_func, ["a1"], {"arg2": "a2"})
58+
self.assertListEqual(["a1"], args)
59+
self.assertDictEqual({"arg2": "a2"}, kwargs)
60+
61+
args, kwargs = parse_args_kwargs(_only_args_test_func, [], {"arg1": "a1", "arg2": "a2"})
62+
self.assertEqual(0, len(args))
63+
self.assertDictEqual({"arg1": "a1", "arg2": "a2"}, kwargs)
64+
65+
def test_group_params(self):
66+
self.assertDictEqual({}, group_params([]))
67+
self.assertDictEqual({"k1": "v1", "k2": "v2"}, group_params([("k1", "v1"), ("k2", "v2")]))
68+
self.assertDictEqual({"k1": ["v1", "v2"]}, group_params([("k1", "v1"), ("k1", "v2")]))
69+
self.assertDictEqual({"k1": "v1", "k2": ["v2", "v3"]}, group_params([("k1", "v1"), ("k2", "v2"), ("k2", "v3")]))
70+
self.assertDictEqual(
71+
{"k1": ["v1", "v2", "v3"]},
72+
group_params([("k1", "v1")], [("k1", "v2")], [("k1", "v3")])
73+
)
74+
5075
def test_whitelist_keys(self):
5176
""" should whitelist keys correctly """
5277
self.assertEqual([{"k1": "v1"}], whitelist_keys([{"k1": "v1", "k2": "v2"}], ["k1"]))

0 commit comments

Comments
 (0)