Restructure module importer to cope with Ansible.

pull/35/head
David Wilson 9 years ago
parent 66f92ad44c
commit 301972bc57

@ -6,6 +6,7 @@ sent to any context that will be used to establish additional child contexts.
import commands import commands
import getpass import getpass
import imp
import inspect import inspect
import logging import logging
import os import os
@ -111,31 +112,77 @@ class ModuleResponder(object):
def __repr__(self): def __repr__(self):
return 'ModuleResponder(%r)' % (self._context,) return 'ModuleResponder(%r)' % (self._context,)
def _get_module_via_pkgutil(self, fullname):
"""Attempt to fetch source code via pkgutil. In an ideal world, this
would be the only required implementation of get_module()."""
loader = pkgutil.find_loader(fullname)
LOG.debug('pkgutil.find_loader(%r) -> %r', fullname, loader)
if not loader:
return
path = loader.get_filename(fullname)
source = loader.get_source(fullname)
if path and source:
return path, source, loader.is_package(fullname)
def _get_module_via_sys_modules(self, fullname):
"""Attempt to fetch source code via sys.modules. This is specifically
to support __main__, but it may catch a few more cases."""
if fullname not in sys.modules:
LOG.debug('%r does not appear in sys.modules', fullname)
return
is_pkg = hasattr(sys.modules[fullname], '__path__')
try:
source = inspect.getsource(sys.modules[fullname])
except IOError:
# Work around inspect.getsourcelines() bug.
if not is_pkg:
raise
source = '\n'
return (sys.modules[fullname].__file__.rstrip('co'),
source,
hasattr(sys.modules[fullname], '__path__'))
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 ansible.compat.six."""
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])
return path, fp.read(), False
except ImportError:
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_parent_enumeration]
def get_module(self, data): def get_module(self, data):
LOG.debug('%r.get_module(%r)', self, data)
if data == econtext.core._DEAD: if data == econtext.core._DEAD:
return return
reply_to, fullname = data reply_to, fullname = data
LOG.debug('%r.get_module(%r, %r)', self, reply_to, fullname)
try: try:
loader = pkgutil.find_loader(fullname) for method in self.get_module_methods:
LOG.debug('pkgutil.find_loader(%r) -> %r', fullname, loader) tup = method(self, fullname)
if loader is None: if tup:
raise ImportError('pkgutil provides no loader for %r' % break
(fullname,))
path = loader.get_filename(fullname)
LOG.debug('%r.get_filename(%r) -> %r', loader, fullname, path)
# Handle __main__ specially. try:
if path is None and fullname in sys.modules: path, source, is_pkg = tup
path = sys.modules[fullname].__file__.rstrip('co') except TypeError:
source = inspect.getsource(sys.modules[fullname]) raise ImportError('could not find %r' % (fullname,))
is_pkg = hasattr(sys.modules[fullname], '__path__')
else:
source = loader.get_source(fullname)
is_pkg = loader.is_package(fullname)
LOG.debug('%r returned for %r: (%r, .., %r)',
method, fullname, path, is_pkg)
if is_pkg: if is_pkg:
pkg_present = get_child_modules(path, fullname) pkg_present = get_child_modules(path, fullname)
LOG.debug('get_child_modules(%r, %r) -> %r', LOG.debug('get_child_modules(%r, %r) -> %r',

@ -4,6 +4,7 @@ import subprocess
import unittest import unittest
import sys import sys
import econtext.master
import econtext.master import econtext.master
import testlib import testlib
@ -11,7 +12,7 @@ import plain_old_module
import simple_pkg.a import simple_pkg.a
class ModuleTest(testlib.BrokerMixin, unittest.TestCase): class GoodModulesTest(testlib.BrokerMixin, unittest.TestCase):
def test_plain_old_module(self): def test_plain_old_module(self):
# The simplest case: a top-level module with no interesting imports or # The simplest case: a top-level module with no interesting imports or
# package machinery damage. # package machinery damage.
@ -33,7 +34,7 @@ class ModuleTest(testlib.BrokerMixin, unittest.TestCase):
self.assertEquals(output, "['__main__', 50]\n") self.assertEquals(output, "['__main__', 50]\n")
class BrokenPackagesTest(unittest.TestCase): class BrokenModulesTest(unittest.TestCase):
def test_ansible_six_messed_up_path(self): def test_ansible_six_messed_up_path(self):
# The copy of six.py shipped with Ansible appears in a package whose # The copy of six.py shipped with Ansible appears in a package whose
# __path__ subsequently ends up empty, which prevents pkgutil from # __path__ subsequently ends up empty, which prevents pkgutil from
@ -50,4 +51,4 @@ class BrokenPackagesTest(unittest.TestCase):
call = context.enqueue.mock_calls[0] call = context.enqueue.mock_calls[0]
reply_to, data = call[1] reply_to, data = call[1]
self.assertEquals(50, reply_to) self.assertEquals(50, reply_to)
self.assertTrue(isinstance(data, str)) self.assertTrue(isinstance(data, tuple))

Loading…
Cancel
Save