Skip to main content
Python Software Foundation 20th Year Anniversary Fundraiser  Donate today!

A Redis Pub/Sub multiplexer.

Project description


RedisMPX is a Redis Pub/Sub multiplexer library written in multiple languages and live coded on Twitch.


When bridging multiple application instances through Redis Pub/Sub it's easy to end up needing support for multiplexing. RedisMPX streamlines this process in a consistent way across multiple languages by offering a consistent set of features that cover the most common use cases.

The library works under the assumption that you are going to create separate subscriptions for each client connected to your service (e.g. WebSockets clients):

  • ChannelSubscription allows you to add and remove individual Redis PubSub channels similarly to how a multi-room chat application would need to.
  • PatternSubscription allows you to subscribe to a single Redis Pub/Sub pattern.
  • PromiseSubscription allows you to create a networked promise system.


Requires Python 3.7+, based on aio-libs/aioredis, an AsyncIO Redis client.

pip install redismpx


  • Simple channel subscriptions
  • Pattern subscriptions
  • Networked promise system
  • Automatic reconnection with exponetial backoff + jitter



from redismpx import Multiplexer

# Pass to Multiplexer the same connection options that
# aioredis.create_connection() would accept.
mpx = Multiplexer('redis://localhost') 

# on_message is a callback (can be async)
# that accepts a channel name and a message.
async def my_on_message(channel: bytes, message: bytes):
    await websocket.send(f"ch: {channel} msg: {message}")

# on_disconnect is a callback (can be async) 
# that accepts the error that caused the disconnection.
def my_on_disconnect(error: Exception):
    print("oh no!")

# on_activation is a callback (can be async)
# that accepts the name of the channel or pattern
# whose subscription just became active (depends
# on whether it's attached to a ChannelSubscription
# or a PatternSubscription).
def my_on_activation(name: bytes):
    print("activated:", name)

# you can also pass None in place of `on_disconnect`
# and `on_activation` if you're not interested in 
# reacting to those events.

# Use `mpx` to create new subscriptions.
channel_sub = mpx.new_channel_subcription(
    my_on_message, my_on_disconnect, None) 
pattern_sub = mpx.new_pattern_subscription("hello-*", 
    my_on_message, None, my_on_activation)
promise_sub = mpx.new_promise_subscription("hello-")


# Create the ChannelSubscription.
channel_sub = mpx.new_channel_subcription(
    lambda ch, msg: print(f"Message @ {ch}: {msg}"),
    lambda e: print(f"Network Error: {type(e)}: {e}"),
    lambda s: print(f"Subscription now active: {s}")) 

# Add channels

# Remove a channel

# Close the subscription


# Create the PatternSubscription.
# Note how it also requires the pattern.
pattern_sub = mpx.new_pattern_subcription(
    lambda ch, msg: print(f"Message @ {ch}: {msg}"),
    lambda e: print(f"Network Error: {type(e)}: {e}"),
    lambda s: print(f"Subscription now active: {s}")) 

# PatternSubscriptions can only be closed


# Create the subscription. 
# Note how it doesn't accept any callback.
promise_sub = mpx.new_promise_subscription("hello-")

# When first created (and after a network error that causes 
# a reconnection), a PromiseSubscription is not immediately 
# able to create new promises as it first needs the underlying
# PatternSubscription to become active. This async function
# waits for that event.
await promise_sub.wait_for_activation()

# Create a new promise. It might fail if the subscription is 
# not active.
    promise = promise_sub.new_promise("world", 10)
    # The provided suffix will be composed with the subscription's
    # prefix to create the final Redis Pub/Sub channel from which
    # the message is expected to come. In this example, to fullfill
    # the promise you could send, using redis-cli (or any other client):
    #   > PUBLISH hello-world "your-promise-payload"
except redismpx.InactiveSubscription:
    # Wait and then Retry? Return an error to the user? Up to you.

