diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index e7c9acbaea..cf2cefeea9 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -40,6 +40,7 @@ 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.encoding import force_bytes, get_system_encoding from django.utils.six.moves import _thread as thread # This import does nothing, but it's necessary to avoid some race conditions @@ -285,6 +286,14 @@ def restart_with_reloader(): while True: args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv new_environ = os.environ.copy() + if _win and six.PY2: + # Environment variables on Python 2 + Windows must be str. + encoding = get_system_encoding() + for key in new_environ.keys(): + str_key = force_bytes(key, encoding=encoding) + str_value = force_bytes(new_environ[key], encoding=encoding) + del new_environ[key] + new_environ[str_key] = str_value new_environ["RUN_MAIN"] = 'true' exit_code = subprocess.call(args, env=new_environ) if exit_code != 3: diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 2cd093335e..9448611ecc 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -18,3 +18,6 @@ Bugfixes * Fixed a regression in pickling of ``LazyObject`` on Python 2 when the wrapped object doesn't have ``__reduce__()`` (:ticket:`28389`). + +* Fixed crash in ``runserver``'s ``autoreload`` with Python 2 on Windows with + non-``str`` environment variables (:ticket:`28174`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 5d42af62c8..3fae2e4da2 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -1,6 +1,9 @@ +from __future__ import unicode_literals + import gettext import os import shutil +import sys import tempfile from importlib import import_module @@ -251,3 +254,17 @@ class ResetTranslationsTests(SimpleTestCase): self.assertEqual(trans_real._translations, {}) self.assertIsNone(trans_real._default) self.assertIsInstance(trans_real._active, _thread._local) + + +class TestRestartWithReloader(SimpleTestCase): + + def test_environment(self): + """" + With Python 2 on Windows, restart_with_reloader() coerces environment + variables to str to avoid "TypeError: environment can only contain + strings" in Python's subprocess.py. + """ + # With unicode_literals, these values are unicode. + os.environ['SPAM'] = 'spam' + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader()