wslink 2.1.3__tar.gz → 2.2.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. {wslink-2.1.3 → wslink-2.2.1}/PKG-INFO +1 -1
  2. {wslink-2.1.3 → wslink-2.2.1}/setup.cfg +1 -1
  3. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/__init__.py +8 -1
  4. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/protocol.py +62 -46
  5. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/publish.py +3 -0
  6. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/websocket.py +53 -0
  7. {wslink-2.1.3 → wslink-2.2.1}/src/wslink.egg-info/PKG-INFO +1 -1
  8. {wslink-2.1.3 → wslink-2.2.1}/MANIFEST.in +0 -0
  9. {wslink-2.1.3 → wslink-2.2.1}/README.rst +0 -0
  10. {wslink-2.1.3 → wslink-2.2.1}/setup.py +0 -0
  11. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/LICENSE +0 -0
  12. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/__init__.py +0 -0
  13. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/aiohttp/__init__.py +0 -0
  14. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/aiohttp/launcher.py +0 -0
  15. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/aiohttp/relay.py +0 -0
  16. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/generic/__init__.py +0 -0
  17. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/generic/core.py +0 -0
  18. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/jupyter/__init__.py +0 -0
  19. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/jupyter/core.py +0 -0
  20. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/tornado/__init__.py +0 -0
  21. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/backends/tornado/core.py +0 -0
  22. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/chunking.py +0 -0
  23. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/launcher.py +0 -0
  24. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/relay.py +0 -0
  25. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/server.py +0 -0
  26. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/ssl_context.py +0 -0
  27. {wslink-2.1.3 → wslink-2.2.1}/src/wslink/uri.py +0 -0
  28. {wslink-2.1.3 → wslink-2.2.1}/src/wslink.egg-info/SOURCES.txt +0 -0
  29. {wslink-2.1.3 → wslink-2.2.1}/src/wslink.egg-info/dependency_links.txt +0 -0
  30. {wslink-2.1.3 → wslink-2.2.1}/src/wslink.egg-info/requires.txt +0 -0
  31. {wslink-2.1.3 → wslink-2.2.1}/src/wslink.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wslink
3
- Version: 2.1.3
3
+ Version: 2.2.1
4
4
  Summary: Python/JavaScript library for communicating over WebSocket
5
5
  Home-page: https://github.com/kitware/wslink
6
6
  Author: Kitware, Inc.
@@ -1,5 +1,5 @@
1
1
  [metadata]
2
- version = 2.1.3
2
+ version = 2.2.1
3
3
 
4
4
  [egg_info]
5
5
  tag_build =
@@ -54,7 +54,7 @@ def schedule_callback(delay, callback, *args, **kwargs):
54
54
  return loop.call_later(delay, functools.partial(callback, *args, **kwargs))
55
55
 
56
56
 
