diff --git a/CHANGES b/CHANGES index 22e5f11ed..149e244dc 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Version 0.10.1 (bugfix release, release date to be decided) - Fixed regression with multiple query values for URLs (pull request ``#667``). +- Fix issues with eventlet's monkeypatching and the builtin server (pull + request ``#663``). Version 0.10 ------------ diff --git a/tests/test_serving.py b/tests/test_serving.py index d4a4e8015..55ac707b8 100644 --- a/tests/test_serving.py +++ b/tests/test_serving.py @@ -10,6 +10,7 @@ """ import os import ssl +import subprocess import textwrap @@ -171,3 +172,28 @@ def real_app(environ, start_response): r = requests.get(server.url) assert r.status_code == 200 assert r.content == b'hello' + + +def test_monkeypached_sleep(tmpdir): + # removing the staticmethod wrapper in the definition of + # ReloaderLoop._sleep works most of the time, since `sleep` is a c + # function, and unlike python functions which are descriptors, doesn't + # become a method when attached to a class. however, if the user has called + # `eventlet.monkey_patch` before importing `_reloader`, `time.sleep` is a + # python function, and subsequently calling `ReloaderLoop._sleep` fails + # with a TypeError. This test checks that _sleep is attached correctly. + script = tmpdir.mkdir('app').join('test.py') + script.write(textwrap.dedent(''' + import time + + def sleep(secs): + pass + + # simulate eventlet.monkey_patch by replacing the builtin sleep + # with a regular function before _reloader is imported + time.sleep = sleep + + from werkzeug._reloader import ReloaderLoop + ReloaderLoop()._sleep(0) + ''')) + subprocess.check_call(['python', str(script)]) diff --git a/werkzeug/_reloader.py b/werkzeug/_reloader.py index e0ea56647..83716f16e 100644 --- a/werkzeug/_reloader.py +++ b/werkzeug/_reloader.py @@ -107,9 +107,13 @@ def _walk(node, path): class ReloaderLoop(object): - _sleep = staticmethod(time.sleep) # monkeypatched by testsuite name = None + # monkeypatched by testsuite. wrapping with `staticmethod` is required in + # case time.sleep has been replaced by a non-c function (e.g. by + # `eventlet.monkey_patch`) before we get here + _sleep = staticmethod(time.sleep) + def __init__(self, extra_files=None, interval=1): self.extra_files = set(os.path.abspath(x) for x in extra_files or ())