Repository: great-expectations/great_expectations

Issue:
Use cleaner solution for non-truncating division in python 2
Prefer `from __future__ import division` to `1.*x/y`


Discussion:
Done inline during improve_partitions push.

Resolving patch:
diff --git a/great_expectations/render/view/view.py b/great_expectations/render/view/view.py
--- a/great_expectations/render/view/view.py
+++ b/great_expectations/render/view/view.py
@@ -2,11 +2,18 @@
 
 import datetime
 import json
+import re
 from collections import OrderedDict
 from string import Template as pTemplate
 from uuid import uuid4
 
 import mistune
+from great_expectations import __version__ as ge_version
+from great_expectations.render.types import (
+    RenderedComponentContent,
+    RenderedContent,
+    RenderedDocumentContent,
+)
 from jinja2 import (
     ChoiceLoader,
     Environment,
@@ -16,13 +23,6 @@
     select_autoescape,
 )
 
-from great_expectations import __version__ as ge_version
-from great_expectations.render.types import (
-    RenderedComponentContent,
-    RenderedContent,
-    RenderedDocumentContent,
-)
-
 
 class NoOpTemplate(object):
     def render(self, document):
@@ -252,6 +252,12 @@ def render_string_template(self, template):
         if not isinstance(template, (dict, OrderedDict)):
             return template
 
+        # if there are any groupings of two or more $, we need to double the groupings to account
+        # for template string substitution escaping
+        template["template"] = re.sub(
+            r"\${2,}", lambda m: m.group(0) * 2, template.get("template", "")
+        )
+
         tag = template.get("tag", "span")
         template["template"] = template.get("template", "").replace("\n", "<br>")
 
@@ -307,7 +313,7 @@ def render_string_template(self, template):
 
                     params[parameter] = pTemplate(
                         base_param_template_string
-                    ).substitute(
+                    ).safe_substitute(
                         {
                             "styling": self.render_styling(default_parameter_styling),
                             "content": params[parameter],
@@ -326,7 +332,9 @@ def render_string_template(self, template):
                     param_template_string = "<{param_tag} $styling>$content</{param_tag}>".format(
                         param_tag=param_tag
                     )
-                    params[parameter] = pTemplate(param_template_string).substitute(
+                    params[parameter] = pTemplate(
+                        param_template_string
+                    ).safe_substitute(
                         {
                             "styling": self.render_styling(parameter_styling),
                             "content": params[parameter],
@@ -334,23 +342,23 @@ def render_string_template(self, template):
                     )
 
             string = pTemplate(
-                pTemplate(base_template_string).substitute(
+                pTemplate(base_template_string).safe_substitute(
                     {
                         "template": template["template"],
                         "styling": self.render_styling(template.get("styling", {})),
                     }
                 )
-            ).substitute(params)
+            ).safe_substitute(params)
             return string
 
         return pTemplate(
-            pTemplate(base_template_string).substitute(
+            pTemplate(base_template_string).safe_substitute(
                 {
                     "template": template.get("template", ""),
                     "styling": self.render_styling(template.get("styling", {})),
                 }
             )
-        ).substitute(template.get("params", {}))
+        ).safe_substitute(template.get("params", {}))
 
     def _validate_document(self, document):
         raise NotImplementedError


Test patch:
diff --git a/tests/render/test_render.py b/tests/render/test_render.py
--- a/tests/render/test_render.py
+++ b/tests/render/test_render.py
@@ -5,9 +5,8 @@
 import shutil
 from collections import OrderedDict
 
-import pytest
-
 import great_expectations as ge
+import pytest
 from great_expectations.core import expectationSuiteSchema
 from great_expectations.data_context.util import file_relative_path
 from great_expectations.profile.basic_dataset_profiler import BasicDatasetProfiler
@@ -459,13 +458,3 @@ def test_render_string_template():
     )
 
     assert res == expected
-
-
-def test_render_string_template_bug_1():
-    # Looks like string templates can't contain dollar signs. We need some kind of escaping
-    with pytest.raises(ValueError):
-        template = {
-            "template": "Car Insurance Premiums ($)",
-            "tooltip": {"content": "expect_column_to_exist", "placement": "top"},
-        }
-        DefaultJinjaPageView().render_string_template(template)
diff --git a/tests/render/test_styled_string_template.py b/tests/render/test_styled_string_template.py
--- a/tests/render/test_styled_string_template.py
+++ b/tests/render/test_styled_string_template.py
@@ -1,9 +1,8 @@
 import json
 
-import pytest
-
 import great_expectations as ge
 import great_expectations.render as render
+import pytest
 from great_expectations.render.renderer import (
     ExpectationSuiteColumnSectionRenderer,
     ExpectationSuitePageRenderer,
@@ -105,3 +104,57 @@ def test_render_template():
     ).replace(
         "\n", ""
     )
+
+
+def test_render_template_with_extra_dollar_signs_in_template():
+    result = DefaultJinjaPageView().render_string_template(
+        {
+            "template": "It was the $first_adj of times; it was the $second_adj of times. Blahhh$hhhh. $Bloooop. "
+            "Bleep$.",
+            "params": {"first_adj": "best", "second_adj": "worst",},
+        }
+    )
+    assert result.replace(" ", "").replace("\t", "").replace(
+        "\n", ""
+    ) == "<span>It was the best of times; it was the worst of times. Blahhh$hhhh. $Bloooop. Bleep$.</span>".replace(
+        " ", ""
+    ).replace(
+        "\t", ""
+    ).replace(
+        "\n", ""
+    )
+
+    result = DefaultJinjaPageView().render_string_template(
+        {
+            "template": "It was the $first_adj of times; it was the $second_adj of times. Blahhh$$$hhhh. $$Bloooop. Bleep$$$$$.",
+            "params": {"first_adj": "best", "second_adj": "worst",},
+        }
+    )
+    assert result.replace(" ", "").replace("\t", "").replace(
+        "\n", ""
+    ) == "<span>It was the best of times; it was the worst of times. Blahhh$$$hhhh. $$Bloooop. Bleep$$$$$.</span>".replace(
+        " ", ""
+    ).replace(
+        "\t", ""
+    ).replace(
+        "\n", ""
+    )
+
+
+def test_render_template_with_extra_dollar_signs_in_param_values():
+    result = DefaultJinjaPageView().render_string_template(
+        {
+            "template": "It was the $first_adj of times; it was the $second_adj of times. Blahhh$hhhh. $Bloooop. "
+            "Bleep$.",
+            "params": {"first_adj": "$best$", "second_adj": "$$worst$",},
+        }
+    )
+    assert result.replace(" ", "").replace("\t", "").replace(
+        "\n", ""
+    ) == "<span>It was the $best$ of times; it was the $$worst$ of times. Blahhh$hhhh. $Bloooop. Bleep$.</span>".replace(
+        " ", ""
+    ).replace(
+        "\t", ""
+    ).replace(
+        "\n", ""
+    )