Commit bc970873 authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

[1.1.X] Fixed #13464 -- Reworked module_has_submodule to break the requirement...

[1.1.X] Fixed #13464 -- Reworked module_has_submodule to break the requirement for loader and finder to be the same class. Thanks to Alex Gaynor for the report and patch, and Brett Cannon for suggesting the approach.

Backport of r13082 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@13085 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent a263d3b4
Loading
Loading
Loading
Loading
+55 −21
Original line number Diff line number Diff line
import os
import imp
import os
import sys

def module_has_submodule(mod, submod_name):
    # If the module was loaded from an egg, __loader__ will be set and
    # its find_module must be used to search for submodules.
    loader = getattr(mod, '__loader__', None)
    if loader:
        mod_path = "%s.%s" % (mod.__name__[mod.__name__.rfind('.')+1:], submod_name)
        x = loader.find_module(mod_path)
        if x is None:
            # zipimport.zipimporter.find_module is documented to take
            # dotted paths but in fact through Python 2.7 is observed
            # to require os.sep in place of dots...so try using os.sep
            # if the dotted path version failed to find the requested
            # submodule.
            x = loader.find_module(mod_path.replace('.', os.sep))
        return x is not None

def module_has_submodule(package, module_name):
    """See if 'module' is in 'package'."""
    name = ".".join([package.__name__, module_name])
    if name in sys.modules:
        return True
    for finder in sys.meta_path:
        if finder.find_module(name):
            return True
    for entry in package.__path__:  # No __path__, then not a package.
        try:
            # Try the cached finder.
            finder = sys.path_importer_cache[entry]
            if finder is None:
                # Implicit import machinery should be used.
                try:
                    file_, _, _ = imp.find_module(module_name, [entry])
                    if file_:
                        file_.close()
                    return True
                except ImportError:
                    continue
            # Else see if the finder knows of a loader.
            elif finder.find_module(name):
                return True
            else:
                continue
        except KeyError:
            # No cached finder, so try and make one.
            for hook in sys.path_hooks:
                try:
        imp.find_module(submod_name, mod.__path__)
                    finder = hook(entry)
                    # XXX Could cache in sys.path_importer_cache
                    if finder.find_module(name):
                        return True
                    else:
                        # Once a finder is found, stop the search.
                        break
                except ImportError:
                    # Continue the search for a finder.
                    continue
            else:
                # No finder found.
                # Try the implicit import machinery if searching a directory.
                if os.path.isdir(entry):
                    try:
                        file_, _, _ = imp.find_module(module_name, [entry])
                        if file_:
                            file_.close()
                        return True
                    except ImportError:
                        pass
                # XXX Could insert None or NullImporter
    else:
        # Exhausted the search, so the module cannot be found.
        return False

+4.73 KiB

File added.

No diff preview for this file type.

+139 −0
Original line number Diff line number Diff line
import os
import sys
from unittest import TestCase
from zipimport import zipimporter

from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule

class DefaultLoader(TestCase):
    def test_loader(self):
        "Normal module existence can be tested"
        test_module = import_module('regressiontests.utils.test_module')

        # An importable child
        self.assertTrue(module_has_submodule(test_module, 'good_module'))
        mod = import_module('regressiontests.utils.test_module.good_module')
        self.assertEqual(mod.content, 'Good Module')

        # A child that exists, but will generate an import error if loaded
        self.assertTrue(module_has_submodule(test_module, 'bad_module'))
        self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.bad_module')

        # A child that doesn't exist
        self.assertFalse(module_has_submodule(test_module, 'no_such_module'))
        self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.no_such_module')

class EggLoader(TestCase):
    def setUp(self):
        self.old_path = sys.path
        self.egg_dir = '%s/eggs' % os.path.dirname(__file__)

    def tearDown(self):
        sys.path = self.old_path

    def test_shallow_loader(self):
        "Module existence can be tested inside eggs"
        egg_name = '%s/test_egg.egg' % self.egg_dir
        sys.path.append(egg_name)
        egg_module = import_module('egg_module')

        # An importable child
        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
        mod = import_module('egg_module.good_module')
        self.assertEqual(mod.content, 'Good Module')

        # A child that exists, but will generate an import error if loaded
        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.bad_module')

        # A child that doesn't exist
        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.no_such_module')

    def test_deep_loader(self):
        "Modules deep inside an egg can still be tested for existence"
        egg_name = '%s/test_egg.egg' % self.egg_dir
        sys.path.append(egg_name)
        egg_module = import_module('egg_module.sub1.sub2')

        # An importable child
        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
        mod = import_module('egg_module.sub1.sub2.good_module')
        self.assertEqual(mod.content, 'Deep Good Module')

        # A child that exists, but will generate an import error if loaded
        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module')

        # A child that doesn't exist
        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')

class TestFinder(object):
    def __init__(self, *args, **kwargs):
        self.importer = zipimporter(*args, **kwargs)

    def find_module(self, path):
        importer = self.importer.find_module(path)
        if importer is None:
            return
        return TestLoader(importer)

class TestLoader(object):
    def __init__(self, importer):
        self.importer = importer

    def load_module(self, name):
        mod = self.importer.load_module(name)
        mod.__loader__ = self
        return mod

class CustomLoader(TestCase):
    def setUp(self):
        self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
        self.old_path = sys.path
        sys.path_hooks.insert(0, TestFinder)
        sys.path_importer_cache.clear()

    def tearDown(self):
        sys.path = self.old_path
        sys.path_hooks.pop(0)

    def test_shallow_loader(self):
        "Module existence can be tested with a custom loader"
        egg_name = '%s/test_egg.egg' % self.egg_dir
        sys.path.append(egg_name)
        egg_module = import_module('egg_module')

        # An importable child
        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
        mod = import_module('egg_module.good_module')
        self.assertEqual(mod.content, 'Good Module')

        # A child that exists, but will generate an import error if loaded
        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.bad_module')

        # A child that doesn't exist
        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.no_such_module')

    def test_deep_loader(self):
        "Modules existence can be tested deep inside a custom loader"
        egg_name = '%s/test_egg.egg' % self.egg_dir
        sys.path.append(egg_name)
        egg_module = import_module('egg_module.sub1.sub2')

        # An importable child
        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
        mod = import_module('egg_module.sub1.sub2.good_module')
        self.assertEqual(mod.content, 'Deep Good Module')

        # A child that exists, but will generate an import error if loaded
        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module')

        # A child that doesn't exist
        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
+0 −0

Empty file added.

+3 −0
Original line number Diff line number Diff line
import a_package_name_that_does_not_exist

content = 'Bad Module'
 No newline at end of file
Loading