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/util.py b/great_expectations/util.py
--- a/great_expectations/util.py
+++ b/great_expectations/util.py
@@ -139,8 +139,8 @@ def underscore(word: str) -> str:
     return word.lower()
 
 
-def hyphen(input: str):
-    return input.replace("_", "-")
+def hyphen(txt: str):
+    return txt.replace("_", "-")
 
 
 def profile(func: Callable = None) -> Callable:
@@ -932,7 +932,7 @@ def lint_code(code: str) -> str:
 
 
 def filter_properties_dict(
-    properties: dict,
+    properties: Optional[dict] = None,
     keep_fields: Optional[Set[str]] = None,
     delete_fields: Optional[Set[str]] = None,
     clean_nulls: Optional[bool] = True,
@@ -963,11 +963,22 @@ def filter_properties_dict(
     if clean_falsy:
         clean_nulls = True
 
+    if properties is None:
+        properties = {}
+
+    if not isinstance(properties, dict):
+        raise ValueError(
+            f'Source "properties" must be a dictionary (illegal type "{str(type(properties))}" detected).'
+        )
+
     if not inplace:
         properties = copy.deepcopy(properties)
 
     keys_for_deletion: list = []
 
+    key: str
+    value: Any
+
     if keep_fields:
         keys_for_deletion.extend(
             [key for key, value in properties.items() if key not in keep_fields]
@@ -1029,6 +1040,47 @@ def filter_properties_dict(
     return properties
 
 
+def deep_filter_properties_dict(
+    properties: Optional[dict] = None,
+    keep_fields: Optional[Set[str]] = None,
+    delete_fields: Optional[Set[str]] = None,
+    clean_nulls: Optional[bool] = True,
+    clean_falsy: Optional[bool] = False,
+    keep_falsy_numerics: Optional[bool] = True,
+    inplace: Optional[bool] = False,
+) -> Optional[dict]:
+    key: str
+    value: Any
+    if isinstance(properties, dict):
+        if not inplace:
+            properties = copy.deepcopy(properties)
+
+        filter_properties_dict(
+            properties=properties,
+            keep_fields=keep_fields,
+            delete_fields=delete_fields,
+            clean_nulls=clean_nulls,
+            clean_falsy=clean_falsy,
+            keep_falsy_numerics=keep_falsy_numerics,
+            inplace=True,
+        )
+        for key, value in properties.items():
+            deep_filter_properties_dict(
+                properties=value,
+                keep_fields=keep_fields,
+                delete_fields=delete_fields,
+                clean_nulls=clean_nulls,
+                clean_falsy=clean_falsy,
+                keep_falsy_numerics=keep_falsy_numerics,
+                inplace=True,
+            )
+
+    if inplace:
+        return None
+
+    return properties
+
+
 def is_numeric(value: Any) -> bool:
     return value is not None and (is_int(value=value) or is_float(value=value))
 


Test patch:
diff --git a/tests/test_ge_utils.py b/tests/test_ge_utils.py
--- a/tests/test_ge_utils.py
+++ b/tests/test_ge_utils.py
@@ -7,6 +7,7 @@
 from great_expectations.core.util import nested_update
 from great_expectations.dataset.util import check_sql_engine_dialect
 from great_expectations.util import (
+    deep_filter_properties_dict,
     filter_properties_dict,
     get_currently_executing_function_call_arguments,
     hyphen,
@@ -291,7 +292,7 @@ def test_filter_properties_dict():
             clean_falsy=True,
         )
     d0_end: dict = filter_properties_dict(properties=d0_begin, clean_falsy=True)
-    d0_end_expected = copy.deepcopy(d0_begin)
+    d0_end_expected: dict = copy.deepcopy(d0_begin)
     d0_end_expected.pop("null")
     assert d0_end == d0_end_expected
 
@@ -300,7 +301,7 @@ def test_filter_properties_dict():
         properties=d1_begin,
         clean_nulls=False,
     )
-    d1_end_expected = copy.deepcopy(d1_begin)
+    d1_end_expected: dict = d1_begin
     assert d1_end == d1_end_expected
 
     d2_begin: dict = copy.deepcopy(source_dict)
@@ -309,7 +310,7 @@ def test_filter_properties_dict():
         clean_nulls=True,
         clean_falsy=False,
     )
-    d2_end_expected = copy.deepcopy(d2_begin)
+    d2_end_expected: dict = copy.deepcopy(d2_begin)
     d2_end_expected.pop("null")
     assert d2_end == d2_end_expected
 
@@ -321,7 +322,7 @@ def test_filter_properties_dict():
         },
         clean_falsy=True,
     )
-    d3_end_expected = {"null": None}
+    d3_end_expected: dict = {"null": None}
     assert d3_end == d3_end_expected
 
     d4_begin: dict = copy.deepcopy(source_dict)
@@ -330,7 +331,7 @@ def test_filter_properties_dict():
         clean_falsy=True,
         keep_falsy_numerics=False,
     )
