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/data_context/store/metric_store.py b/great_expectations/data_context/store/metric_store.py
--- a/great_expectations/data_context/store/metric_store.py
+++ b/great_expectations/data_context/store/metric_store.py
@@ -1,6 +1,7 @@
 import json
 
 from great_expectations.core.metric import ValidationMetricIdentifier
+from great_expectations.core.run_identifier import RunIdentifier
 from great_expectations.core.util import ensure_json_serializable
 from great_expectations.data_context.store.database_store_backend import (
     DatabaseStoreBackend,
@@ -99,7 +100,7 @@ def __init__(self, store_backend=None, store_name=None):
         }
         filter_properties_dict(properties=self._config, clean_falsy=True, inplace=True)
 
-    def get_bind_params(self, run_id):
+    def get_bind_params(self, run_id: RunIdentifier) -> dict:
         params = {}
         for k in self._store_backend.list_keys(run_id.to_tuple()):
             key = self.tuple_to_key(k)
diff --git a/great_expectations/data_context/store/tuple_store_backend.py b/great_expectations/data_context/store/tuple_store_backend.py
--- a/great_expectations/data_context/store/tuple_store_backend.py
+++ b/great_expectations/data_context/store/tuple_store_backend.py
@@ -5,6 +5,7 @@
 import re
 import shutil
 from abc import ABCMeta
+from typing import List, Tuple
 
 from great_expectations.data_context.store.store_backend import StoreBackend
 from great_expectations.exceptions import InvalidKeyError, StoreBackendError
@@ -341,7 +342,7 @@ def _move(self, source_key, dest_key, **kwargs):
 
         return False
 
-    def list_keys(self, prefix=()):
+    def list_keys(self, prefix: Tuple = ()) -> List[Tuple]:
         key_list = []
         for root, dirs, files in os.walk(
             os.path.join(self.full_base_directory, *prefix)
@@ -594,7 +595,8 @@ def _move(self, source_key, dest_key, **kwargs):
 
         s3.Object(self.bucket, source_filepath).delete()
 
-    def list_keys(self):
+    def list_keys(self, prefix: Tuple = ()) -> List[Tuple]:
+        # Note that the prefix arg is only included to maintain consistency with the parent class signature
         s3 = self._create_client()
         paginator = s3.get_paginator("list_objects_v2")
 
@@ -868,7 +870,8 @@ def _move(self, source_key, dest_key, **kwargs):
         blob = bucket.blob(source_filepath)
         _ = bucket.rename_blob(blob, dest_filepath)
 
-    def list_keys(self):
+    def list_keys(self, prefix: Tuple = ()) -> List[Tuple]:
+        # Note that the prefix arg is only included to maintain consistency with the parent class signature
         key_list = []
 
         from google.cloud import storage
@@ -1042,7 +1045,8 @@ def _set(self, key, value, content_encoding="utf-8", **kwargs):
             )
         return az_blob_key
 
-    def list_keys(self):
+    def list_keys(self, prefix: Tuple = ()) -> List[Tuple]:
+        # Note that the prefix arg is only included to maintain consistency with the parent class signature
         key_list = []
 
         for obj in self._get_container_client().list_blobs(


Test patch:
diff --git a/tests/data_context/store/test_evaluation_parameter_store.py b/tests/data_context/store/test_evaluation_parameter_store.py
--- a/tests/data_context/store/test_evaluation_parameter_store.py
+++ b/tests/data_context/store/test_evaluation_parameter_store.py
@@ -1,8 +1,11 @@
 import datetime
 import os
+from unittest import mock
 
+import boto3
 import pytest
 from freezegun import freeze_time
+from moto import mock_s3
 
 import tests.test_utils as test_utils
 from great_expectations.core import ExpectationConfiguration
@@ -13,6 +16,12 @@
 from great_expectations.core.metric import ValidationMetricIdentifier
 from great_expectations.core.run_identifier import RunIdentifier
 from great_expectations.data_context.data_context import DataContext
+from great_expectations.data_context.store import (
+    EvaluationParameterStore,
+    TupleAzureBlobStoreBackend,
+    TupleGCSStoreBackend,
+    TupleS3StoreBackend,
+)
 from great_expectations.data_context.util import instantiate_class_from_config
 
 
@@ -248,3 +257,98 @@ def test_database_evaluation_parameter_store_get_bind_params(param_store):
         "urn:great_expectations:validations:asset2.warning:"
         "expect_column_values_to_match_regex.result.unexpected_percent:column=mycol": 12.3456789,
     }