57
- def schedule_coroutine(delay, coro_func, *args, **kwargs):
57
+ def schedule_coroutine(delay, coro_func, *args, done_callback=None, **kwargs):
58
58
  """
59
59
  Creates a coroutine out of the provided coroutine function coro_func and
60
60
  the provided args and kwargs, then schedules the coroutine to be called
@@ -73,4 +73,11 @@ def schedule_coroutine(delay, coro_func, *args, **kwargs):
73
73
  # See method above for comment on "get_event_loop()" vs "get_running_loop()".
74
74
  loop = asyncio.get_event_loop()
75
75
  coro_partial = functools.partial(coro_func, *args, **kwargs)
76
+ if done_callback is not None:
77
+ return loop.call_later(
78
+ delay,
79
+ lambda: asyncio.ensure_future(coro_partial()).add_done_callback(
80
+ done_callback
81
+ ),
82
+ )
76
83
  return loop.call_later(delay, lambda: asyncio.ensure_future(coro_partial()))
@@ -152,6 +152,7 @@ class WslinkHandler(object):
152
152
  self.attachment_atomic = asyncio.Lock()
153
153
  self.pub_manager = PublishManager()
154
154
  self.unchunkers = {}
155
+ self.network_monitor = protocol.network_monitor
155
156
 
156
157
  # Build the rpc method dictionary, assuming we were given a serverprotocol
157
158
  if self.getServerProtocol():
@@ -257,7 +258,8 @@ class WslinkHandler(object):
257
258
 
258
259
  full_message = self.unchunkers[client_id].process_chunk(msg.data)
259
260
  if full_message is not None:
260
- await self.onCompleteMessage(full_message, client_id)
261
+ with self.network_monitor:
262
+ await self.onCompleteMessage(full_message, client_id)
261
263
 
262
264
  async def onCompleteMessage(self, rpc, client_id):
263
265
  logger.debug("wslink incoming msg %s", self.payloadWithSecretStripped(rpc))
@@ -282,23 +284,25 @@ class WslinkHandler(object):
282
284
 
283
285
  # Prevent any further processing if token is not valid
284
286
  if not self.isClientAuthenticated(client_id):
285
- await self.sendWrappedError(
286
- rpcid,
287
- AUTHENTICATION_ERROR,
288
- "Unauthorized: Skip message processing",
289
- client_id=client_id,
290
- )
287
+ with self.network_monitor:
288
+ await self.sendWrappedError(
289
+ rpcid,
290
+ AUTHENTICATION_ERROR,
291
+ "Unauthorized: Skip message processing",
292
+ client_id=client_id,
293
+ )
291
294
  return
292
295
 
293
296
  # No matching method found
294
297
  if not methodName in self.functionMap:
295
- await self.sendWrappedError(
296
- rpcid,
297
- METHOD_NOT_FOUND,
298
- "Unregistered method called",
299
- methodName,
300
- client_id=client_id,
301
- )
298
+ with self.network_monitor:
299
+ await self.sendWrappedError(
300
+ rpcid,
301
+ METHOD_NOT_FOUND,
302
+ "Unregistered method called",
303
+ methodName,
304
+ client_id=client_id,
305
+ )
302
306
  return
303
307
 
304
308
  obj, func = self.functionMap[methodName]
@@ -308,31 +312,34 @@ class WslinkHandler(object):
308
312
  self.web_app.last_active_client_id = client_id
309
313
  results = func(*args, **kwargs)
310
314
  if inspect.isawaitable(results):
311
- results = await results
315
+ with self.network_monitor:
316
+ results = await results
312
317
 
313
318
  if self.connections[client_id].closed:
314
319
  # Connection was closed during RPC call.
315
320
  return
316
321
 
317
- await self.sendWrappedMessage(
318
- rpcid, results, method=methodName, client_id=client_id
319
- )
322
+ with self.network_monitor:
323
+ await self.sendWrappedMessage(
324
+ rpcid, results, method=methodName, client_id=client_id
325
+ )
320
326
  except Exception as e_inst:
321
327
  captured_trace = traceback.format_exc()
322
328
  logger.error("Exception raised")
323
329
  logger.error(repr(e_inst))
324
330
  logger.error(captured_trace)
325
- await self.sendWrappedError(
326
- rpcid,
327
- EXCEPTION_ERROR,
328
- "Exception raised",
329
- {
330
- "method": methodName,
331
- "exception": repr(e_inst),
332
- "trace": captured_trace,
333
- },
334
- client_id=client_id,
335
- )
331
+ with self.network_monitor:
332
+ await self.sendWrappedError(
333
+ rpcid,
334
+ EXCEPTION_ERROR,
335
+ "Exception raised",
336
+ {
337
+ "method": methodName,
338
+ "exception": repr(e_inst),
339
+ "trace": captured_trace,
340
+ },
341
+ client_id=client_id,
342
+ )
336
343
 
337
344
  def payloadWithSecretStripped(self, payload):
338
345
  payload = copy.deepcopy(payload)
@@ -397,13 +404,14 @@ class WslinkHandler(object):
397
404
  except Exception:
398
405
  # the content which is not serializable might be arbitrarily large, don't include.
399
406
  # repr(content) would do that...
400
- await self.sendWrappedError(
401
- rpcid,
402
- RESULT_SERIALIZE_ERROR,
403
- "Method result cannot be serialized",
404
- method,
405
- client_id=client_id,
406
- )
407
+ with self.network_monitor:
408
+ await self.sendWrappedError(
409
+ rpcid,
410
+ RESULT_SERIALIZE_ERROR,
411
+ "Method result cannot be serialized",
412
+ method,
413
+ client_id=client_id,
414
+ )
407
415
  return
408
416
 
409
417
  websockets = self.getAuthenticatedWebsockets(client_id, skip_last_active_client)
@@ -411,11 +419,15 @@ class WslinkHandler(object):
411
419
  # aiohttp can not handle pending ws.send_bytes()
412
420
  # tried with semaphore but got exception with >1
413
421
  # https://github.com/aio-libs/aiohttp/issues/2934
414
- async with self.attachment_atomic:
415
- for chunk in generate_chunks(packed_wrapper, MAX_MSG_SIZE):
416
- for ws in websockets:
417
- if ws is not None:
418
- await ws.send_bytes(chunk)
422
+ with self.network_monitor:
423
+ async with self.attachment_atomic:
424
+ for chunk in generate_chunks(packed_wrapper, MAX_MSG_SIZE):
425
+ for ws in websockets:
426
+ if ws is not None:
427
+ await ws.send_bytes(chunk)
428
+
429
+ # Network operation completed
430
+ self.network_monitor.network_call_completed()
419
431
 
420
432
  async def sendWrappedError(self, rpcid, code, message, data=None, client_id=None):
421
433
  wrapper = {
@@ -443,11 +455,15 @@ class WslinkHandler(object):
443
455
  # aiohttp can not handle pending ws.send_bytes()
444
456
  # tried with semaphore but got exception with >1
445
457
  # https://github.com/aio-libs/aiohttp/issues/2934
446
- async with self.attachment_atomic:
447
- for chunk in generate_chunks(packed_wrapper, MAX_MSG_SIZE):
448
- for ws in websockets:
449
- if ws is not None:
450
- await ws.send_bytes(chunk)
458
+ with self.network_monitor:
459
+ async with self.attachment_atomic:
460
+ for chunk in generate_chunks(packed_wrapper, MAX_MSG_SIZE):
461
+ for ws in websockets:
462
+ if ws is not None:
463
+ await ws.send_bytes(chunk)
464
+
465
+ # Network operation completed
466
+ self.network_monitor.network_call_completed()
451
467
 
452
468
  def publish(self, topic, data, client_id=None, skip_last_active_client=False):
453
469
  client_list = [client_id] if client_id else [c_id for c_id in self.connections]
@@ -27,6 +27,7 @@ class PublishManager(object):
27
27
  for protocol in self.protocols:
28
28
  # The client is unknown - we send to any client who is subscribed to the topic
29
29
  rpcid = "publish:{0}:{1}".format(topic, self.publishCount)
30
+ protocol.network_monitor.on_enter()
30
31
  schedule_coroutine(
31
32
  0,
32
33
  protocol.sendWrappedMessage,
@@ -34,6 +35,8 @@ class PublishManager(object):
34
35
  data,
35
36
  client_id=client_id,
36
37
  skip_last_active_client=skip_last_active_client,
38
+ # for schedule_coroutine call
39
+ done_callback=protocol.network_monitor.on_exit,
37
40
  )
38
41
 
39
42
 
@@ -5,6 +5,7 @@ ServerProtocol to hook all the needed LinkProtocols together.
5
5
  """
