Repository: jonathanhaigh/multiconfparse

Issue:
Add option to turn off upper casing of env var names

Discussion:

Patch:
diff --git a/multiconfparse/__init__.py b/multiconfparse/__init__.py
index 0d0f524..60aedc0 100644
--- a/multiconfparse/__init__.py
+++ b/multiconfparse/__init__.py
@@ -418,11 +418,10 @@ class EnvironmentSource(Source):
       environment variable names that the source will look for.  The default
       value is ``""``.
 
-    Note that:
+    * ``env_var_force_upper`` (optional, keyword): force the environment
+      variable name to be in upper case. Default is ``True``.
 
-    * The name of the environment variable for a config item is the config
-      item's name, converted to upper case, then prefixed with
-      ``env_var_prefix``.
+    Note that:
 
     * Values in environment variables for config items with ``nargs == 0`` or
       ``nargs == "?"`` (where the ``const`` value should be used rather than
@@ -438,7 +437,12 @@ class EnvironmentSource(Source):
     source_name = "environment"
 
     def __init__(
-        self, actions, none_values=None, priority=10, env_var_prefix="",
+        self,
+        actions,
+        none_values=None,
+        priority=10,
+        env_var_prefix="",
+        env_var_force_upper=True,
     ):
         super().__init__(actions, priority=priority)
 
@@ -446,6 +450,7 @@ class EnvironmentSource(Source):
             none_values = [""]
         self._none_values = none_values
         self._env_var_prefix = env_var_prefix
+        self._env_var_force_upper = env_var_force_upper
 
     def parse_config(self):
         mentions = []
@@ -466,7 +471,9 @@ class EnvironmentSource(Source):
         return mentions
 
     def _config_name_to_env_name(self, config_name):
-        return f"{self._env_var_prefix}{config_name.upper()}"
+        if self._env_var_force_upper:
+            config_name = config_name.upper()
+        return f"{self._env_var_prefix}{config_name}"
 
 
 class ArgparseSource(Source):


Test patch:
diff --git a/tests/test_multiconfparse.py b/tests/test_multiconfparse.py
index f58d3f4..faadf53 100644
--- a/tests/test_multiconfparse.py
+++ b/tests/test_multiconfparse.py
@@ -495,38 +495,6 @@ def test_priorities_stable_sort():
     )
 
 
-def test_dict_source_none_values():
-    mcp_parser = mcp.ConfigParser()
-    mcp_parser.add_config("c", action="store", nargs="?", const="cv")
-    mcp_parser.add_source(
-        "dict", {"c": "none_value"}, none_values=["none_value"]
-    )
-    values = mcp_parser.parse_config()
-    assert values == mcp._namespace_from_dict({"c": "cv"})
-
-
-def test_env_source_none_values():
-    mcp_parser = mcp.ConfigParser()
-    mcp_parser.add_config("c", action="store", nargs="?", const="cv")
-    mcp_parser.add_source(
-        "environment", env_var_prefix="TEST_", none_values=["none_value"],
-    )
-    with utm.patch.object(os, "environ", {"TEST_C": "none_value"}):
-        values = mcp_parser.parse_config()
-    assert values == mcp._namespace_from_dict({"c": "cv"})
-
-
-def test_json_source_none_values():
-    mcp_parser = mcp.ConfigParser()
-    mcp_parser.add_config("c", action="store", nargs="?", const="cv")
-    fileobj = io.StringIO('{"c": "none_value"}')
-    mcp_parser.add_source(
-        "json", fileobj=fileobj, none_values=["none_value"],
-    )
-    values = mcp_parser.parse_config()
-    assert values == mcp._namespace_from_dict({"c": "cv"})
-
-
 def test_config_name_clash():
     mcp_parser = mcp.ConfigParser()
     mcp_parser.add_config("c")
@@ -595,19 +563,6 @@ def test_dest_with_extend():
     assert values == expected
 
 
-def test_dest_with_simple_argparse():
-    mcp_parser = mcp.ConfigParser()
-    mcp_parser.add_config("c1", action="extend", dest="d")
-    mcp_parser.add_config("c2", action="extend", dest="d")
-    mcp_parser.add_config("c3", action="extend", dest="d")
-    mcp_parser.add_source("simple_argparse")
-    argv = "prog --c1 v1 v2 --c2 v3 --c3 v4 v5".split()
-    with utm.patch.object(sys, "argv", argv):
-        values = mcp_parser.parse_config()
-    expected = mcp._namespace_from_dict({"d": ["v1", "v2", "v3", "v4", "v5"]})
-    assert values == expected
-
-
 def test_suppress_help(capfd):
     mcp_parser = mcp.ConfigParser()
     mcp_parser.add_config("config_item1")
