# under the License.
import aiopg
+import asyncio
import bcrypt
import markdown
import os.path
if __name__ == "__main__":
- tornado.ioloop.IOLoop.current().run_sync(main)
+ asyncio.run(main())
import asyncio
import tornado.escape
-import tornado.ioloop
import tornado.locks
import tornado.web
import os.path
self.wait_future.cancel()
-def main():
+async def main():
parse_command_line()
app = tornado.web.Application(
[
debug=options.debug,
)
app.listen(options.port)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == "__main__":
- main()
+ asyncio.run(main())
# License for the specific language governing permissions and limitations
# under the License.
+import asyncio
import os.path
import tornado.auth
import tornado.escape
import tornado.httpserver
-import tornado.ioloop
import tornado.options
import tornado.web
return self.render_string("modules/post.html", post=post)
-def main():
+async def main():
tornado.options.parse_command_line()
if not (options.facebook_api_key and options.facebook_secret):
print("--facebook_api_key and --facebook_secret must be set")
return
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == "__main__":
- main()
+ asyncio.run(main())
See file_uploader.py in this directory for code that uploads files in this format.
"""
+import asyncio
import logging
from urllib.parse import unquote
-import tornado.ioloop
import tornado.web
from tornado import options
return tornado.web.Application([(r"/post", POSTHandler), (r"/(.*)", PUTHandler)])
-if __name__ == "__main__":
- # Tornado configures logging.
+async def main():
options.parse_command_line()
app = make_app()
app.listen(8888)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
See also file_receiver.py in this directory, a server that receives uploads.
"""
+import asyncio
import mimetypes
import os
import sys
from urllib.parse import quote
from uuid import uuid4
-from tornado import gen, httpclient, ioloop
+from tornado import gen, httpclient
from tornado.options import define, options
sys.exit(1)
method = put if options.put else post
- ioloop.IOLoop.current().run_sync(lambda: method(filenames))
+ asyncio.run(method(filenames))
# License for the specific language governing permissions and limitations
# under the License.
+import asyncio
import tornado.httpserver
-import tornado.ioloop
import tornado.options
import tornado.web
self.write("Hello, world")
-def main():
+async def main():
tornado.options.parse_command_line()
application = tornado.web.Application([(r"/", MainHandler)])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == "__main__":
- main()
+ asyncio.run(main())
"""
+import asyncio
import bisect
import datetime
import hashlib
from tornado import escape
from tornado import httpserver
-from tornado import ioloop
from tornado import web
from tornado.util import unicode_type
from tornado.options import options, define
define("bucket_depth", default=0, help="Bucket file system depth limit")
-def start(port, root_directory, bucket_depth):
+async def start(port, root_directory, bucket_depth):
"""Starts the mock S3 server on the given port at the given path."""
application = S3Application(root_directory, bucket_depth)
http_server = httpserver.HTTPServer(application)
http_server.listen(port)
- ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
class S3Application(web.Application):
if __name__ == "__main__":
options.parse_command_line()
- start(options.port, options.root_directory, options.bucket_depth)
+ asyncio.run(start(options.port, options.root_directory, options.bucket_depth))
#!/usr/bin/env python
-from tornado.ioloop import IOLoop
-from tornado import gen
+import asyncio
from tornado.tcpclient import TCPClient
from tornado.options import options, define
define("message", default="ping", help="Message to send")
-@gen.coroutine
-def send_message():
- stream = yield TCPClient().connect(options.host, options.port)
- yield stream.write((options.message + "\n").encode())
+async def send_message():
+ stream = await TCPClient().connect(options.host, options.port)
+ await stream.write((options.message + "\n").encode())
print("Sent to server:", options.message)
- reply = yield stream.read_until(b"\n")
+ reply = await stream.read_until(b"\n")
print("Response from server:", reply.decode().strip())
if __name__ == "__main__":
options.parse_command_line()
- IOLoop.current().run_sync(send_message)
+ asyncio.run(send_message())
#!/usr/bin/env python
+import asyncio
import logging
-from tornado.ioloop import IOLoop
from tornado import gen
from tornado.iostream import StreamClosedError
from tornado.tcpserver import TCPServer
print(e)
-if __name__ == "__main__":
+async def main():
options.parse_command_line()
+ logger.info("Listening on TCP port %d", options.port)
server = EchoServer()
server.listen(options.port)
- logger.info("Listening on TCP port %d", options.port)
- IOLoop.current().start()
+ await asyncio.Event().wait()
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
browser.
"""
+import asyncio
import logging
from tornado.auth import TwitterMixin
from tornado.escape import json_decode, json_encode
-from tornado.ioloop import IOLoop
from tornado import gen
from tornado.options import define, options, parse_command_line, parse_config_file
from tornado.web import Application, RequestHandler, authenticated
self.clear_cookie(self.COOKIE_NAME)
-def main():
+async def main():
parse_command_line(final=False)
parse_config_file(options.config_file)
app.listen(options.port)
logging.info("Listening on http://localhost:%d" % options.port)
- IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == "__main__":
- main()
+ asyncio.run(main())
Authentication, error handling, etc are left as an exercise for the reader :)
"""
+import asyncio
import logging
import tornado.escape
-import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
ChatSocketHandler.send_updates(chat)
-def main():
+async def main():
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == "__main__":
- main()
+ asyncio.run(main())
#!/usr/bin/env python3
+import asyncio
import time
from datetime import timedelta
from html.parser import HTMLParser
from urllib.parse import urljoin, urldefrag
-from tornado import gen, httpclient, ioloop, queues
+from tornado import gen, httpclient, queues
base_url = "http://www.tornadoweb.org/en/stable/"
concurrency = 10
if __name__ == "__main__":
- io_loop = ioloop.IOLoop.current()
- io_loop.run_sync(main)
+ asyncio.run(main())
from tornado import gen
**Coroutines** are the recommended way to write asynchronous code in
-Tornado. Coroutines use the Python ``await`` or ``yield`` keyword to
+Tornado. Coroutines use the Python ``await`` keyword to
suspend and resume execution instead of a chain of callbacks
(cooperative lightweight threads as seen in frameworks like `gevent
<http://www.gevent.org>`_ are sometimes called coroutines as well, but
Running in the background
^^^^^^^^^^^^^^^^^^^^^^^^^
-`.PeriodicCallback` is not normally used with coroutines. Instead, a
+As an alternative to `.PeriodicCallback`, a
coroutine can contain a ``while True:`` loop and use
`tornado.gen.sleep`::
`WebSockets <http://en.wikipedia.org/wiki/WebSocket>`_, and other
applications that require a long-lived connection to each user.
-Tornado can be roughly divided into four major components:
+Tornado can be roughly divided into three major components:
* A web framework (including `.RequestHandler` which is subclassed to
create web applications, and various supporting classes).
* An asynchronous networking library including the classes `.IOLoop`
and `.IOStream`, which serve as the building blocks for the HTTP
components and can also be used to implement other protocols.
-* A coroutine library (`tornado.gen`) which allows asynchronous
- code to be written in a more straightforward way than chaining
- callbacks. This is similar to the native coroutine feature introduced
- in Python 3.5 (``async def``). Native coroutines are recommended
- in place of the `tornado.gen` module when available.
The Tornado web framework and HTTP server together offer a full-stack
alternative to `WSGI <http://www.python.org/dev/peps/pep-3333/>`_.
.. currentmodule:: tornado.queues
-Tornado's `tornado.queues` module implements an asynchronous producer /
-consumer pattern for coroutines, analogous to the pattern implemented for
-threads by the Python standard library's `queue` module.
+Tornado's `tornado.queues` module (and the very similar ``Queue`` classes in
+`asyncio`) implements an asynchronous producer / consumer pattern for
+coroutines, analogous to the pattern implemented for threads by the Python
+standard library's `queue` module.
A coroutine that yields `Queue.get` pauses until there is an item in the queue.
If the queue has a maximum size set, a coroutine that yields `Queue.put` pauses
import asyncio
- async def amain():
+ async def main():
app = make_app()
app.listen(8888)
await asyncio.Event().wait()
-
- def main():
- asyncio.run(amain())
-
if __name__ == '__main__':
- main()
+ asyncio.run(main())
.. testoutput::
:hide:
multiple Python processes to take full advantage of multi-CPU machines.
Typically it is best to run one process per CPU.
+.. note::
+
+ This section is somewhat out of date; the built-in multi-process mode
+ produces deprecation warnings on Python 3.10 (in addition to its other
+ limitations). Updated guidance is still in development; tentative
+ recommendations include running independent processes as described
+ in the paragraph beginning "For more sophisticated deployments", or
+ using ``SO_REUSEPORT`` instead of forking.
+
Tornado includes a built-in multi-process mode to start several
processes at once (note that multi-process mode does not work on
Windows). This requires a slight alteration to the standard main
async def main():
app = make_app()
app.listen(8888)
- await asyncio.Event().wait()
+ shutdown_event = asyncio.Event()
+ await shutdown_event.wait()
if __name__ == "__main__":
asyncio.run(main())
.. testoutput::
:hide:
+The ``main`` coroutine
+~~~~~~~~~~~~~~~~~~~~~~
+
+Beginning with Tornado 6.2 and Python 3.10, the recommended pattern for starting
+a Tornado application is to create a ``main`` coroutine to be run with
+`asyncio.run`. (In older versions, it was common to do initialization in a
+regular function and then start the event loop with
+``IOLoop.current().start()``. However, this pattern produces deprecation
+warnings starting in Python 3.10 and will break in some future version of
+Python.)
+
+When the ``main`` function returns, the program exits, so most of the time for a
+web server ``main`` should run forever. Waiting on an `asyncio.Event` whose
+``set()`` method is never called is a convenient way to make an asynchronus
+function run forever. (and if you wish to have ``main`` exit early as a part of
+a graceful shutdown procedure, you can call ``shutdown_event.set()`` to make it
+exit).
+
The ``Application`` object
~~~~~~~~~~~~~~~~~~~~~~~~~~
source tarball or clone the `git repository
<https://github.com/tornadoweb/tornado>`_ as well.
-**Prerequisites**: Tornado 6.0 requires Python 3.7 or newer (See
-`Tornado 5.1 <https://www.tornadoweb.org/en/branch5.1/>`_ if
-compatibility with Python 2.7 is required). The following optional
-packages may be useful:
+**Prerequisites**: Tornado 6.2 requires Python 3.7 or newer. The following
+optional packages may be useful:
* `pycurl <http://pycurl.io/>`_ is used by the optional
``tornado.curl_httpclient``. Libcurl version 7.22 or higher is required.
-* `Twisted <https://www.twistedmatrix.com/>`_ may be used with the classes in
- `tornado.platform.twisted`.
* `pycares <https://pypi.org/project/pycares/>`_ is an alternative
non-blocking DNS resolver that can be used when threads are not
appropriate.
`HTTPServer` initialization follows one of three patterns (the
initialization methods are defined on `tornado.tcpserver.TCPServer`):
+ .. note::
+
+ The multi-process examples here produce deprecation warnings in
+ Python 3.10; updated guidance is still in development.
+
1. `~tornado.tcpserver.TCPServer.listen`: simple single-process::
server = HTTPServer(app)
"""An I/O event loop for non-blocking sockets.
-In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event
-loop, with a slightly different interface for historical reasons.
-Applications can use either the `.IOLoop` interface or the underlying
-`asyncio` event loop directly (unless compatibility with older
-versions of Tornado is desired, in which case `.IOLoop` must be used).
-
-Typical applications will use a single `IOLoop` object, accessed via
-`IOLoop.current` class method. The `IOLoop.start` method (or
-equivalently, `asyncio.AbstractEventLoop.run_forever`) should usually
-be called at the end of the ``main()`` function. Atypical applications
-may use more than one `IOLoop`, such as one `IOLoop` per thread, or
-per `unittest` case.
+In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event loop, with a
+slightly different interface. The `.IOLoop` interface is now provided primarily
+for backwards compatibility; new code should generally use the `asyncio` event
+loop interface directly. The `IOLoop.current` class method provides the
+`IOLoop` instance corresponding to the running `asyncio` event loop.
"""
class IOLoop(Configurable):
"""An I/O event loop.
- As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event
- loop.
+ As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event loop.
Example usage for a simple TCP server:
.. testoutput::
:hide:
- Do not attempt to construct an `IOLoop` directly; this is deprecated
- since Tornado 6.2. Instead, initialize the `asyncio` event loop and
- use `IOLoop.current()` to access an `IOLoop` wrapper around the
- current event loop.
-
- In general, an `IOLoop` cannot survive a fork or be shared across
- processes in any way. When multiple processes are being used, each
- process should create its own `IOLoop`, which also implies that
- any objects which depend on the `IOLoop` (such as
- `.AsyncHTTPClient`) must also be created in the child processes.
- As a guideline, anything that starts processes (including the
- `tornado.process` and `multiprocessing` modules) should do so as
- early as possible, ideally the first thing the application does
- after loading its configuration in ``main()``.
+ Do not attempt to construct an `IOLoop` directly; this is deprecated since
+ Tornado 6.2. Instead, initialize the `asyncio` event loop and use
+ `IOLoop.current()` to access an `IOLoop` wrapper around the current event
+ loop.
+
+ In general, an `IOLoop` cannot survive a fork or be shared across processes
+ in any way. When multiple processes are being used, each process should
+ create its own `IOLoop`, which also implies that any objects which depend on
+ the `IOLoop` (such as `.AsyncHTTPClient`) must also be created in the child
+ processes. As a guideline, anything that starts processes (including the
+ `tornado.process` and `multiprocessing` modules) should do so as early as
+ possible, ideally the first thing the application does after loading its
+ configuration, and *before* any calls to `.IOLoop.start` or `asyncio.run`.
.. versionchanged:: 4.2
Added the ``make_current`` keyword argument to the `IOLoop`
.. versionchanged:: 5.0
- Uses the `asyncio` event loop by default. The
- ``IOLoop.configure`` method cannot be used on Python 3 except
- to redundantly specify the `asyncio` event loop.
+ Uses the `asyncio` event loop by default. The ``IOLoop.configure`` method
+ cannot be used on Python 3 except to redundantly specify the `asyncio`
+ event loop.
.. deprecated:: 6.2
It is deprecated to create an event loop that is "current" but not
- currently running. This means it is deprecated to pass
+ running. This means it is deprecated to pass
``make_current=True`` to the ``IOLoop`` constructor, or to create
an ``IOLoop`` while no asyncio event loop is running unless
``make_current=False`` is used.
stream.close()
if __name__ == '__main__':
- tornado.ioloop.IOLoop.current().run_sync(main)
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
- stream = tornado.iostream.IOStream(s)
- stream.connect(("friendfeed.com", 80), send_request)
- tornado.ioloop.IOLoop.current().start()
+ asyncio.run(main())
.. testoutput::
:hide:
.. testcode::
+ import asyncio
from tornado import gen
- from tornado.ioloop import IOLoop
from tornado.locks import Condition
condition = Condition()
# Wait for waiter() and notifier() in parallel
await gen.multi([waiter(), notifier()])
- IOLoop.current().run_sync(runner)
+ asyncio.run(runner())
.. testoutput::
next iteration of the `.IOLoop`.
"""
- def __init__(self) -> None:
- super().__init__()
- self.io_loop = ioloop.IOLoop.current()
-
def __repr__(self) -> str:
result = "<%s" % (self.__class__.__name__,)
if self._waiters:
.. testcode::
+ import asyncio
from tornado import gen
- from tornado.ioloop import IOLoop
from tornado.locks import Event
event = Event()
async def runner():
await gen.multi([waiter(), setter()])
- IOLoop.current().run_sync(runner)
+ asyncio.run(runner())
.. testoutput::
from tornado.ioloop import IOLoop
from tornado.concurrent import Future
- # Ensure reliable doctest output: resolve Futures one at a time.
- futures_q = deque([Future() for _ in range(3)])
+ inited = False
async def simulator(futures):
for f in futures:
await gen.sleep(0)
f.set_result(None)
- IOLoop.current().add_callback(simulator, list(futures_q))
-
def use_some_resource():
+ global inited
+ global futures_q
+ if not inited:
+ inited = True
+ # Ensure reliable doctest output: resolve Futures one at a time.
+ futures_q = deque([Future() for _ in range(3)])
+ IOLoop.current().add_callback(simulator, list(futures_q))
+
return futures_q.popleft()
.. testcode:: semaphore
+ import asyncio
from tornado import gen
- from tornado.ioloop import IOLoop
from tornado.locks import Semaphore
sem = Semaphore(2)
# Join all workers.
await gen.multi([worker(i) for i in range(3)])
- IOLoop.current().run_sync(runner)
+ asyncio.run(runner())
.. testoutput:: semaphore
.. testcode::
- from tornado import gen
+ import asyncio
from tornado.ioloop import IOLoop
from tornado.queues import Queue
async for item in q:
try:
print('Doing work on %s' % item)
- await gen.sleep(0.01)
+ await asyncio.sleep(0.01)
finally:
q.task_done()
await q.join() # Wait for consumer to finish all tasks.
print('Done')
- IOLoop.current().run_sync(main)
+ asyncio.run(main())
.. testoutput::
.. testcode::
+ import asyncio
from tornado.queues import PriorityQueue
- q = PriorityQueue()
- q.put((1, 'medium-priority item'))
- q.put((0, 'high-priority item'))
- q.put((10, 'low-priority item'))
+ async def main():
+ q = PriorityQueue()
+ q.put((1, 'medium-priority item'))
+ q.put((0, 'high-priority item'))
+ q.put((10, 'low-priority item'))
+
+ print(await q.get())
+ print(await q.get())
+ print(await q.get())
- print(q.get_nowait())
- print(q.get_nowait())
- print(q.get_nowait())
+ asyncio.run(main())
.. testoutput::
.. testcode::
+ import asyncio
from tornado.queues import LifoQueue
- q = LifoQueue()
- q.put(3)
- q.put(2)
- q.put(1)
+ async def main():
+ q = LifoQueue()
+ q.put(3)
+ q.put(2)
+ q.put(1)
+
+ print(await q.get())
+ print(await q.get())
+ print(await q.get())
- print(q.get_nowait())
- print(q.get_nowait())
- print(q.get_nowait())
+ asyncio.run(main())
.. testoutput::
.. testcode::
- import tornado.ioloop
+ import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
- if __name__ == "__main__":
+ async def main():
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
+
+ if __name__ == "__main__":
+ asyncio.run(main())
.. testoutput::
:hide:
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
- ioloop.IOLoop.current().start()
The constructor for this class takes in a list of `~.routing.Rule`
objects or tuples of values corresponding to the arguments of
def listen(self, port: int, address: str = "", **kwargs: Any) -> HTTPServer:
"""Starts an HTTP server for this application on the given port.
- This is a convenience alias for creating an `.HTTPServer`
- object and calling its listen method. Keyword arguments not
- supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the
- `.HTTPServer` constructor. For advanced uses
- (e.g. multi-process mode), do not use this method; create an
- `.HTTPServer` and call its
+ This is a convenience alias for creating an `.HTTPServer` object and
+ calling its listen method. Keyword arguments not supported by
+ `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer`
+ constructor. For advanced uses (e.g. multi-process mode), do not use
+ this method; create an `.HTTPServer` and call its
`.TCPServer.bind`/`.TCPServer.start` methods directly.
Note that after calling this method you still need to call
- ``IOLoop.current().start()`` to start the server.
+ ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start
+ the server.
Returns the `.HTTPServer` object.
start_response(status, response_headers)
return [b"Hello world!\n"]
- container = tornado.wsgi.WSGIContainer(simple_app)
- http_server = tornado.httpserver.HTTPServer(container)
- http_server.listen(8888)
- tornado.ioloop.IOLoop.current().start()
+ async def main():
+ container = tornado.wsgi.WSGIContainer(simple_app)
+ http_server = tornado.httpserver.HTTPServer(container)
+ http_server.listen(8888)
+ await asyncio.Event().wait()
+
+ asyncio.run(main())
This class is intended to let other frameworks (Django, web.py, etc)
run on the Tornado HTTP server and I/O loop.