Skip to content

Commit c65b763

Browse files
authored
Merge pull request #3910 from StackStorm/cherry-pick-autodict
Cherry pick changes from #3909 into v2.5
2 parents 4ca1f99 + 3c28eca commit c65b763

File tree

3 files changed

+126
-7
lines changed

3 files changed

+126
-7
lines changed

st2client/st2client/commands/action.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,15 @@ def _add_common_options(self):
297297
'key-value pair; dot notation for nested JSON is '
298298
'supported.'))
299299

300+
# Flag to opt-in to functionality introduced in PR #3670. More robust parsing
301+
# of complex datatypes is planned for 2.6, so this flag will be deprecated soon
302+
detail_arg_grp.add_argument('--auto-dict', action='store_true', dest='auto_dict',
303+
default=False, help='Automatically convert list items to '
304+
'dictionaries when colons are detected. '
305+
'(NOTE - this parameter and its functionality will be '
306+
'deprecated in the next release in favor of a more '
307+
'robust conversion method)')
308+
300309
return root_arg_grp
301310

302311
def _print_execution_details(self, execution, args, **kwargs):
@@ -523,7 +532,7 @@ def transform_object(value):
523532
result[key] = value
524533
return result
525534

526-
def transform_array(value, action_params=None):
535+
def transform_array(value, action_params=None, auto_dict=False):
527536
action_params = action_params or {}
528537

529538
# Sometimes an array parameter only has a single element:
@@ -555,13 +564,14 @@ def transform_array(value, action_params=None):
555564

556565
# When each values in this array represent dict type, this converts
557566
# the 'result' to the dict type value.
558-
if all([isinstance(x, str) and ':' in x for x in result]):
567+
if all([isinstance(x, str) and ':' in x for x in result]) and auto_dict:
559568
result_dict = {}
560569
for (k, v) in [x.split(':') for x in result]:
561570
# To parse values using the 'transformer' according to the type which is
562571
# specified in the action metadata, calling 'normalize' method recursively.
563572
if 'properties' in action_params and k in action_params['properties']:
564-
result_dict[k] = normalize(k, v, action_params['properties'])
573+
result_dict[k] = normalize(k, v, action_params['properties'],
574+
auto_dict=auto_dict)
565575
else:
566576
result_dict[k] = v
567577
return [result_dict]
@@ -591,7 +601,7 @@ def get_param_type(key, action_params=None):
591601

592602
return None
593603

594-
def normalize(name, value, action_params=None):
604+
def normalize(name, value, action_params=None, auto_dict=False):
595605
""" The desired type is contained in the action meta-data, so we can look that up
596606
and call the desired "caster" function listed in the "transformer" dict
597607
"""
@@ -609,7 +619,7 @@ def normalize(name, value, action_params=None):
609619
# also leverage that to cast each array item to the correct type.
610620
param_type = get_param_type(name, action_params)
611621
if param_type == 'array' and name in action_params:
612-
return transformer[param_type](value, action_params[name])
622+
return transformer[param_type](value, action_params[name], auto_dict=auto_dict)
613623
elif param_type:
614624
return transformer[param_type](value)
615625

@@ -649,9 +659,9 @@ def normalize(name, value, action_params=None):
649659
else:
650660
# This permits multiple declarations of argument only in the array type.
651661
if get_param_type(k) == 'array' and k in result:
652-
result[k] += normalize(k, v)
662+
result[k] += normalize(k, v, auto_dict=args.auto_dict)
653663
else:
654-
result[k] = normalize(k, v)
664+
result[k] = normalize(k, v, auto_dict=args.auto_dict)
655665

656666
except Exception as e:
657667
# TODO: Move transformers in a separate module and handle

st2client/tests/unit/test_action.py

+54
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,60 @@ def test_param_array_object_conversion(self):
377377
}
378378
httpclient.HTTPClient.post.assert_called_with('/executions', expected)
379379