# A way of creating a promise that ensures no InactiveSubscription error 
# will trigger. Note that this method needs to be awaited.
# The timer will start only after the promise has been created.
promise = await promise_sub.wait_for_new_promise("world", 10)

# A Promise represents a timed, uninterrupted, single-message 
# subscription to a Redis Pub/Sub channel. If network 
# connectivity gets lost, thus causing an interruption, 
# the Promise will be failed (unless already fullfilled). 

# Resolve the promise
    result = await promise
    print(result) # prints b'your-promise-payload'
except asyncio.TimeoutError:
    # The promise timed out.
except asyncio.CancelledError:
    # The promise was canceled. This happens when
    # a reconnection event triggers while the promise
    # is not yet resolved. 

# Close the subscription (will automatically cancel all
# outstanding promises and unlock all `wait_for_*` waiters).

WebSocket Example

This is a more realistic example of how to use RedisMPX.


This code is also available in examples/


import asyncio
import aioredis
from starlette.applications import Starlette
from starlette.routing import WebSocketRoute
from redismpx import Multiplexer

# Pass to Multiplexer the same connection options that
# aioredis.create_redis() would accept.
mpx = Multiplexer('redis://localhost')

pub_conn = None

async def handle_ws(ws):
    global pub_conn
    await ws.accept()

    # Create a separate connection for publishing messages:
    if pub_conn is None:
        pub_conn = await aioredis.create_redis('redis://localhost')

    # Define a callback that sends messages to this websocket
    async def on_message(channel: bytes, message: bytes):
        await ws.send_text(f"ch: [{channel.decode()}] msg: [{message.decode()}]\n")

    # Create a subscription for this websocket
    sub = mpx.new_channel_subscription(on_message, 
        lambda e: print(f"Network Error: {type(e)}: {e}"),
        lambda s: print(f"Subscription now active: {s}"))

    # Keep reading from the websocket, use the messages sent by the user
    # to add and remove channels from the subscription.
    # Use +channel to join a channel, -channel to leave.
    # Sending !channel will send the next message to said channel.
    await ws.send_text('# Use +channel to join a channel, -channel to leave.')
    await ws.send_text('# Sending !channel will send the next message to said channel.')
    await ws.send_text('# To see a message sent to a given channel, you must have joined it beforehand.')

    while True:
        msg = None
            msg = await ws.receive_text()
            print('ws disconnected')
        prefix, chan = msg[0], msg[1:]
        if prefix == "+": 
        elif prefix == "-":
        elif prefix == '!':
            # Send the next message to the given channel
            await pub_conn.publish(chan, await ws.receive_text())

app = Starlette(debug=True, routes=[
    WebSocketRoute('/ws', endpoint=handle_ws),


pip install redismpx aioredis starlette uvcorn

Launching the example

$ uvicorn websocket:app

Interacting with the example

The application works like a simple WebSocket chat application that expects commands from the user.

  • Sending +hello will subscribe you to channel hello, while -hello will do the opposite.
  • Sending !hello will broadcast the next message you send to hello.
  • You can use whatever channel name you like.

To send those commands you can use a browser:

// To create a websocket connection to localhost
// you will need to deal with the browser's security
// policies. Opening a file on the local filesystem
// and typing these commands in the console should
// do the trick.
let ws = new WebSocket("ws://localhost:8000/ws")
ws.onmessage = (x) => console.log("message:",
ws.send("hello world!")

A more handy way of interacting with websockets are command-line clients:

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for redismpx, version 0.5.2
Filename, size File type Python version Upload date Hashes
Filename, size redismpx-0.5.2-py3-none-any.whl (14.3 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size redismpx-0.5.2.tar.gz (13.5 kB) File type Source Python version None Upload date Hashes View

Supported by

AWS AWS Cloud computing Datadog Datadog Monitoring DigiCert DigiCert EV certificate Facebook / Instagram Facebook / Instagram PSF Sponsor Fastly Fastly CDN Google Google Object Storage and Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Salesforce Salesforce PSF Sponsor Sentry Sentry Error logging StatusPage StatusPage Status page