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/__init__.py b/great_expectations/rule_based_profiler/__init__.py
--- a/great_expectations/rule_based_profiler/__init__.py
+++ b/great_expectations/rule_based_profiler/__init__.py
@@ -1 +1 @@
-from .rule_based_profiler import RuleBasedProfiler
+from .rule_based_profiler import BaseRuleBasedProfiler, RuleBasedProfiler
diff --git a/great_expectations/rule_based_profiler/config/base.py b/great_expectations/rule_based_profiler/config/base.py
--- a/great_expectations/rule_based_profiler/config/base.py
+++ b/great_expectations/rule_based_profiler/config/base.py
@@ -102,7 +102,7 @@ def remove_nulls_and_keep_unknowns(
         return cleaned_output
 
 
-class DomainBuilderConfig(DictDot):
+class DomainBuilderConfig(SerializableDictDot):
     def __init__(
         self,
         class_name: str,
@@ -121,6 +121,49 @@ def __init__(
                 self.__class__.__name__,
             )
 
+    def to_json_dict(self) -> dict:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of "SerializableDictDot.to_json_dict() occurs frequently and should ideally serve as the
+        reference implementation in the "SerializableDictDot" class itself.  However, the circular import dependencies,
+        due to the location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules
+        make this refactoring infeasible at the present time.
+        """
+        dict_obj: dict = self.to_dict()
+        serializeable_dict: dict = convert_to_json_serializable(data=dict_obj)
+        return serializeable_dict
+
+    def __repr__(self) -> str:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of a custom "__repr__()" occurs frequently and should ideally serve as the reference
+        implementation in the "SerializableDictDot" class.  However, the circular import dependencies, due to the
+        location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules make this
+        refactoring infeasible at the present time.
+        """
+        json_dict: dict = self.to_json_dict()
+        deep_filter_properties_iterable(
+            properties=json_dict,
+            inplace=True,
+        )
+
+        keys: List[str] = sorted(list(json_dict.keys()))
+
+        key: str
+        sorted_json_dict: dict = {key: json_dict[key] for key in keys}
+
+        return json.dumps(sorted_json_dict, indent=2)
+
+    def __str__(self) -> str:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of a custom "__str__()" occurs frequently and should ideally serve as the reference
+        implementation in the "SerializableDictDot" class.  However, the circular import dependencies, due to the
+        location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules make this
+        refactoring infeasible at the present time.
+        """
+        return self.__repr__()
+
 
 class DomainBuilderConfigSchema(NotNullSchema):
     class Meta:
@@ -139,7 +182,7 @@ class Meta:
     )
 
 
-class ParameterBuilderConfig(DictDot):
+class ParameterBuilderConfig(SerializableDictDot):
     def __init__(
         self,
         name: str,
@@ -167,6 +210,49 @@ def __init__(
                 self.__class__.__name__,
             )
 
+    def to_json_dict(self) -> dict:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of "SerializableDictDot.to_json_dict() occurs frequently and should ideally serve as the
+        reference implementation in the "SerializableDictDot" class itself.  However, the circular import dependencies,
+        due to the location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules
+        make this refactoring infeasible at the present time.
+        """
+        dict_obj: dict = self.to_dict()
+        serializeable_dict: dict = convert_to_json_serializable(data=dict_obj)
+        return serializeable_dict
+
+    def __repr__(self) -> str:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of a custom "__repr__()" occurs frequently and should ideally serve as the reference
+        implementation in the "SerializableDictDot" class.  However, the circular import dependencies, due to the
+        location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules make this
+        refactoring infeasible at the present time.
+        """
+        json_dict: dict = self.to_json_dict()
+        deep_filter_properties_iterable(
+            properties=json_dict,
+            inplace=True,
+        )
+
+        keys: List[str] = sorted(list(json_dict.keys()))
+
+        key: str
+        sorted_json_dict: dict = {key: json_dict[key] for key in keys}
+
+        return json.dumps(sorted_json_dict, indent=2)
+
+    def __str__(self) -> str:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of a custom "__str__()" occurs frequently and should ideally serve as the reference
+        implementation in the "SerializableDictDot" class.  However, the circular import dependencies, due to the
+        location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules make this
+        refactoring infeasible at the present time.
+        """
+        return self.__repr__()
+
 
 class ParameterBuilderConfigSchema(NotNullSchema):
     class Meta:
@@ -203,7 +289,7 @@ class Meta:
     )
 
 
-class ExpectationConfigurationBuilderConfig(DictDot):
+class ExpectationConfigurationBuilderConfig(SerializableDictDot):
     def __init__(
         self,
         expectation_type: str,
@@ -231,6 +317,49 @@ def __init__(
                 self.__class__.__name__,
             )
 
+    def to_json_dict(self) -> dict:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of "SerializableDictDot.to_json_dict() occurs frequently and should ideally serve as the
+        reference implementation in the "SerializableDictDot" class itself.  However, the circular import dependencies,
+        due to the location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules
+        make this refactoring infeasible at the present time.
+        """
+        dict_obj: dict = self.to_dict()
+        serializeable_dict: dict = convert_to_json_serializable(data=dict_obj)
+        return serializeable_dict
+
+    def __repr__(self) -> str:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of a custom "__repr__()" occurs frequently and should ideally serve as the reference
+        implementation in the "SerializableDictDot" class.  However, the circular import dependencies, due to the
+        location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules make this
+        refactoring infeasible at the present time.
+        """
+        json_dict: dict = self.to_json_dict()
+        deep_filter_properties_iterable(
+            properties=json_dict,
+            inplace=True,
+        )
+
+        keys: List[str] = sorted(list(json_dict.keys()))
+
+        key: str
+        sorted_json_dict: dict = {key: json_dict[key] for key in keys}
+
+        return json.dumps(sorted_json_dict, indent=2)
+
+    def __str__(self) -> str:
+        """
+        # TODO: <Alex>2/4/2022</Alex>
+        This implementation of a custom "__str__()" occurs frequently and should ideally serve as the reference
+        implementation in the "SerializableDictDot" class.  However, the circular import dependencies, due to the
+        location of the "great_expectations/types/__init__.py" and "great_expectations/core/util.py" modules make this
+        refactoring infeasible at the present time.
+        """
+        return self.__repr__()
+
 
 class ExpectationConfigurationBuilderConfigSchema(NotNullSchema):
     class Meta:
diff --git a/great_expectations/validator/validator.py b/great_expectations/validator/validator.py
--- a/great_expectations/validator/validator.py
+++ b/great_expectations/validator/validator.py
@@ -55,13 +55,13 @@
 )
 from great_expectations.rule_based_profiler.helpers.configuration_reconciliation import (
     DEFAULT_RECONCILATION_DIRECTIVES,
+    ReconciliationDirectives,
+    ReconciliationStrategy,
 )
 from great_expectations.rule_based_profiler.parameter_builder import ParameterBuilder
 from great_expectations.rule_based_profiler.rule import Rule
 from great_expectations.rule_based_profiler.rule_based_profiler import (
     BaseRuleBasedProfiler,
-    ReconciliationDirectives,
-    ReconciliationStrategy,
 )
 from great_expectations.rule_based_profiler.types import ParameterContainer
 from great_expectations.types import ClassConfig


Test patch:
diff --git a/tests/rule_based_profiler/test_rule_based_profiler.py b/tests/rule_based_profiler/test_rule_based_profiler.py
--- a/tests/rule_based_profiler/test_rule_based_profiler.py
+++ b/tests/rule_based_profiler/test_rule_based_profiler.py
@@ -14,13 +14,27 @@
     GeCloudIdentifier,
 )
 from great_expectations.exceptions.exceptions import InvalidConfigError
