changeset 4833:70b1178dec79 v7.3.1163

updated for version 7.3.1163 Problem: Not easy to load Python modules. Solution: Search "python2", "python3" and "pythonx" directories in 'runtimepath' for Python modules. (ZyX)
author Bram Moolenaar <bram@vim.org>
date Mon, 10 Jun 2013 21:27:29 +0200
parents 54430b55ceff
children 88b448133884
files runtime/doc/if_pyth.txt src/auto/configure src/configure.in src/ex_cmds2.c src/if_py_both.h src/if_python.c src/if_python3.c src/testdir/test86.in src/testdir/test87.in src/version.c
diffstat 10 files changed, 627 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/if_pyth.txt
+++ b/runtime/doc/if_pyth.txt
@@ -180,6 +180,12 @@ vim.strwidth(str)					*python-strwidth*
 	Like |strwidth()|: returns number of display cells str occupies, tab 
 	is counted as one cell.
 
+vim.foreach_rtp(callable)				*python-foreach_rtp*
+	Call the given callable for each path in 'runtimepath' until either 
+	callable returns something but None, the exception is raised or there 
+	are no longer paths. If stopped in case callable returned non-None, 
+	vim.foreach_rtp function returns the value returned by callable.
+
 vim.chdir(*args, **kwargs)				*python-chdir*
 vim.fchdir(*args, **kwargs)				*python-fchdir*
 	Run os.chdir or os.fchdir, then all appropriate vim stuff.
@@ -300,6 +306,113 @@ Output from Python					*python-output*
 	supported, and may cause the program to crash.  This should probably be
 	fixed.
 
+		    *python2-directory* *python3-directory* *pythonx-directory*
+Python 'runtimepath' handling				*python-special-path*
+
+In python vim.VIM_SPECIAL_PATH special directory is used as a replacement for 
+the list of paths found in 'runtimepath': with this directory in sys.path and 
+vim.path_hooks in sys.path_hooks python will try to load module from 
+{rtp}/python2 (or python3) and {rtp}/pythonx (for both python versions) for 
+each {rtp} found in 'runtimepath'.
+
+Implementation for python 2 is the following: usual importing code with empty 
+lists in place of sys.path_hooks and sys.meta_path. Code is similar to the 
+below, but written in C: >
+
+    # Assuming vim variable is already accessible and is set to the current 
+    # module
+    import sys
+
+    def find_module(fullname):
+        return vim
+
+    def load_module(fullname):
+        # see vim._get_paths below
+        new_path = _get_paths()
+
+        try:         old_path = sys.path
+        except: pass
+        try:         old_meta_path = sys.meta_path
+        except: pass
+        try:         old_path_hooks = sys.path_hooks
+        except: pass
+
+        sys.meta_path = []
+        sys.path_hooks = sys.meta_path
+        sys.path = new_path
+
+        try:
+            exec ('import ' + fullname + ' as m')  # No actual exec in C code
+            return m
+        finally:
+            e = None
+            try:                        sys.path = old_path
+            except Exception as e: pass
+            try:                        sys.meta_path = old_meta_path
+            except Exception as e: pass
+            try:                        sys.path_hooks = old_path_hooks
+            except Exception as e: pass
+            if e:
+                raise e
+
+    def path_hook(d):
+        if d == VIM_SPECIAL_PATH:
+            return vim
+        raise ImportError
+
+    sys.path_hooks.append(path_hook)
+
+Implementation for python 3 is cleaner: code is similar to the following, but, 
+again, written in C: >
+
+    from importlib.machinery import PathFinder
+    import sys
+
+    class Finder(PathFinder):
+        @classmethod
+        def find_module(cls, fullname):
+            # see vim._get_paths below
+            new_path = _get_paths()
+
+            # super().find_module is also a class method
+            # super() is not used because this variant is easier to implement 
+            # in C
+            return PathFinder.find_module(fullname, new_path)
+
+    def path_hook(path):
+        if path == VIM_SPECIAL_PATH:
+            return Finder
+        raise ImportError
+
+    sys.path_hooks.append(path_hook)
+
+vim.VIM_SPECIAL_PATH					*python-VIM_SPECIAL_PATH*
+	String constant used in conjunction with vim path hook. If path hook 
+	installed by vim is requested to handle anything but path equal to 
+	vim.VIM_SPECIAL_PATH constant it raises ImportError. In the only other 
+	case it uses special loader.
+
+	Note: you must not use value of this constant directly, always use 
+	      vim.VIM_SPECIAL_PATH object.
+
+vim.load_module(name)					*python-load_module*
+vim.find_module(...)					*python-find_module*
+vim.path_hook(path)					*python-path_hook*
+	Methods or objects used to implement path loading as described above. 
+	You should not be using any of these directly except for vim.path_hook 
+	in case you need to do something with sys.meta_path. It is not 
+	guaranteed that any of the objects will exist in the future vim 
+	versions. In fact, load_module and find_module methods do not exists 
+	in python3.
+
+vim._get_paths						*python-_get_paths*
+	Methods returning a list of paths which will be searched for by path 
+	hook. You should not rely on this method being present in future 
+	versions, but can use it for debugging.
+
+	It returns a list of {rtp}/python2 (or {rtp}/python3) and 
+	{rtp}/pythonx directories for each {rtp} in 'runtimepath'.
+
 ==============================================================================
 3. Buffer objects					*python-buffer*
 
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -5289,10 +5289,10 @@ fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $vi_cv_var_python_version" >&5
 $as_echo "$vi_cv_var_python_version" >&6; }
 
