diff --git a/awscli/botocore/model.py b/awscli/botocore/model.py index e3e6af82ca58..b48d1b4f9c3d 100644 --- a/awscli/botocore/model.py +++ b/awscli/botocore/model.py @@ -89,6 +89,7 @@ class Shape: 'contextParam', 'clientContextParams', 'requiresLength', + 'pattern', ] MAP_TYPE = OrderedDict diff --git a/awscli/clidocs.py b/awscli/clidocs.py index 5387e29df4e4..0b80757050ce 100644 --- a/awscli/clidocs.py +++ b/awscli/clidocs.py @@ -210,6 +210,7 @@ def doc_option(self, arg_name, help_command, **kwargs): self._add_tagged_union_note(argument.argument_model, doc) if hasattr(argument, 'argument_model'): self._document_enums(argument.argument_model, doc) + self._document_constraints(argument.argument_model, doc) self._document_nested_structure(argument.argument_model, doc) doc.style.dedent() doc.style.new_paragraph() @@ -289,6 +290,8 @@ def _do_doc_member(self, doc, member_name, member_shape, stack): doc.include_doc_string(docs) if is_tagged_union_type(member_shape): self._add_tagged_union_note(member_shape, doc) + self._document_enums(member_shape, doc) + self._document_constraints(member_shape, doc) doc.style.new_paragraph() member_type_name = member_shape.type_name if member_type_name == 'structure': @@ -327,6 +330,23 @@ def _add_tagged_union_note(self, shape, doc): doc.writeln(msg) doc.style.end_note() + def _document_constraints(self, model, doc): + """Documents parameter value constraints""" + if not hasattr(model, 'metadata'): + return + constraints = ['min', 'max', 'pattern'] + if not any( + [constraint in model.metadata for constraint in constraints] + ): + return + doc.style.new_paragraph() + doc.write('Constraints:') + doc.style.start_ul() + for constraint in constraints: + if (val := model.metadata.get(constraint)) is not None: + doc.style.li(f'{constraint}: ``{val}``') + doc.style.end_ul() + class ProviderDocumentEventHandler(CLIDocumentEventHandler): def doc_breadcrumbs(self, help_command, event_name, **kwargs): @@ -609,10 +629,6 @@ def doc_option_example(self, arg_name, help_command, event_name, **kwargs): member, include_enum_values=False ) doc.write('%s %s ...' % (example_type, example_type)) - if isinstance(member, StringShape) and member.enum: - # If we have enum values, we can tell the user - # exactly what valid values they can provide. - self._write_valid_enums(doc, member.enum) doc.style.end_codeblock() doc.style.new_paragraph() elif cli_argument.cli_type_name not in SCALAR_TYPES: @@ -623,13 +639,6 @@ def doc_option_example(self, arg_name, help_command, event_name, **kwargs): doc.style.end_codeblock() doc.style.new_paragraph() - def _write_valid_enums(self, doc, enum_values): - doc.style.new_paragraph() - doc.write("Where valid values are:\n") - for value in enum_values: - doc.write(" %s\n" % value) - doc.write("\n") - def doc_output(self, help_command, event_name, **kwargs): doc = help_command.doc doc.style.h2('Output') diff --git a/tests/functional/docs/test_help_output.py b/tests/functional/docs/test_help_output.py index fff29801d0d8..33d9a15024c7 100644 --- a/tests/functional/docs/test_help_output.py +++ b/tests/functional/docs/test_help_output.py @@ -367,35 +367,6 @@ def test_description_from_rst_file(self): self.assert_contains('aws_access_key_id') -class TestEnumDocsArentDuplicated(BaseAWSHelpOutputTest): - def test_enum_docs_arent_duplicated(self): - # Test for: https://github.com/aws/aws-cli/issues/609 - # What's happening is if you have a list param that has - # an enum, we document it as: - # a|b|c|d a|b|c|d - # Except we show all of the possible enum params twice. - # Each enum param should only occur once. The ideal documentation - # should be: - # - # string1 string2 - # - # Where each value is one of: - # value1 - # value2 - self.driver.main(['cloudformation', 'list-stacks', 'help']) - # "CREATE_IN_PROGRESS" is a enum value, and should only - # appear once in the help output. - contents = self.renderer.rendered_contents - self.assertTrue( - contents.count("CREATE_IN_PROGRESS") == 1, - ( - "Enum param was only suppose to be appear once in " - "rendered doc output, appeared: %s" - % contents.count("CREATE_IN_PROGRESS") - ), - ) - - class TestParametersCanBeHidden(BaseAWSHelpOutputTest): def mark_as_undocumented(self, argument_table, **kwargs): argument_table['starting-sequence-number']._UNDOCUMENTED = True diff --git a/tests/unit/test_clidocs.py b/tests/unit/test_clidocs.py index 956e7c7aec92..7f8adbe48ae9 100644 --- a/tests/unit/test_clidocs.py +++ b/tests/unit/test_clidocs.py @@ -499,6 +499,22 @@ def test_tagged_union_comes_after_docstring_output(self): rendered = help_command.doc.getvalue().decode('utf-8') self.assertRegex(rendered, r'FooBar[\s\S]*Tagged Union') + def test_documents_constraints(self): + shape = {'type': 'string', 'min': 0, 'max': 10, 'pattern': '.*'} + shape = StringShape('ConstrainedArg', shape) + arg = CustomArgument('ConstrainedArg', argument_model=shape) + help_command = self.create_help_command() + help_command.arg_table = {'ConstrainedArg': arg} + operation_handler = OperationDocumentEventHandler(help_command) + operation_handler.doc_option( + arg_name='ConstrainedArg', help_command=help_command + ) + rendered = help_command.doc.getvalue().decode('utf-8') + self.assertIn('Constraints', rendered) + self.assertIn('min: ``0``', rendered) + self.assertIn('max: ``10``', rendered) + self.assertIn('pattern: ``.*``', rendered) + class TestTopicDocumentEventHandlerBase(unittest.TestCase): def setUp(self):