Custom Python module importing code

Add optional moduleimport attribute to %module so that the
default module import code can be overridden. See the
"Searching for the wrapper module" documentation in Python.html.
Example:

  %module(moduleimport="import _foo") foo

$module also expands to the low-level C/C++ module name, so
the following is the same as above

  %module(moduleimport="import $module") foo

Issue https://github.com/swig/swig/issues/769
This commit is contained in:
William S Fulton 2016-12-01 18:59:33 +00:00
commit 2a42031b08
6 changed files with 135 additions and 56 deletions

View file

@ -5,6 +5,19 @@ See the RELEASENOTES file for a summary of changes in each release.
Version 3.0.11 (in progress)
============================
2016-12-01: wsfulton
[Python] Issue https://github.com/swig/swig/issues/769
Add optional moduleimport attribute to %module so that the
default module import code can be overridden. See the "Searching for the wrapper module"
documentation in Python.html. Example:
%module(moduleimport="import _foo") foo
$module also expands to the low-level C/C++ module name, so the following is the
same as above
%module(moduleimport="import $module") foo
2016-11-30: olly
[PHP] Add support for PHP7. PHP5's C extension API has changed substantially
so you need to use -php7 to specify you want PHP7 compatible wrappers. The

View file

@ -509,7 +509,7 @@ If using static linking, you might want to rely on a different approach
<p>
To use your module, simply use the Python <tt>import</tt> statement. If
all goes well, you will be able to this:
all goes well, you will be able to run this:
</p>
<div class="targetlang"><pre>
@ -6037,7 +6037,7 @@ zipimporter requires python-3.5.1 or newer to work with subpackages.
<p>
When SWIG creates wrappers from an interface file, say foo.i, two Python modules are
created. There is a pure Python module module (foo.py) and C/C++ code which is
built and linked into a dynamically (or statically) loaded module _foo
built and linked into a dynamically (or statically) loaded low-level module _foo
(see the <a href="Python.html#Python_nn3">Preliminaries section</a> for details). So, the interface
file really defines two Python modules. How these two modules are loaded is
covered next.
@ -6064,7 +6064,43 @@ pure Python module uses to find the C/C++ module is as follows:
</ol>
<p>
As an example suppose foo.i is compiled into foo.py and _foo.so. Assuming
The Python code implementing the loading logic described above is quite complex to handle multiple
versions of Python, but it can be replaced with custom code.
This is not recommended unless you understand the full intricacies of importing Python modules.
The custom code can be specified by setting the <tt>moduleimport</tt> option of the <tt>%module</tt> directive with the appropriate import code. For example:
</p>
<div class="code">
<pre>
%module(moduleimport="import _foo") foo
</pre>
</div>
<p>
The special variable <tt>$module</tt> will also be expanded into the low-level C/C++ module name, <tt>_foo</tt> in the case above.
When you have more than just a line or so then you can retain the easy
readability of the <tt>%module</tt> directive by using a macro. For
example:
</p>
<div class="code">
<pre>
%define MODULEIMPORT
"
print 'Loading low-level module $module'
import $module
print 'Module has loaded'
"
%enddef
%module(moduleimport=MODULEIMPORT) foo
</pre>
</div>
<p>
Now let's consider an example using the SWIG default loading logic.
Suppose foo.i is compiled into foo.py and _foo.so. Assuming
/dir is on PYTHONPATH, then the two modules can be installed and used in the
following ways:
</p>

View file

@ -63,6 +63,7 @@ CPP_TEST_CASES += \
python_director \
python_docstring \
python_extranative \
python_moduleimport \
python_nondynamic \
python_overload_simple_cast \
python_pickle \

View file

@ -0,0 +1,7 @@
import python_moduleimport
if python_moduleimport.simple_function(99) != 99:
raise RuntimeError("simple_function")
if python_moduleimport.extra_import_variable != "custom import of _python_moduleimport":
raise RuntimeError("custom import")

View file

@ -0,0 +1,14 @@
%define MODULEIMPORT
"
#print 'Loading low-level module $module'
import $module
#print 'Module has loaded'
extra_import_variable = 'custom import of $module'
"
%enddef
%module(moduleimport=MODULEIMPORT) python_moduleimport
%inline %{
int simple_function(int i) { return i; }
%}

View file

