InputError: Show an error message without a traceback

(Thomas Kluyver) #1

When writing command line applications, I often end up defining some classes of ‘input error’, which relate to bad input - from command line arguments, config files, etc. The traceback to the code that raised them isn’t important, so the top level of my code does something like this to hide tracebacks for those errors:

try:
    main()
except InputError as e:
    sys.exit(str(e))  # Print the message to stderr and exit with status 1

This is analogous to the difference between HTTP 5xx errors (something went wrong on the server) and 4xx errors (the client made a ‘bad’ request in some way).

So my proposal is that Python’s default excepthook should do something similar - either with a new built-in exception type such as InputError, or some parameter or attribute you can set on any exception to suppress the traceback (akin to the existing __suppress_context__ attribute). This would come with an override, an environment variable or a python command line option that would show the normally-hidden traceback.

Why don’t I use SystemExit, which already behaves this way? Mostly gut feeling, but I’ll try to justify that a bit. SystemExit is a special exception type for cleanly exiting a running program, and catching it is a code smell, not least because it pushes developers towards extreme measures if they really want to exit. Code parsing and validating a config file may know this config file is invalid because XYZ, but it’s not up to the parsing function to decide that the application should terminate as a result. It’s the difference between describing a problem and proposing a solution.

More concretely:

  • Testing the code which raises this error: I want to catch and check for a semantically meaningful exception.
  • The same code might be used in a command-line context where it’s appropriate to exit, and in e.g. an interactive GUI where it’s appropriate to display an error message but not exit.
  • It might have meaning in other contexts, e.g. web applications might want to map InputError exceptions to HTTP 4xx errors.

What’s wrong with defining the exception type and handling it in the application, as I already do? It’s OK so long as the code raising the exception is part of the application, but if the parsing/validation happens in a separate library, there’s no standard way to do this. E.g. argparse currently calls sys.exit() if there are problems with the arguments it’s parsing - this is making an assumption about the context in which it’s called.

So what I’m suggesting is a standard means for code raising an exception to indicate that if this goes uncaught and is shown to the user, the error message should provide enough detail about the problem without a traceback, without necessarily telling the application that it should quit.

0 Likes

(Martijn Pieters) #2

I’d be against this. You are using Python in a highly specific context here, a command-line application, this kind of exception handling would impede other use cases, such as a web server or GUI app.

Moreover, I would absolutely prefer a tracebacks-on-by-default approach. If tracebacks reach your end users, then that’s a bug. Command-line tools should catch except Excepton as e: and turn that into a user-friendly message, with the exception and traceback logged for later developer analysis.

Note that specialised command-line frameworks like click already provide specific exceptions suitable for your use-case. click specifically provides two different classes of exception to handle different cases of exceptional situations, see their Exceptons documentation chapter.

0 Likes

(Thomas Kluyver) #3

On the contrary, this is part of why I proposed this. At the moment, e.g. argparse assumes that it is used in the main code path of a command line application, so if it finds a problem, it tries to exit the application. That’s obviously a reasonable guess for a library that parses command-line arguments, but it’s an assumption that a library shouldn’t be making. I want a better way to handle situations like that.

This is essentially what I already do, except that I turn only a specified subset of errors into user-friendly messages, and let others display a traceback as normal (my users are also developers, so if something unexpected goes wrong, tracebacks are good). This is what I’ll continue doing in the absence of support in Python, but I already outlined the limitations of it.

Thanks, that’s interesting to know about. This is the kind of thing I’m after. But the click classes have the same limitation as defining an exception type in my application: a library for e.g. parsing a config file shouldn’t depend on a particular command-line framework.

0 Likes

(Martijn Pieters) #4

Nor should a config parser assume that a validation error is due to user error! That’s use-case specific. Code driving a config parser library needs to catch the specific exceptions that indicate user error, and then handle those in a way that fits the current app. If that means raising a specific exception meant to indicate that the app should exit with a user-friendly message, that’s up to the specific app.

0 Likes

(Paul Moore) #5

I’d say this sounds more like a bug in argparse (or at least a bad design decision). Rather than calling sys.exit, it should be raising a library-specific exception, something like argparse.InvalidArgs and leave the decision to exit to the caller.

