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.
13from tests import unittest
14from tests.unit.docs import BaseDocsTest
15from botocore.hooks import HierarchicalEmitter
16from botocore.docs.method import document_model_driven_signature
17from botocore.docs.method import document_custom_signature
18from botocore.docs.method import document_custom_method
19from botocore.docs.method import document_model_driven_method
20from botocore.docs.method import get_instance_public_methods
21from botocore.docs.utils import DocumentedShape
22
23
24class TestGetInstanceMethods(unittest.TestCase):
25    class MySampleClass(object):
26        def _internal_method(self):
27            pass
28
29        def public_method(self):
30            pass
31
32    def test_get_instance_methods(self):
33        instance = self.MySampleClass()
34        instance_methods = get_instance_public_methods(instance)
35        self.assertEqual(len(instance_methods), 1)
36        self.assertIn('public_method', instance_methods)
37        self.assertEqual(
38            instance.public_method, instance_methods['public_method'])
39
40
41class TestDocumentModelDrivenSignature(BaseDocsTest):
42    def setUp(self):
43        super(TestDocumentModelDrivenSignature, self).setUp()
44        self.add_shape_to_params('Foo', 'String')
45        self.add_shape_to_params('Bar', 'String', is_required=True)
46        self.add_shape_to_params('Baz', 'String')
47
48    def test_document_signature(self):
49        document_model_driven_signature(
50            self.doc_structure, 'my_method', self.operation_model)
51        self.assert_contains_line(
52            '.. py:method:: my_method(**kwargs)')
53
54    def test_document_signature_exclude_all_kwargs(self):
55        exclude_params = ['Foo', 'Bar', 'Baz']
56        document_model_driven_signature(
57            self.doc_structure, 'my_method', self.operation_model,
58            exclude=exclude_params)
59        self.assert_contains_line(
60            '.. py:method:: my_method()')
61
62    def test_document_signature_exclude_and_include(self):
63        exclude_params = ['Foo', 'Bar', 'Baz']
64        include_params = [
65            DocumentedShape(
66                name='Biz', type_name='integer', documentation='biz docs')
67        ]
68        document_model_driven_signature(
69            self.doc_structure, 'my_method', self.operation_model,
70            include=include_params, exclude=exclude_params)
71        self.assert_contains_line(
72            '.. py:method:: my_method(**kwargs)')
73
74
75class TestDocumentCustomSignature(BaseDocsTest):
76    def sample_method(self, foo, bar='bar', baz=None):
77        pass
78
79    def test_document_signature(self):
80        document_custom_signature(
81            self.doc_structure, 'my_method', self.sample_method)
82        self.assert_contains_line(
83            '.. py:method:: my_method(foo, bar=\'bar\', baz=None)')
84
85
86class TestDocumentCustomMethod(BaseDocsTest):
87    def custom_method(self, foo):
88        """This is a custom method
89
90        :type foo: string
91        :param foo: The foo parameter
92        """
93        pass
94
95    def test_document_custom_signature(self):
96        document_custom_method(
97            self.doc_structure, 'my_method', self.custom_method)
98        self.assert_contains_lines_in_order([
99            '.. py:method:: my_method(foo)',
100            '  This is a custom method',
101            '  :type foo: string',
102            '  :param foo: The foo parameter'
103        ])
104
105
106class TestDocumentModelDrivenMethod(BaseDocsTest):
107    def setUp(self):
108        super(TestDocumentModelDrivenMethod, self).setUp()
109        self.event_emitter = HierarchicalEmitter()
110        self.add_shape_to_params('Bar', 'String')
111
112    def test_default(self):
113        document_model_driven_method(
114            self.doc_structure, 'foo', self.operation_model,
115            event_emitter=self.event_emitter,
116            method_description='This describes the foo method.',
117            example_prefix='response = client.foo'
118        )
119        cross_ref_link = (
120            'See also: `AWS API Documentation '
121            '<https://docs.aws.amazon.com/goto/WebAPI'
122            '/myservice-2014-01-01/SampleOperation>'
123        )
124        self.assert_contains_lines_in_order([
125            '.. py:method:: foo(**kwargs)',
126            '  This describes the foo method.',
127            cross_ref_link,
128            '  **Request Syntax**',
129            '  ::',
130            '    response = client.foo(',
131            '        Bar=\'string\'',
132            '    )',
133            '  :type Bar: string',
134            '  :param Bar:',
135            '  :rtype: dict',
136            '  :returns:',
137            '    **Response Syntax**',
138            '    ::',
139            '      {',
140            '          \'Bar\': \'string\'',
141            '      }',
142            '    **Response Structure**',
143            '    - *(dict) --*',
144            '      - **Bar** *(string) --*'
145        ])
146
147    def test_no_input_output_shape(self):
148        del self.json_model['operations']['SampleOperation']['input']
149        del self.json_model['operations']['SampleOperation']['output']
150        document_model_driven_method(
151            self.doc_structure, 'foo', self.operation_model,
152            event_emitter=self.event_emitter,
153            method_description='This describes the foo method.',
154            example_prefix='response = client.foo'
155        )
156        self.assert_contains_lines_in_order([
157            '.. py:method:: foo()',
158            '  This describes the foo method.',
159            '  **Request Syntax**',
160            '  ::',
161            '    response = client.foo()',
162            '  :returns: None',
163        ])
164
165    def test_include_input(self):
166        include_params = [
167            DocumentedShape(
168                name='Biz', type_name='string', documentation='biz docs')
169        ]
170        document_model_driven_method(
171            self.doc_structure, 'foo', self.operation_model,
172            event_emitter=self.event_emitter,
173            method_description='This describes the foo method.',
174            example_prefix='response = client.foo',
175            include_input=include_params
176        )
177        self.assert_contains_lines_in_order([
178            '.. py:method:: foo(**kwargs)',
179            '  This describes the foo method.',
180            '  **Request Syntax**',
181            '  ::',
182            '    response = client.foo(',
183            '        Bar=\'string\',',
184            '        Biz=\'string\'',
185            '    )',
186            '  :type Bar: string',
187            '  :param Bar:',
188            '  :type Biz: string',
189            '  :param Biz: biz docs',
190            '  :rtype: dict',
191            '  :returns:',
192            '    **Response Syntax**',
193            '    ::',
194            '      {',
195            '          \'Bar\': \'string\'',
196            '      }',
197            '    **Response Structure**',
198            '    - *(dict) --*',
199            '      - **Bar** *(string) --*'
200        ])
201
202    def test_include_output(self):
203        include_params = [
204            DocumentedShape(
205                name='Biz', type_name='string', documentation='biz docs')
206        ]
207        document_model_driven_method(
208            self.doc_structure, 'foo', self.operation_model,
209            event_emitter=self.event_emitter,
210            method_description='This describes the foo method.',
211            example_prefix='response = client.foo',
212            include_output=include_params
213        )
214        self.assert_contains_lines_in_order([
215            '.. py:method:: foo(**kwargs)',
216            '  This describes the foo method.',
217            '  **Request Syntax**',
218            '  ::',
219            '    response = client.foo(',
220            '        Bar=\'string\'',
221            '    )',
222            '  :type Bar: string',
223            '  :param Bar:',
224            '  :rtype: dict',
225            '  :returns:',
226            '    **Response Syntax**',
227            '    ::',
228            '      {',
229            '          \'Bar\': \'string\'',
230            '          \'Biz\': \'string\'',
231            '      }',
232            '    **Response Structure**',
233            '    - *(dict) --*',
234            '      - **Bar** *(string) --*',
235            '      - **Biz** *(string) --*'
236        ])
237
238    def test_exclude_input(self):
239        self.add_shape_to_params('Biz', 'String')
240        document_model_driven_method(
241            self.doc_structure, 'foo', self.operation_model,
242            event_emitter=self.event_emitter,
243            method_description='This describes the foo method.',
244            example_prefix='response = client.foo',
245            exclude_input=['Bar']
246        )
247        self.assert_contains_lines_in_order([
248            '.. py:method:: foo(**kwargs)',
249            '  This describes the foo method.',
250            '  **Request Syntax**',
251            '  ::',
252            '    response = client.foo(',
253            '        Biz=\'string\'',
254            '    )',
255            '  :type Biz: string',
256            '  :param Biz:',
257            '  :rtype: dict',
258            '  :returns:',
259            '    **Response Syntax**',
260            '    ::',
261            '      {',
262            '          \'Bar\': \'string\'',
263            '          \'Biz\': \'string\'',
264            '      }',
265            '    **Response Structure**',
266            '    - *(dict) --*',
267            '      - **Bar** *(string) --*',
268            '      - **Biz** *(string) --*'
269        ])
270        self.assert_not_contains_lines([
271            ':param Bar: string',
272            'Bar=\'string\''
273        ])
274
275    def test_exclude_output(self):
276        self.add_shape_to_params('Biz', 'String')
277        document_model_driven_method(
278            self.doc_structure, 'foo', self.operation_model,
279            event_emitter=self.event_emitter,
280            method_description='This describes the foo method.',
281            example_prefix='response = client.foo',
282            exclude_output=['Bar']
283        )
284        self.assert_contains_lines_in_order([
285            '.. py:method:: foo(**kwargs)',
286            '  This describes the foo method.',
287            '  **Request Syntax**',
288            '  ::',
289            '    response = client.foo(',
290            '        Bar=\'string\'',
291            '        Biz=\'string\'',
292            '    )',
293            '  :type Biz: string',
294            '  :param Biz:',
295            '  :rtype: dict',
296            '  :returns:',
297            '    **Response Syntax**',
298            '    ::',
299            '      {',
300            '          \'Biz\': \'string\'',
301            '      }',
302            '    **Response Structure**',
303            '    - *(dict) --*',
304            '      - **Biz** *(string) --*'
305        ])
306        self.assert_not_contains_lines([
307            '\'Bar\': \'string\'',
308            '- **Bar** *(string) --*',
309        ])
310
311    def test_streaming_body_in_output(self):
312        self.add_shape_to_params('Body', 'Blob')
313        self.json_model['shapes']['Blob'] = {'type': 'blob'}
314        self.json_model['shapes']['SampleOperationInputOutput']['payload'] = \
315            'Body'
316        document_model_driven_method(
317            self.doc_structure, 'foo', self.operation_model,
318            event_emitter=self.event_emitter,
319            method_description='This describes the foo method.',
320            example_prefix='response = client.foo'
321        )
322        self.assert_contains_line('**Body** (:class:`.StreamingBody`)')
323
324    def test_event_stream_body_in_output(self):
325        self.add_shape_to_params('Payload', 'EventStream')
326        self.json_model['shapes']['SampleOperationInputOutput']['payload'] = \
327            'Payload'
328        self.json_model['shapes']['EventStream'] = {
329            'type': 'structure',
330            'eventstream': True,
331            'members': {'Event': {'shape': 'Event'}}
332        }
333        self.json_model['shapes']['Event'] = {
334            'type': 'structure',
335            'event': True,
336            'members': {
337                'Fields': {
338                    'shape': 'EventFields',
339                    'eventpayload': True,
340                }
341            }
342        }
343        self.json_model['shapes']['EventFields'] = {
344            'type': 'structure',
345            'members': {
346                'Field': {'shape': 'EventField'}
347            }
348        }
349        self.json_model['shapes']['EventField'] = {'type': 'blob'}
350        document_model_driven_method(
351            self.doc_structure, 'foo', self.operation_model,
352            event_emitter=self.event_emitter,
353            method_description='This describes the foo method.',
354            example_prefix='response = client.foo'
355        )
356        self.assert_contains_lines_in_order([
357            "this operation contains an :class:`.EventStream`",
358            "'Payload': EventStream({",
359            "'Event': {",
360            "'Fields': {",
361            "'Field': b'bytes'",
362            "**Payload** (:class:`.EventStream`)",
363            "**Event** *(dict)",
364            "**Fields** *(dict)",
365            "**Field** *(bytes)",
366        ])
367
368    def test_streaming_body_in_input(self):
369        del self.json_model['operations']['SampleOperation']['output']
370        self.add_shape_to_params('Body', 'Blob')
371        self.json_model['shapes']['Blob'] = {'type': 'blob'}
372        self.json_model['shapes']['SampleOperationInputOutput']['payload'] = \
373            'Body'
374        document_model_driven_method(
375            self.doc_structure, 'foo', self.operation_model,
376            event_emitter=self.event_emitter,
377            method_description='This describes the foo method.',
378            example_prefix='response = client.foo'
379        )
380        # The line in the example
381        self.assert_contains_line('Body=b\'bytes\'|file')
382        # The line in the parameter description
383        self.assert_contains_line(
384            ':type Body: bytes or seekable file-like object')
385
386    def test_deprecated(self):
387        self.json_model['operations']['SampleOperation']['deprecated'] = True
388        document_model_driven_method(
389            self.doc_structure, 'foo', self.operation_model,
390            event_emitter=self.event_emitter,
391            method_description='This describes the foo method.',
392            example_prefix='response = client.foo'
393        )
394        # The line in the example
395        self.assert_contains_lines_in_order([
396            '  .. danger::',
397            '        This operation is deprecated and may not function as '
398            'expected. This operation should not be used going forward and is '
399            'only kept for the purpose of backwards compatiblity.'
400        ])
401