1# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"). You
4# may not use this file except in compliance with the License. A copy of
5# the License is located at
6#
7# http://aws.amazon.com/apache2.0/
8#
9# or in the "license" file accompanying this file. This file is
10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11# ANY KIND, either express or implied. See the License for the specific
12# language governing permissions and limitations under the License.
13import inspect
14
15from botocore.docs.utils import get_official_service_name
16from botocore.docs.method import document_custom_method
17from botocore.docs.method import document_model_driven_method
18from botocore.docs.method import get_instance_public_methods
19from botocore.docs.sharedexample import document_shared_examples
20from botocore.docs.example import ResponseExampleDocumenter
21from botocore.docs.params import ResponseParamsDocumenter
22from botocore.docs.utils import DocumentedShape
23from botocore.compat import OrderedDict
24
25
26class ClientDocumenter(object):
27    def __init__(self, client, shared_examples=None):
28        self._client = client
29        self._shared_examples = shared_examples
30        if self._shared_examples is None:
31            self._shared_examples = {}
32        self._service_name = self._client.meta.service_model.service_name
33
34    def document_client(self, section):
35        """Documents a client and its methods
36
37        :param section: The section to write to.
38        """
39        self._add_title(section)
40        self._add_class_signature(section)
41        client_methods = get_instance_public_methods(self._client)
42        self._add_client_intro(section, client_methods)
43        self._add_client_methods(section, client_methods)
44
45    def _add_title(self, section):
46        section.style.h2('Client')
47
48    def _add_client_intro(self, section, client_methods):
49        section = section.add_new_section('intro')
50        # Write out the top level description for the client.
51        official_service_name = get_official_service_name(
52            self._client.meta.service_model)
53        section.write(
54            'A low-level client representing %s' % official_service_name)
55
56        # Write out the client example instantiation.
57        self._add_client_creation_example(section)
58
59        # List out all of the possible client methods.
60        section.style.new_line()
61        section.write('These are the available methods:')
62        section.style.new_line()
63        class_name = self._client.__class__.__name__
64        for method_name in sorted(client_methods):
65            section.style.li(':py:meth:`~%s.Client.%s`' % (
66                class_name, method_name))
67
68    def _add_class_signature(self, section):
69        section.style.start_sphinx_py_class(
70            class_name='%s.Client' % self._client.__class__.__name__)
71
72    def _add_client_creation_example(self, section):
73        section.style.start_codeblock()
74        section.style.new_line()
75        section.write(
76            'client = session.create_client(\'{service}\')'.format(
77                service=self._service_name)
78        )
79        section.style.end_codeblock()
80
81    def _add_client_methods(self, section, client_methods):
82        section = section.add_new_section('methods')
83        for method_name in sorted(client_methods):
84            self._add_client_method(
85                section, method_name, client_methods[method_name])
86
87    def _add_client_method(self, section, method_name, method):
88        section = section.add_new_section(method_name)
89        if self._is_custom_method(method_name):
90            self._add_custom_method(section, method_name, method)
91        else:
92            self._add_model_driven_method(section, method_name)
93
94    def _is_custom_method(self, method_name):
95        return method_name not in self._client.meta.method_to_api_mapping
96
97    def _add_custom_method(self, section, method_name, method):
98        document_custom_method(section, method_name, method)
99
100    def _add_method_exceptions_list(self, section, operation_model):
101        error_section = section.add_new_section('exceptions')
102        error_section.style.new_line()
103        error_section.style.bold('Exceptions')
104        error_section.style.new_line()
105        client_name = self._client.__class__.__name__
106        for error in operation_model.error_shapes:
107            class_name = '%s.Client.exceptions.%s' % (client_name, error.name)
108            error_section.style.li(':py:class:`%s`' % class_name)
109
110    def _add_model_driven_method(self, section, method_name):
111        service_model = self._client.meta.service_model
112        operation_name = self._client.meta.method_to_api_mapping[method_name]
113        operation_model = service_model.operation_model(operation_name)
114
115        example_prefix = 'response = client.%s' % method_name
116        document_model_driven_method(
117            section, method_name, operation_model,
118            event_emitter=self._client.meta.events,
119            method_description=operation_model.documentation,
120            example_prefix=example_prefix,
121        )
122
123        # Add any modeled exceptions
124        if operation_model.error_shapes:
125            self._add_method_exceptions_list(section, operation_model)
126
127        # Add the shared examples
128        shared_examples = self._shared_examples.get(operation_name)
129        if shared_examples:
130            document_shared_examples(
131                section, operation_model, example_prefix, shared_examples)
132
133
134class ClientExceptionsDocumenter(object):
135    _USER_GUIDE_LINK = (
136        'https://boto3.amazonaws.com/'
137        'v1/documentation/api/latest/guide/error-handling.html'
138    )
139    _GENERIC_ERROR_SHAPE = DocumentedShape(
140        name='Error',
141        type_name='structure',
142        documentation=(
143            'Normalized access to common exception attributes.'
144        ),
145        members=OrderedDict([
146            ('Code', DocumentedShape(
147                name='Code',
148                type_name='string',
149                documentation=(
150                    'An identifier specifying the exception type.'
151                ),
152            )),
153            ('Message', DocumentedShape(
154                name='Message',
155                type_name='string',
156                documentation=(
157                    'A descriptive message explaining why the exception '
158                    'occured.'
159                ),
160            )),
161        ]),
162    )
163
164    def __init__(self, client):
165        self._client = client
166        self._service_name = self._client.meta.service_model.service_name
167
168    def document_exceptions(self, section):
169        self._add_title(section)
170        self._add_overview(section)
171        self._add_exceptions_list(section)
172        self._add_exception_classes(section)
173
174    def _add_title(self, section):
175        section.style.h2('Client Exceptions')
176
177    def _add_overview(self, section):
178        section.style.new_line()
179        section.write(
180            'Client exceptions are available on a client instance '
181            'via the ``exceptions`` property. For more detailed instructions '
182            'and examples on the exact usage of client exceptions, see the '
183            'error handling '
184        )
185        section.style.external_link(
186            title='user guide',
187            link=self._USER_GUIDE_LINK,
188        )
189        section.write('.')
190        section.style.new_line()
191
192    def _exception_class_name(self, shape):
193        cls_name = self._client.__class__.__name__
194        return '%s.Client.exceptions.%s' % (cls_name, shape.name)
195
196    def _add_exceptions_list(self, section):
197        error_shapes = self._client.meta.service_model.error_shapes
198        if not error_shapes:
199            section.style.new_line()
200            section.write('This client has no modeled exception classes.')
201            section.style.new_line()
202            return
203        section.style.new_line()
204        section.write('The available client exceptions are:')
205        section.style.new_line()
206        for shape in error_shapes:
207            class_name = self._exception_class_name(shape)
208            section.style.li(':py:class:`%s`' % class_name)
209
210    def _add_exception_classes(self, section):
211        for shape in self._client.meta.service_model.error_shapes:
212            self._add_exception_class(section, shape)
213
214    def _add_exception_class(self, section, shape):
215        class_section = section.add_new_section(shape.name)
216        class_name = self._exception_class_name(shape)
217        class_section.style.start_sphinx_py_class(class_name=class_name)
218        self._add_top_level_documentation(class_section, shape)
219        self._add_exception_catch_example(class_section, shape)
220        self._add_response_attr(class_section, shape)
221        class_section.style.end_sphinx_py_class()
222
223    def _add_top_level_documentation(self, section, shape):
224        if shape.documentation:
225            section.style.new_line()
226            section.include_doc_string(shape.documentation)
227            section.style.new_line()
228
229    def _add_exception_catch_example(self, section, shape):
230        section.style.new_line()
231        section.style.bold('Example')
232        section.style.start_codeblock()
233        section.write('try:')
234        section.style.indent()
235        section.style.new_line()
236        section.write('...')
237        section.style.dedent()
238        section.style.new_line()
239        section.write('except client.exceptions.%s as e:' % shape.name)
240        section.style.indent()
241        section.style.new_line()
242        section.write('print(e.response)')
243        section.style.dedent()
244        section.style.end_codeblock()
245
246    def _add_response_attr(self, section, shape):
247        response_section = section.add_new_section('response')
248        response_section.style.start_sphinx_py_attr('response')
249        self._add_response_attr_description(response_section)
250        self._add_response_example(response_section, shape)
251        self._add_response_params(response_section, shape)
252        response_section.style.end_sphinx_py_attr()
253
254    def _add_response_attr_description(self, section):
255        section.style.new_line()
256        section.include_doc_string(
257            'The parsed error response. All exceptions have a top level '
258            '``Error`` key that provides normalized access to common '
259            'exception atrributes. All other keys are specific to this '
260            'service or exception class.'
261        )
262        section.style.new_line()
263
264    def _add_response_example(self, section, shape):
265        example_section = section.add_new_section('syntax')
266        example_section.style.new_line()
267        example_section.style.bold('Syntax')
268        example_section.style.new_paragraph()
269        documenter = ResponseExampleDocumenter(
270            service_name=self._service_name,
271            operation_name=None,
272            event_emitter=self._client.meta.events,
273        )
274        documenter.document_example(
275            example_section, shape, include=[self._GENERIC_ERROR_SHAPE],
276        )
277
278    def _add_response_params(self, section, shape):
279        params_section = section.add_new_section('Structure')
280        params_section.style.new_line()
281        params_section.style.bold('Structure')
282        params_section.style.new_paragraph()
283        documenter = ResponseParamsDocumenter(
284            service_name=self._service_name,
285            operation_name=None,
286            event_emitter=self._client.meta.events,
287        )
288        documenter.document_params(
289            params_section, shape, include=[self._GENERIC_ERROR_SHAPE],
290        )
291