Repository: boto/botocore
Pull request #1292 (2017-10-11 20:27:26)

Add warning to documentation for deprecated operations
This adds a warning message to the documentation that hopefully makes it easier to notice deprecated operations and discourage their use.
<img width="690" alt="screen shot 2017-10-11 at 1 23 38 pm" src="https://user-images.githubusercontent.com/1935727/31465115-d1bd4e94-ae87-11e7-8129-4dfdd8347661.png">



Patch:
diff --git a/botocore/docs/bcdoc/style.py b/botocore/docs/bcdoc/style.py
index ac42d55e2..1a448e640 100644
--- a/botocore/docs/bcdoc/style.py
+++ b/botocore/docs/bcdoc/style.py
@@ -186,6 +186,16 @@ class ReSTStyle(BaseStyle):
         self.dedent()
         self.new_paragraph()
 
+    def start_danger(self, attrs=None):
+        self.new_paragraph()
+        self.doc.write('.. danger::')
+        self.indent()
+        self.new_paragraph()
+
+    def end_danger(self):
+        self.dedent()
+        self.new_paragraph()
+
     def start_a(self, attrs=None):
         if attrs:
             for attr_key, attr_value in attrs:
diff --git a/botocore/docs/method.py b/botocore/docs/method.py
index 882c3172e..f7baa570c 100644
--- a/botocore/docs/method.py
+++ b/botocore/docs/method.py
@@ -174,6 +174,13 @@ def document_model_driven_method(section, method_name, operation_model,
     # Add the description for the method.
     method_intro_section = section.add_new_section('method-intro')
     method_intro_section.include_doc_string(method_description)
+    if operation_model.deprecated:
+        method_intro_section.style.start_danger()
+        method_intro_section.writeln(
+                'This operation is deprecated and may not function as '
+                'expected. This operation should not be used going forward '
+                'and is only kept for the purpose of backwards compatiblity.')
+        method_intro_section.style.end_danger()
     service_uid = operation_model.service_model.metadata.get('uid')
     if service_uid is not None:
         method_intro_section.style.new_paragraph()
diff --git a/botocore/model.py b/botocore/model.py
index f9ecce663..d3a8a57eb 100644
--- a/botocore/model.py
+++ b/botocore/model.py
@@ -396,6 +396,10 @@ class OperationModel(object):
     def documentation(self):
         return self._operation_model.get('documentation', '')
 
+    @CachedProperty
+    def deprecated(self):
+        return self._operation_model.get('deprecated', False)
+
     @CachedProperty
     def input_shape(self):
         if 'input' not in self._operation_model:


Test patch:
diff --git a/tests/unit/docs/bcdoc/test_style.py b/tests/unit/docs/bcdoc/test_style.py
index a6278733a..273106fd9 100644
--- a/tests/unit/docs/bcdoc/test_style.py
+++ b/tests/unit/docs/bcdoc/test_style.py
@@ -105,6 +105,27 @@ class TestStyle(unittest.TestCase):
         self.assertEqual(style.doc.getvalue(),
                          six.b('::\n\n  foobar\n\n\n'))
 
+    def test_important(self):
+        style = ReSTStyle(ReSTDocument())
+        style.start_important()
+        style.end_important()
+        self.assertEqual(style.doc.getvalue(),
+                         six.b('\n\n.. warning::\n\n  \n\n'))
+
+    def test_note(self):
+        style = ReSTStyle(ReSTDocument())
+        style.start_note()
+        style.end_note()
+        self.assertEqual(style.doc.getvalue(),
+                         six.b('\n\n.. note::\n\n  \n\n'))
+
+    def test_danger(self):
+        style = ReSTStyle(ReSTDocument())
+        style.start_danger()
+        style.end_danger()
+        self.assertEqual(style.doc.getvalue(),
+                         six.b('\n\n.. danger::\n\n  \n\n'))
+
     def test_toctree_html(self):
         style = ReSTStyle(ReSTDocument())
         style.doc.target = 'html'
diff --git a/tests/unit/docs/test_method.py b/tests/unit/docs/test_method.py
index 2be588026..3072b4a5c 100644
--- a/tests/unit/docs/test_method.py
+++ b/tests/unit/docs/test_method.py
@@ -338,3 +338,19 @@ class TestDocumentModelDrivenMethod(BaseDocsTest):
         # The line in the parameter description
         self.assert_contains_line(
             ':type Body: bytes or seekable file-like object')
+
+    def test_deprecated(self):
+        self.json_model['operations']['SampleOperation']['deprecated'] = True
+        document_model_driven_method(
+            self.doc_structure, 'foo', self.operation_model,
+            event_emitter=self.event_emitter,
+            method_description='This describes the foo method.',
+            example_prefix='response = client.foo'
+        )
+        # The line in the example
+        self.assert_contains_lines_in_order([
+            '  .. danger::',
+            '        This operation is deprecated and may not function as '
+            'expected. This operation should not be used going forward and is '
+            'only kept for the purpose of backwards compatiblity.'
+        ])
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index 4e4ae49b2..3854b3a9f 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -225,6 +225,23 @@ class TestOperationModelFromService(unittest.TestCase):
         operation = self.service_model.operation_model('OperationTwo')
         self.assertIsNone(operation.auth_type)
 
+    def test_deprecated_present(self):
+        self.model['operations']['OperationName']['deprecated'] = True
+        service_model = model.ServiceModel(self.model)
+        operation_name = service_model.operation_model('OperationName')
+        self.assertTrue(operation_name.deprecated)
+
+    def test_deprecated_present_false(self):
+        self.model['operations']['OperationName']['deprecated'] = False
+        service_model = model.ServiceModel(self.model)
+        operation_name = service_model.operation_model('OperationName')
+        self.assertFalse(operation_name.deprecated)
+
+    def test_deprecated_absent(self):
+        service_model = model.ServiceModel(self.model)
+        operation_two = service_model.operation_model('OperationTwo')
+        self.assertFalse(operation_two.deprecated)
+
 
 class TestOperationModelStreamingTypes(unittest.TestCase):
     def setUp(self):