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/metrics/map_metric_provider.py b/great_expectations/expectations/metrics/map_metric_provider.py
--- a/great_expectations/expectations/metrics/map_metric_provider.py
+++ b/great_expectations/expectations/metrics/map_metric_provider.py
@@ -1889,7 +1889,9 @@ def _get_evaluation_dependencies(
             runtime_configuration=runtime_configuration,
         )
         table_domain_kwargs: dict = {
-            k: v for k, v in metric.metric_domain_kwargs.items() if k != "column_list"
+            k: v
+            for k, v in metric.metric_domain_kwargs.items()
+            if k not in ["column_list", "ignore_row_if"]
         }
         dependencies["table.columns"] = MetricConfiguration(
             metric_name="table.columns",
diff --git a/great_expectations/expectations/metrics/multicolumn_map_metrics/__init__.py b/great_expectations/expectations/metrics/multicolumn_map_metrics/__init__.py
--- a/great_expectations/expectations/metrics/multicolumn_map_metrics/__init__.py
+++ b/great_expectations/expectations/metrics/multicolumn_map_metrics/__init__.py
@@ -1 +1,2 @@
+from .compound_columns_unique import CompoundColumnsUnique
 from .multicolumn_sum_equal import MulticolumnSumEqual
diff --git a/great_expectations/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py b/great_expectations/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py
new file mode 100644
--- /dev/null
+++ b/great_expectations/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py
@@ -0,0 +1,23 @@
+from great_expectations.execution_engine import PandasExecutionEngine
+from great_expectations.expectations.metrics.map_metric_provider import (
+    MulticolumnMapMetricProvider,
+    multicolumn_condition_partial,
+)
+
+
+class CompoundColumnsUnique(MulticolumnMapMetricProvider):
+    condition_metric_name = "compound_columns.unique"
+    condition_domain_keys = (
+        "batch_id",
+        "table",
+        "column_list",
+        "row_condition",
+        "condition_parser",
+        "ignore_row_if",
+    )
+
+    # TODO: <Alex>ALEX -- temporarily only a Pandas implementation is provided (others to follow).</Alex>
+    @multicolumn_condition_partial(engine=PandasExecutionEngine)
+    def _pandas(cls, column_list, **kwargs):
+        row_wise_cond = ~column_list.duplicated(keep=False)
+        return row_wise_cond


Test patch:
diff --git a/tests/expectations/metrics/test_core.py b/tests/expectations/metrics/test_core.py
--- a/tests/expectations/metrics/test_core.py
+++ b/tests/expectations/metrics/test_core.py
@@ -1708,4 +1708,149 @@ def test_map_multicolumn_sum_equal():
         pd.DataFrame(data={"a": [2], "b": [3], "c": [1], "d": [9]}, index=[2])
     )
     assert len(metrics[unexpected_rows_metric.id].columns) == 4
