From 4ca7c4e34de273f0deab727d57132c9d18241b0b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 4 May 2010 06:14:47 +0000 Subject: [PATCH] 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. git-svn-id: http://code.djangoproject.com/svn/django/trunk@13082 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/module_loading.py | 76 +++++++--- tests/regressiontests/utils/eggs/test_egg.egg | Bin 0 -> 4844 bytes tests/regressiontests/utils/module_loading.py | 139 ++++++++++++++++++ .../utils/test_module/__init__.py | 0 .../utils/test_module/bad_module.py | 3 + .../utils/test_module/good_module.py | 1 + tests/regressiontests/utils/tests.py | 1 + 7 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 tests/regressiontests/utils/eggs/test_egg.egg create mode 100644 tests/regressiontests/utils/module_loading.py create mode 100644 tests/regressiontests/utils/test_module/__init__.py create mode 100644 tests/regressiontests/utils/test_module/bad_module.py create mode 100644 tests/regressiontests/utils/test_module/good_module.py diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py index 9bcdd276fd..f251035387 100644 --- a/django/utils/module_loading.py +++ b/django/utils/module_loading.py @@ -1,26 +1,60 @@ -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__.rsplit('.',1)[-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 - try: - imp.find_module(submod_name, mod.__path__) +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 - except ImportError: + 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: + 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 - - diff --git a/tests/regressiontests/utils/eggs/test_egg.egg b/tests/regressiontests/utils/eggs/test_egg.egg new file mode 100644 index 0000000000000000000000000000000000000000..9b08cc10eff413ec883186fdba4eb7b7707fff10 GIT binary patch literal 4844 zcmb7|2~ZPP7{`|z(G*QVj*13NMvep&tCf+CK?M^+$boo`1w?|P2xt(cMJ0;WfI^6< zSnaghctk5@ysA^`NJlzhNW@ya+D^5cwb}zyELI)+-Y#U{?q(UAnPp&x{k{M9f8YDw zACxjTcOMLXeAk-d@NWjcjKKV{g!%JD(v(C+blQRum|M!Z`!bKq_ho6D+_8QaKTPR( zw>7^iZm;K^7#Hv^d_vH7vo(d9{A^ABijs`H-264#sG?0pIIn7Pvuy2olIH_~e&BU) z3<6(bM`Xp|rz_wYg94Q@LX}g9T`rO)ONG*Qj!YDB-P(3eJ*9oxgYG9XtNeo6VpB+M zQeoJG?m7E7Q8t@gc)dkW4nOsrbW>bXQq_?W8+pMiZef6EllU+#(-Y0r{Pl)fWijnGP@+)L} zN39F;+V93qEcbtKG3bcrEsl8M>XfW_-FW4h{G9ZqSGJUW`BnXiPY%rK#>WN*mR;Co6m7`;g-d-{HEp-8(tN8@x)5lAAf zOGg4>BkrP1CRE!ss7CN49j%S^5RA1xNyTqjcN+!jhW3c zj0;u?IehL&pN`gPvX~C7Jyor(=DW{;6|boTp1RK}J)nIZ1Gd3eDrLwGaqJ){BO z0SX(kB>ur%zTcoNXO}Q~CS@oAkWLmE)q5|&ITJ4A9cLD{M!4ou$3c@0|Yp4up z(VE*m&wp+irM&8X?G5oVp(kHmwyP%ekS^56l9{zD%YEJU>2*5w!B@JgU(}7X6hR@x z>A@aG)wpo=eU~$928_F}83$@#w08_NzAz57S6DlY+~QBSb-idbwl{J)hL63E)&)rT zg5%;1kNg*m)nW;Gyk)Zz4O~M=Trx7`qCiPu_fIDT;-)f6T3Jv)cXlVOq!k7EMVkB~ z-n+cX^TBWPdpzkFMKN_JN_Z~c~Z>FRX7r^VY{W%c6U)CG5Fn}Yww zTW!va(e)vc52i{&5j)@!016$S(NwA04Ovr(;|P1-`L(f&hIq>ff_^4_0S&!0$U6fH z5+6C)vk*wy2%d4n)7TGY<<@%%q9s@=9zsQ^D*T4%t9tG6 zH^EZm&^0&%gH#jU_}ory)5JbOkY3Oi{>pJ#cLkB1|BWr zPnz4{`KBZplUy<#9~*WF;E?dM#xqDD98Ms+k%+QOn<^!n?jg$z?QZh4rqBxE#f}>?o0EAhTtd%W# zqQK=SkPM=XS_u|2ff6iYPmco@k|@&_018YtbVbZ0Iz)lH+C`;MW;g)CTuK7j5-ABZ z=|pZ6SqKoE6F`J_G@Zx{lB8)SorOxIjah(%v67Btrb1L?iaSLl1xd(s<|H$gp)x}~ zC`{^%z)OcAjjod!!%&?&hGP;!VJIV^MB^|6(-0I6gDIXAp@Z>>u{8vW8Jvbt7-rIG zouH5%$E$`hiWEq;5H&!d)TZI7&iaTD%ZxljSXg(C7qjlc00fFBZba%pWJaGM9xT3| zjYoHY_`_}pO*%l0Kq%V9n?)*uc4h;?W+oPb4d*zJ&Bmsj0(|RWexYw2GkKuG=Z;_z zPJt8BojK0D^-<{pu+7NG!#fw>K_`a|W&pZ&<^_*xuX{Z@A)dJE;huVcZiRoA2Vmu( KA=L1H7}$TXr<2tH literal 0 HcmV?d00001 diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py new file mode 100644 index 0000000000..7b233f8e14 --- /dev/null +++ b/tests/regressiontests/utils/module_loading.py @@ -0,0 +1,139 @@ +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') diff --git a/tests/regressiontests/utils/test_module/__init__.py b/tests/regressiontests/utils/test_module/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/utils/test_module/bad_module.py b/tests/regressiontests/utils/test_module/bad_module.py new file mode 100644 index 0000000000..cc0cd168ce --- /dev/null +++ b/tests/regressiontests/utils/test_module/bad_module.py @@ -0,0 +1,3 @@ +import a_package_name_that_does_not_exist + +content = 'Bad Module' \ No newline at end of file diff --git a/tests/regressiontests/utils/test_module/good_module.py b/tests/regressiontests/utils/test_module/good_module.py new file mode 100644 index 0000000000..0ca689832a --- /dev/null +++ b/tests/regressiontests/utils/test_module/good_module.py @@ -0,0 +1 @@ +content = 'Good Module' \ No newline at end of file diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 4ad4e63e33..8897df6789 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -34,6 +34,7 @@ __test__ = { from dateformat import * from feedgenerator import * +from module_loading import * from termcolors import * class TestUtilsHtml(TestCase):