diff --git a/docs/changelog.rst b/docs/changelog.rst index dab5821e..5ef62611 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -246,6 +246,15 @@ Core Library causing Mitogen to install itself at the end of the importer chain rather than the front. +* `#310 `_: support has returned for + trying to figure out the real source of non-module objects installed in + :data:`sys.modules`, so they can be imported. This is needed to handle syntax + sugar used by packages like :mod:`plumbum`. + +* `#349 `_: an incorrect format + string could cause large stack traces when attempting to import built-in + modules on Python 3. + * `#387 `_, `#413 `_: dead messages include an optional reason in their body. This is used to cause @@ -367,6 +376,10 @@ Core Library raised by the import hook were updated to include probable reasons for a failure. +* `57b652ed `_: a stray import + meant an extra roundtrip and ~20KiB of data was wasted for any context that + imported :mod:`mitogen.parent`. + Thanks! ~~~~~~~ @@ -379,6 +392,7 @@ bug reports, testing, features and fixes in this release contributed by `Duane Zamrok `_, `Eric Chang `_, `Guy Knights `_, +`Jesse London `_, `Jiří Vávra `_, `Jonathan Rosser `_, `Johan Beisser `_, @@ -386,6 +400,7 @@ bug reports, testing, features and fixes in this release contributed by `Mehdi `_, `Michael DeHaan `_, `Mohammed Naser `_, +`Peter V. Saveliev `_, `Stéphane `_, `@whky `_, `@syntonym `_, diff --git a/mitogen/master.py b/mitogen/master.py index 28d3fa9d..1b6aaa61 100644 --- a/mitogen/master.py +++ b/mitogen/master.py @@ -453,8 +453,47 @@ class ModuleFinder(object): return path, source, is_pkg + def _get_module_via_parent_enumeration(self, fullname): + """ + Attempt to fetch source code by examining the module's (hopefully less + insane) parent package. Required for older versions of + ansible.compat.six and plumbum.colors. + """ + if fullname not in sys.modules: + # Don't attempt this unless a module really exists in sys.modules, + # else we could return junk. + return + + pkgname, _, modname = fullname.rpartition('.') + pkg = sys.modules.get(pkgname) + if pkg is None or not hasattr(pkg, '__file__'): + return + + pkg_path = os.path.dirname(pkg.__file__) + try: + fp, path, ext = imp.find_module(modname, [pkg_path]) + try: + path = self._py_filename(path) + if not path: + fp.close() + return + + source = fp.read() + finally: + fp.close() + + if isinstance(source, mitogen.core.UnicodeType): + # get_source() returns "string" according to PEP-302, which was + # reinterpreted for Python 3 to mean a Unicode string. + source = source.encode('utf-8') + return path, source, False + except ImportError: + e = sys.exc_info()[1] + LOG.debug('imp.find_module(%r, %r) -> %s', modname, [pkg_path], e) + get_module_methods = [_get_module_via_pkgutil, - _get_module_via_sys_modules] + _get_module_via_sys_modules, + _get_module_via_parent_enumeration] def get_module_source(self, fullname): """Given the name of a loaded module `fullname`, attempt to find its @@ -471,6 +510,7 @@ class ModuleFinder(object): for method in self.get_module_methods: tup = method(self, fullname) if tup: + #LOG.debug('%r returned %r', method, tup) break else: tup = None, None, None diff --git a/tests/data/pkg_like_plumbum/__init__.py b/tests/data/pkg_like_plumbum/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/pkg_like_plumbum/colors.py b/tests/data/pkg_like_plumbum/colors.py new file mode 100644 index 00000000..bff19555 --- /dev/null +++ b/tests/data/pkg_like_plumbum/colors.py @@ -0,0 +1,15 @@ + +# coding=utf-8 + +import sys + + +# £ + +class EvilObject(object): + """ + Wild cackles! I have come to confuse perplex your importer with rainbows! + """ + +sys.modules[__name__] = EvilObject() + diff --git a/tests/module_finder_test.py b/tests/module_finder_test.py index 1d5a0796..f452a38c 100644 --- a/tests/module_finder_test.py +++ b/tests/module_finder_test.py @@ -105,6 +105,39 @@ class GetModuleViaSysModulesTest(testlib.TestCase): self.assertIsNone(tup) +class GetModuleViaParentEnumerationTest(testlib.TestCase): + klass = mitogen.master.ModuleFinder + + def call(self, fullname): + return self.klass()._get_module_via_parent_enumeration(fullname) + + def test_main_fails(self): + import __main__ + self.assertIsNone(self.call('__main__')) + + def test_dylib_fails(self): + # _socket comes from a .so + import _socket + tup = self.call('_socket') + self.assertIsNone(tup) + + def test_builtin_fails(self): + # sys is built-in + tup = self.call('sys') + self.assertIsNone(tup) + + def test_plumbum_colors_like_pkg_succeeds(self): + # plumbum has been eating too many rainbow-colored pills + import pkg_like_plumbum.colors + path, src, is_pkg = self.call('pkg_like_plumbum.colors') + self.assertEquals(path, + testlib.data_path('pkg_like_plumbum/colors.py')) + + s = open(testlib.data_path('pkg_like_plumbum/colors.py'), 'rb').read() + self.assertEquals(src, s) + self.assertFalse(is_pkg) + + class ResolveRelPathTest(testlib.TestCase): klass = mitogen.master.ModuleFinder