Skip to main content

pyrsolace

License: GPL v3 Python PyPI

Python bindings for rsolace with full async/await support and proper GIL release.

✨ Key Features

  • 🐍 Full asyncio Support: Native async/await patterns with asyncio
  • 🔓 GIL Release: Properly releases GIL during blocking operations
  • 🔄 Sync + Async: Choose the best pattern for your use case
  • 📦 Complete API: Pub/Sub, Request/Reply, Message Caching, Events
  • ⚡ High Performance: Zero-copy message handling from Rust
  • 🛡️ Type Safe: Complete type hints with .pyi files

🚀 Installation

# Using pip
pip install pyrsolace

# Using uv (recommended)
uv add pyrsolace

🔥 Quick Start

Async Example (NEW!)

import asyncio
import pyrsolace

async def main():
    # Initialize client
    client = pyrsolace.Client()
    
    # Connect to Solace broker
    connected = client.connect(
        host="tcp://localhost:55555",
        vpn="default",
        username="admin",
        password="admin",
        compression_level=5
    )
    
    if not connected:
        print("Failed to connect")
        return
    
    # Subscribe to topics
    client.subscribe_ext("test/topic/*", pyrsolace.SubscribeFlag.RequestConfirm)
    
    # Get async receivers
    async_msg_receiver = client.get_async_msg_receiver()
    async_event_receiver = client.get_async_event_receiver()
    
    # Handle messages asynchronously
    async def message_handler():
        while True:
            try:
                msg = await async_msg_receiver.recv()
                print(f"Async received: {msg.topic} - {msg.data}")
            except Exception as e:
                print(f"Message handler error: {e}")
                break
    
    # Handle events asynchronously
    async def event_handler():
        while True:
            try:
                event = await async_event_receiver.recv()
                print(f"Event: {event.session_event}")
            except Exception as e:
                print(f"Event handler error: {e}")
                break
    
    # Start async handlers
    msg_task = asyncio.create_task(message_handler())
    event_task = asyncio.create_task(event_handler())
    
    # Send some test messages
    for i in range(5):
        msg = pyrsolace.Msg(
            topic="test/topic/async",
            data=f"Async message {i}".encode()
        )
        client.send_msg(msg)
        await asyncio.sleep(1)
    
    # Send async request (true async, no blocking!)
    try:
        request_msg = pyrsolace.Msg(
            topic="test/request", 
            data=b"Hello async world!", 
            corr_id="req123"
        )
        
        response = await client.send_request_async(request_msg)
        print(f"Async response: {response.data}")
    except Exception as e:
        print(f"Async request failed: {e}")
    
    # Cleanup
    await asyncio.sleep(2)
    msg_task.cancel()
    event_task.cancel()
    client.disconnect()

if __name__ == "__main__":
    asyncio.run(main())

Sync Example (Enhanced with GIL Release)

import pyrsolace
import threading
import time

def message_handler(receiver, name):
    """Handle messages synchronously with proper GIL release."""
    while True:
        try:
            # This properly releases GIL, allowing other threads to run
            msg = receiver.recv()
            print(f"{name} received: {msg.topic} - {msg.data}")
        except Exception as e:
            print(f"{name} handler error: {e}")
            break

def main():
    client = pyrsolace.Client()
    
    # Connect
    connected = client.connect(
        host="tcp://localhost:55555",
        vpn="default",
        username="admin", 
        password="admin"
    )
    
    if not connected:
        print("Failed to connect")
        return
    
    # Subscribe
    client.subscribe("test/topic/*")
    
    # Get receivers
    msg_receiver = client.get_msg_receiver()
    event_receiver = client.get_event_receiver()
    
    # Start background threads (GIL is properly released)
    msg_thread = threading.Thread(
        target=message_handler, 
        args=(msg_receiver, "Messages")
    )
    event_thread = threading.Thread(
        target=message_handler, 
        args=(event_receiver, "Events")
    )
    
    msg_thread.start()
    event_thread.start()
    
    # Send messages
    for i in range(5):
        msg = pyrsolace.Msg(
            topic="test/topic/sync",
            data=f"Sync message {i}".encode()
        )
        client.send_msg(msg)
        time.sleep(1)
    
    client.disconnect()

