From 5d263dee304fdaf95e18d2f0619d6925984a7f02 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 20 Jan 2014 22:15:14 +0200 Subject: [PATCH] Fixed #21674 -- Deprecated the import_by_path() function in favor of import_string(). Thanks Aymeric Augustin for the suggestion and review. --- AUTHORS | 1 + django/contrib/admin/tests.py | 4 +- django/contrib/auth/__init__.py | 4 +- django/contrib/auth/hashers.py | 4 +- django/contrib/auth/middleware.py | 2 +- .../formtools/wizard/storage/__init__.py | 7 ++-- django/contrib/messages/storage/__init__.py | 6 +-- django/contrib/sessions/backends/base.py | 4 +- django/contrib/staticfiles/finders.py | 4 +- django/core/cache/__init__.py | 10 ++--- django/core/cache/backends/base.py | 4 +- django/core/files/storage.py | 4 +- django/core/files/uploadhandler.py | 4 +- django/core/handlers/base.py | 4 +- django/core/mail/__init__.py | 4 +- django/core/servers/basehttp.py | 20 +++++++--- django/core/signing.py | 4 +- django/db/migrations/state.py | 8 ++-- django/db/utils.py | 4 +- django/template/context.py | 4 +- django/template/loader.py | 4 +- django/utils/log.py | 4 +- django/utils/module_loading.py | 39 +++++++++++++------ django/views/debug.py | 4 +- docs/internals/deprecation.txt | 3 ++ docs/ref/utils.txt | 23 +++++++---- docs/releases/1.7.txt | 11 ++++++ tests/file_storage/tests.py | 18 +++------ tests/staticfiles_tests/tests.py | 4 +- tests/utils_tests/test_module_loading.py | 32 +++++++++++---- tests/wsgi/tests.py | 2 +- 31 files changed, 155 insertions(+), 95 deletions(-) diff --git a/AUTHORS b/AUTHORS index f3f300889ad..fe1ede2f544 100644 --- a/AUTHORS +++ b/AUTHORS @@ -484,6 +484,7 @@ answer newbie questions, and generally made Django that much better: John Paulett pavithran s Barry Pederson + Berker Peksag Andreas Pelme permonik@mesias.brnonet.cz peter@mymart.com diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py index 40fd10089b5..56b8b660234 100644 --- a/django/contrib/admin/tests.py +++ b/django/contrib/admin/tests.py @@ -2,7 +2,7 @@ import os from unittest import SkipTest from django.contrib.staticfiles.testing import StaticLiveServerCase -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils.translation import ugettext as _ @@ -22,7 +22,7 @@ class AdminSeleniumWebDriverTestCase(StaticLiveServerCase): if not os.environ.get('DJANGO_SELENIUM_TESTS', False): raise SkipTest('Selenium tests not requested') try: - cls.selenium = import_by_path(cls.webdriver_class)() + cls.selenium = import_string(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 9fd46814b28..ed01da94cf9 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -4,7 +4,7 @@ import re from django.apps import apps as django_apps from django.conf import settings from django.core.exceptions import ImproperlyConfigured, PermissionDenied -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.middleware.csrf import rotate_token from .signals import user_logged_in, user_logged_out, user_login_failed @@ -15,7 +15,7 @@ REDIRECT_FIELD_NAME = 'next' def load_backend(path): - return import_by_path(path)() + return import_string(path)() def get_backends(): diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 713240b5024..992e19e2a43 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -13,7 +13,7 @@ from django.utils.encoding import force_bytes, force_str, force_text 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.module_loading import import_string from django.utils.translation import ugettext_noop as _ @@ -92,7 +92,7 @@ def load_hashers(password_hashers=None): if not password_hashers: password_hashers = settings.PASSWORD_HASHERS for backend in password_hashers: - hasher = import_by_path(backend)() + hasher = import_string(backend)() if not getattr(hasher, 'algorithm'): raise ImproperlyConfigured("hasher doesn't specify an " "algorithm name: %s" % backend) diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index a107055e34c..3ee742446d8 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -58,7 +58,7 @@ class RemoteUserMiddleware(object): auth.BACKEND_SESSION_KEY, '')) if isinstance(stored_backend, RemoteUserBackend): auth.logout(request) - except ImproperlyConfigured: + except ImportError: # backend failed to load auth.logout(request) return diff --git a/django/contrib/formtools/wizard/storage/__init__.py b/django/contrib/formtools/wizard/storage/__init__.py index 4dbc15a2987..71a21f89a33 100644 --- a/django/contrib/formtools/wizard/storage/__init__.py +++ b/django/contrib/formtools/wizard/storage/__init__.py @@ -1,5 +1,4 @@ -from django.core.exceptions import ImproperlyConfigured -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.contrib.formtools.wizard.storage.base import BaseStorage from django.contrib.formtools.wizard.storage.exceptions import ( @@ -12,7 +11,7 @@ __all__ = [ def get_storage(path, *args, **kwargs): try: - storage_class = import_by_path(path) - except ImproperlyConfigured as e: + storage_class = import_string(path) + except ImportError as e: raise MissingStorage('Error loading storage: %s' % e) return storage_class(*args, **kwargs) diff --git a/django/contrib/messages/storage/__init__.py b/django/contrib/messages/storage/__init__.py index 591ae14d7c7..373dbbaada8 100644 --- a/django/contrib/messages/storage/__init__.py +++ b/django/contrib/messages/storage/__init__.py @@ -1,12 +1,12 @@ from django.conf import settings -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string def default_storage(request): """ Callable with the same interface as the storage classes. - This isn't just default_storage = import_by_path(settings.MESSAGE_STORAGE) + This isn't just default_storage = import_string(settings.MESSAGE_STORAGE) to avoid accessing the settings at the module level. """ - return import_by_path(settings.MESSAGE_STORAGE)(request) + return import_string(settings.MESSAGE_STORAGE)(request) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 2a497fe9a1d..a77a25bb317 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -12,7 +12,7 @@ from django.utils.crypto import get_random_string from django.utils.crypto import salted_hmac from django.utils import timezone from django.utils.encoding import force_bytes, force_text -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.contrib.sessions.exceptions import SuspiciousSession @@ -40,7 +40,7 @@ class SessionBase(object): self._session_key = session_key self.accessed = False self.modified = False - self.serializer = import_by_path(settings.SESSION_SERIALIZER) + self.serializer = import_string(settings.SESSION_SERIALIZER) def __contains__(self, key): return key in self._session diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index 30767405d33..86b620696f3 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -6,7 +6,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage, Storage, FileSystemStorage from django.utils.functional import empty, LazyObject -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils._os import safe_join from django.utils import six, lru_cache @@ -257,7 +257,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. """ - Finder = import_by_path(import_path) + Finder = import_string(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 c362800dcc9..603086a39cc 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -20,7 +20,7 @@ from django.core import signals from django.core.cache.backends.base import ( InvalidCacheBackendError, CacheKeyWarning, BaseCache) from django.core.exceptions import ImproperlyConfigured -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string __all__ = [ @@ -69,8 +69,8 @@ def _create_cache(backend, **kwargs): except KeyError: try: # Trying to import the given backend, in case it's a dotted path - import_by_path(backend) - except ImproperlyConfigured as e: + import_string(backend) + except ImportError as e: raise InvalidCacheBackendError("Could not find backend '%s': %s" % ( backend, e)) location = kwargs.pop('LOCATION', '') @@ -80,8 +80,8 @@ def _create_cache(backend, **kwargs): params.update(kwargs) backend = params.pop('BACKEND') location = params.pop('LOCATION', '') - backend_cls = import_by_path(backend) - except (AttributeError, ImportError, ImproperlyConfigured) as e: + backend_cls = import_string(backend) + except ImportError as e: raise InvalidCacheBackendError( "Could not find backend '%s': %s" % (backend, e)) return backend_cls(location, params) diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index 2c3151369af..ae83865db80 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -5,7 +5,7 @@ import time import warnings from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string class InvalidCacheBackendError(ImproperlyConfigured): @@ -45,7 +45,7 @@ def get_key_func(key_func): if callable(key_func): return key_func else: - return import_by_path(key_func) + return import_string(key_func) return default_key_func diff --git a/django/core/files/storage.py b/django/core/files/storage.py index c8b4c7185df..ade1817dadd 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -9,7 +9,7 @@ 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.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils.six.moves.urllib.parse import urljoin from django.utils.text import get_valid_filename from django.utils._os import safe_join, abspathu @@ -301,7 +301,7 @@ class FileSystemStorage(Storage): def get_storage_class(import_path=None): - return import_by_path(import_path or settings.DEFAULT_FILE_STORAGE) + return import_string(import_path or settings.DEFAULT_FILE_STORAGE) class DefaultStorage(LazyObject): diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py index 995e97a2f66..2fa1d7a934e 100644 --- a/django/core/files/uploadhandler.py +++ b/django/core/files/uploadhandler.py @@ -9,7 +9,7 @@ from io import BytesIO from django.conf import settings from django.core.files.uploadedfile import TemporaryUploadedFile, InMemoryUploadedFile from django.utils.encoding import python_2_unicode_compatible -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string __all__ = [ 'UploadFileException', 'StopUpload', 'SkipFile', 'FileUploadHandler', @@ -214,4 +214,4 @@ def load_handler(path, *args, **kwargs): """ - return import_by_path(path)(*args, **kwargs) + return import_string(path)(*args, **kwargs) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 587cbae2c3f..3986257660a 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -11,7 +11,7 @@ from django.core import signals from django.core.exceptions import MiddlewareNotUsed, PermissionDenied, SuspiciousOperation from django.db import connections, transaction from django.utils.encoding import force_text -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils import six from django.views import debug @@ -43,7 +43,7 @@ class BaseHandler(object): request_middleware = [] for middleware_path in settings.MIDDLEWARE_CLASSES: - mw_class = import_by_path(middleware_path) + mw_class = import_string(middleware_path) try: mw_instance = mw_class() except MiddlewareNotUsed: diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py index 5a01adc4ea7..cef9d1b87b4 100644 --- a/django/core/mail/__init__.py +++ b/django/core/mail/__init__.py @@ -4,7 +4,7 @@ Tools for sending email. from __future__ import unicode_literals from django.conf import settings -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string # Imported for backwards compatibility, and for the sake # of a cleaner namespace. These symbols used to be in @@ -34,7 +34,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. """ - klass = import_by_path(backend or settings.EMAIL_BACKEND) + klass = import_string(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 d34c246d511..090a694c3e2 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -16,9 +16,11 @@ import traceback from wsgiref import simple_server from wsgiref.util import FileWrapper # NOQA: for backwards compatibility +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.module_loading import import_by_path +from django.utils import six +from django.utils.module_loading import import_string from django.utils.six.moves import socketserver __all__ = ('WSGIServer', 'WSGIRequestHandler', 'MAX_SOCKET_CHUNK_SIZE') @@ -50,10 +52,18 @@ def get_internal_wsgi_application(): if app_path is None: return get_wsgi_application() - return import_by_path( - app_path, - error_prefix="WSGI application '%s' could not be loaded; " % app_path - ) + try: + return import_string(app_path) + except ImportError as e: + msg = ( + "WSGI application '%(app_path)s' could not be loaded; " + "Error importing module: '%(exception)s'" % ({ + 'app_path': app_path, + 'exception': e, + }) + ) + six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg), + sys.exc_info()[2]) class ServerHandler(simple_server.ServerHandler, object): diff --git a/django/core/signing.py b/django/core/signing.py index 15b9eaec6ea..17953af1514 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -44,7 +44,7 @@ from django.conf import settings 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.module_loading import import_by_path +from django.utils.module_loading import import_string class BadSignature(Exception): @@ -75,7 +75,7 @@ def base64_hmac(salt, value, key): def get_cookie_signer(salt='django.core.signing.get_cookie_signer'): - Signer = import_by_path(settings.SIGNING_BACKEND) + Signer = import_string(settings.SIGNING_BACKEND) return Signer('django.http.cookies' + settings.SECRET_KEY, salt=salt) diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 949d2251e5a..3e73744091a 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -3,7 +3,7 @@ from django.apps.registry import Apps from django.db import models from django.db.models.options import DEFAULT_NAMES, normalize_unique_together from django.utils import six -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string class InvalidBasesError(ValueError): @@ -115,7 +115,7 @@ class ModelState(object): fields = [] for field in model._meta.local_fields: name, path, args, kwargs = field.deconstruct() - field_class = import_by_path(path) + field_class = import_string(path) try: fields.append((name, field_class(*args, **kwargs))) except TypeError as e: @@ -127,7 +127,7 @@ class ModelState(object): )) for field in model._meta.local_many_to_many: name, path, args, kwargs = field.deconstruct() - field_class = import_by_path(path) + field_class = import_string(path) try: fields.append((name, field_class(*args, **kwargs))) except TypeError as e: @@ -175,7 +175,7 @@ class ModelState(object): fields = [] for name, field in self.fields: _, path, args, kwargs = field.deconstruct() - field_class = import_by_path(path) + field_class = import_string(path) fields.append((name, field_class(*args, **kwargs))) # Now make a copy return self.__class__( diff --git a/django/db/utils.py b/django/db/utils.py index 0bcf33f2521..51d2ffcba02 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -7,7 +7,7 @@ import warnings from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.functional import cached_property -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils._os import upath from django.utils import six @@ -221,7 +221,7 @@ class ConnectionRouter(object): routers = [] for r in self._routers: if isinstance(r, six.string_types): - router = import_by_path(r)() + router = import_string(r)() else: router = r routers.append(router) diff --git a/django/template/context.py b/django/template/context.py index 774f7af6b11..0f624ff8a6d 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -1,5 +1,5 @@ from copy import copy -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string # Cache of actual callables. _standard_context_processors = None @@ -162,7 +162,7 @@ def get_standard_processors(): collect.extend(_builtin_context_processors) collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS) for path in collect: - func = import_by_path(path) + func = import_string(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 1e18eecc022..e9e2172e4b2 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -28,7 +28,7 @@ from django.core.exceptions import ImproperlyConfigured from django.template.base import Origin, Template, Context, TemplateDoesNotExist from django.conf import settings -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils import six template_source_loaders = None @@ -95,7 +95,7 @@ def find_template_loader(loader): else: args = [] if isinstance(loader, six.string_types): - TemplateLoader = import_by_path(loader) + TemplateLoader = import_string(loader) if hasattr(TemplateLoader, 'load_template_source'): func = TemplateLoader(*args) diff --git a/django/utils/log.py b/django/utils/log.py index cf778b619be..f3197041167 100644 --- a/django/utils/log.py +++ b/django/utils/log.py @@ -5,7 +5,7 @@ import warnings from django.conf import settings from django.core import mail from django.core.mail import get_connection -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.views.debug import ExceptionReporter, get_exception_reporter_filter # Imports kept for backwards-compatibility in Django 1.7. @@ -73,7 +73,7 @@ def configure_logging(logging_config, logging_settings): if logging_config: # First find the logging configuration function ... - logging_config_func = import_by_path(logging_config) + logging_config_func = import_string(logging_config) logging_config_func(DEFAULT_LOGGING) diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py index a6fe7aebcb1..7ba03813074 100644 --- a/django/utils/module_loading.py +++ b/django/utils/module_loading.py @@ -5,33 +5,48 @@ import imp from importlib import import_module import os import sys +import warnings from django.core.exceptions import ImproperlyConfigured from django.utils import six +def import_string(dotted_path): + """ + Import a dotted module path and return the attribute/class designated by the + last name in the path. Raise ImportError if the import failed. + """ + try: + module_path, class_name = dotted_path.rsplit('.', 1) + except ValueError: + msg = "%s doesn't look like a module path" % dotted_path + six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) + + module = import_module(module_path) + + try: + return getattr(module, class_name) + except AttributeError: + msg = 'Module "%s" does not define a "%s" attribute/class' % ( + dotted_path, class_name) + six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) + + 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. """ + warnings.warn( + 'import_by_path() has been deprecated. Use import_string() instead.', + PendingDeprecationWarning, stacklevel=2) 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) + attr = import_string(dotted_path) except ImportError as e: msg = '%sError importing module %s: "%s"' % ( - error_prefix, module_path, e) + error_prefix, dotted_path, e) six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg), sys.exc_info()[2]) - 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 diff --git a/django/views/debug.py b/django/views/debug.py index 4f2097600f3..d665c4e36b2 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -14,7 +14,7 @@ from django.template.defaultfilters import force_escape, pprint from django.utils.datastructures import MultiValueDict from django.utils.html import escape from django.utils.encoding import force_bytes, smart_text -from django.utils.module_loading import import_by_path +from django.utils.module_loading import import_string from django.utils import six HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE') @@ -85,7 +85,7 @@ 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. - default_exception_reporter_filter = import_by_path( + default_exception_reporter_filter = import_string( settings.DEFAULT_EXCEPTION_REPORTER_FILTER)() if request: return getattr(request, 'exception_reporter_filter', default_exception_reporter_filter) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 7b5a5d74680..4b79e4696d8 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -116,6 +116,9 @@ details on these changes. * ``django.db.backends.DatabaseValidation.validate_field`` will be removed in favor of the ``check_field`` method. +* ``django.utils.module_loading.import_by_path`` will be removed in favor of + ``django.utils.module_loading.import_string``. + .. _deprecation-removed-in-1.8: 1.8 diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 6cac9fa0585..5313a1de194 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -709,22 +709,31 @@ escaping HTML. Functions for working with Python modules. -.. function:: import_by_path(dotted_path, error_prefix='') +.. function:: import_string(dotted_path) - .. versionadded:: 1.6 + .. versionadded:: 1.7 Imports a dotted module path and returns the attribute/class designated by - the last name in the path. Raises - :exc:`~django.core.exceptions.ImproperlyConfigured` if something goes - wrong. For example:: + the last name in the path. Raises ``ImportError`` if the import failed. For + example:: - from django.utils.module_loading import import_by_path - ImproperlyConfigured = import_by_path('django.core.exceptions.ImproperlyConfigured') + from django.utils.module_loading import import_string + ImproperlyConfigured = import_string('django.core.exceptions.ImproperlyConfigured') is equivalent to:: from django.core.exceptions import ImproperlyConfigured +.. function:: import_by_path(dotted_path, error_prefix='') + + .. versionadded:: 1.6 + .. deprecated:: 1.7 + Use :meth:`~django.utils.module_loading.import_string` instead. + + Imports a dotted module path and returns the attribute/class designated by + the last name in the path. Raises :exc:`~django.core.exceptions.ImproperlyConfigured` + if something goes wrong. + ``django.utils.safestring`` =========================== diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index f9d3a7e795a..1fe2b25ecf8 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -1042,6 +1042,17 @@ Features deprecated in 1.7 respectively :mod:`logging.config` and :mod:`importlib` provided for Python versions prior to 2.7. They have been deprecated. +``django.utils.module_loading.import_by_path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current :meth:`~django.utils.module_loading.import_by_path` function +catches ``AttributeError``, ``ImportError`` and ``ValueError`` exceptions, +and re-raises :exc:`~django.core.exceptions.ImproperlyConfigured`. Such +exception masking makes it needlessly hard to diagnose circular import +problems, because it makes it look like the problem comes from inside Django. +It has been deprecated in favor of +:meth:`~django.utils.module_loading.import_string`. + ``django.utils.tzinfo`` ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py index 2f047f049aa..ae2076c5e40 100644 --- a/tests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -16,7 +16,7 @@ except ImportError: import dummy_threading as threading from django.core.cache import cache -from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured +from django.core.exceptions import SuspiciousOperation from django.core.files.base import File, ContentFile from django.core.files.storage import FileSystemStorage, get_storage_class from django.core.files.uploadedfile import SimpleUploadedFile @@ -43,29 +43,23 @@ class GetStorageClassTests(SimpleTestCase): """ get_storage_class raises an error if the requested import don't exist. """ - with six.assertRaisesRegex(self, ImproperlyConfigured, - "Error importing module storage: \"No module named '?storage'?\""): + with six.assertRaisesRegex(self, ImportError, "No module named '?storage'?"): get_storage_class('storage.NonExistingStorage') def test_get_nonexisting_storage_class(self): """ get_storage_class raises an error if the requested class don't exist. """ - self.assertRaisesMessage( - ImproperlyConfigured, - 'Module "django.core.files.storage" does not define a ' - '"NonExistingStorage" attribute/class', - get_storage_class, - 'django.core.files.storage.NonExistingStorage') + self.assertRaises(ImportError, get_storage_class, + 'django.core.files.storage.NonExistingStorage') def test_get_nonexisting_storage_module(self): """ get_storage_class raises an error if the requested module don't exist. """ # Error message may or may not be the fully qualified path. - with six.assertRaisesRegex(self, ImproperlyConfigured, - "Error importing module django.core.files.non_existing_storage: " - "\"No module named '?(django.core.files.)?non_existing_storage'?\""): + with six.assertRaisesRegex(self, ImportError, + "No module named '?(django.core.files.)?non_existing_storage'?"): get_storage_class( 'django.core.files.non_existing_storage.NonExistingStorage') diff --git a/tests/staticfiles_tests/tests.py b/tests/staticfiles_tests/tests.py index 74e3782c833..1d3daff11ee 100644 --- a/tests/staticfiles_tests/tests.py +++ b/tests/staticfiles_tests/tests.py @@ -788,11 +788,11 @@ class TestMiscFinder(TestCase): finders.FileSystemFinder) def test_get_finder_bad_classname(self): - self.assertRaises(ImproperlyConfigured, finders.get_finder, + self.assertRaises(ImportError, finders.get_finder, 'django.contrib.staticfiles.finders.FooBarFinder') def test_get_finder_bad_module(self): - self.assertRaises(ImproperlyConfigured, + self.assertRaises(ImportError, finders.get_finder, 'foo.bar.FooBarFinder') def test_cache(self): diff --git a/tests/utils_tests/test_module_loading.py b/tests/utils_tests/test_module_loading.py index 905163b1b99..2eb01ad770c 100644 --- a/tests/utils_tests/test_module_loading.py +++ b/tests/utils_tests/test_module_loading.py @@ -3,13 +3,15 @@ from importlib import import_module import os import sys import unittest +import warnings from zipimport import zipimporter from django.core.exceptions import ImproperlyConfigured from django.test import SimpleTestCase, modify_settings -from django.test.utils import extend_sys_path +from django.test.utils import IgnorePendingDeprecationWarningsMixin, extend_sys_path from django.utils import six -from django.utils.module_loading import autodiscover_modules, import_by_path, module_has_submodule +from django.utils.module_loading import (autodiscover_modules, import_by_path, import_string, + module_has_submodule) from django.utils._os import upath @@ -107,15 +109,13 @@ class EggLoader(unittest.TestCase): self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module') -class ModuleImportTestCase(unittest.TestCase): +class ModuleImportTestCase(IgnorePendingDeprecationWarningsMixin, unittest.TestCase): def test_import_by_path(self): - cls = import_by_path( - 'django.utils.module_loading.import_by_path') + 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', - 'utils_tests.unexistent'): + for path in ('no_dots_in_path', 'unexistent.path', 'utils_tests.unexistent'): self.assertRaises(ImproperlyConfigured, import_by_path, path) with self.assertRaises(ImproperlyConfigured) as cm: @@ -132,6 +132,24 @@ class ModuleImportTestCase(unittest.TestCase): self.assertIsNotNone(traceback.tb_next.tb_next, 'Should have more than the calling frame in the traceback.') + def test_import_by_path_pending_deprecation_warning(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always', category=PendingDeprecationWarning) + cls = import_by_path('django.utils.module_loading.import_by_path') + self.assertEqual(cls, import_by_path) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, PendingDeprecationWarning)) + self.assertIn('deprecated', str(w[-1].message)) + + def test_import_string(self): + cls = import_string('django.utils.module_loading.import_string') + self.assertEqual(cls, import_string) + + # Test exceptions raised + self.assertRaises(ImportError, import_string, 'no_dots_in_path') + self.assertRaises(ImportError, import_string, 'utils_tests.unexistent') + self.assertRaises(ImportError, import_string, 'unexistent.path') + @modify_settings(INSTALLED_APPS={'append': 'utils_tests.test_module'}) class AutodiscoverModulesTestCase(SimpleTestCase): diff --git a/tests/wsgi/tests.py b/tests/wsgi/tests.py index 567ca94f98c..299beacf46b 100644 --- a/tests/wsgi/tests.py +++ b/tests/wsgi/tests.py @@ -101,6 +101,6 @@ class GetInternalWSGIApplicationTest(unittest.TestCase): def test_bad_name(self): with six.assertRaisesRegex(self, ImproperlyConfigured, - r"^WSGI application 'wsgi.wsgi.noexist' could not be loaded; Module.*"): + r"^WSGI application 'wsgi.wsgi.noexist' could not be loaded; Error importing.*"): get_internal_wsgi_application()