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/templates.py b/great_expectations/data_context/templates.py
--- a/great_expectations/data_context/templates.py
+++ b/great_expectations/data_context/templates.py
@@ -32,12 +32,10 @@
 # Environment variables take precedence over variables defined here.
 #
 # Substitution values defined here can be a simple (non-nested) value,
-# or a nested value such as a dictionary.
+# nested value such as a dictionary, or an environment variable (i.e. ${ENV_VAR})
 #
-# NOTE: Environment variables are currently *only* supported in
-# great_expectations.yml, but not in this config file!
 #
-# https://docs.greatexpectations.io/en/latest/reference/data_context_reference.html#managing-environment-and-secrets
+# https://docs.greatexpectations.io/en/latest/how_to_guides/configuring_data_contexts/how_to_use_a_yaml_file_or_environment_variables_to_populate_credentials.html
 
 """
 
diff --git a/great_expectations/data_context/util.py b/great_expectations/data_context/util.py
--- a/great_expectations/data_context/util.py
+++ b/great_expectations/data_context/util.py
@@ -145,6 +145,15 @@ def substitute_config_variable(template_str, config_variables_dict):
 
     if match:
         config_variable_value = config_variables_dict.get(match.group(1))
+        try:
+            inner_match = re.search(r"\$\{(.*?)\}", config_variable_value) or re.search(
+                r"\$([_a-z][_a-z0-9]*)", config_variable_value
+            )
+        except TypeError:
+            inner_match = None
+
+        if inner_match:
+            config_variable_value = os.getenv(inner_match.group(1))
 
         if config_variable_value is not None:
             if match.start() == 0 and match.end() == len(template_str):


Test patch:
diff --git a/tests/data_context/test_data_context_config_variables.py b/tests/data_context/test_data_context_config_variables.py
--- a/tests/data_context/test_data_context_config_variables.py
+++ b/tests/data_context/test_data_context_config_variables.py
@@ -10,6 +10,7 @@
 )
 from great_expectations.data_context.util import (
     file_relative_path,
+    substitute_all_config_variables,
     substitute_config_variable,
 )
 from great_expectations.exceptions import InvalidConfigError, MissingConfigVariableError
@@ -220,3 +221,39 @@ def test_substitute_config_variable():
         == config_variables_dict["arg2"]
     )
     assert exc.value.missing_config_variable == "arg1"
+
+
+def test_substitute_env_var_in_config_variable_file(monkeypatch):
+    monkeypatch.setenv("FOO", "val_of_arg_0")
+    config_variables_dict = {"arg0": "${FOO}", "arg2": {"v1": 2}, "replace_me": "wrong"}
+    assert (
+        substitute_config_variable("abc${arg0}", config_variables_dict)
+        == "abcval_of_arg_0"
+    )
+    monkeypatch.delenv("FOO")
+    with pytest.raises(MissingConfigVariableError):
+        substitute_config_variable("abc${arg0}", config_variables_dict)
+
+    with open(
+        file_relative_path(
+            __file__, "../test_fixtures/great_expectations_basic_with_variables.yml",
+        )
+    ) as f:
+        config = yaml.load(f)
+
+    monkeypatch.setenv("replace_me", "correct")
+
+    # this is how dict is created in data_context.get_config_with_variables_substituted, for env var override
+    config_variables_dict = {
+        **config_variables_dict,
+        **dict(os.environ),
+    }
+
+    config = substitute_all_config_variables(config, config_variables_dict)
+
+    assert (
+        config["datasources"]["mydatasource"]["batch_kwargs_generators"]["mygenerator"][
+            "reader_options"
+        ]["test_variable_sub1"]
+        == "correct"
+    )