We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Triggering a code reload during a PDB session breaks terminal input
Pylons and Django hit the same issue: https://github.com/Pylons/pyramid/issues/689

PR incoming to do exactly what they did 🤷‍♂️

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `werkzeug/_reloader.py`
Content:
```
1 import os
2 import sys
3 import time
4 import subprocess
5 import threading
6 from itertools import chain
7 
8 from werkzeug._internal import _log
9 from werkzeug._compat import PY2, iteritems, text_type
10 
11 
12 def _iter_module_files():
13     """This iterates over all relevant Python files.  It goes through all
14     loaded files from modules, all files in folders of already loaded modules
15     as well as all files reachable through a package.
16     """
17     # The list call is necessary on Python 3 in case the module
18     # dictionary modifies during iteration.
19     for module in list(sys.modules.values()):
20         if module is None:
21             continue
22         filename = getattr(module, '__file__', None)
23         if filename:
24             if os.path.isdir(filename) and \
25                os.path.exists(os.path.join(filename, "__init__.py")):
26                 filename = os.path.join(filename, "__init__.py")
27 
28             old = None
29             while not os.path.isfile(filename):
30                 old = filename
31                 filename = os.path.dirname(filename)
32                 if filename == old:
33                     break
34             else:
35                 if filename[-4:] in ('.pyc', '.pyo'):
36                     filename = filename[:-1]
37                 yield filename
38 
39 
40 def _find_observable_paths(extra_files=None):
41     """Finds all paths that should be observed."""
42     rv = set(os.path.dirname(os.path.abspath(x))
43              if os.path.isfile(x) else os.path.abspath(x)
44              for x in sys.path)
45 
46     for filename in extra_files or ():
47         rv.add(os.path.dirname(os.path.abspath(filename)))
48 
49     for module in list(sys.modules.values()):
50         fn = getattr(module, '__file__', None)
51         if fn is None:
52             continue
53         fn = os.path.abspath(fn)
54         rv.add(os.path.dirname(fn))
55 
56     return _find_common_roots(rv)
57 
58 
59 def _get_args_for_reloading():
60     """Returns the executable. This contains a workaround for windows
61     if the executable is incorrectly reported to not have the .exe
62     extension which can cause bugs on reloading.
63     """
64     rv = [sys.executable]
65     py_script = sys.argv[0]
66     if os.name == 'nt' and not os.path.exists(py_script) and \
67        os.path.exists(py_script + '.exe'):
68         py_script += '.exe'
69     if os.path.splitext(rv[0])[1] == '.exe' and os.path.splitext(py_script)[1] == '.exe':
70         rv.pop(0)
71     rv.append(py_script)
72     rv.extend(sys.argv[1:])
73     return rv
74 
75 
76 def _find_common_roots(paths):
77     """Out of some paths it finds the common roots that need monitoring."""
78     paths = [x.split(os.path.sep) for x in paths]
79     root = {}
80     for chunks in sorted(paths, key=len, reverse=True):
81         node = root
82         for chunk in chunks:
83             node = node.setdefault(chunk, {})
84         node.clear()
85 
86     rv = set()
87 
88     def _walk(node, path):
89         for prefix, child in iteritems(node):
90             _walk(child, path + (prefix,))
91         if not node:
92             rv.add('/'.join(path))
93     _walk(root, ())
94     return rv
95 
96 
97 class ReloaderLoop(object):
98     name = None
99 
100     # monkeypatched by testsuite. wrapping with `staticmethod` is required in
101     # case time.sleep has been replaced by a non-c function (e.g. by
102     # `eventlet.monkey_patch`) before we get here
103     _sleep = staticmethod(time.sleep)
104 
105     def __init__(self, extra_files=None, interval=1):
106         self.extra_files = set(os.path.abspath(x)
107                                for x in extra_files or ())
108         self.interval = interval
109 
110     def run(self):
111         pass
112 
113     def restart_with_reloader(self):
114         """Spawn a new Python interpreter with the same arguments as this one,
115         but running the reloader thread.
116         """
117         while 1:
118             _log('info', ' * Restarting with %s' % self.name)
119             args = _get_args_for_reloading()
120             new_environ = os.environ.copy()
121             new_environ['WERKZEUG_RUN_MAIN'] = 'true'
122 
123             # a weird bug on windows. sometimes unicode strings end up in the
124             # environment and subprocess.call does not like this, encode them
125             # to latin1 and continue.
126             if os.name == 'nt' and PY2:
127                 for key, value in iteritems(new_environ):
128                     if isinstance(value, text_type):
129                         new_environ[key] = value.encode('iso-8859-1')
130 
131             exit_code = subprocess.call(args, env=new_environ,
132                                         close_fds=False)
133             if exit_code != 3:
134                 return exit_code
135 
136     def trigger_reload(self, filename):
137         self.log_reload(filename)
138         sys.exit(3)
139 
140     def log_reload(self, filename):
141         filename = os.path.abspath(filename)
142         _log('info', ' * Detected change in %r, reloading' % filename)
143 
144 
145 class StatReloaderLoop(ReloaderLoop):
146     name = 'stat'
147 
148     def run(self):
149         mtimes = {}
150         while 1:
151             for filename in chain(_iter_module_files(),
152                                   self.extra_files):
153                 try:
154                     mtime = os.stat(filename).st_mtime
155                 except OSError:
156                     continue
157 
158                 old_time = mtimes.get(filename)
159                 if old_time is None:
160                     mtimes[filename] = mtime
161                     continue
162                 elif mtime > old_time:
163                     self.trigger_reload(filename)
164             self._sleep(self.interval)
165 
166 
167 class WatchdogReloaderLoop(ReloaderLoop):
168 
169     def __init__(self, *args, **kwargs):
170         ReloaderLoop.__init__(self, *args, **kwargs)
171         from watchdog.observers import Observer
172         from watchdog.events import FileSystemEventHandler
173         self.observable_paths = set()
174 
175         def _check_modification(filename):
176             if filename in self.extra_files:
177                 self.trigger_reload(filename)
178             dirname = os.path.dirname(filename)
179             if dirname.startswith(tuple(self.observable_paths)):
180                 if filename.endswith(('.pyc', '.pyo', '.py')):
181                     self.trigger_reload(filename)
182 
183         class _CustomHandler(FileSystemEventHandler):
184 
185             def on_created(self, event):
186                 _check_modification(event.src_path)
187 
188             def on_modified(self, event):
189                 _check_modification(event.src_path)
190 
191             def on_moved(self, event):
192                 _check_modification(event.src_path)
193                 _check_modification(event.dest_path)
194 
195             def on_deleted(self, event):
196                 _check_modification(event.src_path)
197 
198         reloader_name = Observer.__name__.lower()
199         if reloader_name.endswith('observer'):
200             reloader_name = reloader_name[:-8]
201         reloader_name += ' reloader'
202 
203         self.name = reloader_name
204 
205         self.observer_class = Observer
206         self.event_handler = _CustomHandler()
207         self.should_reload = False
208 
209     def trigger_reload(self, filename):
210         # This is called inside an event handler, which means throwing
211         # SystemExit has no effect.
212         # https://github.com/gorakhargosh/watchdog/issues/294
213         self.should_reload = True
214         self.log_reload(filename)
215 
216     def run(self):
217         watches = {}
218         observer = self.observer_class()
219         observer.start()
220 
221         try:
222             while not self.should_reload:
223                 to_delete = set(watches)
224                 paths = _find_observable_paths(self.extra_files)
225                 for path in paths:
226                     if path not in watches:
227                         try:
228                             watches[path] = observer.schedule(
229                                 self.event_handler, path, recursive=True)
230                         except OSError:
231                             # Clear this path from list of watches We don't want
232                             # the same error message showing again in the next
233                             # iteration.
234                             watches[path] = None
235                     to_delete.discard(path)
236                 for path in to_delete:
237                     watch = watches.pop(path, None)
238                     if watch is not None:
239                         observer.unschedule(watch)
240                 self.observable_paths = paths
241                 self._sleep(self.interval)
242         finally:
243             observer.stop()
244             observer.join()
245 
246         sys.exit(3)
247 
248 
249 reloader_loops = {
250     'stat': StatReloaderLoop,
251     'watchdog': WatchdogReloaderLoop,
252 }
253 
254 try:
255     __import__('watchdog.observers')
256 except ImportError:
257     reloader_loops['auto'] = reloader_loops['stat']
258 else:
259     reloader_loops['auto'] = reloader_loops['watchdog']
260 
261 
262 def run_with_reloader(main_func, extra_files=None, interval=1,
263                       reloader_type='auto'):
264     """Run the given function in an independent python interpreter."""
265     import signal
266     reloader = reloader_loops[reloader_type](extra_files, interval)
267     signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
268     try:
269         if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
270             t = threading.Thread(target=main_func, args=())
271             t.setDaemon(True)
272             t.start()
273             reloader.run()
274         else:
275             sys.exit(reloader.restart_with_reloader())
276     except KeyboardInterrupt:
277         pass
278
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/werkzeug/_reloader.py b/werkzeug/_reloader.py
--- a/werkzeug/_reloader.py
+++ b/werkzeug/_reloader.py
@@ -259,6 +259,22 @@
     reloader_loops['auto'] = reloader_loops['watchdog']
 
 
+def ensure_echo_on():
+    """Ensure that echo mode is enabled. Some tools such as PDB disable
+    it which causes usability issues after reload."""
+    # tcgetattr will fail if stdin isn't a tty
+    if not sys.stdin.isatty():
+        return
+    try:
+        import termios
+    except ImportError:
+        return
+    attributes = termios.tcgetattr(sys.stdin)
+    if not attributes[3] & termios.ECHO:
+        attributes[3] |= termios.ECHO
+        termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes)
+
+
 def run_with_reloader(main_func, extra_files=None, interval=1,
                       reloader_type='auto'):
     """Run the given function in an independent python interpreter."""
@@ -267,6 +283,7 @@
     signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
     try:
         if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
+            ensure_echo_on()
             t = threading.Thread(target=main_func, args=())
             t.setDaemon(True)
             t.start()

{"golden_diff": "diff --git a/werkzeug/_reloader.py b/werkzeug/_reloader.py\n--- a/werkzeug/_reloader.py\n+++ b/werkzeug/_reloader.py\n@@ -259,6 +259,22 @@\n     reloader_loops['auto'] = reloader_loops['watchdog']\n \n \n+def ensure_echo_on():\n+    \"\"\"Ensure that echo mode is enabled. Some tools such as PDB disable\n+    it which causes usability issues after reload.\"\"\"\n+    # tcgetattr will fail if stdin isn't a tty\n+    if not sys.stdin.isatty():\n+        return\n+    try:\n+        import termios\n+    except ImportError:\n+        return\n+    attributes = termios.tcgetattr(sys.stdin)\n+    if not attributes[3] & termios.ECHO:\n+        attributes[3] |= termios.ECHO\n+        termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes)\n+\n+\n def run_with_reloader(main_func, extra_files=None, interval=1,\n                       reloader_type='auto'):\n     \"\"\"Run the given function in an independent python interpreter.\"\"\"\n@@ -267,6 +283,7 @@\n     signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))\n     try:\n         if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':\n+            ensure_echo_on()\n             t = threading.Thread(target=main_func, args=())\n             t.setDaemon(True)\n             t.start()\n", "issue": "Triggering a code reload during a PDB session breaks terminal input\nPylons and Django hit the same issue: https://github.com/Pylons/pyramid/issues/689\r\n\r\nPR incoming to do exactly what they did \ud83e\udd37\u200d\u2642\ufe0f\n", "before_files": [{"content": "import os\nimport sys\nimport time\nimport subprocess\nimport threading\nfrom itertools import chain\n\nfrom werkzeug._internal import _log\nfrom werkzeug._compat import PY2, iteritems, text_type\n\n\ndef _iter_module_files():\n    \"\"\"This iterates over all relevant Python files.  It goes through all\n    loaded files from modules, all files in folders of already loaded modules\n    as well as all files reachable through a package.\n    \"\"\"\n    # The list call is necessary on Python 3 in case the module\n    # dictionary modifies during iteration.\n    for module in list(sys.modules.values()):\n        if module is None:\n            continue\n        filename = getattr(module, '__file__', None)\n        if filename:\n            if os.path.isdir(filename) and \\\n               os.path.exists(os.path.join(filename, \"__init__.py\")):\n                filename = os.path.join(filename, \"__init__.py\")\n\n            old = None\n            while not os.path.isfile(filename):\n                old = filename\n                filename = os.path.dirname(filename)\n                if filename == old:\n                    break\n            else:\n                if filename[-4:] in ('.pyc', '.pyo'):\n                    filename = filename[:-1]\n                yield filename\n\n\ndef _find_observable_paths(extra_files=None):\n    \"\"\"Finds all paths that should be observed.\"\"\"\n    rv = set(os.path.dirname(os.path.abspath(x))\n             if os.path.isfile(x) else os.path.abspath(x)\n             for x in sys.path)\n\n    for filename in extra_files or ():\n        rv.add(os.path.dirname(os.path.abspath(filename)))\n\n    for module in list(sys.modules.values()):\n        fn = getattr(module, '__file__', None)\n        if fn is None:\n            continue\n        fn = os.path.abspath(fn)\n        rv.add(os.path.dirname(fn))\n\n    return _find_common_roots(rv)\n\n\ndef _get_args_for_reloading():\n    \"\"\"Returns the executable. This contains a workaround for windows\n    if the executable is incorrectly reported to not have the .exe\n    extension which can cause bugs on reloading.\n    \"\"\"\n    rv = [sys.executable]\n    py_script = sys.argv[0]\n    if os.name == 'nt' and not os.path.exists(py_script) and \\\n       os.path.exists(py_script + '.exe'):\n        py_script += '.exe'\n    if os.path.splitext(rv[0])[1] == '.exe' and os.path.splitext(py_script)[1] == '.exe':\n        rv.pop(0)\n    rv.append(py_script)\n    rv.extend(sys.argv[1:])\n    return rv\n\n\ndef _find_common_roots(paths):\n    \"\"\"Out of some paths it finds the common roots that need monitoring.\"\"\"\n    paths = [x.split(os.path.sep) for x in paths]\n    root = {}\n    for chunks in sorted(paths, key=len, reverse=True):\n        node = root\n        for chunk in chunks:\n            node = node.setdefault(chunk, {})\n        node.clear()\n\n    rv = set()\n\n    def _walk(node, path):\n        for prefix, child in iteritems(node):\n            _walk(child, path + (prefix,))\n        if not node:\n            rv.add('/'.join(path))\n    _walk(root, ())\n    return rv\n\n\nclass ReloaderLoop(object):\n    name = None\n\n    # monkeypatched by testsuite. wrapping with `staticmethod` is required in\n    # case time.sleep has been replaced by a non-c function (e.g. by\n    # `eventlet.monkey_patch`) before we get here\n    _sleep = staticmethod(time.sleep)\n\n    def __init__(self, extra_files=None, interval=1):\n        self.extra_files = set(os.path.abspath(x)\n                               for x in extra_files or ())\n        self.interval = interval\n\n    def run(self):\n        pass\n\n    def restart_with_reloader(self):\n        \"\"\"Spawn a new Python interpreter with the same arguments as this one,\n        but running the reloader thread.\n        \"\"\"\n        while 1:\n            _log('info', ' * Restarting with %s' % self.name)\n            args = _get_args_for_reloading()\n            new_environ = os.environ.copy()\n            new_environ['WERKZEUG_RUN_MAIN'] = 'true'\n\n            # a weird bug on windows. sometimes unicode strings end up in the\n            # environment and subprocess.call does not like this, encode them\n            # to latin1 and continue.\n            if os.name == 'nt' and PY2:\n                for key, value in iteritems(new_environ):\n                    if isinstance(value, text_type):\n                        new_environ[key] = value.encode('iso-8859-1')\n\n            exit_code = subprocess.call(args, env=new_environ,\n                                        close_fds=False)\n            if exit_code != 3:\n                return exit_code\n\n    def trigger_reload(self, filename):\n        self.log_reload(filename)\n        sys.exit(3)\n\n    def log_reload(self, filename):\n        filename = os.path.abspath(filename)\n        _log('info', ' * Detected change in %r, reloading' % filename)\n\n\nclass StatReloaderLoop(ReloaderLoop):\n    name = 'stat'\n\n    def run(self):\n        mtimes = {}\n        while 1:\n            for filename in chain(_iter_module_files(),\n                                  self.extra_files):\n                try:\n                    mtime = os.stat(filename).st_mtime\n                except OSError:\n                    continue\n\n                old_time = mtimes.get(filename)\n                if old_time is None:\n                    mtimes[filename] = mtime\n                    continue\n                elif mtime > old_time:\n                    self.trigger_reload(filename)\n            self._sleep(self.interval)\n\n\nclass WatchdogReloaderLoop(ReloaderLoop):\n\n    def __init__(self, *args, **kwargs):\n        ReloaderLoop.__init__(self, *args, **kwargs)\n        from watchdog.observers import Observer\n        from watchdog.events import FileSystemEventHandler\n        self.observable_paths = set()\n\n        def _check_modification(filename):\n            if filename in self.extra_files:\n                self.trigger_reload(filename)\n            dirname = os.path.dirname(filename)\n            if dirname.startswith(tuple(self.observable_paths)):\n                if filename.endswith(('.pyc', '.pyo', '.py')):\n                    self.trigger_reload(filename)\n\n        class _CustomHandler(FileSystemEventHandler):\n\n            def on_created(self, event):\n                _check_modification(event.src_path)\n\n            def on_modified(self, event):\n                _check_modification(event.src_path)\n\n            def on_moved(self, event):\n                _check_modification(event.src_path)\n                _check_modification(event.dest_path)\n\n            def on_deleted(self, event):\n                _check_modification(event.src_path)\n\n        reloader_name = Observer.__name__.lower()\n        if reloader_name.endswith('observer'):\n            reloader_name = reloader_name[:-8]\n        reloader_name += ' reloader'\n\n        self.name = reloader_name\n\n        self.observer_class = Observer\n        self.event_handler = _CustomHandler()\n        self.should_reload = False\n\n    def trigger_reload(self, filename):\n        # This is called inside an event handler, which means throwing\n        # SystemExit has no effect.\n        # https://github.com/gorakhargosh/watchdog/issues/294\n        self.should_reload = True\n        self.log_reload(filename)\n\n    def run(self):\n        watches = {}\n        observer = self.observer_class()\n        observer.start()\n\n        try:\n            while not self.should_reload:\n                to_delete = set(watches)\n                paths = _find_observable_paths(self.extra_files)\n                for path in paths:\n                    if path not in watches:\n                        try:\n                            watches[path] = observer.schedule(\n                                self.event_handler, path, recursive=True)\n                        except OSError:\n                            # Clear this path from list of watches We don't want\n                            # the same error message showing again in the next\n                            # iteration.\n                            watches[path] = None\n                    to_delete.discard(path)\n                for path in to_delete:\n                    watch = watches.pop(path, None)\n                    if watch is not None:\n                        observer.unschedule(watch)\n                self.observable_paths = paths\n                self._sleep(self.interval)\n        finally:\n            observer.stop()\n            observer.join()\n\n        sys.exit(3)\n\n\nreloader_loops = {\n    'stat': StatReloaderLoop,\n    'watchdog': WatchdogReloaderLoop,\n}\n\ntry:\n    __import__('watchdog.observers')\nexcept ImportError:\n    reloader_loops['auto'] = reloader_loops['stat']\nelse:\n    reloader_loops['auto'] = reloader_loops['watchdog']\n\n\ndef run_with_reloader(main_func, extra_files=None, interval=1,\n                      reloader_type='auto'):\n    \"\"\"Run the given function in an independent python interpreter.\"\"\"\n    import signal\n    reloader = reloader_loops[reloader_type](extra_files, interval)\n    signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))\n    try:\n        if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':\n            t = threading.Thread(target=main_func, args=())\n            t.setDaemon(True)\n            t.start()\n            reloader.run()\n        else:\n            sys.exit(reloader.restart_with_reloader())\n    except KeyboardInterrupt:\n        pass\n", "path": "werkzeug/_reloader.py"}], "after_files": [{"content": "import os\nimport sys\nimport time\nimport subprocess\nimport threading\nfrom itertools import chain\n\nfrom werkzeug._internal import _log\nfrom werkzeug._compat import PY2, iteritems, text_type\n\n\ndef _iter_module_files():\n    \"\"\"This iterates over all relevant Python files.  It goes through all\n    loaded files from modules, all files in folders of already loaded modules\n    as well as all files reachable through a package.\n    \"\"\"\n    # The list call is necessary on Python 3 in case the module\n    # dictionary modifies during iteration.\n    for module in list(sys.modules.values()):\n        if module is None:\n            continue\n        filename = getattr(module, '__file__', None)\n        if filename:\n            if os.path.isdir(filename) and \\\n               os.path.exists(os.path.join(filename, \"__init__.py\")):\n                filename = os.path.join(filename, \"__init__.py\")\n\n            old = None\n            while not os.path.isfile(filename):\n                old = filename\n                filename = os.path.dirname(filename)\n                if filename == old:\n                    break\n            else:\n                if filename[-4:] in ('.pyc', '.pyo'):\n                    filename = filename[:-1]\n                yield filename\n\n\ndef _find_observable_paths(extra_files=None):\n    \"\"\"Finds all paths that should be observed.\"\"\"\n    rv = set(os.path.dirname(os.path.abspath(x))\n             if os.path.isfile(x) else os.path.abspath(x)\n             for x in sys.path)\n\n    for filename in extra_files or ():\n        rv.add(os.path.dirname(os.path.abspath(filename)))\n\n    for module in list(sys.modules.values()):\n        fn = getattr(module, '__file__', None)\n        if fn is None:\n            continue\n        fn = os.path.abspath(fn)\n        rv.add(os.path.dirname(fn))\n\n    return _find_common_roots(rv)\n\n\ndef _get_args_for_reloading():\n    \"\"\"Returns the executable. This contains a workaround for windows\n    if the executable is incorrectly reported to not have the .exe\n    extension which can cause bugs on reloading.\n    \"\"\"\n    rv = [sys.executable]\n    py_script = sys.argv[0]\n    if os.name == 'nt' and not os.path.exists(py_script) and \\\n       os.path.exists(py_script + '.exe'):\n        py_script += '.exe'\n    if os.path.splitext(rv[0])[1] == '.exe' and os.path.splitext(py_script)[1] == '.exe':\n        rv.pop(0)\n    rv.append(py_script)\n    rv.extend(sys.argv[1:])\n    return rv\n\n\ndef _find_common_roots(paths):\n    \"\"\"Out of some paths it finds the common roots that need monitoring.\"\"\"\n    paths = [x.split(os.path.sep) for x in paths]\n    root = {}\n    for chunks in sorted(paths, key=len, reverse=True):\n        node = root\n        for chunk in chunks:\n            node = node.setdefault(chunk, {})\n        node.clear()\n\n    rv = set()\n\n    def _walk(node, path):\n        for prefix, child in iteritems(node):\n            _walk(child, path + (prefix,))\n        if not node:\n            rv.add('/'.join(path))\n    _walk(root, ())\n    return rv\n\n\nclass ReloaderLoop(object):\n    name = None\n\n    # monkeypatched by testsuite. wrapping with `staticmethod` is required in\n    # case time.sleep has been replaced by a non-c function (e.g. by\n    # `eventlet.monkey_patch`) before we get here\n    _sleep = staticmethod(time.sleep)\n\n    def __init__(self, extra_files=None, interval=1):\n        self.extra_files = set(os.path.abspath(x)\n                               for x in extra_files or ())\n        self.interval = interval\n\n    def run(self):\n        pass\n\n    def restart_with_reloader(self):\n        \"\"\"Spawn a new Python interpreter with the same arguments as this one,\n        but running the reloader thread.\n        \"\"\"\n        while 1:\n            _log('info', ' * Restarting with %s' % self.name)\n            args = _get_args_for_reloading()\n            new_environ = os.environ.copy()\n            new_environ['WERKZEUG_RUN_MAIN'] = 'true'\n\n            # a weird bug on windows. sometimes unicode strings end up in the\n            # environment and subprocess.call does not like this, encode them\n            # to latin1 and continue.\n            if os.name == 'nt' and PY2:\n                for key, value in iteritems(new_environ):\n                    if isinstance(value, text_type):\n                        new_environ[key] = value.encode('iso-8859-1')\n\n            exit_code = subprocess.call(args, env=new_environ,\n                                        close_fds=False)\n            if exit_code != 3:\n                return exit_code\n\n    def trigger_reload(self, filename):\n        self.log_reload(filename)\n        sys.exit(3)\n\n    def log_reload(self, filename):\n        filename = os.path.abspath(filename)\n        _log('info', ' * Detected change in %r, reloading' % filename)\n\n\nclass StatReloaderLoop(ReloaderLoop):\n    name = 'stat'\n\n    def run(self):\n        mtimes = {}\n        while 1:\n            for filename in chain(_iter_module_files(),\n                                  self.extra_files):\n                try:\n                    mtime = os.stat(filename).st_mtime\n                except OSError:\n                    continue\n\n                old_time = mtimes.get(filename)\n                if old_time is None:\n                    mtimes[filename] = mtime\n                    continue\n                elif mtime > old_time:\n                    self.trigger_reload(filename)\n            self._sleep(self.interval)\n\n\nclass WatchdogReloaderLoop(ReloaderLoop):\n\n    def __init__(self, *args, **kwargs):\n        ReloaderLoop.__init__(self, *args, **kwargs)\n        from watchdog.observers import Observer\n        from watchdog.events import FileSystemEventHandler\n        self.observable_paths = set()\n\n        def _check_modification(filename):\n            if filename in self.extra_files:\n                self.trigger_reload(filename)\n            dirname = os.path.dirname(filename)\n            if dirname.startswith(tuple(self.observable_paths)):\n                if filename.endswith(('.pyc', '.pyo', '.py')):\n                    self.trigger_reload(filename)\n\n        class _CustomHandler(FileSystemEventHandler):\n\n            def on_created(self, event):\n                _check_modification(event.src_path)\n\n            def on_modified(self, event):\n                _check_modification(event.src_path)\n\n            def on_moved(self, event):\n                _check_modification(event.src_path)\n                _check_modification(event.dest_path)\n\n            def on_deleted(self, event):\n                _check_modification(event.src_path)\n\n        reloader_name = Observer.__name__.lower()\n        if reloader_name.endswith('observer'):\n            reloader_name = reloader_name[:-8]\n        reloader_name += ' reloader'\n\n        self.name = reloader_name\n\n        self.observer_class = Observer\n        self.event_handler = _CustomHandler()\n        self.should_reload = False\n\n    def trigger_reload(self, filename):\n        # This is called inside an event handler, which means throwing\n        # SystemExit has no effect.\n        # https://github.com/gorakhargosh/watchdog/issues/294\n        self.should_reload = True\n        self.log_reload(filename)\n\n    def run(self):\n        watches = {}\n        observer = self.observer_class()\n        observer.start()\n\n        try:\n            while not self.should_reload:\n                to_delete = set(watches)\n                paths = _find_observable_paths(self.extra_files)\n                for path in paths:\n                    if path not in watches:\n                        try:\n                            watches[path] = observer.schedule(\n                                self.event_handler, path, recursive=True)\n                        except OSError:\n                            # Clear this path from list of watches We don't want\n                            # the same error message showing again in the next\n                            # iteration.\n                            watches[path] = None\n                    to_delete.discard(path)\n                for path in to_delete:\n                    watch = watches.pop(path, None)\n                    if watch is not None:\n                        observer.unschedule(watch)\n                self.observable_paths = paths\n                self._sleep(self.interval)\n        finally:\n            observer.stop()\n            observer.join()\n\n        sys.exit(3)\n\n\nreloader_loops = {\n    'stat': StatReloaderLoop,\n    'watchdog': WatchdogReloaderLoop,\n}\n\ntry:\n    __import__('watchdog.observers')\nexcept ImportError:\n    reloader_loops['auto'] = reloader_loops['stat']\nelse:\n    reloader_loops['auto'] = reloader_loops['watchdog']\n\n\ndef ensure_echo_on():\n    \"\"\"Ensure that echo mode is enabled. Some tools such as PDB disable\n    it which causes usability issues after reload.\"\"\"\n    # tcgetattr will fail if stdin isn't a tty\n    if not sys.stdin.isatty():\n        return\n    try:\n        import termios\n    except ImportError:\n        return\n    attributes = termios.tcgetattr(sys.stdin)\n    if not attributes[3] & termios.ECHO:\n        attributes[3] |= termios.ECHO\n        termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes)\n\n\ndef run_with_reloader(main_func, extra_files=None, interval=1,\n                      reloader_type='auto'):\n    \"\"\"Run the given function in an independent python interpreter.\"\"\"\n    import signal\n    reloader = reloader_loops[reloader_type](extra_files, interval)\n    signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))\n    try:\n        if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':\n            ensure_echo_on()\n            t = threading.Thread(target=main_func, args=())\n            t.setDaemon(True)\n            t.start()\n            reloader.run()\n        else:\n            sys.exit(reloader.restart_with_reloader())\n    except KeyboardInterrupt:\n        pass\n", "path": "werkzeug/_reloader.py"}]}