if __name__ == "__main__":
    main()

Callback-based Example

import pyrsolace
import time

def on_message(msg):
    """Message callback function."""
    print(f"Callback received: {msg.topic} - {msg.data}")

def on_event(event):
    """Event callback function."""
    print(f"Event: {event.session_event} - {event.info}")

def main():
    client = pyrsolace.Client()
    
    # Set callbacks
    client.set_msg_callback(on_message)
    client.set_event_callback(on_event)
    
    # Connect and subscribe
    client.connect(
        host="tcp://localhost:55555",
        vpn="default",
        username="admin",
        password="admin"
    )
    
    client.subscribe("test/topic/*")
    
    # Send messages
    for i in range(5):
        msg = pyrsolace.Msg(
            topic="test/topic/callback",
            data=f"Callback message {i}".encode()
        )
        client.send_msg(msg)
        time.sleep(1)
    
    client.disconnect()

if __name__ == "__main__":
    main()

🔄 Sync vs Async

Pattern Best For Usage GIL Behavior
Callbacks Simple event handling client.set_msg_callback(fn) Released during callback
Sync Receivers Threading, blocking I/O receiver.recv() Released during recv
Async Receivers High concurrency await async_receiver.recv() N/A (async)

Migration from Sync to Async

# Before: Sync only
receiver = client.get_msg_receiver()
msg = receiver.recv()  # Blocks thread (but releases GIL)

# After: True async
async_receiver = client.get_async_msg_receiver()
msg = await async_receiver.recv()  # Non-blocking, async

# Mixed: Use both in same application
sync_receiver = client.get_msg_receiver()     # For background threads
async_receiver = client.get_async_msg_receiver()  # For async tasks

📋 API Reference

Client Class

class Client:
    def connect(self, host: str, vpn: str, username: str, password: str, ...) -> bool
    def disconnect(self) -> None
    def subscribe(self, topic: str) -> ReturnCode
    def subscribe_ext(self, topic: str, flag: SubscribeFlag) -> ReturnCode
    
    # Message sending
    def send_msg(self, msg: Msg) -> ReturnCode
    def send_reply(self, rx_msg: Msg, reply_msg: Msg) -> ReturnCode
    
    # Sync receivers (with GIL release)
    def get_msg_receiver(self) -> MsgReceiver
    def get_request_receiver(self) -> MsgReceiver
    def get_p2p_receiver(self) -> MsgReceiver
    def get_event_receiver(self) -> EventReceiver
    
    # Async receivers (NEW!)
    def get_async_msg_receiver(self) -> AsyncMsgReceiver
    def get_async_request_receiver(self) -> AsyncMsgReceiver
    def get_async_p2p_receiver(self) -> AsyncMsgReceiver
    def get_async_event_receiver(self) -> AsyncEventReceiver
    
    # Request/Reply
    def send_request(self, msg: Msg, timeout: int) -> MsgReceiver
    async def send_request_async(self, msg: Msg) -> Msg  # NEW!
    
    # Callbacks
    def set_msg_callback(self, callback: Callable[[Msg], None]) -> None
    def set_event_callback(self, callback: Callable[[Event], None]) -> None

Message Class

class Msg:
    def __init__(self, topic: str = None, data: bytes = None, ...) -> None
    
    # Properties
    topic: str
    data: bytes
    corr_id: str
    reply_topic: str
    delivery_mode: DeliveryMode
    
    # Methods
    def set_user_prop(self, key: str, value: str) -> None
    def get_user_prop(self, key: str) -> str
    def dump(self) -> str

Receiver Classes

class MsgReceiver:
    def recv(self) -> Msg  # Releases GIL

class AsyncMsgReceiver:
    async def recv(self) -> Msg  # True async

class EventReceiver:
    def recv(self) -> Event  # Releases GIL

class AsyncEventReceiver:
    async def recv(self) -> Event  # True async

🛠️ Development

Building from Source

# Clone repository
git clone https://github.com/Yvictor/rsolace.git
cd rsolace/pyrsolace

# Using uv (recommended)
uv build
uv pip install -e .

# Using maturin
pip install maturin
maturin develop --release

Running Tests

# Run tests
uv run pytest tests/

# Run specific tests
uv run pytest tests/test_msg.py -v

🔧 Configuration

Connection Parameters

client.connect(
    host="tcp://broker:55555",          # Broker URL
    vpn="vpn_name",                     # VPN name
    username="user",                    # Username
    password="pass",                    # Password
    client_name="my_client",            # Client identifier
    compression_level=5,                # 1-9 (higher = more compression)
    connect_timeout_ms=30000,           # Connection timeout
    connect_retries=3,                  # Retry attempts
    reconnect_retries=10,               # Auto-reconnect attempts
    keep_alive_ms=3000,                 # Keep-alive interval
    reapply_subscriptions=True,         # Restore subs on reconnect
    generate_sender_id=True,            # Add sender ID to messages
    generate_timestamps=True,           # Add timestamps
)

Message Properties

msg = pyrsolace.Msg(
    topic="my/topic",
    data=b"payload",
    corr_id="request-123",
    reply_topic="reply/topic",
    delivery_mode=pyrsolace.DeliveryMode.Persistent
)

# User properties
msg.set_user_prop("priority", "high")
msg.set_user_prop("version", "1.0")

🎯 Advanced Examples

Async Producer-Consumer Pattern

import asyncio
from asyncio import Queue

async def producer(client, queue):
    """Produce messages to queue."""
    for i in range(100):
        msg = pyrsolace.Msg(
            topic=f"data/stream/{i % 10}",
            data=f"Data packet {i}".encode()
        )
        await queue.put(msg)
        await asyncio.sleep(0.1)

async def consumer(client, queue):
    """Consume messages from queue."""
    while True:
        msg = await queue.get()
        client.send_msg(msg)
        queue.task_done()

async def message_processor(client):
    """Process incoming messages."""
    receiver = client.get_async_msg_receiver()
    while True:
        msg = await receiver.recv()
        # Process message asynchronously
        await process_message(msg)

async def main():
    client = pyrsolace.Client()
    client.connect(...)
    
    queue = Queue(maxsize=100)
    
    # Start producer, consumer, and processor
    await asyncio.gather(
        producer(client, queue),
        consumer(client, queue),
        message_processor(client)
    )

Request/Reply Service

async def request_handler(client):
    """Handle incoming requests asynchronously."""
    receiver = client.get_async_request_receiver()
    
    while True:
        request = await receiver.recv()
        
        # Process request
        response_data = await process_request(request.data)
        
        # Send reply
        reply = pyrsolace.Msg(
            topic=request.reply_topic,
            data=response_data,
            corr_id=request.corr_id
        )
        client.send_reply(request, reply)

🚀 Performance Tips

Async Best Practices

  1. Use Semaphores: Limit concurrent operations
semaphore = asyncio.Semaphore(10)
async with semaphore:
    await process_message(msg)
  1. Batch Operations: Group related operations
messages = []
async for msg in message_stream():
    messages.append(msg)
    if len(messages) >= 100:
        await process_batch(messages)
        messages.clear()
  1. Graceful Shutdown: Cancel tasks properly
try:
    await main_task
except asyncio.CancelledError:
    await cleanup()

📚 Documentation

  • Main Project: See root README for complete documentation
  • Rust Library: Check rsolace for Rust-specific features
  • Type Hints: Complete API in pyrsolace.pyi
  • Examples: More examples in tests/ directory

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Run tests: uv run pytest
  5. Submit a pull request

📄 License

GPL-3.0-only License - see LICENSE for details.


Powered by rsolace ⚡ - High-performance Rust Solace bindings

Metadata