As far as suppressing the traceback is concerned, my experience is that displaying tracebacks to the user is a lousy UI, so I agree that a friendly message is the right way to go - but not having the traceback when an error occurs is a complete PITA, so throwing the traceback away at the interpreter level seems wrong to me - the application should decide what to do with the traceback data (put it in a log file, offer an “extra details” option to the user, or whatever.

This whole area is hard to get right, and I’m very much in favour of anything that makes it easier to write user-friendly error handling - but IMO it’s very much an area where “as simple as possible but no simpler” (my emphasis) applies.

1 Like

(Martijn Pieters) #6

Yeah, that’s an issue with just argparse, which I dislike too, I’d love to see it refactored to at least optionally not call parser.error(), but let you handle parsing errors differently. Still, you can also work around this, catching SystemExit there is a valid use of exception handling.

0 Likes

(Thomas Kluyver) #7

I’m fine with calling it a bug in argparse, but there isn’t currently a good solution: ‘fixing’ it to raise a standard exception by default would break the behaviour of many command line applications using argparse. Even if it had been designed that way from the start, it would require extra boilerplate to configure argparse for the most common, obvious use case.

To frame it slightly differently, I’m suggesting that code raising an exception should be able to provide a hint for how the exception behaves if it is not handled. This is already crudely possible by using SystemExit or a subclass, but I’d like more elegant ways of doing it.

0 Likes

(Thomas Kluyver) #8

As another loose analogy, consider warnings: different warning categories are handled differently by default, and this behaviour can be controlled either inside the code, or from outside by environment variables or command line options. I’m suggesting something similar (in the rough shape, not the details) for unhandled exceptions.

0 Likes

(Paul Moore) #9

Good point. So basically, you’re asking for a wider range of default behaviours for exceptions, beyond “exit with a traceback” (the default) and “exit with a message” (SystemExit)? That seems OK, other than the important detail of what intermediate behaviour(s) you want - which isn’t obvious from your original post, as I’m not clear how your InputError differs in behaviour from SystemExit.

Or do you just want some other exceptions to work like SystemExit - in which case, why not just subclass SystemExit (something that argparse could do, without breaking backward compatibility)? (I get that catching SystemExit itself feels like something that shouldn’t be done except in special cases, but I don’t see any such problem with catching a subclass that’s specifically defined as “will exit unless you catch it”.

0 Likes

(Thomas Kluyver) #10

Yes, plus better ways to control the behaviour - e.g. an environment variable to show the traceback anyway.

This works, especially as you can use multiple inheritance - MyExc(SystemExit, Exception) to make it look like a normal exception for except Exception cases. It feels like a bit of a hack, but maybe I should just live with that.

A related idea that comes from looking into this: SystemExit currently takes an integer exit status or a string message (with an implicit status of 1). Could we enhance it to allow specifying both a message and an exit status?

0 Likes

(Chris Jerdonek) #11

Unless I’m not fully understanding the proposal, one possible wrinkle or problem I see is that, in cases where exceptions like these are being raised from libraries, how the exception should be handled might depend not just on the code raising the exception, but also on how the application is using the library and what the application wants the UX to be like. Maybe an application wants exceptions like these from package1 to show a traceback, but exceptions from package2 to use the simpler behavior. Or maybe it wants to do some special handling with some subset of these exceptions from package1 (e.g. render the exception a bit differently), and so on.

Stated this way, the problem seems very similar to the parallel issue of controlling whether and how to log messages logged from various libraries. In this case, the logging module has a pretty involved set of ways to configure all of that.

Since it’s just a matter of defining a base class, I’ve never really felt inconvenienced by the solution of defining your own exception class to represent exceptions that should be rendered to the user.

Also, another use case is when developing a web API. There you would want to return the error message to the user rather than let it bubble up to a higher-level error handler. I’m not sure if this use case would fit into the proposal since the error handling would occur before being handled by the interpreter.

0 Likes

(Thomas Kluyver) #12

The application would still be in control of how the exception is handled and/or displayed - either through catching the exception and doing things with it, or setting sys.excepthook. But I’m thinking of giving libraries some more say over the default handling if the application doesn’t do anything like that.

That said, I’m not convinced myself that ‘input error’ is a semantically meaningful category, so maybe a new exception class is not the right approach.

0 Likes

(Jeroen Demeyer) #13

I think we can split the proposal in 2 pieces:

  1. Define a new exception class InputError for these kinds of errors.

  2. Change the way how InputError exceptions are displayed.

I think that 1. should be pretty uncontroversial, so maybe we should start with that?

0 Likes