wslink 2.1.3__py3-none-any.whl → 2.2.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
wslink/__init__.py CHANGED
@@ -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()))
wslink/protocol.py CHANGED
@@ -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]
wslink/publish.py CHANGED
@@ -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
 
wslink/websocket.py CHANGED
@@ -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,42 @@ class LinkProtocol(object):
65
66
  # =============================================================================
66
67
 
67
68
 
69
+ class NetworkMonitor:
70
+ def __init__(self):
71
+ self.pending = 0
72
+ self.event = asyncio.Event()
73
+
74
+ def network_call_completed(self):
75
+ self.event.set()
76
+
77
+ def on_enter(self, *args, **kwargs):
78
+ self.pending += 1
79
+
80
+ def on_exit(self, *args, **kwargs):
81
+ self.pending -= 1
82
+ if self.pending == 0 and not self.event.is_set():
83
+ self.event.set()
84
+
85
+ # Sync ctx manager
86
+ def __enter__(self):
87
+ self.on_enter()
88
+ return self
89
+
90
+ def __exit__(self, exc_type, exc_value, exc_traceback):
91
+ self.on_exit()
92
+
93
+ # Async ctx manager
94
+ async def __aenter__(self):
95
+ self.on_enter()
96
+ return self
97
+
98
+ async def __aexit__(self, exc_t, exc_v, exc_tb):
99
+ self.on_exit()
100
+ while self.pending:
101
+ self.event.clear()
102
+ await self.event.wait()
103
+
104
+
68
105
  class ServerProtocol(object):
69
106
  """
70
107
  Defines the core server protocol for wslink. Gathers a list of LinkProtocol
@@ -72,6 +109,7 @@ class ServerProtocol(object):
72
109
  """
73
110
 
74
111
  def __init__(self):
112
+ self.network_monitor = NetworkMonitor()
75
113
  self.linkProtocols = []
76
114
  self.secret = None
77
115
  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.0
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,14 +1,14 @@
1
1
  wslink/LICENSE,sha256=I44UH7kDVqxDLnnlOWw_hFL2Fz7RjQ_4vPzZv9NYgTU,1483
2
- wslink/__init__.py,sha256=7Uyn6lk5LhykHbBwQ9pwSmX1YhxDanD9TT6H95DXzhk,2709
2
+ wslink/__init__.py,sha256=qdLGVhzpEXBoraTnLr5S-8bX9oK8pZCQWe8rZRlwAco,2946
3
3
  wslink/chunking.py,sha256=1DJlGG6fjknGFrqPOtqUUc5tCrijldP7Kdx56d5e3Wg,7337
4
4
  wslink/launcher.py,sha256=8VMs3juObLkyGYQFNLjMoo4qFpKIcxWz0kS-af-DKO4,21170
5
- wslink/protocol.py,sha256=V5kqfHebfyXzmZq0-ua0SjDkOXdQmx1Mu29G3hORgx0,15971
6
- wslink/publish.py,sha256=9G5TXqyGr-LCo_LwHYhzif6lhG2iXDvEBmEgwR8fh1M,1437
5
+ wslink/protocol.py,sha256=T3F_Y_pZ4qXUApjpDoi9zXDkDvp3qnlKQICj2jnPlsA,16744
6
+ wslink/publish.py,sha256=Xyv9piZT4HxO5l_SA7zeReH8t6tisVHUgU57Hjzgkcg,1595
7
7
  wslink/relay.py,sha256=E8Lzu2Ay7KbOheN1-ArAZawo8lLqdDgJXOZSBuMknYs,86
8
8
  wslink/server.py,sha256=yvhCjpzPOfhbZrpDvW9i4H_uSyuQAe3ZOP-BRBmgHQA,9326
9
9
  wslink/ssl_context.py,sha256=hNOJJCdrStws1Qf6vPvY4vTk9Bf8J5d90W3fS0cRv8o,2290
10
10
  wslink/uri.py,sha256=woCQ4yChUqTMg9IT6YYDtUYeKmCg7OUCEgeBGA-19DY,384
11
- wslink/websocket.py,sha256=pBiWqkL8Zn8LuSJ9nv3yA-KjEynbolOQ2gLHtQFJ2Ic,4611
11
+ wslink/websocket.py,sha256=Gdp8ll5z3wq6gBU0GSVu2iq_o598Mxt8jQ1wZi6Z87A,5520
12
12
  wslink/backends/__init__.py,sha256=cyJGjm-YyBSyOEX81owyTbJ3YnrA6dB7--B4LnsEtHI,1214
13
13
  wslink/backends/aiohttp/__init__.py,sha256=Bc3uPVrhNXnn5Xt3Tck2Th-dsysz1VAnQdh8WzRn1y0,9253
14
14
  wslink/backends/aiohttp/launcher.py,sha256=gHNMvtgNHEwBN_QBRDSCrTp2B4K1PsfV81rKaHi7Cxo,8897
@@ -19,7 +19,7 @@ wslink/backends/jupyter/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOy
19
19
  wslink/backends/jupyter/core.py,sha256=PCQN-uZPFROnRv8B5dNwnwHV67o4Bpme3_Z6V-zbOUA,3864
20
20
  wslink/backends/tornado/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOyNIxnGs,112
21
21
  wslink/backends/tornado/core.py,sha256=tPMkkhWuO_ovkisVim0zcegwZKEAG4IRUdd_O_0a_R0,2157
22
- wslink-2.1.3.dist-info/METADATA,sha256=MY8aB802QkaDhIEEw7ekhqlW371QtyqLk4uws5kMqwY,3120
23
- wslink-2.1.3.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
24
- wslink-2.1.3.dist-info/top_level.txt,sha256=N0d8eqvhwhfW1p1yPTmvxlbzhjz7ZyhBfysNvaFqpQY,7
25
- wslink-2.1.3.dist-info/RECORD,,
22
+ wslink-2.2.0.dist-info/METADATA,sha256=SEj4XM87pZO5pMxU6WTxYxyGz7rZdxFOspQkHEYTkEg,3120
23
+ wslink-2.2.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
24
+ wslink-2.2.0.dist-info/top_level.txt,sha256=N0d8eqvhwhfW1p1yPTmvxlbzhjz7ZyhBfysNvaFqpQY,7
25
+ wslink-2.2.0.dist-info/RECORD,,
File without changes