Skip to content
Draft
3 changes: 2 additions & 1 deletion botocore/docs/bcdoc/restdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
'client-api': 4,
'paginator-api': 3,
'waiter-api': 3,
'sdk-examples-api': 3,
}


Expand Down Expand Up @@ -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
Expand Down
97 changes: 97 additions & 0 deletions botocore/docs/codeexamples.py
Original file line number Diff line number Diff line change
@@ -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()


20 changes: 20 additions & 0 deletions botocore/docs/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions botocore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
133 changes: 133 additions & 0 deletions tests/unit/docs/test_sdkcodeexamples.py
Original file line number Diff line number Diff line change
@@ -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 <https://docs.aws.amazon.com/code-library/latest/ug/python_3_myservice_code_examples.html>`_')
self.assert_contains_line('Api')
self.assert_contains_line('* `Action1 <https://docs.aws.amazon.com/code-library/latest/ug/service1_example_service1_Action1_section.html>`_')
self.assert_contains_line('Basics')
self.assert_contains_line(
'* `Learn the Basics <https://docs.aws.amazon.com/code-library/latest/ug/service_example_service1_Basics_section.html>`_')
self.assert_contains_line('Scenarios')
self.assert_contains_line(
'* `Scenario1 <https://docs.aws.amazon.com/code-library/latest/ug/service_example_service1_Scenario1_section.html>`_')

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')