diff --git a/CHANGES.current b/CHANGES.current index 679d786f9..27194fba2 100644 --- a/CHANGES.current +++ b/CHANGES.current @@ -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 diff --git a/Doc/Manual/Python.html b/Doc/Manual/Python.html index b6df0895f..cb847c074 100644 --- a/Doc/Manual/Python.html +++ b/Doc/Manual/Python.html @@ -509,7 +509,7 @@ If using static linking, you might want to rely on a different approach
To use your module, simply use the Python import statement. If -all goes well, you will be able to this: +all goes well, you will be able to run this:
@@ -6037,7 +6037,7 @@ zipimporter requires python-3.5.1 or newer to work with subpackages.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 Preliminaries section 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:
-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 moduleimport option of the %module directive with the appropriate import code. For example: +
+ +++ ++%module(moduleimport="import _foo") foo +++The special variable $module will also be expanded into the low-level C/C++ module name, _foo in the case above. +When you have more than just a line or so then you can retain the easy +readability of the %module directive by using a macro. For +example: +
+ +++ + ++%define MODULEIMPORT +" +print 'Loading low-level module $module' +import $module +print 'Module has loaded' +" +%enddef + +%module(moduleimport=MODULEIMPORT) foo +++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:
diff --git a/Examples/test-suite/python/Makefile.in b/Examples/test-suite/python/Makefile.in index 8ccb1b306..133b7056a 100644 --- a/Examples/test-suite/python/Makefile.in +++ b/Examples/test-suite/python/Makefile.in @@ -63,6 +63,7 @@ CPP_TEST_CASES += \ python_director \ python_docstring \ python_extranative \ + python_moduleimport \ python_nondynamic \ python_overload_simple_cast \ python_pickle \ diff --git a/Examples/test-suite/python/python_moduleimport_runme.py b/Examples/test-suite/python/python_moduleimport_runme.py new file mode 100644 index 000000000..cdb860730 --- /dev/null +++ b/Examples/test-suite/python/python_moduleimport_runme.py @@ -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") diff --git a/Examples/test-suite/python_moduleimport.i b/Examples/test-suite/python_moduleimport.i new file mode 100644 index 000000000..942cdab6e --- /dev/null +++ b/Examples/test-suite/python_moduleimport.i @@ -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; } +%} diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx index 20ffeb4ae..8a10ce2d5 100644 --- a/Source/Modules/python.cxx +++ b/Source/Modules/python.cxx @@ -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);