Repository: loganasherjones/yapconf

Issue:
Environment names with prefix
* Yapcon version: 0.1.1
* Python version: 3.6.4
* Operating System: Centos 7

I was trying to use a config option with the name `bg_host` and a specification with the environment prefix `BG_`.

I expected to need to set the environment variable named `BG_BG_HOST` for yapconf to work correctly. That was successful.

That looks a little weird to me, so I set the `env_name` option in the spec to `HOST`, expecting that then setting the `BG_HOST` environment variable would work, but it didn't.

Looking though the code it looks like if you specify an alternate environment name the prefix doesn't get applied to it. I can see certain situations where that would be preferable, but in my case I'd like the prefix to still be applied to the new name.

I worked around the issue by setting the `env_name` to `BG_HOST` and that worked like I wanted, but that requires knowing at spec creation time what prefix I'm planning on applying.

I kind of expected the prefix to be applied to the new name, so I'm wondering if that should be the default behavior. Perhaps with a little flag like `apply_prefix` that could be set to False if you didn't want that behavior?




Comments:

Patch:
diff --git a/docs/usage.rst b/docs/usage.rst
index 32a719f..cc0a303 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -129,6 +129,9 @@ set an environment prefix on the ``YapconfSpec`` item via the ``env_prefix``:
     config.foo # returns 'namespaced_value'
 
 
+.. note:: When using an ``env_name`` with ``env_prefix`` the ``env_prefix`` will still be applied
+    to the name you provided. If you want to avoid this behavior, set the ``apply_env_prefix`` to ``False``.
+
 CLI Support
 -----------
 Yapconf has some great support for adding your configuration items as command-line arguments by utilizing
@@ -329,6 +332,8 @@ For each item in a specification, you can set any of these keys:
 +-------------------+------------------+----------------------------------------------------------------------------------------------------------------+
 | format_cli        | ``True``         | A flag to determine if we should format the command-line arguments to be kebab-case.                           |
 +-------------------+------------------+----------------------------------------------------------------------------------------------------------------+
+| apply_env_prefix  | ``True``         | Apply the env_prefix even if the environment name was set manually. Ignored if ``format_env`` is ``False``     |
++-------------------+------------------+----------------------------------------------------------------------------------------------------------------+
 
 
 .. _python-box: https://github.com/cdgriffith/Box
diff --git a/yapconf/items.py b/yapconf/items.py
index 37531a0..1bc5edd 100644
--- a/yapconf/items.py
+++ b/yapconf/items.py
@@ -75,6 +75,7 @@ def _generate_item(name, item_dict, env_prefix,
     init_args['env_name'] = item_dict.get('env_name', None)
     init_args['format_cli'] = item_dict.get('format_cli', True)
     init_args['format_env'] = item_dict.get('format_env', True)
+    init_args['apply_env_prefix'] = item_dict.get('apply_env_prefix', True)
     init_args['env_prefix'] = env_prefix
 
     if parent_names:
@@ -139,6 +140,9 @@ class YapconfItem(object):
          format_env: A flag to determine if environment variables will be all
             upper-case SNAKE_CASE.
          env_prefix: The env_prefix to apply to the environment name.
+         apply_env_prefix: Apply the env_prefix even if the environment name
+            was set manually. Setting format_env to false will override this
+            behavior.
 
     Raises:
         YapconfItemError: If any of the information given during
@@ -151,7 +155,7 @@ class YapconfItem(object):
                  cli_choices=None, previous_names=None, previous_defaults=None,
                  children=None, cli_expose=True, separator='.', prefix=None,
                  bootstrap=False, format_cli=True, format_env=True,
-                 env_prefix=None):
+                 env_prefix=None, apply_env_prefix=True):
 
         self.name = name
         self.item_type = item_type
@@ -171,6 +175,7 @@ class YapconfItem(object):
         self.format_env = format_env
         self.format_cli = format_cli
         self.env_prefix = env_prefix or ''
+        self.apply_env_prefix = apply_env_prefix
 
         if self.prefix:
             self.fq_name = self.separator.join([self.prefix, self.name])
@@ -330,6 +335,8 @@ class YapconfItem(object):
 
     def _setup_env_name(self):
         if self.env_name is not None:
