Python SignalR Core full client (transports and encodings).Compatible with azure / serverless functions.Also with automatic reconnect and manually reconnect.
Project description
SignalR core client
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.8
- 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)
Project details
Release history Release notifications | RSS feed
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 signalrcore-1.0.1.tar.gz.
File metadata
- Download URL: signalrcore-1.0.1.tar.gz
- Upload date:
- Size: 38.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e767750eb52ad0a07746686cef8bc68e5832168069b5c43c477be2a68fd1e44c
|
|
| MD5 |
64764859212358914a1408b600988e7e
|
|
| BLAKE2b-256 |
2cb9213c11c93c9c08e864ed9ab6ae66182cba065d88d64e43ea5c34515d3c00
|
File details
Details for the file signalrcore-1.0.1-py3-none-any.whl.
File metadata
- Download URL: signalrcore-1.0.1-py3-none-any.whl
- Upload date:
- Size: 52.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46c967227cc918ae268f46d0e0b9a7e9cd53efaa4690caf5f04e8d6a5b6cd1c5
|
|
| MD5 |
11758783d7c9fabc2b5716ff4f4f5041
|
|
| BLAKE2b-256 |
ae16c7367b9d1a6dd21950471bdb78202ee2541fd8a21ef2a45e0178cc8bd2e0
|