Skip to content

Commit 66be536

Browse files
committed
Fix argparse error for BooleanArgument with % in documentation
Python 3.14+ argparse treats % characters in help strings as format specifiers. When service model documentation contains % (e.g., IAM's UpdateAccountPasswordPolicy RequireSymbols parameter), argparse raises: ValueError: unsupported format character '^' (0x5e) at index 129 This was already fixed for CLIArgument in PR aws#9790 but BooleanArgument was missed. This commit adds the same .replace('%', '%%') escaping to BooleanArgument.add_to_parser() and adds a test to verify all service operations with % in documentation work correctly. Fixes compatibility with Python 3.14+
1 parent ff104c9 commit 66be536

File tree

2 files changed

+26
-1
lines changed

2 files changed

+26
-1
lines changed

awscli/arguments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ def add_to_arg_table(self, argument_table):
593593
def add_to_parser(self, parser):
594594
parser.add_argument(
595595
self.cli_name,
596-
help=self.documentation,
596+
help=self.documentation.replace('%', '%%'),
597597
action=self._action,
598598
default=self._default,
599599
dest=self._destination,

tests/unit/test_argprocess.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
from awscli.arguments import ListArgument, BooleanArgument
3333
from awscli.arguments import create_argument_model_from_schema
3434

35+
from awscli.clidriver import ServiceOperation, CLIOperationCaller
36+
from awscli.argparser import ArgTableArgParser
37+
3538

3639
# These tests use real service types so that we can
3740
# verify the real shapes of services.
@@ -894,6 +897,28 @@ def test_json_value_decode_error(self):
894897
with self.assertRaises(ParamError):
895898
unpack_cli_arg(self.p, value)
896899

900+
class TestPercentInDocumentation(BaseArgProcessTest):
901+
def test_percent_characters_escaped_in_argument_help(self):
902+
operation_caller = CLIOperationCaller(self.session)
903+
for service_name in self.session.get_available_services():
904+
service_model = self.session.get_service_model(service_name)
905+
for operation_name in service_model.operation_names:
906+
operation_model = service_model.operation_model(operation_name)
907+
if not operation_model.input_shape:
908+
continue
909+
has_percent = any(
910+
'%' in (member.documentation or '')
911+
for member in operation_model.input_shape.members.values()
912+
)
913+
if has_percent:
914+
service_op = ServiceOperation(
915+
name=xform_name(operation_name, '-'),
916+
parent_name=service_name,
917+
operation_caller=operation_caller,
918+
operation_model=operation_model,
919+
session=self.session,
920+
)
921+
ArgTableArgParser(service_op.arg_table)
897922

898923
if __name__ == '__main__':
899924
unittest.main()

0 commit comments

Comments
 (0)