Release files for pyrsolace 0.3.14

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for pyrsolace 0.3.14
File
pyrsolace-0.3.14-cp37-abi3-win_amd64.whl CPython 3.7 abi3 Windows x86-64 Details
pyrsolace-0.3.14-cp37-abi3-win32.whl CPython 3.7 abi3 Windows x86-32 Details
pyrsolace-0.3.14-cp37-abi3-manylinux_2_34_aarch64.whl CPython 3.7 abi3 Linux glibc 2.34+ ARM64 Details
pyrsolace-0.3.14-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.7 abi3 Linux glibc 2.17+ x86-64 Details
pyrsolace-0.3.14-cp37-abi3-macosx_11_0_arm64.whl CPython 3.7 abi3 macOS 11.0+ ARM64 Details
pyrsolace-0.3.14-cp37-abi3-macosx_10_12_x86_64.whl CPython 3.7 abi3 macOS 10.12+ x86-64 Details

Total release size: 10.7 MB

Release files / pyrsolace-0.3.14-cp37-abi3-win_amd64.whl

Download URL pyrsolace-0.3.14-cp37-abi3-win_amd64.whl
Size 2.1 MB
Tags CPython 3.7 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
569ddbbb296c6809cf9be1385bdb98928f5fe09775a32594c61879ffcd71114b
BLAKE2b-256 checksum
How to use checksums
8657e81da6940f41c9a313e9a590270e435fa751eafa6df96fec129e659284ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / pyrsolace-0.3.14-cp37-abi3-win32.whl

Download URL pyrsolace-0.3.14-cp37-abi3-win32.whl
Size 1.7 MB
Tags CPython 3.7 Windows x86-32 abi3
SHA-256 checksum
How to use checksums
ba42b51b1ff458d5b183aa0df82cee5031eb065220c1262d371dcf4c1c7593b7
BLAKE2b-256 checksum
How to use checksums
e5e9ad926d9d6648b76ea51a8fac6d1c9b1b4466fe4948ab46a542897894f5d4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / pyrsolace-0.3.14-cp37-abi3-manylinux_2_34_aarch64.whl

Download URL pyrsolace-0.3.14-cp37-abi3-manylinux_2_34_aarch64.whl
Size 1.4 MB
Tags CPython 3.7 Linux glibc 2.34+ ARM64 abi3
SHA-256 checksum
How to use checksums
7550dafd564eb925c3a52a27ae805330aae4b72c29225bb3bd8851c1c3fd7830
BLAKE2b-256 checksum
How to use checksums
c71589238a005c702757a3efad59306d289a88ca5d673c73d6b921f4b4b7305f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / pyrsolace-0.3.14-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pyrsolace-0.3.14-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.2 MB
Tags CPython 3.7 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
81d871aa980a707edc9a884e062225e268ff079b526903bdd82024edb21accf4
BLAKE2b-256 checksum
How to use checksums
af2107b1ea34b7fbd29c8d3228b63a0cf6270ef68eb5e90e623cd4ff99b77ca2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / pyrsolace-0.3.14-cp37-abi3-macosx_11_0_arm64.whl

Download URL pyrsolace-0.3.14-cp37-abi3-macosx_11_0_arm64.whl
Size 2.1 MB
Tags CPython 3.7 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
e3cfc85f4108297dcad175832c2f958570bf3916c63989f19c095160cd16284e
BLAKE2b-256 checksum
How to use checksums
d5fcd1bf7d468fad8341b5d2739cb5dd2a864dfead86a647b74f0e6668dd99d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release files / pyrsolace-0.3.14-cp37-abi3-macosx_10_12_x86_64.whl

Download URL pyrsolace-0.3.14-cp37-abi3-macosx_10_12_x86_64.whl
Size 2.2 MB
Tags CPython 3.7 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
5b31d9730a726938ecb82178938640f98bc460d837c9d3b572ed800b5dcf7a44
BLAKE2b-256 checksum
How to use checksums
1c0026b739405a8f5bc84063568575410405ecfa7f9471015337245b1007f3e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Release history Release notifications | RSS feed

This release

0.3.14 This release

6 release files

0.3.13

6 release files

0.3.12

6 release files

0.3.11

6 release files

0.3.10

6 release files

0.3.9

6 release files

0.3.8

6 release files

0.3.7

6 release files

0.3.5

6 release files

0.3.4

6 release files

0.3.3

6 release files

0.3.2

6 release files

0.3.0

6 release files

0.1.3

6 release files

0.1.2

6 release files

0.1.1

6 release files

0.1.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page