classification
Title: add ImportNotFoundError
Type: enhancement Stage: test needed
Components: Interpreter Core Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Arfrever, asvetlov, barry, brett.cannon, chris.jerdonek, cvrebert, eric.snow, ezio.melotti, pitrou, serhiy.storchaka
Priority: normal Keywords:

Created on 2012-08-22 19:45 by eric.snow, last changed 2013-02-20 03:50 by eric.snow.

Messages (31)
msg168906 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2012-08-22 19:45
In issue 15316 (msg168896, Brett Cannon):

  Create a ModuleNotFoundError exception that subclasses ImportError
  and catch that (breaks doctests and introduces a new exception that
  people will need to be aware of, plus the question of whether it
  should just exist in importlib or be a builtin)

While it's too late to go into 3.3, this is a reasonable addition for 3.4.  Perhaps other ImportError subclasses are warranted, but they can be addressed separately.
msg175638 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2012-11-15 20:40
Should it be ModuleNotFoundError or ModuleNotFound? It's not necessarily an error if a module doesn't exist, so failing to find it isn't quite that negative. It also acts somewhat like StopIteration/GeneratorExit which also don't have the common "Error" suffix of exception names.
msg175650 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-11-15 22:02
Since it subclasses ImportError, it seems like we're already considering it to be an error?  StopIteration and GeneratorExit don't inherit from an "Error" exception type unlike, say, the OSError exception types.
msg175651 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-11-15 22:03
I meant to include this link for convenience:

http://docs.python.org/dev/library/exceptions.html#exception-hierarchy
msg175659 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2012-11-16 00:03
I'd say ModuleNotFoundError.  You could argue that other exception types aren't really errors in certain circumstances.  I'd think that generally this would be an error.
msg175660 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2012-11-16 00:03
Effectively the exception indicates that the import system had an error.
msg175674 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2012-11-16 10:13
I prefer ModuleNotFound.  Its meaning is already clear enough, even without the Error suffix.
OTOH we now have FileNotFoundError, but all the other OSError subclasses have that suffix.
In general I think the suffix is necessary when it's not already clear from the name that we are dealing with an exception, otherwise it can be dropped.
msg175699 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2012-11-16 18:21
To state more explicitly the observation I alluded to in my comment above, we currently follow (without exception -- pun intended :) ) the convention that subclasses of exceptions that end in "Error" also end in "Error".  We also do the same with the suffix "Warning".

The latter is another reason to include the suffix "Error" -- to eliminate ambiguity as to whether the exception type inherits from a Warning class (e.g. from ImportWarning).
msg175703 - (view) Author: Ezio Melotti (ezio.melotti) * (Python committer) Date: 2012-11-16 18:53
That seems indeed to be the case with built-in exceptions.  I'm not sure if it's intentional or just a coincidence.  I agree that warnings should always have a "Warning" suffix to distinguish them from exceptions, but in the stdlib the "Error" suffix is not used consistently. There are exceptions like: FloatOperation, DivisionByZero, InvalidOperation, TimeoutExpired, BrokenProcessPool, BufferTooShort, ImproperConnectionState, UnknownProtocol, InvalidURL, etc..
Anyway I don't have a strong opinion about this, so if you think the name should be ModuleNotFoundError it's OK with me (i.e. I'm -0).
msg181901 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2013-02-11 15:40
For me, it mostly comes down to whether end-users are expected to see such errors generally or not.  We see ImportErrors all the time, and they are clearly errors.  If we're expected to see and deal with MNF, and if in such cases it's generally considered an error, then MNFError is better.  If it's mostly an internal/hidden thing, then MNF doesn't bother me.
msg181914 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-11 17:31
Right, so what's typical? =) I mean do most people see ImportError for optional modules (e.g. not on support platforms), or do most people see ImportError because they messed up and tried to import something that they expected but actually isn't there for some reason.
msg181919 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2013-02-11 18:33
On Feb 11, 2013, at 05:31 PM, Brett Cannon wrote:

>Right, so what's typical? =) I mean do most people see ImportError for
>optional modules (e.g. not on support platforms), or do most people see
>ImportError because they messed up and tried to import something that they
>expected but actually isn't there for some reason.

There are a few common use cases (or perhaps anti-use cases) where you see
ImportErrors.  I might be missing some, but I'd put these in roughly
descending order of commonness.

* Trying alternative imports for compatibility reasons.  You always expect
  ImportErrors in these cases, and you'll always catch them in try/excepts.

* Missing modules, submodules, or attributes in from-imports.  These can be
  unexpected if you think you've got the right version of a package, or
  expected for compatibility reasons.

* Trying to conditionally import optional modules.  Again, expected, and
  they'll be wrapped in try/except.

I guess the case you're trying to differentiate with MNF is, the from-import
case, i.e. did the error occur because the module was missing or because the
attribute was missing?

It's hard to say which is more likely, which I guess is why you're having a
hard time deciding. :) If I had to vote, I'd go with MNFError 1) because it's
a subclass of ImportError; 2) it'll be more informative in the case where it
really *is* an error; 3) isn't that big of a deal in cases where it's
expected; 4) we're used to seeing ImportError anyway, and probably most code
won't care and will just use ImportError.
msg181926 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-11 19:18
Screw it, I'll go with ModuleNotFoundError since it is a subclass of ImportError and so it might come off as weird as saying the superclass is an "Error" but the subclass is not.
msg182260 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2013-02-17 04:18
+1 on just getting it done with ModuleNotFoundError.  FWIW, I'd be glad to do it.  I'm taking a self-imposed break from the nearly finished C-OrderedDict!
msg182263 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2013-02-17 09:44
from foo import bar

