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/expectations/expectation.py b/great_expectations/expectations/expectation.py
--- a/great_expectations/expectations/expectation.py
+++ b/great_expectations/expectations/expectation.py
@@ -2395,6 +2395,16 @@ def _format_map_output(
     if result_format["result_format"] == "BASIC":
         return return_obj
 
+    if len(unexpected_list) and isinstance(unexpected_list[0], dict):
+        # in the case of multicolumn map expectations `unexpected_list` contains dicts,
+        # which will throw an exception when we hash it to count unique members.
+        # As a workaround, we flatten the values out to tuples.
+        immutable_unexpected_list = [
+            tuple([val for val in item.values()]) for item in unexpected_list
+        ]
+    else:
+        immutable_unexpected_list = unexpected_list
+
     # Try to return the most common values, if possible.
     partial_unexpected_counts = None
     if 0 < result_format.get("partial_unexpected_count"):
@@ -2402,7 +2412,7 @@ def _format_map_output(
             partial_unexpected_counts = [
                 {"value": key, "count": value}
                 for key, value in sorted(
-                    Counter(unexpected_list).most_common(
+                    Counter(immutable_unexpected_list).most_common(
                         result_format["partial_unexpected_count"]
                     ),
                     key=lambda x: (-x[1], x[0]),
@@ -2410,7 +2420,7 @@ def _format_map_output(
             ]
         except TypeError:
             partial_unexpected_counts = [
-                "partial_exception_counts requires a hashable type"
+                {"error": "partial_exception_counts requires a hashable type"}
             ]
         finally:
             return_obj["result"].update(


Test patch:
diff --git a/tests/expectations/test_format_map_output.py b/tests/expectations/test_format_map_output.py
new file mode 100644
--- /dev/null
+++ b/tests/expectations/test_format_map_output.py
@@ -0,0 +1,154 @@
+from great_expectations.expectations.expectation import _format_map_output
+
+
+def test_format_map_output_with_numbers():
+    success = False
+    element_count = 5
+    nonnull_count = 5
+    success_count = 0
+    unexpected_list = [
+        {"foreign_key_1": 1, "foreign_key_2": 2},
+        {"foreign_key_1": 1, "foreign_key_2": 2},
+        {"foreign_key_1": 1, "foreign_key_2": 2},
+    ]
+    unexpected_index_list = [1, 2, 3]
+    assert _format_map_output(
+        result_format={
+            "result_format": "COMPLETE",
+            "partial_unexpected_count": 20,
+            "include_unexpected_rows": False,
+        },
+        success=success,
+        element_count=element_count,
+        nonnull_count=nonnull_count,
+        unexpected_count=len(unexpected_list),
+        unexpected_list=unexpected_list,
+        unexpected_index_list=unexpected_index_list,
+    ) == {
+        "success": False,
+        "result": {
+            "element_count": 5,
+            "missing_count": 0,
+            "missing_percent": 0.0,
+            "partial_unexpected_list": [
+                {"foreign_key_1": 1, "foreign_key_2": 2},
+                {"foreign_key_1": 1, "foreign_key_2": 2},
+                {"foreign_key_1": 1, "foreign_key_2": 2},
+            ],
+            "unexpected_count": 3,
+            "unexpected_percent": 60.0,
+            "unexpected_percent_total": 60.0,
+            "unexpected_percent_nonmissing": 60.0,
+            "partial_unexpected_counts": [{"value": (1, 2), "count": 3}],
+            "partial_unexpected_index_list": [1, 2, 3],
+            "unexpected_list": [
+                {"foreign_key_1": 1, "foreign_key_2": 2},
+                {"foreign_key_1": 1, "foreign_key_2": 2},
+                {"foreign_key_1": 1, "foreign_key_2": 2},
+            ],
+            "unexpected_index_list": [1, 2, 3],
+        },
+    }
+
+
+def test_format_map_output_with_strings():
+    success = False
+    element_count = 5
+    nonnull_count = 5
+    success_count = 0
+    unexpected_list = [
+        {"foreign_key_1": "a", "foreign_key_2": 2},
+        {"foreign_key_1": "a", "foreign_key_2": 2},
+        {"foreign_key_1": "a", "foreign_key_2": 2},
+    ]
+    unexpected_index_list = [1, 2, 3]
+    assert _format_map_output(
+        result_format={
+            "result_format": "COMPLETE",
+            "partial_unexpected_count": 20,
+            "include_unexpected_rows": False,
+        },
+        success=success,
+        element_count=element_count,
+        nonnull_count=nonnull_count,
+        unexpected_count=len(unexpected_list),
+        unexpected_list=unexpected_list,
+        unexpected_index_list=unexpected_index_list,
+    ) == {
+        "success": False,
+        "result": {
+            "element_count": 5,
+            "missing_count": 0,
+            "missing_percent": 0.0,
+            "partial_unexpected_list": [
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+            ],
+            "unexpected_count": 3,
+            "unexpected_percent": 60.0,
+            "unexpected_percent_total": 60.0,
+            "unexpected_percent_nonmissing": 60.0,
+            "partial_unexpected_counts": [{"value": ("a", 2), "count": 3}],
+            "partial_unexpected_index_list": [1, 2, 3],
+            "unexpected_list": [
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+            ],
+            "unexpected_index_list": [1, 2, 3],
+        },
+    }
+
+
+def test_format_map_output_with_strings_two_matches():
+    success = False
+    element_count = 5
+    nonnull_count = 5
+    success_count = 0
+    unexpected_list = [
+        {"foreign_key_1": "a", "foreign_key_2": 2},
+        {"foreign_key_1": "a", "foreign_key_2": 2},
+        {"foreign_key_1": "b", "foreign_key_2": 3},
+    ]
+    unexpected_index_list = [1, 3]
+    assert _format_map_output(
+        result_format={
+            "result_format": "COMPLETE",
+            "partial_unexpected_count": 20,
+            "include_unexpected_rows": False,
+        },
+        success=success,
+        element_count=element_count,
+        nonnull_count=nonnull_count,
+        unexpected_count=len(unexpected_list),
+        unexpected_list=unexpected_list,
+        unexpected_index_list=unexpected_index_list,
+    ) == {
+        "success": False,
+        "result": {
+            "element_count": 5,
+            "missing_count": 0,
+            "missing_percent": 0.0,
+            "partial_unexpected_list": [
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "b", "foreign_key_2": 3},
+            ],
+            "unexpected_count": 3,
+            "unexpected_percent": 60.0,
+            "unexpected_percent_total": 60.0,
+            "unexpected_percent_nonmissing": 60.0,
+            "partial_unexpected_counts": [
+                {"value": ("a", 2), "count": 2},
+                {"value": ("b", 3), "count": 1},
+            ],
+            "partial_unexpected_index_list": [1, 3],
+            "unexpected_list": [
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "a", "foreign_key_2": 2},
+                {"foreign_key_1": "b", "foreign_key_2": 3},
+            ],
+            "unexpected_index_list": [1, 3],
+        },
+    }