Skip to content

Commit c42f9f5

Browse files
committed
Port from PR aws#10044 argparse percent escaping fix to CLI v2
1 parent 5c1a217 commit c42f9f5

2 files changed

Lines changed: 58 additions & 14 deletions

File tree

awscli/arguments.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ def add_to_parser(self, parser):
449449
cli_name = self.cli_name
450450
parser.add_argument(
451451
cli_name,
452-
help=self.documentation,
452+
help=self.documentation.replace('%', '%%'),
453453
type=self.cli_type,
454454
required=self.required,
455455
)
@@ -482,7 +482,7 @@ def _unpack_argument(self, value):
482482
service_name = self._operation_model.service_model.service_name
483483
operation_name = xform_name(self._operation_model.name, '-')
484484
override = self._emit_first_response(
485-
'process-cli-arg.%s.%s' % (service_name, operation_name),
485+
f'process-cli-arg.{service_name}.{operation_name}',
486486
param=self.argument_model,
487487
cli_argument=self,
488488
value=value,
@@ -548,7 +548,7 @@ def __init__(
548548
default=None,
549549
serialized_name=None,
550550
):
551-
super(BooleanArgument, self).__init__(
551+
super().__init__(
552552
name,
553553
argument_model,
554554
operation_model,
@@ -584,7 +584,7 @@ def add_to_arg_table(self, argument_table):
584584
# ourselves for the negative service. We then insert both into the
585585
# arg table.
586586
argument_table[self.name] = self
587-
negative_name = 'no-%s' % self.name
587+
negative_name = f'no-{self.name}'
588588
negative_version = self.__class__(
589589
negative_name,
590590
self.argument_model,
@@ -600,7 +600,7 @@ def add_to_arg_table(self, argument_table):
600600
def add_to_parser(self, parser):
601601
parser.add_argument(
602602
self.cli_name,
603-
help=self.documentation,
603+
help=self.documentation.replace('%', '%%'),
604604
action=self._action,
605605
default=self._default,
606606
dest=self._destination,

tests/unit/test_argprocess.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from botocore import model, xform_name
1616
from botocore.compat import OrderedDict
1717

18+
from awscli.argparser import ArgTableArgParser
1819
from awscli.argprocess import (
1920
ParamError,
2021
ParamShorthandDocGen,
@@ -69,7 +70,7 @@ def create_argument(self, argument_model, argument_name=None):
6970

7071
class TestURIParams(BaseArgProcessTest):
7172
def setUp(self):
72-
super(TestURIParams, self).setUp()
73+
super().setUp()
7374
self.uri_param = URIArgumentHandler(LOCAL_PREFIX_MAP.copy())
7475

7576
def test_uri_param(self):
@@ -80,7 +81,7 @@ def test_uri_param(self):
8081
)
8182
f.write(json_argument)
8283
f.flush()
83-
result = self.uri_param('event-name', p, 'file://%s' % f.name)
84+
result = self.uri_param('event-name', p, f'file://{f.name}')
8485
self.assertEqual(result, json_argument)
8586

8687

@@ -173,7 +174,7 @@ class TestParamShorthand(BaseArgProcessTest):
173174
maxDiff = None
174175

175176
def setUp(self):
176-
super(TestParamShorthand, self).setUp()
177+
super().setUp()
177178
self._shorthand = ParamShorthandParser()
178179

179180
def parse_shorthand(self, cli_argument, value, event_name=None):
@@ -490,7 +491,7 @@ def test_csv_syntax_errors(self):
490491

491492
class TestParamShorthandCustomArguments(BaseArgProcessTest):
492493
def setUp(self):
493-
super(TestParamShorthandCustomArguments, self).setUp()
494+
super().setUp()
494495
self.shorthand = ParamShorthandParser()
495496

496497
def test_list_structure_list_scalar_custom_arg(self):
@@ -555,7 +556,7 @@ class TestDocGen(BaseArgProcessTest):
555556
# flexible and allow the docs to slightly change without breaking these
556557
# tests.
557558
def setUp(self):
558-
super(TestDocGen, self).setUp()
559+
super().setUp()
559560
self.shorthand_documenter = ParamShorthandDocGen()
560561
self.service_name = 'foo'
561562
self.operation_name = 'bar'
@@ -728,7 +729,7 @@ def test_gen_structure_list_scalar_docs(self):
728729

729730
def test_can_gen_recursive_structure(self):
730731
argument = self.get_param_model('dynamodb.PutItem.Item')
731-
generated_example = self.get_generated_example_for(argument)
732+
self.get_generated_example_for(argument)
732733

733734
def test_can_document_nested_structs(self):
734735
argument = self.get_param_model('ec2.RunInstances.BlockDeviceMappings')
@@ -885,12 +886,14 @@ def test_structure_within_map(self):
885886
}
886887
)
887888
generated_example = self.get_generated_example_for(argument)
888-
self.assertEqual('A={KeyName1={B=string},KeyName2={B=string}}', generated_example)
889+
self.assertEqual(
890+
'A={KeyName1={B=string},KeyName2={B=string}}', generated_example
891+
)
889892

890893

891894
class TestUnpackJSONParams(BaseArgProcessTest):
892895
def setUp(self):
893-
super(TestUnpackJSONParams, self).setUp()
896+
super().setUp()
894897
self.simplify = ParamShorthandParser()
895898

896899
def test_json_with_spaces(self):
@@ -925,7 +928,7 @@ def test_json_with_spaces(self):
925928

926929
class TestJSONValueHeaderParams(BaseArgProcessTest):
927930
def setUp(self):
928-
super(TestJSONValueHeaderParams, self).setUp()
931+
super().setUp()
929932
self.p = self.get_param_model(
930933
'lex-runtime.PostContent.sessionAttributes'
931934
)
@@ -968,5 +971,46 @@ def test_json_value_decode_error(self):
968971
unpack_cli_arg(self.p, value)
969972

970973

974+
class TestArgumentPercentEscaping(BaseArgProcessTest):
975+
def _test_percent_escaping(self, arg_type, arg_class, doc_string):
976+
argument = self.create_argument(
977+
{
978+
'Test': {
979+
'type': arg_type,
980+
'documentation': doc_string,
981+
}
982+
}
983+
)
984+
arg = arg_class(
985+
'test-arg',
986+
argument.argument_model.members['Test'],
987+
mock.Mock(),
988+
mock.Mock(),
989+
is_required=False,
990+
)
991+
arg_table = {arg.name: arg}
992+
parser = ArgTableArgParser(arg_table)
993+
help_output = parser.format_help()
994+
self.assertIn(doc_string, help_output)
995+
996+
def test_cli_argument_escapes_percent(self):
997+
self._test_percent_escaping('string', CLIArgument, 'Symbols: % ^ & *')
998+
999+
def test_boolean_argument_escapes_percent(self):
1000+
self._test_percent_escaping(
1001+
'boolean', BooleanArgument, 'Symbols: % ^ & *'
1002+
)
1003+
1004+
def test_cli_argument_escapes_url_encoded_percent(self):
1005+
self._test_percent_escaping(
1006+
'string', CLIArgument, 'File: test%28file%29.png'
1007+
)
1008+
1009+
def test_boolean_argument_escapes_url_encoded_percent(self):
1010+
self._test_percent_escaping(
1011+
'boolean', BooleanArgument, 'File: test%28file%29.png'
1012+
)
1013+
1014+
9711015
if __name__ == '__main__':
9721016
unittest.main()

0 commit comments

Comments
 (0)