Here bar can be not module, but an attribute of foo (for example, os.path). What error will be raised in this case? Module or attribute - this is an implementation detail; why do we distinguish between these cases?
msg182271 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-17 14:43
Eric: knock yourself out. =)

Serhiy: What exception is raised in that situation is controlled by the eval loop, not importlib so that would be a separate change. But regardless, there is no way to infer whether you expected an attribute or module to be there, just that you were after something that didn't exist. But I would argue most people import at the module level and not the attribute level, and so raising an ModuleNotFoundError would be acceptable.
msg182322 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-02-18 13:48
I've lost track of why this new exception was needed. Could someone provide a summary? Thanks :-)
msg182332 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-18 18:36
TL;DR for Antoine: when using a fromlist, import failures from that list are silently ignored. Because ImportError is overloaded in terms of what it means (e.g. bag magic number, module not found) there needs to be a clean way to tell the import failed because the module wasn't found so other ImportError reasons can properly propagate.
msg182339 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2013-02-18 20:10
>> from foo import bar
>> Here bar can be not module, but an attribute of foo (for example, os.path).
> Serhiy: What exception is raised in that situation is controlled by the eval loop, not importlib so that would be a separate change.

Just to clarify from this exchange, is there a chance we will use this same exception type (perhaps in a later change) in cases where bar is not found?  If so, I think it's worth considering something like "NotFoundImportError" or "ImportNotFoundError" that doesn't single out module.  Importing classes, etc. is quite a common pattern (e.g. examples appear in PEP 8).
msg182385 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-19 14:52
The original need was for internal importlib usage, but upon reflection it could also be used by the eval loop for that (http://hg.python.org/cpython/file/83d70dd58fef/Python/ceval.c#l4560), so I'm fine with changing the name to ImportNotFoundError.
msg182396 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-02-19 17:02
> The original need was for internal importlib usage, but upon
> reflection it could also be used by the eval loop for that
> (http://hg.python.org/cpython/file/83d70dd58fef/Python/ceval.c#l4560),
> so I'm fine with changing the name to ImportNotFoundError.

I don't understand what ImportNotFoundError means: an import was
not found? ModuleNotFoundError was obvious.
msg182401 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-19 17:33
Technically it should be ModuleOrSomeObjectNotFoundBecauseFromListIsTheBaneOfMyExistenceError, but we might be starting to mix paints for paints a shed shortly.

Fine, that's 1 to 1 for ModuleNotFoundError vs. ImportNotFoundError.
msg182404 - (view) Author: Chris Jerdonek (chris.jerdonek) * (Python committer) Date: 2013-02-19 18:28
If we can promise not to use it in the from-import case :) I'm okay with the more specific name (in fact it is preferable).  From Brett's response, it sounds like we have flexibility there and don't need it to be the same?  For from-import I would prefer the generic ImportError or adding a new type between ImportError and ModuleNotFoundError (like ImportNotFoundError) over using a name that is not entirely correct.
msg182409 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2013-02-19 19:24
Can this be the same ImportError but with special flag?
msg182411 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2013-02-19 19:44
On Feb 19, 2013, at 07:24 PM, Serhiy Storchaka wrote:

>Can this be the same ImportError but with special flag?

Like an attribute on the exception?  +1
msg182414 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-02-19 19:49
> >Can this be the same ImportError but with special flag?
> 
> Like an attribute on the exception?  +1

ImportError.has_different_meaning_but_too_lazy_to_create_a_distinct_exception_class_for_it ?

(or perhaps you would prefer the camelCase version :-))
msg182415 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-02-19 19:49
Chris: Having a more generic name for import-from at the eval loop level on top of NoModuleFoundError is breaking the "practicality over purity" rule. ImportSearchFailed might be the closest we can come to a generic name for what occurred.

Serihy & Barry: no. We do that now and it's already a nasty little hack. It would be better to let people catch an exception signaling that an import didn't happen because some module is missing than require introspection on a caught ImportError to tell what is going on (there's a reason why Antoine went to all of that trouble to add new exceptions so we don't have to look at the errno attribute on OSError). Exceptions are structured to work off of inheritance hierarchies (says the man who co-wrote the PEP to make all PEPs inherit from BaseException).
msg182437 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2013-02-19 21:56
On Feb 19, 2013, at 07:49 PM, Brett Cannon wrote:

