Fixed #22920 -- Avoid masking some exceptions.
If loading an application trigger an ImportError, the details of that error were lost in some cases. Thanks Ben Davis for the report.
This commit is contained in:
parent
d3543af750
commit
b161c01c48
|
@ -87,6 +87,10 @@ class AppConfig(object):
|
||||||
module = import_module(entry)
|
module = import_module(entry)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
# Track that importing as an app module failed. If importing as an
|
||||||
|
# app config class fails too, we'll trigger the ImportError again.
|
||||||
|
module = None
|
||||||
|
|
||||||
mod_path, _, cls_name = entry.rpartition('.')
|
mod_path, _, cls_name = entry.rpartition('.')
|
||||||
|
|
||||||
# Raise the original exception when entry cannot be a path to an
|
# Raise the original exception when entry cannot be a path to an
|
||||||
|
@ -104,8 +108,8 @@ class AppConfig(object):
|
||||||
else:
|
else:
|
||||||
mod_path, _, cls_name = entry.rpartition('.')
|
mod_path, _, cls_name = entry.rpartition('.')
|
||||||
|
|
||||||
# If we're reaching this point, we must load the app config class
|
# If we're reaching this point, we must attempt to load the app config
|
||||||
# located at <mod_path>.<cls_name>.
|
# class located at <mod_path>.<cls_name>
|
||||||
|
|
||||||
# Avoid django.utils.module_loading.import_by_path because it
|
# Avoid django.utils.module_loading.import_by_path because it
|
||||||
# masks errors -- it reraises ImportError as ImproperlyConfigured.
|
# masks errors -- it reraises ImportError as ImproperlyConfigured.
|
||||||
|
@ -113,11 +117,12 @@ class AppConfig(object):
|
||||||
try:
|
try:
|
||||||
cls = getattr(mod, cls_name)
|
cls = getattr(mod, cls_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Emulate the error that "from <mod_path> import <cls_name>"
|
if module is None:
|
||||||
# would raise when <mod_path> exists but not <cls_name>, with
|
# If importing as an app module failed, that error probably
|
||||||
# more context (Python just says "cannot import name ...").
|
# contains the most informative traceback. Trigger it again.
|
||||||
raise ImportError(
|
import_module(entry)
|
||||||
"cannot import name '%s' from '%s'" % (cls_name, mod_path))
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Check for obvious errors. (This check prevents duck typing, but
|
# Check for obvious errors. (This check prevents duck typing, but
|
||||||
# it could be removed if it became a problem in practice.)
|
# it could be removed if it became a problem in practice.)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
raise ImportError("Oops")
|
|
@ -166,6 +166,14 @@ class AppsTests(TestCase):
|
||||||
with self.settings(INSTALLED_APPS=['apps.apps.RelabeledAppsConfig', 'apps']):
|
with self.settings(INSTALLED_APPS=['apps.apps.RelabeledAppsConfig', 'apps']):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_import_exception_is_not_masked(self):
|
||||||
|
"""
|
||||||
|
App discovery should preserve stack traces. Regression test for #22920.
|
||||||
|
"""
|
||||||
|
with six.assertRaisesRegex(self, ImportError, "Oops"):
|
||||||
|
with self.settings(INSTALLED_APPS=['apps.failing_app']):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_models_py(self):
|
def test_models_py(self):
|
||||||
"""
|
"""
|
||||||
Tests that the models in the models.py file were loaded correctly.
|
Tests that the models in the models.py file were loaded correctly.
|
||||||
|
|
Loading…
Reference in New Issue