@ -565,6 +565,7 @@ public:
* interface file to enable director generation.
*/
String *mod_docstring = NULL;
String *moduleimport = NULL;
{
Node *mod = Getattr(n, "module");
if (mod) {
@ -605,6 +606,7 @@ public:
}
mod_docstring = Getattr(options, "docstring");
package = Getattr(options, "package");
moduleimport = Getattr(options, "moduleimport");
}
}
}
@ -779,6 +781,7 @@ public:
}
/* If shadow classing is enabled, we're going to change the module name to "_module" */
String *default_import_code = NewString("");
if (shadow) {
String *filen = NewStringf("%s%s.py", SWIG_output_directory(), Char(module));
// If we don't have an interface then change the module name X to _X
@ -814,13 +817,13 @@ public:
mod_docstring = NULL;
}
Printv(f_shadow, "\nfrom sys import version_info as _swig_python_version_info\n", NULL);
Printv(default_import_code, "from sys import version_info as _swig_python_version_info\n", NULL);
if (!builtin && fastproxy) {
Printv(f_shadow, "if _swig_python_version_info >= (3, 0, 0):\n", NULL);
Printf(f_shadow, tab4 "new_instancemethod = lambda func, inst, cls: %s.SWIG_PyInstanceMethod_New(func)\n", module);
Printv(f_shadow, "else:\n", NULL);
Printv(f_shadow, tab4, "from new import instancemethod as new_instancemethod\n", NULL);
Printv(default_import_code, "if _swig_python_version_info >= (3, 0, 0):\n", NULL);
Printf(default_import_code, tab4 "new_instancemethod = lambda func, inst, cls: %s.SWIG_PyInstanceMethod_New(func)\n", module);
Printv(default_import_code, "else:\n", NULL);
Printv(default_import_code, tab4, "from new import instancemethod as new_instancemethod\n", NULL);
}
/* Import the C-extension module. This should be a relative import,
@ -840,42 +843,39 @@ public:
* ImportError then fallback and try and load just the module name from
* the global namespace.
*/
Printv(f_shadow, "if _swig_python_version_info >= (2, 7, 0):\n", NULL);
Printv(f_shadow, tab4, "def swig_import_helper():\n", NULL);
Printv(f_shadow, tab8, "import importlib\n", NULL);
Printv(f_shadow, tab8, "pkg = __name__.rpartition('.')[0]\n", NULL);
Printf(f_shadow, tab8 "mname = '.'.join((pkg, '%s')).lstrip('.')\n",
module);
Printv(f_shadow, tab8, "try:\n", NULL);
Printv(f_shadow, tab8, tab4, "return importlib.import_module(mname)\n",
NULL);
Printv(f_shadow, tab8, "except ImportError:\n", NULL);
Printf(f_shadow, tab8 tab4 "return importlib.import_module('%s')\n",
module);
Printf(f_shadow, tab4 "%s = swig_import_helper()\n", module);
Printv(f_shadow, tab4, "del swig_import_helper\n", NULL);
Printv(f_shadow, "elif _swig_python_version_info >= (2, 6, 0):\n", NULL);
Printv(f_shadow, tab4, "def swig_import_helper():\n", NULL);
Printv(f_shadow, tab8, "from os.path import dirname\n", NULL);
Printv(f_shadow, tab8, "import imp\n", NULL);
Printv(f_shadow, tab8, "fp = None\n", NULL);
Printv(f_shadow, tab8, "try:\n", NULL);
Printf(f_shadow, tab4 tab8 "fp, pathname, description = imp.find_module('%s', [dirname(__file__)])\n", module);
Printf(f_shadow, tab8 "except ImportError:\n");
Printv(default_import_code, "if _swig_python_version_info >= (2, 7, 0):\n", NULL);
Printv(default_import_code, tab4, "def swig_import_helper():\n", NULL);
Printv(default_import_code, tab8, "import importlib\n", NULL);
Printv(default_import_code, tab8, "pkg = __name__.rpartition('.')[0]\n", NULL);
Printf(default_import_code, tab8 "mname = '.'.join((pkg, '%s')).lstrip('.')\n", module);
Printv(default_import_code, tab8, "try:\n", NULL);
Printv(default_import_code, tab8, tab4, "return importlib.import_module(mname)\n", NULL);
Printv(default_import_code, tab8, "except ImportError:\n", NULL);
Printf(default_import_code, tab8 tab4 "return importlib.import_module('%s')\n", module);
Printf(default_import_code, tab4 "%s = swig_import_helper()\n", module);
Printv(default_import_code, tab4, "del swig_import_helper\n", NULL);
Printv(default_import_code, "elif _swig_python_version_info >= (2, 6, 0):\n", NULL);
Printv(default_import_code, tab4, "def swig_import_helper():\n", NULL);
Printv(default_import_code, tab8, "from os.path import dirname\n", NULL);
Printv(default_import_code, tab8, "import imp\n", NULL);
Printv(default_import_code, tab8, "fp = None\n", NULL);
Printv(default_import_code, tab8, "try:\n", NULL);
Printf(default_import_code, tab4 tab8 "fp, pathname, description = imp.find_module('%s', [dirname(__file__)])\n", module);
Printf(default_import_code, tab8 "except ImportError:\n");
/* At here, the module may already loaded, so simply import it. */
Printf(f_shadow, tab4 tab8 "import %s\n", module);
Printf(f_shadow, tab4 tab8 "return %s\n", module);
Printv(f_shadow, tab8 "try:\n", NULL);
Printf(default_import_code, tab4 tab8 "import %s\n", module);
Printf(default_import_code, tab4 tab8 "return %s\n", module);
Printv(default_import_code, tab8 "try:\n", NULL);
/* imp.load_module() handles fp being None. */
Printf(f_shadow, tab4 tab8 "_mod = imp.load_module('%s', fp, pathname, description)\n", module);
Printv(f_shadow, tab8, "finally:\n", NULL);
Printv(f_shadow, tab4 tab8 "if fp is not None:\n", NULL);
Printv(f_shadow, tab8 tab8, "fp.close()\n", NULL);
Printv(f_shadow, tab8, "return _mod\n", NULL);
Printf(f_shadow, tab4 "%s = swig_import_helper()\n", module);
Printv(f_shadow, tab4, "del swig_import_helper\n", NULL);
Printv(f_shadow, "else:\n", NULL);
Printf(f_shadow, tab4 "import %s\n", module);
Printf(default_import_code, tab4 tab8 "_mod = imp.load_module('%s', fp, pathname, description)\n", module);
Printv(default_import_code, tab8, "finally:\n", NULL);
Printv(default_import_code, tab4 tab8 "if fp is not None:\n", NULL);
Printv(default_import_code, tab8 tab8, "fp.close()\n", NULL);
Printv(default_import_code, tab8, "return _mod\n", NULL);
Printf(default_import_code, tab4 "%s = swig_import_helper()\n", module);
Printv(default_import_code, tab4, "del swig_import_helper\n", NULL);
Printv(default_import_code, "else:\n", NULL);
Printf(default_import_code, tab4 "import %s\n", module);
if (builtin) {
/*
@ -890,22 +890,22 @@ public:
* globals()[attr] = getattr(_foo, attr)
*
*/
Printf(f_shadow, "# pull in all the attributes from %s\n", module);
Printv(f_shadow, "if __name__.rpartition('.')[0] != '':\n", NULL);
Printv(f_shadow, tab4, "if _swig_python_version_info >= (2, 7, 0):\n", NULL);
Printv(f_shadow, tab8, "try:\n", NULL);
Printf(f_shadow, tab8 tab4 "from .%s import *\n", module);
Printv(f_shadow, tab8 "except ImportError:\n", NULL);
Printf(f_shadow, tab8 tab4 "from %s import *\n", module);
Printv(f_shadow, tab4, "else:\n", NULL);
Printf(f_shadow, tab8 "from %s import *\n", module);
Printv(f_shadow, "else:\n", NULL);
Printf(f_shadow, tab4 "from %s import *\n", module);
Printf(default_import_code, "# pull in all the attributes from %s\n", module);
Printv(default_import_code, "if __name__.rpartition('.')[0] != '':\n", NULL);
Printv(default_import_code, tab4, "if _swig_python_version_info >= (2, 7, 0):\n", NULL);
Printv(default_import_code, tab8, "try:\n", NULL);
Printf(default_import_code, tab8 tab4 "from .%s import *\n", module);
Printv(default_import_code, tab8 "except ImportError:\n", NULL);
Printf(default_import_code, tab8 tab4 "from %s import *\n", module);
Printv(default_import_code, tab4, "else:\n", NULL);
Printf(default_import_code, tab8 "from %s import *\n", module);
Printv(default_import_code, "else:\n", NULL);
Printf(default_import_code, tab4 "from %s import *\n", module);
}
/* Delete the _swig_python_version_info symbol since we don't use it elsewhere in the
* module. */
Printv(f_shadow, "del _swig_python_version_info\n", NULL);
Printv(default_import_code, "del _swig_python_version_info\n", NULL);
if (modern || !classic) {
Printv(f_shadow, "try:\n", tab4, "_swig_property = property\n", "except NameError:\n", tab4, "pass # Python < 2.2 doesn't have 'property'.\n\n", NULL);
@ -1053,8 +1053,13 @@ public:
Printv(f_shadow, "# This file is compatible with both classic and new-style classes.\n", NIL);
}
Printv(f_shadow_py, "\n", f_shadow_begin, "\n", NIL);
Printv(f_shadow_py, "\n", f_shadow_builtin_imports, "\n", NIL);
Printv(f_shadow_py, f_shadow, "\n", NIL);
if (moduleimport) {
Replaceall(moduleimport, "$module", module);
Printv(f_shadow_py, moduleimport, NIL);
} else {
Printv(f_shadow_py, default_import_code, NIL);
}
Printv(f_shadow_py, "\n", f_shadow, "\n", NIL);
Printv(f_shadow_py, f_shadow_stubs, "\n", NIL);
Delete(f_shadow_py);
}
@ -1077,6 +1082,9 @@ public:
Printf(f_begin, "static PyTypeObject *builtin_bases[%d];\n\n", max_bases + 2);
Wrapper_pretty_print(f_init, f_begin);
Delete(default_import_code);
Delete(f_shadow_begin);
Delete(f_shadow);
Delete(f_header);
Delete(f_wrappers);
Delete(f_builtins);