A lightweight, framework-agnostic real-time library for Python applications.
Project description
PyStrands
A lightweight, framework-agnostic real-time communication library for Python applications.
Overview
PyStrands provides a client-server architecture for building real-time features. A Go-based WebSocket broker handles client connections, while Python backends process messages via TCP — enabling horizontal scaling and clean separation of concerns.
WebSocket Clients ←→ Go Broker ←TCP→ Python Backends
Features
- Sync & Async clients —
PyStrandsClient(threading) andAsyncPyStrandsClient(asyncio) - Room-based, private, and broadcast messaging
- Connection request handling with auth & routing
- Auto-reconnect with exponential backoff
- Message queuing in the Go broker when backends are down
- Framework-agnostic — works with Flask, FastAPI, Django, or standalone
- 66 tests including real-world production scenarios
Installation
pip install pystrands
Quick Start
Running the Server
python -m pystrands server --ws-port 8080 --tcp-port 8081 --queue-size 1000
The server binary is automatically downloaded for your platform.
Options:
--ws-port— WebSocket port for clients (default: 8080)--tcp-port— TCP port for Python backends (default: 8081)--queue-size— Message buffer when no backends connected (default: 1000, 0 = disabled)
Async Client (Recommended)
import asyncio
from pystrands import AsyncPyStrandsClient
class ChatBackend(AsyncPyStrandsClient):
async def on_connection_request(self, request):
# Auth: check headers, assign room, set metadata
auth = request.headers.get("Authorization", [None])[0]
if auth != "Bearer valid-token":
return False # reject
request.context.room_id = request.url.strip("/")
request.context.metadata = {"role": "user"}
return True # accept
async def on_message(self, message, context):
print(f"[{context.room_id}] {context.client_id}: {message}")
# Echo back to the room
await self.send_room_message(context.room_id, f"echo: {message}")
async def on_disconnect(self, context):
print(f"{context.client_id} left {context.room_id}")
client = ChatBackend(host="localhost", port=8081)
asyncio.run(client.run_forever())
Sync Client
from pystrands import PyStrandsClient
class ChatBackend(PyStrandsClient):
def on_connection_request(self, request):
request.context.room_id = request.url.strip("/")
return True
def on_message(self, message, context):
print(f"[{context.room_id}] {context.client_id}: {message}")
self.send_room_message(context.room_id, f"echo: {message}")
client = ChatBackend(host="localhost", port=8081)
client.run_forever()
API Reference
PyStrandsClient / AsyncPyStrandsClient
# Constructor
client = PyStrandsClient(
host="localhost",
port=8081,
auto_reconnect=True, # reconnect on connection loss
reconnect_delay=1.0, # initial delay (seconds)
max_reconnect_delay=30.0, # max delay cap
reconnect_backoff=2.0, # exponential multiplier
)
Methods
| Method | Description |
|---|---|
connect() |
Connect to the Go broker |
disconnect() |
Cleanly disconnect |
run_forever() |
Block until disconnect |
broadcast_message(message) |
Send to all WebSocket clients |
send_room_message(room_id, message) |
Send to a specific room |
send_private_message(client_id, message) |
Send to a specific client |
Callbacks (override these)
| Callback | When |
|---|---|
on_connection_request(context) |
WebSocket client wants to connect. Return True/False. |
on_new_connection(context) |
Client successfully connected |
on_message(message, context) |
Client sent a message |
on_disconnect(context) |
Client disconnected |
on_error(error, context) |
Error occurred |
Context Classes
Context
client_id— Unique client identifierroom_id— Room the client belongs tometadata— Dict for custom data (auth info, user details, etc.)
ConnectionRequestContext
headers— HTTP headers from the WebSocket upgrade requesturl— URL endpoint the client connected toremote_addr— Client's IP addresscontext— TheContextobject (modify this for routing)accepted— Whether to accept (default:True)
Architecture
┌──────────┐ WebSocket ┌──────────┐ TCP ┌──────────────┐
│ Browser │ ←──────────────→ │ Go Broker │ ←────────────→ │ Python Backend│
│ Client │ │ │ │ (your code) │
└──────────┘ │ Queue: │ └──────────────┘
│ [msg1] │ ┌──────────────┐
│ [msg2] │ ←────────────→ │ Python Backend│
│ [msg3] │ │ (replica 2) │
└──────────┘ └──────────────┘
- Go Broker handles WebSocket connections, routing, and message queuing
- Python Backends connect via TCP, process messages, send responses
- Horizontal scaling — add more Python backends, broker load-balances
- Message queue — if all backends go down, messages buffer in the broker and flush on reconnect
Requirements
- Python >= 3.8
License
MIT — see LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pystrands-1.0.0.tar.gz.
File metadata
- Download URL: pystrands-1.0.0.tar.gz
- Upload date:
- Size: 19.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2488d3bd205c2e7b40e502800918a41a5808dd3b51e9a68c126d315a2f2239a1
|
|
| MD5 |
2d1b014dc952138c10577e91d0938256
|
|
| BLAKE2b-256 |
c29c5473e79e06e7b3f9aa1bbaa4765b06bbf24ac707ad4a91f8e3f057b3e70d
|
Provenance
The following attestation bundles were made for pystrands-1.0.0.tar.gz:
Publisher:
publish.yml on pystrands/pystrands
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pystrands-1.0.0.tar.gz -
Subject digest:
2488d3bd205c2e7b40e502800918a41a5808dd3b51e9a68c126d315a2f2239a1 - Sigstore transparency entry: 1051165740
- Sigstore integration time:
-
Permalink:
pystrands/pystrands@f7d3bf997a41a7362544cf526d78193b0a99b58c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pystrands
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7d3bf997a41a7362544cf526d78193b0a99b58c -
Trigger Event:
release
-
Statement type:
File details
Details for the file pystrands-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pystrands-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
208d7f1dd5e0f1c1c188cdd084cf121c3a820942fedd68a1bee3db9eaaaf4e98
|
|
| MD5 |
2ad145f0914adb6944021d0e31084ef1
|
|
| BLAKE2b-256 |
710f8bc4919339e2ceb1f447c9d78a99701f37ec2a20a4b35560c698dbc5206f
|
Provenance
The following attestation bundles were made for pystrands-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on pystrands/pystrands
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pystrands-1.0.0-py3-none-any.whl -
Subject digest:
208d7f1dd5e0f1c1c188cdd084cf121c3a820942fedd68a1bee3db9eaaaf4e98 - Sigstore transparency entry: 1051165747
- Sigstore integration time:
-
Permalink:
pystrands/pystrands@f7d3bf997a41a7362544cf526d78193b0a99b58c -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pystrands
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7d3bf997a41a7362544cf526d78193b0a99b58c -
Trigger Event:
release
-
Statement type: