← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~mbp/testtools/try-import-error into lp:testtools

 

Martin Pool has proposed merging lp:~mbp/testtools/try-import-error into lp:testtools.

Requested reviews:
  testtools developers (testtools-dev)

For more details, see:
https://code.launchpad.net/~mbp/testtools/try-import-error/+merge/62841

jml pointed out try_import and try_imports. I would like to use this in Bazaar to clean up some similar existing code.  We want to log the fact that a module isn't available because it might indicate an installation problem.  This adds a callback to observe the error.
-- 
https://code.launchpad.net/~mbp/testtools/try-import-error/+merge/62841
Your team testtools developers is requested to review the proposed merge of lp:~mbp/testtools/try-import-error into lp:testtools.
=== modified file 'testtools/helpers.py'
--- testtools/helpers.py	2011-03-22 15:25:38 +0000
+++ testtools/helpers.py	2011-05-30 07:22:23 +0000
@@ -6,7 +6,7 @@
     ]
 
 
-def try_import(name, alternative=None):
+def try_import(name, alternative=None, error_callback=None):
     """Attempt to import ``name``.  If it fails, return ``alternative``.
 
     When supporting multiple versions of Python or optional dependencies, it
@@ -16,29 +16,37 @@
         ``os.path.join``.
     :param alternative: The value to return if no module can be imported.
         Defaults to None.
+    :param error_callback: If non-None, a callable that is passed the ImportError
+        when the module cannot be loaded.
     """
     module_segments = name.split('.')
+    last_error = None
     while module_segments:
         module_name = '.'.join(module_segments)
         try:
             module = __import__(module_name)
-        except ImportError:
+        except ImportError, e:
+            last_error = e
             module_segments.pop()
             continue
         else:
             break
     else:
+        if last_error is not None and error_callback is not None:
+            error_callback(last_error)
         return alternative
     nonexistent = object()
     for segment in name.split('.')[1:]:
         module = getattr(module, segment, nonexistent)
         if module is nonexistent:
+            if last_error is not None and error_callback is not None:
+                error_callback(last_error)
             return alternative
     return module
 
 
 _RAISE_EXCEPTION = object()
-def try_imports(module_names, alternative=_RAISE_EXCEPTION):
+def try_imports(module_names, alternative=_RAISE_EXCEPTION, error_callback=None):
     """Attempt to import modules.
 
     Tries to import the first module in ``module_names``.  If it can be
@@ -50,12 +58,14 @@
     :param module_names: A sequence of module names to try to import.
     :param alternative: The value to return if no module can be imported.
         If unspecified, we raise an ImportError.
+    :param error_callback: If None, called with the ImportError for *each* 
+        module that fails to load.
     :raises ImportError: If none of the modules can be imported and no
         alternative value was specified.
     """
     module_names = list(module_names)
     for module_name in module_names:
-        module = try_import(module_name)
+        module = try_import(module_name, error_callback=error_callback)
         if module:
             return module
     if alternative is _RAISE_EXCEPTION:


Follow ups