fdo#66025: Improve accuracy of ImportError traceback and message

The ImportError raised on an import failure with the uno module loaded
now includes a complete traceback and the original Python exception
message text, combined with the most relevant (nearest to failure if
imports are nested) uno lookup that also failed.

Cherry-picked from 948b6ea02ea9de7fb4e1e2baf95ecae3ba1cd54e plus previous
patches leading up to that, 7fd81244c21ad54a8b9766902fd7c34e8055b165 "fdo#66025:
Improve ImportError raised from _uno_import,"
329125abb63061214897e4f215d41cfa4b13b4b0 "fdo#66025: Minor clean-up of previous
patch," and fbe28de6fbfdce41544e4e93168d32661add8285 "fdo#66025: Simplify new
ImportError logic."

Change-Id: I8c22f22c2d96bdd7fb99a87273ba574e22a86923
Signed-off-by: Stephan Bergmann <sbergman@redhat.com>
diff --git a/pyuno/source/module/uno.py b/pyuno/source/module/uno.py
index 86011f3..d99884a 100644
--- a/pyuno/source/module/uno.py
+++ b/pyuno/source/module/uno.py
@@ -263,11 +263,14 @@ def _uno_import( name, *optargs, **kwargs ):
    try:
#       print "optargs = " + repr(optargs)
        return _g_delegatee( name, *optargs, **kwargs )
    except ImportError:
    except ImportError as e:
        # process optargs
        globals, locals, fromlist = list(optargs)[:3] + [kwargs.get('globals',{}), kwargs.get('locals',{}), kwargs.get('fromlist',[])][len(optargs):]
        if not fromlist:
        # from import form only, but skip if an uno lookup has already failed
        if not fromlist or hasattr(e, '_uno_import_failed'):
            raise
        # hang onto exception for possible use on subsequent uno lookup failure
        py_import_exc = e
    modnames = name.split( "." )
    mod = None
    d = sys.modules
@@ -281,26 +284,55 @@ def _uno_import( name, *optargs, **kwargs ):
    RuntimeException = pyuno.getClass( "com.sun.star.uno.RuntimeException" )
    for x in fromlist:
       if x not in d:
          failed = False
          if x.startswith( "typeOf" ):
             try: 
                d[x] = pyuno.getTypeByName( name + "." + x[6:len(x)] )
             except RuntimeException as e:
                raise ImportError( "type " + name + "." + x[6:len(x)] +" is unknown" )
             except RuntimeException:
                failed = True
          else:
            try:
                # check for structs, exceptions or interfaces
                d[x] = pyuno.getClass( name + "." + x )
            except RuntimeException as e:
            except RuntimeException:
                # check for enums 
                try:
                   d[x] = Enum( name , x )
                except RuntimeException as e2:
                except RuntimeException:
                   # check for constants
                   try:
                      d[x] = getConstantByName( name + "." + x )
                   except RuntimeException as e3:
                      # no known uno type !
                      raise ImportError( "type "+ name + "." +x + " is unknown" )
                   except RuntimeException:
                      failed = True

          if failed:
              # We have an import failure, but cannot distinguish between
              # uno and non-uno errors as uno lookups are attempted for all
              # "from xxx import yyy" imports following a python failure.
              #
              # The traceback from the original python exception is kept to
              # pinpoint the actual failing location, but in Python 3 the
              # original message is most likely unhelpful for uno failures,
              # as it will most commonly be a missing top level module,
              # like 'com'.  Our exception appends the uno lookup failure.
              # This is more ambiguous, but it plus the traceback should be
              # sufficient to identify a root cause for python or uno issues.
              #
              # Our exception is raised outside of the nested exception
              # handlers above, to avoid Python 3 nested exception
              # information for the RuntimeExceptions during lookups.
              #
              # Finally, a private attribute is used to prevent further
              # processing if this failure was in a nested import.  That
              # keeps the exception relevant to the primary failure point,
              # preventing us from re-processing our own import errors.

              uno_import_exc = ImportError(
                  "%s (or '%s.%s' is unknown)" % (py_import_exc, name, x)
                  ).with_traceback(py_import_exc.__traceback__)
              uno_import_exc._uno_import_failed = True
              raise uno_import_exc

    return mod

# private function, don't use