Index: Python/import.c =================================================================== --- Python/import.c (Revision 59449) +++ Python/import.c (Arbeitskopie) @@ -161,7 +161,7 @@ void _PyImportHooks_Init(void) { - PyObject *v, *path_hooks = NULL, *zimpimport; + PyObject *v, *path_hooks = NULL, *zimpimport, *pihr; int err = 0; /* adding sys.path_hooks and sys.path_importer_cache, setting up @@ -198,6 +198,14 @@ ); } + pihr = PyDict_New(); + if (pihr == NULL || + PySys_SetObject("post_import_hooks", pihr) != 0) { + PyErr_Print(); + Py_FatalError("initialization of post import hook registry " + "failed"); + } + zimpimport = PyImport_ImportModule("zipimport"); if (zimpimport == NULL) { PyErr_Clear(); /* No zip import module -- okay */ @@ -369,6 +377,7 @@ "path", "argv", "ps1", "ps2", "last_type", "last_value", "last_traceback", "path_hooks", "path_importer_cache", "meta_path", + "post_import_hooks", NULL }; @@ -623,6 +632,228 @@ "sys.modules failed"); } +/* post import hook API */ +PyObject * +PyImport_GetPostImportHooks(void) +{ + PyObject *pihr; + + pihr = PySys_GetObject("post_import_hooks"); + /* This should only happen during initialization */ + if (pihr == NULL) + return NULL; + + if (!PyDict_Check(pihr)) { + PyErr_SetString(PyExc_TypeError, + "post import registry is not a dict"); + } + return pihr; +} + +PyObject * +PyImport_NotifyPostImport(PyObject *module) +{ + static PyObject *name = NULL; + PyObject *mod_name = NULL, *registry = NULL, *o; + PyObject *hooks = NULL, *hook, *it = NULL; + int status = -1; + + if (name == NULL) { + name = PyUnicode_InternFromString("__name__"); + if (name == NULL) { + return NULL; + } + } + + if (module == NULL) { + return NULL; + } + + if (!PyModule_Check(module)) { + PyErr_Format(PyExc_TypeError, + "A module object was expected, got '%.200s'", + Py_Type(module)->tp_name); + Py_DECREF(module); + return NULL; + } + + if (PyModule_IsLazy(module)) { + /* nothing to do here */ + return module; + } + /* XXX check if module is in sys.modules ? */ + + registry = PyImport_GetPostImportHooks(); + if (registry == NULL) { + /* This should only happen during initialization */ + return module; + } + + mod_name = PyObject_GetAttr(module, name); + if (mod_name == NULL) { + goto error; + } + if (!PyUnicode_Check(mod_name)) { + PyObject *repr; + char *name; + + repr = PyObject_Repr(module); + name = repr ? PyUnicode_AsString(repr) : ""; + PyErr_Format(PyExc_TypeError, + "Module __name__ attribute of '%.200s' is not " + "string", name); + Py_XDECREF(repr); + goto error; + } + + hooks = PyDict_GetItem(registry, mod_name); + if (hooks == NULL || hooks == Py_None) { + /* Either no hooks are defined or they are already fired */ + if (hooks == NULL) { + PyErr_Clear(); + } + goto end; + } + if (!PyList_Check(hooks)) { + PyErr_Format(PyExc_TypeError, + "expected None or list of hooks, got '%.200s'", + Py_Type(hooks)->tp_name); + goto error; + } + + /* fire hooks */ + it = PyObject_GetIter(hooks); + if (it == NULL) { + goto error; + } + while ((hook = PyIter_Next(it)) != NULL) { + o = PyObject_CallFunctionObjArgs(hook, module, NULL); + Py_DECREF(hook); + if (o == NULL) { + goto error; + } + Py_DECREF(o); + } + + /* Mark hooks as fired */ + if (PyDict_SetItem(registry, mod_name, Py_None) < 0) { + goto error; + } + + end: + status = 0; + error: + Py_XDECREF(mod_name); + Py_XDECREF(it); + if (status < 0) { + return NULL; + } + else { + return module; + } +} + +PyObject * +PyImport_RegisterPostImportHook(PyObject *callable, PyObject *mod_name) +{ + PyObject *registry = NULL, *hooks = NULL; + int status = -1; + + if (!PyCallable_Check(callable)) { + PyErr_SetString(PyExc_TypeError, "expected callable"); + goto error; + } + if (!PyUnicode_Check(mod_name)) { + PyErr_SetString(PyExc_TypeError, "expected string"); + goto error; + } + + registry = PyImport_GetPostImportHooks(); + if (registry == NULL) { + goto error; + } + + lock_import(); + + hooks = PyDict_GetItem(registry, mod_name); + /* module may be already loaded, get the module object from sys */ + if (hooks == NULL || hooks == Py_None) { + PyObject *o, *modules; + PyObject *module = NULL; + + modules = PySys_GetObject("modules"); + if (modules == NULL) { + goto error; + } + module = PyObject_GetItem(modules, mod_name); + if (module == NULL) { + PyErr_Clear(); + /* somebody messed up sys.modules */ + if (hooks == Py_None) { + ; /* XXX error */ + } + } + /* mark hooks as fired */ + if (hooks == NULL) { + if (PyDict_SetItem(registry, mod_name, Py_None) < 0) { + goto error; + } + } + /* module is already loaded, fire hook immediately */ + if (module != NULL) { + o = PyObject_CallFunctionObjArgs(callable, module, NULL); + Py_DECREF(module); + if (o == NULL) { + goto error; + } + Py_DECREF(o); + goto end; + } + } + /* no hook registered so far */ + if (hooks == NULL) { + PyErr_Clear(); + hooks = PyList_New(0); + if (hooks == NULL) { + goto error; + } + if (PyDict_SetItem(registry, mod_name, hooks) < 0) { + goto error; + } + } + else { + if (!PyList_Check(hooks)) { + PyErr_Format(PyExc_TypeError, + "expected list of hooks, got '%.200s'", + Py_Type(hooks)->tp_name); + goto error; + } + } + + /* append a new callable */ + if (PyList_Append(hooks, callable) < 0) { + goto error; + } + + end: + status = 0; + error: + Py_XDECREF(callable); + Py_XDECREF(hooks); + Py_XDECREF(mod_name); + if (unlock_import() < 0) { + PyErr_SetString(PyExc_RuntimeError, + "not holding the import lock"); + return NULL; + } + if (status < 0) { + return NULL; + } + else { + Py_RETURN_NONE; + } +} + /* Execute a code object in a module and return the module object * WITH INCREMENTED REFERENCE COUNT. If an error occurs, name is * removed from sys.modules, to avoid leaving damaged module objects @@ -1040,7 +1271,23 @@ return 0; } +static int +is_lazy(char *name) +{ + PyObject *modules, *module; + modules = PySys_GetObject("modules"); + if (modules == NULL) { + return -1; + } + module = PyDict_GetItemString(modules, name); + if (module == NULL) { + return -1; + } + return PyModule_IsLazy(module); +} + + /* Return an importer object for a sys.path/pkg.__path__ item 'p', possibly by fetching it from the path_importer_cache dict. If it wasn't yet cached, traverse path_hooks until a hook is found @@ -1964,33 +2211,14 @@ return tail; } -/* For DLL compatibility */ -#undef PyImport_ImportModuleEx PyObject * -PyImport_ImportModuleEx(char *name, PyObject *globals, PyObject *locals, - PyObject *fromlist) -{ - PyObject *result; - lock_import(); - result = import_module_level(name, globals, locals, fromlist, -1); - if (unlock_import() < 0) { - Py_XDECREF(result); - PyErr_SetString(PyExc_RuntimeError, - "not holding the import lock"); - return NULL; - } - return result; -} -#define PyImport_ImportModuleEx(n, g, l, f) \ - PyImport_ImportModuleLevel(n, g, l, f, -1); - -PyObject * PyImport_ImportModuleLevel(char *name, PyObject *globals, PyObject *locals, PyObject *fromlist, int level) { PyObject *result; lock_import(); result = import_module_level(name, globals, locals, fromlist, level); + result = PyImport_NotifyPostImport(result); if (unlock_import() < 0) { Py_XDECREF(result); PyErr_SetString(PyExc_RuntimeError, @@ -2761,6 +2989,22 @@ return PyBool_FromLong((long) (p == NULL ? 0 : p->size)); } +static PyObject * +imp_is_lazy(PyObject *self, PyObject *args) +{ + char *name; + PyObject *mod; + + if (PyArg_ParseTuple(args, "s:is_lazy", &name)) { + return PyBool_FromLong(is_lazy(name)); + } + PyErr_Clear(); + if (!PyArg_ParseTuple(args, "O!:is_lazy", &PyModule_Type, &mod)) { + return NULL; + } + return PyBool_FromLong(PyModule_IsLazy(mod)); +} + static FILE * get_file(char *pathname, PyObject *fob, char *mode) { @@ -2902,6 +3146,31 @@ return PyModule_New(name); } +static PyObject * +imp_register_post_import_hook(PyObject *self, PyObject *args) +{ + PyObject *callable, *mod_name; + + if (!PyArg_ParseTuple(args, "OO:register_post_import_hook", + &callable, &mod_name)) + return NULL; + Py_INCREF(callable); + Py_INCREF(mod_name); + return PyImport_RegisterPostImportHook(callable, mod_name); +} + +static PyObject * +imp_post_import_notify(PyObject *self, PyObject *args) +{ + PyObject *mod; + + if (!PyArg_ParseTuple(args, "O:post_import_notify", &mod)) + return NULL; + + Py_INCREF(mod); + return PyImport_NotifyPostImport(mod); +} + /* Doc strings */ PyDoc_STRVAR(doc_imp, @@ -2951,15 +3220,29 @@ Release the interpreter's import lock.\n\ On platforms without threads, this function does nothing."); +PyDoc_STRVAR(doc_register_post_import_hook, +"register_post_import_hook(callable, module_name) -> None"); + +PyDoc_STRVAR(doc_post_import_notify, +"post_import_notify(module) -> None"); + +PyDoc_STRVAR(doc_is_lazy, +"is_lazy(name_or_module) -> bool"); + static PyMethodDef imp_methods[] = { {"find_module", imp_find_module, METH_VARARGS, doc_find_module}, {"get_magic", imp_get_magic, METH_NOARGS, doc_get_magic}, {"get_suffixes", imp_get_suffixes, METH_NOARGS, doc_get_suffixes}, + {"is_lazy", imp_is_lazy, METH_VARARGS, doc_is_lazy}, {"load_module", imp_load_module, METH_VARARGS, doc_load_module}, {"new_module", imp_new_module, METH_VARARGS, doc_new_module}, {"lock_held", imp_lock_held, METH_NOARGS, doc_lock_held}, {"acquire_lock", imp_acquire_lock, METH_NOARGS, doc_acquire_lock}, {"release_lock", imp_release_lock, METH_NOARGS, doc_release_lock}, + {"register_post_import_hook", imp_register_post_import_hook, + METH_VARARGS, doc_register_post_import_hook}, + {"post_import_notify", imp_post_import_notify, METH_VARARGS, + doc_post_import_notify}, /* The rest are obsolete */ {"get_frozen_object", imp_get_frozen_object, METH_VARARGS}, {"init_builtin", imp_init_builtin, METH_VARARGS}, Index: Include/moduleobject.h =================================================================== --- Include/moduleobject.h (Revision 59449) +++ Include/moduleobject.h (Arbeitskopie) @@ -16,6 +16,7 @@ PyAPI_FUNC(PyObject *) PyModule_GetDict(PyObject *); PyAPI_FUNC(const char *) PyModule_GetName(PyObject *); PyAPI_FUNC(const char *) PyModule_GetFilename(PyObject *); +PyAPI_FUNC(int) PyModule_IsLazy(PyObject *); PyAPI_FUNC(void) _PyModule_Clear(PyObject *); #ifdef __cplusplus Index: Include/import.h =================================================================== --- Include/import.h (Revision 59449) +++ Include/import.h (Arbeitskopie) @@ -17,13 +17,6 @@ PyAPI_FUNC(PyObject *) PyImport_ImportModuleLevel(char *name, PyObject *globals, PyObject *locals, PyObject *fromlist, int level); -/* For DLL compatibility */ -#undef PyImport_ImportModuleEx -PyAPI_FUNC(PyObject *) PyImport_ImportModuleEx( - char *name, PyObject *globals, PyObject *locals, PyObject *fromlist); -#define PyImport_ImportModuleEx(n, g, l, f) \ - PyImport_ImportModuleLevel(n, g, l, f, -1) - PyAPI_FUNC(PyObject *) PyImport_GetImporter(PyObject *path); PyAPI_FUNC(PyObject *) PyImport_Import(PyObject *name); PyAPI_FUNC(PyObject *) PyImport_ReloadModule(PyObject *m); @@ -38,6 +31,12 @@ PyAPI_FUNC(PyObject *)_PyImport_FindExtension(char *, char *); PyAPI_FUNC(PyObject *)_PyImport_FixupExtension(char *, char *); +/* post import hook API */ +PyAPI_FUNC(PyObject *) PyImport_GetPostImportHooks(void); +PyAPI_FUNC(PyObject *) PyImport_NotifyPostImport(PyObject *module); +PyAPI_FUNC(PyObject *) PyImport_RegisterPostImportHook( + PyObject *callable, PyObject *mod_name); + struct _inittab { char *name; void (*initfunc)(void); Index: Objects/object.c =================================================================== --- Objects/object.c (Revision 59449) +++ Objects/object.c (Arbeitskopie) @@ -191,6 +191,7 @@ "%s:%i object at %p has negative ref count " "%" PY_FORMAT_SIZE_T "d", fname, lineno, op, op->ob_refcnt); + _PyObject_Dump(op); Py_FatalError(buf); } Index: Objects/moduleobject.c =================================================================== --- Objects/moduleobject.c (Revision 59449) +++ Objects/moduleobject.c (Arbeitskopie) @@ -146,6 +146,27 @@ } +int +PyModule_IsLazy(PyObject *m) +{ + PyObject *lazy; + int result; + + if (!PyModule_Check(m)) { + PyErr_BadArgument(); + return -1; + } + + lazy = PyObject_GetAttrString(m, "__lazy_import__"); + if (lazy == NULL) { + PyErr_Clear(); + return 0; + } + result = PyObject_IsTrue(lazy); + Py_DECREF(lazy); + return result; +} + /* Methods */ static int Index: Lib/test/test_imp.py =================================================================== --- Lib/test/test_imp.py (Revision 59449) +++ Lib/test/test_imp.py (Arbeitskopie) @@ -1,4 +1,5 @@ import imp +import sys import thread import unittest from test import test_support @@ -60,11 +61,86 @@ '"""Tokenization help for Python programs.\n') fp.close() +def f(mod): + pass +class CallBack: + def __init__(self): + self.mods = {} + + def __call__(self, mod): + self.mods[mod.__name__] = mod + #self.spam = object() + +class PostImportHookTests(unittest.TestCase): + + def setUp(self): + self.pihr = sys.post_import_hooks.copy() + + def tearDown(self): + sys.post_import_hooks = self.pihr + + def test_registry(self): + reg = sys.post_import_hooks + self.assert_(isinstance(reg, dict)) + + def test_register_callback_existing(self): + callback = CallBack() + imp.register_post_import_hook(callback, "sys") + + # sys is already loaded and the callback is fired immediately + self.assert_("sys" in callback.mods, callback.mods) + self.assert_(callback.mods["sys"] is sys, callback.mods) + self.failIf("telnetlib" in callback.mods, callback.mods) + regc = sys.post_import_hooks.get("sys", False) + self.assert_(regc is None, regc) + + def test_register_callback_new(self): + callback = CallBack() + # an arbitrary module + if "telnetlib" in sys.modules: + del sys.modules["telnetlib"] + imp.register_post_import_hook(callback, "telnetlib") + + regc = sys.post_import_hooks.get("telnetlib") + self.assert_(regc is not None, regc) + self.assert_(isinstance(regc, list), regc) + self.assert_(callback in regc, regc) + + import telnetlib + self.assert_("telnetlib" in callback.mods, callback.mods) + self.assert_(callback.mods["telnetlib"] is telnetlib, callback.mods) + + def test_post_import_notify(self): + imp.post_import_notify(sys) + self.failUnlessRaises(TypeError, imp.post_import_notify, None) + self.failUnlessRaises(TypeError, imp.post_import_notify, object()) + +class LazyImportTests(unittest.TestCase): + + def test_is_lazy(self): + mod = imp.new_module("lazyimporttest") + name = mod.__name__ + self.assertEqual(imp.is_lazy(mod), False) + mod.__lazy_import__ = True + self.assertEqual(imp.is_lazy(mod), True) + mod.__lazy_import__ = False + self.assertEqual(imp.is_lazy(mod), False) + + sys.modules[name] = mod + self.assertEqual(imp.is_lazy(name), False) + mod.__lazy_import__ = True + self.assertEqual(imp.is_lazy(name), True) + mod.__lazy_import__ = False + self.assertEqual(imp.is_lazy(name), False) + del sys.modules[name] + def test_main(): test_support.run_unittest( LockTests, ImportTests, + PostImportHookTests, + LazyImportTests, ) if __name__ == "__main__": Index: Modules/gcmodule.c =================================================================== --- Modules/gcmodule.c (Revision 59449) +++ Modules/gcmodule.c (Arbeitskopie) @@ -271,6 +271,10 @@ * generation being collected, which can be recognized * because only they have positive gc_refs. */ +#ifdef Py_DEBUG + if (gc->gc.gc_refs == 0) + _PyObject_Dump(op); +#endif assert(gc->gc.gc_refs != 0); /* else refcount was too small */ if (gc->gc.gc_refs > 0) gc->gc.gc_refs--;