Index: Python/import.c =================================================================== --- Python/import.c (Revision 59859) +++ Python/import.c (Arbeitskopie) @@ -163,7 +163,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 @@ -200,6 +200,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 */ @@ -371,6 +379,7 @@ "path", "argv", "ps1", "ps2", "last_type", "last_value", "last_traceback", "path_hooks", "path_importer_cache", "meta_path", + "post_import_hooks", NULL }; @@ -625,6 +634,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_NotifyModuleLoaded(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 = PyImport_GetModuleDict(); + 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; + } +} + static PyObject * get_sourcefile(const char *file); /* Execute a code object in a module and return the module object @@ -1078,7 +1309,23 @@ return 0; } +static int +is_lazy(char *name) +{ + PyObject *modules, *module; + modules = PyImport_GetModuleDict(); + 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 @@ -2056,6 +2303,7 @@ PyObject *result; lock_import(); result = import_module_level(name, globals, locals, fromlist, level); + result = PyImport_NotifyModuleLoaded(result); if (unlock_import() < 0) { Py_XDECREF(result); PyErr_SetString(PyExc_RuntimeError, @@ -2827,6 +3075,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) { @@ -2969,6 +3233,60 @@ } static PyObject * +imp_new_lazy_module(PyObject *self, PyObject *args) +{ + char *name; + if (!PyArg_ParseTuple(args, "s:new_module", &name)) + return NULL; + return PyModule_NewLazy(name); +} + +static PyObject * +imp_import_lazy(PyObject *self, PyObject *args) +{ + char *name; + PyObject *mod; + if (!PyArg_ParseTuple(args, "s:new_module", &name)) + return NULL; + mod = PyModule_NewLazy(name); + if (mod != NULL) { + PyObject *modules; + + modules = PyImport_GetModuleDict(); + if (modules == NULL) + return NULL; + if (PyDict_SetItemString(modules, name, mod) < 0) + return NULL; + } + return mod; +} + +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_notify_module_loaded(PyObject *self, PyObject *args) +{ + PyObject *mod; + + if (!PyArg_ParseTuple(args, "O:notify_module_loaded", &mod)) + return NULL; + + Py_INCREF(mod); + return PyImport_NotifyModuleLoaded(mod); +} + +static PyObject * imp_reload(PyObject *self, PyObject *v) { return PyImport_ReloadModule(v); @@ -3028,15 +3346,37 @@ 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_notify_module_loaded, +"notify_module_loaded(module) -> module"); + +PyDoc_STRVAR(doc_is_lazy, +"is_lazy(name_or_module) -> bool"); + +PyDoc_STRVAR(doc_new_lazy_module, +"imp_new_lazy(name) -> lazy module"); + +PyDoc_STRVAR(doc_import_lazy, +"imp_new_lazy(name) -> lazy module"); + 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}, + {"import_lazy", imp_import_lazy, METH_VARARGS, doc_import_lazy}, {"load_module", imp_load_module, METH_VARARGS, doc_load_module}, {"new_module", imp_new_module, METH_VARARGS, doc_new_module}, + {"new_lazy_module", imp_new_lazy_module, METH_VARARGS, doc_new_lazy_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}, + {"notify_module_loaded", imp_notify_module_loaded, METH_VARARGS, + doc_notify_module_loaded}, {"reload", imp_reload, METH_O, doc_reload}, /* The rest are obsolete */ {"get_frozen_object", imp_get_frozen_object, METH_VARARGS}, Index: Include/moduleobject.h =================================================================== --- Include/moduleobject.h (Revision 59859) +++ Include/moduleobject.h (Arbeitskopie) @@ -7,17 +7,46 @@ extern "C" { #endif +/* md_lazy flag + * -1 real module can't be loaded, further attribute access raises an error + * 0 module is loaded + * 1 module is lazy, write and read access except __name__ and + * __lazy_import__ will load the real module. + * 2 Intermediate state of a real module, md_dict isn't cleared + */ + +typedef enum { + Py_MOD_INVALID = -1, + Py_MOD_LOADED, + Py_MOD_LAZY, + Py_MOD_KEEP_DICT +} PyModule_State; + +typedef struct { + PyObject_HEAD + PyObject *md_dict; + PyObject *md_name; + PyModule_State md_lazy; +} PyModuleObject; + PyAPI_DATA(PyTypeObject) PyModule_Type; #define PyModule_Check(op) PyObject_TypeCheck(op, &PyModule_Type) #define PyModule_CheckExact(op) (Py_TYPE(op) == &PyModule_Type) PyAPI_FUNC(PyObject *) PyModule_New(const char *); +PyAPI_FUNC(PyObject *) PyModule_NewLazy(const char *); 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(int) PyModule_LoadLazy(PyObject *); PyAPI_FUNC(void) _PyModule_Clear(PyObject *); +#define PyModule_GET_LAZY(m) (((PyModuleObject *)(m))->md_lazy) +#define PyModule_GET_NAME(m) (((PyModuleObject *)(m))->md_name) +#define PyModule_GET_DICT(m) (((PyModuleObject *)(m))->md_dict) + #ifdef __cplusplus } #endif Index: Include/import.h =================================================================== --- Include/import.h (Revision 59859) +++ Include/import.h (Arbeitskopie) @@ -35,6 +35,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_NotifyModuleLoaded(PyObject *module); +PyAPI_FUNC(PyObject *) PyImport_RegisterPostImportHook( + PyObject *callable, PyObject *mod_name); + struct _inittab { char *name; void (*initfunc)(void); Index: Include/pyerrors.h =================================================================== --- Include/pyerrors.h (Revision 59859) +++ Include/pyerrors.h (Arbeitskopie) @@ -124,6 +124,7 @@ PyAPI_DATA(PyObject *) PyExc_ImportError; PyAPI_DATA(PyObject *) PyExc_IndexError; PyAPI_DATA(PyObject *) PyExc_KeyError; +PyAPI_DATA(PyObject *) PyExc_LazyImportError; PyAPI_DATA(PyObject *) PyExc_KeyboardInterrupt; PyAPI_DATA(PyObject *) PyExc_MemoryError; PyAPI_DATA(PyObject *) PyExc_NameError; Index: Objects/object.c =================================================================== --- Objects/object.c (Revision 59859) +++ 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 59859) +++ Objects/moduleobject.c (Arbeitskopie) @@ -4,18 +4,29 @@ #include "Python.h" #include "structmember.h" -typedef struct { - PyObject_HEAD - PyObject *md_dict; -} PyModuleObject; +#define LAZY_MOD "__lazy_import__" +#define CHECK_VALID(mod, exc, retvalue) \ + if (PyModule_GET_LAZY(mod) == Py_MOD_INVALID) { \ + PyErr_SetString(exc, "Module can't be loaded"); \ + return retvalue; \ + } +#define LOAD_LAZY(mod, retvalue) \ + if (PyModule_GET_LAZY(mod) == Py_MOD_LAZY) { \ + if (PyModule_LoadLazy(mod) < 0) { \ + return retvalue; \ + } \ + } + +#define OFF(name) offsetof(PyModuleObject, name) static PyMemberDef module_members[] = { - {"__dict__", T_OBJECT, offsetof(PyModuleObject, md_dict), READONLY}, + {"__dict__", T_OBJECT, OFF(md_dict), READONLY}, {0} }; + PyObject * -PyModule_New(const char *name) +_PyModule_New(const char *name, int lazy) { PyModuleObject *m; PyObject *nameobj; @@ -32,7 +43,10 @@ goto fail; if (PyDict_SetItemString(m->md_dict, "__package__", Py_None) != 0) goto fail; - Py_DECREF(nameobj); + + m->md_name = nameobj; /* eat ref */ + m->md_lazy = lazy; + PyObject_GC_Track(m); return (PyObject *)m; @@ -43,33 +57,57 @@ } PyObject * -PyModule_GetDict(PyObject *m) +PyModule_New(const char *name) { + return _PyModule_New(name, Py_MOD_LOADED); +} + +PyObject * +PyModule_NewLazy(const char *name) +{ + return _PyModule_New(name, Py_MOD_LAZY); +} + +PyObject * +PyModule_GetDict(PyObject *mod) +{ PyObject *d; - if (!PyModule_Check(m)) { + if (!PyModule_Check(mod)) { PyErr_BadInternalCall(); return NULL; } - d = ((PyModuleObject *)m) -> md_dict; - if (d == NULL) - ((PyModuleObject *)m) -> md_dict = d = PyDict_New(); + CHECK_VALID(mod, PyExc_ImportError, NULL); + LOAD_LAZY(mod, NULL); + d = PyModule_GET_DICT(mod); + if (d == NULL) { + PyModule_GET_DICT(mod) = d = PyDict_New(); + } return d; } const char * -PyModule_GetName(PyObject *m) +PyModule_GetName(PyObject *mod) { PyObject *d; PyObject *nameobj; - if (!PyModule_Check(m)) { + + if (!PyModule_Check(mod)) { PyErr_BadArgument(); return NULL; } - d = ((PyModuleObject *)m)->md_dict; + CHECK_VALID(mod, PyExc_ImportError, NULL); + if (PyModule_GET_LAZY(mod) != Py_MOD_LOADED) { + nameobj = PyModule_GET_NAME(mod); + if (nameobj != NULL || PyUnicode_Check(nameobj)) { + return PyUnicode_AsString(nameobj); + } + PyErr_SetString(PyExc_SystemError, "nameless module"); + return NULL; + } + d = PyModule_GET_DICT(mod); if (d == NULL || (nameobj = PyDict_GetItemString(d, "__name__")) == NULL || - !PyUnicode_Check(nameobj)) - { + !PyUnicode_Check(nameobj)) { PyErr_SetString(PyExc_SystemError, "nameless module"); return NULL; } @@ -77,19 +115,22 @@ } const char * -PyModule_GetFilename(PyObject *m) +PyModule_GetFilename(PyObject *mod) { PyObject *d; PyObject *fileobj; - if (!PyModule_Check(m)) { + + if (!PyModule_Check(mod)) { PyErr_BadArgument(); return NULL; } - d = ((PyModuleObject *)m)->md_dict; + CHECK_VALID(mod, PyExc_ImportError, NULL); + LOAD_LAZY(mod, NULL); + + d = PyModule_GET_DICT(mod); if (d == NULL || (fileobj = PyDict_GetItemString(d, "__file__")) == NULL || - !PyUnicode_Check(fileobj)) - { + !PyUnicode_Check(fileobj)) { PyErr_SetString(PyExc_SystemError, "module filename missing"); return NULL; } @@ -97,7 +138,7 @@ } void -_PyModule_Clear(PyObject *m) +_PyModule_Clear(PyObject *mod) { /* To make the execution order of destructors for global objects a bit more predictable, we first zap all objects @@ -110,7 +151,7 @@ PyObject *key, *value; PyObject *d; - d = ((PyModuleObject *)m)->md_dict; + d = PyModule_GET_DICT(mod); if (d == NULL) return; @@ -146,22 +187,117 @@ } +int +PyModule_IsLazy(PyObject *mod) +{ + PyObject *lazy; + int result; + + if (!PyModule_Check(mod)) { + PyErr_BadArgument(); + return -1; + } + CHECK_VALID(mod, PyExc_ImportError, -1); + /* check struct var first */ + if (PyModule_GET_LAZY(mod) == Py_MOD_LAZY) + return 1; + + /* fall back to module attribute for Python code */ + lazy = PyObject_GetAttrString(mod, LAZY_MOD); + if (lazy == NULL) { + PyErr_Clear(); + return 0; + } + result = PyObject_IsTrue(lazy); + Py_DECREF(lazy); + return result; +} + +int +PyModule_LoadLazy(PyObject *lazymod) +{ + PyObject *modules; + PyObject *name; + PyObject *realmod; + PyModuleObject *real, *lazy; + char *cname; + + name = PyModule_GET_NAME(lazymod); + cname = PyUnicode_AsString(name); + if (cname == NULL) + return -1; + + modules = PyImport_GetModuleDict(); + if (modules == NULL) + return -1; + + /* remove the module from sys.modules first */ + if (PyDict_GetItem(modules, name) != NULL) { + if (PyDict_DelItem(modules, name) < 0) { + /* XXX */ + return -1; + } + } + realmod = PyImport_Import(name); + if (realmod == NULL) { + PyObject *p_type, *p_value, *p_traceback; + + PyErr_Fetch(&p_type, &p_value, &p_traceback); + if (PyErr_GivenExceptionMatches(p_type, PyExc_ImportError)) { + Py_CLEAR(p_type); + p_type = PyExc_LazyImportError; + Py_INCREF(p_type); + } + + lazy->md_lazy = Py_MOD_INVALID; + if (PyDict_SetItem(modules, name, lazymod) < 0) { + /* XXX */ + return -1; + } + PyErr_Restore(p_type, p_value, p_traceback); + return -1; + } + /* remove the real module from sys.modules */ + if (PyDict_DelItem(modules, name) < 0) { + /* XXX */ + return -1; + } + lazy = (PyModuleObject *)lazymod; + real = (PyModuleObject *)realmod; + /* clear lazy dict and assign real dict to lazy dict */ + Py_CLEAR(lazy->md_dict); + Py_INCREF(real->md_dict); + lazy->md_dict = real->md_dict; + /* preserve the real dict and decref the real module */ + real->md_lazy = Py_MOD_KEEP_DICT; + /* XXX the real module might still be around if it was imported in an + * import cycle 10 lines above. */ + Py_DECREF(realmod); + /* Add the lazy module with the real dict to sys.modules. */ + if (PyDict_SetItem(modules, name, lazymod) < 0) { + /* XXX */ + return -1; + } + PyModule_GET_LAZY(lazymod) = Py_MOD_LOADED; + return 0; +} + /* Methods */ static int -module_init(PyModuleObject *m, PyObject *args, PyObject *kwds) +module_init(PyObject *mod, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"name", "doc", NULL}; PyObject *dict, *name = Py_None, *doc = Py_None; if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|O:module.__init__", kwlist, &name, &doc)) return -1; - dict = m->md_dict; + dict = PyModule_GET_DICT(mod); if (dict == NULL) { dict = PyDict_New(); if (dict == NULL) return -1; - m->md_dict = dict; + PyModule_GET_DICT(mod) = dict; } if (PyDict_SetItemString(dict, "__name__", name) < 0) return -1; @@ -171,28 +307,36 @@ } static void -module_dealloc(PyModuleObject *m) +module_dealloc(PyObject *mod) { - PyObject_GC_UnTrack(m); - if (m->md_dict != NULL) { - _PyModule_Clear((PyObject *)m); - Py_DECREF(m->md_dict); + PyObject_GC_UnTrack(mod); + Py_CLEAR(PyModule_GET_NAME(mod)); + if (PyModule_GET_DICT(mod) != NULL && + PyModule_GET_LAZY(mod) != Py_MOD_KEEP_DICT) { + _PyModule_Clear(mod); + Py_DECREF(PyModule_GET_DICT(mod)); } - Py_TYPE(m)->tp_free((PyObject *)m); + Py_TYPE(mod)->tp_free(mod); } static PyObject * -module_repr(PyModuleObject *m) +module_repr(PyObject *mod) { const char *name; const char *filename; - name = PyModule_GetName((PyObject *)m); + name = PyModule_GetName(mod); if (name == NULL) { PyErr_Clear(); name = "?"; } - filename = PyModule_GetFilename((PyObject *)m); + if (PyModule_GET_LAZY(mod) == Py_MOD_LAZY) { + return PyUnicode_FromFormat("", name); + } + if (PyModule_GET_LAZY(mod) == Py_MOD_INVALID) { + return PyUnicode_FromFormat("", name); + } + filename = PyModule_GetFilename(mod); if (filename == NULL) { PyErr_Clear(); return PyUnicode_FromFormat("", name); @@ -204,12 +348,43 @@ is in a cycle, md_dict will be cleared as well, which will break the cycle. */ static int -module_traverse(PyModuleObject *m, visitproc visit, void *arg) +module_traverse(PyObject *mod, visitproc visit, void *arg) { - Py_VISIT(m->md_dict); + Py_VISIT(PyModule_GET_NAME(mod)); + Py_VISIT(PyModule_GET_DICT(mod)); return 0; } +PyObject * +module_getattr(PyObject *mod, PyObject *name) +{ + CHECK_VALID(mod, PyExc_AttributeError, NULL); + if (PyModule_GET_LAZY(mod) == Py_MOD_LAZY) { + char *cname; + + cname = PyUnicode_AsString(name); + if (cname == NULL) + return NULL; + if (strcmp(cname, "__name__") == 0) { + Py_INCREF(PyModule_GET_NAME(mod)); + return PyModule_GET_NAME(mod); + } + if (strcmp(cname, LAZY_MOD) == 0) { + Py_RETURN_TRUE; + } + LOAD_LAZY(mod, NULL); + } + return PyObject_GenericGetAttr(mod, name); +} + +int +module_setattr(PyObject *mod, PyObject *name, PyObject *value) +{ + CHECK_VALID(mod, PyExc_AttributeError, -1); + LOAD_LAZY(mod, -1); + return PyObject_GenericSetAttr(mod, name, value); +} + PyDoc_STRVAR(module_doc, "module(name[, doc])\n\ \n\ @@ -233,8 +408,8 @@ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - PyObject_GenericSetAttr, /* tp_setattro */ + module_getattr, /* tp_getattro */ + module_setattr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ Index: Objects/exceptions.c =================================================================== --- Objects/exceptions.c (Revision 59859) +++ Objects/exceptions.c (Arbeitskopie) @@ -498,7 +498,13 @@ SimpleExtendsException(PyExc_Exception, ImportError, "Import can't find module, or can't find name in module."); +/* + * LazyImportError extends ImportError + */ +SimpleExtendsException(PyExc_ImportError, LazyImportError, + "Import can't load lazy module."); + /* * EnvironmentError extends Exception */ @@ -1807,6 +1813,7 @@ PRE_INIT(SystemExit) PRE_INIT(KeyboardInterrupt) PRE_INIT(ImportError) + PRE_INIT(LazyImportError) PRE_INIT(EnvironmentError) PRE_INIT(IOError) PRE_INIT(OSError) @@ -1868,6 +1875,7 @@ POST_INIT(SystemExit) POST_INIT(KeyboardInterrupt) POST_INIT(ImportError) + POST_INIT(LazyImportError) POST_INIT(EnvironmentError) POST_INIT(IOError) POST_INIT(OSError) Index: Lib/test/test_imp.py =================================================================== --- Lib/test/test_imp.py (Revision 59859) +++ Lib/test/test_imp.py (Arbeitskopie) @@ -1,4 +1,5 @@ import imp +import sys import thread import unittest from test import test_support @@ -68,11 +69,84 @@ ## import sys ## self.assertRaises(ImportError, reload, sys) +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.notify_module_loaded(sys) + self.failUnlessRaises(TypeError, imp.notify_module_loaded, None) + self.failUnlessRaises(TypeError, imp.notify_module_loaded, 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 59859) +++ Modules/gcmodule.c (Arbeitskopie) @@ -269,6 +269,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--;