issue 14660: Implement PEP 420, namespace packages.

This commit is contained in:
Eric V. Smith 2012-05-24 20:21:04 -04:00
parent fa52cbd5e6
commit 984b11f88f
25 changed files with 4455 additions and 3412 deletions

View file

@ -259,6 +259,29 @@ enum zi_module_info {
MI_PACKAGE
};
/* Does this path represent a directory?
on error, return < 0
if not a dir, return 0
if a dir, return 1
*/
static int
check_is_directory(ZipImporter *self, PyObject* prefix, PyObject *path)
{
PyObject *dirpath;
PyObject *item;
/* See if this is a "directory". If so, it's eligible to be part
of a namespace package. We test by seeing if the name, with an
appended path separator, exists. */
dirpath = PyUnicode_FromFormat("%U%U%c", prefix, path, SEP);
if (dirpath == NULL)
return -1;
/* If dirpath is present in self->files, we have a directory. */
item = PyDict_GetItem(self->files, dirpath);
Py_DECREF(dirpath);
return item != NULL;
}
/* Return some information about a module. */
static enum zi_module_info
get_module_info(ZipImporter *self, PyObject *fullname)
@ -296,6 +319,46 @@ get_module_info(ZipImporter *self, PyObject *fullname)
return MI_NOT_FOUND;
}
/* The guts of "find_loader" and "find_module". Return values:
-1: error
0: no loader or namespace portions found
1: module/package found
2: namespace portion found: *namespace_portion will point to the name
*/
static int
find_loader(ZipImporter *self, PyObject *fullname, PyObject **namespace_portion)
{
enum zi_module_info mi;
*namespace_portion = NULL;
mi = get_module_info(self, fullname);
if (mi == MI_ERROR)
return -1;
if (mi == MI_NOT_FOUND) {
/* Not a module or regular package. See if this is a directory, and
therefore possibly a portion of a namespace package. */
int is_dir = check_is_directory(self, self->prefix, fullname);
if (is_dir < 0)
return -1;
if (is_dir) {
/* This is possibly a portion of a namespace
package. Return the string representing its path,
without a trailing separator. */
*namespace_portion = PyUnicode_FromFormat("%U%c%U%U",
self->archive, SEP,
self->prefix, fullname);
if (*namespace_portion == NULL)
return -1;
return 2;
}
return 0;
}
/* This is a module or package. */
return 1;
}
/* Check whether we can satisfy the import of the module named by
'fullname'. Return self if we can, None if we can't. */
static PyObject *
@ -304,21 +367,78 @@ zipimporter_find_module(PyObject *obj, PyObject *args)
ZipImporter *self = (ZipImporter *)obj;
PyObject *path = NULL;
PyObject *fullname;
enum zi_module_info mi;
PyObject* namespace_portion = NULL;
if (!PyArg_ParseTuple(args, "U|O:zipimporter.find_module",
&fullname, &path))
return NULL;
goto error;
mi = get_module_info(self, fullname);
if (mi == MI_ERROR)
return NULL;
if (mi == MI_NOT_FOUND) {
switch (find_loader(self, fullname, &namespace_portion)) {
case -1: /* Error */
goto error;
case 0: /* Not found, return None */
Py_INCREF(Py_None);
return Py_None;
case 1: /* Return self */
Py_INCREF(self);
return (PyObject *)self;
case 2: /* A namespace portion, but not allowed via
find_module, so return None */
Py_DECREF(namespace_portion);
Py_INCREF(Py_None);
return Py_None;
}
Py_INCREF(self);
return (PyObject *)self;
/* Can't get here. */
assert(0);
return NULL;
error:
Py_XDECREF(namespace_portion);
return NULL;
}
/* Check whether we can satisfy the import of the module named by
'fullname', or whether it could be a portion of a namespace
package. Return self if we can load it, a string containing the
full path if it's a possible namespace portion, None if we
can't load it. */
static PyObject *
zipimporter_find_loader(PyObject *obj, PyObject *args)
{
ZipImporter *self = (ZipImporter *)obj;
PyObject *path = NULL;
PyObject *fullname;
PyObject *result = NULL;
PyObject *namespace_portion = NULL;
if (!PyArg_ParseTuple(args, "U|O:zipimporter.find_module",
&fullname, &path))
goto error;
switch (find_loader(self, fullname, &namespace_portion)) {
case -1: /* Error */
goto error;
case 0: /* Not found, return (None, []) */
if (!(result = Py_BuildValue("O[]", Py_None)))
goto error;
return result;
case 1: /* Return (self, []) */
if (!(result = Py_BuildValue("O[]", self)))
goto error;
return result;
case 2: /* Return (None, [namespace_portion]) */
if (!(result = Py_BuildValue("O[O]", Py_None, namespace_portion)))
goto error;
return result;
}
/* Can't get here. */
assert(0);
return NULL;
error:
Py_XDECREF(namespace_portion);
Py_XDECREF(result);
return NULL;
}
/* Load and return the module named by 'fullname'. */
@ -558,6 +678,16 @@ instance itself if the module was found, or None if it wasn't.\n\
The optional 'path' argument is ignored -- it's there for compatibility\n\
with the importer protocol.");
PyDoc_STRVAR(doc_find_loader,
"find_loader(fullname, path=None) -> self, str or None.\n\
\n\
Search for a module specified by 'fullname'. 'fullname' must be the\n\
fully qualified (dotted) module name. It returns the zipimporter\n\
instance itself if the module was found, a string containing the\n\
full path name if it's possibly a portion of a namespace package,\n\
or None otherwise. The optional 'path' argument is ignored -- it's\n\
there for compatibility with the importer protocol.");
PyDoc_STRVAR(doc_load_module,
"load_module(fullname) -> module.\n\
\n\
@ -599,6 +729,8 @@ Return the filename for the specified module.");
static PyMethodDef zipimporter_methods[] = {
{"find_module", zipimporter_find_module, METH_VARARGS,
doc_find_module},
{"find_loader", zipimporter_find_loader, METH_VARARGS,
doc_find_loader},
{"load_module", zipimporter_load_module, METH_VARARGS,
doc_load_module},
{"get_data", zipimporter_get_data, METH_VARARGS,