>Serihy & Barry: no. We do that now and it's already a nasty little hack. It
>would be better to let people catch an exception signaling that an import
>didn't happen because some module is missing than require introspection on a
>caught ImportError to tell what is going on (there's a reason why Antoine
>went to all of that trouble to add new exceptions so we don't have to look at
>the errno attribute on OSError). Exceptions are structured to work off of
>inheritance hierarchies (says the man who co-wrote the PEP to make all PEPs
>inherit from BaseException).

The difference being that checking errno on OSError/IOError is essentially
required to do anything useful with it, while this one seems like a rare
corner case (we've been doing pretty well without it so far).
msg182455 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2013-02-20 00:25
The common case for catching ImportError is exactly what ModuleNotFoundError represents.  If people are already going to change their code to handle just this special case, I'd think having the subclass would be the better route.  I find it simpler (both to update existing code and to read:

    try:
        from _thread import _local as local
    except ModuleNotFoundError:
        from _threading_local import local

vs.

    try:
        from _thread import _local as local
    except ImportError as e:
        if e.not_found:
            from _threading_local import local
        else:
            raise

And for the broader case:

    try:
        import breakfast
    except ModuleNotFoundError:
        class breakfast:
            spam = 0
            eggs = 1
            ham = 2
    except ImportError as e:
        log_some_message("uh oh: {}".format(e))
        raise

vs.

    try:
        import breakfast
    except ImportError as e:
        if e.not_found:
            class breakfast:
                spam = 0
                eggs = 1
                ham = 2
        else:
            log_some_message("uh oh: {}".format(e))
            raise
msg182459 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2013-02-20 01:02
Why not just

    try:
        from _thread import _local as local
    except ImportError:
        from _threading_local import local

?
msg182466 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2013-02-20 03:50
Right.  Sorry I wasn't more clear.  Existing code will continue to work either way.  The point is in exposing the distinct module/name-not-found case, which I consider to be the common case for catching ImportError.  My examples cover the case where you want to handle the module-not-found case specifically.
History
Date User Action Args
2013-02-20 03:50:35eric.snowsetmessages: + msg182466
2013-02-20 01:02:13serhiy.storchakasetmessages: + msg182459
2013-02-20 00:25:08eric.snowsetmessages: + msg182455
2013-02-19 21:56:19barrysetmessages: + msg182437
2013-02-19 19:49:38brett.cannonsetmessages: + msg182415
2013-02-19 19:49:23pitrousetmessages: + msg182414
2013-02-19 19:44:30barrysetmessages: + msg182411
2013-02-19 19:24:21serhiy.storchakasetmessages: + msg182409
2013-02-19 18:28:16chris.jerdoneksetmessages: + msg182404
2013-02-19 17:33:31brett.cannonsetmessages: + msg182401
2013-02-19 17:02:43pitrousetmessages: + msg182396
2013-02-19 14:52:13brett.cannonsetmessages: + msg182385
title: add ModuleNotFoundError -> add ImportNotFoundError
2013-02-18 20:10:46chris.jerdoneksetmessages: + msg182339
2013-02-18 18:36:53brett.cannonsetmessages: + msg182332
2013-02-18 16:57:12makersetnosy: - maker
2013-02-18 13:48:06pitrousetnosy: + pitrou
messages: + msg182322
2013-02-17 21:01:15Arfreversetnosy: + Arfrever
2013-02-17 14:43:55brett.cannonsetmessages: + msg182271
2013-02-17 09:44:32serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg182263
2013-02-17 04:18:03eric.snowsetmessages: + msg182260
2013-02-11 19:18:32brett.cannonsetmessages: + msg181926
2013-02-11 18:33:40barrysetmessages: + msg181919
2013-02-11 17:31:00brett.cannonsetmessages: + msg181914
2013-02-11 15:40:40barrysetmessages: + msg181901
2013-02-11 15:37:52barrysetnosy: + barry
2012-11-16 18:53:46ezio.melottisetmessages: + msg175703
2012-11-16 18:21:15chris.jerdoneksetmessages: + msg175699
2012-11-16 10:13:58ezio.melottisetmessages: + msg175674
2012-11-16 00:03:56eric.snowsetmessages: + msg175660
2012-11-16 00:03:29eric.snowsetmessages: + msg175659
2012-11-15 22:03:24chris.jerdoneksetmessages: + msg175651
2012-11-15 22:02:29chris.jerdoneksetmessages: + msg175650
2012-11-15 20:40:47brett.cannonsetstage: needs patch -> test needed
2012-11-15 20:40:27brett.cannonsetmessages: + msg175638
2012-11-01 22:08:19asvetlovsetnosy: + asvetlov
2012-10-06 10:27:41makersetnosy: + maker
2012-08-27 07:34:41cvrebertsetnosy: + cvrebert
2012-08-26 05:54:10ezio.melottisetnosy: + ezio.melotti
2012-08-22 20:14:50chris.jerdoneksetnosy: + chris.jerdonek
2012-08-22 19:45:17eric.snowcreate