Repository: pypa/pip

Issue:
Multiple calls to `pip.main()` produce duplicate output
For example:

```
>>> import pip
>>> pip.main(['install'])
You must give at least one requirement to install (see "pip help install")
0
>>> pip.main(['install'])
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
0
>>> pip.main(['install'])
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
0
>>> pip.main(['install'])
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
0
>>> pip.main(['install'])
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
0
```

Pip version: pip 1.6.dev1



Discussion:
the logger _consumers_ are being configured over and over again every time. not sure if the maintainers would accept to reset them on _main_; in case you need to run pip this way, here's a workaround:

``` python
import pip

print('first')
pip.main(['install'])
print('second')
pip.main(['install'])  # problem exists
pip.logger.consumers = []
print('third')
pip.main(['install']) # problem no longer exists
```

this outputs:

```
first
You must give at least one requirement to install (see "pip help install")
second
You must give at least one requirement to install (see "pip help install")
You must give at least one requirement to install (see "pip help install")
third
You must give at least one requirement to install (see "pip help install")
```


Resolving patch:
diff --git a/pip/log.py b/pip/log.py
--- a/pip/log.py
+++ b/pip/log.py
@@ -82,16 +82,23 @@ def __init__(self):
         self.in_progress_hanging = False
 
     def add_consumers(self, *consumers):
-        if sys.platform.startswith("win"):
-            for level, consumer in consumers:
-                if hasattr(consumer, "write"):
-                    self.consumers.append(
-                        (level, colorama.AnsiToWin32(consumer)),
-                    )
-                else:
-                    self.consumers.append((level, consumer))
-        else:
-            self.consumers.extend(consumers)
+        for level, consumer in consumers:
+            # Try to check for duplicate consumers before adding them
+            for chk_level, chk_consumer in self.consumers:
+                # Account for coloroma wrapped streams
+                if isinstance(chk_consumer, colorama.AnsiToWin32):
+                    chk_consumer = chk_consumer.wrapped
+
+                if (level, consumer) == (chk_level, chk_consumer):
+                    break
+            # If we didn't find a duplicate, then add it
+            else:
+                # Colorize consumer for Windows
+                if sys.platform.startswith('win') \
+                   and hasattr(consumer, 'write'):
+                    consumer = colorama.AnsiToWin32(consumer)
+
+                self.consumers.append((level, consumer))
 
     def debug(self, msg, *args, **kw):
         self.log(self.DEBUG, msg, *args, **kw)


Test patch:
diff --git a/tests/unit/test_log.py b/tests/unit/test_log.py
--- a/tests/unit/test_log.py
+++ b/tests/unit/test_log.py
@@ -45,6 +45,55 @@ def test_should_warn_significance():
     assert should_warn("1.4.dev1", "1.6")
 
 
+def test_ignores_duplicate_consumers():
+    """
+    Make sure if the same consumer & level pair are asked to be added,
+    they're ignored.
+    """
+    logger = Logger()
+
+    import sys
+    consumer1 = sys.stdout
+    consumer2 = sys.stdout
+
+    logger.add_consumers(
+        (logger.NOTIFY, consumer1),
+        (logger.NOTIFY, consumer2),
+    )
+    logger.add_consumers(
+        (logger.NOTIFY, consumer1),
+        (logger.NOTIFY, consumer2),
+    )
+
+    assert 1 == len(logger.consumers)
+
+
+def test_ignores_Win32_wrapped_consumers(monkeypatch):
+    """
+    Test that colorama wrapped duplicate streams are ignored, too.
+    """
+    logger = Logger()
+    consumer = StringIO()
+
+    consumer1 = consumer
+    consumer2 = consumer
+
+    # Pretend to be Windows
+    monkeypatch.setattr('sys.platform', 'win32')
+    logger.add_consumers(
+        (logger.NOTIFY, consumer1),
+        (logger.NOTIFY, consumer2),
+    )
+    # Pretend to be linux
+    monkeypatch.setattr('sys.platform', 'linux2')
+    logger.add_consumers(
+        (logger.NOTIFY, consumer2),
+        (logger.NOTIFY, consumer1),
+    )
+
+    assert 1 == len(logger.consumers)
+
+
 def test_log_no_extra_line_break():
     """
     Confirm that multiple `.write()` consumers doesn't result in additional