@@ -1461,24 +1416,17 @@ def test_simple_argparse_source_with_count_with_default():
     assert values == expected_values
 
 
-def test_simple_argparse_source_with_count_missing():
+def test_simple_argparse_source_with_dest():
     mcp_parser = mcp.ConfigParser()
-    mcp_parser.add_config("c1", action="count")
-    mcp_parser.add_source("simple_argparse")
-    with utm.patch.object(sys, "argv", "prog".split()):
-        values = mcp_parser.parse_config()
-    expected_values = mcp._namespace_from_dict({"c1": None})
-    assert values == expected_values
-
-
-def test_simple_argparse_source_with_count_missing_with_default():
-    mcp_parser = mcp.ConfigParser()
-    mcp_parser.add_config("c1", action="count", default=10)
+    mcp_parser.add_config("c1", action="extend", dest="d")
+    mcp_parser.add_config("c2", action="extend", dest="d")
+    mcp_parser.add_config("c3", action="extend", dest="d")
     mcp_parser.add_source("simple_argparse")
-    with utm.patch.object(sys, "argv", "prog".split()):
+    argv = "prog --c1 v1 v2 --c2 v3 --c3 v4 v5".split()
+    with utm.patch.object(sys, "argv", argv):
         values = mcp_parser.parse_config()
-    expected_values = mcp._namespace_from_dict({"c1": 10})
-    assert values == expected_values
+    expected = mcp._namespace_from_dict({"d": ["v1", "v2", "v3", "v4", "v5"]})
+    assert values == expected
 
 
 # ------------------------------------------------------------------------------
@@ -1506,6 +1454,72 @@ def test_json_source_with_config_added_after_source():
     assert values == expected_values
 
 
+def test_json_source_none_values():
+    mcp_parser = mcp.ConfigParser()
+    mcp_parser.add_config("c", action="store", nargs="?", const="cv")
+    fileobj = io.StringIO('{"c": "none_value"}')
+    mcp_parser.add_source(
+        "json", fileobj=fileobj, none_values=["none_value"],
+    )
+    values = mcp_parser.parse_config()
+    assert values == mcp._namespace_from_dict({"c": "cv"})
+
+
+# ------------------------------------------------------------------------------
+# environment source tests
+# ------------------------------------------------------------------------------
+
+
+def test_env_source_none_values():
+    mcp_parser = mcp.ConfigParser()
+    mcp_parser.add_config("c", action="store", nargs="?", const="cv")
+    mcp_parser.add_source(
+        "environment", env_var_prefix="TEST_", none_values=["none_value"],
+    )
+    with utm.patch.object(os, "environ", {"TEST_C": "none_value"}):
+        values = mcp_parser.parse_config()
+    assert values == mcp._namespace_from_dict({"c": "cv"})
+
+
+def test_env_source_force_upper_true():
+    mcp_parser = mcp.ConfigParser()
+    mcp_parser.add_config("c1")
+    mcp_parser.add_config("c2")
+    mcp_parser.add_source(
+        "environment", env_var_prefix="TEST_", env_var_force_upper=True
+    )
+    with utm.patch.object(os, "environ", {"TEST_C1": "v1", "TEST_c2": "v2"}):
+        values = mcp_parser.parse_config()
+    assert values == mcp._namespace_from_dict({"c1": "v1", "c2": None})
+
+
+def test_env_source_force_upper_false():
+    mcp_parser = mcp.ConfigParser()
+    mcp_parser.add_config("c1")
+    mcp_parser.add_config("c2")
+    mcp_parser.add_source(
+        "environment", env_var_prefix="TEST_", env_var_force_upper=False
+    )
+    with utm.patch.object(os, "environ", {"TEST_C1": "v1", "TEST_c2": "v2"}):
+        values = mcp_parser.parse_config()
+    assert values == mcp._namespace_from_dict({"c1": None, "c2": "v2"})
+
+
+# ------------------------------------------------------------------------------
+# dict source tests
+# ------------------------------------------------------------------------------
+
+
+def test_dict_source_none_values():
+    mcp_parser = mcp.ConfigParser()
+    mcp_parser.add_config("c", action="store", nargs="?", const="cv")
+    mcp_parser.add_source(
+        "dict", {"c": "none_value"}, none_values=["none_value"]
+    )
+    values = mcp_parser.parse_config()
+    assert values == mcp._namespace_from_dict({"c": "cv"})
+
+
 # ------------------------------------------------------------------------------
 # Multiple source tests
 # ------------------------------------------------------------------------------