From 3297e04b7cbfd2e8e0d351c2992be3608c6d5a3f Mon Sep 17 00:00:00 2001 From: Mitch Garnaat Date: Tue, 3 Dec 2013 03:24:16 -0800 Subject: [PATCH 1/2] Fix an assumption in argprocess.py that all map types will have an enum of possible keys. Also change the import of json to come from the botocore.compat module and also set the object_pairs_hook to use OrderedDict so unit tests can be compared properly. Dependent on https://github.com/boto/botocore/pull/185. Fixes #407. --- awscli/argprocess.py | 24 +++++---- tests/unit/sns/__init__.py | 12 +++++ .../sns/test_create_platform_application.py | 53 +++++++++++++++++++ 3 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 tests/unit/sns/__init__.py create mode 100644 tests/unit/sns/test_create_platform_application.py diff --git a/awscli/argprocess.py b/awscli/argprocess.py index 87b942b3132a..dc3dc5e9030c 100644 --- a/awscli/argprocess.py +++ b/awscli/argprocess.py @@ -12,10 +12,11 @@ # language governing permissions and limitations under the License. """Module for processing CLI args.""" import os -import json import logging import six +from botocore.compat import OrderedDict, json + from awscli import utils from awscli import SCALAR_TYPES, COMPLEX_TYPES @@ -243,9 +244,10 @@ def _key_value_parse(self, param, value): # that is, csv key value pairs, where the key and values # are separated by '='. All of this should be whitespace # insensitive. - parsed = {} + parsed = OrderedDict() parts = self._split_on_commas(value) valid_names = self._create_name_to_params(param) + LOG.debug('valid_names=%s', valid_names) for part in parts: try: key, value = part.split('=', 1) @@ -253,18 +255,20 @@ def _key_value_parse(self, param, value): raise ParamSyntaxError(part) key = key.strip() value = value.strip() - if key not in valid_names: + if valid_names and key not in valid_names: raise ParamUnknownKeyError(param, key, valid_names) - sub_param = valid_names[key] - if sub_param is not None: - value = unpack_scalar_cli_arg(sub_param, value) + if valid_names: + sub_param = valid_names[key] + if sub_param is not None: + value = unpack_scalar_cli_arg(sub_param, value) parsed[key] = value + LOG.debug('parsed=%s', parsed) return parsed def _create_name_to_params(self, param): if param.type == 'structure': return dict([(p.name, p) for p in param.members]) - elif param.type == 'map': + elif param.type == 'map' and hasattr(param.keys, 'enum'): return dict([(v, None) for v in param.keys.enum]) def _docs_list_scalar_list_parse(self, param): @@ -351,7 +355,7 @@ def unpack_cli_arg(parameter, value): def unpack_complex_cli_arg(parameter, value): if parameter.type == 'structure' or parameter.type == 'map': if value.lstrip()[0] == '{': - d = json.loads(value) + d = json.loads(value, object_pairs_hook=OrderedDict) else: msg = 'The value for parameter "%s" must be JSON or path to file.' % ( parameter.cli_name) @@ -360,11 +364,11 @@ def unpack_complex_cli_arg(parameter, value): elif parameter.type == 'list': if isinstance(value, six.string_types): if value.lstrip()[0] == '[': - return json.loads(value) + return json.loads(value, object_pairs_hook=OrderedDict) elif isinstance(value, list) and len(value) == 1: single_value = value[0].strip() if single_value and single_value[0] == '[': - return json.loads(value[0]) + return json.loads(value[0], object_pairs_hook=OrderedDict) return [unpack_cli_arg(parameter.members, v) for v in value] diff --git a/tests/unit/sns/__init__.py b/tests/unit/sns/__init__.py new file mode 100644 index 000000000000..a4018327e46f --- /dev/null +++ b/tests/unit/sns/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. diff --git a/tests/unit/sns/test_create_platform_application.py b/tests/unit/sns/test_create_platform_application.py new file mode 100644 index 000000000000..28b23ed8b72a --- /dev/null +++ b/tests/unit/sns/test_create_platform_application.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +from tests.unit import BaseAWSCommandParamsTest + + +class TestCreatePlatformApplication(BaseAWSCommandParamsTest): + + prefix = 'sns create-platform-application' + + def test_gcm_shorthand(self): + cmdline = self.prefix + cmdline += ' --name gcmpushapp' + cmdline += ' --platform GCM' + cmdline += ' --attributes ' + cmdline += 'PlatformCredential=foo,' + cmdline += 'PlatformPrincipal=bar' + result = {'Name': 'gcmpushapp', + 'Platform': 'GCM', + 'Attributes.entry.1.key': 'PlatformCredential', + 'Attributes.entry.1.value': 'foo', + 'Attributes.entry.2.key': 'PlatformPrincipal', + 'Attributes.entry.2.value': 'bar'} + self.assert_params_for_cmd(cmdline, result) + + def test_gcm_json(self): + cmdline = self.prefix + cmdline += ' --name gcmpushapp' + cmdline += ' --platform GCM' + cmdline += ' --attributes ' + cmdline += ('{"PlatformCredential":"AIzaSyClE2lcV2zEKTLYYo645zfk2jhQPFeyxDo",' + '"PlatformPrincipal":"There+is+no+principal+for+GCM"}') + result = {'Name': 'gcmpushapp', + 'Platform': 'GCM', + 'Attributes.entry.1.key': 'PlatformCredential', + 'Attributes.entry.1.value': 'AIzaSyClE2lcV2zEKTLYYo645zfk2jhQPFeyxDo', + 'Attributes.entry.2.key': 'PlatformPrincipal', + 'Attributes.entry.2.value': 'There+is+no+principal+for+GCM'} + self.assert_params_for_cmd(cmdline, result) + + +if __name__ == "__main__": + unittest.main() From 60b3820cea330e9f9ca237d60938f2fee4baa0f7 Mon Sep 17 00:00:00 2001 From: Mitch Garnaat Date: Tue, 3 Dec 2013 13:38:41 -0800 Subject: [PATCH 2/2] Remove extraneous log statements. --- awscli/argprocess.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awscli/argprocess.py b/awscli/argprocess.py index dc3dc5e9030c..26bef03c1509 100644 --- a/awscli/argprocess.py +++ b/awscli/argprocess.py @@ -247,7 +247,6 @@ def _key_value_parse(self, param, value): parsed = OrderedDict() parts = self._split_on_commas(value) valid_names = self._create_name_to_params(param) - LOG.debug('valid_names=%s', valid_names) for part in parts: try: key, value = part.split('=', 1) @@ -262,7 +261,6 @@ def _key_value_parse(self, param, value): if sub_param is not None: value = unpack_scalar_cli_arg(sub_param, value) parsed[key] = value - LOG.debug('parsed=%s', parsed) return parsed def _create_name_to_params(self, param):