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 */ @@ -623,6 +631,239 @@ "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"); + } + + Py_INCREF(pihr); + 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 (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_IsLoaded(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; + } + + if (name == NULL) { + name = PyUnicode_InternFromString("__name__"); + if (name == NULL) { + return NULL; + } + } + + 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(); + } + Py_DECREF(mod_name); + Py_DECREF(registry); + return module; + } + 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_CallFunction(hook, "O", module); + 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(hooks); + Py_XDECREF(it); + Py_XDECREF(registry); + 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; + } + } + else { + Py_DECREF(hooks); + } + /* module is already loaded, fire hook immediately */ + if (module != NULL) { + o = PyObject_CallFunction(callable, "O", module); + Py_DECREF(module); + if (o == NULL) { + goto error; + } + else { + Py_DECREF(o); + goto end; + } + } + } + /* no hook registered so far */ + if (hooks == NULL) { + PyErr_Clear(); + hooks = PyList_New(1); + if (hooks == NULL) { + goto error; + } + Py_INCREF(callable); + if (PyList_SetItem(hooks, 0, callable) < 0) { + goto error; + } + if (PyDict_SetItem(registry, mod_name, hooks) < 0) { + goto error; + } + goto end; + } + /* append a new callable */ + if (!PyList_Check(hooks)) { + PyErr_Format(PyExc_TypeError, + "expected list of hooks, got '%.200s'", + Py_Type(hooks)->tp_name); + goto error; + } + + if (PyList_Append(hooks, callable) < 0) { + goto error; + } + Py_DECREF(hooks); + + end: + status = 0; + error: + Py_XDECREF(registry); + 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 @@ -1964,33 +2205,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, @@ -2902,6 +3124,30 @@ 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; + + 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,6 +3197,12 @@ 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"); + static PyMethodDef imp_methods[] = { {"find_module", imp_find_module, METH_VARARGS, doc_find_module}, {"get_magic", imp_get_magic, METH_NOARGS, doc_get_magic}, @@ -2960,6 +3212,10 @@ {"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_IsLoaded(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/moduleobject.c =================================================================== --- Objects/moduleobject.c (Revision 59449) +++ Objects/moduleobject.c (Arbeitskopie) @@ -146,6 +146,17 @@ } +/* Dummy method, always returns true */ +int +PyModule_IsLoaded(PyObject *m) +{ + if (!PyModule_Check(m)) { + PyErr_BadArgument(); + return -1; + } + return 1; +} + /* 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,59 @@ '"""Tokenization help for Python programs.\n') fp.close() +class CallBack: + def __init__(self): + self.mods = {} + def __call__(self, mod): + self.mods[mod.__name__] = mod + +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(self): + callback = CallBack() + # an arbitrary module + if "telnetlib" in sys.modules: + del sys.modules["telnetlib"] + imp.register_post_import_hook(callback, "telnetlib") + 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) + + 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()) + def test_main(): test_support.run_unittest( LockTests, ImportTests, + PostImportHookTests, ) 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--;