-from great_expectations.rule_based_profiler import RuleBasedProfiler
-from great_expectations.rule_based_profiler.config import RuleBasedProfilerConfig
-from great_expectations.rule_based_profiler.rule import Rule
-from great_expectations.rule_based_profiler.rule_based_profiler import (
+from great_expectations.rule_based_profiler import (
+    BaseRuleBasedProfiler,
+    RuleBasedProfiler,
+)
+from great_expectations.rule_based_profiler.config import (
+    DomainBuilderConfig,
+    ParameterBuilderConfig,
+    RuleBasedProfilerConfig,
+)
+from great_expectations.rule_based_profiler.domain_builder import TableDomainBuilder
+from great_expectations.rule_based_profiler.expectation_configuration_builder import (
+    DefaultExpectationConfigurationBuilder,
+)
+from great_expectations.rule_based_profiler.helpers.configuration_reconciliation import (
     ReconciliationDirectives,
     ReconciliationStrategy,
 )
+from great_expectations.rule_based_profiler.parameter_builder import (
+    MetricMultiBatchParameterBuilder,
+)
+from great_expectations.rule_based_profiler.rule import Rule
 from great_expectations.rule_based_profiler.types import ParameterContainer
 from great_expectations.util import deep_filter_properties_iterable
 
@@ -1050,6 +1064,67 @@ def test_get_profiler_with_too_many_args_raises_error(
     assert "either name or ge_cloud_id" in str(e.value)
 
 
+@mock.patch("great_expectations.data_context.data_context.BaseDataContext")
+def test_serialize_profiler_config(
+    mock_data_context: mock.MagicMock,
+    profiler_config_with_placeholder_args: RuleBasedProfilerConfig,
+):
+    profiler: BaseRuleBasedProfiler = BaseRuleBasedProfiler(
+        profiler_config=profiler_config_with_placeholder_args,
+        data_context=mock_data_context,
+    )
+    assert profiler.config == profiler_config_with_placeholder_args
+    assert len(profiler.rules) == 1
+    assert isinstance(profiler.rules[0].domain_builder, TableDomainBuilder)
+    assert DomainBuilderConfig(
+        **profiler.rules[0].domain_builder.to_json_dict()
+    ).to_json_dict() == {
+        "module_name": "great_expectations.rule_based_profiler.domain_builder.table_domain_builder",
+        "class_name": "TableDomainBuilder",
+    }
+    assert isinstance(
+        profiler.rules[0].parameter_builders[0], MetricMultiBatchParameterBuilder
+    )
+    assert ParameterBuilderConfig(
+        **profiler.rules[0].parameter_builders[0].to_json_dict()
+    ).to_json_dict() == {
+        "module_name": "great_expectations.rule_based_profiler.parameter_builder.metric_multi_batch_parameter_builder",
+        "class_name": "MetricMultiBatchParameterBuilder",
+        "name": "my_parameter",
+        "metric_name": "my_metric",
+        "metric_domain_kwargs": None,
+        "metric_value_kwargs": None,
+        "enforce_numeric_metric": False,
+        "replace_nan_with_zero": False,
+        "reduce_scalar_metric": True,
+        "evaluation_parameter_builder_configs": None,
+        "json_serialize": True,
+    }
+    assert isinstance(
+        profiler.rules[0].expectation_configuration_builders[0],
+        DefaultExpectationConfigurationBuilder,
+    )
+    assert DefaultExpectationConfigurationBuilder(
+        **profiler.rules[0].expectation_configuration_builders[0].to_json_dict()
+    ).to_json_dict() == {
+        "module_name": "great_expectations.rule_based_profiler.expectation_configuration_builder.default_expectation_configuration_builder",
+        "class_name": "DefaultExpectationConfigurationBuilder",
+        "expectation_type": "expect_column_pair_values_A_to_be_greater_than_B",
+        "validation_parameter_builder_configs": None,
+        "column_A": "$domain.domain_kwargs.column_A",
+        "column_B": "$domain.domain_kwargs.column_B",
+        "condition": None,
+        "my_arg": "$parameter.my_parameter.value[0]",
+        "my_other_arg": "$parameter.my_parameter.value[1]",
+        "meta": {
+            "details": {
+                "my_parameter_estimator": "$parameter.my_parameter.details",
+                "note": "Important remarks about estimation algorithm.",
+            },
+        },
+    }
+
+
 @mock.patch("great_expectations.data_context.data_context.BaseDataContext")
 def test_add_profiler(
     mock_data_context: mock.MagicMock,
@@ -1057,7 +1132,7 @@ def test_add_profiler(
     profiler_config_with_placeholder_args: RuleBasedProfilerConfig,
 ):
     mock_data_context.ge_cloud_mode.return_value = False
-    profiler = RuleBasedProfiler.add_profiler(
+    profiler: RuleBasedProfiler = RuleBasedProfiler.add_profiler(
         profiler_config_with_placeholder_args,
         data_context=mock_data_context,
         profiler_store=mock_data_context.profiler_store,
@@ -1078,7 +1153,7 @@ def test_add_profiler_ge_cloud_mode(
     profiler_config_with_placeholder_args: RuleBasedProfilerConfig,
 ):
     mock_data_context.ge_cloud_mode.return_value = True
-    profiler = RuleBasedProfiler.add_profiler(
+    profiler: RuleBasedProfiler = RuleBasedProfiler.add_profiler(
         profiler_config_with_placeholder_args,
         data_context=mock_data_context,
         profiler_store=mock_data_context.profiler_store,
@@ -1147,7 +1222,7 @@ def test_get_profiler(
         "great_expectations.data_context.store.profiler_store.ProfilerStore.get",
         return_value=profiler_config_with_placeholder_args,
     ):
-        profiler = RuleBasedProfiler.get_profiler(
+        profiler: RuleBasedProfiler = RuleBasedProfiler.get_profiler(
             data_context=mock_data_context,
             profiler_store=populated_profiler_store,
             name="my_profiler",
@@ -1343,7 +1418,6 @@ def test_add_rule_add_second_rule(
 @mock.patch("great_expectations.data_context.data_context.BaseDataContext")
 def test_add_rule_bad_rule(
     mock_data_context: mock.MagicMock,
-    sample_rule_dict: dict,
 ):
     profiler: RuleBasedProfiler = RuleBasedProfiler(
         name="my_rbp",
@@ -1390,6 +1464,7 @@ def test_run_with_conflicting_expectation_suite_args_raises_error(
 
     with pytest.raises(AssertionError) as e:
         profiler.run()
+        # noinspection PyUnusedLocal
         suite = profiler.get_expectation_suite(
             expectation_suite=suite, expectation_suite_name="my_expectation_suite"
         )