Repository: jazzband/pip-tools

Issue:
pip-compile gives misleading error message if no internet
If there is no internet access, `pip-compile` will fail with a misleading message. This is with pip-compile 1.6.1, pip 8.1.1, and Python 3.5.1.
##### Steps to replicate
1. Create a `requirements.in` file with a single project name.
2. Disable internet access (e.g. by disabling wi-fi).
3. Run `pip-compile --verbose requirements.in`.
##### Expected result

The command should fail with an error message relating to the fact that there is no internet connection.
##### Actual result

Instead, the output looks something like this:

```
Using indexes:
  https://pypi.python.org/simple

                          ROUND 1                           
Current constraints:
  MyPackage

Finding the best candidates:
Could not find a version that matches MyPackage
Tried: (no version found at all)
```



Discussion:
Wow, that's pretty bad. Thanks for reporting, this obviously needs to be fixed.

Note that it's possible this is due in part to a similar bug / behavior in pip itself. If I run the following with no internet:

```
$ pip install --retries 0 MyPackage > temp.txt
```

then pip's stderr output looks like this--

```
  Could not find a version that satisfies the requirement MyPackage (from versions: )
No matching distribution found for MyPackage
```

Without the retries argument, you will at least see connection errors prior to this. As with this report, it would be better if pip's final error message says something about the inability to connect.

FYI, I filed the analogous issue for pip here: https://github.com/pypa/pip/issues/3642

It's possible that if pip fixes that issue the right way, then this bug will also go away (at least for the versions of pip where that issue is fixed).


Resolving patch:
diff --git a/piptools/exceptions.py b/piptools/exceptions.py
--- a/piptools/exceptions.py
+++ b/piptools/exceptions.py
@@ -3,9 +3,10 @@ class PipToolsError(Exception):
 
 
 class NoCandidateFound(PipToolsError):
-    def __init__(self, ireq, candidates_tried):
+    def __init__(self, ireq, candidates_tried, index_urls):
         self.ireq = ireq
         self.candidates_tried = candidates_tried
+        self.index_urls = index_urls
 
     def __str__(self):
         sorted_versions = sorted(c.version for c in self.candidates_tried)
@@ -13,6 +14,12 @@ def __str__(self):
             'Could not find a version that matches {}'.format(self.ireq),
             'Tried: {}'.format(', '.join(str(version) for version in sorted_versions) or '(no version found at all)')
         ]
+        if sorted_versions:
+            lines.append('There are incompatible versions in the resolved dependencies.')
+        else:
+            lines.append('{} {} reachable?'.format(
+                'Were' if len(self.index_urls) > 1 else 'Was', ' or '.join(self.index_urls))
+            )
         return '\n'.join(lines)
 
 
diff --git a/piptools/repositories/pypi.py b/piptools/repositories/pypi.py
--- a/piptools/repositories/pypi.py
+++ b/piptools/repositories/pypi.py
@@ -115,7 +115,7 @@ def find_best_match(self, ireq, prereleases=None):
         # Reuses pip's internal candidate sort key to sort
         matching_candidates = [candidates_by_version[ver] for ver in matching_versions]
         if not matching_candidates:
-            raise NoCandidateFound(ireq, all_candidates)
+            raise NoCandidateFound(ireq, all_candidates, self.finder.index_urls)
         best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key)
 
         # Turn the candidate into a pinned InstallRequirement


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -36,7 +36,7 @@ def find_best_match(self, ireq, prereleases=False):
         versions = list(ireq.specifier.filter(self.index[key_from_req(ireq.req)],
                                               prereleases=prereleases))
         if not versions:
-            raise NoCandidateFound(ireq, self.index[key_from_req(ireq.req)])
+            raise NoCandidateFound(ireq, self.index[key_from_req(ireq.req)], ['https://fake.url.foo'])
         best_version = max(versions, key=Version)
         return make_install_requirement(key_from_req(ireq.req), best_version, ireq.extras, constraint=ireq.constraint)