views:

65

answers:

4

I want to implement some friendlier error messages if the user tries to run a python script which tries to import modules that have not been installed. This includes printing out instructions on how to install the missing module.

One way to do this would be to put a try..catch block around the imports, but this is a bit ugly since it would turn something simple like

import some_module

into

try:
  import some_module
except ImportError, e:
  handle_error(e)

and it would have to be added to every file. Additionally, ImportError doesn't seem to store the name of the missing module anywhere (except for in the message) so you would have to put a separate try..catch around each import if you need to know the name (like I do). Parsing the name of the module is not the option since the message carried by ImportError might change for python version to version and depending on the user's locale.

I guess I could use sys.excepthook to catch all exceptions and pass those except ImportError along. Or would it be possible to define something like

safe_import some_module

that would behave like I want?

Does anyone know of any existing solutions to this problem?

+2  A: 

I would put additional modules into the package which, when imported, print out the more helpful message, and then raise a regular ImportError. When the true module is installed, your modules will get shadowed (make sure you add the directory where they live at the end of sys.path).

Martin v. Löwis
I think this is probably the best proposed solution. It doesn't rely on any special "tricks" and because of that should be pretty robust. At the same time it doesn't require any changes in the source code (except maybe adding the correct path to sys.path which can be dealt with easily).
pafcu
+4  A: 

You can put, somewhere that it will always be executed (e.g. in site.py or sitecustomize.py):

import __builtin__

realimport = __builtin__.__import__

def myimport(modname, *a):
  try:
    return realimport(modname, *a)
  except ImportError, e:
    print "Oops, couldn't import %s (%s)" % (modname, e)
    print "Here: add nice directions and whatever else"
    raise

__builtin__.__import__ = myimport

See the __import__ docs here.

Alex Martelli
I guess one problem with this is that it doesn't play along nicely if someone else comes up with the same idea. If some other package uses the same trick you are out of luck (since it would install another version if __import__).
pafcu
+1  A: 

You don't have to catch ImportError for every import. You can use a global try..except block in the entry point of your script.

You can get the name of the missing module from a ImportError instance using the message property.

For example, if the entry point of your script is main.py:

if __name__ == '__main__':
    try:
        import module1
        import module2

        module1.main()
     except ImportError as error:
        print "You don't have module {0} installed".format(error.message[16:])

Don't import anything outside the try..except block. This will cover module1 and module2 and all the modules imported by them and so on.

As you said, you could define your own import_safe function:

def import_safe(module):
    try:
        return __import__(module)
    except ImportError as error:
        print "You don't have module {0} installed".format(error.message[16:])

Then you can use the function:

sys = import_safe('sys')
gtk = import_safe('gkt')

In my opinion, the fist strategy is better. The second one would make your code hard to read. And will change an essential part of the language.

Manuel Ceron
The problem with the first approach is that it tries to parse the error message (not even that, it just assumes the module name is at a specific position). This simply isn't an assumption that can be made: Error messages might be localized or change between python versions
pafcu
A: 

Replace sys.excepthook:

orig_excepthook = sys.excepthook

def my_excepthook(type, value, tb):
    orig_excepthook(type, value, tb)
    if issubclass(type, ImportError):
        # print some friendly message

sys.excepthook = orig_excepthook
Denis Otkidach