6
6
 
7
7
  import logging
8
+ import asyncio
8
9
 
9
10
  from . import register as exportRpc
10
11
  from . import schedule_callback
@@ -65,6 +66,57 @@ class LinkProtocol(object):
65
66
  # =============================================================================
66
67
 
67
68
 
69
+ class NetworkMonitor:
70
+ """
71
+ Provide context manager for increase/decrease pending request
72
+ either synchronously or asynchronously.
73
+
74
+ The Asynchronous version also await completion.
75
+ """
76
+
77
+ def __init__(self):
78
+
79
+ self.pending = 0
80
+ self.event = asyncio.Event()
81
+
82
+ def network_call_completed(self):
83
+ """Trigger completion event"""
84
+ self.event.set()
85
+
86
+ def on_enter(self, *args, **kwargs):
87
+ """Increase pending request"""
88
+ self.pending += 1
89
+
90
+ def on_exit(self, *args, **kwargs):
91
+ """Decrease pending request and trigger completion event if we reach 0 pending request"""
92
+ self.pending -= 1
93
+ if self.pending == 0 and not self.event.is_set():
94
+ self.event.set()
95
+
96
+ # Sync ctx manager
97
+ def __enter__(self):
98
+ self.on_enter()
99
+ return self
100
+
101
+ def __exit__(self, exc_type, exc_value, exc_traceback):
102
+ self.on_exit()
103
+
104
+ # Async ctx manager
105
+ async def __aenter__(self):
106
+ self.on_enter()
107
+ return self
108
+
109
+ async def __aexit__(self, exc_t, exc_v, exc_tb):
110
+ self.on_exit()
111
+ await self.completion()
112
+
113
+ async def completion(self):
114
+ """Await completion of any pending network request"""
115
+ while self.pending:
116
+ self.event.clear()
117
+ await self.event.wait()
118
+
119
+
68
120
  class ServerProtocol(object):
69
121
  """
70
122
  Defines the core server protocol for wslink. Gathers a list of LinkProtocol
@@ -72,6 +124,7 @@ class ServerProtocol(object):
72
124
  """
73
125
 
74
126
  def __init__(self):
127
+ self.network_monitor = NetworkMonitor()
75
128
  self.linkProtocols = []
76
129
  self.secret = None
77
130
  self.initialize()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wslink
3
- Version: 2.1.3
3
+ Version: 2.2.1
4
4
  Summary: Python/JavaScript library for communicating over WebSocket
5
5
  Home-page: https://github.com/kitware/wslink
6
6
  Author: Kitware, Inc.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes