Skip to content

Commit 18bbea9

Browse files
Improve list parameters handling
1 parent afa59c0 commit 18bbea9

File tree

2 files changed

+60
-6
lines changed

2 files changed

+60
-6
lines changed

cloudinary_cli/utils/utils.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
from functools import reduce
88
from hashlib import md5
99
from inspect import signature, getfullargspec
10+
from typing import get_type_hints
1011
from multiprocessing import pool
1112

1213
import click
1314
import cloudinary
1415
from jinja2 import Environment, FileSystemLoader
1516
from docstring_parser import parse
1617
from cloudinary_cli.defaults import logger, TEMPLATE_FOLDER
18+
from cloudinary.utils import build_array
1719

1820
not_callable = ('is_appengine_sandbox', 'call_tags_api', 'call_context_api', 'call_cacheable_api', 'call_api',
1921
'call_metadata_api', 'call_json_api', 'only', 'transformation_string', 'account_config',
@@ -127,9 +129,11 @@ def parse_args_kwargs(func, params=None, kwargs=None):
127129
num_req = num_args - num_defaults
128130
num_provided_args = len(params)
129131
num_overall_provided = num_provided_args + len([p for p in kwargs.keys() if p in spec.args[num_provided_args:]])
132+
130133
if num_overall_provided < num_req:
131134
func_sig = signature(func)
132135
raise Exception(f"Function '{func.__name__}{func_sig}' requires {num_req} positional arguments")
136+
133137
# consume required args
134138
args = [parse_option_value(p) for p in params[:num_req]]
135139

@@ -142,8 +146,27 @@ def parse_args_kwargs(func, params=None, kwargs=None):
142146
k, v = p.split('=', 1)
143147
kwargs[k] = parse_option_value(v)
144148

149+
params_specs = parse(func.__doc__).params
150+
151+
if len(args) > num_req:
152+
# Here we comsumed more args than the function can get,
153+
# let's see if we have a list arg and pass everything as list.
154+
# Otherwise, let's pass everything as is and hope for the best :)
155+
last_positional_list_param = next((s for s in reversed(params_specs) if s.arg_name not in kwargs and s.type_name and s.type_name.startswith('list')), None)
156+
if last_positional_list_param:
157+
pos = get_index_by_name(spec.args, last_positional_list_param.arg_name)
158+
args[pos] = [args[pos]] + args[num_args:]
159+
args = args[:num_args]
160+
161+
for s in params_specs:
162+
if s.type_name and s.type_name.startswith('list'):
163+
pos = get_index_by_name(spec.args, s.arg_name)
164+
args[pos] = normalize_list_params(args[pos])
165+
145166
return args, kwargs
146167

168+
def get_index_by_name(lst, name):
169+
return next((i for i, item in enumerate(lst) if item == name), -1)
147170

148171
def remove_string_prefix(string, prefix):
149172
return string[string.startswith(prefix) and len(prefix):]
@@ -297,17 +320,20 @@ def normalize_list_params(params):
297320
"""
298321
Normalizes parameters that could be provided as strings separated by ','.
299322
300-
>>> normalize_list_params(["f1,f2", "f3"])
301-
["f1", "f2", "f3"]
323+
>>> normalize_list_params(['f1,f2', 'f3'])
324+
['f1', 'f2', 'f3']
325+
326+
>>> normalize_list_params('f1,f2,f3')
327+
['f1', 'f2', 'f3']
302328
303329
:param params: Params to normalize.
304-
:type params: list
330+
:type params: list[string] or string
305331
306332
:return: A list of normalized params.
307333
:rtype list
308334
"""
309335
normalized_params = []
310-
for f in list(params):
336+
for f in build_array(params):
311337
if "," in f:
312338
normalized_params += f.split(",")
313339
else:

test/test_utils.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def test_parse_option_value(self):
1414
self.assertDictEqual({"foo": "bar"}, parse_option_value('{"foo":"bar"}'))
1515
self.assertDictEqual({"an": "object", "or": "dict"}, parse_option_value('{"an":"object","or":"dict"}'))
1616
self.assertListEqual(
17-
["this", "will", "be", "read", "as","a", "list"],
17+
["this", "will", "be", "read", "as", "a", "list"],
1818
parse_option_value('["this","will","be","read","as","a","list"]')
1919
)
2020
self.assertListEqual(
@@ -62,6 +62,14 @@ def test_parse_args_kwargs(self):
6262
self.assertEqual(0, len(args))
6363
self.assertDictEqual({"arg1": "a1", "arg2": "a2"}, kwargs)
6464

65+
# should consume list values separated by spaces and commas
66+
args, kwargs = parse_args_kwargs(_list_args_test_func, ["l0a0,l0a1,l0a2", "sa0", "l1a0", "sa2", "l1a1,l1a2", "l1a3"])
67+
self.assertEqual(4, len(args))
68+
self.assertListEqual(["l0a0", "l0a1", "l0a2"], args[0])
69+
self.assertEqual("sa0", args[1])
70+
self.assertListEqual(["l1a0", "l1a1", "l1a2", "l1a3"], args[2])
71+
self.assertEqual("sa2", args[3])
72+
6573
def test_group_params(self):
6674
self.assertDictEqual({}, group_params([]))
6775
self.assertDictEqual({"k1": "v1", "k2": "v2"}, group_params([("k1", "v1"), ("k2", "v2")]))
@@ -113,7 +121,9 @@ def test_merge_responses(self):
113121

114122
def test_normalize_list_params(self):
115123
""" should normalize a list of parameters """
116-
self.assertEqual(["f1", "f2", "f3"], normalize_list_params(["f1,f2", "f3"]))
124+
self.assertListEqual(["f1"], normalize_list_params("f1"))
125+
self.assertListEqual(["f1", "f2", "f3"], normalize_list_params(["f1,f2", "f3"]))
126+
self.assertListEqual(["f1", "f2", "f3"], normalize_list_params("f1,f2,f3"))
117127

118128
def test_chunker(self):
119129
animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']
@@ -131,3 +141,21 @@ def _only_args_test_func(arg1, arg2):
131141

132142
def _args_kwargs_test_func(arg1, arg2=None):
133143
return arg1, arg2
144+
145+
146+
def _list_args_test_func(fist_list_arg, non_list_arg, list_arg, non_list_arg2):
147+
"""
148+
Function for testing list args.
149+
150+
:param fist_list_arg: first list argument
151+
:type fist_list_arg: list
152+
:param non_list_arg: some non-list argument
153+
:type non_list_arg: str
154+
:param list_arg: some list argument
155+
:type list_arg: list
156+
:param non_list_arg2: another non-list argument
157+
:type non_list_arg2: str
158+
:return: tuple of arguments
159+
:rtype: tuple
160+
"""
161+
return fist_list_arg, non_list_arg, list_arg, non_list_arg2

0 commit comments

Comments
 (0)