signalrcore 0.9.5__tar.gz → 0.9.6a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of signalrcore might be problematic. Click here for more details.

Files changed (59) hide show
  1. {signalrcore-0.9.5/signalrcore.egg-info → signalrcore-0.9.6a2}/PKG-INFO +10 -19
  2. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/README.md +3 -11
  3. signalrcore-0.9.6a2/setup.py +38 -0
  4. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/helpers.py +61 -0
  5. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/hub/base_hub_connection.py +70 -56
  6. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/hub/errors.py +2 -0
  7. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/hub/handlers.py +2 -2
  8. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/hub_connection_builder.py +27 -5
  9. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/protocol/base_hub_protocol.py +10 -6
  10. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/protocol/messagepack_protocol.py +26 -9
  11. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/transport/base_transport.py +11 -9
  12. signalrcore-0.9.6a2/signalrcore/transport/websockets/websocket_client.py +263 -0
  13. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/transport/websockets/websocket_transport.py +49 -58
  14. {signalrcore-0.9.5 → signalrcore-0.9.6a2/signalrcore.egg-info}/PKG-INFO +10 -19
  15. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore.egg-info/SOURCES.txt +3 -1
  16. signalrcore-0.9.6a2/signalrcore.egg-info/requires.txt +8 -0
  17. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/base_test_case.py +9 -4
  18. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/client_streaming_test.py +10 -14
  19. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/configuration_test.py +28 -26
  20. signalrcore-0.9.6a2/test/open_close_test.py +73 -0
  21. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/reconnection_test.py +46 -32
  22. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/send_auth_errors_test.py +18 -18
  23. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/send_auth_test.py +21 -18
  24. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/send_test.py +61 -36
  25. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/streaming_test.py +17 -18
  26. signalrcore-0.9.6a2/test/subscribe_test.py +18 -0
  27. signalrcore-0.9.5/setup.py +0 -26
  28. signalrcore-0.9.5/signalrcore.egg-info/requires.txt +0 -3
  29. signalrcore-0.9.5/test/open_close_test.py +0 -64
  30. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/LICENSE +0 -0
  31. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/MANIFEST.in +0 -0
  32. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/setup.cfg +0 -0
  33. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/__init__.py +0 -0
  34. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/hub/__init__.py +0 -0
  35. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/hub/auth_hub_connection.py +0 -0
  36. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/__init__.py +0 -0
  37. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/base_message.py +0 -0
  38. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/cancel_invocation_message.py +0 -0
  39. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/close_message.py +0 -0
  40. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/completion_message.py +0 -0
  41. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/handshake/__init__.py +0 -0
  42. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/handshake/request.py +0 -0
  43. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/handshake/response.py +0 -0
  44. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/invocation_message.py +0 -0
  45. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/message_type.py +0 -0
  46. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/ping_message.py +0 -0
  47. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/stream_invocation_message.py +0 -0
  48. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/messages/stream_item_message.py +0 -0
  49. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/protocol/__init__.py +0 -0
  50. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/protocol/handshake/__init__.py +0 -0
  51. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/protocol/json_hub_protocol.py +0 -0
  52. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/subject.py +0 -0
  53. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/transport/__init__.py +0 -0
  54. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/transport/websockets/__init__.py +0 -0
  55. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/transport/websockets/connection.py +0 -0
  56. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore/transport/websockets/reconnection.py +0 -0
  57. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore.egg-info/dependency_links.txt +0 -0
  58. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/signalrcore.egg-info/top_level.txt +0 -0
  59. {signalrcore-0.9.5 → signalrcore-0.9.6a2}/test/__init__.py +0 -0
@@ -1,15 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: signalrcore
3
- Version: 0.9.5
4
- 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.
3
+ Version: 0.9.6a2
4
+ 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.
5
5
  Home-page: https://github.com/mandrewcito/signalrcore
6
6
  Author: mandrewcito
7
- Author-email: anbaalo@gmail.com
8
- License: UNKNOWN
7
+ Author-email: signalrcore@mandrewcito.dev
9
8
  Keywords: signalr core client 3.1
10
- Platform: UNKNOWN
11
- Classifier: Programming Language :: Python :: 3.6
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
+ Provides-Extra: dev
13
14
  License-File: LICENSE
