classification
Title: add ModuleNotFoundError
Type: enhancement Stage:
Components: Interpreter Core Versions: Python 3.4
process
Status: closed Resolution: rejected
Dependencies: 18200 Superseder:
Assigned To: brett.cannon Nosy List: Arfrever, Guido.van.Rossum, asvetlov, barry, brett.cannon, chris.jerdonek, cvrebert, eric.snow, ezio.melotti, gvanrossum, jcea, pitrou, python-dev, serhiy.storchaka, theller
Priority: normal Keywords:

Created on 2012-08-22 19:45 by eric.snow, last changed 2013-07-05 15:19 by jcea. This issue is now closed.

Messages (51)
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.
msg191046 - (view) Author: Roundup Robot (python-dev) Date: 2013-06-12 20:59
New changeset 8a0ed9f63c6e by Brett Cannon in branch 'default':
Issue #15767: Introduce ModuleNotFoundError, a subclass of
http://hg.python.org/cpython/rev/8a0ed9f63c6e
msg191054 - (view) Author: Roundup Robot (python-dev) Date: 2013-06-13 03:29
New changeset 3a50025f1900 by Brett Cannon in branch 'default':
Issue #15767: Touch up ModuleNotFoundError usage by import.
http://hg.python.org/cpython/rev/3a50025f1900
msg191055 - (view) Author: Roundup Robot (python-dev) Date: 2013-06-13 03:38
New changeset c6c0faaf65d7 by Brett Cannon in branch 'default':
Issue #15767: Add an explicit test for raising ModuleNotFoundError
http://hg.python.org/cpython/rev/c6c0faaf65d7
msg192095 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-07-01 00:00
Hey Brett,

Sorry for reopening this issue.  I am confused by the spec for ModuleNotFoundError.  Look at this (in a pretty recent repo):

$ ./python.exe
Python 3.4.0a0 (default:8f22e03f5f07, Jun 27 2013, 08:49:16) 
[GCC 4.2.1 Compatible Apple Clang 4.0 ((tags/Apple/clang-421.0.60))] on darwin
Type "help", "copyright", "credits" or "license" for more information.

[1]
>>> import bogus
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'bogus'

[2]
>>> from re import bogus
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: cannot import name bogus

[3]
>>> import re.bogus
Traceback (most recent call last):
  File "<frozen importlib._bootstrap>", line 1553, in _find_and_load_unlocked
AttributeError: 'module' object has no attribute '__path__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 're.bogus'; re is not a package

Given that it knows that re is not a package, I would have expected [2] not to raise ModuleNotFoundError, because there is no way that bogus could be a package.  OTOH, I would have expected [3] to raise ModuleNotFoundError, since this syntax implies that bogus is a submodule.

But perhaps I am missing something and I need to look at the distinction differently?  Sadly the docs don't really help me; they claim to explain why I get ModuleNotFoundError in [2], but the motivation "as the specific attribute being requested cannot be known a priori to be a module or some other type of object" seems wrong, given that [3] proves it *does* know.

(Aside, it's also odd that bogus is quoted in the error message for [1] and [3] but not for [2] -- in fact the phrasing of [2] compared to [1] seems arbitrarily different, both seem to tell me exactly the same thing.)
msg192139 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-07-01 16:54
Obviously no worries for opening this up again; if something isn't clear better to get it sorted out now rather than after 3.4 is out that door.

So I see two questions: why isn't ImportError being raised in the ``import re.bogus`` case and why the subtle difference in exception messages.

Let's deal with the latter first since it's the easiest: it's because that's how it was in Python 3.3::

> python3.3                                                                                  ~
Python 3.3.2 (default, Jun 19 2013, 13:30:51) 
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import bogus
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'bogus'

>>> from re import bogus
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name bogus

>>> import re.bogus
Traceback (most recent call last):
  File "<frozen importlib._bootstrap>", line 1521, in _find_and_load_unlocked
AttributeError: 'module' object has no attribute '__path__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 're.bogus'; re is not a package

All I did for ModuleNotFoundError is change what exception is raised. I didn't muck with any messages in adding this exception to keep the change minimal (at least to start with). It also doesn't help that in your case [2] (the ``from re import bogus`` case) is actually not handled by importlib but ceval.c. I really hate the semantics of ``from ... import`` and __import__ in this specific instance. Hopefully it can be something I can clean up in Python 4. Anyway, it can be updated to match: http://bugs.python.org/issue18342

As for the ``import re.bogus`` case not raising ModuleNotFoundError, I'm fine with changing it. I don't have a clear recollection as to why I chose to leave it as-is, but I also can't come up with a good reason to not change it.

And to explain why the ``from ... import`` case raises ModuleNotFoundError even when re is a module and obviously not a package is that while that might be true now, that does not necessarily hold in the future. If you have been using something like an object to hold attributes but then decide to switch to a module, this instance would break. Plus ``from ... import`` has never directly distinguished between a missing attribute and a missing module. Once again, something I would like to change in Python 4.
msg192142 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-07-01 17:43
Ok, the history of all that makes sense, except now I wonder if ModuleNotFoundError is useful at all.  I just reviewed a patch for 3.4 and in the latest revision all ImportError checks were replaced with ModuleNotFoundError checks.  What purpose does that have except encouraging code churn?
msg192143 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-07-01 17:45
Le lundi 01 juillet 2013 à 16:54 +0000, Brett Cannon a écrit :
> As for the ``import re.bogus`` case not raising ModuleNotFoundError,
> I'm fine with changing it.

+1.
msg192161 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-07-01 23:38
The impetus of ModuleNotFoundError is the need for something similar in importlib thanks to ``from ... import``. When you do something like __import__('os', fromlist=['path']), any ImportError must be silenced **if** it relates only to the fact that the module can't be found. Other errors directly related to technical errors which also raised ImportError are supposed to propagate.

Before ModuleNotFoundError I was setting a made-up attribute called _not_found on ImportError and then raising the exception. I could then catch the exception and introspect for that case as necessary. See http://hg.python.org/cpython/file/3e10025346c1/Lib/importlib/_bootstrap.py for the last revision that contained _not_found.

Since I had a legitimate case of wanting to differentiate between ImportError (which represents both technical errors and an inability to find a module) and the more specific case of simply not finding a module, I decided to formalize the distinction. It made sense to me so that the old try/except ImportError trick to ignore missing module could not accidentally catch the technical error use of ImportError by accident and mask the problem.

Now if you still think the subclass is not worth it then I probably need a better solution than a faked private attribute on ImportError to delineate the technical error/module not found difference as I had already received one bug report asking to define what _not_found was. ImportError could grow a not_found attribute formally or even a 'reason' attribute which could be set to some enum value representing the fact the exception was triggered because the module simply wasn't found (although that seems unnecessary and more like errno from OSError).

To summarize, the options I see are:

1. Back to _not_found on ImportError (only existed to start with as it was too late in 3.3 to come up with a better solution)

2. Keep ModuleNotFoundError (this option can include reverting all of the ``except ImportError`` changes I made and just allow the exception to exist if you want)

3. Add a not_found attribute to ImportError more formally (defaults to None for undefined, set to True/False to clearly signal when an obvious answer is known)

4. Add a 'reason' attribute that can be set to an enum which has a value which represents "module not found"

I can't easily make it a private exception that I prevent from escaping importlib thanks to trying to allow the accelerated version of import to use importlib's fromlist code (see how _handle_fromlist is called with its import_ argument).
msg192172 - (view) Author: Guido van Rossum (Guido.van.Rossum) Date: 2013-07-02 04:54
[switching to gmail-powered account]

I'm sorry, but this seems like it should be an importlib internal affair.  The new exception is too much in everyone's face, because the exception name gets printed on every traceback.

I like #4, #3 is also acceptable.  But #4 seems best because it can obviate a bunch of exception message parsing in user code, I'm sure.  Though we shouldn't go overboard with distinguishing cases, the two different places where you currently raise MNFE should be distinguished IMO.
msg192176 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2013-07-02 07:16
If most user code should catch a ModuleNotFoundError exception, perhaps it will be better rename old ImportError to ImportlibError (or ImportingError, or ImportMachineryError, or BaseImportError) and new ModuleNotFoundError to ImportError. This will left most existing user code untouched.
msg192189 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2013-07-02 12:03
On Jul 02, 2013, at 07:16 AM, Serhiy Storchaka wrote:

>If most user code should catch a ModuleNotFoundError exception, perhaps it
>will be better rename old ImportError to ImportlibError (or ImportingError,
>or ImportMachineryError, or BaseImportError) and new ModuleNotFoundError to
>ImportError. This will left most existing user code untouched.

I urge some caution here.  I don't know that the above will cause problems,
but I do think you're walking into PEP territory.  I would feel much more
comfortable with an analysis based on testing existing third party code.
msg192199 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-07-02 14:26
OK, I'll revert the changes related to ModuleNotFoundError.

As for adding a 'reason' attribute, I see two sticking points. One is how to set the enum value. There is both the C code issue (specifically so ceval.c and import.c can use the values) as well as importlib's bootstrapping restrictions. I'll have to think about whether there is any reasonable way to make it work.

Second, as you hinted at Guido, is exactly what the acceptable cases may be. I would probably start with any ImportError raised directly by import itself and nothing more. Other things from loaders (e.g. bad magic number, stale bytecode, etc.) could be initially left out. That would mean the following possible values:

* module is not a package
* module not found
* None in sys.modules

But the bootstrapping issues for the enum module is probably going to be the showstopper for this. That suggests either adding not_found or figuring out some way to prevent _not_found from leaking outside of importlib (which I now think I can do somewhat reasonably).
msg192200 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-07-02 14:41
> But the bootstrapping issues for the enum module is probably going
> to be the showstopper for this.

Have we succumbed to the enum religion already? Just make it a plain string.
msg192201 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-07-02 14:59
In this instance where there are only a set number of options are expected to be officially valid, yes I think enums are a good fit.

As for strings, the only way I would be okay with that is defining the strings either as attributes on ImportError itself or off of importlib to make it easy to do a comparison. But in that case I might as well just drop _not_found and use ``str(exc).startswith('No module named ')`` to detect what I need and be done with it.
msg192202 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-07-02 15:01
> In this instance where there are only a set number of options are
> expected to be officially valid, yes I think enums are a good fit.

They are a good fit, that doesn't mean they're the only one.

> As for strings, the only way I would be okay with that is defining
> the strings either as attributes on ImportError itself or off of
> importlib to make it easy to do a comparison.

What does that mean?
I don't understand how `exc.reason == 'module_not_found'` is
harder than `exc.reason == ImportReason.MODULE_NOT_FOUND`.
msg192207 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2013-07-02 16:06
It's not a question of harder but more error-prone since a typo in the string won't be directly noticed while mistyping an attribute name will be.
msg192209 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-07-02 16:16
> It's not a question of harder but more error-prone since a typo in
> the string won't be directly noticed while mistyping an attribute
> name will be.

Ok, agreed. I guess it's ok, if it only adds one or two attributes.
msg192261 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2013-07-04 00:44
> I'm sorry, but this seems like it should be an importlib internal
> affair.  The new exception is too much in everyone's face, because
> the exception name gets printed on every traceback.

That's the crux of the issue.  If there isn't much utility outside importlib to distinguishing between module-not-found and other causes of ImportError, then there isn't much point to a new exception.  It just boils down to what the other potential causes of ImportError are and how much people care about them.

I keep thinking about PEP 3151 (IOError/OSError hierarchy rework) and the lessons we've learned about exception attributes vs. subclasses.  For readability and write-ability, I'd rather write this:

  try:
      from _collections import OrderedDict
  except ModuleNotFoundError:
      pass

than this:

  try:
      from _collections import OrderedDict
  except ImportError as e:
      if e.reason is not importlib.machinery.ImportReason.MODULE_NOT_FOUND:
          raise

But the relevant question is, what is the benefit (outside importlib) of either over this:

  try:
      from _collections import OrderedDict
  except ImportError:
      pass
msg192263 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-07-04 02:09
Right.  Outside importlib there shouldn't be a need to distinguish between the cases (especially given that the exception works differently than its name suggests).
msg192318 - (view) Author: Roundup Robot (python-dev) Date: 2013-07-04 22:16
New changeset 0e4e062751fa by Brett Cannon in branch 'default':
Issue #15767: Back out 8d28d44f3a9a related to ModuleNotFoundError.
http://hg.python.org/cpython/rev/0e4e062751fa

New changeset e3ec8b176a80 by Brett Cannon in branch 'default':
Issue #15767: Revert 3a50025f1900 for ModuleNotFoundError
http://hg.python.org/cpython/rev/e3ec8b176a80

New changeset ee9662d77ebb by Brett Cannon in branch 'default':
Issue #15767: back out 8a0ed9f63c6e, finishing the removal of
http://hg.python.org/cpython/rev/ee9662d77ebb

New changeset de947db308ba by Brett Cannon in branch 'default':
Issue #15767: Excise the remaining instances of ModuleNotFoundError
http://hg.python.org/cpython/rev/de947db308ba
History
Date User Action Args
2013-07-05 15:19:14jceasetnosy: + jcea
2013-07-04 22:17:40brett.cannonsetstatus: open -> closed
resolution: rejected
2013-07-04 22:16:30python-devsetmessages: + msg192318
2013-07-04 19:36:36brett.cannonsetdependencies: + Update stdlib to use ModuleNotFoundError
2013-07-04 19:36:23brett.cannonunlinkissue18200 dependencies
2013-07-04 19:29:26brett.cannonlinkissue18302 dependencies
2013-07-04 02:09:54gvanrossumsetmessages: + msg192263
2013-07-04 00:44:56eric.snowsetmessages: + msg192261
2013-07-02 16:16:19pitrousetmessages: + msg192209
2013-07-02 16:06:40brett.cannonsetmessages: + msg192207
2013-07-02 15:01:11pitrousetmessages: + msg192202
2013-07-02 14:59:03brett.cannonsetmessages: + msg192201
2013-07-02 14:41:24pitrousetmessages: + msg192200
2013-07-02 14:26:57brett.cannonsetresolution: fixed -> (no value)
messages: + msg192199
stage: committed/rejected ->
2013-07-02 12:03:00barrysetmessages: + msg192189
2013-07-02 07:16:30serhiy.storchakasetmessages: + msg192176
2013-07-02 06:09:05thellersetnosy: + theller
2013-07-02 04:54:10Guido.van.Rossumsetnosy: + Guido.van.Rossum
messages: + msg192172
2013-07-01 23:38:20brett.cannonsetmessages: + msg192161
2013-07-01 17:45:17pitrousetmessages: + msg192143
2013-07-01 17:43:18gvanrossumsetmessages: + msg192142
2013-07-01 16:54:57brett.cannonsetmessages: + msg192139
2013-07-01 00:00:10gvanrossumsetstatus: closed -> open
nosy: + gvanrossum
messages: + msg192095

2013-06-13 03:38:57python-devsetmessages: + msg191055
2013-06-13 03:29:28python-devsetmessages: + msg191054
2013-06-12 21:01:54brett.cannonlinkissue18200 dependencies
2013-06-12 21:00:34brett.cannonsetstatus: open -> closed
resolution: fixed
stage: test needed -> committed/rejected
2013-06-12 20:59:54python-devsetnosy: + python-dev
messages: + msg191046
2013-06-12 20:08:46brett.cannonsettitle: add ImportNotFoundError -> add ModuleNotFoundError
2013-05-28 22:07:10brett.cannonsetassignee: brett.cannon
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