Of course! Here is a comprehensive guide to using tornadoredis, a popular Python library for integrating Redis with the Tornado web framework.

What is tornadoredis?
tornadoredis is a client library for the Redis key-value store, designed specifically to work with the asynchronous, non-blocking nature of the Tornado I/O loop.
Key Features:
- Asynchronous: All Redis commands are non-blocking, meaning they don't block the entire Tornado application while waiting for a response from the Redis server. This is crucial for building high-performance, scalable web services.
- Connection Pooling: It efficiently manages a pool of connections to Redis, avoiding the overhead of establishing a new connection for every request.
- Pub/Sub Support: It has excellent, built-in support for Redis's Publish/Subscribe (Pub/Sub) messaging pattern, allowing you to build real-time features like chat applications or live notifications.
- Tornado Integration: It provides a simple
Future-based API that integrates seamlessly with Tornado'sgen.coroutineandasync/awaitfeatures.
Installation
First, you need to install the library. It's available on PyPI.
pip install tornadoredis
You will also need a running Redis server.

Basic Usage: Connecting and Making Commands
The core of tornadoredis is the Client class. You create an instance of it and then use its methods to interact with Redis.
The methods return a Future. In Tornado, you typically yield these Futures inside a gen.coroutine decorated function.
Example 1: Setting and Getting a Key
Let's start with a simple "Hello, World!" example.
import tornado.ioloop
import tornado.web
import tornado.gen
import tornadoredis
# --- Redis Client Setup ---
# Create a client instance.
# tornadoredis can connect to a local Redis instance by default.
# For a remote server, provide the host and port, e.g., Client(host='redis.server.com', port=6379)
client = tornadoredis.Client()
# It's good practice to connect explicitly to catch any connection errors early.
try:
client.connect()
print("Successfully connected to Redis!")
except tornadoredis.ConnectionError as e:
print(f"Could not connect to Redis: {e}")
# In a real app, you might want to exit or enter a fallback mode.
# --- Tornado Application ---
class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
# Use the yield keyword to wait for the Redis Future to complete
# without blocking the I/O loop.
# 1. SET a key
set_future = client.set("my_key", "Hello from Tornado!")
yield set_future
self.write("Key 'my_key' was set.\n")
# 2. GET the key
get_future = client.get("my_key")
value = yield get_future
if value is not None:
# Redis returns bytes, so we need to decode it to a string
self.write(f"The value of 'my_key' is: {value.decode('utf-8')}\n")
else:
self.write("Could not find 'my_key'.\n")
# 3. INCR a counter
incr_future = client.incr("my_counter")
counter_value = yield incr_future
self.write(f"The counter 'my_counter' is now: {counter_value}\n")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
# Start the I/O loop
print("Tornado server is running on http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
To run this:
- Save the code as
app.py. - Run your Redis server:
redis-server - Run the Tornado app:
python app.py - Open your browser and go to
http://localhost:8888. Refresh the page a few times to see the counter increment.
Advanced Usage: Pipelines and Pub/Sub
Pipelines
For executing multiple commands in a single round-trip to the Redis server (which is much more efficient), you can use pipelines.
# Inside a RequestHandler...
@tornado.gen.coroutine
def post(self):
# Create a pipeline
pipe = client.pipeline()
# Queue commands
pipe.set("pipeline_key1", "value1")
pipe.get("pipeline_key1")
pipe.incr("pipeline_counter")
pipe.get("pipeline_counter")
# Execute all commands at once
# The result is a list of the results of each command in order.
results = yield pipe.execute()
# results will be: [True, b'value1', 1, 2]
self.write(f"Pipeline results: {results}")
Pub/Sub (Publish/Subscribe)
This is where tornadoredis truly shines. You can create a long-lived connection to listen for messages on specific channels.
Important: The Pub/Sub connection is a long-running operation that will block the I/O loop if not handled correctly. The standard pattern is to run the subscriber listener in a separate thread.
Example: A Simple Chat Application
This example has two parts: a publisher (the web handler) and a subscriber (a background thread).
import tornado.ioloop
import tornado.web
import tornado.gen
import tornadoredis
import threading
import time
import json
# --- Redis Setup ---
REDIS = tornadoredis.Client()
REDIS.connect()
# --- Subscriber Thread (Background) ---
def subscribe_to_channel():
"""
Listens for messages on the 'chat' channel in a separate thread.
This prevents blocking the main Tornado I/O loop.
"""
print("Subscriber thread started, listening to 'chat' channel...")
subscriber = tornadoredis.Client()
subscriber.connect()
# The subscribe method returns a Future. We need to run it in its own
# IOLoop for the thread.
ioloop = tornado.ioloop.IOLoop()
def message_handler(message):
if message.kind == 'message':
print(f"Received message in thread: {message.body.decode('utf-8')}")
subscriber.subscribe('chat', message_handler)
ioloop.start()
# Start the subscriber thread when the app starts
sub_thread = threading.Thread(target=subscribe_to_channel, daemon=True)
sub_thread.start()
# --- Tornado Application ---
class ChatHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def post(self):
"""
Publishes a message to the 'chat' channel.
"""
message = self.get_argument("message")
# Publish the message. It returns the number of subscribers that received it.
future = REDIS.publish('chat', message)
subscribers_count = yield future
self.write(f"Message '{message}' published to {subscribers_count} subscriber(s).")
class MessageHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
"""
A simple page to send messages.
"""
self.write("""
<html>
<body>
<h1>Send a Chat Message</h1>
<form action="/chat" method="post">
<input type="text" name="message">
<input type="submit" value="Send">
</form>
</body>
</html>
""")
def make_app():
return tornado.web.Application([
(r"/chat", ChatHandler),
(r"/", MessageHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Tornado chat server is running on http://localhost:8888")
print("Open the page in a browser and send a message.")
print("Check the console to see the subscriber receiving the message.")
tornado.ioloop.IOLoop.current().start()
To run this:
- Save as
chat_app.py. - Ensure Redis is running.
- Run the app:
python chat_app.py - Open
http://localhost:8888in your browser, type a message, and hit "Send". - Look at your terminal where the server is running. You will see the message printed by the subscriber thread.
Best Practices and Alternatives
Best Practices
- Use a Connection Pool: Don't create a new
Clientfor every request. Create a singleClientinstance (or a small pool) at the application level and reuse it. TheClientclass itself acts as a connection pool. - Handle Connection Errors: Wrap your Redis calls in
try...exceptblocks or check forNonereturns to gracefully handle connection issues or keys that don't exist. - Use Pipelines for Bulk Operations: Whenever you need to send multiple commands, use a pipeline to reduce network latency.
- Manage Subscribers in Threads: As shown, Pub/Sub listeners should run in a separate thread to avoid blocking the main application.
Modern Alternatives
While tornadoredis is a solid and mature library, the Python async ecosystem has evolved. Here are some modern alternatives:
-
aioredis:
- Description: A very popular library that provides an async/await interface for Redis. It's not specific to Tornado and works with any
asyncio-based framework (like FastAPI, Starlette, or modern Tornado versions). - Pros: Uses the modern
asynciostandard library, excellent performance, active development. - Cons: Requires Python 3.7+.
- Description: A very popular library that provides an async/await interface for Redis. It's not specific to Tornado and works with any
-
redis-py (with asyncio):
- Description: The most widely used Redis client for Python. Recent versions (4.1.0+) have added native support for
asyncio. - Pros: The "official" client, very stable, huge community.
- Cons: The async support is newer than
aioredis.
- Description: The most widely used Redis client for Python. Recent versions (4.1.0+) have added native support for
Example with aioredis (for context):
# Requires: pip install aioredis
import asyncio
import aioredis
async def main():
redis = aioredis.from_url("redis://localhost")
await redis.set("my_key", "Hello aioredis!")
value = await redis.get("my_key")
print(f"Value: {value.decode('utf-8')}")
await redis.close()
# To run with Tornado, you'd use tornado.platform.asyncio.AsyncIOMainLoop
# and then run this coroutine.
Conclusion
tornadoredis is a powerful and well-established tool for integrating Redis into Tornado applications. Its strength lies in its simple, Future-based API and robust, built-in Pub/Sub support that is designed to work with Tornado's I/O loop model. For new projects, you might also consider aioredis for its modern async/await syntax, but tornadoredis remains an excellent and reliable choice, especially for existing Tornado codebases.
