I'm trying to figure out why Python Tornado autoreload feature does not work when configured and started inside a module. The following module contains a MyTornadoApp
class that configures and starts autoreload if the proper option is set:
import os
import tornado.autoreload
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello from index handler!")
class MyTornadoApp:
"""Setup my custom tornado app with default settings"""
# default tornado settings
default_settings = dict(
debug=False,
autoreload=True,
compress_response=True,
port=8080,
)
default_handlers = [
(r"/", IndexHandler),
]
def __init__(self, user_settings):
self.options = {**self.default_settings, **user_settings}
if self.options['autoreload'] == True:
self._autoreload_config()
self.tornado = tornado.web.Application(self.default_handlers,
**self.options)
def _autoreload_config(self):
"""Setup autoreload for tornado"""
print("Setting up autoreload for tornado...")
# autoreload (exclusive) monitor file
monitoring_file = os.path.abspath('autoreload')
if not os.path.isfile(monitoring_file):
with open(monitoring_file, 'w', encoding='UTF-8') as f:
f.write('Do NOT delete. Monitoring changes in this file!')
tornado.autoreload.watch(monitoring_file)
tornado.autoreload.start()
The main python script below then creates the tornado app but, unfortunately, autoreload
only works if start
is called (again, according to the Tornado emitted warning message). Any ideias why is this happening? Is it a feature or a bug? What am I missing? TIA.
import asyncio
import tornado.web
from tornadoapp import MyTornadoApp
async def main():
################################################################
# Why do I need this in order to autoreload work properly???
################################################################
if app.options['autoreload'] == True:
tornado.autoreload.start()
app.tornado.listen(app.options['port'])
print(f"Tornado is listening on port {app.options['port']}")
await asyncio.Event().wait()
if __name__ == "__main__":
my_settings = dict(
debug=False,
autoreload=True,
)
app = MyTornadoApp(my_settings)
asyncio.run(main())
Please note the warning message: tornado.autoreload started more than once in the same process
. But if we remove the tornado.autoreload.start()
line from the main()
function then autoreload will not be triggered if we change the monitoring file (or any other watched files).
This is an unfortunate quirk/flaw of asyncio.run
: it will silently discard any event loop that already existed in the thread and create a new one to run the main function. Since Tornado initializes autoreload when the Application is constructed, that means it uses the wrong event loop when you construct it outside of main
.
There are two possible solutions:
asyncio.get_event_loop().run_until_complete()
instead of asyncio.run()
. This should work the way you expect, but this usage is deprecated and will be removed in some future version of python.asyncio.run()
, but do everything (including construction of the Application) inside the main function. This is the recommended pattern for the future.