Repository: easybuilders/easybuild-framework

Issue:
easystack tests in test/framework/option.py fail when PyYAML is not installed
with python3, there is a proper error message mentioning the requirement, but with python2 the error is `TypeError: unbound method error() must be called with EasyStackParser instance as first argument (got str instance instead)`

this will likely also happen outside of the tests


Discussion:

Resolving patch:
diff --git a/easybuild/framework/easystack.py b/easybuild/framework/easystack.py
--- a/easybuild/framework/easystack.py
+++ b/easybuild/framework/easystack.py
@@ -86,7 +86,6 @@ def __init__(self, name, version, versionsuffix, toolchain_version, toolchain_na
 class EasyStackParser(object):
     """Parser for easystack files (in YAML syntax)."""
 
-    @only_if_module_is_available('yaml', pkgname='PyYAML')
     @staticmethod
     def parse(filepath):
         """Parses YAML file and assigns obtained values to SW config instances as well as general config instance"""
@@ -211,6 +210,7 @@ def parse(filepath):
         return easystack
 
 
+@only_if_module_is_available('yaml', pkgname='PyYAML')
 def parse_easystack(filepath):
     """Parses through easystack file, returns what EC are to be installed together with their options."""
     log_msg = "Support for easybuild-ing from multiple easyconfigs based on "
diff --git a/easybuild/tools/utilities.py b/easybuild/tools/utilities.py
--- a/easybuild/tools/utilities.py
+++ b/easybuild/tools/utilities.py
@@ -173,18 +173,18 @@ def wrap(orig):
                     pass
 
             if imported is None:
-                raise ImportError("None of the specified modules (%s) is available" % ', '.join(modnames))
+                raise ImportError
             else:
                 return orig
 
-        except ImportError as err:
-            # need to pass down 'err' via named argument to ensure it's in scope when using Python 3.x
-            def error(err=err, *args, **kwargs):
-                msg = "%s; required module '%s' is not available" % (err, modname)
+        except ImportError:
+            def error(*args, **kwargs):
+                msg = "None of the specified modules (%s) is available" % ', '.join(modnames)
                 if pkgname:
                     msg += " (provided by Python package %s, available from %s)" % (pkgname, url)
                 elif url:
                     msg += " (available from %s)" % url
+                msg += ", yet one of them is required!"
                 raise EasyBuildError("ImportError: %s", msg)
             return error
 


Test patch:
diff --git a/test/framework/general.py b/test/framework/general.py
--- a/test/framework/general.py
+++ b/test/framework/general.py
@@ -81,7 +81,8 @@ def foo2():
         def bar():
             pass
 
-        err_pat = "required module 'nosuchmoduleoutthere' is not available.*package nosuchpkg.*pypi/nosuchpkg"
+        err_pat = r"None of the specified modules \(nosuchmoduleoutthere\) is available.*"
+        err_pat += r"package nosuchpkg.*pypi/nosuchpkg"
         self.assertErrorRegex(EasyBuildError, err_pat, bar)
 
         @only_if_module_is_available(('nosuchmodule', 'anothernosuchmodule'))
@@ -96,7 +97,8 @@ class Foo():
             def foobar(self):
                 pass
 
-        err_pat = r"required module 'thisdoesnotexist' is not available \(available from http://example.com\)"
+        err_pat = r"None of the specified modules \(thisdoesnotexist\) is available "
+        err_pat += r"\(available from http://example.com\)"
         self.assertErrorRegex(EasyBuildError, err_pat, Foo().foobar)
 
     def test_docstrings(self):