Deal with partially setup packages in the redirect hook.

This commit is contained in:
Armin Ronacher 2011-09-21 21:34:47 +02:00
parent ee9b401632
commit 9691b7f0bf
6 changed files with 49 additions and 7 deletions

View file

@ -57,6 +57,17 @@ class _ExtensionImporter(object):
__import__(realname)
except ImportError:
exc_type, exc_value, tb = exc_info()
# since we only establish the entry in sys.modules at the
# very this seems to be redundant, but if recursive imports
# happen we will call into the move import a second time.
# On the second invocation we still don't have an entry for
# fullname in sys.modules, but we will end up with the same
# fake module name and that import will succeed since this
# one already has a temporary entry in the modules dict.
# Since this one "succeeded" temporarily that second
# invocation now will have created a fullname entry in
# sys.modules which we have to kill.
modules.pop(fullname, None)
if self.is_important_traceback(realname, tb):
raise exc_type, exc_value, tb
continue

View file

@ -134,6 +134,32 @@ class FlaskTestCase(unittest.TestCase):
def assert_equal(self, x, y):
return self.assertEqual(x, y)
def assert_raises(self, exc_type, callable=None, *args, **kwargs):
catcher = _ExceptionCatcher(self, exc_type)
if callable is None:
return catcher
with catcher:
callable(*args, **kwargs)
class _ExceptionCatcher(object):
def __init__(self, test_case, exc_type):
self.test_case = test_case
self.exc_type = exc_type
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
exception_name = self.exc_type.__name__
if exc_type is None:
self.test_case.fail('Expected exception of type %r' %
exception_name)
elif not issubclass(exc_type, self.exc_type):
raise exc_type, exc_value, tb
return True
class BetterLoader(unittest.TestLoader):
"""A nicer loader that solves two problems. First of all we are setting

View file

@ -8,6 +8,8 @@
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from __future__ import with_statement
import sys
import unittest
from flask.testsuite import FlaskTestCase
@ -92,6 +94,11 @@ class ExtImportHookTestCase(FlaskTestCase):
from flask.ext.oldext_package.submodule import test_function
self.assert_equal(test_function(), 42)
def test_flaskext_broken_package_no_module_caching(self):
for x in xrange(2):
with self.assert_raises(ImportError):
import flask.ext.broken
def suite():
suite = unittest.TestSuite()

View file

@ -0,0 +1,2 @@
import flask.ext.broken.b
import missing_module

View file

@ -25,7 +25,8 @@ ext_module.__package__ = ext_module.__name__
class _ExtensionImporter(object):
"""This importer redirects imports from the flask.ext module to other
locations.
locations. For implementation details see the code in Flask 0.8
that does the same.
"""
_module_choices = ['flask_%s', 'flaskext.%s']
prefix = ext_module.__name__ + '.'
@ -45,6 +46,7 @@ class _ExtensionImporter(object):
__import__(realname)
except ImportError:
exc_type, exc_value, tb = sys.exc_info()
sys.modules.pop(fullname, None)
if self.is_important_traceback(realname, tb):
raise exc_type, exc_value, tb
continue
@ -55,12 +57,6 @@ class _ExtensionImporter(object):
raise ImportError('No module named %s' % fullname)
def is_important_traceback(self, important_module, tb):
"""Walks a traceback's frames and checks if any of the frames
originated in the given important module. If that is the case
then we were able to import the module itself but apparently
something went wrong when the module was imported. (Eg: import
of an import failed).
"""
while tb is not None:
if tb.tb_frame.f_globals.get('__name__') == important_module:
return True