From 7c5b244826be636429791a8ca76b2adc678e82e7 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 2 Feb 2013 22:58:02 +0100 Subject: [PATCH] Fixed #17061 -- Factored out importing object from a dotted path Thanks Carl Meyer for the report. --- django/conf/__init__.py | 5 ++-- django/contrib/admin/tests.py | 8 ++---- django/contrib/auth/__init__.py | 18 +++---------- django/contrib/auth/hashers.py | 9 ++----- .../formtools/tests/wizard/loadstorage.py | 12 +++------ .../formtools/wizard/storage/__init__.py | 20 +++++--------- .../formtools/wizard/storage/exceptions.py | 5 +--- django/contrib/messages/storage/__init__.py | 25 +----------------- django/contrib/staticfiles/finders.py | 14 ++-------- django/core/cache/__init__.py | 17 ++++++------ django/core/cache/backends/base.py | 6 ++--- django/core/files/storage.py | 20 +++----------- django/core/files/uploadhandler.py | 18 ++----------- django/core/handlers/base.py | 20 ++++---------- django/core/mail/__init__.py | 16 ++---------- django/core/servers/basehttp.py | 23 ++++------------ django/core/signing.py | 16 ++---------- django/db/utils.py | 13 ++-------- django/template/context.py | 14 ++-------- django/template/loader.py | 12 ++------- django/utils/module_loading.py | 26 +++++++++++++++++++ django/views/debug.py | 15 +++-------- tests/regressiontests/file_storage/tests.py | 12 ++++----- tests/regressiontests/utils/module_loading.py | 20 +++++++++++++- tests/regressiontests/utils/tests.py | 3 ++- tests/regressiontests/wsgi/tests.py | 4 +-- 26 files changed, 116 insertions(+), 255 deletions(-) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index bbcc16e120..efb56b2871 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -15,6 +15,7 @@ from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured from django.utils.functional import LazyObject, empty from django.utils import importlib +from django.utils.module_loading import import_by_path from django.utils import six ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" @@ -68,9 +69,7 @@ class LazySettings(LazyObject): if self.LOGGING_CONFIG: from django.utils.log import DEFAULT_LOGGING # First find the logging configuration function ... - logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1) - logging_config_module = importlib.import_module(logging_config_path) - logging_config_func = getattr(logging_config_module, logging_config_func_name) + logging_config_func = import_by_path(self.LOGGING_CONFIG) logging_config_func(DEFAULT_LOGGING) diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py index 7c62c1a22f..4daa6ec6d8 100644 --- a/django/contrib/admin/tests.py +++ b/django/contrib/admin/tests.py @@ -1,5 +1,5 @@ from django.test import LiveServerTestCase -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path from django.utils.unittest import SkipTest from django.utils.translation import ugettext as _ @@ -9,11 +9,7 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase): @classmethod def setUpClass(cls): try: - # Import and start the WebDriver class. - module, attr = cls.webdriver_class.rsplit('.', 1) - mod = import_module(module) - WebDriver = getattr(mod, attr) - cls.selenium = WebDriver() + cls.selenium = import_by_path(cls.webdriver_class)() except Exception as e: raise SkipTest('Selenium webdriver "%s" not installed or not ' 'operational: %s' % (cls.webdriver_class, str(e))) diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 99348d3ae5..ef9066657d 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -1,8 +1,8 @@ import re -from django.core.exceptions import ImproperlyConfigured, PermissionDenied -from django.utils.importlib import import_module from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed +from django.core.exceptions import ImproperlyConfigured, PermissionDenied +from django.utils.module_loading import import_by_path SESSION_KEY = '_auth_user_id' BACKEND_SESSION_KEY = '_auth_user_backend' @@ -10,19 +10,7 @@ REDIRECT_FIELD_NAME = 'next' def load_backend(path): - i = path.rfind('.') - module, attr = path[:i], path[i + 1:] - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing authentication backend %s: "%s"' % (path, e)) - except ValueError: - raise ImproperlyConfigured('Error importing authentication backends. Is AUTHENTICATION_BACKENDS a correctly defined list or tuple?') - try: - cls = getattr(mod, attr) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr)) - return cls() + return import_by_path(path)() def get_backends(): diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 0fd9144ddf..bd760cd3cc 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -12,6 +12,7 @@ from django.utils.encoding import force_bytes, force_str from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( pbkdf2, constant_time_compare, get_random_string) +from django.utils.module_loading import import_by_path from django.utils.translation import ugettext_noop as _ @@ -84,13 +85,7 @@ def load_hashers(password_hashers=None): if not password_hashers: password_hashers = settings.PASSWORD_HASHERS for backend in password_hashers: - try: - mod_path, cls_name = backend.rsplit('.', 1) - mod = importlib.import_module(mod_path) - hasher_cls = getattr(mod, cls_name) - except (AttributeError, ImportError, ValueError): - raise ImproperlyConfigured("hasher not found: %s" % backend) - hasher = hasher_cls() + hasher = import_by_path(backend)() if not getattr(hasher, 'algorithm'): raise ImproperlyConfigured("hasher doesn't specify an " "algorithm name: %s" % backend) diff --git a/django/contrib/formtools/tests/wizard/loadstorage.py b/django/contrib/formtools/tests/wizard/loadstorage.py index 267dee0379..bb0b06eecf 100644 --- a/django/contrib/formtools/tests/wizard/loadstorage.py +++ b/django/contrib/formtools/tests/wizard/loadstorage.py @@ -1,8 +1,6 @@ from django.test import TestCase -from django.contrib.formtools.wizard.storage import (get_storage, - MissingStorageModule, - MissingStorageClass) +from django.contrib.formtools.wizard.storage import get_storage, MissingStorage from django.contrib.formtools.wizard.storage.base import BaseStorage @@ -12,11 +10,9 @@ class TestLoadStorage(TestCase): type(get_storage('django.contrib.formtools.wizard.storage.base.BaseStorage', 'wizard1')), BaseStorage) - def test_missing_module(self): - self.assertRaises(MissingStorageModule, get_storage, + def test_missing_storage(self): + self.assertRaises(MissingStorage, get_storage, 'django.contrib.formtools.wizard.storage.idontexist.IDontExistStorage', 'wizard1') - - def test_missing_class(self): - self.assertRaises(MissingStorageClass, get_storage, + self.assertRaises(MissingStorage, get_storage, 'django.contrib.formtools.wizard.storage.base.IDontExistStorage', 'wizard1') diff --git a/django/contrib/formtools/wizard/storage/__init__.py b/django/contrib/formtools/wizard/storage/__init__.py index f2293c93a3..8f57605ec5 100644 --- a/django/contrib/formtools/wizard/storage/__init__.py +++ b/django/contrib/formtools/wizard/storage/__init__.py @@ -1,22 +1,14 @@ -from django.utils.importlib import import_module +from django.core.exceptions import ImproperlyConfigured +from django.utils.module_loading import import_by_path from django.contrib.formtools.wizard.storage.base import BaseStorage from django.contrib.formtools.wizard.storage.exceptions import ( - MissingStorageModule, MissingStorageClass, NoFileStorageConfigured) + MissingStorage, NoFileStorageConfigured) def get_storage(path, *args, **kwargs): - i = path.rfind('.') - module, attr = path[:i], path[i+1:] try: - mod = import_module(module) - except ImportError as e: - raise MissingStorageModule( - 'Error loading storage %s: "%s"' % (module, e)) - try: - storage_class = getattr(mod, attr) - except AttributeError: - raise MissingStorageClass( - 'Module "%s" does not define a storage named "%s"' % (module, attr)) + storage_class = import_by_path(path) + except ImproperlyConfigured as e: + raise MissingStorage('Error loading storage: %s' % e) return storage_class(*args, **kwargs) - diff --git a/django/contrib/formtools/wizard/storage/exceptions.py b/django/contrib/formtools/wizard/storage/exceptions.py index eab9030cf1..e273a8654b 100644 --- a/django/contrib/formtools/wizard/storage/exceptions.py +++ b/django/contrib/formtools/wizard/storage/exceptions.py @@ -1,9 +1,6 @@ from django.core.exceptions import ImproperlyConfigured -class MissingStorageModule(ImproperlyConfigured): - pass - -class MissingStorageClass(ImproperlyConfigured): +class MissingStorage(ImproperlyConfigured): pass class NoFileStorageConfigured(ImproperlyConfigured): diff --git a/django/contrib/messages/storage/__init__.py b/django/contrib/messages/storage/__init__.py index a584acca1c..9a09aff007 100644 --- a/django/contrib/messages/storage/__init__.py +++ b/django/contrib/messages/storage/__init__.py @@ -1,28 +1,5 @@ from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from django.utils.importlib import import_module - - -def get_storage(import_path): - """ - Imports the message storage class described by import_path, where - import_path is the full Python path to the class. - """ - try: - dot = import_path.rindex('.') - except ValueError: - raise ImproperlyConfigured("%s isn't a Python path." % import_path) - module, classname = import_path[:dot], import_path[dot + 1:] - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing module %s: "%s"' % - (module, e)) - try: - return getattr(mod, classname) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a "%s" ' - 'class.' % (module, classname)) +from django.utils.module_loading import import_by_path as get_storage # Callable with the same interface as the storage classes i.e. accepts a diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index 9b06c2cf60..8631570e79 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -4,7 +4,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage, Storage, FileSystemStorage from django.utils.datastructures import SortedDict from django.utils.functional import empty, memoize, LazyObject -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path from django.utils._os import safe_join from django.utils import six @@ -258,17 +258,7 @@ def _get_finder(import_path): Imports the staticfiles finder class described by import_path, where import_path is the full Python path to the class. """ - module, attr = import_path.rsplit('.', 1) - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing module %s: "%s"' % - (module, e)) - try: - Finder = getattr(mod, attr) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a "%s" ' - 'class.' % (module, attr)) + Finder = import_by_path(import_path) if not issubclass(Finder, BaseFinder): raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' % (Finder, BaseFinder)) diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 562bcc21bb..1242372dcf 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -25,6 +25,8 @@ from django.core.cache.backends.base import ( InvalidCacheBackendError, CacheKeyWarning, BaseCache) from django.core.exceptions import ImproperlyConfigured from django.utils import importlib +from django.utils.module_loading import import_by_path + __all__ = [ 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS' @@ -86,11 +88,10 @@ def parse_backend_conf(backend, **kwargs): else: try: # Trying to import the given backend, in case it's a dotted path - mod_path, cls_name = backend.rsplit('.', 1) - mod = importlib.import_module(mod_path) - backend_cls = getattr(mod, cls_name) - except (AttributeError, ImportError, ValueError): - raise InvalidCacheBackendError("Could not find backend '%s'" % backend) + backend_cls = import_by_path(backend) + except ImproperlyConfigured as e: + raise InvalidCacheBackendError("Could not find backend '%s': %s" % ( + backend, e)) location = kwargs.pop('LOCATION', '') return backend, location, kwargs @@ -126,10 +127,8 @@ def get_cache(backend, **kwargs): backend_cls = mod.CacheClass else: backend, location, params = parse_backend_conf(backend, **kwargs) - mod_path, cls_name = backend.rsplit('.', 1) - mod = importlib.import_module(mod_path) - backend_cls = getattr(mod, cls_name) - except (AttributeError, ImportError) as e: + backend_cls = import_by_path(backend) + except (AttributeError, ImportError, ImproperlyConfigured) as e: raise InvalidCacheBackendError( "Could not find backend '%s': %s" % (backend, e)) cache = backend_cls(location, params) diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index 7234d3c4db..53b0270a57 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import warnings from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path class InvalidCacheBackendError(ImproperlyConfigured): @@ -40,9 +40,7 @@ def get_key_func(key_func): if callable(key_func): return key_func else: - key_func_module_path, key_func_name = key_func.rsplit('.', 1) - key_func_module = import_module(key_func_module_path) - return getattr(key_func_module, key_func_name) + return import_by_path(key_func) return default_key_func diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 650373f0c3..0b623fe7c2 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -8,12 +8,12 @@ import itertools from datetime import datetime from django.conf import settings -from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation +from django.core.exceptions import SuspiciousOperation from django.core.files import locks, File from django.core.files.move import file_move_safe from django.utils.encoding import force_text, filepath_to_uri from django.utils.functional import LazyObject -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path from django.utils.text import get_valid_filename from django.utils._os import safe_join, abspathu @@ -277,21 +277,7 @@ class FileSystemStorage(Storage): return datetime.fromtimestamp(os.path.getmtime(self.path(name))) def get_storage_class(import_path=None): - if import_path is None: - import_path = settings.DEFAULT_FILE_STORAGE - try: - dot = import_path.rindex('.') - except ValueError: - raise ImproperlyConfigured("%s isn't a storage module." % import_path) - module, classname = import_path[:dot], import_path[dot+1:] - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing storage module %s: "%s"' % (module, e)) - try: - return getattr(mod, classname) - except AttributeError: - raise ImproperlyConfigured('Storage module "%s" does not define a "%s" class.' % (module, classname)) + return import_by_path(import_path or settings.DEFAULT_FILE_STORAGE) class DefaultStorage(LazyObject): def _setup(self): diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py index c422945d6f..f5e95cf2fd 100644 --- a/django/core/files/uploadhandler.py +++ b/django/core/files/uploadhandler.py @@ -7,10 +7,9 @@ from __future__ import unicode_literals from io import BytesIO from django.conf import settings -from django.core.exceptions import ImproperlyConfigured from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile -from django.utils import importlib from django.utils.encoding import python_2_unicode_compatible +from django.utils.module_loading import import_by_path __all__ = ['UploadFileException','StopUpload', 'SkipFile', 'FileUploadHandler', 'TemporaryFileUploadHandler', 'MemoryFileUploadHandler', @@ -201,17 +200,4 @@ def load_handler(path, *args, **kwargs): """ - i = path.rfind('.') - module, attr = path[:i], path[i+1:] - try: - mod = importlib.import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing upload handler module %s: "%s"' % (module, e)) - except ValueError: - raise ImproperlyConfigured('Error importing upload handler module.' - 'Is FILE_UPLOAD_HANDLERS a correctly defined list or tuple?') - try: - cls = getattr(mod, attr) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a "%s" upload handler backend' % (module, attr)) - return cls(*args, **kwargs) + return import_by_path(path)(*args, **kwargs) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 023947585e..0dcd9794c7 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -9,8 +9,9 @@ from django.conf import settings from django.core import exceptions from django.core import urlresolvers from django.core import signals +from django.core.exceptions import MiddlewareNotUsed, PermissionDenied from django.utils.encoding import force_text -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path from django.utils import six from django.views import debug @@ -43,21 +44,10 @@ class BaseHandler(object): request_middleware = [] for middleware_path in settings.MIDDLEWARE_CLASSES: - try: - mw_module, mw_classname = middleware_path.rsplit('.', 1) - except ValueError: - raise exceptions.ImproperlyConfigured('%s isn\'t a middleware module' % middleware_path) - try: - mod = import_module(mw_module) - except ImportError as e: - raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e)) - try: - mw_class = getattr(mod, mw_classname) - except AttributeError: - raise exceptions.ImproperlyConfigured('Middleware module "%s" does not define a "%s" class' % (mw_module, mw_classname)) + mw_class = import_by_path(middleware_path) try: mw_instance = mw_class() - except exceptions.MiddlewareNotUsed: + except MiddlewareNotUsed: continue if hasattr(mw_instance, 'process_request'): @@ -154,7 +144,7 @@ class BaseHandler(object): except: signals.got_request_exception.send(sender=self.__class__, request=request) response = self.handle_uncaught_exception(request, resolver, sys.exc_info()) - except exceptions.PermissionDenied: + except PermissionDenied: logger.warning( 'Forbidden (Permission denied): %s', request.path, extra={ diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py index 08f9702934..fcff803376 100644 --- a/django/core/mail/__init__.py +++ b/django/core/mail/__init__.py @@ -4,8 +4,7 @@ Tools for sending email. from __future__ import unicode_literals from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path # Imported for backwards compatibility, and for the sake # of a cleaner namespace. These symbols used to be in @@ -27,18 +26,7 @@ def get_connection(backend=None, fail_silently=False, **kwds): Both fail_silently and other keyword arguments are used in the constructor of the backend. """ - path = backend or settings.EMAIL_BACKEND - try: - mod_name, klass_name = path.rsplit('.', 1) - mod = import_module(mod_name) - except ImportError as e: - raise ImproperlyConfigured(('Error importing email backend module %s: "%s"' - % (mod_name, e))) - try: - klass = getattr(mod, klass_name) - except AttributeError: - raise ImproperlyConfigured(('Module "%s" does not define a ' - '"%s" class' % (mod_name, klass_name))) + klass = import_by_path(backend or settings.EMAIL_BACKEND) return klass(fail_silently=fail_silently, **kwds) diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 68ca0c1079..89ec2b35f2 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -21,11 +21,9 @@ from django.utils.six.moves import socketserver from wsgiref import simple_server from wsgiref.util import FileWrapper # for backwards compatibility -import django -from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style from django.core.wsgi import get_wsgi_application -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path __all__ = ['WSGIServer', 'WSGIRequestHandler'] @@ -49,22 +47,11 @@ def get_internal_wsgi_application(): app_path = getattr(settings, 'WSGI_APPLICATION') if app_path is None: return get_wsgi_application() - module_name, attr = app_path.rsplit('.', 1) - try: - mod = import_module(module_name) - except ImportError as e: - raise ImproperlyConfigured( - "WSGI application '%s' could not be loaded; " - "could not import module '%s': %s" % (app_path, module_name, e)) - try: - app = getattr(mod, attr) - except AttributeError as e: - raise ImproperlyConfigured( - "WSGI application '%s' could not be loaded; " - "can't find '%s' in module '%s': %s" - % (app_path, attr, module_name, e)) - return app + return import_by_path( + app_path, + error_prefix="WSGI application '%s' could not be loaded; " % app_path + ) class WSGIServerException(Exception): diff --git a/django/core/signing.py b/django/core/signing.py index 92ab968123..bbe53aafe3 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -41,11 +41,10 @@ import time import zlib from django.conf import settings -from django.core.exceptions import ImproperlyConfigured from django.utils import baseconv from django.utils.crypto import constant_time_compare, salted_hmac from django.utils.encoding import force_bytes, force_str, force_text -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path class BadSignature(Exception): @@ -76,18 +75,7 @@ def base64_hmac(salt, value, key): def get_cookie_signer(salt='django.core.signing.get_cookie_signer'): - modpath = settings.SIGNING_BACKEND - module, attr = modpath.rsplit('.', 1) - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured( - 'Error importing cookie signer %s: "%s"' % (modpath, e)) - try: - Signer = getattr(mod, attr) - except AttributeError as e: - raise ImproperlyConfigured( - 'Error importing cookie signer %s: "%s"' % (modpath, e)) + Signer = import_by_path(settings.SIGNING_BACKEND) return Signer('django.http.cookies' + settings.SECRET_KEY, salt=salt) diff --git a/django/db/utils.py b/django/db/utils.py index 842fd354d6..91fa774ed4 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -5,6 +5,7 @@ from threading import local from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path from django.utils._os import upath from django.utils import six @@ -110,17 +111,7 @@ class ConnectionRouter(object): self.routers = [] for r in routers: if isinstance(r, six.string_types): - try: - module_name, klass_name = r.rsplit('.', 1) - module = import_module(module_name) - except ImportError as e: - raise ImproperlyConfigured('Error importing database router %s: "%s"' % (klass_name, e)) - try: - router_class = getattr(module, klass_name) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a database router name "%s"' % (module, klass_name)) - else: - router = router_class() + router = import_by_path(r)() else: router = r self.routers.append(router) diff --git a/django/template/context.py b/django/template/context.py index 81aa194447..1ef7e889bc 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -1,6 +1,5 @@ from copy import copy -from django.core.exceptions import ImproperlyConfigured -from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path # Cache of actual callables. _standard_context_processors = None @@ -146,16 +145,7 @@ def get_standard_processors(): collect.extend(_builtin_context_processors) collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS) for path in collect: - i = path.rfind('.') - module, attr = path[:i], path[i+1:] - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing request processor module %s: "%s"' % (module, e)) - try: - func = getattr(mod, attr) - except AttributeError: - raise ImproperlyConfigured('Module "%s" does not define a "%s" callable request processor' % (module, attr)) + func = import_by_path(path) processors.append(func) _standard_context_processors = tuple(processors) return _standard_context_processors diff --git a/django/template/loader.py b/django/template/loader.py index cfffb4014e..6df4e43c4f 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -27,8 +27,8 @@ from django.core.exceptions import ImproperlyConfigured from django.template.base import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins -from django.utils.importlib import import_module from django.conf import settings +from django.utils.module_loading import import_by_path from django.utils import six template_source_loaders = None @@ -91,15 +91,7 @@ def find_template_loader(loader): else: args = [] if isinstance(loader, six.string_types): - module, attr = loader.rsplit('.', 1) - try: - mod = import_module(module) - except ImportError as e: - raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e)) - try: - TemplateLoader = getattr(mod, attr) - except AttributeError as e: - raise ImproperlyConfigured('Error importing template source loader %s: "%s"' % (loader, e)) + TemplateLoader = import_by_path(loader) if hasattr(TemplateLoader, 'load_template_source'): func = TemplateLoader(*args) diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py index f8aadb34df..01996743cd 100644 --- a/django/utils/module_loading.py +++ b/django/utils/module_loading.py @@ -2,6 +2,32 @@ import imp import os import sys +from django.core.exceptions import ImproperlyConfigured +from django.utils.importlib import import_module + + +def import_by_path(dotted_path, error_prefix=''): + """ + Import a dotted module path and return the attribute/class designated by the + last name in the path. Raise ImproperlyConfigured if something goes wrong. + """ + try: + module_path, class_name = dotted_path.rsplit('.', 1) + except ValueError: + raise ImproperlyConfigured("%s%s doesn't look like a module path" % ( + error_prefix, dotted_path)) + try: + module = import_module(module_path) + except ImportError as e: + raise ImproperlyConfigured('%sError importing module %s: "%s"' % ( + error_prefix, module_path, e)) + try: + attr = getattr(module, class_name) + except AttributeError: + raise ImproperlyConfigured('%sModule "%s" does not define a "%s" attribute/class' % ( + error_prefix, module_path, class_name)) + return attr + def module_has_submodule(package, module_name): """See if 'module' is in 'package'.""" diff --git a/django/views/debug.py b/django/views/debug.py index efab03f09c..1c331d6803 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -13,8 +13,8 @@ from django.http import (HttpResponse, HttpResponseServerError, from django.template import Template, Context, TemplateDoesNotExist from django.template.defaultfilters import force_escape, pprint from django.utils.html import escape -from django.utils.importlib import import_module from django.utils.encoding import force_bytes, smart_text +from django.utils.module_loading import import_by_path from django.utils import six HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE') @@ -76,17 +76,8 @@ def get_exception_reporter_filter(request): global default_exception_reporter_filter if default_exception_reporter_filter is None: # Load the default filter for the first time and cache it. - modpath = settings.DEFAULT_EXCEPTION_REPORTER_FILTER - modname, classname = modpath.rsplit('.', 1) - try: - mod = import_module(modname) - except ImportError as e: - raise ImproperlyConfigured( - 'Error importing default exception reporter filter %s: "%s"' % (modpath, e)) - try: - default_exception_reporter_filter = getattr(mod, classname)() - except AttributeError: - raise ImproperlyConfigured('Default exception reporter filter module "%s" does not define a "%s" class' % (modname, classname)) + default_exception_reporter_filter = import_by_path( + settings.DEFAULT_EXCEPTION_REPORTER_FILTER)() if request: return getattr(request, 'exception_reporter_filter', default_exception_reporter_filter) else: diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 4872ce87b9..6ab7f90ee7 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -57,9 +57,9 @@ class GetStorageClassTests(SimpleTestCase): """ self.assertRaisesMessage( ImproperlyConfigured, - "NonExistingStorage isn't a storage module.", + "Error importing module storage: \"No module named storage\"", get_storage_class, - 'NonExistingStorage') + 'storage.NonExistingStorage') def test_get_nonexisting_storage_class(self): """ @@ -67,8 +67,8 @@ class GetStorageClassTests(SimpleTestCase): """ self.assertRaisesMessage( ImproperlyConfigured, - 'Storage module "django.core.files.storage" does not define a '\ - '"NonExistingStorage" class.', + 'Module "django.core.files.storage" does not define a ' + '"NonExistingStorage" attribute/class', get_storage_class, 'django.core.files.storage.NonExistingStorage') @@ -79,8 +79,8 @@ class GetStorageClassTests(SimpleTestCase): # Error message may or may not be the fully qualified path. six.assertRaisesRegex(self, ImproperlyConfigured, - ('Error importing storage module django.core.files.non_existing_' - 'storage: "No module named .*non_existing_storage'), + 'Error importing module django.core.files.non_existing_storage: ' + '"No module named non_existing_storage"', get_storage_class, 'django.core.files.non_existing_storage.NonExistingStorage' ) diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py index 3fc92b0862..a9e3706bb9 100644 --- a/tests/regressiontests/utils/module_loading.py +++ b/tests/regressiontests/utils/module_loading.py @@ -3,9 +3,10 @@ import sys import imp from zipimport import zipimporter +from django.core.exceptions import ImproperlyConfigured from django.utils import unittest from django.utils.importlib import import_module -from django.utils.module_loading import module_has_submodule +from django.utils.module_loading import import_by_path, module_has_submodule from django.utils._os import upath @@ -103,6 +104,23 @@ class EggLoader(unittest.TestCase): self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module') + +class ModuleImportTestCase(unittest.TestCase): + def test_import_by_path(self): + cls = import_by_path( + 'django.utils.module_loading.import_by_path') + self.assertEqual(cls, import_by_path) + + # Test exceptions raised + for path in ('no_dots_in_path', 'unexistent.path', + 'tests.regressiontests.utils.unexistent'): + self.assertRaises(ImproperlyConfigured, import_by_path, path) + + with self.assertRaises(ImproperlyConfigured) as cm: + import_by_path('unexistent.module.path', error_prefix="Foo") + self.assertTrue(str(cm.exception).startswith('Foo')) + + class ProxyFinder(object): def __init__(self): self._cache = {} diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 11dd7c320e..726df3d979 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -20,7 +20,8 @@ from .html import TestUtilsHtml from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests from .ipv6 import TestUtilsIPv6 from .jslex import JsToCForGettextTest, JsTokensTest -from .module_loading import CustomLoader, DefaultLoader, EggLoader +from .module_loading import (CustomLoader, DefaultLoader, EggLoader, + ModuleImportTestCase) from .numberformat import TestNumberFormat from .os_utils import SafeJoinTests from .regex_helper import NormalizeTests diff --git a/tests/regressiontests/wsgi/tests.py b/tests/regressiontests/wsgi/tests.py index 6c1483d2cd..0b8c9d2b67 100644 --- a/tests/regressiontests/wsgi/tests.py +++ b/tests/regressiontests/wsgi/tests.py @@ -85,7 +85,7 @@ class GetInternalWSGIApplicationTest(unittest.TestCase): def test_bad_module(self): with six.assertRaisesRegex(self, ImproperlyConfigured, - r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; could not import module 'regressiontests.wsgi.noexist':"): + r"^WSGI application 'regressiontests.wsgi.noexist.app' could not be loaded; Error importing.*"): get_internal_wsgi_application() @@ -94,6 +94,6 @@ class GetInternalWSGIApplicationTest(unittest.TestCase): def test_bad_name(self): with six.assertRaisesRegex(self, ImproperlyConfigured, - r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; can't find 'noexist' in module 'regressiontests.wsgi.wsgi':"): + r"^WSGI application 'regressiontests.wsgi.wsgi.noexist' could not be loaded; Module.*"): get_internal_wsgi_application()