14
15
 
15
16
  # SignalR core client
@@ -34,24 +35,16 @@ License-File: LICENSE
34
35
 
35
36
  # Develop
36
37
 
37
- Test server will be avaiable in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.
38
+ Test server will be available in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.
38
39
 
39
40
  ```bash
40
41
  git clone https://github.com/mandrewcito/signalrcore-containertestservers
41
42
  cd signalrcore-containertestservers
42
- docker-compose up
43
+ docker compose up
43
44
  cd ../signalrcore
44
45
  make tests
45
46
  ```
46
47
 
47
- ## Known Issues
48
-
49
- 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.
50
- 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).
51
- 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 :()
52
- * [1. Closing socket error](https://github.com/slackapi/python-slackclient/issues/171)
53
- * [2. Random errors closing socket](https://github.com/websocket-client/websocket-client/issues/449)
54
-
55
48
  # A Tiny How To
56
49
 
57
50
  ## Connect to a server without auth
@@ -159,7 +152,7 @@ connection = HubConnectionBuilder()\
159
152
  })\
160
153
  .build()
161
154
  ```
162
- ## Congfiguring skip negotiation
155
+ ## Configuring skip negotiation
163
156
  ```python
164
157
  hub_connection = HubConnectionBuilder() \
165
158
  .with_url("ws://"+server_url, options={
@@ -330,5 +323,3 @@ hub_connection.stop()
330
323
  sys.exit(0)
331
324
 
332
325
  ```
333
-
334
-
@@ -20,24 +20,16 @@
20
20
 
21
21
  # Develop
22
22
 
23
- Test server will be avaiable in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.
23
+ Test server will be available in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.
24
24
 
25
25
  ```bash
26
26
  git clone https://github.com/mandrewcito/signalrcore-containertestservers
27
27
  cd signalrcore-containertestservers
28
- docker-compose up
28
+ docker compose up
29
29
  cd ../signalrcore
30
30
  make tests
31
31
  ```
32
32
 
33
- ## Known Issues
34
-
35
- 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.
36
- 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).
37
- 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 :()
38
- * [1. Closing socket error](https://github.com/slackapi/python-slackclient/issues/171)
39
- * [2. Random errors closing socket](https://github.com/websocket-client/websocket-client/issues/449)
40
-
41
33
  # A Tiny How To
42
34
 
43
35
  ## Connect to a server without auth
@@ -145,7 +137,7 @@ connection = HubConnectionBuilder()\
145
137
  })\
146
138
  .build()
147
139
  ```
148
- ## Congfiguring skip negotiation
140
+ ## Configuring skip negotiation
149
141
  ```python
150
142
  hub_connection = HubConnectionBuilder() \
151
143
  .with_url("ws://"+server_url, options={
@@ -0,0 +1,38 @@
1
+ import setuptools
2
+
3
+ with open("README.md", "r") as fh:
4
+ long_description = fh.read()
5
+
6
+ setuptools.setup(
7
+ name="signalrcore",
8
+ version="0.9.6a2",
9
+ author="mandrewcito",
10
+ author_email="signalrcore@mandrewcito.dev",
11
+ description="A Python SignalR Core client(json and messagepack),"
12
+ "with invocation auth and two way streaming."
13
+ "Compatible with azure / serverless functions."
14
+ "Also with automatic reconnect and manually reconnect.",
15
+ keywords="signalr core client 3.1",
16
+ long_description=long_description,
17
+ long_description_content_type="text/markdown",
18
+ license_file="LICENSE",
19
+ url="https://github.com/mandrewcito/signalrcore",
20
+ packages=setuptools.find_packages(),
21
+ classifiers=[
22
+ "Programming Language :: Python :: 3.8",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent"
25
+ ],
26
+ install_requires=[
27
+ "msgpack==1.0.2"
28
+ ],
29
+ extras_require={
30
+ 'dev': [
31
+ 'requests',
32
+ 'flake8',
33
+ 'coverage',
34
+ 'pytest',
35
+ 'pytest-cov'
36
+ ]
37
+ },
38
+ )
@@ -1,8 +1,69 @@
1
1
  import logging
2
2
  import urllib.parse as parse
3
+ import urllib
4
+ import urllib.request
5
+ import ssl
6
+ from typing import Tuple
7
+ import json
8
+
9
+
10
+ class RequestHelpers:
11
+ @staticmethod
12
+ def post(
13
+ url: str,
14
+ headers: dict = {},
15
+ proxies: dict = {},
16
+ verify_ssl: bool = False) -> Tuple[int, dict]:
17
+ return RequestHelpers.request(
18
+ url,
19
+ "POST",
20
+ headers=headers,
21
+ proxies=proxies,
22
+ verify_ssl=verify_ssl
23
+ )
24
+
25
+ @staticmethod
26
+ def request(
27
+ url: str,
28
+ method: str,
29
+ headers: dict = {},
30
+ proxies: dict = {},
31
+ verify_ssl: bool = False) -> Tuple[int, dict]:
32
+ context = ssl.create_default_context()
33
+ if not verify_ssl:
34
+ context.check_hostname = False
35
+ context.verify_mode = ssl.CERT_NONE
36
+ headers.update({'Content-Type': 'application/json'})
37
+ proxy_handler = None
38
+
39
+ if len(proxies.keys()) > 0:
40
+ proxy_handler = urllib.request.ProxyHandler(proxies)
41
+
42
+ req = urllib.request.Request(
43
+ url,
44
+ method=method,
45
+ headers=headers)
46
+
47
+ opener = urllib.request.build_opener(proxy_handler)\
48
+ if proxy_handler is not None else\
49
+ urllib.request.urlopen
50
+
51
+ with opener(
52
+ req,
53
+ context=context) as response:
54
+ status_code = response.getcode()
55
+ response_body = response.read().decode('utf-8')
56
+
57
+ try:
58
+ json_data = json.loads(response_body)
59
+ except json.JSONDecodeError:
60
+ json_data = None
61
+
62
+ return status_code, json_data
3
63
 
4
64
 
5
65
  class Helpers:
66
+
6
67
  @staticmethod
7
68
  def configure_logger(level=logging.INFO, handler=None):
8
69
  logger = Helpers.get_logger()
@@ -1,30 +1,23 @@
1
- from operator import inv
2
- import websocket
3
- import threading
4
- import requests
5
- import traceback
6
1
  import uuid
7
- import time
8
- import ssl
9
- from typing import Callable
2
+ from typing import Callable, List, Union, Optional
10
3
  from signalrcore.messages.message_type import MessageType
11
4
  from signalrcore.messages.stream_invocation_message\
12
5
  import StreamInvocationMessage
13
- from signalrcore.messages.ping_message import PingMessage
14
- from .errors import UnAuthorizedHubError, HubError, HubConnectionError
6
+ from .errors import HubConnectionError
15
7
  from signalrcore.helpers import Helpers
16
8
  from .handlers import StreamHandler, InvocationHandler
17
- from ..protocol.messagepack_protocol import MessagePackHubProtocol
18
9
  from ..transport.websockets.websocket_transport import WebsocketTransport
19
- from ..helpers import Helpers
20
10
  from ..subject import Subject
21
11
  from ..messages.invocation_message import InvocationMessage
12
+ from collections import defaultdict
13
+
22
14
 
23
15
  class InvocationResult(object):
24
16
  def __init__(self, invocation_id) -> None:
25
17
  self.invocation_id = invocation_id
26
18
  self.message = None
27
19
 
20
+
28
21
  class BaseHubConnection(object):
29
22
  def __init__(
30
23
  self,
@@ -37,10 +30,12 @@ class BaseHubConnection(object):
37
30
  else:
38
31
  self.headers = headers
39
32
  self.logger = Helpers.get_logger()
40
- self.handlers = []
41
- self.stream_handlers = []
33
+ self.handlers = defaultdict(list)
34
+ self.stream_handlers = defaultdict(list)
35
+
42
36
  self._on_error = lambda error: self.logger.info(
43
37
  "on_error not defined {0}".format(error))
38
+
44
39
  self.transport = WebsocketTransport(
45
40
  url=url,
46
41
  protocol=protocol,
@@ -71,7 +66,7 @@ class BaseHubConnection(object):
71
66
  connection.on_open(lambda: print(
72
67
  "connection opened "))
73
68
  Args:
74
- callback (function): funciton without params
69
+ callback (function): function without params
75
70
  """
76
71
  self.transport.on_open_callback(callback)
77
72
 
@@ -101,30 +96,57 @@ class BaseHubConnection(object):
101
96
  Args:
102
97
  event (string): Event name
103
98
  callback_function (Function): callback function,
104
- arguments will be binded
99
+ arguments will be bound
105
100
  """
106
101
  self.logger.debug("Handler registered started {0}".format(event))
107
- self.handlers.append((event, callback_function))
102
+ self.handlers[event].append(callback_function)
103
+
104
+ def unsubscribe(self, event, callback_function: Callable):
105
+ """Removes a callback from the specified event
106
+ Args:
107
+ event (string): Event name
108
+ callback_function (Function): callback function,
109
+ arguments will be bound
110
+ """
111
+ self.logger.debug("Handler removed {0}".format(event))
112
+
113
+ self.handlers[event].remove(callback_function)
114
+
115
+ if len(self.handlers[event]) == 0:
116
+ del self.handlers[event]
117
+
118
+ def send(self, method, arguments, on_invocation=None, invocation_id=None)\
119
+ -> InvocationResult:
120
+ return self.invoke(method, arguments, on_invocation, invocation_id)
108
121
 
109
- def send(self, method, arguments, on_invocation=None, invocation_id=str(uuid.uuid4())) -> InvocationResult:
110
- """Sends a message
122
+ def invoke(
123
+ self,
124
+ method: str,
125
+ arguments: Union[List, Subject],
126
+ on_invocation: Optional[Callable] = None,
127
+ invocation_id: Optional[str] = None)\
128
+ -> InvocationResult:
129
+ """invokes a server function
111
130
 
112
131
  Args:
113
132
  method (string): Method name
114
133
  arguments (list|Subject): Method parameters
115
134
  on_invocation (function, optional): On invocation send callback
116
135
  will be raised on send server function ends. Defaults to None.
117
- invocation_id (string, optional): Override invocation ID. Exceptions
118
- thrown by the hub will use this ID, making it easier to handle
119
- with the on_error call.
136
+ invocation_id (string, optional): Override invocation ID.
137
+ Exceptions thrown by the hub will use this ID,
138
+ making it easier to handle with the on_error call.
120
139
 
121
140
  Raises:
122
141
  HubConnectionError: If hub is not ready to send
123
142
  TypeError: If arguments are invalid list or Subject
124
143
  """
144
+ if invocation_id is None:
145
+ invocation_id = str(uuid.uuid4())
146
+
125
147
  if not self.transport.is_running():
126
148
  raise HubConnectionError(
127
- "Hub is not running you cand send messages")
149
+ "Cannot connect to SignalR hub. Unable to transmit messages")
128
150
 
129
151
  if type(arguments) is not list and type(arguments) is not Subject:
130
152
  raise TypeError("Arguments of a message must be a list or subject")
@@ -139,25 +161,23 @@ class BaseHubConnection(object):
139
161
  headers=self.headers)
140
162
 
141
163
  if on_invocation:
142
- self.stream_handlers.append(
164
+ self.stream_handlers[message.invocation_id].append(
143
165
  InvocationHandler(
144
166
  message.invocation_id,
145
167
  on_invocation))
146
-
168
+
147
169
  self.transport.send(message)
148
170
  result.message = message
149
-
171
+
150
172
  if type(arguments) is Subject:
151
173
  arguments.connection = self
152
174
  arguments.target = method
153
175
  arguments.start()
154
176
  result.invocation_id = arguments.invocation_id
155
177
  result.message = arguments
156
-
157
178
 
158
179
  return result
159
180
 
160
-
161
181
  def on_message(self, messages):
162
182
  for message in messages:
163
183
  if message.type == MessageType.invocation_binding_failure:
@@ -169,13 +189,14 @@ class BaseHubConnection(object):
169
189
  continue
170
190
 
171
191
  if message.type == MessageType.invocation:
172
- fired_handlers = list(
173
- filter(
174
- lambda h: h[0] == message.target,
175
- self.handlers))
192
+
193
+ fired_handlers = self.handlers.get(message.target, [])
194
+
176
195
  if len(fired_handlers) == 0:
177
- self.logger.debug(f"event '{message.target}' hasn't fired any handler")
178
- for _, handler in fired_handlers:
196
+ self.logger.debug(
197
+ f"event '{message.target}' hasn't fired any handler")
198
+
199
+ for handler in fired_handlers:
179
200
  handler(message.arguments)
180
201
 
181
202
  if message.type == MessageType.close:
@@ -188,30 +209,26 @@ class BaseHubConnection(object):
188
209
  self._on_error(message)
189
210
 
190
211
  # Send callbacks
191
- fired_handlers = list(
192
- filter(
193
- lambda h: h.invocation_id == message.invocation_id,
194
- self.stream_handlers))
212
+ fired_handlers = self.stream_handlers.get(
213
+ message.invocation_id, [])
195
214
 
196
215
  # Stream callbacks
197
216
  for handler in fired_handlers:
198
217
  handler.complete_callback(message)
199
218
 
200
219
  # unregister handler
201
- self.stream_handlers = list(
202
- filter(
203
- lambda h: h.invocation_id != message.invocation_id,
204
- self.stream_handlers))
220
+ if message.invocation_id in self.stream_handlers:
221
+ del self.stream_handlers[message.invocation_id]
205
222
 
206
223
  if message.type == MessageType.stream_item:
207
- fired_handlers = list(
208
- filter(
209
- lambda h: h.invocation_id == message.invocation_id,
210
- self.stream_handlers))
224
+ fired_handlers = self.stream_handlers.get(
225
+ message.invocation_id, [])
226
+
211
227
  if len(fired_handlers) == 0:
212
228
  self.logger.warning(
213
229
  "id '{0}' hasn't fire any stream handler".format(
214
230
  message.invocation_id))
231
+
215
232
  for handler in fired_handlers:
216
233
  handler.next_callback(message.item)
217
234
 
@@ -219,10 +236,9 @@ class BaseHubConnection(object):
219
236
  pass
220
237
 
221
238
  if message.type == MessageType.cancel_invocation:
222
- fired_handlers = list(
223
- filter(
224
- lambda h: h.invocation_id == message.invocation_id,
225
- self.stream_handlers))
239
+ fired_handlers = self.stream_handlers.get(
240
+ message.invocation_id, [])
241
+
226
242
  if len(fired_handlers) == 0:
227
243
  self.logger.warning(
228
244
  "id '{0}' hasn't fire any stream handler".format(
@@ -232,10 +248,8 @@ class BaseHubConnection(object):
232
248
  handler.error_callback(message)
233
249
 
234
250
  # unregister handler
235
- self.stream_handlers = list(
236
- filter(
237
- lambda h: h.invocation_id != message.invocation_id,
238
- self.stream_handlers))
251
+ if message.invocation_id in self.stream_handlers:
252
+ del self.stream_handlers[message.invocation_id]
239
253
 
240
254
  def stream(self, event, event_params):
241
255
  """Starts server streaming
@@ -256,11 +270,11 @@ class BaseHubConnection(object):
256
270
  """
257
271
  invocation_id = str(uuid.uuid4())
258
272
  stream_obj = StreamHandler(event, invocation_id)
259
- self.stream_handlers.append(stream_obj)
273
+ self.stream_handlers[invocation_id].append(stream_obj)
260
274
  self.transport.send(
261
275
  StreamInvocationMessage(
262
276
  invocation_id,
263
277
  event,
264
278
  event_params,
265
279
  headers=self.headers))
266
- return stream_obj
280
+ return stream_obj
@@ -1,9 +1,11 @@
1
1
  class HubError(OSError):
2
2
  pass
3
3
 
4
+
4
5
  class UnAuthorizedHubError(HubError):
5
6
  pass
6
7
 
8
+
7
9
  class HubConnectionError(ValueError):
8
10
  """Hub connection error
9
11
  """
@@ -1,7 +1,7 @@
1
- import logging
2
-
3
1
  from typing import Callable
4
2
  from ..helpers import Helpers
3
+
4
+
5
5
  class StreamHandler(object):
6
6
  def __init__(self, event: str, invocation_id: str):
7
7
  self.event = event
@@ -1,12 +1,9 @@
1
- import uuid
2
1
  from .hub.base_hub_connection import BaseHubConnection
3
2
  from .hub.auth_hub_connection import AuthHubConnection
4
3
  from .transport.websockets.reconnection import \
5
4
  IntervalReconnectionHandler, RawReconnectionHandler, ReconnectionType
6
5
  from .helpers import Helpers
7
- from .messages.invocation_message import InvocationMessage
8
6
  from .protocol.json_hub_protocol import JsonHubProtocol
9
- from .subject import Subject
10
7
 
11
8
 
12
9
  class HubConnectionBuilder(object):
@@ -37,6 +34,7 @@ class HubConnectionBuilder(object):
37
34
  self.enable_trace = False # socket trace
38
35
  self.skip_negotiation = False # By default do not skip negotiation
39
36
  self.running = False
37
+ self.proxies = dict()
40
38
 
41
39
  def with_url(
42
40
  self,
@@ -85,7 +83,7 @@ class HubConnectionBuilder(object):
85
83
  if hub_url is None or hub_url.strip() == "":
86
84
  raise ValueError("hub_url must be a valid url.")
87
85
 
88
- if options is not None and type(options) != dict:
86
+ if options is not None and type(options) is not dict:
89
87
  raise TypeError(
90
88
  "options must be a dict {0}.".format(self.options))
91
89
 
@@ -127,6 +125,28 @@ class HubConnectionBuilder(object):
127
125
  self.enable_trace = socket_trace
128
126
  return self
129
127
 
128
+ def configure_proxies(
129
+ self,
130
+ proxies: dict):
131
+ """configures proxies
132
+
133
+ Args:
134
+ proxies (dict): {
135
+ "http" : "http://host:port",
136
+ "https" : "https://host:port",
137
+ "ftp" : "ftp://port:port"
138
+ }
139
+
140
+ Returns:
141
+ [HubConnectionBuilder]: Instance hub with proxies configured
142
+ """
143
+
144
+ if "http" not in proxies.keys() or "https" not in proxies.keys():
145
+ raise ValueError("Only http and https keys are allowed")
146
+
147
+ self.proxies = proxies
148
+ return self
149
+
130
150
  def with_hub_protocol(self, protocol):
131
151
  """Changes transport protocol
132
152
  from signalrcore.protocol.messagepack_protocol\
@@ -182,6 +202,7 @@ class HubConnectionBuilder(object):
182
202
  keep_alive_interval=self.keep_alive_interval,
183
203
  reconnection_handler=self.reconnection_handler,
184
204
  verify_ssl=self.verify_ssl,
205
+ proxies=self.proxies,
185
206
  skip_negotiation=self.skip_negotiation,
186
207
  enable_trace=self.enable_trace)\
187
208
  if self.has_auth_configured else\
@@ -192,9 +213,10 @@ class HubConnectionBuilder(object):
192
213
  reconnection_handler=self.reconnection_handler,
193
214
  headers=self.headers,
194
215
  verify_ssl=self.verify_ssl,
216
+ proxies=self.proxies,
195
217
  skip_negotiation=self.skip_negotiation,
196
218
  enable_trace=self.enable_trace)
197
-
219
+
198
220
  def with_automatic_reconnect(self, data: dict):
199
221
  """Configures automatic reconnection
200
222
  https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-core-3-0-preview-4/
@@ -10,7 +10,7 @@ from ..messages.cancel_invocation_message import CancelInvocationMessage # 5
10
10
  from ..messages.ping_message import PingMessage # 6
11
11
  from ..messages.close_message import CloseMessage # 7
12
12
  from ..messages.message_type import MessageType
13
- from ..helpers import Helpers
13
+
14
14
 
15
15
  class BaseHubProtocol(object):
16
16
  def __init__(self, protocol, version, transfer_format, record_separator):
@@ -21,9 +21,10 @@ class BaseHubProtocol(object):
21
21
 
22
22
  @staticmethod
23
23
  def get_message(dict_message):
24
- message_type = MessageType.close\
25
- if not "type" in dict_message.keys() else MessageType(dict_message["type"])
26
-
24
+ message_type = MessageType.close\
25
+ if "type" not in dict_message.keys() else\
26
+ MessageType(dict_message["type"])
27
+
27
28
  dict_message["invocation_id"] = dict_message.get("invocationId", None)
28
29
  dict_message["headers"] = dict_message.get("headers", {})
29
30
  dict_message["error"] = dict_message.get("error", None)
@@ -45,10 +46,13 @@ class BaseHubProtocol(object):
45
46
 
46
47
  def decode_handshake(self, raw_message: str) -> HandshakeResponseMessage:
47
48
  messages = raw_message.split(self.record_separator)
48
- messages = list(filter(lambda x: x != "", messages))
49
+ messages = list(filter(lambda x: x != "", messages))
49
50
  data = json.loads(messages[0])
50
51
  idx = raw_message.index(self.record_separator)
51
- return HandshakeResponseMessage(data.get("error", None)), self.parse_messages(raw_message[idx + 1 :]) if len(messages) > 1 else []
52
+ return (
53
+ HandshakeResponseMessage(data.get("error", None)),
54
+ self.parse_messages(raw_message[idx + 1:])
55
+ if len(messages) > 1 else [])
52
56
 
53
57
  def handshake_message(self) -> HandshakeRequestMessage:
54
58
  return HandshakeRequestMessage(self.protocol, self.version)
@@ -37,30 +37,47 @@ class MessagePackHubProtocol(BaseHubProtocol):
37
37
  try:
38
38
  messages = []
39
39
  offset = 0
40
+ max_length_prefix_size = 5
41
+ num_bits_to_shift = [0, 7, 14, 21, 28]
40
42
  while offset < len(raw):
41
- length = msgpack.unpackb(raw[offset: offset + 1])
42
- values = msgpack.unpackb(raw[offset + 1: offset + length + 1])
43
- offset = offset + length + 1
43
+ length = 0
44
+ num_bytes = 0
45
+ while True:
46
+ byte_read = raw[offset + num_bytes]
47
+ length |=\
48
+ (byte_read & 0x7F) << num_bits_to_shift[num_bytes]
49
+ num_bytes += 1
50
+ if byte_read & 0x80 == 0:
51
+ break
52
+ if offset == max_length_prefix_size\
53
+ or offset + num_bytes > len(raw):
54
+ raise Exception("Cannot read message length")
55
+ offset = offset + num_bytes
56
+ values = msgpack.unpackb(raw[offset: offset + length])
57
+ offset = offset + length
44
58
  message = self._decode_message(values)
45
59
  messages.append(message)
46
60
  except Exception as ex:
47
- Helpers.get_logger().error("Parse messages Error {0}".format(ex))
48
- Helpers.get_logger().error("raw msg '{0}'".format(raw))
61
+ self.logger.error("Parse messages Error {0}".format(ex))
62
+ self.logger.error("raw msg '{0}'".format(raw))
49
63
  return messages
50
64
 
51
65
  def decode_handshake(self, raw_message):
52
66
  try:
53
67
  has_various_messages = 0x1E in raw_message
54
- handshake_data = raw_message[0: raw_message.index(0x1E)] if has_various_messages else raw_message
55
- messages = self.parse_messages(raw_message[raw_message.index(0x1E) + 1:]) if has_various_messages else []
68
+ handshake_data = raw_message[0: raw_message.index(0x1E)]\
69
+ if has_various_messages else raw_message
70
+ messages = self.parse_messages(
71
+ raw_message[raw_message.index(0x1E) + 1:])\
72
+ if has_various_messages else []
56
73
  data = json.loads(handshake_data)
57
74
  return HandshakeResponseMessage(data.get("error", None)), messages
58
75
  except Exception as ex:
59
76
  if type(raw_message) is str:
60
77
  data = json.loads(raw_message[0: raw_message.index("}") + 1])
61
78
  return HandshakeResponseMessage(data.get("error", None)), []
62
- Helpers.get_logger().error(raw_message)
63
- Helpers.get_logger().error(ex)
79
+ self.logger.error(raw_message)
80
+ self.logger.error(ex)
64
81
  raise ex
65
82
 
66
83
  def encode(self, message):