+
+
+@mock.patch(
+    "great_expectations.data_context.store.tuple_store_backend.TupleS3StoreBackend.list_keys"
+)
+@mock.patch(
+    "great_expectations.data_context.store.tuple_store_backend.TupleStoreBackend.list_keys"
+)
+def test_evaluation_parameter_store_calls_proper_cloud_tuple_store_methods(
+    mock_parent_list_keys,
+    mock_s3_list_keys,
+):
+    """
+    What does this test and why?
+
+    Demonstrate that EvaluationParameterStore works as expected with TupleS3StoreBackend
+    and that the store backend adheres to the Liskov substitution principle.
+    """
+    evaluation_parameter_store = EvaluationParameterStore()
+    run_id = RunIdentifier()
+    s3_store = TupleS3StoreBackend(bucket="my_bucket")
+    evaluation_parameter_store._store_backend = s3_store
+
+    # Sanity check to ensure neither parent nor child method has been called
+    assert not mock_s3_list_keys.called
+    assert not mock_parent_list_keys.called
+
+    # `get_bind_params` calls the child method due to proper polymorphism
+    evaluation_parameter_store.get_bind_params(run_id=run_id)
+    assert mock_s3_list_keys.called
+    assert not mock_parent_list_keys.called
+
+
+@mock.patch(
+    "great_expectations.data_context.store.tuple_store_backend.TupleAzureBlobStoreBackend.list_keys"
+)
+@mock.patch(
+    "great_expectations.data_context.store.tuple_store_backend.TupleStoreBackend.list_keys"
+)
+def test_evaluation_parameter_store_calls_proper_azure_tuple_store_methods(
+    mock_parent_list_keys,
+    mock_azure_list_keys,
+):
+    """
+    What does this test and why?
+
+    Demonstrate that EvaluationParameterStore works as expected with TupleAzureBlobStoreBackend
+    and that the store backend adheres to the Liskov substitution principle.
+    """
+    evaluation_parameter_store = EvaluationParameterStore()
+    run_id = RunIdentifier()
+    azure_store = TupleAzureBlobStoreBackend(
+        container="my_container", connection_string="my_connection_string"
+    )
+    evaluation_parameter_store._store_backend = azure_store
+
+    # Sanity check to ensure neither parent nor child method has been called
+    assert not mock_azure_list_keys.called
+    assert not mock_parent_list_keys.called
+
+    # `get_bind_params` calls the child method due to proper polymorphism
+    evaluation_parameter_store.get_bind_params(run_id=run_id)
+    assert mock_azure_list_keys.called
+    assert not mock_parent_list_keys.called
+
+
+@mock.patch(
+    "great_expectations.data_context.store.tuple_store_backend.TupleGCSStoreBackend.list_keys"
+)
+@mock.patch(
+    "great_expectations.data_context.store.tuple_store_backend.TupleStoreBackend.list_keys"
+)
+def test_evaluation_parameter_store_calls_proper_gcs_tuple_store_methods(
+    mock_parent_list_keys,
+    mock_gcs_list_keys,
+):
+    """
+    What does this test and why?
+
+    Demonstrate that EvaluationParameterStore works as expected with TupleGCSStoreBackend
+    and that the store backend adheres to the Liskov substitution principle.
+    """
+    evaluation_parameter_store = EvaluationParameterStore()
+    run_id = RunIdentifier()
+    gcs_store = TupleGCSStoreBackend(bucket="my_bucket", project="my_project")
+    evaluation_parameter_store._store_backend = gcs_store
+
+    # Sanity check to ensure neither parent nor child method has been called
+    assert not mock_gcs_list_keys.called
+    assert not mock_parent_list_keys.called
+
+    # `get_bind_params` calls the child method due to proper polymorphism
+    evaluation_parameter_store.get_bind_params(run_id=run_id)
+    assert mock_gcs_list_keys.called
+    assert not mock_parent_list_keys.called