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/rule_based_profiler/domain_builder/categorical_column_domain_builder.py b/great_expectations/rule_based_profiler/domain_builder/categorical_column_domain_builder.py
--- a/great_expectations/rule_based_profiler/domain_builder/categorical_column_domain_builder.py
+++ b/great_expectations/rule_based_profiler/domain_builder/categorical_column_domain_builder.py
@@ -49,7 +49,7 @@ def __init__(
         allowed_semantic_types_passthrough: Optional[
             Union[str, SemanticDomainTypes, List[Union[str, SemanticDomainTypes]]]
         ] = None,
-        limit_mode: Optional[Union[CardinalityLimitMode, str]] = None,
+        limit_mode: Optional[Union[str, CardinalityLimitMode, dict]] = None,
         max_unique_values: Optional[Union[str, int]] = None,
         max_proportion_unique: Optional[Union[str, float]] = None,
         data_context: Optional["BaseDataContext"] = None,  # noqa: F821
@@ -149,7 +149,7 @@ def allowed_semantic_types_passthrough(
         self._allowed_semantic_types_passthrough = value
 
     @property
-    def limit_mode(self) -> Optional[Union[CardinalityLimitMode, str]]:
+    def limit_mode(self) -> Optional[Union[str, CardinalityLimitMode, dict]]:
         return self._limit_mode
 
     @property
@@ -190,7 +190,7 @@ def _get_domains(
 
         # Obtain limit_mode from "rule state" (i.e., variables and parameters); from instance variable otherwise.
         limit_mode: Optional[
-            Union[CardinalityLimitMode, str]
+            Union[str, CardinalityLimitMode, dict]
         ] = get_parameter_value_and_validate_return_type(
             domain=None,
             parameter_reference=self.limit_mode,
diff --git a/great_expectations/rule_based_profiler/helpers/cardinality_checker.py b/great_expectations/rule_based_profiler/helpers/cardinality_checker.py
--- a/great_expectations/rule_based_profiler/helpers/cardinality_checker.py
+++ b/great_expectations/rule_based_profiler/helpers/cardinality_checker.py
@@ -114,7 +114,7 @@ class CardinalityChecker:
 
     def __init__(
         self,
-        limit_mode: Optional[Union[CardinalityLimitMode, str]] = None,
+        limit_mode: Optional[Union[str, CardinalityLimitMode, dict]] = None,
         max_unique_values: Optional[int] = None,
         max_proportion_unique: Optional[float] = None,
     ) -> None:
@@ -161,7 +161,7 @@ def _validate_metric_value(metric_value: float) -> None:
 
     @staticmethod
     def _convert_to_cardinality_mode(
-        limit_mode: Optional[Union[CardinalityLimitMode, str]] = None,
+        limit_mode: Optional[Union[str, CardinalityLimitMode, dict]] = None,
         max_unique_values: Optional[int] = None,
         max_proportion_unique: Optional[float] = None,
     ) -> Union[AbsoluteCardinalityLimit, RelativeCardinalityLimit]:
@@ -179,13 +179,43 @@ def _convert_to_cardinality_mode(
                     raise ProfilerConfigurationError(
                         f"Please specify a supported cardinality mode. Supported cardinality modes are {[member.name for member in CardinalityLimitMode]}"
                     )
+            elif isinstance(limit_mode, dict):
+                validate_input_parameters(
+                    limit_mode=limit_mode.get("name"),
+                    max_unique_values=limit_mode.get("max_unique_values"),
+                    max_proportion_unique=limit_mode.get("max_proportion_unique"),
+                    required_num_supplied_params=2,
+                )
+                try:
+                    return AbsoluteCardinalityLimit(
+                        name=limit_mode["name"],
+                        max_unique_values=limit_mode["max_unique_values"],
+                        metric_name_defining_limit=limit_mode[
+                            "metric_name_defining_limit"
+                        ],
+                    )
+                except (KeyError, ValueError):
+                    try:
+                        return RelativeCardinalityLimit(
+                            name=limit_mode["name"],
+                            max_proportion_unique=limit_mode["max_proportion_unique"],
+                            metric_name_defining_limit=limit_mode[
+                                "metric_name_defining_limit"
+                            ],
+                        )
+                    except (KeyError, ValueError):
+                        raise ProfilerConfigurationError(
+                            f"Please specify a supported cardinality mode.  Supported cardinality modes are {[member.name for member in CardinalityLimitMode]}"
+                        )
             else:
                 return limit_mode.value
+
         if max_unique_values is not None:
             return AbsoluteCardinalityLimit(
                 name=f"CUSTOM_ABS_{max_unique_values}",
                 max_unique_values=max_unique_values,
             )
+
         if max_proportion_unique is not None:
             return RelativeCardinalityLimit(
                 name=f"CUSTOM_REL_{max_proportion_unique}",
@@ -194,9 +224,10 @@ def _convert_to_cardinality_mode(
 
 
 def validate_input_parameters(
-    limit_mode: Optional[Union[CardinalityLimitMode, str]] = None,
+    limit_mode: Optional[Union[str, CardinalityLimitMode, dict]] = None,
     max_unique_values: Optional[int] = None,
     max_proportion_unique: Optional[int] = None,
+    required_num_supplied_params: int = 1,
 ) -> None:
     num_supplied_params: int = sum(
         [
@@ -208,23 +239,31 @@ def validate_input_parameters(
             )
         ]
     )
-    if num_supplied_params != 1:
+    if num_supplied_params != required_num_supplied_params:
         raise ProfilerConfigurationError(
             f"Please pass ONE of the following parameters: limit_mode, max_unique_values, max_proportion_unique, you passed {num_supplied_params} parameters."
         )
 
     if limit_mode is not None:
-        if not (
-            isinstance(limit_mode, CardinalityLimitMode) or isinstance(limit_mode, str)
-        ):
+        if not isinstance(limit_mode, (str, CardinalityLimitMode, dict)):
             raise ProfilerConfigurationError(
                 f"Please specify a supported cardinality limit type, supported classes are {','.join(CardinalityChecker.SUPPORTED_LIMIT_MODE_CLASS_NAMES)} and supported strings are {','.join(CardinalityChecker.SUPPORTED_CARDINALITY_LIMIT_MODE_STRINGS)}"
             )
+
+        if required_num_supplied_params == 2:
+            try:
+                return CardinalityLimitMode[limit_mode.upper()].value
+            except KeyError:
+                raise ProfilerConfigurationError(
+                    f"Please specify a supported cardinality mode. Supported cardinality modes are {[member.name for member in CardinalityLimitMode]}"
+                )
+
     if max_unique_values is not None:
         if not isinstance(max_unique_values, int):
             raise ProfilerConfigurationError(
                 f"Please specify an int, you specified a {type(max_unique_values)}"
             )
+
     if max_proportion_unique is not None:
         if not isinstance(max_proportion_unique, (float, int)):
             raise ProfilerConfigurationError(


Test patch:
diff --git a/tests/rule_based_profiler/domain_builder/test_categorical_column_domain_builder.py b/tests/rule_based_profiler/domain_builder/test_categorical_column_domain_builder.py
--- a/tests/rule_based_profiler/domain_builder/test_categorical_column_domain_builder.py
+++ b/tests/rule_based_profiler/domain_builder/test_categorical_column_domain_builder.py
@@ -10,6 +10,9 @@
 from great_expectations.rule_based_profiler.domain_builder.categorical_column_domain_builder import (
     CategoricalColumnDomainBuilder,
 )
+from great_expectations.rule_based_profiler.helpers.cardinality_checker import (
+    CardinalityLimitMode,
+)
 from great_expectations.rule_based_profiler.types import (
     INFERRED_SEMANTIC_TYPE_KEY,
     Domain,
@@ -17,7 +20,7 @@
 )
 
 
-def test_instantiate_with_cardinality_limit_modes(
+def test_instantiate_with_cardinality_limit_modes_from_class_variable(
     alice_columnar_table_single_batch_context,
 ):
     data_context: DataContext = alice_columnar_table_single_batch_context
@@ -37,6 +40,70 @@ def test_instantiate_with_cardinality_limit_modes(
     domain_builder.get_domains(rule_name="my_rule", batch_request=batch_request)
 
 
+def test_instantiate_with_cardinality_limit_modes_from_enum(
+    alice_columnar_table_single_batch_context,
+):
+    data_context: DataContext = alice_columnar_table_single_batch_context
+
+    batch_request: BatchRequest = BatchRequest(
+        datasource_name="alice_columnar_table_single_batch_datasource",
+        data_connector_name="alice_columnar_table_single_batch_data_connector",
+        data_asset_name="alice_columnar_table_single_batch_data_asset",
+    )
+
+    domain_builder: DomainBuilder = CategoricalColumnDomainBuilder(
+        exclude_column_name_suffixes="_id",
+        limit_mode=CardinalityLimitMode.VERY_FEW,
+        data_context=data_context,
+    )
+
+    domain_builder.get_domains(rule_name="my_rule", batch_request=batch_request)
+
+
+def test_instantiate_with_cardinality_limit_modes_from_string(
+    alice_columnar_table_single_batch_context,
+):
+    data_context: DataContext = alice_columnar_table_single_batch_context
+
+    batch_request: BatchRequest = BatchRequest(
+        datasource_name="alice_columnar_table_single_batch_datasource",
+        data_connector_name="alice_columnar_table_single_batch_data_connector",
+        data_asset_name="alice_columnar_table_single_batch_data_asset",
+    )
+
+    domain_builder: DomainBuilder = CategoricalColumnDomainBuilder(
+        exclude_column_name_suffixes="_id",
+        limit_mode="very_few",
+        data_context=data_context,
+    )
+
+    domain_builder.get_domains(rule_name="my_rule", batch_request=batch_request)
+
+
+def test_instantiate_with_cardinality_limit_modes_from_dictionary(
+    alice_columnar_table_single_batch_context,
+):
+    data_context: DataContext = alice_columnar_table_single_batch_context
+
+    batch_request: BatchRequest = BatchRequest(
+        datasource_name="alice_columnar_table_single_batch_datasource",
+        data_connector_name="alice_columnar_table_single_batch_data_connector",
+        data_asset_name="alice_columnar_table_single_batch_data_asset",
+    )
+
+    domain_builder: DomainBuilder = CategoricalColumnDomainBuilder(
+        exclude_column_name_suffixes="_id",
+        limit_mode={
+            "name": "very_few",
+            "max_proportion_unique": 10,
+            "metric_name_defining_limit": "column.distinct_values.count",
+        },
+        data_context=data_context,
+    )
+
+    domain_builder.get_domains(rule_name="my_rule", batch_request=batch_request)
+
+
 def test_single_batch_very_few_cardinality(alice_columnar_table_single_batch_context):
     data_context: DataContext = alice_columnar_table_single_batch_context
 
@@ -126,7 +193,7 @@ def test_single_batch_one_cardinality(alice_columnar_table_single_batch_context)
     assert domains == alice_all_column_domains
 
 
-def test_unsupported_cardinality_limit(
+def test_unsupported_cardinality_limit_from_string(
     alice_columnar_table_single_batch_context,
 ):
     data_context: DataContext = alice_columnar_table_single_batch_context
@@ -149,6 +216,33 @@ def test_unsupported_cardinality_limit(
     assert "MANY" in str(excinfo.value)
 
 
+def test_unsupported_cardinality_limit_from_dictionary(
+    alice_columnar_table_single_batch_context,
+):
+    data_context: DataContext = alice_columnar_table_single_batch_context
+
+    batch_request: BatchRequest = BatchRequest(
+        datasource_name="alice_columnar_table_single_batch_datasource",
+        data_connector_name="alice_columnar_table_single_batch_data_connector",
+        data_asset_name="alice_columnar_table_single_batch_data_asset",
+    )
+
+    with pytest.raises(ProfilerConfigurationError) as excinfo:
+        # noinspection PyUnusedLocal,PyArgumentList
+        domains: List[Domain] = CategoricalColumnDomainBuilder(
+            limit_mode={
+                "name": "&*#$&INVALID&*#$*&",
+                "max_proportion_unique": 10,
+                "metric_name_defining_limit": "column.distinct_values.count",
+            },
+            data_context=data_context,
+        ).get_domains(rule_name="my_rule", batch_request=batch_request)
+
+    assert "specify a supported cardinality mode" in str(excinfo.value)
+    assert "REL_1" in str(excinfo.value)
+    assert "MANY" in str(excinfo.value)
+
+
 def test_unspecified_cardinality_limit(
     alice_columnar_table_single_batch_context,
 ):