diff --git a/Include/pyerrors.h b/Include/pyerrors.h --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -230,6 +230,13 @@ ... ); +typedef struct { + PyException_HEAD + PyObject *msg; + PyObject *name; + PyObject *path; +} PyImportErrorObject; + #ifdef MS_WINDOWS PyAPI_FUNC(PyObject *) PyErr_SetFromWindowsErrWithFilename( int ierr, @@ -255,6 +262,12 @@ PyAPI_FUNC(PyObject *) PyErr_SetExcFromWindowsErr(PyObject *, int); #endif /* MS_WINDOWS */ +PyAPI_FUNC(PyObject *) PyErr_SetExcWithArgsKwargs(PyObject *, PyObject *, + PyObject *); +PyAPI_FUNC(PyObject *) PyErr_SetFromImportErrorWithNameAndPath(PyObject *, + PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) PyErr_SetFromImportErrorWithName(PyObject *, PyObject *); + /* Export the old function so that the existing API remains available: */ PyAPI_FUNC(void) PyErr_BadInternalCall(void); PyAPI_FUNC(void) _PyErr_BadInternalCall(const char *filename, int lineno); diff --git a/Lib/pydoc.py b/Lib/pydoc.py --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -272,6 +272,7 @@ package path is specified, the module at the end of the path is returned, not the package at the beginning. If the optional 'forceload' argument is 1, we reload the module from disk (unless it's a dynamic extension).""" + print('Trying a safeimport for', path, forceload, cache, file=sys.stderr) try: # If forceload is 1 and the module has been previously loaded from # disk, we always have to reload the module. Checking the file's @@ -290,20 +291,25 @@ cache[key] = sys.modules[key] del sys.modules[key] module = __import__(path) + print('WORKED', file=sys.stderr) except: # Did the error occur before or after the module was found? (exc, value, tb) = info = sys.exc_info() + print('*', value, path, value.name, file=sys.stderr) if path in sys.modules: # An error occurred while executing the imported module. raise ErrorDuringImport(sys.modules[path].__file__, info) elif exc is SyntaxError: # A SyntaxError occurred before we could execute the module. raise ErrorDuringImport(value.filename, info) - elif exc is ImportError and extract_tb(tb)[-1][2]=='safeimport': + #elif exc is ImportError and extract_tb(tb)[-1][2]=='safeimport': + elif exc is ImportError and value.name == path.rpartition('.')[-1]: # The import error occurred directly in this function, # which means there is no such module in the path. + print('***', value, path, value.name, file=sys.stderr) return None else: + rint('**', value, path, value.name, file=sys.stderr) # Some other error occurred during the importing process. raise ErrorDuringImport(path, sys.exc_info()) for part in path.split('.')[1:]: diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py --- a/Lib/test/test_import.py +++ b/Lib/test/test_import.py @@ -307,6 +307,20 @@ """)) script_helper.assert_python_ok(testfn) + def test_exception_keywords(self): + with self.assertRaises(ImportError) as exc1: + raise ImportError('test', name='name') + self.assertEqual(exc1.exception.name, 'name') + + with self.assertRaises(ImportError) as exc2: + raise ImportError('test', path='path') + self.assertEqual(exc2.exception.path, 'path') + + with self.assertRaises(ImportError) as exc3: + raise ImportError('test', name='name', path='path') + self.assertEqual(exc3.exception.name, 'name') + self.assertEqual(exc3.exception.path, 'path') + def test_timestamp_overflow(self): # A modification timestamp larger than 2**32 should not be a problem # when importing a module (issue #11235). diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -396,6 +396,7 @@ f.write("import {}\n".format(importstring)) result = run_pydoc(modname, PYTHONPATH=TESTFN).decode("ascii") expected = badimport_pattern % (modname, expectedinmsg) + print(importstring) self.assertEqual(expected, result) def test_apropos_with_bad_package(self): @@ -526,12 +527,12 @@ @reap_threads def test_main(): try: - test.support.run_unittest(PydocDocTest, + test.support.run_unittest(#PydocDocTest, PydocImportTest, - TestDescriptions, - PydocServerTest, - PydocUrlHandlerTest, - TestHelper, + #TestDescriptions, + #PydocServerTest, + #PydocUrlHandlerTest, + #TestHelper, ) finally: reap_children() diff --git a/Objects/exceptions.c b/Objects/exceptions.c --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -628,9 +628,104 @@ /* * ImportError extends Exception */ -SimpleExtendsException(PyExc_Exception, ImportError, - "Import can't find module, or can't find name in module."); +static int +ImportError_init(PyImportErrorObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *msg = NULL; + PyObject *name = NULL; + PyObject *path = NULL; + +/* Macro replacement doesn't allow ## to start the first line of a macro, + so we move the assignment and NULL check into the if-statement. */ +#define GET_KWD(kwd) { \ + kwd = PyDict_GetItemString(kwds, #kwd); \ + if (kwd) { \ + Py_CLEAR(self->kwd); \ + self->kwd = kwd; \ + Py_INCREF(self->kwd);\ + if (PyDict_DelItemString(kwds, #kwd)) \ + return -1; \ + } \ + } + + if (kwds) { + GET_KWD(name); + GET_KWD(path); + } + + if (BaseException_init((PyBaseExceptionObject *)self, args, kwds) == -1) + return -1; + if (PyTuple_GET_SIZE(args) != 1) + return 0; + if (!PyArg_UnpackTuple(args, "ImportError", 1, 1, &msg)) + return -1; + + Py_CLEAR(self->msg); /* replacing */ + self->msg = msg; + Py_INCREF(self->msg); + + return 0; +} + +static int +ImportError_clear(PyImportErrorObject *self) +{ + Py_CLEAR(self->msg); + Py_CLEAR(self->name); + Py_CLEAR(self->path); + return BaseException_clear((PyBaseExceptionObject *)self); +} + +static void +ImportError_dealloc(PyImportErrorObject *self) +{ + _PyObject_GC_UNTRACK(self); + ImportError_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static int +ImportError_traverse(PyImportErrorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->msg); + Py_VISIT(self->name); + Py_VISIT(self->path); + return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg); +} + +static PyObject * +ImportError_str(PyImportErrorObject *self) +{ + if (self->msg) { + Py_INCREF(self->msg); + return self->msg; + } + else { + return BaseException_str((PyBaseExceptionObject *)self); + } +} + +static PyMemberDef ImportError_members[] = { + {"msg", T_OBJECT, offsetof(PyImportErrorObject, msg), 0, + PyDoc_STR("exception message")}, + {"name", T_OBJECT, offsetof(PyImportErrorObject, name), 0, + PyDoc_STR("module name")}, + {"path", T_OBJECT, offsetof(PyImportErrorObject, path), 0, + PyDoc_STR("module path")}, + {NULL} /* Sentinel */ +}; + +static PyMethodDef ImportError_methods[] = { + {NULL} +}; + +ComplexExtendsException(PyExc_Exception, ImportError, + ImportError, 0 /* new */, + ImportError_methods, ImportError_members, + 0 /* getset */, ImportError_str, + "Import can't find module, or can't find name in " + "module."); /* * OSError extends Exception diff --git a/Python/errors.c b/Python/errors.c --- a/Python/errors.c +++ b/Python/errors.c @@ -585,6 +585,53 @@ } #endif /* MS_WINDOWS */ +PyObject * +PyErr_SetExcWithArgsKwargs(PyObject *exc, PyObject *args, PyObject *kwargs) +{ + PyObject *val; + + /* args must at least be an empty tuple */ + if (args == NULL) + args = PyTuple_New(0); + + val = PyObject_Call(exc, args, kwargs); + if (val != NULL) { + PyErr_SetObject((PyObject *) Py_TYPE(val), val); + Py_DECREF(val); + } + + return NULL; +} + +PyObject * +PyErr_SetFromImportErrorWithNameAndPath(PyObject *msg, + PyObject *name, PyObject *path) +{ + PyObject *args = PyTuple_New(1); + PyObject *kwargs = PyDict_New(); + PyObject *result; + + if (path == NULL) + path = Py_None; + + PyTuple_SetItem(args, 0, msg); + PyDict_SetItemString(kwargs, "name", name); + PyDict_SetItemString(kwargs, "path", path); + + result = PyErr_SetExcWithArgsKwargs(PyExc_ImportError, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + return result; +} + +PyObject * +PyErr_SetFromImportErrorWithName(PyObject *msg, PyObject *name) +{ + return PyErr_SetFromImportErrorWithNameAndPath(msg, name, NULL); +} + void _PyErr_BadInternalCall(const char *filename, int lineno) { diff --git a/Python/import.c b/Python/import.c --- a/Python/import.c +++ b/Python/import.c @@ -871,9 +871,8 @@ Py_DECREF(v); if ((m = PyDict_GetItem(modules, name)) == NULL) { - PyErr_Format(PyExc_ImportError, - "Loaded module %R not found in sys.modules", - name); + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( + "Loaded module %R not found in sys.modules", name), name); return NULL; } @@ -1120,8 +1119,8 @@ if (co == NULL) return NULL; if (!PyCode_Check(co)) { - PyErr_Format(PyExc_ImportError, - "Non-code object in %R", cpathname); + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( + "Non-code object in %R", cpathname), cpathname); Py_DECREF(co); return NULL; } @@ -1141,8 +1140,8 @@ magic = PyMarshal_ReadLongFromFile(fp); if (magic != pyc_magic) { - PyErr_Format(PyExc_ImportError, - "Bad magic number in %R", cpathname); + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( + "Bad magic number in %R", cpathname), cpathname); return NULL; } /* Skip mtime and size */ @@ -2005,8 +2004,8 @@ } Py_DECREF(prefix); } - PyErr_Format(PyExc_ImportError, - "No module named %R", name); + PyErr_SetFromImportErrorWithName( + PyUnicode_FromFormat("No module named %R", name), name); return NULL; } @@ -2430,21 +2429,20 @@ if (err < 0) return NULL; if (err == 0) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "Purported %s module %R not found", type == C_BUILTIN ? "builtin" : "frozen", - name); + name), name); return NULL; } modules = PyImport_GetModuleDict(); m = PyDict_GetItem(modules, name); if (m == NULL) { - PyErr_Format( - PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "%s module %R not properly initialized", type == C_BUILTIN ? "builtin" : "frozen", - name); + name), name); return NULL; } Py_INCREF(m); @@ -2508,9 +2506,9 @@ } default: - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "Don't know how to import %R (type code %d)", - name, type); + name, type), name); m = NULL; } @@ -2536,9 +2534,9 @@ PyModuleDef *def; if (PyUnicode_CompareWithASCIIString(name, p->name) == 0) { if (p->initfunc == NULL) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "Cannot re-init internal module %R", - name); + name), name); return -1; } if (Py_VerboseFlag) @@ -2587,15 +2585,15 @@ int size; if (p == NULL) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "No such frozen object named %R", - name); + name), name); return NULL; } if (p->code == NULL) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "Excluded frozen object named %R", - name); + name), name); return NULL; } size = p->size; @@ -2611,9 +2609,9 @@ int size; if (p == NULL) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "No such frozen object named %R", - name); + name), name); return NULL; } @@ -2644,9 +2642,9 @@ if (p == NULL) return 0; if (p->code == NULL) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "Excluded frozen object named %R", - name); + name), name); return -1; } size = p->size; @@ -2772,10 +2770,10 @@ result = PyImport_Import(nameobj); } else { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "Failed to import %R because the import lock" "is held by another thread.", - nameobj); + nameobj), nameobj); result = NULL; } #else @@ -3169,8 +3167,8 @@ if (result == Py_None) { Py_DECREF(result); - PyErr_Format(PyExc_ImportError, - "No module named %R", inputname); + PyErr_SetFromImportErrorWithName( + PyUnicode_FromFormat("No module named %R", inputname), inputname); goto error; } @@ -3381,9 +3379,9 @@ if (name == NULL || PyUnicode_READY(name) == -1) return NULL; if (m != PyDict_GetItem(modules, name)) { - PyErr_Format(PyExc_ImportError, - "reload(): module %R not in sys.modules", - name); + PyErr_SetFromImportErrorWithName( + PyUnicode_FromFormat("reload(): module %R not in sys.modules", + name), name); Py_DECREF(name); return NULL; } @@ -3415,9 +3413,9 @@ } parent = PyDict_GetItem(modules, parentname); if (parent == NULL) { - PyErr_Format(PyExc_ImportError, + PyErr_SetFromImportErrorWithName(PyUnicode_FromFormat( "reload(): parent %R not in sys.modules", - parentname); + parentname), parentname); Py_DECREF(parentname); goto error; }