Repository: pypa/pip

Issue:
PIP_EXISTS_ACTION environment variable causes error.
```
export PIP_EXISTS_ACTION=w

pip install requests

=>
An error occured during configuration: option --exists-action: invalid choice: ['w'] (choose from 's', 'i', 'w', 'b')
```

The bug is on baseparser.py, ConfigOptionParser.update_defaults.



Discussion:

Resolving patch:
diff --git a/pip/basecommand.py b/pip/basecommand.py
--- a/pip/basecommand.py
+++ b/pip/basecommand.py
@@ -109,7 +109,7 @@ def main(self, args, initial_options):
             os.environ['PIP_NO_INPUT'] = '1'
 
         if options.exists_action:
-            os.environ['PIP_EXISTS_ACTION'] = ''.join(options.exists_action)
+            os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
 
         if options.cert:
             os.environ['PIP_CERT'] = options.cert
diff --git a/pip/baseparser.py b/pip/baseparser.py
--- a/pip/baseparser.py
+++ b/pip/baseparser.py
@@ -139,6 +139,14 @@ def get_config_files(self):
             return [config_file]
         return [default_config_file]
 
+    def check_default(self, option, key, val):
+        try:
+            return option.check_value(key, val)
+        except optparse.OptionValueError:
+            e = sys.exc_info()[1]
+            print("An error occurred during configuration: %s" % e)
+            sys.exit(3)
+
     def update_defaults(self, defaults):
         """Updates the given defaults with values from the config files and
         the environ. Does a little special handling for certain types of
@@ -157,19 +165,14 @@ def update_defaults(self, defaults):
                 # ignore empty values
                 if not val:
                     continue
-                # handle multiline configs
+                if option.action in ('store_true', 'store_false', 'count'):
+                    val = strtobool(val)
                 if option.action == 'append':
                     val = val.split()
+                    val = [self.check_default(option, key, v) for v in val]
                 else:
-                    option.nargs = 1
-                if option.action in ('store_true', 'store_false', 'count'):
-                    val = strtobool(val)
-                try:
-                    val = option.convert_value(key, val)
-                except optparse.OptionValueError:
-                    e = sys.exc_info()[1]
-                    print("An error occurred during configuration: %s" % e)
-                    sys.exit(3)
+                    val = self.check_default(option, key, val)
+
                 defaults[option.dest] = val
         return defaults
 
diff --git a/pip/util.py b/pip/util.py
--- a/pip/util.py
+++ b/pip/util.py
@@ -117,7 +117,7 @@ def get_pathext(default_pathext=None):
 
 
 def ask_path_exists(message, options):
-    for action in os.environ.get('PIP_EXISTS_ACTION', ''):
+    for action in os.environ.get('PIP_EXISTS_ACTION', '').split():
         if action in options:
             return action
     return ask(message, options)


Test patch:
diff --git a/tests/unit/test_baseparser.py b/tests/unit/test_baseparser.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/test_baseparser.py
@@ -0,0 +1,72 @@
+import os
+from pip.baseparser import ConfigOptionParser
+
+class TestConfigOptionParser(object):
+    """
+    Unit tests for `pip.baseparser.ConfigOptionParser` (our option parser that
+    overrides defaults from config files and environment vars)
+    """
+
+    def setup(self):
+        self.environ_before = os.environ.copy()
+        self.parser = ConfigOptionParser(name='test')
+        self.parser.add_option(
+            '--normal',
+            default='v1')
+        self.parser.add_option(
+            '--append',
+            action='append',
+            default=['v1'])
+        self.parser.add_option(
+            '--choice',
+            action='append',
+            choices=['v1', 'v2', 'v3'],
+            type='choice',
+            default=['v1'])
+
+    def teardown(self):
+        os.environ = self.environ_before
+
+    def test_env_non_append_override_default(self):
+        """
+        Test that a PIP_* environ variable overrides a non-append option default.
+        """
+        os.environ['PIP_NORMAL'] = 'v2'
+        options, args = self.parser.parse_args([])
+        assert options.normal == 'v2'
+
+    def test_env_append_single_override_default(self):
+        """
+        Test that a PIP_* environ variable overrides an append option default.
+        (where the value is one item)
+        """
+        os.environ['PIP_APPEND'] = 'v2'
+        options, args = self.parser.parse_args([])
+        assert options.append == ['v2']
+
+    def test_env_append_multi_override_default(self):
+        """
+        Test that a PIP_* environ variable overrides an append option default.
+        (where the value is multiple)
+        """
+        os.environ['PIP_APPEND'] = 'v1 v2'
+        options, args = self.parser.parse_args([])
+        assert options.append == ['v1', 'v2']
+
+    def test_env_choice_single_override_default(self):
+        """
+        Test that a PIP_* environ variable overrides a choice option default.
+        (where the value is one item)
+        """
+        os.environ['PIP_CHOICE'] = 'v2'
+        options, args = self.parser.parse_args([])
+        assert options.choice == ['v2']
+
+    def test_env_choice_multi_override_default(self):
+        """
+        Test that a PIP_* environ variable overrides a choice option default.
+        (where the value is multiple)
+        """
+        os.environ['PIP_CHOICE'] = 'v1 v2'
+        options, args = self.parser.parse_args([])
+        assert options.choice == ['v1', 'v2']