-    d4_end_expected = copy.deepcopy(d4_begin)
+    d4_end_expected: dict = copy.deepcopy(d4_begin)
     d4_end_expected.pop("integer_zero")
     d4_end_expected.pop("null")
     assert d4_end == d4_end_expected
@@ -344,7 +345,7 @@ def test_filter_properties_dict():
         },
         clean_falsy=True,
     )
-    d5_end_expected = {
+    d5_end_expected: dict = {
         "integer_zero": 0,
         "scientific_notation_floating_point_number": 9.8e1,
     }
@@ -359,7 +360,7 @@ def test_filter_properties_dict():
         },
         clean_falsy=True,
     )
-    d6_end_expected = {"string": "xyz_0", "integer_one": 1}
+    d6_end_expected: dict = {"string": "xyz_0", "integer_one": 1}
     assert d6_end == d6_end_expected
 
     d7_begin: dict = copy.deepcopy(source_dict)
@@ -372,11 +373,120 @@ def test_filter_properties_dict():
         clean_falsy=True,
         inplace=True,
     )
-    d7_end = copy.deepcopy(d7_begin)
-    d7_end_expected = {"string": "xyz_0", "integer_one": 1}
+    d7_end: dict = d7_begin
+    d7_end_expected: dict = {"string": "xyz_0", "integer_one": 1}
     assert d7_end == d7_end_expected
 
 
+def test_deep_filter_properties_dict():
+    source_dict: dict = {
+        "integer_zero": 0,
+        "null": None,
+        "string": "xyz_0",
+        "integer_one": 1,
+        "scientific_notation_floating_point_number": 9.8e1,
+        "empty_top_level_dictionary": {},
+        "empty_top_level_list": [],
+        "empty_top_level_set": set(),
+        "non_empty_top_level_set": {
+            0,
+            1,
+            2,
+            "a",
+            "b",
+            "c",
+        },
+        "non_empty_top_level_dictionary": {
+            "empty_1st_level_list": [],
+            "empty_1st_level_set": set(),
+            "non_empty_1st_level_set": {
+                "empty_2nd_level_list": [],
+                "non_empty_2nd_level_list": [
+                    0,
+                    1,
+                    2,
+                    "a",
+                    "b",
+                    "c",
+                ],
+                "non_empty_2nd_level_dictionary": {
+                    "integer_zero": 0,
+                    "null": None,
+                    "string": "xyz_0",
+                    "integer_one": 1,
+                    "scientific_notation_floating_point_number": 9.8e1,
+                },
+                "empty_2nd_level_dictionary": {},
+            },
+        },
+    }
+
+    d0_begin: dict = copy.deepcopy(source_dict)
+    deep_filter_properties_dict(
+        properties=d0_begin,
+        clean_falsy=True,
+        inplace=True,
+    )
+    d0_end: dict = d0_begin
+    d0_end_expected: dict = {
+        "integer_zero": 0,
+        "string": "xyz_0",
+        "integer_one": 1,
+        "scientific_notation_floating_point_number": 98.0,
+        "non_empty_top_level_set": {
+            0,
+            1,
+            2,
+            "a",
+            "b",
+            "c",
+        },
+        "non_empty_top_level_dictionary": {
+            "non_empty_1st_level_set": {
+                "non_empty_2nd_level_list": [0, 1, 2, "a", "b", "c"],
+                "non_empty_2nd_level_dictionary": {
+                    "integer_zero": 0,
+                    "string": "xyz_0",
+                    "integer_one": 1,
+                    "scientific_notation_floating_point_number": 98.0,
+                },
+            }
+        },
+    }
+    assert d0_end == d0_end_expected
+
+    d1_begin: dict = copy.deepcopy(source_dict)
+    d1_end: dict = deep_filter_properties_dict(
+        properties=d1_begin,
+        clean_falsy=True,
+        keep_falsy_numerics=False,
+    )
+    d1_end_expected: dict = {
+        "string": "xyz_0",
+        "integer_one": 1,
+        "scientific_notation_floating_point_number": 98.0,
+        "non_empty_top_level_set": {
+            0,
+            1,
+            2,
+            "a",
+            "b",
+            "c",
+        },
+        "non_empty_top_level_dictionary": {
+            "non_empty_1st_level_set": {
+                "non_empty_2nd_level_list": [0, 1, 2, "a", "b", "c"],
+                "non_empty_2nd_level_dictionary": {
+                    "string": "xyz_0",
+                    "integer_one": 1,
+                    "scientific_notation_floating_point_number": 98.0,
+                },
+            }
+        },
+    }
+    assert d1_end == d1_end_expected
+
+
 def test_hyphen():
-    input = "suite_validation_result"
-    assert hyphen(input) == "suite-validation-result"
+    txt: str = "suite_validation_result"
+    assert hyphen(txt=txt) == "suite-validation-result"