summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2015-08-05 11:07:36 +0200
committerAymeric Augustin <aymeric.augustin@m4x.org>2015-08-29 23:12:08 +0200
commitcc14d51ee8325c82665cb98af4dfe49aab565d52 (patch)
treefe39cc4516e833140c4e456c35919971a4303dd3
parentdc8786ce9413f5d8553997a6b957886694205fa2 (diff)
Fixed #24704 -- Made the autoreloader survive SyntaxErrors.
With this change, it's expected to survive anything except errors that make it impossible to import the settings. It's too complex to fallback to a sensible behavior with a broken settings module. Harcoding things about runserver in ManagementUtility.execute is atrocious but it's the only way out of the chicken'n'egg problem: the current implementation of the autoreloader primarily watches imported Python modules -- and then a few other things that were bolted on top of this design -- but we want it to kick in even if the project contains import-time errors and django.setup() fails. At some point we should throw away this code and replace it by an off-the-shelf autoreloader that watches the working directory and re-runs `django-admin runserver` whenever something changes. Backport of fe6ddb837d from master
-rw-r--r--django/core/management/__init__.py17
-rw-r--r--django/core/management/commands/runserver.py4
-rw-r--r--django/utils/autoreload.py13
-rw-r--r--docs/releases/1.8.5.txt2
4 files changed, 33 insertions, 3 deletions
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index 5b0897748b..1beba4bc84 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -13,7 +13,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import (BaseCommand, CommandError,
CommandParser, handle_default_options)
from django.core.management.color import color_style
-from django.utils import lru_cache, six
+from django.utils import autoreload, lru_cache, six
from django.utils._os import npath, upath
@@ -309,7 +309,20 @@ class ManagementUtility(object):
settings.configure()
if settings.configured:
- django.setup()
+ # Start the auto-reloading dev server even if the code is broken.
+ # The hardcoded condition is a code smell but we can't rely on a
+ # flag on the command class because we haven't located it yet.
+ if subcommand == 'runserver' and '--noreload' not in self.argv:
+ try:
+ autoreload.check_errors(django.setup)()
+ except Exception:
+ # The exception will be raised later in the child process
+ # started by the autoreloader.
+ pass
+
+ # In all other cases, django.setup() is required to succeed.
+ else:
+ django.setup()
self.autocomplete()
diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
index af81393c13..73ddb44817 100644
--- a/django/core/management/commands/runserver.py
+++ b/django/core/management/commands/runserver.py
@@ -102,6 +102,10 @@ class Command(BaseCommand):
from django.conf import settings
from django.utils import translation
+ # If an exception was silenced in ManagementUtility.execute in order
+ # to be raised in the child process, raise it now.
+ autoreload.raise_last_exception()
+
threading = options.get('use_threading')
shutdown_message = options.get('shutdown_message', '')
quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index 869020359a..a07fb04abf 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -40,6 +40,7 @@ import traceback
from django.apps import apps
from django.conf import settings
from django.core.signals import request_finished
+from django.utils import six
from django.utils._os import npath
from django.utils.six.moves import _thread as thread
@@ -75,6 +76,7 @@ I18N_MODIFIED = 2
_mtimes = {}
_win = (sys.platform == "win32")
+_exception = None
_error_files = []
_cached_modules = set()
_cached_filenames = []
@@ -222,11 +224,14 @@ def code_changed():
def check_errors(fn):
def wrapper(*args, **kwargs):
+ global _exception
try:
fn(*args, **kwargs)
except (ImportError, IndentationError, NameError, SyntaxError,
TypeError, AttributeError):
- et, ev, tb = sys.exc_info()
+ _exception = sys.exc_info()
+
+ et, ev, tb = _exception
if getattr(ev, 'filename', None) is None:
# get the filename from the last item in the stack
@@ -242,6 +247,12 @@ def check_errors(fn):
return wrapper
+def raise_last_exception():
+ global _exception
+ if _exception is not None:
+ six.reraise(*_exception)
+
+
def ensure_echo_on():
if termios:
fd = sys.stdin
diff --git a/docs/releases/1.8.5.txt b/docs/releases/1.8.5.txt
index 543e8824be..ac1735bb16 100644
--- a/docs/releases/1.8.5.txt
+++ b/docs/releases/1.8.5.txt
@@ -9,6 +9,8 @@ Django 1.8.5 fixes several bugs in 1.8.4.
Bugfixes
========
+* Made the development server's autoreload more robust (:ticket:`24704`).
+
* Fixed ``AssertionError`` in some delete queries with a model containing a
field that is both a foreign and primary key (:ticket:`24951`).