Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions cloudinary_cli/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
from functools import reduce
from hashlib import md5
from inspect import signature, getfullargspec
from typing import get_type_hints
from multiprocessing import pool

import click
import cloudinary
from jinja2 import Environment, FileSystemLoader
from docstring_parser import parse
from cloudinary_cli.defaults import logger, TEMPLATE_FOLDER
from cloudinary.utils import build_array

not_callable = ('is_appengine_sandbox', 'call_tags_api', 'call_context_api', 'call_cacheable_api', 'call_api',
'call_metadata_api', 'call_json_api', 'only', 'transformation_string', 'account_config',
Expand Down Expand Up @@ -127,9 +129,11 @@ def parse_args_kwargs(func, params=None, kwargs=None):
num_req = num_args - num_defaults
num_provided_args = len(params)
num_overall_provided = num_provided_args + len([p for p in kwargs.keys() if p in spec.args[num_provided_args:]])

if num_overall_provided < num_req:
func_sig = signature(func)
raise Exception(f"Function '{func.__name__}{func_sig}' requires {num_req} positional arguments")

# consume required args
args = [parse_option_value(p) for p in params[:num_req]]

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

params_specs = parse(func.__doc__).params

if len(args) > num_req:
# Here we comsumed more args than the function can get,
# let's see if we have a list arg and pass everything as list.
# Otherwise, let's pass everything as is and hope for the best :)
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)
if last_positional_list_param:
pos = get_index_by_name(spec.args, last_positional_list_param.arg_name)
args[pos] = [args[pos]] + args[num_args:]
args = args[:num_args]

for s in params_specs:
if s.type_name and s.type_name.startswith('list'):
pos = get_index_by_name(spec.args, s.arg_name)
args[pos] = normalize_list_params(args[pos])

return args, kwargs

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

def remove_string_prefix(string, prefix):
return string[string.startswith(prefix) and len(prefix):]
Expand Down Expand Up @@ -297,17 +320,20 @@ def normalize_list_params(params):
"""
Normalizes parameters that could be provided as strings separated by ','.

>>> normalize_list_params(["f1,f2", "f3"])
["f1", "f2", "f3"]
>>> normalize_list_params(['f1,f2', 'f3'])
['f1', 'f2', 'f3']

>>> normalize_list_params('f1,f2,f3')
['f1', 'f2', 'f3']

:param params: Params to normalize.
:type params: list
:type params: list[string] or string

:return: A list of normalized params.
:rtype list
"""
normalized_params = []
for f in list(params):
for f in build_array(params):
if "," in f:
normalized_params += f.split(",")
else:
Expand Down
32 changes: 30 additions & 2 deletions test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_parse_option_value(self):
self.assertDictEqual({"foo": "bar"}, parse_option_value('{"foo":"bar"}'))
self.assertDictEqual({"an": "object", "or": "dict"}, parse_option_value('{"an":"object","or":"dict"}'))
self.assertListEqual(
["this", "will", "be", "read", "as","a", "list"],
["this", "will", "be", "read", "as", "a", "list"],
parse_option_value('["this","will","be","read","as","a","list"]')
)
self.assertListEqual(
Expand Down Expand Up @@ -62,6 +62,14 @@ def test_parse_args_kwargs(self):
self.assertEqual(0, len(args))
self.assertDictEqual({"arg1": "a1", "arg2": "a2"}, kwargs)

# should consume list values separated by spaces and commas
args, kwargs = parse_args_kwargs(_list_args_test_func, ["l0a0,l0a1,l0a2", "sa0", "l1a0", "sa2", "l1a1,l1a2", "l1a3"])
self.assertEqual(4, len(args))
self.assertListEqual(["l0a0", "l0a1", "l0a2"], args[0])
self.assertEqual("sa0", args[1])
self.assertListEqual(["l1a0", "l1a1", "l1a2", "l1a3"], args[2])
self.assertEqual("sa2", args[3])

def test_group_params(self):
self.assertDictEqual({}, group_params([]))
self.assertDictEqual({"k1": "v1", "k2": "v2"}, group_params([("k1", "v1"), ("k2", "v2")]))
Expand Down Expand Up @@ -113,7 +121,9 @@ def test_merge_responses(self):

def test_normalize_list_params(self):
""" should normalize a list of parameters """
self.assertEqual(["f1", "f2", "f3"], normalize_list_params(["f1,f2", "f3"]))
self.assertListEqual(["f1"], normalize_list_params("f1"))
self.assertListEqual(["f1", "f2", "f3"], normalize_list_params(["f1,f2", "f3"]))
self.assertListEqual(["f1", "f2", "f3"], normalize_list_params("f1,f2,f3"))

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

def _args_kwargs_test_func(arg1, arg2=None):
return arg1, arg2


def _list_args_test_func(fist_list_arg, non_list_arg, list_arg, non_list_arg2):
"""
Function for testing list args.

:param fist_list_arg: first list argument
:type fist_list_arg: list
:param non_list_arg: some non-list argument
:type non_list_arg: str
:param list_arg: some list argument
:type list_arg: list
:param non_list_arg2: another non-list argument
:type non_list_arg2: str
:return: tuple of arguments
:rtype: tuple
"""
return fist_list_arg, non_list_arg, list_arg, non_list_arg2
Loading