Repository: jazzband/pip-tools

Issue:
flake8 fails due to bare except (E772) in _compat/contextlib
I noticed that recent builds of master have failed due a bare exception here: https://github.com/jazzband/pip-tools/blob/master/piptools/_compat/contextlib.py#L111 (eg https://travis-ci.org/jazzband/pip-tools/jobs/293392462#L691 )

This is due to flake8==3.5.0  (flake8==3.4.1 passes)

I'm not clear if a bare exception is justified in this case, but in any case the code is the same as in contextlib2.  Should piptools add contextlib2 as a conditional dependency for python 2.7 instead of _compat/contextlib?

##### Environment Versions

1. Ubuntu 16.04
1. Python version: 2.7.12
1. pip version:  9.0.1
1. pip-tools version: 1.10.2.dev7+g29ea9a4

##### Steps to replicate

tox -e flake8

##### Expected result

test pass

##### Actual result

```
$ tox -e flake8
GLOB sdist-make: /home/johnc/workspace/pip-tools/setup.py
flake8 create: /home/johnc/workspace/pip-tools/.tox/flake8
flake8 installdeps: flake8
flake8 inst: /home/johnc/workspace/pip-tools/.tox/dist/pip-tools-1.10.2.dev7+g29ea9a4.zip
flake8 installed: click==6.7,configparser==3.5.0,enum34==1.1.6,first==2.0.1,flake8==3.5.0,mccabe==0.6.1,pycodestyle==2.3.1,pyflakes==1.6.0,six==1.11.0
flake8 runtests: PYTHONHASHSEED='1185616339'
flake8 runtests: commands[0] | flake8 piptools tests --max-line-length=120
piptools/_compat/contextlib.py:111:13: E722 do not use bare except'

```


Discussion:
Hi @therefromhere, thanks for looking into this.

So, you're saying this is just really a vendoring of `contextlib2` (this was done before my time here I think)? If so, yeah, let's go ahead with a condition dependency for py2.7, using those sweet environment markers.
>> This is due to flake8==3.5.0 (flake8==3.4.1 passes)
>
> So, you're saying this is just really a vendoring of `contextlib2`

That only means that the checks are more strict now. We shouldn't look into pinning or purposely downgrading, I feel.

What if we catch `Exception` and let errors fall through? See [this SO answer](https://stackoverflow.com/questions/14797375/should-i-always-specify-an-exception-type-in-except-statements/14797393#14797393) for comparison.

Resolving patch:
diff --git a/piptools/_compat/__init__.py b/piptools/_compat/__init__.py
--- a/piptools/_compat/__init__.py
+++ b/piptools/_compat/__init__.py
@@ -6,8 +6,8 @@
 import six
 
 if six.PY2:
+    from contextlib2 import ExitStack
     from .tempfile import TemporaryDirectory
-    from .contextlib import ExitStack
 else:
-    from tempfile import TemporaryDirectory
     from contextlib import ExitStack
+    from tempfile import TemporaryDirectory
diff --git a/piptools/_compat/contextlib.py b/piptools/_compat/contextlib.py
deleted file mode 100644
--- a/piptools/_compat/contextlib.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# coding: utf-8
-from __future__ import (absolute_import, division, print_function,
-                        unicode_literals)
-
-import sys
-from collections import deque
-
-
-# Inspired by discussions on http://bugs.python.org/issue13585
-class ExitStack(object):
-    """Context manager for dynamic management of a stack of exit callbacks
-
-    For example:
-
-        with ExitStack() as stack:
-            files = [stack.enter_context(open(fname)) for fname in filenames]
-            # All opened files will automatically be closed at the end of
-            # the with statement, even if attempts to open files later
-            # in the list throw an exception
-
-    """
-    def __init__(self):
-        self._exit_callbacks = deque()
-
-    def pop_all(self):
-        """Preserve the context stack by transferring it to a new instance"""
-        new_stack = type(self)()
-        new_stack._exit_callbacks = self._exit_callbacks
-        self._exit_callbacks = deque()
-        return new_stack
-
-    def _push_cm_exit(self, cm, cm_exit):
-        """Helper to correctly register callbacks to __exit__ methods"""
-        def _exit_wrapper(*exc_details):
-            return cm_exit(cm, *exc_details)
-        _exit_wrapper.__self__ = cm
-        self.push(_exit_wrapper)
-
-    def push(self, exit):
-        """Registers a callback with the standard __exit__ method signature
-
-        Can suppress exceptions the same way __exit__ methods can.
-
-        Also accepts any object with an __exit__ method (registering the
-        method instead of the object itself)
-        """
-        # We use an unbound method rather than a bound method to follow
-        # the standard lookup behaviour for special methods
-        _cb_type = type(exit)
-        try:
-            exit_method = _cb_type.__exit__
-        except AttributeError:
-            # Not a context manager, so assume its a callable
-            self._exit_callbacks.append(exit)
-        else:
-            self._push_cm_exit(exit, exit_method)
-        return exit  # Allow use as a decorator
-
-    def callback(self, callback, *args, **kwds):
-        """Registers an arbitrary callback and arguments.
-
-        Cannot suppress exceptions.
-        """
-        def _exit_wrapper(exc_type, exc, tb):
-            callback(*args, **kwds)
-        # We changed the signature, so using @wraps is not appropriate, but
-        # setting __wrapped__ may still help with introspection
-        _exit_wrapper.__wrapped__ = callback
-        self.push(_exit_wrapper)
-        return callback  # Allow use as a decorator
-
-    def enter_context(self, cm):
-        """Enters the supplied context manager
-
-        If successful, also pushes its __exit__ method as a callback and
-        returns the result of the __enter__ method.
-        """
-        # We look up the special methods on the type to match the with
-        # statement
-        _cm_type = type(cm)
-        _exit = _cm_type.__exit__
-        result = _cm_type.__enter__(cm)
-        self._push_cm_exit(cm, _exit)
-        return result
-
-    def close(self):
-        """Immediately unwind the context stack"""
-        self.__exit__(None, None, None)
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, *exc_details):
-        if not self._exit_callbacks:
-            return
-
-        # This looks complicated, but it is really just
-        # setting up a chain of try-expect statements to ensure
-        # that outer callbacks still get invoked even if an
-        # inner one throws an exception
-        def _invoke_next_callback(exc_details):
-            # Callbacks are removed from the list in FIFO order
-            # but the recursion means they're invoked in LIFO order
-            cb = self._exit_callbacks.popleft()
-            if not self._exit_callbacks:
-                # Innermost callback is invoked directly
-                return cb(*exc_details)
-            # More callbacks left, so descend another level in the stack
-            try:
-                suppress_exc = _invoke_next_callback(exc_details)
-            except:
-                suppress_exc = cb(*sys.exc_info())
-                # Check if this cb suppressed the inner exception
-                if not suppress_exc:
-                    raise
-            else:
-                # Check if inner cb suppressed the original exception
-                if suppress_exc:
-                    exc_details = (None, None, None)
-                suppress_exc = cb(*exc_details) or suppress_exc
-            return suppress_exc
-        # Kick off the recursive chain
-        return _invoke_next_callback(exc_details)
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,9 @@
         'six',
         'setuptools'
     ],
+    extras_require={
+        ':python_version < "3.0"': ['contextlib2']
+    },
     zip_safe=False,
     entry_points={
         'console_scripts': [


Test patch: