OddSockets Python SDK
Official Python SDK for OddSockets real-time messaging platform
Overview & Features
The OddSockets Python SDK provides a powerful, easy-to-use interface for real-time messaging in Python applications with full async/await support.
Python 3.8+ Support
Compatible with Python 3.8+ with full async/await support and type hints.
Async/Await Native
Built from the ground up with asyncio for high-performance concurrent operations.
Type Safety
Comprehensive type hints for better IDE support and runtime safety.
High Performance
Optimized for low latency with efficient Socket.IO connections and smart routing.
Cost Effective
No per-message pricing, industry-standard 32KB message limits, transparent pricing.
Cross Platform
Works on Windows, macOS, and Linux with consistent behavior across platforms.
Installation
pip install oddsockets
poetry add oddsockets
conda install -c conda-forge oddsockets
Quick Start
Basic Usage
import asyncio
from oddsockets import OddSockets
async def message_handler(data):
print(f"Received: {data}")
async def main():
# Create client
client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
# Get channel
channel = client.channel('my-channel')
# Subscribe to messages
await channel.subscribe(message_handler)
# Publish a message
await channel.publish('Hello, World!')
# Keep alive
await asyncio.sleep(5)
# Clean up
await client.disconnect()
# Run the async function
asyncio.run(main())
Synchronous Wrapper
# For applications that need synchronous API
from oddsockets.sync import OddSockets
def message_handler(data):
print(f"Received: {data}")
# Create client
client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
# Get channel
channel = client.channel('my-channel')
# Subscribe to messages
channel.subscribe(message_handler)
# Publish a message
channel.publish('Hello, World!')
# Clean up
client.disconnect()
Type Hints Usage
import asyncio
from typing import Dict, Any
from oddsockets import OddSockets, Channel
async def typed_message_handler(data: Dict[str, Any]) -> None:
print(f"Received: {data}")
async def main() -> None:
client: OddSockets = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
channel: Channel = client.channel('typed-channel')
await channel.subscribe(typed_message_handler)
asyncio.run(main())
Configuration
Client Options
client = OddSockets({
'api_key': 'your-api-key', # Required: Your OddSockets API key
'user_id': 'user-id', # Optional: User identifier
'auto_connect': True, # Optional: Auto-connect on creation
'options': {
'reconnect_attempts': 5, # Optional: Max reconnection attempts
'heartbeat_interval': 30.0 # Optional: Heartbeat interval (seconds)
}
})
Channel Options
await channel.subscribe(callback, {
'enable_presence': True, # Enable presence tracking
'retain_history': True, # Retain message history
'max_history': 100 # Maximum history messages
})
await channel.publish(message, {
'ttl': 3600, # Time to live (seconds)
'metadata': {'priority': 'high'}, # Additional metadata
})
Examples
Explore comprehensive examples demonstrating the OddSockets Python SDK in action:
Enhanced Features
Beyond core pub/sub, OddSockets ships a Slack-like enhanced surface: typing indicators, reactions, threads, read receipts, presence/status, notifications, DMs, channel management, message editing and search. Send actions with await client.enhanced.* (snake_case methods) and receive the paired broadcasts with client.on('<event>', handler).
Typing & Reactions
import asyncio
from oddsockets import OddSockets
async def main():
client = OddSockets({'api_key': 'ak_live_1234567890abcdef', 'user_id': 'alice'})
channel = client.channel('room-42')
await channel.subscribe()
# Receive-path: broadcasts from other users on the channel
client.on('user_typing', lambda e: print(f"{e['userId']} is typing"))
client.on('reaction_added', lambda e: print(f"{e['userId']} reacted {e['emoji']}"))
# Send-path: enhanced actions over the live socket
await client.enhanced.start_typing('alice', 'room-42')
await client.enhanced.add_reaction(
message_id='msg-1',
channel='room-42',
emoji=':thumbsup:',
user_id='alice',
user_name='Alice'
)
await asyncio.sleep(2)
await client.disconnect()
asyncio.run(main())
Threads
client.on('thread_reply', lambda e: print('New reply:', e))
await client.enhanced.thread_reply(
channel='room-42',
parent_message_id='msg-1',
message='Replying in the thread',
user_id='alice',
user_name='Alice'
)
Enhanced surface
Each area exposes coroutine methods on client.enhanced; the worker broadcasts the paired events which you handle with client.on(...). Query methods (get_*, search_*) await and return the worker response.
- Typing —
start_typing,stop_typing→user_typing,user_stopped_typing - Reactions —
add_reaction,remove_reaction,get_reactions→reaction_added,reaction_removed - Threads —
thread_reply,get_thread,subscribe_thread,follow_thread,mark_thread_read→thread_reply,thread_subscribed,thread_followed,thread_read_updated - Read receipts —
mark_read,mark_all_read,get_unread_counts→user_read,unread_count_updated,all_marked_read - Messages —
edit_message,delete_message,pin_message,unpin_message,get_pinned_messages,search_messages→message_edited,message_deleted,message_pinned,message_unpinned - Presence & status —
set_status,set_custom_status,set_dnd,get_user_presence→user_status_changed,custom_status_updated,dnd_status_changed - Channels —
create_channel,update_channel,archive_channel,invite_to_channel,join_channel,leave_channel→channel_created,channel_updated,user_invited,user_joined_channel,user_left_channel - DMs —
create_dm,send_dm,get_dm_conversations→dm_created,dm_received - Notifications —
subscribe_notifications,get_notifications,mark_notification_read,clear_notifications→notification,notification_read,notifications_cleared - File uploads —
start_file_upload,upload_progress,upload_complete→file_upload_completed,file_upload_progress,file_upload_failed
For any worker event not wrapped above, subscribe with the raw client.on('<event>', handler) API — all enhanced broadcasts are forwarded to the client surface.
Challenges & Leaderboards
Challenges, leaderboards and achievements build on the same live socket. The send side lives on the enhanced surface (client.enhanced.*); request/query methods are coroutines that resolve with the worker's reply, while progress and achievement calls are fire-and-forget. Inbound broadcasts arrive on the client event surface — subscribe with the client's normal client.on(...).
Quick start
# Create a ranked leaderboard (awaits challenge_create_success)
await client.enhanced.create_challenge({'challengeId': 'weekly-score', 'metric': 'points', 'ranked': True})
# Report progress toward it (fire-and-forget)
await client.enhanced.report_progress({'challengeId': 'weekly-score', 'value': 1200})
# Read the top-N plus your own rank (awaits challenge_standings_success)
standings = await client.enhanced.get_standings({'challengeId': 'weekly-score', 'limit': 10})
# Finalize with an outcome (awaits challenge_complete_success)
await client.enhanced.complete_challenge({'challengeId': 'weekly-score', 'outcome': 'completed'})
Methods
Send actions with await client.enhanced.* (snake_case). Request/query methods await the worker's ack; progress and achievement calls are fire-and-forget and return nothing.
- create_challenge — create a challenge / leaderboard. Ack
challenge_create_success. - report_progress — fire-and-forget metric progress. No ack.
- complete_challenge — finalize with an
outcome. Ackchallenge_complete_success. - unlock_achievement — fire-and-forget; pass
percentComplete(0–100). No ack. - get_standings — request top-N + caller rank (awaited). Ack
challenge_standings_success. - get_achievements — query achievement state (awaited). Ack
achievement_state. - send_challenge_invite — directed invite to another user. Ack
challenge_invite_success. - reply_challenge_invite — accept / decline an invite. Ack
challenge_reply_success. - cancel_challenge_invite — cancel a sent invite. Ack
challenge_invite_cancel_success. - get_challenge_invites — list pending invites (awaited). Ack
challenge_invites.
Outcome vocabulary
The outcome passed to complete_challenge is one of:
completed— win (rank 1)failed— losstied— drawconceded— resign / concedeexpired— timed out
Progressive achievements
unlock_achievement always emits the wire event achievement_unlock; the worker is authoritative and derives the outbound broadcast from percentComplete: a value < 100 broadcasts achievement_progress (status in_progress), while >= 100 or an omitted value broadcasts achievement_unlock (status unlocked). Do not emit achievement_progress yourself.
Inbound events
Subscribe to these with the client's normal client.on('<event>', handler).
- Room broadcasts —
challenge_progress,leaderboard_rank_change,challenge_complete,achievement_unlock,achievement_progress - Directed (per-user) —
challenge_invited,challenge_reply_received,challenge_invite_cancelled
Performance & Compatibility
OddSockets Python SDK delivers superior performance with broad compatibility:
Python Support
- Python 3.8+ (2019)
- Python 3.9+ (2020)
- Python 3.10+ (2021)
- Python 3.11+ (2022)
- Python 3.12+ (2023)
Platform Support
- Windows 10+
- macOS 10.15+
- Linux (Ubuntu 18.04+)
- Docker containers
Framework Integrations
The OddSockets Python SDK works seamlessly with all modern Python frameworks. Here are examples showing how to integrate with popular frameworks:
FastAPI
from fastapi import FastAPI, WebSocket
from oddsockets import OddSockets
import asyncio
app = FastAPI()
# Initialize OddSockets client
client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
@app.on_event("startup")
async def startup_event():
# Connect to OddSockets on startup
await client.connect()
@app.on_event("shutdown")
async def shutdown_event():
# Disconnect on shutdown
await client.disconnect()
@app.websocket("/ws/{channel_name}")
async def websocket_endpoint(websocket: WebSocket, channel_name: str):
await websocket.accept()
# Get OddSockets channel
channel = client.channel(channel_name)
# Message handler
async def on_message(data):
await websocket.send_json(data)
# Subscribe to channel
await channel.subscribe(on_message)
try:
while True:
# Receive message from WebSocket
data = await websocket.receive_json()
# Publish to OddSockets channel
await channel.publish(data)
except Exception:
await channel.unsubscribe()
await websocket.close()
Django Channels
from channels.generic.websocket import AsyncWebsocketConsumer
from oddsockets import OddSockets
import json
class ChatConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
self.channel = None
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
# Accept WebSocket connection
await self.accept()
# Get OddSockets channel
self.channel = self.client.channel(f'chat_{self.room_name}')
# Subscribe to messages
await self.channel.subscribe(self.on_oddsockets_message)
async def disconnect(self, close_code):
# Unsubscribe from OddSockets
if self.channel:
await self.channel.unsubscribe()
await self.client.disconnect()
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Publish to OddSockets
await self.channel.publish({
'message': message,
'user': self.scope['user'].username
})
async def on_oddsockets_message(self, data):
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': data['message'],
'user': data['user']
}))
Flask-SocketIO
from flask import Flask
from flask_socketio import SocketIO, emit, join_room, leave_room
from oddsockets import OddSockets
import asyncio
import threading
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")
# Initialize OddSockets client
client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
# Run async client in separate thread
def run_async_client():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(client.connect())
loop.run_forever()
# Start async client thread
client_thread = threading.Thread(target=run_async_client, daemon=True)
client_thread.start()
@socketio.on('join')
def on_join(data):
room = data['room']
join_room(room)
# Get OddSockets channel
channel = client.channel(f'room_{room}')
# Message handler
def on_message(msg_data):
socketio.emit('message', msg_data, room=room)
# Subscribe to channel (run in async context)
asyncio.run_coroutine_threadsafe(
channel.subscribe(on_message),
client._loop
)
@socketio.on('message')
def handle_message(data):
room = data['room']
message = data['message']
# Get channel and publish
channel = client.channel(f'room_{room}')
asyncio.run_coroutine_threadsafe(
channel.publish(message),
client._loop
)
if __name__ == '__main__':
socketio.run(app, debug=True)
Quart (Async Flask)
from quart import Quart, websocket
from oddsockets import OddSockets
import asyncio
import json
app = Quart(__name__)
# Initialize OddSockets client
client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
@app.before_serving
async def startup():
await client.connect()
@app.after_serving
async def shutdown():
await client.disconnect()
@app.websocket('/ws/')
async def ws(channel_name):
# Get OddSockets channel
channel = client.channel(channel_name)
# Message handler
async def on_message(data):
await websocket.send(json.dumps(data))
# Subscribe to channel
await channel.subscribe(on_message)
try:
while True:
# Receive message from WebSocket
message = await websocket.receive()
data = json.loads(message)
# Publish to OddSockets channel
await channel.publish(data)
except Exception:
await channel.unsubscribe()
if __name__ == '__main__':
app.run()
Tornado
import tornado.ioloop
import tornado.web
import tornado.websocket
from oddsockets import OddSockets
import json
import asyncio
class ChatWebSocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self):
self.client = OddSockets({
'api_key': 'ak_live_1234567890abcdef'
})
self.channel = None
async def open(self, channel_name):
print(f"WebSocket opened for channel: {channel_name}")
# Connect to OddSockets
await self.client.connect()
# Get channel
self.channel = self.client.channel(channel_name)
# Subscribe to messages
await self.channel.subscribe(self.on_oddsockets_message)
async def on_message(self, message):
try:
data = json.loads(message)
# Publish to OddSockets
await self.channel.publish(data)
except json.JSONDecodeError:
await self.write_message({
'error': 'Invalid JSON message'
})
async def on_oddsockets_message(self, data):
# Send message to WebSocket client
await self.write_message(data)
async def on_close(self):
print("WebSocket closed")
if self.channel:
await self.channel.unsubscribe()
await self.client.disconnect()
def check_origin(self, origin):
return True # Allow all origins for demo
def make_app():
return tornado.web.Application([
(r"/ws/([^/]+)", ChatWebSocketHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Server started on http://localhost:8888")
tornado.ioloop.IOLoop.current().start()