Skip to main content

logo alt

SignalR core client

Donate Pypi Downloads Downloads Issues Open issues codecov.io

Python signalr core client library, made by a guy born in Vilalba (Lugo).

About V1.0.0 (aka poluca)

Feature list:

  • All kind of communications with the server (streaming, sending messages)
  • All transports implemented (sse, long polling and web sockets)
  • All encodings (text, binary - msgpack)
  • Authentication
  • Automatic reconnection with different strategies
  • Custom ssl context passthrough (see certificates article)
  • AsyncIO minimal implementation (will be improved on following versions)
  • ...

Upcoming changes

  • AsyncIO transport layer and callbacks
  • Test suite, divide test into integration and unit. Making stubs of clients which enable testing without server
    • Managed solution azure server. For testing purposes only (PRs targeting main branch)
  • Ack/Sequence implementation
  • ...

Links

Development

Software requirements:

  • python >= 3.9
  • virtualenv
  • pip
  • docker
  • docker compose

Test environment has as a requirement a signalr core server, is available in here

Clone repos and install virtual environment:

git clone https://github.com/mandrewcito/signalrcore-containertestservers
cd signalrcore
make dev-install
git clone https://github.com/mandrewcito/signalrcore-containertestservers
cd signalrcore-containertestservers
docker compose up
cd ../signalrcore
make pytest-cov

Have fun :)

A Tiny How To

You can reach a lot of examples in tests folder, raw implementations in playground and fully working examples at the examples folder.

Connect to a server without auth

hub_connection = HubConnectionBuilder()\
    .with_url(server_url)\
    .configure_logging(logging.DEBUG)\
    .with_automatic_reconnect({
        "type": "raw",
        "keep_alive_interval": 10,
        "reconnect_interval": 5,
        "max_attempts": 5
    }).build()

Connect to a server with auth

login_function must provide auth token

hub_connection = HubConnectionBuilder()\
            .with_url(server_url,
            options={
                "access_token_factory": login_function,
                "headers": {
                    "mycustomheader": "mycustomheadervalue"
                }
            })\
            .configure_logging(logging.DEBUG)\
            .with_automatic_reconnect({
                "type": "raw",
                "keep_alive_interval": 10,
                "reconnect_interval": 5,
                "max_attempts": 5
            }).build()

Unauthorized errors

A login function must provide an error controller if authorization fails. When connection starts, if authorization fails exception will be propagated.

    def login(self):
        response = requests.post(
            self.login_url,
            json={
                "username": self.email,
                "password": self.password
                },verify=False)
        if response.status_code == 200:
            return response.json()["token"]
        raise requests.exceptions.ConnectionError()

    hub_connection.start()   # this code will raise  requests.exceptions.ConnectionError() if auth fails

Configure logging

