diff --git a/botocore/docs/bcdoc/restdoc.py b/botocore/docs/bcdoc/restdoc.py index 3868126cc9..c24f1d698a 100644 --- a/botocore/docs/bcdoc/restdoc.py +++ b/botocore/docs/bcdoc/restdoc.py @@ -44,6 +44,7 @@ 'client-api': 4, 'paginator-api': 3, 'waiter-api': 3, + 'sdk-examples-api': 3, } @@ -203,7 +204,7 @@ def add_new_section(self, name, context=None): name=name, target=self.target, context=context ) section.path = self.path + [name] - # Indent the section apporpriately as well + # Indent the section appropriately as well section.style.indentation = self.style.indentation section.translation_map = self.translation_map section.hrefs = self.hrefs diff --git a/botocore/docs/codeexamples.py b/botocore/docs/codeexamples.py new file mode 100644 index 0000000000..2ceb40f5ce --- /dev/null +++ b/botocore/docs/codeexamples.py @@ -0,0 +1,97 @@ +# Copyright 2015 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. + +import requests +from botocore.compat import json + +CODE_EXAMPLE_LINK = 'https://docs.aws.amazon.com/code-library/latest/ug/python_3_' +CODE_EXAMPLE_CATALOG_VERSION = '2025.0.0-alpha' +# Change this to the main repo and branch when PR is merged. +CODE_EXAMPLE_CATALOG_BASE = 'https://raw.githubusercontent.com/rlhagerm/aws-doc-sdk-examples/refs/tags/' + + +class CodeExamplesDocumenter: + def __init__(self, client, root_docs_path): + self._client = client + self._client_class_name = self._client.__class__.__name__ + self._service_name = self._client.meta.service_model.service_name + self._root_docs_path = root_docs_path + + def load_code_examples_catalog(self, service_id): + """Loads the code library example catalog listing for the service. + + :param service_id: The code examples service id. + :return: The list of examples. + """ + git_service_url = f"{CODE_EXAMPLE_CATALOG_BASE}/{CODE_EXAMPLE_CATALOG_VERSION}/python/example_code/{service_id}/examples_catalog.json" + + response = requests.get(git_service_url) + if response.status_code == 200: + example_json = response.text + if example_json: + examples = json.loads(example_json) + if len(examples) > 0: + return examples['examples'] + + return [] + + def document_code_examples(self, section, examples, service_id): + """Documents the code library code examples for a service. + + :param section: The section to write to. + :param examples: The list of examples. + :param service_id: The code examples service id. + """ + if examples: + section.style.h2('AWS SDK Code Examples') + self._add_overview(section) + + # Group the examples by category. Do not show a category if there are no examples. + example_categories = {} + for i in range(len(examples)): + example_categories.setdefault(examples[i]['category'], []).append(examples[i]) + + # Write a link item for each example in the category. + for category in sorted(example_categories): + section.style.new_line() + section.style.h3(category) + for i in range(len(example_categories[category])): + section.style.start_li() + title_text = example_categories[category][i]['title'] + if not title_text: + title_text = example_categories[category][i]['id'].rsplit('_', 1)[-1] + service_page = example_categories[category][i]['doc_filenames']['service_pages'].get( + service_id,list(example_categories[category][i]['doc_filenames']['service_pages'].values())[0]) + section.style.external_link( + title=title_text, + link=service_page, + ) + section.style.end_li() + + def _add_overview(self, section): + """Write the overview section for code examples. + + :param section: The section to write to. + """ + section.style.new_line() + section.write( + 'Explore code examples in the ' + ) + section.style.external_link( + title='AWS SDK Code Examples Code Library', + link=CODE_EXAMPLE_LINK + self._service_name + '_code_examples.html', + ) + section.write('.') + section.style.new_line() + + diff --git a/botocore/docs/service.py b/botocore/docs/service.py index d20a889dc9..6764c81226 100644 --- a/botocore/docs/service.py +++ b/botocore/docs/service.py @@ -18,7 +18,11 @@ ) from botocore.docs.paginator import PaginatorDocumenter from botocore.docs.waiter import WaiterDocumenter +from botocore.docs.codeexamples import CodeExamplesDocumenter from botocore.exceptions import DataNotFoundError +from botocore.utils import ( + SDK_CODE_EXAMPLE_ALIASES +) class ServiceDocumenter: @@ -42,6 +46,7 @@ def __init__(self, service_name, session, root_docs_path): 'paginator-api', 'waiter-api', 'client-context-params', + 'sdk-examples-api', ] def document_service(self): @@ -61,6 +66,7 @@ def document_service(self): 'client-context-params' ) self.client_context_params(context_params_section) + self.sdk_examples_api(doc_structure.get_section('sdk-examples-api')) return doc_structure.flush_structure() def title(self, section): @@ -111,6 +117,20 @@ def waiter_api(self, section): ) waiter_documenter.document_waiters(section) + def sdk_examples_api(self, section): + library_examples_list = None + try: + code_example_service_name = SDK_CODE_EXAMPLE_ALIASES.get(self._service_name, self._service_name) + examples_documenter = CodeExamplesDocumenter( + self._client, self._root_docs_path + ) + library_examples_list = examples_documenter.load_code_examples_catalog(code_example_service_name) + # Only document the examples if there are any in the list. + if library_examples_list: + examples_documenter.document_code_examples(section, library_examples_list, code_example_service_name) + except DataNotFoundError: + pass + def get_examples(self, service_name, api_version=None): loader = self._session.get_component('data_loader') examples = loader.load_service_model( diff --git a/botocore/utils.py b/botocore/utils.py index 30a513ea90..2d69c24d11 100644 --- a/botocore/utils.py +++ b/botocore/utils.py @@ -178,6 +178,13 @@ "tagging": "resource-groups-tagging-api", } +# The SDK code examples library service names mapped to the service id, if they do not match. +SDK_CODE_EXAMPLE_ALIASES = { + "apigateway": "api-gateway", + "autoscaling": "auto-scaling", + "cognito-identity": "cognito", + "kinesis-analytics-v2": "kinesisanalyticsv2", +} # This pattern can be used to detect if a header is a flexible checksum header CHECKSUM_HEADER_PATTERN = re.compile( diff --git a/tests/unit/docs/test_sdkcodeexamples.py b/tests/unit/docs/test_sdkcodeexamples.py new file mode 100644 index 0000000000..45ad2e7bed --- /dev/null +++ b/tests/unit/docs/test_sdkcodeexamples.py @@ -0,0 +1,133 @@ +# Copyright 2015 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 botocore.docs.codeexamples import CodeExamplesDocumenter +from botocore.compat import json +from tests.unit.docs import BaseDocsTest +from tests import mock + + +class TestCodeExamplesDocumenter(BaseDocsTest): + def setUp(self): + super().setUp() + self.add_shape_to_params('Biz', 'String') + self.extra_setup() + + def extra_setup(self): + self.setup_client() + self.examples_documenter = CodeExamplesDocumenter( + client=self.client, + root_docs_path=self.root_services_path, + ) + + def mock_response( + self, + status=200, + content="CONTENT", + json_data=None): + + mock_resp = mock.Mock() + # Aet status code and content. + mock_resp.status_code = status + mock_resp.content = content + # Add json data if provided. + if json_data: + mock_resp.text = json_data + return mock_resp + + def test_load_catalog(self): + mock_json = ''' + { + "examples": [ + { + "id": "service1_actions1" + } + ] + } + ''' + examples_result = json.loads(mock_json) + mock_resp = self.mock_response(status=200, json_data=mock_json) + self.request_patch = mock.patch('requests.get', return_value=mock_resp) + self.request_patch.start() + examples = self.examples_documenter.load_code_examples_catalog('service1') + self.request_patch.stop() + self.assertEqual(examples, examples_result['examples']) + + def test_document_codeexamples(self): + examples_json = ''' + { + "examples": [ + { + "id": "service1_actions1", + "file": "service1_metadata.yaml", + "languages": [], + "title": "Action1", + "category": "Api", + "doc_filenames": { + "service_pages": { + "service1": "https://docs.aws.amazon.com/code-library/latest/ug/service1_example_service1_Action1_section.html" + }, + "sdk_pages": [] + }, + "synopsis_list": [], + "source_key": null + }, + { + "id": "service1_basics", + "file": "service1_metadata.yaml", + "title": "Learn the Basics", + "category": "Basics", + "doc_filenames": { + "service_pages": { + "service1": "https://docs.aws.amazon.com/code-library/latest/ug/service_example_service1_Basics_section.html" + }, + "sdk_pages": [] + }, + "synopsis_list": [], + "source_key": null + }, + { + "id": "service1_Scenario1", + "file": "service1_metadata.yaml", + "title": "Scenario1", + "category": "Scenarios", + "doc_filenames": { + "service_pages": { + "service1": "https://docs.aws.amazon.com/code-library/latest/ug/service_example_service1_Scenario1_section.html" + }, + "sdk_pages": [] + }, + "synopsis_list": [], + "source_key": null + } + ] + } + ''' + examples = json.loads(examples_json) + self.examples_documenter.document_code_examples(self.doc_structure, examples['examples'], 'service1') + self.assert_contains_line('AWS SDK Code Examples') + self.assert_contains_line( + 'Explore code examples in the `AWS SDK Code Examples Code Library `_') + self.assert_contains_line('Api') + self.assert_contains_line('* `Action1 `_') + self.assert_contains_line('Basics') + self.assert_contains_line( + '* `Learn the Basics `_') + self.assert_contains_line('Scenarios') + self.assert_contains_line( + '* `Scenario1 `_') + + def test_no_empty_examples_section(self): + examples = [] + + self.examples_documenter.document_code_examples(self.doc_structure, examples, 'service1') + self.assert_not_contains_line('AWS SDK Code Examples')