Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13464 -- Reworked module_has_submodule to break the requiremen…

…t 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.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13082 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 4ca7c4e34de273f0deab727d57132c9d18241b0b 1 parent 7fc2571
Russell Keith-Magee authored
76  django/utils/module_loading.py
... ...
@@ -1,26 +1,60 @@
1  
-import os
2 1
 import imp
  2
+import os
  3
+import sys
3 4
 
4  
-def module_has_submodule(mod, submod_name):
5  
-    # If the module was loaded from an egg, __loader__ will be set and
6  
-    # its find_module must be used to search for submodules.
7  
-    loader = getattr(mod, '__loader__', None)
8  
-    if loader:
9  
-        mod_path = "%s.%s" % (mod.__name__.rsplit('.',1)[-1], submod_name)
10  
-        x = loader.find_module(mod_path)
11  
-        if x is None:
12  
-            # zipimport.zipimporter.find_module is documented to take
13  
-            # dotted paths but in fact through Python 2.7 is observed
14  
-            # to require os.sep in place of dots...so try using os.sep
15  
-            # if the dotted path version failed to find the requested
16  
-            # submodule.
17  
-            x = loader.find_module(mod_path.replace('.', os.sep))
18  
-        return x is not None
19 5
 
20  
-    try:
21  
-        imp.find_module(submod_name, mod.__path__)
  6
+def module_has_submodule(package, module_name):
  7
+    """See if 'module' is in 'package'."""
  8
+    name = ".".join([package.__name__, module_name])
  9
+    if name in sys.modules:
22 10
         return True
23  
-    except ImportError:
  11
+    for finder in sys.meta_path:
  12
+        if finder.find_module(name):
  13
+            return True
  14
+    for entry in package.__path__:  # No __path__, then not a package.
  15
+        try:
  16
+            # Try the cached finder.
  17
+            finder = sys.path_importer_cache[entry]
  18
+            if finder is None:
  19
+                # Implicit import machinery should be used.
  20
+                try:
  21
+                    file_, _, _ = imp.find_module(module_name, [entry])
  22
+                    if file_:
  23
+                        file_.close()
  24
+                    return True
  25
+                except ImportError:
  26
+                    continue
  27
+            # Else see if the finder knows of a loader.
  28
+            elif finder.find_module(name):
  29
+                return True
  30
+            else:
  31
+                continue
  32
+        except KeyError:
  33
+            # No cached finder, so try and make one.
  34
+            for hook in sys.path_hooks:
  35
+                try:
  36
+                    finder = hook(entry)
  37
+                    # XXX Could cache in sys.path_importer_cache
  38
+                    if finder.find_module(name):
  39
+                        return True
  40
+                    else:
  41
+                        # Once a finder is found, stop the search.
  42
+                        break
  43
+                except ImportError:
  44
+                    # Continue the search for a finder.
  45
+                    continue
  46
+            else:
  47
+                # No finder found.
  48
+                # Try the implicit import machinery if searching a directory.
  49
+                if os.path.isdir(entry):
  50
+                    try:
  51
+                        file_, _, _ = imp.find_module(module_name, [entry])
  52
+                        if file_:
  53
+                            file_.close()
  54
+                        return True
  55
+                    except ImportError:
  56
+                        pass
  57
+                # XXX Could insert None or NullImporter
  58
+    else:
  59
+        # Exhausted the search, so the module cannot be found.
24 60
         return False
25  
-
26  
-
BIN  tests/regressiontests/utils/eggs/test_egg.egg
Binary file not shown
139  tests/regressiontests/utils/module_loading.py
... ...
@@ -0,0 +1,139 @@
  1
+import os
  2
+import sys
  3
+from unittest import TestCase
  4
+from zipimport import zipimporter
  5
+
  6
+from django.utils.importlib import import_module
  7
+from django.utils.module_loading import module_has_submodule
  8
+
  9
+class DefaultLoader(TestCase):
  10
+    def test_loader(self):
  11
+        "Normal module existence can be tested"
  12
+        test_module = import_module('regressiontests.utils.test_module')
  13
+
  14
+        # An importable child
  15
+        self.assertTrue(module_has_submodule(test_module, 'good_module'))
  16
+        mod = import_module('regressiontests.utils.test_module.good_module')
  17
+        self.assertEqual(mod.content, 'Good Module')
  18
+
  19
+        # A child that exists, but will generate an import error if loaded
  20
+        self.assertTrue(module_has_submodule(test_module, 'bad_module'))
  21
+        self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.bad_module')
  22
+
  23
+        # A child that doesn't exist
  24
+        self.assertFalse(module_has_submodule(test_module, 'no_such_module'))
  25
+        self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.no_such_module')
  26
+
  27
+class EggLoader(TestCase):
  28
+    def setUp(self):
  29
+        self.old_path = sys.path
  30
+        self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
  31
+
  32
+    def tearDown(self):
  33
+        sys.path = self.old_path
  34
+
  35
+    def test_shallow_loader(self):
  36
+        "Module existence can be tested inside eggs"
  37