380+
@mock.patch.object(
381+
models.ResourceManager, 'get_by_ref_or_id',
382+
mock.MagicMock(side_effect=get_by_ref))
383+
@mock.patch.object(
384+
models.ResourceManager, 'get_by_name',
385+
mock.MagicMock(side_effect=get_by_name))
386+
@mock.patch.object(
387+
httpclient.HTTPClient, 'post',
388+
mock.MagicMock(return_value=base.FakeResponse(json.dumps(LIVE_ACTION), 200, 'OK')))
389+
def test_param_dict_conversion_flag(self):
390+
"""Ensure that the automatic conversion to dict based on colons only occurs with the flag
391+
"""
392+
393+
self.shell.run(
394+
[
395+
'run',
396+
'mockety.mock2',
397+
'list=key1:value1,key2:value2',
398+
'--auto-dict'
399+
]
400+
)
401+
expected = {
402+
'action': 'mockety.mock2',
403+
'user': None,
404+
'parameters': {
405+
'list': [
406+
{
407+
'key1': 'value1',
408+
'key2': 'value2'
409+
}
410+
]
411+
}
412+
}
413+
httpclient.HTTPClient.post.assert_called_with('/executions', expected)
414+
415+
self.shell.run(
416+
[
417+
'run',
418+
'mockety.mock2',
419+
'list=key1:value1,key2:value2'
420+
]
421+
)
422+
expected = {
423+
'action': 'mockety.mock2',
424+
'user': None,
425+
'parameters': {
426+
'list': [
427+
'key1:value1',
428+
'key2:value2'
429+
]
430+
}
431+
}
432+
httpclient.HTTPClient.post.assert_called_with('/executions', expected)
433+
380434
@mock.patch.object(
381435
models.ResourceManager, 'get_by_ref_or_id',
382436
mock.MagicMock(side_effect=get_by_ref))

st2client/tests/unit/test_command_actionrun.py

+55
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,47 @@ def test_get_params_types(self):
5757
self.assertEqual(runner.runner_parameters, orig_runner_params, 'Runner params modified.')
5858
self.assertEqual(action.parameters, orig_action_params, 'Action params modified.')
5959

60+
def test_opt_in_dict_auto_convert(self):
61+
"""Test ability for user to opt-in to dict convert functionality
62+
"""
63+
64+
runner = RunnerType()
65+
runner.runner_parameters = {}
66+
67+
action = Action()
68+
action.ref = 'test.action'
69+
action.parameters = {
70+
'param_array': {'type': 'array'},
71+
}
72+
73+
subparser = mock.Mock()
74+
command = ActionRunCommand(action, self, subparser, name='test')
75+
76+
mockarg = mock.Mock()
77+
mockarg.inherit_env = False
78+
mockarg.parameters = [
79+
'param_array=foo:bar,foo2:bar2',
80+
]
81+
82+
mockarg.auto_dict = False
83+
param = command._get_action_parameters_from_args(action=action, runner=runner, args=mockarg)
84+
self.assertEqual(param['param_array'], ['foo:bar', 'foo2:bar2'])
85+
86+
mockarg.auto_dict = True
87+
param = command._get_action_parameters_from_args(action=action, runner=runner, args=mockarg)
88+
self.assertEqual(param['param_array'], [{'foo': 'bar', 'foo2': 'bar2'}])
89+
90+
# set auto_dict back to default
91+
mockarg.auto_dict = False
92+
6093
def test_get_params_from_args(self):
94+
"""test_get_params_from_args
95+
96+
This tests the details of the auto-dict conversion, assuming it's enabled. Please
97+
see test_opt_in_dict_auto_convert for a test of detecting whether or not this
98+
functionality is enabled.
99+
"""
100+
61101
runner = RunnerType()
62102
runner.runner_parameters = {}
63103

@@ -84,6 +124,7 @@ def test_get_params_from_args(self):
84124

85125
mockarg = mock.Mock()
86126
mockarg.inherit_env = False
127+
mockarg.auto_dict = True
87128
mockarg.parameters = [
88129
'param_string=hoge',
89130
'param_integer=123',
@@ -116,7 +157,17 @@ def test_get_params_from_args(self):
116157
self.assertTrue(isinstance(param['qux'], dict))
117158
self.assertTrue(isinstance(param['quux'], bool))
118159

160+
# set auto_dict back to default
161+
mockarg.auto_dict = False
162+
119163
def test_get_params_from_args_with_multiple_declarations(self):
164+
"""test_get_params_from_args_with_multiple_declarations
165+
166+
This tests the details of the auto-dict conversion, assuming it's enabled. Please
167+
see test_opt_in_dict_auto_convert for a test of detecting whether or not this
168+
functionality is enabled.
169+
"""
170+
120171
runner = RunnerType()
121172
runner.runner_parameters = {}
122173

@@ -133,6 +184,7 @@ def test_get_params_from_args_with_multiple_declarations(self):
133184

134185
mockarg = mock.Mock()
135186
mockarg.inherit_env = False
187+
mockarg.auto_dict = True
136188
mockarg.parameters = [
137189
'param_string=hoge', # This value will be overwritten with the next declaration.
138190
'param_string=fuga',
@@ -151,3 +203,6 @@ def test_get_params_from_args_with_multiple_declarations(self):
151203
{'foo': '1', 'bar': '2'},
152204
{'hoge': 'A', 'fuga': 'B'}
153205
])
206+
207+
# set auto_dict back to default
208+
mockarg.auto_dict = False

0 commit comments

Comments
 (0)