-        { $as_echo "$as_me:${as_lineno-$LINENO}: checking Python is 2.2 or better" >&5
-$as_echo_n "checking Python is 2.2 or better... " >&6; }
+        { $as_echo "$as_me:${as_lineno-$LINENO}: checking Python is 2.3 or better" >&5
+$as_echo_n "checking Python is 2.3 or better... " >&6; }
     if ${vi_cv_path_python} -c \
-	"import sys; sys.exit(${vi_cv_var_python_version} < 2.2)"
+	"import sys; sys.exit(${vi_cv_var_python_version} < 2.3)"
     then
       { $as_echo "$as_me:${as_lineno-$LINENO}: result: yep" >&5
 $as_echo "yep" >&6; }
--- a/src/configure.in
+++ b/src/configure.in
@@ -863,10 +863,10 @@ if test "$enable_pythoninterp" = "yes" -
 	    ${vi_cv_path_python} -c 'import sys; print sys.version[:3]'`
     ]])
 
-    dnl -- it must be at least version 2.2
-    AC_MSG_CHECKING(Python is 2.2 or better)
+    dnl -- it must be at least version 2.3
+    AC_MSG_CHECKING(Python is 2.3 or better)
     if ${vi_cv_path_python} -c \
-	"import sys; sys.exit(${vi_cv_var_python_version} < 2.2)"
+	"import sys; sys.exit(${vi_cv_var_python_version} < 2.3)"
     then
       AC_MSG_RESULT(yep)
 
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -2737,6 +2737,10 @@ source_runtime(name, all)
  * When "all" is TRUE repeat for all matches, otherwise only the first one is
  * used.
  * Returns OK when at least one match found, FAIL otherwise.
+ *
+ * If "name" is NULL calls callback for each entry in runtimepath. Cookie is 
+ * passed by reference in this case, setting it to NULL indicates that callback 
+ * has done its job.
  */
     int
 do_in_runtimepath(name, all, callback, cookie)
@@ -2768,7 +2772,7 @@ do_in_runtimepath(name, all, callback, c
     buf = alloc(MAXPATHL);
     if (buf != NULL && rtp_copy != NULL)
     {
-	if (p_verbose > 1)
+	if (p_verbose > 1 && name != NULL)
 	{
 	    verbose_enter();
 	    smsg((char_u *)_("Searching for \"%s\" in \"%s\""),
@@ -2782,7 +2786,13 @@ do_in_runtimepath(name, all, callback, c
 	{
 	    /* Copy the path from 'runtimepath' to buf[]. */
 	    copy_option_part(&rtp, buf, MAXPATHL, ",");
-	    if (STRLEN(buf) + STRLEN(name) + 2 < MAXPATHL)
+	    if (name == NULL)
+	    {
+		(*callback)(buf, (void *) &cookie);
+		if (!did_one)
+		    did_one = (cookie == NULL);
+	    }
+	    else if (STRLEN(buf) + STRLEN(name) + 2 < MAXPATHL)
 	    {
 		add_pathsep(buf);
 		tail = buf + STRLEN(buf);
@@ -2821,7 +2831,7 @@ do_in_runtimepath(name, all, callback, c
     }
     vim_free(buf);
     vim_free(rtp_copy);
-    if (p_verbose > 0 && !did_one)
+    if (p_verbose > 0 && !did_one && name != NULL)
     {
 	verbose_enter();
 	smsg((char_u *)_("not found in 'runtimepath': \"%s\""), name);
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -24,6 +24,8 @@ typedef int Py_ssize_t;  /* Python 2.4 a
 #endif
 #define DOPY_FUNC "_vim_pydo"
 
+static const char *vim_special_path = "_vim_path_";
+
 #define PyErr_SetVim(str) PyErr_SetString(VimError, str)
 
 #define RAISE_NO_EMPTY_KEYS PyErr_SetString(PyExc_ValueError, \
@@ -55,6 +57,8 @@ static PyObject *globals;
 static PyObject *py_chdir;
 static PyObject *py_fchdir;
 static PyObject *py_getcwd;
+static PyObject *vim_module;
+static PyObject *vim_special_path_object;
 
 /*
  * obtain a lock on the Vim data structures
@@ -779,19 +783,168 @@ VimFchdir(PyObject *self UNUSED, PyObjec
     return _VimChdir(py_fchdir, args, kwargs);
 }
 
+typedef struct {
+    PyObject	*callable;
+    PyObject	*result;
+} map_rtp_data;
+
+    static void
+map_rtp_callback(char_u *path, void *_data)
+{
+    void	**data = (void **) _data;
+    PyObject	*pathObject;
+    map_rtp_data	*mr_data = *((map_rtp_data **) data);
+
+    if (!(pathObject = PyString_FromString((char *) path)))
+    {
+	*data = NULL;
+	return;
+    }
+
+    mr_data->result = PyObject_CallFunctionObjArgs(mr_data->callable,
+						   pathObject, NULL);
+
+    Py_DECREF(pathObject);
+
+    if (!mr_data->result || mr_data->result != Py_None)
+	*data = NULL;
+    else
+    {
+	Py_DECREF(mr_data->result);
+	mr_data->result = NULL;
+    }
+}
+
+    static PyObject *
+VimForeachRTP(PyObject *self UNUSED, PyObject *args)
+{
+    map_rtp_data	data;
+
+    if (!PyArg_ParseTuple(args, "O", &data.callable))
+	return NULL;
+
+    data.result = NULL;
+
+    do_in_runtimepath(NULL, FALSE, &map_rtp_callback, &data);
+
+    if (data.result == NULL)
+    {
+	if (PyErr_Occurred())
+	    return NULL;
+	else
+	{
+	    Py_INCREF(Py_None);
+	    return Py_None;
+	}
+    }
+    return data.result;
+}
+
+/*
+ * _vim_runtimepath_ special path implementation.
+ */
+
+    static void
+map_finder_callback(char_u *path, void *_data)
+{
+    void	**data = (void **) _data;
+    PyObject	*list = *((PyObject **) data);
+    PyObject	*pathObject1, *pathObject2;
+    char	*pathbuf;
+    size_t	pathlen;
+
+    pathlen = STRLEN(path);
+
+#if PY_MAJOR_VERSION < 3
+# define PY_MAIN_DIR_STRING "python2"
+#else
+# define PY_MAIN_DIR_STRING "python3"
+#endif
+#define PY_ALTERNATE_DIR_STRING "pythonx"
+
+#define PYTHONX_STRING_LENGTH 7 /* STRLEN("pythonx") */
+    if (!(pathbuf = PyMem_New(char,
+		    pathlen + STRLEN(PATHSEPSTR) + PYTHONX_STRING_LENGTH + 1)))
+    {
+	PyErr_NoMemory();
+	*data = NULL;
+	return;
+    }
+
+    mch_memmove(pathbuf, path, pathlen + 1);
+    add_pathsep((char_u *) pathbuf);
+
+    pathlen = STRLEN(pathbuf);
+    mch_memmove(pathbuf + pathlen, PY_MAIN_DIR_STRING,
+	    PYTHONX_STRING_LENGTH + 1);
+
+    if (!(pathObject1 = PyString_FromString(pathbuf)))
+    {
+	*data = NULL;
+	PyMem_Free(pathbuf);
+	return;
+    }
+
+    mch_memmove(pathbuf + pathlen, PY_ALTERNATE_DIR_STRING,
+	    PYTHONX_STRING_LENGTH + 1);
+
+    if (!(pathObject2 = PyString_FromString(pathbuf)))
+    {
+	Py_DECREF(pathObject1);
+	PyMem_Free(pathbuf);
+	*data = NULL;
+	return;
+    }
+
+    PyMem_Free(pathbuf);
+
+    if (PyList_Append(list, pathObject1)
+	    || PyList_Append(list, pathObject2))
+	*data = NULL;
+
+    Py_DECREF(pathObject1);
+    Py_DECREF(pathObject2);
+}
+
+    static PyObject *
+Vim_GetPaths(PyObject *self UNUSED)
+{
+    PyObject	*r;
+
+    if (!(r = PyList_New(0)))
+	return NULL;
+
+    do_in_runtimepath(NULL, FALSE, &map_finder_callback, r);
+
+    if (PyErr_Occurred())
+    {
+	Py_DECREF(r);
+	return NULL;
+    }
+
+    return r;
+}
+
 /*
  * Vim module - Definitions
  */
 
 static struct PyMethodDef VimMethods[] = {
-    /* name,	     function,			calling,			documentation */
-    {"command",	     VimCommand,		METH_VARARGS,			"Execute a Vim ex-mode command" },
-    {"eval",	     VimEval,			METH_VARARGS,			"Evaluate an expression using Vim evaluator" },
-    {"bindeval",     VimEvalPy,			METH_VARARGS,			"Like eval(), but returns objects attached to vim ones"},
-    {"strwidth",     VimStrwidth,		METH_VARARGS,			"Screen string width, counts <Tab> as having width 1"},
-    {"chdir",	     (PyCFunction)VimChdir,	METH_VARARGS|METH_KEYWORDS,	"Change directory"},
-    {"fchdir",	     (PyCFunction)VimFchdir,	METH_VARARGS|METH_KEYWORDS,	"Change directory"},
-    { NULL,	     NULL,			0,				NULL }
+    /* name,	    function,			calling,			documentation */
+    {"command",	    VimCommand,			METH_VARARGS,			"Execute a Vim ex-mode command" },
+    {"eval",	    VimEval,			METH_VARARGS,			"Evaluate an expression using Vim evaluator" },
+    {"bindeval",    VimEvalPy,			METH_VARARGS,			"Like eval(), but returns objects attached to vim ones"},
+    {"strwidth",    VimStrwidth,		METH_VARARGS,			"Screen string width, counts <Tab> as having width 1"},
+    {"chdir",	    (PyCFunction)VimChdir,	METH_VARARGS|METH_KEYWORDS,	"Change directory"},
+    {"fchdir",	    (PyCFunction)VimFchdir,	METH_VARARGS|METH_KEYWORDS,	"Change directory"},
+    {"foreach_rtp", VimForeachRTP,		METH_VARARGS,			"Call given callable for each path in &rtp"},
+#if PY_MAJOR_VERSION < 3
+    {"find_module", FinderFindModule,		METH_VARARGS,			"Internal use only, returns loader object for any input it receives"},
+    {"load_module", LoaderLoadModule,		METH_VARARGS,			"Internal use only, tries importing the given module from &rtp by temporary mocking sys.path (to an rtp-based one) and unsetting sys.meta_path and sys.path_hooks"},
+#endif
+    {"path_hook",   VimPathHook,		METH_VARARGS,			"Hook function to install in sys.path_hooks"},
+    {"_get_paths",  (PyCFunction)Vim_GetPaths,	METH_NOARGS,			"Get &rtp-based additions to sys.path"},
+    { NULL,	    NULL,			0,				NULL}
 };
 
 /*
@@ -5036,6 +5189,14 @@ typedef struct
 } CurrentObject;
 static PyTypeObject CurrentType;
 
+#if PY_MAJOR_VERSION >= 3
+typedef struct
+{
+    PyObject_HEAD
+} FinderObject;
+static PyTypeObject FinderType;
+#endif
+
     static void
 init_structs(void)
 {
@@ -5281,6 +5442,81 @@ init_types()
     PYTYPE_READY(FunctionType);
     PYTYPE_READY(OptionsType);
     PYTYPE_READY(OutputType);
+#if PY_MAJOR_VERSION >= 3
+    PYTYPE_READY(FinderType);
+#endif
+    return 0;
+}
+
+    static int
+init_sys_path()
+{
+    PyObject	*path;
+    PyObject	*path_hook;
+    PyObject	*path_hooks;
+
+    if (!(path_hook = PyObject_GetAttrString(vim_module, "path_hook")))
+	return -1;
+
+    if (!(path_hooks = PySys_GetObject("path_hooks")))
+    {
+	PyErr_Clear();
+	path_hooks = PyList_New(1);
+	PyList_SET_ITEM(path_hooks, 0, path_hook);
+	if (PySys_SetObject("path_hooks", path_hooks))
+	{
+	    Py_DECREF(path_hooks);
+	    return -1;
+	}
+	Py_DECREF(path_hooks);
+    }
+    else if (PyList_Check(path_hooks))
+    {
+	if (PyList_Append(path_hooks, path_hook))
+	{
+	    Py_DECREF(path_hook);
+	    return -1;
+	}
+	Py_DECREF(path_hook);
+    }
+    else
+    {
+	VimTryStart();
+	EMSG(_("Failed to set path hook: sys.path_hooks is not a list\n"
+	       "You should now do the following:\n"
+	       "- append vim.path_hook to sys.path_hooks\n"
+	       "- append vim.VIM_SPECIAL_PATH to sys.path\n"));
+	VimTryEnd(); /* Discard the error */
+	Py_DECREF(path_hook);
+	return 0;
+    }
+
+    if (!(path = PySys_GetObject("path")))
+    {
+	PyErr_Clear();
+	path = PyList_New(1);
+	Py_INCREF(vim_special_path_object);
+	PyList_SET_ITEM(path, 0, vim_special_path_object);
+	if (PySys_SetObject("path", path))
+	{
+	    Py_DECREF(path);
+	    return -1;
+	}
+	Py_DECREF(path);
+    }
+    else if (PyList_Check(path))
+    {
+	if (PyList_Append(path, vim_special_path_object))
+	    return -1;
+    }
+    else
+    {
+	VimTryStart();
+	EMSG(_("Failed to set path: sys.path is not a list\n"
+	       "You should now append vim.VIM_SPECIAL_PATH to sys.path"));
+	VimTryEnd(); /* Discard the error */
+    }
+
     return 0;
 }
 
@@ -5332,6 +5568,9 @@ static struct object_constant {
     {"List",       (PyObject *)&ListType},
     {"Function",   (PyObject *)&FunctionType},
     {"Options",    (PyObject *)&OptionsType},
+#if PY_MAJOR_VERSION >= 3
+    {"Finder",     (PyObject *)&FinderType},
+#endif
 };
 
 typedef int (*object_adder)(PyObject *, const char *, PyObject *);
@@ -5417,5 +5656,17 @@ populate_module(PyObject *m, object_adde
     else
 	PyErr_Clear();
 
+    if (!(vim_special_path_object = PyString_FromString(vim_special_path)))
+	return -1;
+
+    ADD_OBJECT(m, "VIM_SPECIAL_PATH", vim_special_path_object);
+
+#if PY_MAJOR_VERSION >= 3
+    ADD_OBJECT(m, "_PathFinder", path_finder);
+    ADD_CHECKED_OBJECT(m, "_find_module",
+	    (py_find_module = PyObject_GetAttrString(path_finder,
+						     "find_module")));
+#endif
+
     return 0;
 }
--- a/src/if_python.c
+++ b/src/if_python.c
@@ -24,9 +24,9 @@
 /* uncomment this if used with the debug version of python.
  * Checked on 2.7.4. */
 /* #define Py_DEBUG */
-/* Note: most of time you can add -DPy_DEBUG to CFLAGS in place of uncommenting 
+/* Note: most of time you can add -DPy_DEBUG to CFLAGS in place of uncommenting
  */
-/* uncomment this if used with the debug version of python, but without its 
+/* uncomment this if used with the debug version of python, but without its
  * allocator */
 /* #define Py_DEBUG_NO_PYMALLOC */
 
@@ -168,6 +168,7 @@ struct PyMethodDef { Py_ssize_t a; };
 # define PyErr_SetNone dll_PyErr_SetNone
 # define PyErr_SetString dll_PyErr_SetString
 # define PyErr_SetObject dll_PyErr_SetObject
+# define PyErr_ExceptionMatches dll_PyErr_ExceptionMatches
 # define PyEval_InitThreads dll_PyEval_InitThreads
 # define PyEval_RestoreThread dll_PyEval_RestoreThread
 # define PyEval_SaveThread dll_PyEval_SaveThread
@@ -184,6 +185,7 @@ struct PyMethodDef { Py_ssize_t a; };
 # define PyLong_Type (*dll_PyLong_Type)
 # define PyList_GetItem dll_PyList_GetItem
 # define PyList_Append dll_PyList_Append
+# define PyList_Insert dll_PyList_Insert
 # define PyList_New dll_PyList_New
 # define PyList_SetItem dll_PyList_SetItem
 # define PyList_Size dll_PyList_Size
@@ -233,6 +235,7 @@ struct PyMethodDef { Py_ssize_t a; };
 # define PyFloat_Type (*dll_PyFloat_Type)
 # define PyImport_AddModule (*dll_PyImport_AddModule)
 # define PySys_SetObject dll_PySys_SetObject
+# define PySys_GetObject dll_PySys_GetObject
 # define PySys_SetArgv dll_PySys_SetArgv
 # define PyType_Type (*dll_PyType_Type)
 # define PyType_Ready (*dll_PyType_Ready)
@@ -305,6 +308,7 @@ static PyObject*(*dll_PyErr_Occurred)(vo
 static void(*dll_PyErr_SetNone)(PyObject *);
 static void(*dll_PyErr_SetString)(PyObject *, const char *);
 static void(*dll_PyErr_SetObject)(PyObject *, PyObject *);
+static int(*dll_PyErr_ExceptionMatches)(PyObject *);
 static void(*dll_PyEval_InitThreads)(void);
 static void(*dll_PyEval_RestoreThread)(PyThreadState *);
 static PyThreadState*(*dll_PyEval_SaveThread)(void);
@@ -320,7 +324,8 @@ static PyTypeObject* dll_PyBool_Type;
 static PyTypeObject* dll_PyInt_Type;
 static PyTypeObject* dll_PyLong_Type;
 static PyObject*(*dll_PyList_GetItem)(PyObject *, PyInt);
-static PyObject*(*dll_PyList_Append)(PyObject *, PyObject *);
+static int(*dll_PyList_Append)(PyObject *, PyObject *);
+static int(*dll_PyList_Insert)(PyObject *, int, PyObject *);
 static PyObject*(*dll_PyList_New)(PyInt size);
 static int(*dll_PyList_SetItem)(PyObject *, PyInt, PyObject *);
 static PyInt(*dll_PyList_Size)(PyObject *);
@@ -366,6 +371,7 @@ static double(*dll_PyFloat_AsDouble)(PyO
 static PyObject*(*dll_PyFloat_FromDouble)(double);
 static PyTypeObject* dll_PyFloat_Type;
 static int(*dll_PySys_SetObject)(char *, PyObject *);
+static PyObject *(*dll_PySys_GetObject)(char *);
 static int(*dll_PySys_SetArgv)(int, char **);
 static PyTypeObject* dll_PyType_Type;
 static int (*dll_PyType_Ready)(PyTypeObject *type);
@@ -431,6 +437,7 @@ static PyObject *imp_PyExc_KeyboardInter
 static PyObject *imp_PyExc_TypeError;
 static PyObject *imp_PyExc_ValueError;
 static PyObject *imp_PyExc_RuntimeError;
+static PyObject *imp_PyExc_ImportError;
 
 # define PyExc_AttributeError imp_PyExc_AttributeError
 # define PyExc_IndexError imp_PyExc_IndexError
@@ -439,6 +446,7 @@ static PyObject *imp_PyExc_RuntimeError;
 # define PyExc_TypeError imp_PyExc_TypeError
 # define PyExc_ValueError imp_PyExc_ValueError
 # define PyExc_RuntimeError imp_PyExc_RuntimeError
+# define PyExc_ImportError imp_PyExc_ImportError
 
 /*
  * Table of name to function pointer of python.
@@ -471,6 +479,7 @@ static struct
     {"PyErr_SetNone", (PYTHON_PROC*)&dll_PyErr_SetNone},
     {"PyErr_SetString", (PYTHON_PROC*)&dll_PyErr_SetString},
     {"PyErr_SetObject", (PYTHON_PROC*)&dll_PyErr_SetObject},
+    {"PyErr_ExceptionMatches", (PYTHON_PROC*)&dll_PyErr_ExceptionMatches},
     {"PyEval_InitThreads", (PYTHON_PROC*)&dll_PyEval_InitThreads},
     {"PyEval_RestoreThread", (PYTHON_PROC*)&dll_PyEval_RestoreThread},
     {"PyEval_SaveThread", (PYTHON_PROC*)&dll_PyEval_SaveThread},
@@ -487,6 +496,7 @@ static struct
     {"PyLong_Type", (PYTHON_PROC*)&dll_PyLong_Type},
     {"PyList_GetItem", (PYTHON_PROC*)&dll_PyList_GetItem},
     {"PyList_Append", (PYTHON_PROC*)&dll_PyList_Append},
+    {"PyList_Insert", (PYTHON_PROC*)&dll_PyList_Insert},
     {"PyList_New", (PYTHON_PROC*)&dll_PyList_New},
     {"PyList_SetItem", (PYTHON_PROC*)&dll_PyList_SetItem},
     {"PyList_Size", (PYTHON_PROC*)&dll_PyList_Size},
@@ -532,6 +542,7 @@ static struct
     {"PyFloat_FromDouble", (PYTHON_PROC*)&dll_PyFloat_FromDouble},
     {"PyImport_AddModule", (PYTHON_PROC*)&dll_PyImport_AddModule},
     {"PySys_SetObject", (PYTHON_PROC*)&dll_PySys_SetObject},
+    {"PySys_GetObject", (PYTHON_PROC*)&dll_PySys_GetObject},
     {"PySys_SetArgv", (PYTHON_PROC*)&dll_PySys_SetArgv},
     {"PyType_Type", (PYTHON_PROC*)&dll_PyType_Type},
     {"PyType_Ready", (PYTHON_PROC*)&dll_PyType_Ready},
@@ -706,6 +717,7 @@ get_exceptions(void)
     imp_PyExc_TypeError = PyDict_GetItemString(exdict, "TypeError");
     imp_PyExc_ValueError = PyDict_GetItemString(exdict, "ValueError");
     imp_PyExc_RuntimeError = PyDict_GetItemString(exdict, "RuntimeError");
+    imp_PyExc_ImportError = PyDict_GetItemString(exdict, "ImportError");
     Py_XINCREF(imp_PyExc_AttributeError);
     Py_XINCREF(imp_PyExc_IndexError);
     Py_XINCREF(imp_PyExc_KeyError);
@@ -713,6 +725,7 @@ get_exceptions(void)
     Py_XINCREF(imp_PyExc_TypeError);
     Py_XINCREF(imp_PyExc_ValueError);
     Py_XINCREF(imp_PyExc_RuntimeError);
+    Py_XINCREF(imp_PyExc_ImportError);
     Py_XDECREF(exmod);
 }
 #endif /* DYNAMIC_PYTHON */
@@ -735,6 +748,10 @@ static PyObject *DictionaryGetattr(PyObj
 static PyObject *ListGetattr(PyObject *, char *);
 static PyObject *FunctionGetattr(PyObject *, char *);
 
+static PyObject *LoaderLoadModule(PyObject *, PyObject *);
+static PyObject *FinderFindModule(PyObject *, PyObject *);
+static PyObject *VimPathHook(PyObject *, PyObject *);
+
 #ifndef Py_VISIT
 # define Py_VISIT(obj) visit(obj, arg)
 #endif
@@ -1359,11 +1376,112 @@ python_tabpage_free(tabpage_T *tab)
 }
 #endif
 
+    static PyObject *
+LoaderLoadModule(PyObject *self, PyObject *args)
+{
+    char	*fullname;
+    PyObject	*path;
+    PyObject	*meta_path;
+    PyObject	*path_hooks;
+    PyObject	*new_path;
+    PyObject	*r;
+    PyObject	*new_list;
+
+    if (!PyArg_ParseTuple(args, "s", &fullname))
+	return NULL;
+
+    if (!(new_path = Vim_GetPaths(self)))
+	return NULL;
+
+    if (!(new_list = PyList_New(0)))
+	return NULL;
+
+#define GET_SYS_OBJECT(objstr, obj) \
+    obj = PySys_GetObject(objstr); \
+    PyErr_Clear(); \
+    Py_XINCREF(obj);
+
+    GET_SYS_OBJECT("meta_path", meta_path);
+    if (PySys_SetObject("meta_path", new_list))
+    {
+	Py_XDECREF(meta_path);
+	Py_DECREF(new_list);
+	return NULL;
+    }
+    Py_DECREF(new_list); /* Now it becomes a reference borrowed from
+			    sys.meta_path */
+
+#define RESTORE_SYS_OBJECT(objstr, obj) \
+    if (obj) \
+    { \
+	PySys_SetObject(objstr, obj); \
+	Py_DECREF(obj); \
+    }
+
+    GET_SYS_OBJECT("path_hooks", path_hooks);
+    if (PySys_SetObject("path_hooks", new_list))
+    {
+	RESTORE_SYS_OBJECT("meta_path", meta_path);
+	Py_XDECREF(path_hooks);
+	return NULL;
+    }
+
+    GET_SYS_OBJECT("path", path);
+    if (PySys_SetObject("path", new_path))
+    {
+	RESTORE_SYS_OBJECT("meta_path", meta_path);
+	RESTORE_SYS_OBJECT("path_hooks", path_hooks);
+	Py_XDECREF(path);
+	return NULL;
+    }
+    Py_DECREF(new_path);
+
+    r = PyImport_ImportModule(fullname);
+
+    RESTORE_SYS_OBJECT("meta_path", meta_path);
+    RESTORE_SYS_OBJECT("path_hooks", path_hooks);
+    RESTORE_SYS_OBJECT("path", path);
+
+    if (PyErr_Occurred())
+    {
+	Py_XDECREF(r);
+	return NULL;
+    }
+
+    return r;
+}
+
+    static PyObject *
+FinderFindModule(PyObject *self UNUSED, PyObject *args UNUSED)
+{
+    /*
+     * Don't bother actually finding the module, it is delegated to the "loader"
+     * object (which is basically the same object: vim module).
+     */
+    Py_INCREF(vim_module);
+    return vim_module;
+}
+
+    static PyObject *
+VimPathHook(PyObject *self UNUSED, PyObject *args)
+{
+    char	*path;
+
+    if (PyArg_ParseTuple(args, "s", &path)
+	    && STRCMP(path, vim_special_path) == 0)
+    {
+	Py_INCREF(vim_module);
+	return vim_module;
+    }
+
+    PyErr_Clear();
+    PyErr_SetNone(PyExc_ImportError);
+    return NULL;
+}
+
     static int
 PythonMod_Init(void)
 {
-    PyObject *mod;
-
     /* The special value is removed from sys.path in Python_Init(). */
     static char *(argv[2]) = {"/must>not&exist/foo", NULL};
 
@@ -1373,10 +1491,17 @@ PythonMod_Init(void)
     /* Set sys.argv[] to avoid a crash in warn(). */
     PySys_SetArgv(1, argv);
 
-    mod = Py_InitModule4("vim", VimMethods, (char *)NULL, (PyObject *)NULL,
-			    PYTHON_API_VERSION);
+    vim_module = Py_InitModule4("vim", VimMethods, (char *)NULL,
+				(PyObject *)NULL, PYTHON_API_VERSION);
 
-    return populate_module(mod, PyModule_AddObject, PyObject_GetAttrString);
+    if (populate_module(vim_module, PyModule_AddObject,
+			   PyObject_GetAttrString))
+	return -1;
+
+    if (init_sys_path())
+	return -1;
+
+    return 0;
 }
 
 /*************************************************************************
--- a/src/if_python3.c
+++ b/src/if_python3.c
@@ -134,6 +134,7 @@
 # define PyErr_SetNone py3_PyErr_SetNone
 # define PyErr_SetString py3_PyErr_SetString
 # define PyErr_SetObject py3_PyErr_SetObject
+# define PyErr_ExceptionMatches py3_PyErr_ExceptionMatches
 # define PyEval_InitThreads py3_PyEval_InitThreads
 # define PyEval_RestoreThread py3_PyEval_RestoreThread
 # define PyEval_SaveThread py3_PyEval_SaveThread
@@ -143,6 +144,7 @@
 # define PyLong_FromLong py3_PyLong_FromLong
 # define PyList_GetItem py3_PyList_GetItem
 # define PyList_Append py3_PyList_Append
+# define PyList_Insert py3_PyList_Insert
 # define PyList_New py3_PyList_New
 # define PyList_SetItem py3_PyList_SetItem
 # define PyList_Size py3_PyList_Size
@@ -177,6 +179,7 @@
 # define PyEval_GetLocals py3_PyEval_GetLocals
 # define PyEval_GetGlobals py3_PyEval_GetGlobals
 # define PySys_SetObject py3_PySys_SetObject
+# define PySys_GetObject py3_PySys_GetObject
 # define PySys_SetArgv py3_PySys_SetArgv
 # define PyType_Ready py3_PyType_Ready
 #undef Py_BuildValue
@@ -268,7 +271,9 @@ static PyObject* (*py3_PyList_New)(Py_ss
 static PyGILState_STATE (*py3_PyGILState_Ensure)(void);
 static void (*py3_PyGILState_Release)(PyGILState_STATE);
 static int (*py3_PySys_SetObject)(char *, PyObject *);
-static PyObject* (*py3_PyList_Append)(PyObject *, PyObject *);
+static PyObject* (*py3_PySys_GetObject)(char *);
+static int (*py3_PyList_Append)(PyObject *, PyObject *);
+static int (*py3_PyList_Insert)(PyObject *, int, PyObject *);
 static Py_ssize_t (*py3_PyList_Size)(PyObject *);
 static int (*py3_PySequence_Check)(PyObject *);
 static Py_ssize_t (*py3_PySequence_Size)(PyObject *);
@@ -284,6 +289,7 @@ static PyObject* (*py3_PyErr_NoMemory)(v
 static void (*py3_Py_Finalize)(void);
 static void (*py3_PyErr_SetString)(PyObject *, const char *);
 static void (*py3_PyErr_SetObject)(PyObject *, PyObject *);
+static int (*py3_PyErr_ExceptionMatches)(PyObject *);
 static int (*py3_PyRun_SimpleString)(char *);
 static PyObject* (*py3_PyRun_String)(char *, int, PyObject *, PyObject *);
 static PyObject* (*py3_PyObject_GetAttrString)(PyObject *, const char *);
@@ -393,6 +399,7 @@ static PyObject *p3imp_PyExc_KeyboardInt
 static PyObject *p3imp_PyExc_TypeError;
 static PyObject *p3imp_PyExc_ValueError;
 static PyObject *p3imp_PyExc_RuntimeError;
+static PyObject *p3imp_PyExc_ImportError;
 
 # define PyExc_AttributeError p3imp_PyExc_AttributeError
 # define PyExc_IndexError p3imp_PyExc_IndexError
@@ -401,6 +408,7 @@ static PyObject *p3imp_PyExc_RuntimeErro
 # define PyExc_TypeError p3imp_PyExc_TypeError
 # define PyExc_ValueError p3imp_PyExc_ValueError
 # define PyExc_RuntimeError p3imp_PyExc_RuntimeError
+# define PyExc_ImportError p3imp_PyExc_ImportError
 
 /*
  * Table of name to function pointer of python.
@@ -428,7 +436,9 @@ static struct
     {"PyGILState_Ensure", (PYTHON_PROC*)&py3_PyGILState_Ensure},
     {"PyGILState_Release", (PYTHON_PROC*)&py3_PyGILState_Release},
     {"PySys_SetObject", (PYTHON_PROC*)&py3_PySys_SetObject},
+    {"PySys_GetObject", (PYTHON_PROC*)&py3_PySys_GetObject},
     {"PyList_Append", (PYTHON_PROC*)&py3_PyList_Append},
+    {"PyList_Insert", (PYTHON_PROC*)&py3_PyList_Insert},
     {"PyList_Size", (PYTHON_PROC*)&py3_PyList_Size},
     {"PySequence_Check", (PYTHON_PROC*)&py3_PySequence_Check},
     {"PySequence_Size", (PYTHON_PROC*)&py3_PySequence_Size},
@@ -441,6 +451,7 @@ static struct
     {"Py_Finalize", (PYTHON_PROC*)&py3_Py_Finalize},
     {"PyErr_SetString", (PYTHON_PROC*)&py3_PyErr_SetString},
     {"PyErr_SetObject", (PYTHON_PROC*)&py3_PyErr_SetObject},
+    {"PyErr_ExceptionMatches", (PYTHON_PROC*)&py3_PyErr_ExceptionMatches},
     {"PyRun_SimpleString", (PYTHON_PROC*)&py3_PyRun_SimpleString},
     {"PyRun_String", (PYTHON_PROC*)&py3_PyRun_String},
     {"PyObject_GetAttrString", (PYTHON_PROC*)&py3_PyObject_GetAttrString},
@@ -664,6 +675,7 @@ get_py3_exceptions()
     p3imp_PyExc_TypeError = PyDict_GetItemString(exdict, "TypeError");
     p3imp_PyExc_ValueError = PyDict_GetItemString(exdict, "ValueError");
     p3imp_PyExc_RuntimeError = PyDict_GetItemString(exdict, "RuntimeError");
+    p3imp_PyExc_ImportError = PyDict_GetItemString(exdict, "ImportError");
     Py_XINCREF(p3imp_PyExc_AttributeError);
     Py_XINCREF(p3imp_PyExc_IndexError);
     Py_XINCREF(p3imp_PyExc_KeyError);
@@ -671,6 +683,7 @@ get_py3_exceptions()
     Py_XINCREF(p3imp_PyExc_TypeError);
     Py_XINCREF(p3imp_PyExc_ValueError);
     Py_XINCREF(p3imp_PyExc_RuntimeError);
+    Py_XINCREF(p3imp_PyExc_ImportError);
     Py_XDECREF(exmod);
 }
 #endif /* DYNAMIC_PYTHON3 */
@@ -723,8 +736,13 @@ static PyObject *ListGetattro(PyObject *
 static int ListSetattro(PyObject *, PyObject *, PyObject *);
 static PyObject *FunctionGetattro(PyObject *, PyObject *);
 
+static PyObject *VimPathHook(PyObject *, PyObject *);
+
 static struct PyModuleDef vimmodule;
 
+static PyObject *path_finder;
+static PyObject *py_find_module = NULL;
+
 #define PY_CAN_RECURSE
 
 /*
@@ -1585,12 +1603,70 @@ python3_tabpage_free(tabpage_T *tab)
 #endif
 
     static PyObject *
+VimPathHook(PyObject *self UNUSED, PyObject *args)
+{
+    char	*path;
+
+    if (PyArg_ParseTuple(args, "s", &path)
+	    && STRCMP(path, vim_special_path) == 0)
+    {
+	Py_INCREF(&FinderType);
+	return (PyObject *) &FinderType;
+    }
+
+    PyErr_Clear();
+    PyErr_SetNone(PyExc_ImportError);
+    return NULL;
+}
+
+    static PyObject *
+FinderFindModule(PyObject *cls UNUSED, PyObject *fullname)
+{
+    PyObject	*new_path;
+    PyObject	*r;
+
+    if (!(new_path = Vim_GetPaths(NULL)))
+	return NULL;
+
+    /* call find_module of the super() class */
+    r = PyObject_CallFunctionObjArgs(py_find_module, fullname, new_path, NULL);
+
+    Py_DECREF(new_path);
+
+    return r;
+}
+
+static struct PyMethodDef FinderMethods[] = {
+    {"find_module",	FinderFindModule,	METH_CLASS|METH_O,	""},
+    {NULL,		NULL,			0,			NULL}
+};
+
+    static PyObject *
 Py3Init_vim(void)
 {
-    PyObject *mod;
-
     /* The special value is removed from sys.path in Python3_Init(). */
     static wchar_t *(argv[2]) = {L"/must>not&exist/foo", NULL};
+    PyObject	*importlib_machinery;
+
+    if (!(importlib_machinery = PyImport_ImportModule("importlib.machinery")))
+	return NULL;
+
+    if (!(path_finder = PyObject_GetAttrString(importlib_machinery,
+					       "PathFinder")))
+    {
+	Py_DECREF(importlib_machinery);
+	return NULL;
+    }
+
+    Py_DECREF(importlib_machinery);
+
+    vim_memset(&FinderType, 0, sizeof(FinderObject));
+    FinderType.tp_name = "vim.Finder";
+    FinderType.tp_basicsize = sizeof(FinderObject);
+    FinderType.tp_base = (PyTypeObject *) path_finder;
+    FinderType.tp_flags = Py_TPFLAGS_DEFAULT;
+    FinderType.tp_doc = "Vim finder class, for use with path hook";
+    FinderType.tp_methods = FinderMethods;
 
     if (init_types())
 	return NULL;
@@ -1598,14 +1674,16 @@ Py3Init_vim(void)
     /* Set sys.argv[] to avoid a crash in warn(). */
     PySys_SetArgv(1, argv);
 
-    mod = PyModule_Create(&vimmodule);
-    if (mod == NULL)
+    if ((vim_module = PyModule_Create(&vimmodule)) == NULL)
 	return NULL;
 
-    if (populate_module(mod, PyModule_AddObject, PyObject_GetAttrString))
+    if (populate_module(vim_module, PyModule_AddObject, PyObject_GetAttrString))
 	return NULL;
 
-    return mod;
+    if (init_sys_path())
+	return NULL;
+
+    return vim_module;
 }
 
 /*************************************************************************
--- a/src/testdir/test86.in
+++ b/src/testdir/test86.in
@@ -1069,6 +1069,14 @@ ee('vim.current.tabpage = True')
 ee('vim.current.xxx = True')
 EOF
 :"
+:" Test import  TODO: BROKEN
+:"py << EOF
+:"vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\')
+:"from module import dir as d
+:"from modulex import ddir
+:"cb.append(d + ',' + ddir)
+:"EOF
+:"
 :" Test exceptions
 :fun Exe(e)
 :   execute a:e
--- a/src/testdir/test87.in
+++ b/src/testdir/test87.in
@@ -1036,6 +1036,14 @@ ee('vim.current.tabpage = True')
 ee('vim.current.xxx = True')
 EOF
 :"
+:" Test import  TODO: BROKEN
+:"py3 << EOF
+:"vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\')
+:"from module import dir as d
+:"from modulex import ddir
+:"cb.append(d + ',' + ddir)
+:"EOF
+:"
 :" Test exceptions
 :fun Exe(e)
 :   execute a:e
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1163,
+/**/
     1162,
 /**/
     1161,