+            if self.apply_env_prefix:
+                self.env_name = self.env_prefix + self.env_name
             return
 
         if self.format_env:
@@ -507,12 +514,12 @@ class YapconfBoolItem(YapconfItem):
                  cli_choices=None, previous_names=None, previous_defaults=None,
                  children=None, cli_expose=True, separator='.', prefix=None,
                  bootstrap=False, format_cli=True, format_env=True,
-                 env_prefix=None):
+                 env_prefix=None, apply_env_prefix=True):
         super(YapconfBoolItem, self).__init__(
             name, item_type, default, env_name, description, required,
             cli_short_name, cli_choices, previous_names, previous_defaults,
             children, cli_expose, separator, prefix, bootstrap, format_cli,
-            format_env, env_prefix)
+            format_env, env_prefix, apply_env_prefix)
 
     def add_argument(self, parser, bootstrap=False):
         """Add boolean item as an argument to the given parser.
@@ -622,13 +629,13 @@ class YapconfListItem(YapconfItem):
                  cli_choices=None, previous_names=None, previous_defaults=None,
                  children=None, cli_expose=True, separator='.', prefix=None,
                  bootstrap=False, format_cli=True, format_env=True,
-                 env_prefix=None):
+                 env_prefix=None, apply_env_prefix=True):
 
         super(YapconfListItem, self).__init__(
             name, item_type, default, env_name, description, required,
             cli_short_name, cli_choices, previous_names, previous_defaults,
             children, cli_expose, separator, prefix, bootstrap, format_cli,
-            format_env, env_prefix)
+            format_env, env_prefix, apply_env_prefix)
 
         if len(self.children) != 1:
             raise YapconfListItemError("List Items can only have a "
@@ -754,13 +761,13 @@ class YapconfDictItem(YapconfItem):
                  cli_choices=None, previous_names=None, previous_defaults=None,
                  children=None, cli_expose=True, separator='.', prefix=None,
                  bootstrap=False, format_cli=True, format_env=True,
-                 env_prefix=None):
+                 env_prefix=None, apply_env_prefix=True):
 
         super(YapconfDictItem, self).__init__(
             name, item_type, default, env_name, description, required,
             cli_short_name, cli_choices, previous_names, previous_defaults,
             children, cli_expose, separator, prefix, bootstrap, format_cli,
-            format_env, env_prefix)
+            format_env, env_prefix, apply_env_prefix)
 
         if len(self.children) < 1:
             raise YapconfDictItemError('Dict item {0} must have children'


Test patch:
diff --git a/tests/spec_test.py b/tests/spec_test.py
index dddf583..85eb866 100644
--- a/tests/spec_test.py
+++ b/tests/spec_test.py
@@ -347,8 +347,6 @@ def test_load_config_yaml_not_supported(basic_spec):
 
 def test_load_config_nested_from_environment(spec_with_dicts):
     os.environ['FOO_BAR_BAZ'] = 'baz_value'
-    print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
-    print(spec_with_dicts.get_item('foo').children['bar'].children['baz'].env_name)
     config = spec_with_dicts.load_config(
         {
             'database': {'name': 'dbname', 'host': 'dbhost', 'port': 1234},
@@ -594,3 +592,22 @@ def test_real_world_migrations(real_world_spec, current, expected,
             always_update=always_update)
 
         assert new_config == expected
+
+
+@pytest.mark.parametrize('env_name,apply_prefix,env_prefix,key', [
+    ('BG_HOST', True, 'BG_', 'BG_BG_HOST'),
+    ('BG_HOST', False, 'BG_', 'BG_HOST'),
+    ('HOST', True, 'BG_', 'BG_HOST'),
+])
+def test_env_names_with_prefixes(env_name, apply_prefix, env_prefix, key):
+    spec = YapconfSpec(
+        {
+            'bg_host': {
+                'type': 'str',
+                'env_name': env_name,
+                'apply_env_prefix': apply_prefix,
+            }
+        }, env_prefix=env_prefix)
+
+    config = spec.load_config(('ENVIRONMENT', {key: 'host_value'}))
+    assert config.bg_host == 'host_value'