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/examples/expectations/table_expectation_template.py b/examples/expectations/table_expectation_template.py
--- a/examples/expectations/table_expectation_template.py
+++ b/examples/expectations/table_expectation_template.py
@@ -1,101 +1,69 @@
-import json
+"""
+This is a template for creating custom TableExpectations.
+For detailed instructions on how to use it, please see:
+    https://docs.greatexpectations.io/docs/guides/expectations/creating_custom_expectations/how_to_create_custom_table_expectations
+"""
 
-# This class defines a Metric to support your Expectation
-# For most Expectations, the main business logic for calculation will live here.
-# To learn about the relationship between Metrics and Expectations, please visit
-# https://docs.greatexpectations.io/en/latest/reference/core_concepts.html#expectations-and-metrics.
-from typing import Any, Dict, Optional, Tuple
+from typing import Dict, Optional
 
-import numpy as np
-
-from great_expectations.core import ExpectationConfiguration
-
-#!!! This giant block of imports should be something simpler, such as:
-# from great_exepectations.helpers.expectation_creation import *
+from great_expectations.core.expectation_configuration import ExpectationConfiguration
 from great_expectations.exceptions import InvalidExpectationConfigurationError
 from great_expectations.execution_engine import (
     ExecutionEngine,
     PandasExecutionEngine,
     SparkDFExecutionEngine,
-)
-from great_expectations.execution_engine.sqlalchemy_execution_engine import (
     SqlAlchemyExecutionEngine,
 )
