# SignalR core client
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/mandrewcito/1)
![Pypi](https://img.shields.io/pypi/v/signalrcore.svg)
[![Downloads](https://pepy.tech/badge/signalrcore/month)](https://pepy.tech/project/signalrcore/month)
[![Downloads](https://pepy.tech/badge/signalrcore)](https://pepy.tech/project/signalrcore)
![Issues](https://img.shields.io/github/issues/mandrewcito/signalrcore.svg)
![Open issues](https://img.shields.io/github/issues-raw/mandrewcito/signalrcore.svg)
![codecov.io](https://codecov.io/github/mandrewcito/signalrcore/coverage.svg?branch=master)
![logo alt](https://raw.githubusercontent.com/mandrewcito/signalrcore/master/docs/img/logo_temp.128.svg.png)
# Links
* [Dev to posts with library examples and implementation](https://dev.to/mandrewcito/singlar-core-python-client-58e7)
* [Pypi](https://pypi.org/project/signalrcore/)
* [Wiki - This Doc](https://mandrewcito.github.io/signalrcore/)
# Develop
Test server will be avaiable in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.
```bash
git clone https://github.com/mandrewcito/signalrcore-containertestservers
cd signalrcore-containertestservers
docker-compose up
cd ../signalrcore
make tests
```
## Known Issues
Issues related with closing sockets are inherited from the websocket-client library. Due to these problems i can't update the library to versions higher than websocket-client 0.54.0.
I'm working to solve it but for now its patched (Error number 1. Raises an exception, and then exception is treated for prevent errors).
If I update the websocket library I fall into error number 2, on local machine I can't reproduce it but travis builds fail (sometimes and randomly :()
* [1. Closing socket error](https://github.com/slackapi/python-slackclient/issues/171)
* [2. Random errors closing socket](https://github.com/websocket-client/websocket-client/issues/449)
# A Tiny How To
## Connect to a server without auth
```python
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
```python
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.
```python
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
```python
HubConnectionBuilder()\
.with_url(server_url,
.configure_logging(logging.DEBUG)
...
```
## Configure socket trace
```python
HubConnectionBuilder()\
.with_url(server_url,
.configure_logging(logging.DEBUG, socket_trace=True)
...
```
## Configure your own handler
```python
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 exeption will be thrown and on_disconnect event will be fired.
```python
hub_connection = HubConnectionBuilder()\
.with_url(server_url)\
...
.build()
```
## Configuring additional headers
```python
hub_connection = HubConnectionBuilder()\
.with_url(server_url,
options={
"headers": {
"mycustomheader": "mycustomheadervalue"
}
})
...
.build()
```
## Configuring additional querystring parameters
```python
server_url ="http.... /?myquerystringparam=134&foo=bar"
connection = HubConnectionBuilder()\
.with_url(server_url,
options={
})\
.build()
```
## Congfiguring skip negotiation
```python
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
```python
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
```python
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
```python
from signalrcore.protocol.messagepack_protocol import MessagePackHubProtocol
HubConnectionBuilder()\
.with_url(self.server_url, options={"verify_ssl":False})\
...
.with_hub_protocol(MessagePackHubProtocol())\
...
.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
```python
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
```python
hub_connection.on("ReceiveMessage", print)
```
## Sending messages
SendMessage - signalr method
username, message - parameters of signalrmethod
```python
hub_connection.send("SendMessage", [username, message])
```
## Sending messages with callback
SendMessage - signalr method
username, message - parameters of signalrmethod
```python
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)
```python
hub_connection.stream(
"Counter",
[len(self.items), 500]).subscribe({
"next": self.on_next,
"complete": self.on_complete,
"error": self.on_error
})
```
## Client side Streaming
```python
from signalrcore.subject import Subject
subject = Subject()
# Start Streaming
hub_connection.send("UploadStream", subject)
# Each iteration
subject.next(str(iteration))
# End streaming
subject.complete()
```
# Full Examples
Examples will be avaiable [here](https://github.com/mandrewcito/signalrcore/tree/master/test/examples)
It were developed using package from [aspnet core - SignalRChat](https://codeload.github.com/aspnet/Docs/zip/master)
## Chat example
A mini example could be something like this:
```python
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)
```
Raw data
{
"_id": null,
"home_page": "https://github.com/mandrewcito/signalrcore",
"name": "signalrcore-new",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "signalr core client 3.1",
"author": "sipanohanyan",
"author_email": "sipanohanyan@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/b8/a7/6c5188e3d0f757976ffd214c6735597e63a62248184e465286dfdfd5fffc/signalrcore_new-1.0.0.tar.gz",
"platform": null,
"description": "# SignalR core client\n[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/mandrewcito/1)\n![Pypi](https://img.shields.io/pypi/v/signalrcore.svg)\n[![Downloads](https://pepy.tech/badge/signalrcore/month)](https://pepy.tech/project/signalrcore/month)\n[![Downloads](https://pepy.tech/badge/signalrcore)](https://pepy.tech/project/signalrcore)\n![Issues](https://img.shields.io/github/issues/mandrewcito/signalrcore.svg)\n![Open issues](https://img.shields.io/github/issues-raw/mandrewcito/signalrcore.svg)\n![codecov.io](https://codecov.io/github/mandrewcito/signalrcore/coverage.svg?branch=master)\n\n![logo alt](https://raw.githubusercontent.com/mandrewcito/signalrcore/master/docs/img/logo_temp.128.svg.png)\n\n\n# Links \n\n* [Dev to posts with library examples and implementation](https://dev.to/mandrewcito/singlar-core-python-client-58e7)\n\n* [Pypi](https://pypi.org/project/signalrcore/)\n\n* [Wiki - This Doc](https://mandrewcito.github.io/signalrcore/)\n\n# Develop\n\nTest server will be avaiable in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.\n\n```bash\ngit clone https://github.com/mandrewcito/signalrcore-containertestservers\ncd signalrcore-containertestservers\ndocker-compose up\ncd ../signalrcore\nmake tests\n```\n\n## Known Issues\n\nIssues related with closing sockets are inherited from the websocket-client library. Due to these problems i can't update the library to versions higher than websocket-client 0.54.0. \nI'm working to solve it but for now its patched (Error number 1. Raises an exception, and then exception is treated for prevent errors). \nIf I update the websocket library I fall into error number 2, on local machine I can't reproduce it but travis builds fail (sometimes and randomly :()\n* [1. Closing socket error](https://github.com/slackapi/python-slackclient/issues/171)\n* [2. Random errors closing socket](https://github.com/websocket-client/websocket-client/issues/449)\n\n# A Tiny How To\n\n## Connect to a server without auth\n\n```python\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url)\\\n .configure_logging(logging.DEBUG)\\\n .with_automatic_reconnect({\n \"type\": \"raw\",\n \"keep_alive_interval\": 10,\n \"reconnect_interval\": 5,\n \"max_attempts\": 5\n }).build()\n```\n## Connect to a server with auth\n\nlogin_function must provide auth token\n\n```python\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url,\n options={\n \"access_token_factory\": login_function,\n \"headers\": {\n \"mycustomheader\": \"mycustomheadervalue\"\n }\n })\\\n .configure_logging(logging.DEBUG)\\\n .with_automatic_reconnect({\n \"type\": \"raw\",\n \"keep_alive_interval\": 10,\n \"reconnect_interval\": 5,\n \"max_attempts\": 5\n }).build()\n```\n### Unauthorized errors\nA login function must provide an error controller if authorization fails. When connection starts, if authorization fails exception will be propagated.\n\n```python\n def login(self):\n response = requests.post(\n self.login_url,\n json={\n \"username\": self.email,\n \"password\": self.password\n },verify=False)\n if response.status_code == 200:\n return response.json()[\"token\"]\n raise requests.exceptions.ConnectionError()\n\n hub_connection.start() # this code will raise requests.exceptions.ConnectionError() if auth fails\n```\n## Configure logging\n\n```python\nHubConnectionBuilder()\\\n .with_url(server_url,\n .configure_logging(logging.DEBUG)\n ...\n```\n## Configure socket trace\n```python \nHubConnectionBuilder()\\\n .with_url(server_url,\n .configure_logging(logging.DEBUG, socket_trace=True) \n ... \n ```\n## Configure your own handler\n```python\n import logging\nhandler = logging.StreamHandler()\nhandler.setLevel(logging.DEBUG)\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url, options={\"verify_ssl\": False}) \\\n .configure_logging(logging.DEBUG, socket_trace=True, handler=handler)\n ...\n ```\n## Configuring reconnection\nAfter reaching max_attempts an exeption will be thrown and on_disconnect event will be fired.\n```python\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url)\\\n ...\n .build()\n```\n## Configuring additional headers\n```python\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url,\n options={\n \"headers\": {\n \"mycustomheader\": \"mycustomheadervalue\"\n }\n })\n ...\n .build()\n```\n## Configuring additional querystring parameters\n```python\nserver_url =\"http.... /?myquerystringparam=134&foo=bar\"\nconnection = HubConnectionBuilder()\\\n .with_url(server_url,\n options={\n })\\\n .build()\n```\n## Congfiguring skip negotiation\n```python\nhub_connection = HubConnectionBuilder() \\\n .with_url(\"ws://\"+server_url, options={\n \"verify_ssl\": False,\n \"skip_negotiation\": False,\n \"headers\": {\n }\n }) \\\n .configure_logging(logging.DEBUG, socket_trace=True, handler=handler) \\\n .build()\n\n```\n## Configuring ping(keep alive)\n\nkeep_alive_interval sets the seconds of ping message\n\n```python\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url)\\\n .configure_logging(logging.DEBUG)\\\n .with_automatic_reconnect({\n \"type\": \"raw\",\n \"keep_alive_interval\": 10,\n \"reconnect_interval\": 5,\n \"max_attempts\": 5\n }).build()\n```\n## Configuring logging\n```python\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url)\\\n .configure_logging(logging.DEBUG)\\\n .with_automatic_reconnect({\n \"type\": \"raw\",\n \"keep_alive_interval\": 10,\n \"reconnect_interval\": 5,\n \"max_attempts\": 5\n }).build()\n```\n\n## Configure messagepack\n\n```python\nfrom signalrcore.protocol.messagepack_protocol import MessagePackHubProtocol\n\nHubConnectionBuilder()\\\n .with_url(self.server_url, options={\"verify_ssl\":False})\\\n ... \n .with_hub_protocol(MessagePackHubProtocol())\\\n ...\n .build()\n```\n## Events\n\n### On Connect / On Disconnect\non_open - fires when connection is opened and ready to send messages\non_close - fires when connection is closed\n```python\nhub_connection.on_open(lambda: print(\"connection opened and handshake received ready to send messages\"))\nhub_connection.on_close(lambda: print(\"connection closed\"))\n\n```\n### On Hub Error (Hub Exceptions ...)\n```\nhub_connection.on_error(lambda data: print(f\"An exception was thrown closed{data.error}\"))\n```\n### Register an operation \nReceiveMessage - signalr method\nprint - function that has as parameters args of signalr method\n```python\nhub_connection.on(\"ReceiveMessage\", print)\n```\n## Sending messages\nSendMessage - signalr method\nusername, message - parameters of signalrmethod\n```python\n hub_connection.send(\"SendMessage\", [username, message])\n```\n\n## Sending messages with callback\nSendMessage - signalr method\nusername, message - parameters of signalrmethod\n```python\n send_callback_received = threading.Lock()\n send_callback_received.acquire()\n self.connection.send(\n \"SendMessage\", # Method\n [self.username, self.message], # Params\n lambda m: send_callback_received.release()) # Callback\n if not send_callback_received.acquire(timeout=1):\n raise ValueError(\"CALLBACK NOT RECEIVED\")\n```\n\n## Requesting streaming (Server to client)\n```python\nhub_connection.stream(\n \"Counter\",\n [len(self.items), 500]).subscribe({\n \"next\": self.on_next,\n \"complete\": self.on_complete,\n \"error\": self.on_error\n })\n```\n## Client side Streaming\n```python\nfrom signalrcore.subject import Subject\n\nsubject = Subject()\n\n# Start Streaming\nhub_connection.send(\"UploadStream\", subject)\n\n# Each iteration\nsubject.next(str(iteration))\n\n# End streaming\nsubject.complete()\n```\n\n# Full Examples\n\nExamples will be avaiable [here](https://github.com/mandrewcito/signalrcore/tree/master/test/examples)\nIt were developed using package from [aspnet core - SignalRChat](https://codeload.github.com/aspnet/Docs/zip/master) \n\n## Chat example\nA mini example could be something like this:\n\n```python\nimport logging\nimport sys\nfrom signalrcore.hub_connection_builder import HubConnectionBuilder\n\n\ndef input_with_default(input_text, default_value):\n value = input(input_text.format(default_value))\n return default_value if value is None or value.strip() == \"\" else value\n\n\nserver_url = input_with_default('Enter your server url(default: {0}): ', \"wss://localhost:44376/chatHub\")\nusername = input_with_default('Enter your username (default: {0}): ', \"mandrewcito\")\nhandler = logging.StreamHandler()\nhandler.setLevel(logging.DEBUG)\nhub_connection = HubConnectionBuilder()\\\n .with_url(server_url, options={\"verify_ssl\": False}) \\\n .configure_logging(logging.DEBUG, socket_trace=True, handler=handler) \\\n .with_automatic_reconnect({\n \"type\": \"interval\",\n \"keep_alive_interval\": 10,\n \"intervals\": [1, 3, 5, 6, 7, 87, 3]\n }).build()\n\nhub_connection.on_open(lambda: print(\"connection opened and handshake received ready to send messages\"))\nhub_connection.on_close(lambda: print(\"connection closed\"))\n\nhub_connection.on(\"ReceiveMessage\", print)\nhub_connection.start()\nmessage = None\n\n# Do login\n\nwhile message != \"exit()\":\n message = input(\">> \")\n if message is not None and message != \"\" and message != \"exit()\":\n hub_connection.send(\"SendMessage\", [username, message])\n\nhub_connection.stop()\n\nsys.exit(0)\n\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "A Python SignalR Core client(json and messagepack), with invocation auth and two way streaming. Compatible with azure / serverless functions. Also with automatic reconnect and manually reconnect.",
"version": "1.0.0",
"project_urls": {
"Homepage": "https://github.com/mandrewcito/signalrcore"
},
"split_keywords": [
"signalr",
"core",
"client",
"3.1"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "7ff0171c182879af23a2363af6b702efd37680f97162ef82c56d96dc250dd576",
"md5": "e8dcf3be63586cde108e8d4313e42f8d",
"sha256": "205dda924fdc29166b0b0ed6d2e59de176e607f0ea23d0906f65cb53fd522d85"
},
"downloads": -1,
"filename": "signalrcore_new-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e8dcf3be63586cde108e8d4313e42f8d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 35112,
"upload_time": "2024-11-13T15:38:46",
"upload_time_iso_8601": "2024-11-13T15:38:46.764652Z",
"url": "https://files.pythonhosted.org/packages/7f/f0/171c182879af23a2363af6b702efd37680f97162ef82c56d96dc250dd576/signalrcore_new-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b8a76c5188e3d0f757976ffd214c6735597e63a62248184e465286dfdfd5fffc",
"md5": "70b6f837094edbd773f5d708c62914a5",
"sha256": "27621038bde912c5cb70b952878c9f9cae88341edd888eb6fc28e82231ec24af"
},
"downloads": -1,
"filename": "signalrcore_new-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "70b6f837094edbd773f5d708c62914a5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 25069,
"upload_time": "2024-11-13T15:38:48",
"upload_time_iso_8601": "2024-11-13T15:38:48.638728Z",
"url": "https://files.pythonhosted.org/packages/b8/a7/6c5188e3d0f757976ffd214c6735597e63a62248184e465286dfdfd5fffc/signalrcore_new-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-13 15:38:48",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "mandrewcito",
"github_project": "signalrcore",
"travis_ci": true,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "signalrcore-new"
}