-    assert metrics[unexpected_rows_metric.id].index == [2]
+    pd.testing.assert_index_equal(
+        metrics[unexpected_rows_metric.id].index, pd.Index([2])
+    )
+
+
+def test_map_compound_columns_unique():
+    engine = build_pandas_engine(
+        pd.DataFrame(data={"a": [0, 1, 1], "b": [1, 2, 3], "c": [0, 2, 2]})
+    )
+
+    metrics: dict = {}
+
+    table_columns_metric: MetricConfiguration
+    results: dict
+
+    table_columns_metric, results = get_table_columns_metric(engine=engine)
+    metrics.update(results)
+
+    """
+    Two tests:
+    1. Pass -- no duplicated compound column keys.
+    2. Fail -- two duplicated compound column keys.
+    """
+
+    # Save original metrics for testing unexpected results.
+    metrics_save: dict = copy.deepcopy(metrics)
+
+    metric_name: str = "compound_columns.unique"
+    condition_metric_name: str = f"{metric_name}.condition"
+    unexpected_count_metric_name: str = f"{metric_name}.unexpected_count"
+
+    # First, assert Pass (no unexpected results).
+
+    condition_metric = MetricConfiguration(
+        metric_name=condition_metric_name,
+        metric_domain_kwargs={"column_list": ["a", "b"]},
+        metric_value_kwargs=dict(),
+        metric_dependencies={
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(condition_metric,),
+        metrics=metrics,
+    )
+    metrics.update(results)
+
+    unexpected_count_metric = MetricConfiguration(
+        metric_name=unexpected_count_metric_name,
+        metric_domain_kwargs={"column_list": ["a", "b"]},
+        metric_value_kwargs=dict(),
+        metric_dependencies={
+            "unexpected_condition": condition_metric,
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(unexpected_count_metric,), metrics=metrics
+    )
+    metrics.update(results)
+
+    # Condition metrics return "negative logic" series.
+    assert list(metrics[condition_metric.id][0]) == [False, False, False]
+    assert metrics[unexpected_count_metric.id] == 0
+
+    unexpected_rows_metric_name: str = f"{metric_name}.unexpected_rows"
+    unexpected_rows_metric = MetricConfiguration(
+        metric_name=unexpected_rows_metric_name,
+        metric_domain_kwargs={"column_list": ["a", "b"]},
+        metric_value_kwargs={
+            "result_format": {"result_format": "SUMMARY", "partial_unexpected_count": 3}
+        },
+        metric_dependencies={
+            "unexpected_condition": condition_metric,
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(unexpected_rows_metric,), metrics=metrics
+    )
+    metrics.update(results)
+
+    assert metrics[unexpected_rows_metric.id].empty
+    assert len(metrics[unexpected_rows_metric.id].columns) == 3
+
+    # Restore from saved original metrics in order to start fresh on testing for unexpected results.
+    metrics = copy.deepcopy(metrics_save)
+
+    # Second, assert Fail (one unexpected result).
+
+    condition_metric = MetricConfiguration(
+        metric_name=condition_metric_name,
+        metric_domain_kwargs={"column_list": ["a", "c"]},
+        metric_value_kwargs=dict(),
+        metric_dependencies={
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(condition_metric,),
+        metrics=metrics,
+    )
+    metrics.update(results)
+
+    unexpected_count_metric = MetricConfiguration(
+        metric_name=unexpected_count_metric_name,
+        metric_domain_kwargs={"column_list": ["a", "c"]},
+        metric_value_kwargs=dict(),
+        metric_dependencies={
+            "unexpected_condition": condition_metric,
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(unexpected_count_metric,), metrics=metrics
+    )
+    metrics.update(results)
+
+    # Condition metrics return "negative logic" series.
+    assert list(metrics[condition_metric.id][0]) == [False, True, True]
+    assert metrics[unexpected_count_metric.id] == 2
+
+    unexpected_rows_metric_name: str = f"{metric_name}.unexpected_rows"
+    unexpected_rows_metric = MetricConfiguration(
+        metric_name=unexpected_rows_metric_name,
+        metric_domain_kwargs={"column_list": ["a", "c"]},
+        metric_value_kwargs={
+            "result_format": {"result_format": "SUMMARY", "partial_unexpected_count": 3}
+        },
+        metric_dependencies={
+            "unexpected_condition": condition_metric,
+            "table.columns": table_columns_metric,
+        },
+    )
+    results = engine.resolve_metrics(
+        metrics_to_resolve=(unexpected_rows_metric,), metrics=metrics
+    )
+    metrics.update(results)
+
+    assert metrics[unexpected_rows_metric.id].equals(
+        pd.DataFrame(data={"a": [1, 1], "b": [2, 3], "c": [2, 2]}, index=[1, 2])
+    )
+    assert len(metrics[unexpected_rows_metric.id].columns) == 3
+    pd.testing.assert_index_equal(
+        metrics[unexpected_rows_metric.id].index, pd.Index([1, 2])
+    )