-from great_expectations.expectations.expectation import (
-    ColumnMapExpectation,
-    Expectation,
-    ExpectationConfiguration,
-    TableExpectation,
-)
-from great_expectations.expectations.metrics import (
-    ColumnMapMetricProvider,
-    column_condition_partial,
+from great_expectations.expectations.expectation import TableExpectation
+from great_expectations.expectations.metrics.metric_provider import (
+    MetricConfiguration,
+    metric_value,
 )
-from great_expectations.expectations.metrics.metric_provider import metric_value
-from great_expectations.expectations.metrics.table_metric import TableMetricProvider
-from great_expectations.expectations.registry import (
-    _registered_expectations,
-    _registered_metrics,
-    _registered_renderers,
+from great_expectations.expectations.metrics.table_metric_provider import (
+    TableMetricProvider,
 )
-from great_expectations.expectations.util import render_evaluation_parameter_string
-from great_expectations.render.renderer.renderer import renderer
-from great_expectations.render.types import RenderedStringTemplateContent
-from great_expectations.render.util import num_to_str, substitute_none_for_missing
-from great_expectations.validator.validation_graph import MetricConfiguration
-from great_expectations.validator.validator import Validator
 
 
-# This class defines the Metric, a class used by the Expectation to compute important data for validating itself
-class TableAlphabeticalColumnNameCount(TableMetricProvider):
+# This class defines a Metric to support your Expectation.
+# For most ColumnExpectations, the main business logic for calculation will live in this class.
+class TableMeetsSomeCriteria(TableMetricProvider):
 
-    # This is a built in metric - you do not have to implement it yourself. If you would like to use
-    # a metric that does not yet exist, you can use the template below to implement it!
-    metric_name = "table.alphabetical_column_name_count"
+    # This is the id string that will be used to reference your Metric.
+    metric_name = "METRIC NAME GOES HERE"
 
-    # Below are metric computations for different dialects (Pandas, SqlAlchemy, Spark)
-    # They can be used to compute the table data you will need to validate your Expectation
+    # This method implements the core logic for the PandasExecutionEngine
     @metric_value(engine=PandasExecutionEngine)
     def _pandas(
         cls,
-        execution_engine: PandasExecutionEngine,
-        metric_domain_kwargs: Dict,
-        metric_value_kwargs: Dict,
-        metrics: Dict[Tuple, Any],
-        runtime_configuration: Dict,
-    ):
-        columns = metrics.get("table.columns")
-
-        # For each column, testing if alphabetical and returning number of alphabetical columns
-        return len([column for column in columns if column.isalpha()])
-
-    @metric_value(engine=SqlAlchemyExecutionEngine)
-    def _sqlalchemy(
-        cls,
-        execution_engine: SqlAlchemyExecutionEngine,
-        metric_domain_kwargs: Dict,
-        metric_value_kwargs: Dict,
-        metrics: Dict[Tuple, Any],
-        runtime_configuration: Dict,
+        execution_engine,
+        metric_domain_kwargs,
+        metric_value_kwargs,
+        metrics,
+        runtime_configuration,
     ):
-        columns = metrics.get("table.columns")
-
-        # For each column, testing if alphabetical and returning number of alphabetical columns
-        return len([column for column in columns if column.isalpha()])
-
-    @metric_value(engine=SparkDFExecutionEngine)
-    def _spark(
-        cls,
-        execution_engine: SparkDFExecutionEngine,
-        metric_domain_kwargs: Dict,
-        metric_value_kwargs: Dict,
-        metrics: Dict[Tuple, Any],
-        runtime_configuration: Dict,
-    ):
-        columns = metrics.get("table.columns")
-
-        # For each column, testing if alphabetical and returning number of alphabetical columns
-        return len([column for column in columns if column.isalpha()])
+        raise NotImplementedError
+
+    # @metric_value(engine=SqlAlchemyExecutionEngine)
+    # def _sqlalchemy(
+    #         cls,
+    #         execution_engine,
+    #         metric_domain_kwargs,
+    #         metric_value_kwargs,
+    #         metrics,
+    #         runtime_configuration,
+    # ):
+    #    raise NotImplementedError
+
+    # @metric_value(engine=SparkDFExecutionEngine)
+    # def _spark(
+    #         cls,
+    #         execution_engine,
+    #         metric_domain_kwargs,
+    #         metric_value_kwargs,
+    #         metrics,
+    #         runtime_configuration,
+    # ):
+    #    raise NotImplementedError
 
     @classmethod
     def _get_evaluation_dependencies(
@@ -114,62 +82,21 @@ def _get_evaluation_dependencies(
 
 # This class defines the Expectation itself
 # The main business logic for calculation lives here.
-class ExpectAlphabeticalColumnNameCountToEqual4(TableExpectation):
+class ExpectTableToMeetSomeCriteria(TableExpectation):
     """TODO: add a docstring here"""
 
-    # These examples will be shown in the public gallery, and also executed as unit tests for your Expectation
-    # These examples will be shown in the public gallery, and also executed as unit tests for your Expectation
-    examples = [
-        {
-            "data": {
-                "columnone": [3, 5, 7],
-                "columntwo": [True, False, True],
-                "columnthree": ["a", "b", "c"],
-                "columnfour": [None, 2, None],
-            },
-            "data_2": {
-                "columnnumberone": [3, 5, 7],
-                "columnnumbertwo": [True, False, True],
-                "columnnumberthree": ["a", "b", "c"],
-                "!_!!": ["c", "d", "e"],
-            },
-            "tests": [
-                {
-                    "title": "positive_test_with_4_alphabetical_columns",
-                    "exact_match_out": False,
-                    "include_in_gallery": True,
-                    "in": {"user_input": "helloWorld"},
-                    "out": {
-                        "success": True,
-                        "observed_value": 4,
-                    },
-                },
-            ],
-        }
-    ]
+    # These examples will be shown in the public gallery.
+    # They will also be executed as unit tests for your Expectation.
+    examples = []
 
-    # This dictionary contains metadata for display in the public gallery
-    library_metadata = {
-        "maturity": "experimental",  # "experimental", "beta", or "production"
-        "tags": [  # Tags for this Expectation in the gallery
-            #         "experimental"
-        ],
-        "contributors": [  # Github handles for all contributors to this Expectation.
-            #         "@your_name_here", # Don't forget to add your github handle here!
-        ],
-        "package": "experimental_expectations",
-    }
+    # This is a tuple consisting of all Metrics necessary to evaluate the Expectation.
+    metric_dependencies = ("METRIC NAME GOES HERE",)
 
-    metric_dependencies = ("table.alphabetical_column_name_count",)
-    success_keys = ("user_input",)
+    # This a tuple of parameter names that can affect whether the Expectation evaluates to True or False.
+    success_keys = ()
 
-    default_kwarg_values = {
-        "user_input": None,
-        "result_format": "BASIC",
-        "include_config": True,
-        "catch_exceptions": False,
-        "meta": None,
-    }
+    # This dictionary contains default values for any parameters that should have default values.
+    default_kwarg_values = {}
 
     def validate_configuration(self, configuration: Optional[ExpectationConfiguration]):
         """
@@ -183,46 +110,24 @@ def validate_configuration(self, configuration: Optional[ExpectationConfiguratio
             True if the configuration has been validated successfully. Otherwise, raises an exception
         """
 
-        #     # Setting up a configuration
-        try:
-            assert "user_input" in configuration.kwargs, "user_input is required"
-            assert isinstance(
-                configuration.kwargs["user_input"], str
-            ), "user_input must be a string"
-        except AssertionError as e:
-            raise InvalidExpectationConfigurationError(str(e))
         super().validate_configuration(configuration)
+        if configuration is None:
+            configuration = self.configuration
+
+        # # Check other things in configuration.kwargs and raise Exceptions if needed
+        # try:
+        #     assert (
+        #         ...
+        #     ), "message"
+        #     assert (
+        #         ...
+        #     ), "message"
+        # except AssertionError as e:
+        #     raise InvalidExpectationConfigurationError(str(e))
+
         return True
 
-    @classmethod
-    @renderer(renderer_type="renderer.prescriptive")
-    @render_evaluation_parameter_string
-    def _prescriptive_renderer(
-        cls,
-        configuration=None,
-        result=None,
-        language=None,
-        runtime_configuration=None,
-        **kwargs
-    ):
-        runtime_configuration = runtime_configuration or {}
-        styling = runtime_configuration.get("styling")
-        params = substitute_none_for_missing(configuration.kwargs, ["value"])
-        template_str = "Must have exactly 4 columns."
-        return [
-            RenderedStringTemplateContent(
-                **{
-                    "content_block_type": "string_template",
-                    "string_template": {
-                        "template": template_str,
-                        "params": params,
-                        "styling": styling,
-                    },
-                }
-            )
-        ]
-
-    # This method will utilize the computed metric to validate that your Expectation about the Table is true
+    # This method performs a validation of your metrics against your success keys, returning a dict indicating the success or failure of the Expectation.
     def _validate(
         self,
         configuration: ExpectationConfiguration,
@@ -230,13 +135,16 @@ def _validate(
         runtime_configuration: dict = None,
         execution_engine: ExecutionEngine = None,
     ):
-        actual_column_count = metrics.get("table.alphabetical_column_name_count")
+        raise NotImplementedError
 
-        return {
-            "success": actual_column_count == 4,
-            "result": {"observed_value": actual_column_count},
-        }
+    # This object contains metadata for display in the public Gallery
+    library_metadata = {
+        "tags": [],  # Tags for this Expectation in the Gallery
+        "contributors": [  # Github handles for all contributors to this Expectation.
+            "@your_name_here",  # Don't forget to add your github handle here!
+        ],
+    }
 
 
 if __name__ == "__main__":
-    ExpectAlphabeticalColumnNameCountToEqual4().print_diagnostic_checklist()
+    ExpectTableToMeetSomeCriteria().print_diagnostic_checklist()


Test patch:
diff --git a/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_column_values_to_equal_three.py b/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_column_values_to_equal_three.py
--- a/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_column_values_to_equal_three.py
+++ b/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_column_values_to_equal_three.py
@@ -294,78 +294,76 @@ def _diagnostic_unexpected_statement_renderer(
                 )
             ]
 
-        @renderer(renderer_type="renderer.diagnostic.unexpected_table")
-        @render_evaluation_parameter_string
-        def _diagnostic_unexpected_table_renderer(
-            cls,
-            configuration: ExpectationConfiguration = None,
-            result: ExpectationValidationResult = None,
-            language: str = None,
-            runtime_configuration: dict = None,
-            **kwargs,
-        ):
-            try:
-                result_dict = result.result
-            except KeyError:
-                return None
+    @renderer(renderer_type="renderer.diagnostic.unexpected_table")
+    @render_evaluation_parameter_string
+    def _diagnostic_unexpected_table_renderer(
+        cls,
+        configuration: ExpectationConfiguration = None,
+        result: ExpectationValidationResult = None,
+        language: str = None,
+        runtime_configuration: dict = None,
+        **kwargs,
+    ):
+        try:
+            result_dict = result.result
+        except KeyError:
+            return None
 
-            if result_dict is None:
-                return None
+        if result_dict is None:
+            return None
 
-            if not result_dict.get("partial_unexpected_list") and not result_dict.get(
-                "partial_unexpected_counts"
-            ):
-                return None
-
-            table_rows = []
-
-            if result_dict.get("partial_unexpected_counts"):
-                total_count = 0
-                for unexpected_count_dict in result_dict.get(
-                    "partial_unexpected_counts"
-                ):
-                    value = unexpected_count_dict.get("value")
-                    count = unexpected_count_dict.get("count")
-                    total_count += count
-                    if value is not None and value != "":
-                        table_rows.append([value, count])
-                    elif value == "":
-                        table_rows.append(["EMPTY", count])
-                    else:
-                        table_rows.append(["null", count])
-
-                if total_count == result_dict.get("unexpected_count"):
-                    header_row = ["Unexpected Value", "Count"]
+        if not result_dict.get("partial_unexpected_list") and not result_dict.get(
+            "partial_unexpected_counts"
+        ):
+            return None
+
+        table_rows = []
+
+        if result_dict.get("partial_unexpected_counts"):
+            total_count = 0
+            for unexpected_count_dict in result_dict.get("partial_unexpected_counts"):
+                value = unexpected_count_dict.get("value")
+                count = unexpected_count_dict.get("count")
+                total_count += count
+                if value is not None and value != "":
+                    table_rows.append([value, count])
+                elif value == "":
+                    table_rows.append(["EMPTY", count])
                 else:
-                    header_row = ["Sampled Unexpected Values"]
-                    table_rows = [[row[0]] for row in table_rows]
+                    table_rows.append(["null", count])
 
+            if total_count == result_dict.get("unexpected_count"):
+                header_row = ["Unexpected Value", "Count"]
             else:
                 header_row = ["Sampled Unexpected Values"]
-                sampled_values_set = set()
-                for unexpected_value in result_dict.get("partial_unexpected_list"):
-                    if unexpected_value:
-                        string_unexpected_value = str(unexpected_value)
-                    elif unexpected_value == "":
-                        string_unexpected_value = "EMPTY"
-                    else:
-                        string_unexpected_value = "null"
-                    if string_unexpected_value not in sampled_values_set:
-                        table_rows.append([unexpected_value])
-                        sampled_values_set.add(string_unexpected_value)
-
-            unexpected_table_content_block = RenderedTableContent(
-                **{
-                    "content_block_type": "table",
-                    "table": table_rows,
-                    "header_row": header_row,
-                    "styling": {
-                        "body": {"classes": ["table-bordered", "table-sm", "mt-3"]}
-                    },
-                }
-            )
+                table_rows = [[row[0]] for row in table_rows]
+
+        else:
+            header_row = ["Sampled Unexpected Values"]
+            sampled_values_set = set()
+            for unexpected_value in result_dict.get("partial_unexpected_list"):
+                if unexpected_value:
+                    string_unexpected_value = str(unexpected_value)
+                elif unexpected_value == "":
+                    string_unexpected_value = "EMPTY"
+                else:
+                    string_unexpected_value = "null"
+                if string_unexpected_value not in sampled_values_set:
+                    table_rows.append([unexpected_value])
+                    sampled_values_set.add(string_unexpected_value)
+
+        unexpected_table_content_block = RenderedTableContent(
+            **{
+                "content_block_type": "table",
+                "table": table_rows,
+                "header_row": header_row,
+                "styling": {
+                    "body": {"classes": ["table-bordered", "table-sm", "mt-3"]}
+                },
+            }
+        )
 
-            return unexpected_table_content_block
+        return unexpected_table_content_block
 
     # This dictionary contains metadata for display in the public gallery
     library_metadata = {
diff --git a/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_table_columns_to_be_unique.py b/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_table_columns_to_be_unique.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/docusaurus/expectations/creating_custom_expectations/expect_table_columns_to_be_unique.py
@@ -0,0 +1,226 @@
+"""
+This is a template for creating custom TableExpectations.
+For detailed instructions on how to use it, please see:
+    https://docs.greatexpectations.io/docs/guides/expectations/creating_custom_expectations/how_to_create_custom_table_expectations
+"""
+
+from typing import Dict, Optional
+
+import pandas as pd
+
+from great_expectations.core.expectation_configuration import ExpectationConfiguration
+from great_expectations.exceptions import InvalidExpectationConfigurationError
+from great_expectations.execution_engine import (
+    ExecutionEngine,
+    PandasExecutionEngine,
+    SparkDFExecutionEngine,
+    SqlAlchemyExecutionEngine,
+)
+from great_expectations.expectations.expectation import TableExpectation
+from great_expectations.expectations.metrics.metric_provider import (
+    MetricConfiguration,
+    MetricDomainTypes,
+    metric_value,
+)
+from great_expectations.expectations.metrics.table_metric_provider import (
+    TableMetricProvider,
+)
+
+
+# <snippet>
+# This class defines a Metric to support your Expectation.
+class TableColumnsUnique(TableMetricProvider):
+
+    # This is the id string that will be used to reference your Metric.
+    metric_name = "table.columns.unique"
+
+    # This method implements the core logic for the PandasExecutionEngine
+    @metric_value(engine=PandasExecutionEngine)
+    def _pandas(
+        cls,
+        execution_engine,
+        metric_domain_kwargs,
+        metric_value_kwargs,
+        metrics,
+        runtime_configuration,
+    ):
+        df, _, _ = execution_engine.get_compute_domain(
+            metric_domain_kwargs, domain_type=MetricDomainTypes.TABLE
+        )
+
+        unique_columns = set(df.T.drop_duplicates().T.columns)
+
+        return unique_columns
+
+    @metric_value(engine=SqlAlchemyExecutionEngine)
+    def _sqlalchemy(
+        cls,
+        execution_engine,
+        metric_domain_kwargs,
+        metric_value_kwargs,
+        metrics,
+        runtime_configuration,
+    ):
+        raise NotImplementedError
+
+    @metric_value(engine=SparkDFExecutionEngine)
+    def _spark(
+        cls,
+        execution_engine,
+        metric_domain_kwargs,
+        metric_value_kwargs,
+        metrics,
+        runtime_configuration,
+    ):
+        raise NotImplementedError
+
+    @classmethod
+    def _get_evaluation_dependencies(
+        cls,
+        metric: MetricConfiguration,
+        configuration: Optional[ExpectationConfiguration] = None,
+        execution_engine: Optional[ExecutionEngine] = None,
+        runtime_configuration: Optional[dict] = None,
+    ):
+        return {
+            "table.columns": MetricConfiguration(
+                "table.columns", metric.metric_domain_kwargs
+            ),
+        }
+
+
+# </snippet>
+# <snippet>
+class ExpectTableColumnsToBeUnique(TableExpectation):
+    """Expect table to contain columns with unique contents."""
+
+    # These examples will be shown in the public gallery.
+    # They will also be executed as unit tests for your Expectation.
+    examples = [
+        {
+            "data": {
+                "col1": [1, 2, 3, 4, 5],
+                "col2": [2, 3, 4, 5, 6],
+                "col3": [3, 4, 5, 6, 7],
+            },
+            "tests": [
+                {
+                    "title": "strict_positive_test",
+                    "exact_match_out": False,
+                    "include_in_gallery": True,
+                    "in": {"strict": True},
+                    "out": {"success": True},
+                }
+            ],
+        },
+        {
+            "data": {
+                "col1": [1, 2, 3, 4, 5],
+                "col2": [1, 2, 3, 4, 5],
+                "col3": [3, 4, 5, 6, 7],
+            },
+            "tests": [
+                {
+                    "title": "loose_positive_test",
+                    "exact_match_out": False,
+                    "include_in_gallery": True,
+                    "in": {"strict": False},
+                    "out": {"success": True},
+                },
+                {
+                    "title": "strict_negative_test",
+                    "exact_match_out": False,
+                    "include_in_gallery": True,
+                    "in": {"strict": True},
+                    "out": {"success": False},
+                },
+            ],
+        },
+    ]
+
+    # This is a tuple consisting of all Metrics necessary to evaluate the Expectation.
+    metric_dependencies = ("table.columns.unique", "table.columns")
+
+    # This a tuple of parameter names that can affect whether the Expectation evaluates to True or False.
+    success_keys = ("strict",)
+
+    # This dictionary contains default values for any parameters that should have default values.
+    default_kwarg_values = {"strict": True}
+
+    def validate_configuration(self, configuration: Optional[ExpectationConfiguration]):
+        """
+        Validates that a configuration has been set, and sets a configuration if it has yet to be set. Ensures that
+        necessary configuration arguments have been provided for the validation of the expectation.
+
+        Args:
+            configuration (OPTIONAL[ExpectationConfiguration]): \
+                An optional Expectation Configuration entry that will be used to configure the expectation
+        Returns:
+            True if the configuration has been validated successfully. Otherwise, raises an exception
+        """
+
+        super().validate_configuration(configuration)
+        if configuration is None:
+            configuration = self.configuration
+
+        strict = configuration.kwargs.get("strict")
+
+        # Check other things in configuration.kwargs and raise Exceptions if needed
+        try:
+            assert (
+                isinstance(strict, bool) or strict is None
+            ), "strict must be a boolean value"
+        except AssertionError as e:
+            raise InvalidExpectationConfigurationError(str(e))
+
+        return True
+
+    # This method performs a validation of your metrics against your success keys, returning a dict indicating the success or failure of the Expectation.
+    def _validate(
+        self,
+        configuration: ExpectationConfiguration,
+        metrics: Dict,
+        runtime_configuration: dict = None,
+        execution_engine: ExecutionEngine = None,
+    ):
+        unique_columns = metrics.get("table.columns.unique")
+        table_columns = metrics.get("table.columns")
+        strict = configuration.kwargs.get("strict")
+
+        duplicate_columns = unique_columns.symmetric_difference(table_columns)
+
+        if strict is True:
+            success = len(duplicate_columns) == 0
+        else:
+            success = len(duplicate_columns) < len(table_columns)
+
+        return {
+            "success": success,
+            "result": {"observed_value": {"duplicate_columns": duplicate_columns}},
+        }
+
+    # This dictionary contains metadata for display in the public gallery
+    library_metadata = {
+        "tags": ["uniqueness"],
+        "contributors": ["@joegargery"],
+    }
+
+
+# </snippet>
+if __name__ == "__main__":
+    ExpectTableColumnsToBeUnique().print_diagnostic_checklist()
+
+# Note to users: code below this line is only for integration testing -- ignore!
+
+diagnostics = ExpectTableColumnsToBeUnique().run_diagnostics()
+
+for check in diagnostics["tests"]:
+    assert check["test_passed"] is True
+    assert check["error_message"] is None
+    assert check["stack_trace"] is None
+
+for check in diagnostics["errors"]:
+    assert check is None
+
+for check in diagnostics["maturity_checklist"]["experimental"]:
+    assert check["passed"] is True