diff --git a/.changes/1.26.81.json b/.changes/1.26.81.json new file mode 100644 index 0000000000..bd6ff53303 --- /dev/null +++ b/.changes/1.26.81.json @@ -0,0 +1,42 @@ +[ + { + "category": "Documentation", + "description": "Splits service documentation into multiple sub-pages for better organization and faster loading time.", + "type": "enhancement" + }, + { + "category": "Documentation", + "description": "[``botocore``] Splits service documentation into multiple sub-pages for better organization and faster loading time.", + "type": "enhancement" + }, + { + "category": "``comprehend``", + "description": "[``botocore``] Amazon Comprehend now supports flywheels to help you train and manage new model versions for custom models.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] This release allows IMDS support to be set to v2-only on an existing AMI, so that all future instances launched from that AMI will use IMDSv2 by default.", + "type": "api-change" + }, + { + "category": "``kms``", + "description": "[``botocore``] AWS KMS is deprecating the RSAES_PKCS1_V1_5 wrapping algorithm option in the GetParametersForImport API that is used in the AWS KMS Import Key Material feature. AWS KMS will end support for this wrapping algorithm by October 1, 2023.", + "type": "api-change" + }, + { + "category": "``lightsail``", + "description": "[``botocore``] This release adds Lightsail for Research feature support, such as GUI session access, cost estimates, stop instance on idle, and disk auto mount.", + "type": "api-change" + }, + { + "category": "``managedblockchain``", + "description": "[``botocore``] This release adds support for tagging to the accessor resource in Amazon Managed Blockchain", + "type": "api-change" + }, + { + "category": "``omics``", + "description": "[``botocore``] Minor model changes to accomodate batch imports feature", + "type": "api-change" + } +] \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8364fa34fa..12584d8c12 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,19 @@ CHANGELOG ========= +1.26.81 +======= + +* enhancement:Documentation: Splits service documentation into multiple sub-pages for better organization and faster loading time. +* enhancement:Documentation: [``botocore``] Splits service documentation into multiple sub-pages for better organization and faster loading time. +* api-change:``comprehend``: [``botocore``] Amazon Comprehend now supports flywheels to help you train and manage new model versions for custom models. +* api-change:``ec2``: [``botocore``] This release allows IMDS support to be set to v2-only on an existing AMI, so that all future instances launched from that AMI will use IMDSv2 by default. +* api-change:``kms``: [``botocore``] AWS KMS is deprecating the RSAES_PKCS1_V1_5 wrapping algorithm option in the GetParametersForImport API that is used in the AWS KMS Import Key Material feature. AWS KMS will end support for this wrapping algorithm by October 1, 2023. +* api-change:``lightsail``: [``botocore``] This release adds Lightsail for Research feature support, such as GUI session access, cost estimates, stop instance on idle, and disk auto mount. +* api-change:``managedblockchain``: [``botocore``] This release adds support for tagging to the accessor resource in Amazon Managed Blockchain +* api-change:``omics``: [``botocore``] Minor model changes to accomodate batch imports feature + + 1.26.80 ======= diff --git a/boto3/__init__.py b/boto3/__init__.py index fded60e2d8..d31feb2c35 100644 --- a/boto3/__init__.py +++ b/boto3/__init__.py @@ -17,7 +17,7 @@ from boto3.session import Session __author__ = 'Amazon Web Services' -__version__ = '1.26.80' +__version__ = '1.26.81' # The default Boto3 session; autoloaded when needed. diff --git a/boto3/docs/__init__.py b/boto3/docs/__init__.py index d1cd8b52f4..261747b42f 100644 --- a/boto3/docs/__init__.py +++ b/boto3/docs/__init__.py @@ -32,7 +32,9 @@ def generate_docs(root_dir, session): os.makedirs(services_doc_path) for service_name in session.get_available_services(): - docs = ServiceDocumenter(service_name, session).document_service() + docs = ServiceDocumenter( + service_name, session, services_doc_path + ).document_service() service_doc_path = os.path.join( services_doc_path, service_name + '.rst' ) diff --git a/boto3/docs/action.py b/boto3/docs/action.py index 907f428663..acfcebf1b3 100644 --- a/boto3/docs/action.py +++ b/boto3/docs/action.py @@ -10,7 +10,10 @@ # 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 os + from botocore import xform_name +from botocore.docs.bcdoc.restdoc import DocumentStructure from botocore.docs.method import ( document_custom_method, document_model_driven_method, @@ -18,7 +21,7 @@ from botocore.model import OperationModel from botocore.utils import get_service_module_name -from boto3.docs.base import BaseDocumenter +from boto3.docs.base import NestedDocumenter from boto3.docs.method import document_model_driven_resource_method from boto3.docs.utils import ( add_resource_type_overview, @@ -27,7 +30,7 @@ ) -class ActionDocumenter(BaseDocumenter): +class ActionDocumenter(NestedDocumenter): def document_actions(self, section): modeled_actions_list = self._resource_model.actions modeled_actions = {} @@ -49,7 +52,10 @@ def document_actions(self, section): ) for action_name in sorted(resource_actions): - action_section = section.add_new_section(action_name) + # Create a new DocumentStructure for each action and add contents. + action_doc = DocumentStructure(action_name, target='html') + action_doc.add_title_section(action_name) + action_section = action_doc.add_new_section(action_name) if action_name in ['load', 'reload'] and self._resource_model.load: document_load_reload_action( section=action_section, @@ -71,6 +77,14 @@ def document_actions(self, section): document_custom_method( action_section, action_name, resource_actions[action_name] ) + # Write actions in individual/nested files. + # Path: /reference/services///.rst + actions_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + action_doc.write_to_file(actions_dir_path, action_name) def document_action( diff --git a/boto3/docs/base.py b/boto3/docs/base.py index a12d94543f..7945d0757d 100644 --- a/boto3/docs/base.py +++ b/boto3/docs/base.py @@ -30,3 +30,12 @@ def __init__(self, resource): @property def class_name(self): return f'{self._service_docs_name}.{self._resource_name}' + + +class NestedDocumenter(BaseDocumenter): + def __init__(self, resource, root_docs_path): + super().__init__(resource) + self._root_docs_path = root_docs_path + self._resource_sub_path = self._resource_name.lower() + if self._resource_name == self._service_name: + self._resource_sub_path = 'service-resource' diff --git a/boto3/docs/collection.py b/boto3/docs/collection.py index a1f9ca7faa..9de772cc6f 100644 --- a/boto3/docs/collection.py +++ b/boto3/docs/collection.py @@ -10,11 +10,14 @@ # 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 os + from botocore import xform_name +from botocore.docs.bcdoc.restdoc import DocumentStructure from botocore.docs.method import get_instance_public_methods from botocore.docs.utils import DocumentedShape -from boto3.docs.base import BaseDocumenter +from boto3.docs.base import NestedDocumenter from boto3.docs.method import document_model_driven_resource_method from boto3.docs.utils import ( add_resource_type_overview, @@ -22,7 +25,7 @@ ) -class CollectionDocumenter(BaseDocumenter): +class CollectionDocumenter(NestedDocumenter): def document_collections(self, section): collections = self._resource.meta.resource_model.collections collections_list = [] @@ -37,10 +40,24 @@ def document_collections(self, section): ) self.member_map['collections'] = collections_list for collection in collections: - collection_section = section.add_new_section(collection.name) collections_list.append(collection.name) + # Create a new DocumentStructure for each collection and add contents. + collection_doc = DocumentStructure(collection.name, target='html') + collection_doc.add_title_section(collection.name) + collection_section = collection_doc.add_new_section( + collection.name + ) self._document_collection(collection_section, collection) + # Write collections in individual/nested files. + # Path: /reference/services///.rst + collections_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + collection_doc.write_to_file(collections_dir_path, collection.name) + def _document_collection(self, section, collection): methods = get_instance_public_methods( getattr(self._resource, collection.name) diff --git a/boto3/docs/resource.py b/boto3/docs/resource.py index 7d5516e9c1..b4eef3c544 100644 --- a/boto3/docs/resource.py +++ b/boto3/docs/resource.py @@ -10,7 +10,10 @@ # 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 os + from botocore import xform_name +from botocore.docs.bcdoc.restdoc import DocumentStructure from botocore.docs.utils import get_official_service_name from boto3.docs.action import ActionDocumenter @@ -32,14 +35,18 @@ class ResourceDocumenter(BaseDocumenter): - def __init__(self, resource, botocore_session): + def __init__(self, resource, botocore_session, root_docs_path): super().__init__(resource) self._botocore_session = botocore_session + self._root_docs_path = root_docs_path + self._resource_sub_path = self._resource_name.lower() + if self._resource_name == self._service_name: + self._resource_sub_path = 'service-resource' def document_resource(self, section): self._add_title(section) + self._add_resource_note(section) self._add_intro(section) - overview_section = section.add_new_section('member-overview') self._add_identifiers(section) self._add_attributes(section) self._add_references(section) @@ -47,7 +54,6 @@ def document_resource(self, section): self._add_sub_resources(section) self._add_collections(section) self._add_waiters(section) - self._add_overview_of_members(overview_section) def _add_title(self, section): section.style.h2(self._resource_name) @@ -60,23 +66,27 @@ def _add_intro(self, section): # Write out the class signature. class_args = get_identifier_args_for_signature(identifier_names) - section.style.start_sphinx_py_class( + start_class = section.add_new_section('start_class') + start_class.style.start_sphinx_py_class( class_name=f'{self.class_name}({class_args})' ) # Add as short description about the resource - description_section = section.add_new_section('description') + description_section = start_class.add_new_section('description') self._add_description(description_section) # Add an example of how to instantiate the resource - example_section = section.add_new_section('example') + example_section = start_class.add_new_section('example') self._add_example(example_section, identifier_names) # Add the description for the parameters to instantiate the # resource. - param_section = section.add_new_section('params') + param_section = start_class.add_new_section('params') self._add_params_description(param_section, identifier_names) + end_class = section.add_new_section('end_class') + end_class.style.end_sphinx_py_class() + def _add_description(self, section): official_service_name = get_official_service_name(self._service_model) section.write( @@ -118,23 +128,15 @@ def _add_params_description(self, section, identifier_names): section.write(f':param {identifier_name}: {description}') section.style.new_line() - def _add_overview_of_members(self, section): - for resource_member_type in self.member_map: - section.style.new_line() - section.write( - f'These are the resource\'s available {resource_member_type}:' - ) - section.style.new_line() - for member in self.member_map[resource_member_type]: - if resource_member_type in ( - 'attributes', - 'collections', - 'identifiers', - 'references', - ): - section.style.li(f':py:attr:`{member}`') - else: - section.style.li(f':py:meth:`{member}()`') + def _add_overview_of_member_type(self, section, resource_member_type): + section.style.new_line() + section.write( + f'These are the resource\'s available {resource_member_type}:' + ) + section.style.new_line() + section.style.toctree() + for member in self.member_map[resource_member_type]: + section.style.tocitem(f'{member}') def _add_identifiers(self, section): identifiers = self._resource.meta.resource_model.identifiers @@ -152,13 +154,29 @@ def _add_identifiers(self, section): intro_link='identifiers_attributes_intro', ) for identifier in identifiers: - identifier_section = section.add_new_section(identifier.name) member_list.append(identifier.name) + # Create a new DocumentStructure for each identifier and add contents. + identifier_doc = DocumentStructure(identifier.name, target='html') + identifier_doc.add_title_section(identifier.name) + identifier_section = identifier_doc.add_new_section( + identifier.name + ) document_identifier( section=identifier_section, resource_name=self._resource_name, identifier_model=identifier, ) + # Write identifiers in individual/nested files. + # Path: /reference/services///.rst + identifiers_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + identifier_doc.write_to_file(identifiers_dir_path, identifier.name) + + if identifiers: + self._add_overview_of_member_type(section, 'identifiers') def _add_attributes(self, section): service_model = self._resource.meta.client.meta.service_model @@ -187,8 +205,11 @@ def _add_attributes(self, section): self.member_map['attributes'] = attribute_list for attr_name in sorted(attributes): _, attr_shape = attributes[attr_name] - attribute_section = section.add_new_section(attr_name) attribute_list.append(attr_name) + # Create a new DocumentStructure for each attribute and add contents. + attribute_doc = DocumentStructure(attr_name, target='html') + attribute_doc.add_title_section(attr_name) + attribute_section = attribute_doc.add_new_section(attr_name) document_attribute( section=attribute_section, service_name=self._service_name, @@ -197,6 +218,16 @@ def _add_attributes(self, section): event_emitter=self._resource.meta.client.meta.events, attr_model=attr_shape, ) + # Write attributes in individual/nested files. + # Path: /reference/services///.rst + attributes_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + attribute_doc.write_to_file(attributes_dir_path, attr_name) + if attributes: + self._add_overview_of_member_type(section, 'attributes') def _add_references(self, section): section = section.add_new_section('references') @@ -213,36 +244,57 @@ def _add_references(self, section): intro_link='references_intro', ) self.member_map['references'] = reference_list + self._add_overview_of_member_type(section, 'references') for reference in references: - reference_section = section.add_new_section(reference.name) reference_list.append(reference.name) + # Create a new DocumentStructure for each reference and add contents. + reference_doc = DocumentStructure(reference.name, target='html') + reference_doc.add_title_section(reference.name) + reference_section = reference_doc.add_new_section(reference.name) document_reference( section=reference_section, reference_model=reference ) + # Write references in individual/nested files. + # Path: /reference/services///.rst + references_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + reference_doc.write_to_file(references_dir_path, reference.name) + if references: + self._add_overview_of_member_type(section, 'references') def _add_actions(self, section): section = section.add_new_section('actions') actions = self._resource.meta.resource_model.actions if actions: - documenter = ActionDocumenter(self._resource) + documenter = ActionDocumenter(self._resource, self._root_docs_path) documenter.member_map = self.member_map documenter.document_actions(section) + self._add_overview_of_member_type(section, 'actions') def _add_sub_resources(self, section): section = section.add_new_section('sub-resources') sub_resources = self._resource.meta.resource_model.subresources if sub_resources: - documenter = SubResourceDocumenter(self._resource) + documenter = SubResourceDocumenter( + self._resource, self._root_docs_path + ) documenter.member_map = self.member_map documenter.document_sub_resources(section) + self._add_overview_of_member_type(section, 'sub-resources') def _add_collections(self, section): section = section.add_new_section('collections') collections = self._resource.meta.resource_model.collections if collections: - documenter = CollectionDocumenter(self._resource) + documenter = CollectionDocumenter( + self._resource, self._root_docs_path + ) documenter.member_map = self.member_map documenter.document_collections(section) + self._add_overview_of_member_type(section, 'collections') def _add_waiters(self, section): section = section.add_new_section('waiters') @@ -252,10 +304,21 @@ def _add_waiters(self, section): self._service_name ) documenter = WaiterResourceDocumenter( - self._resource, service_waiter_model + self._resource, service_waiter_model, self._root_docs_path ) documenter.member_map = self.member_map documenter.document_resource_waiters(section) + self._add_overview_of_member_type(section, 'waiters') + + def _add_resource_note(self, section): + section = section.add_new_section('feature-freeze') + section.style.start_note() + section.write( + "Before using anything on this page, please refer to the resources " + ":doc:`user guide <../../../../guide/resources>` for the most recent " + "guidance on using resources." + ) + section.style.end_note() class ServiceResourceDocumenter(ResourceDocumenter): diff --git a/boto3/docs/service.py b/boto3/docs/service.py index 56a4e6360d..6d3b31d5d6 100644 --- a/boto3/docs/service.py +++ b/boto3/docs/service.py @@ -26,12 +26,13 @@ class ServiceDocumenter(BaseServiceDocumenter): # The path used to find examples EXAMPLE_PATH = os.path.join(os.path.dirname(boto3.__file__), 'examples') - def __init__(self, service_name, session): + def __init__(self, service_name, session, root_docs_path): super().__init__( service_name=service_name, # I know that this is an internal attribute, but the botocore session # is needed to load the paginator and waiter models. session=session._session, + root_docs_path=root_docs_path, ) self._boto3_session = session self._client = self._boto3_session.client(service_name) @@ -44,10 +45,14 @@ def __init__(self, service_name, session): 'client', 'paginators', 'waiters', - 'service-resource', 'resources', 'examples', ] + self._root_docs_path = root_docs_path + self._USER_GUIDE_LINK = ( + 'https://boto3.amazonaws.com/' + 'v1/documentation/api/latest/guide/resources.html' + ) def document_service(self): """Documents an entire service. @@ -64,10 +69,7 @@ def document_service(self): self.paginator_api(doc_structure.get_section('paginators')) self.waiter_api(doc_structure.get_section('waiters')) if self._service_resource: - self._document_service_resource( - doc_structure.get_section('service-resource') - ) - self._document_resources(doc_structure.get_section('resources')) + self.resource_section(doc_structure.get_section('resources')) self._document_examples(doc_structure.get_section('examples')) return doc_structure.flush_structure() @@ -78,12 +80,52 @@ def client_api(self, section): except DataNotFoundError: pass - Boto3ClientDocumenter(self._client, examples).document_client(section) + Boto3ClientDocumenter( + self._client, self._root_docs_path, examples + ).document_client(section) + + def resource_section(self, section): + section.style.h2('Resources') + section.style.new_line() + section.write( + 'Resources are available in boto3 via the ' + '``resource`` method. For more detailed instructions ' + 'and examples on the usage of resources, see the ' + 'resources ' + ) + section.style.external_link( + title='user guide', + link=self._USER_GUIDE_LINK, + ) + section.write('.') + section.style.new_line() + section.style.new_line() + section.write('The available resources are:') + section.style.new_line() + section.style.toctree() + self._document_service_resource(section) + self._document_resources(section) def _document_service_resource(self, section): + # Create a new DocumentStructure for each Service Resource and add contents. + service_resource_doc = DocumentStructure( + 'service-resource', target='html' + ) ServiceResourceDocumenter( - self._service_resource, self._session - ).document_resource(section) + self._service_resource, self._session, self._root_docs_path + ).document_resource(service_resource_doc) + # Write collections in individual/nested files. + # Path: /reference/services///.rst + resource_name = self._service_resource.meta.resource_model.name + if resource_name == self._service_name: + resource_name = 'service-resource' + service_resource_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{resource_name.lower()}', + ) + service_resource_doc.write_to_file(service_resource_dir_path, 'index') + section.style.tocitem(f'{self._service_name}/{resource_name}/index') def _document_resources(self, section): temp_identifier_value = 'foo' @@ -113,8 +155,24 @@ def _document_resources(self, section): for _ in identifiers: args.append(temp_identifier_value) resource = resource_cls(*args, client=self._client) - ResourceDocumenter(resource, self._session).document_resource( - section.add_new_section(resource.meta.resource_model.name) + # Create a new DocumentStructure for each Resource and add contents. + resource_name = resource.meta.resource_model.name.lower() + resource_doc = DocumentStructure(resource_name, target='html') + ResourceDocumenter( + resource, self._session, self._root_docs_path + ).document_resource( + resource_doc.add_new_section(resource.meta.resource_model.name) + ) + # Write collections in individual/nested files. + # Path: /reference/services///.rst + service_resource_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{resource_name}', + ) + resource_doc.write_to_file(service_resource_dir_path, 'index') + section.style.tocitem( + f'{self._service_name}/{resource_name}/index' ) def _get_example_file(self): diff --git a/boto3/docs/subresource.py b/boto3/docs/subresource.py index e22311c814..b0c4ff4a69 100644 --- a/boto3/docs/subresource.py +++ b/boto3/docs/subresource.py @@ -10,10 +10,13 @@ # 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 os + from botocore import xform_name +from botocore.docs.bcdoc.restdoc import DocumentStructure from botocore.utils import get_service_module_name -from boto3.docs.base import BaseDocumenter +from boto3.docs.base import NestedDocumenter from boto3.docs.utils import ( add_resource_type_overview, get_identifier_args_for_signature, @@ -22,7 +25,7 @@ ) -class SubResourceDocumenter(BaseDocumenter): +class SubResourceDocumenter(NestedDocumenter): def document_sub_resources(self, section): add_resource_type_overview( section=section, @@ -41,8 +44,15 @@ def document_sub_resources(self, section): sub_resources_list = [] self.member_map['sub-resources'] = sub_resources_list for sub_resource in sub_resources: - sub_resource_section = section.add_new_section(sub_resource.name) sub_resources_list.append(sub_resource.name) + # Create a new DocumentStructure for each sub_resource and add contents. + sub_resource_doc = DocumentStructure( + sub_resource.name, target='html' + ) + sub_resource_doc.add_title_section(sub_resource.name) + sub_resource_section = sub_resource_doc.add_new_section( + sub_resource.name + ) document_sub_resource( section=sub_resource_section, resource_name=self._resource_name, @@ -50,6 +60,17 @@ def document_sub_resources(self, section): service_model=self._service_model, ) + # Write sub_resources in individual/nested files. + # Path: /reference/services///.rst + sub_resources_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + sub_resource_doc.write_to_file( + sub_resources_dir_path, sub_resource.name + ) + def document_sub_resource( section, diff --git a/boto3/docs/utils.py b/boto3/docs/utils.py index 13a098dd61..0830af5052 100644 --- a/boto3/docs/utils.py +++ b/boto3/docs/utils.py @@ -71,10 +71,7 @@ def add_resource_type_overview( section, resource_type, description, intro_link=None ): section.style.new_line() - section.write('.. rst-class:: admonition-title') - section.style.new_line() - section.style.new_line() - section.write(resource_type) + section.style.h3(resource_type) section.style.new_line() section.style.new_line() section.write(description) diff --git a/boto3/docs/waiter.py b/boto3/docs/waiter.py index 255d850bc3..559447cab5 100644 --- a/boto3/docs/waiter.py +++ b/boto3/docs/waiter.py @@ -10,20 +10,23 @@ # 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 os + from botocore import xform_name +from botocore.docs.bcdoc.restdoc import DocumentStructure from botocore.docs.method import document_model_driven_method from botocore.utils import get_service_module_name -from boto3.docs.base import BaseDocumenter +from boto3.docs.base import NestedDocumenter from boto3.docs.utils import ( add_resource_type_overview, get_resource_ignore_params, ) -class WaiterResourceDocumenter(BaseDocumenter): - def __init__(self, resource, service_waiter_model): - super().__init__(resource) +class WaiterResourceDocumenter(NestedDocumenter): + def __init__(self, resource, service_waiter_model, root_docs_path): + super().__init__(resource, root_docs_path) self._service_waiter_model = service_waiter_model def document_resource_waiters(self, section): @@ -40,8 +43,11 @@ def document_resource_waiters(self, section): waiter_list = [] self.member_map['waiters'] = waiter_list for waiter in waiters: - waiter_section = section.add_new_section(waiter.name) waiter_list.append(waiter.name) + # Create a new DocumentStructure for each waiter and add contents. + waiter_doc = DocumentStructure(waiter.name, target='html') + waiter_doc.add_title_section(waiter.name) + waiter_section = waiter_doc.add_new_section(waiter.name) document_resource_waiter( section=waiter_section, resource_name=self._resource_name, @@ -50,6 +56,14 @@ def document_resource_waiters(self, section): resource_waiter_model=waiter, service_waiter_model=self._service_waiter_model, ) + # Write waiters in individual/nested files. + # Path: /reference/services///.rst + waiters_dir_path = os.path.join( + self._root_docs_path, + f'{self._service_name}', + f'{self._resource_sub_path}', + ) + waiter_doc.write_to_file(waiters_dir_path, waiter.name) def document_resource_waiter( diff --git a/docs/source/_static/js/custom.js b/docs/source/_static/js/custom.js new file mode 100644 index 0000000000..3a5b1be1fe --- /dev/null +++ b/docs/source/_static/js/custom.js @@ -0,0 +1,73 @@ +/* +Copyright 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License 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. +*/ +const nonResourceSubHeadings = [ + 'client', + 'waiters', + 'paginators', + 'resources', + 'examples' +]; +// Checks if an html doc name matches a service class name. +function isValidServiceName(docName, serviceClassName) { + const newDocName = docName.replaceAll('-', '').toLowerCase(); + return newDocName === serviceClassName; +} +// Checks if all elements of the split fragment are valid. +// Fragment items should only contain alphanumerics, hyphens, & underscores. +// A fragment should also only be redirected if it contain 3-5 items. +function isValidFragment(splitFragment) { + const regex = /^[a-z0-9-_]+$/i; + for (index in splitFragment) { + if (!regex.test(splitFragment[index])) { + return false; + } + } + return splitFragment.length >= 1 && splitFragment.length < 5; +} +// Checks if a name is a possible resource name. +function isValidResource(name, serviceDocName) { + return name.replaceAll('-', '') !== serviceDocName && !nonResourceSubHeadings.includes(name); +} +// Reroutes previously existing links to the new path. +// Old: /reference/services/s3.html#S3.Client.delete_bucket +// New: /reference/services/s3/client/delete_bucket.html +// This must be done client side since the fragment (#S3.Client.delete_bucket) is never +// passed to the server. +(function () { + const currentPath = window.location.pathname.split('/'); + const fragment = window.location.hash.substring(1); + const splitFragment = fragment.split('.').map(part => part.replace(/serviceresource/i, 'service-resource')); + // Only redirect when viewing a top-level service page. + if (isValidFragment(splitFragment) && currentPath[currentPath.length - 2] === 'services') { + const serviceDocName = currentPath[currentPath.length - 1].replace('.html', ''); + if (splitFragment.length > 1) { + splitFragment[0] = splitFragment[0].toLowerCase(); + splitFragment[1] = splitFragment[1].toLowerCase(); + } + let newPath; + if (splitFragment.length >= 3 && isValidServiceName(serviceDocName, splitFragment[0])) { + splitFragment[0] = serviceDocName; + newPath = `${ splitFragment.slice(0, 3).join('/') }.html#${ splitFragment.length > 3 ? fragment : '' }`; + } else if (splitFragment.length == 2 && isValidResource(splitFragment[1].toLowerCase(), serviceDocName)) { + newPath = `${ splitFragment.join('/') }/index.html#${ fragment }`; + } else if (splitFragment.length == 1 && isValidResource(splitFragment[0], serviceDocName)) { + newPath = `${ serviceDocName }/${ splitFragment.join('/') }/index.html`; + } else { + return; + } + window.location.assign(newPath); + } +}()); diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html index f7503fe37d..2241114db3 100644 --- a/docs/source/_templates/layout.html +++ b/docs/source/_templates/layout.html @@ -7,6 +7,7 @@ {%- block footer %}