+        egg_name = '%s/test_egg.egg' % self.egg_dir
  38
+        sys.path.append(egg_name)
  39
+        egg_module = import_module('egg_module')
  40
+
  41
+        # An importable child
  42
+        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
  43
+        mod = import_module('egg_module.good_module')
  44
+        self.assertEqual(mod.content, 'Good Module')
  45
+
  46
+        # A child that exists, but will generate an import error if loaded
  47
+        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
  48
+        self.assertRaises(ImportError, import_module, 'egg_module.bad_module')
  49
+
  50
+        # A child that doesn't exist
  51
+        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
  52
+        self.assertRaises(ImportError, import_module, 'egg_module.no_such_module')
  53
+
  54
+    def test_deep_loader(self):
  55
+        "Modules deep inside an egg can still be tested for existence"
  56
+        egg_name = '%s/test_egg.egg' % self.egg_dir
  57
+        sys.path.append(egg_name)
  58
+        egg_module = import_module('egg_module.sub1.sub2')
  59
+
  60
+        # An importable child
  61
+        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
  62
+        mod = import_module('egg_module.sub1.sub2.good_module')
  63
+        self.assertEqual(mod.content, 'Deep Good Module')
  64
+
  65
+        # A child that exists, but will generate an import error if loaded
  66
+        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
  67
+        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module')
  68
+
  69
+        # A child that doesn't exist
  70
+        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
  71
+        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
  72
+
  73
+class TestFinder(object):
  74
+    def __init__(self, *args, **kwargs):
  75
+        self.importer = zipimporter(*args, **kwargs)
  76
+
  77
+    def find_module(self, path):
  78
+        importer = self.importer.find_module(path)
  79
+        if importer is None:
  80
+            return
  81
+        return TestLoader(importer)
  82
+
  83
+class TestLoader(object):
  84
+    def __init__(self, importer):
  85
+        self.importer = importer
  86
+
  87
+    def load_module(self, name):
  88
+        mod = self.importer.load_module(name)
  89
+        mod.__loader__ = self
  90
+        return mod
  91
+
  92
+class CustomLoader(TestCase):
  93
+    def setUp(self):
  94
+        self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
  95
+        self.old_path = sys.path
  96
+        sys.path_hooks.insert(0, TestFinder)
  97
+        sys.path_importer_cache.clear()
  98
+
  99
+    def tearDown(self):
  100
+        sys.path = self.old_path
  101
+        sys.path_hooks.pop(0)
  102
+
  103
+    def test_shallow_loader(self):
  104
+        "Module existence can be tested with a custom loader"
  105
+        egg_name = '%s/test_egg.egg' % self.egg_dir
  106
+        sys.path.append(egg_name)
  107
+        egg_module = import_module('egg_module')
  108
+
  109
+        # An importable child
  110
+        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
  111
+        mod = import_module('egg_module.good_module')
  112
+        self.assertEqual(mod.content, 'Good Module')
  113
+
  114
+        # A child that exists, but will generate an import error if loaded
  115
+        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
  116
+        self.assertRaises(ImportError, import_module, 'egg_module.bad_module')
  117
+
  118
+        # A child that doesn't exist
  119
+        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
  120
+        self.assertRaises(ImportError, import_module, 'egg_module.no_such_module')
  121
+
  122
+    def test_deep_loader(self):
  123
+        "Modules existence can be tested deep inside a custom loader"
  124
+        egg_name = '%s/test_egg.egg' % self.egg_dir
  125
+        sys.path.append(egg_name)
  126
+        egg_module = import_module('egg_module.sub1.sub2')
  127
+
  128
+        # An importable child
  129
+        self.assertTrue(module_has_submodule(egg_module, 'good_module'))
  130
+        mod = import_module('egg_module.sub1.sub2.good_module')
  131
+        self.assertEqual(mod.content, 'Deep Good Module')
  132
+
  133
+        # A child that exists, but will generate an import error if loaded
  134
+        self.assertTrue(module_has_submodule(egg_module, 'bad_module'))
  135
+        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module')
  136
+
  137
+        # A child that doesn't exist
  138
+        self.assertFalse(module_has_submodule(egg_module, 'no_such_module'))
  139
+        self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module')
0  tests/regressiontests/utils/test_module/__init__.py
No changes.
3  tests/regressiontests/utils/test_module/bad_module.py
... ...
@@ -0,0 +1,3 @@
  1
+import a_package_name_that_does_not_exist
  2
+
  3
+content = 'Bad Module'
1  tests/regressiontests/utils/test_module/good_module.py
... ...
@@ -0,0 +1 @@
  1
+content = 'Good Module'
1  tests/regressiontests/utils/tests.py
@@ -34,6 +34,7 @@
34 34
 
35 35
 from dateformat import *
36 36
 from feedgenerator import *
  37
+from module_loading import *
37 38
 from termcolors import *
38 39
 
39 40
 class TestUtilsHtml(TestCase):

0 notes on commit 4ca7c4e

Please sign in to comment.
Something went wrong with that request. Please try again.