HubConnectionBuilder()\
    .with_url(server_url,
    .configure_logging(logging.DEBUG)
    ...

Configure socket trace

HubConnectionBuilder()\
    .with_url(server_url,
    .configure_logging(logging.DEBUG, socket_trace=True) 
    ... 

Configure your own handler

 import logging
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
hub_connection = HubConnectionBuilder()\
    .with_url(server_url, options={"verify_ssl": False}) \
    .configure_logging(logging.DEBUG, socket_trace=True, handler=handler)
    ...

Configuring reconnection

After reaching max_attempts an exception will be thrown and on_disconnect event will be fired.

hub_connection = HubConnectionBuilder()\
    .with_url(server_url)\
    ...
    .build()

Configuring additional headers

hub_connection = HubConnectionBuilder()\
            .with_url(server_url,
            options={
                "headers": {
                    "mycustomheader": "mycustomheadervalue"
                }
            })
            ...
            .build()

Configuring additional querystring parameters

server_url ="http.... /?myQueryStringParam=134&foo=bar"
connection = HubConnectionBuilder()\
            .with_url(server_url,
            options={
            })\
            .build()

Configuring skip negotiation

hub_connection = HubConnectionBuilder() \
        .with_url("ws://"+server_url, options={
            "verify_ssl": False,
            "skip_negotiation": False,
            "headers": {
            }
        }) \
        .configure_logging(logging.DEBUG, socket_trace=True, handler=handler) \
        .build()

Configuring ping(keep alive)

keep_alive_interval sets the seconds of ping message

hub_connection = HubConnectionBuilder()\
    .with_url(server_url)\
    .configure_logging(logging.DEBUG)\
    .with_automatic_reconnect({
        "type": "raw",
        "keep_alive_interval": 10,
        "reconnect_interval": 5,
        "max_attempts": 5
    }).build()

Configuring logging

hub_connection = HubConnectionBuilder()\
    .with_url(server_url)\
    .configure_logging(logging.DEBUG)\
    .with_automatic_reconnect({
        "type": "raw",
        "keep_alive_interval": 10,
        "reconnect_interval": 5,
        "max_attempts": 5
    }).build()

Configure messagepack

from signalrcore.protocol.messagepack_protocol import MessagePackHubProtocol

HubConnectionBuilder()\
            .with_url(self.server_url, options={"verify_ssl":False})\
                ... 
            .with_hub_protocol(MessagePackHubProtocol())\
                ...
            .build()

Configure custom ssl context

You can add a custom ssl context to all requests and sockets

MY_CA_FILE_PATH = "ca.crt"
context = ssl.create_default_context(
    cafile=MY_CA_FILE_PATH
)

options = {
    "ssl_context": context
}

builder = HubConnectionBuilder()\
    .with_url(self.server_url, options=options)\
    .configure_logging(
        logging.INFO,
        socket_trace=True)
 
connection = builder.build()

More info about certificates here

Websockets

Will be used as transport layer by default, you do not need to specify it.

HubConnectionBuilder()\
    .with_url(server_http_url, options={
        ...
        "transport": HttpTransportType.web_sockets
        })\
    .configure_logging(logging.ERROR)\
    .build()

Server sent events

HubConnectionBuilder()\
    .with_url(server_http_url, options={
        ...
        "transport": HttpTransportType.server_sent_events
        })\
    .configure_logging(logging.ERROR)\
    .build()

Long polling

HubConnectionBuilder()\
    .with_url(server_http_url, options={
        ...
        "transport": HttpTransportType.long_polling
        })\
    .configure_logging(logging.ERROR)\
    .build()

Events

On Connect / On Disconnect

on_open - fires when connection is opened and ready to send messages on_close - fires when connection is closed

hub_connection.on_open(lambda: print("connection opened and handshake received ready to send messages"))
hub_connection.on_close(lambda: print("connection closed"))

On Hub Error (Hub Exceptions ...)

hub_connection.on_error(lambda data: print(f"An exception was thrown closed{data.error}"))

Register an operation

ReceiveMessage - signalr method print - function that has as parameters args of signalr method

hub_connection.on("ReceiveMessage", print)

Sending messages

SendMessage - signalr method username, message - parameters of signalrmethod

    hub_connection.send("SendMessage", [username, message])

Sending messages with callback

SendMessage - signalr method username, message - parameters of signalrmethod

    send_callback_received = threading.Lock()
    send_callback_received.acquire()
    self.connection.send(
        "SendMessage", # Method
        [self.username, self.message], # Params
        lambda m: send_callback_received.release()) # Callback
    if not send_callback_received.acquire(timeout=1):
        raise ValueError("CALLBACK NOT RECEIVED")

Requesting streaming (Server to client)

hub_connection.stream(
            "Counter",
            [len(self.items), 500]).subscribe({
                "next": self.on_next,
                "complete": self.on_complete,
                "error": self.on_error
            })

Client side Streaming

from signalrcore.subject import  Subject

subject = Subject()

# Start Streaming
hub_connection.send("UploadStream", subject)

# Each iteration
subject.next(str(iteration))

# End streaming
subject.complete()

AIO

Create connection

from signalrcore.aio.aio_hub_connection_builder import AIOHubConnectionBuilder

builder = AIOHubConnectionBuilder()\
    .with_url(self.server_url, options=options)\
    .configure_logging(
        self.get_log_level(),
        socket_trace=self.is_debug())\
    .with_automatic_reconnect({
        "type": "raw",
        "keep_alive_interval": 10,
        "reconnect_interval": 5,
        "max_attempts": 5
    })

hub = builder.build()

await hub.start()

await connection.send("SendMessage", [username, message])

await connection.stop()

Full Examples

Examples will be available here It were developed using package from aspnet core - SignalRChat

Chat example

A mini example could be something like this:

import logging
import sys
from signalrcore.hub_connection_builder import HubConnectionBuilder


def input_with_default(input_text, default_value):
    value = input(input_text.format(default_value))
    return default_value if value is None or value.strip() == "" else value


server_url = input_with_default('Enter your server url(default: {0}): ', "wss://localhost:44376/chatHub")
username = input_with_default('Enter your username (default: {0}): ', "mandrewcito")
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
hub_connection = HubConnectionBuilder()\
    .with_url(server_url, options={"verify_ssl": False}) \
    .configure_logging(logging.DEBUG, socket_trace=True, handler=handler) \
    .with_automatic_reconnect({
            "type": "interval",
            "keep_alive_interval": 10,
            "intervals": [1, 3, 5, 6, 7, 87, 3]
        }).build()

hub_connection.on_open(lambda: print("connection opened and handshake received ready to send messages"))
hub_connection.on_close(lambda: print("connection closed"))

hub_connection.on("ReceiveMessage", print)
hub_connection.start()
message = None

# Do login

while message != "exit()":
    message = input(">> ")
    if message is not None and message != "" and message != "exit()":
        hub_connection.send("SendMessage", [username, message])

hub_connection.stop()

sys.exit(0)

Metadata

Release files for signalrcore 1.0.2

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

Source distribution (sdist)

Source distribution for signalrcore 1.0.2
File Size Uploaded
signalrcore-1.0.2.tar.gz 40.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for signalrcore 1.0.2
File Interpreter ABI Platform
signalrcore-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 96.0 kB

Release files / signalrcore-1.0.2.tar.gz

Download URL signalrcore-1.0.2.tar.gz
Size 40.4 kB
Tags Source
SHA-256 checksum
How to use checksums
8d3bdcb6c66de58894d0dce43f16d2cd95188cd426342b82d5d52c72b8856927
BLAKE2b-256 checksum
How to use checksums
1283aadae49949f82fbc38e27a0776ab16f14a8dacd8942c19dfbd6507b4e8b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / signalrcore-1.0.2-py3-none-any.whl

Download URL signalrcore-1.0.2-py3-none-any.whl
Size 55.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
486e3b18131191f5f5284184f43f2d5142479f8d22b42bff982d52ef7f81025c
BLAKE2b-256 checksum
How to use checksums
5257e0ac3c85e1e91294db48aa816f5c971989c00a5880893bfe52356e6acfbb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.71

2 release files

0.9.7

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 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