Repository: getmoto/moto
Pull request: Prettify responses
Prettify responses from moto. I monkey patch jinja render to avoid adding the same piece of code manually in each place where template is rendered.

- [x] make monkey patch to prettify responses
- [x] add documentation
- [x] add test

Please note that AWS returns pretiffied responses

Solution to https://github.com/spulec/moto/issues/5537

Problem statement:
Option for prettified responses
Moto is returning responses in form of single line xml strings. For debugging purposes it would be useful to have an option to have prettified responses. 

Please note that when logging terraform logs for with TF_LOG=True and selected TF_LOG_PATH, AWS returns prettified responses. I did not investigate AWS behaviour in this regard in other modes.

Hints from issue discussion:

Commit message: Merge branch 'master' into prettify-responses

Patch to review:
diff --git a/docs/docs/configuration/index.rst b/docs/docs/configuration/index.rst
index b98154f69103..c275b1875d23 100644
--- a/docs/docs/configuration/index.rst
+++ b/docs/docs/configuration/index.rst
@@ -12,6 +12,7 @@ Moto has a variety of ways to configure the mock behaviour.
 
   environment_variables
   recorder/index
+  prettify_jinja_render
   state_transition/index
   state_transition/models
 
diff --git a/docs/docs/configuration/prettify_jinja_render.rst b/docs/docs/configuration/prettify_jinja_render.rst
new file mode 100644
index 000000000000..76f78df4ef66
--- /dev/null
+++ b/docs/docs/configuration/prettify_jinja_render.rst
@@ -0,0 +1,38 @@
+.. _mokey_patch_page:
+
+.. role:: raw-html(raw)
+    :format: html
+
+=============================
+Prettify jinja render
+=============================
+
+This option allows to prettify responses from moto. Pretty responses are more readable (eg. for debugging purposes). 
+It also makes moto better in mocking AWS as AWS returns prettified responses.
+
+Ugly output:
+
+.. sourcecode:: python
+
+    <DeleteLaunchTemplatesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/"><requestId>178936da-50ad-4d58-8871-22d9979e8658example</requestId><launchTemplate><defaultVersionNumber>1</defaultVersionNumber><launchTemplateId>lt-d920e32b0cccd6adb</launchTemplateId><launchTemplateName>example-name</launchTemplateName></launchTemplate></DeleteLaunchTemplatesResponse>
+
+Prettified output:
+
+.. sourcecode:: python
+
+    <DeleteLaunchTemplatesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
+        <requestId>178936da-50ad-4d58-8871-22d9979e8658example</requestId>
+        <launchTemplate>
+            <defaultVersionNumber>1</defaultVersionNumber>
+            <launchTemplateId>lt-d920e32b0cccd6adb</launchTemplateId>
+            <launchTemplateName>example-name</launchTemplateName>
+        </launchTemplate>
+    </DeleteLaunchTemplatesResponse>
+
+
+Configuration
+#################
+
+As changing responses can interfere with some external tools, it is disabled by default.
+If you want to enable it, use environment variable:
+`MOTO_PRETTIFY_RESPONSES=True`
\ No newline at end of file
diff --git a/moto/core/responses.py b/moto/core/responses.py
index ac47413a260e..d1a774f83afe 100644
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -18,6 +18,7 @@
 from typing import Dict, List, Union, Any, Optional
 from urllib.parse import parse_qs, parse_qsl, urlparse
 from werkzeug.exceptions import HTTPException
+import moto.prettify_jinja_render  # noqa # pylint: disable=unused-import
 
 log = logging.getLogger(__name__)
 
diff --git a/moto/prettify_jinja_render.py b/moto/prettify_jinja_render.py
new file mode 100644
index 000000000000..d6cb6a11a7ad
--- /dev/null
+++ b/moto/prettify_jinja_render.py
@@ -0,0 +1,19 @@
+from jinja2.environment import Template
+from functools import wraps
+from xml.dom.minidom import parseString as parseXML
+from moto import settings
+
+original_render = Template.render
+
+
+@wraps(original_render)
+def pretty_render(*args, **kwargs):
+    ugly_string = original_render(*args, **kwargs)
+    try:
+        return parseXML(ugly_string).toprettyxml()
+    except Exception:
+        return ugly_string
+
+
+if settings.PRETTIFY_RESPONSES:
+    Template.render = pretty_render
diff --git a/moto/settings.py b/moto/settings.py
index 13e1b2a17978..65ceaeb4da78 100644
--- a/moto/settings.py
+++ b/moto/settings.py
@@ -29,6 +29,9 @@
 ENABLE_AMI_VALIDATION = bool(os.environ.get("MOTO_ENABLE_AMI_VALIDATION", False))
 
 
+PRETTIFY_RESPONSES = bool(os.environ.get("MOTO_PRETTIFY_RESPONSES", False))
+
+
 def get_sf_execution_history_type():
     """
     Determines which execution history events `get_execution_history` returns
diff --git a/tests/test_core/test_responses.py b/tests/test_core/test_responses.py
index 5f42084863c7..43dfb88159e5 100644
--- a/tests/test_core/test_responses.py
+++ b/tests/test_core/test_responses.py
@@ -1,3 +1,4 @@
+from unittest import mock
 import sure  # noqa # pylint: disable=unused-import
 
 from collections import OrderedDict
@@ -6,6 +7,7 @@
 
 from moto.core.responses import AWSServiceSpec, BaseResponse
 from moto.core.responses import flatten_json_request_body
+from moto.prettify_jinja_render import pretty_render
 
 
 def test_flatten_json_request_body():
@@ -205,3 +207,17 @@ class ResponseB(BaseResponse):
     assert resp_a_new_instance.contains_template(
         BaseResponse._make_template_id(source_2)
     )
+
+
+@mock.patch(
+    "jinja2.environment.Template.render",
+    pretty_render,
+)
+def test_jinja_render_prettify():
+
+    response = BaseResponse()
+    TEMPLATE = """<TestTemplate><ResponseText>Test text</ResponseText></TestTemplate>"""
+    expected_output = '<?xml version="1.0" ?>\n<TestTemplate>\n\t<ResponseText>Test text</ResponseText>\n</TestTemplate>\n'
+    template = response.response_template(TEMPLATE)
+    xml_string = template.render()
+    assert xml_string == expected_output


Code review comments:
diff_hunk: @@ -18,6 +18,7 @@
 from typing import Dict, List, Union, Any, Optional
 from urllib.parse import parse_qs, parse_qsl, urlparse
 from werkzeug.exceptions import HTTPException
+import moto.prettify_jinja_render  # noqa # pylint: disable=unused-import
path: moto/core/responses.py
text: @user1:
Sorry, I wasn't very clear earlier. I was hoping to have all the logic here, instead of in a separate file.
It is better to extend the existing method that stores the XML, IMO. In pseudocode:
```
def response_template(self, source):
    template_id = self._make_template_id(source)
        if not self.contains_template(template_id):
            if PRETTIFY:
                pretty_xml = parseXML(..)
            else:
                collapsed = re.sub(
                    self.RIGHT_PATTERN, ">", re.sub(self.LEFT_PATTERN, "<", source)
                )
            self.environment.loader.update({template_id: collapsed or pretty_xml})
        return self.environment.get_template(template_id)
```

 - easier to reason if the logic is in one place
 - it doesn't clutter the root folder
 - it prevents people from treating this behaviour as a public API, by calling `from moto import prettify_jinja_render` in their own code