From 65db73f1804de4fff2712e25566f25e2b8cf9ae1 Mon Sep 17 00:00:00 2001 From: Alessandra Romero <24320222+alexgromero@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:13:33 -0500 Subject: [PATCH] Add client exceptions to boto3 docs (#4343) --- boto3/docs/service.py | 2 + tests/unit/docs/__init__.py | 6 +++ tests/unit/docs/test_client.py | 86 +++++++++++++++++++++++++++++++++ tests/unit/docs/test_service.py | 8 +++ 4 files changed, 102 insertions(+) diff --git a/boto3/docs/service.py b/boto3/docs/service.py index 39ed89b871..0076a3420b 100644 --- a/boto3/docs/service.py +++ b/boto3/docs/service.py @@ -42,6 +42,7 @@ def __init__(self, service_name, session, root_docs_path): self.sections = [ 'title', 'client', + 'client-exceptions', 'paginators', 'waiters', 'resources', @@ -65,6 +66,7 @@ def document_service(self): self.title(doc_structure.get_section('title')) self.client_api(doc_structure.get_section('client')) + self.client_exceptions(doc_structure.get_section('client-exceptions')) self.paginator_api(doc_structure.get_section('paginators')) self.waiter_api(doc_structure.get_section('waiters')) if self._service_resource: diff --git a/tests/unit/docs/__init__.py b/tests/unit/docs/__init__.py index a50934c8be..4d04a2f70b 100644 --- a/tests/unit/docs/__init__.py +++ b/tests/unit/docs/__init__.py @@ -344,6 +344,12 @@ def add_shape_to_params( required_list.append(param_name) params_shape['required'] = required_list + def add_shape_to_errors(self, shape_name): + operation = self.json_model['operations']['SampleOperation'] + errors = operation.get('errors', []) + errors.append({'shape': shape_name}) + operation['errors'] = errors + def assert_contains_lines_in_order(self, lines, contents=None): if contents is None: contents = self.doc_structure.flush_structure().decode('utf-8') diff --git a/tests/unit/docs/test_client.py b/tests/unit/docs/test_client.py index 11a42fa3cf..1facc5c52f 100644 --- a/tests/unit/docs/test_client.py +++ b/tests/unit/docs/test_client.py @@ -10,6 +10,8 @@ # 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 botocore.docs.client import ClientExceptionsDocumenter + from boto3.docs.client import Boto3ClientDocumenter from tests.unit.docs import BaseDocsTest @@ -17,6 +19,16 @@ class TestBoto3ClientDocumenter(BaseDocsTest): def setUp(self): super().setUp() + exception_shape = { + 'SomeException': { + 'exception': True, + 'type': 'structure', + 'members': {'Message': {'shape': 'String'}}, + } + } + self.add_shape(exception_shape) + self.add_shape_to_errors('SomeException') + self.setup_client_and_resource() self.client_documenter = Boto3ClientDocumenter( self.client, self.root_services_path ) @@ -96,8 +108,82 @@ def test_document_client(self): ' - *(dict) --*', ' - **Foo** *(string) --*', ' - **Bar** *(string) --*', + '**Exceptions**', + '* :py:class:`MyService.Client.exceptions.SomeException`', ], self.get_nested_service_contents( 'myservice', 'client', 'sample_operation' ), ) + + +class TestClientExceptionsDocumenter(BaseDocsTest): + def setup_documenter(self): + self.setup_client_and_resource() + self.exceptions_documenter = ClientExceptionsDocumenter( + self.client, self.root_services_path + ) + + def test_no_modeled_exceptions(self): + self.setup_documenter() + self.exceptions_documenter.document_exceptions(self.doc_structure) + self.assert_contains_lines_in_order( + [ + '=================', + 'Client Exceptions', + '=================', + 'Client exceptions are available', + 'This client has no modeled exception classes.', + ] + ) + + def test_modeled_exceptions(self): + exception_shape = { + 'SomeException': { + 'exception': True, + 'type': 'structure', + 'members': {'Message': {'shape': 'String'}}, + } + } + self.add_shape(exception_shape) + self.setup_documenter() + self.exceptions_documenter.document_exceptions(self.doc_structure) + self.assert_contains_lines_in_order( + [ + '=================', + 'Client Exceptions', + '=================', + 'Client exceptions are available', + 'The available client exceptions are:', + '.. toctree::', + ':maxdepth: 1', + ':titlesonly:', + ' myservice/client/exceptions/SomeException', + ] + ) + self.assert_contains_lines_in_order( + [ + '.. py:class:: MyService.Client.exceptions.SomeException', + '**Example**', + '::', + 'except client.exceptions.SomeException as e:', + '.. py:attribute:: response', + '**Syntax**', + '{', + "'Message': 'string',", + "'Error': {", + "'Code': 'string',", + "'Message': 'string'", + '}', + '}', + '**Structure**', + '- *(dict) --*', + '- **Message** *(string) --* ', + '- **Error** *(dict) --* ', + '- **Code** *(string) --* ', + '- **Message** *(string) --* ', + ], + self.get_nested_service_contents( + 'myservice', 'client/exceptions', 'SomeException' + ), + ) diff --git a/tests/unit/docs/test_service.py b/tests/unit/docs/test_service.py index a6e8ef19f8..5ceae3713c 100644 --- a/tests/unit/docs/test_service.py +++ b/tests/unit/docs/test_service.py @@ -34,6 +34,14 @@ def test_document_service(self): '.. py:class:: MyService.Client', 'These are the available methods:', ' myservice/client/sample_operation', + '=================', + 'Client Exceptions', + '=================', + 'Client exceptions are available on a client instance ', + 'via the ``exceptions`` property. For more detailed instructions ', + 'and examples on the exact usage of client exceptions, see the ', + 'error handling ', + 'This client has no modeled exception classes.', '==========', 'Paginators', '==========',