classification
Title: A more helpful ImportError message
Type: enhancement Stage: resolved
Components: Interpreter Core Versions: Python 3.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: barry, brett.cannon, levkivskyi, mbussonn
Priority: normal Keywords:

Created on 2017-02-13 16:30 by barry, last changed 2017-03-24 23:52 by barry. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 91 merged mbussonn, 2017-02-14 22:20
PR 91 merged mbussonn, 2017-02-14 22:20
PR 91 merged mbussonn, 2017-02-14 22:20
PR 103 merged mbussonn, 2017-02-15 01:00
Messages (8)
msg287708 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2017-02-13 16:30
I haven't really thought about this deeply but I've observed that there are lots of cases where a user will report getting an ImportError trying to import a name from a module, where it turns out that the problem is that the module is coming from an unexpected location.  Examples include pip installed packages overriding system packages, or unexpected vendorized wheels.

The standard first response is typically, "can you please run this to tell us where the foo library is coming from?"  E.g.

```
^^ this is indicative of an old version of urllib3. Can you please double 
check whether you have an old urllib3 installed e.g. in /usr/local or ~/.

Testing:

$ python3 -c 'import urllib3; print(urllib3.__file__)'

should return:

	/usr/lib/python3/dist-packages/urllib3/__init__.py

and might tell you where else a locally installed urllib3 is to be found if it 
doesn't return that value.
```

It would be kind of useful if the original ImportError showed you where the module came from (i.e. its __file__), if such information can be discerned.  E.g.:

```
>>> import requests; requests.__version__.split('.')  
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "/usr/lib/python2.7/dist-packages/requests/__init__.py", line 
61, in <module>
     from .packages.urllib3.exceptions import DependencyWarning
ImportError: cannot import name DependencyWarning from requests.packages.urllib3.exceptions (/usr/local/lib/python3.5/site-packages/requests/packages/urllib3.py)
```

If you saw that in a bug report, you'd almost immediately know that the user has some local override that's potentially causing the problem.
msg287719 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2017-02-13 18:24
So 'path' already exists on ImportError so tweaking the message when the path is known wouldn't be difficult (https://docs.python.org/3/library/exceptions.html?highlight=importerror#ImportError).
msg287741 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-02-14 08:38
Unsure if GitHub PR autolinks here. 

I'm attempting part of that at https://github.com/python/cpython/pull/91

AFAICT with my little knowledge of CPython internal, `name` and `path` where unset for `from ... import`. 

I'll be happy to change the formatting as well but I suspect this will lead to lot of bikeshedding.
msg287802 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2017-02-15 00:05
New changeset bc4bed440504597cac47d0a215ee094bfa99ba7e by Brett Cannon in branch 'master':
bpo-29546: Set 'path' on ImportError for ``from ... import ...`` (GH-91)
https://github.com/python/cpython/commit/bc4bed440504597cac47d0a215ee094bfa99ba7e
msg287803 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2017-02-15 00:31
Thanks to Matthias' PR the information is all there in the exception, but the message has not been changed. One idea for this -- depending on how much C code you want to write -- is to provide a default message for __str__() that changes depending on whether 'path' and/or 'name' are set. Then you can just set the attributes in the __init__() and have __str__() take care of providing a common message format. Another option is to do all of that in the __init__() so that BaseException.args continues to have the full error message (but that is added overhead if the __str__() is never taken of the exception). I also have no clue how much C code this would take :) (This is all why I have toyed with the idea of re-implementing the exceptions in Python for easier customization.)
msg287804 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-02-15 00:51
I'm unsure I understand changing only the default __str__() method. You will anyway have to format the message differently depending on whether you raise from a from-import or a from-import-* or any other locations. 

AFAIU you "just" need the following 

  -        PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, pkgpath);
  +        PyErr_SetImportError(
  +                PyUnicode_FromFormat("cannot import name %R from %R (%S)",
  +                    name, pkgname, pkgpath),
  +                pkgname, pkgpath);

To use Barry format (though keeping quotes around identifiers to match current behavior).
(And similar if path is null). 

I'm unsure if you meant to provide a set of "format-template" to ImportError that are guarantied to be called with format(name=..., path=...) but I doubt it.
msg287805 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-02-15 01:01
See https://github.com/python/cpython/pull/103 that implements Barry's proposed format.
msg290430 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2017-03-24 23:52
New changeset 1bc156430bad8177b5beecf57979628c1d071230 by Barry Warsaw (Matthias Bussonnier) in branch 'master':
bpo-29546: Improve from-import error message with location (#103)
https://github.com/python/cpython/commit/1bc156430bad8177b5beecf57979628c1d071230
History
Date User Action Args
2017-03-24 23:52:03barrysetmessages: + msg290430
2017-03-18 19:16:29serhiy.storchakasetpull_requests: - pull_request615
2017-03-17 21:00:35larrysetpull_requests: + pull_request615
2017-02-22 15:08:42barrysetstatus: open -> closed
resolution: fixed
stage: resolved
2017-02-17 17:46:47levkivskyisetnosy: + levkivskyi
2017-02-15 01:01:11mbussonnsetmessages: + msg287805
2017-02-15 01:00:11mbussonnsetpull_requests: + pull_request66
2017-02-15 00:51:41mbussonnsetmessages: + msg287804
2017-02-15 00:31:59brett.cannonsetmessages: + msg287803
2017-02-15 00:05:27brett.cannonsetmessages: + msg287802
2017-02-14 22:20:28mbussonnsetpull_requests: + pull_request64
2017-02-14 22:20:28mbussonnsetpull_requests: + pull_request63
2017-02-14 22:20:28mbussonnsetpull_requests: + pull_request62
2017-02-14 08:38:41mbussonnsetnosy: + mbussonn
messages: + msg287741
2017-02-13 18:24:56brett.cannonsettype: enhancement
2017-02-13 18:24:50brett.cannonsetnosy: + brett.cannon
messages: + msg287719
2017-02-13 16:30:11barrycreate