wslink 2.2.1__py3-none-any.whl → 2.3.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,6 +23,8 @@ HTTP_HEADERS: str | None = os.environ.get("WSLINK_HTTP_HEADERS") # path to json
23
23
  if HTTP_HEADERS and Path(HTTP_HEADERS).exists():
24
24
  HTTP_HEADERS: dict = json.loads(Path(HTTP_HEADERS).read_text())
25
25
 
26
+ STATE_KEY = aiohttp_web.AppKey("state", str)
27
+
26
28
  logger = logging.getLogger(__name__)
27
29
 
28
30
  # -----------------------------------------------------------------------------
@@ -92,7 +94,7 @@ class WebAppServer(AbstractWebApp):
92
94
  self.app.router.add_route("GET", "/", _root_handler)
93
95
  self.app.add_routes(routes)
94
96
 
95
- self.app["state"] = {}
97
+ self.app[STATE_KEY] = {}
96
98
 
97
99
  # -------------------------------------------------------------------------
98
100
  # Server status
@@ -56,7 +56,7 @@ class WsConnection:
56
56
 
57
57
 
58
58
  class WsEndpoint(WslinkHandler):
59
- def __init__(self, protocol=None, web_app=None):
59
+ def __init__(self, protocol, web_app=None):
60
60
  super().__init__(protocol, web_app)
61
61
 
62
62
  async def connect(self):
@@ -1,49 +1,9 @@
1
- import asyncio
2
1
  from functools import partial
2
+ from wslink.emitter import EventEmitter
3
3
  from wslink.backends.generic.core import GenericServer
4
4
  from IPython.core.getipython import get_ipython
5
5
 
6
6
 
7
- class EventEmitter:
8
- def __init__(self):
9
- self._listeners = {}
10
-
11
- def clear(self):
12
- self._listeners = {}
13
-
14
- def emit(self, event, *args, **kwargs):
15
- listeners = self._listeners.get(event)
16
- if listeners is None:
17
- return
18
-
19
- loop = asyncio.get_running_loop()
20
- coroutine_run = (
21
- loop.create_task if (loop and loop.is_running()) else asyncio.run
22
- )
23
-
24
- for listener in listeners:
25
- if asyncio.iscoroutinefunction(listener):
26
- coroutine_run(listener(*args, **kwargs))
27
- else:
28
- listener(*args, **kwargs)
29
-
30
- def add_event_listener(self, event, listener):
31
- listeners = self._listeners.get(event)
32
- if listeners is None:
33
- listeners = set()
34
- self._listeners[event] = listeners
35
-
36
- listeners.add(listener)
37
-
38
- def remove_event_listener(self, event, listener):
39
- listeners = self._listeners.get(event)
40
- if listeners is None:
41
- return
42
-
43
- if listener in listeners:
44
- listeners.remove(listener)
45
-
46
-
47
7
  class WsJupyterComm(EventEmitter):
48
8
  def __init__(self, kernel=None):
49
9
  super().__init__()
wslink/emitter.py ADDED
@@ -0,0 +1,63 @@
1
+ from typing import TypeVar, Generic, LiteralString
2
+
3
+ import asyncio
4
+ import functools
5
+
6
+
7
+ T = TypeVar("T", bound=LiteralString)
8
+
9
+
10
+ class EventEmitter(Generic[T]):
11
+ def __init__(self):
12
+ self._listeners = {}
13
+
14
+ def clear(self):
15
+ self._listeners = {}
16
+
17
+ def __call__(self, event: T, *args, **kwargs):
18
+ self.emit(event, *args, **kwargs)
19
+
20
+ def __getattr__(self, name: T):
21
+ return functools.partial(self.emit, name)
22
+
23
+ def emit(self, event: T, *args, **kwargs):
24
+ listeners = self._listeners.get(event)
25
+ if listeners is None:
26
+ return
27
+
28
+ loop = asyncio.get_running_loop()
29
+ coroutine_run = (
30
+ loop.create_task if (loop and loop.is_running()) else asyncio.run
31
+ )
32
+
33
+ for listener in listeners:
34
+ if asyncio.iscoroutinefunction(listener):
35
+ coroutine_run(listener(*args, **kwargs))
36
+ else:
37
+ listener(*args, **kwargs)
38
+
39
+ def add_event_listener(self, event: T, listener):
40
+ listeners = self._listeners.get(event)
41
+ if listeners is None:
42
+ listeners = set()
43
+ self._listeners[event] = listeners
44
+
45
+ listeners.add(listener)
46
+
47
+ def remove_event_listener(self, event: T, listener):
48
+ listeners = self._listeners.get(event)
49
+ if listeners is None:
50
+ return
51
+
52
+ if listener in listeners:
53
+ listeners.remove(listener)
54
+
55
+ def has(self, event: T):
56
+ return self.listeners_count(event) > 0
57
+
58
+ def listeners_count(self, event: T):
59
+ listeners = self._listeners.get(event)
60
+ if listeners is None:
61
+ return 0
62
+
63
+ return len(listeners)
wslink/protocol.py CHANGED
@@ -9,6 +9,7 @@ import traceback
9
9
  from wslink import schedule_coroutine
10
10
  from wslink.publish import PublishManager
11
11
  from wslink.chunking import generate_chunks, UnChunker
12
+ from wslink.websocket import ServerProtocol
12
13
 
13
14
  # from http://www.jsonrpc.org/specification, section 5.1
14
15
  METHOD_NOT_FOUND = -32601
@@ -141,7 +142,7 @@ class AbstractWebApp:
141
142
 
142
143
 
143
144
  class WslinkHandler(object):
144
- def __init__(self, protocol=None, web_app=None):
145
+ def __init__(self, protocol: ServerProtocol, web_app=None):
145
146
  self.serverProtocol = protocol
146
147
  self.web_app = web_app
147
148
  self.functionMap = {}
@@ -153,6 +154,7 @@ class WslinkHandler(object):
153
154
  self.pub_manager = PublishManager()
154
155
  self.unchunkers = {}
155
156
  self.network_monitor = protocol.network_monitor
157
+ self.log_emitter = protocol.log_emitter
156
158
 
157
159
  # Build the rpc method dictionary, assuming we were given a serverprotocol
158
160
  if self.getServerProtocol():
@@ -253,7 +255,9 @@ class WslinkHandler(object):
253
255
 
254
256
  async def onMessage(self, is_binary, msg, client_id):
255
257
  if not is_binary:
256
- logger.critical("wslink is not expecting text message:\n> %s", msg.data)
258
+ error_message = "wslink is not expecting text message:\n> %s"
259
+ logger.critical(error_message, msg.data)
260
+ self.log_emitter.critical(error_message % msg.data)
257
261
  return
258
262
 
259
263
  full_message = self.unchunkers[client_id].process_chunk(msg.data)
@@ -262,7 +266,13 @@ class WslinkHandler(object):
262
266
  await self.onCompleteMessage(full_message, client_id)
263
267
 
264
268
  async def onCompleteMessage(self, rpc, client_id):
265
- logger.debug("wslink incoming msg %s", self.payloadWithSecretStripped(rpc))
269
+ debug_message = "wslink incoming msg %s"
270
+ stripped_payload = self.payloadWithSecretStripped(rpc)
271
+ logger.debug(debug_message, stripped_payload)
272
+
273
+ if self.log_emitter.has("debug"):
274
+ self.log_emitter.debug(debug_message % stripped_payload)
275
+
266
276
  if "id" not in rpc:
267
277
  return
268
278
 
@@ -295,6 +305,7 @@ class WslinkHandler(object):
295
305
 
296
306
  # No matching method found
297
307
  if not methodName in self.functionMap:
308
+ self.log_emitter.error(f"Method not found: {methodName}")
298
309
  with self.network_monitor:
299
310
  await self.sendWrappedError(
300
311
  rpcid,
@@ -328,6 +339,7 @@ class WslinkHandler(object):
328
339
  logger.error("Exception raised")
329
340
  logger.error(repr(e_inst))
330
341
  logger.error(captured_trace)
342
+ self.log_emitter.exception(e_inst)
331
343
  with self.network_monitor:
332
344
  await self.sendWrappedError(
333
345
  rpcid,
wslink/websocket.py CHANGED
@@ -6,9 +6,12 @@ ServerProtocol to hook all the needed LinkProtocols together.
6
6
 
7
7
  import logging
8
8
  import asyncio
9
+ from typing import Literal
10
+ from dataclasses import dataclass
9
11
 
10
- from . import register as exportRpc
11
- from . import schedule_callback
12
+ from wslink import register as exportRpc
13
+ from wslink import schedule_callback
14
+ from wslink.emitter import EventEmitter
12
15
 
13
16
  logger = logging.getLogger(__name__)
14
17
 
@@ -117,6 +120,9 @@ class NetworkMonitor:
117
120
  await self.event.wait()
118
121
 
119
122
 
123
+ LogEmitterEvents = Literal["exception", "error", "critical", "info", "debug"]
124
+
125
+
120
126
  class ServerProtocol(object):
121
127
  """
122
128
  Defines the core server protocol for wslink. Gathers a list of LinkProtocol
@@ -125,6 +131,7 @@ class ServerProtocol(object):
125
131
 
126
132
  def __init__(self):
127
133
  self.network_monitor = NetworkMonitor()
134
+ self.log_emitter = EventEmitter[LogEmitterEvents]()
128
135
  self.linkProtocols = []
129
136
  self.secret = None
130
137
  self.initialize()
@@ -169,7 +176,9 @@ class ServerProtocol(object):
169
176
  try:
170
177
  self.linkProtocols.remove(protocol)
171
178
  except ValueError as e:
172
- logger.error("Link protocol missing from registered list.")
179
+ error_message = "Link protocol missing from registered list."
180
+ logger.error(error_message)
181
+ self.log_emitter("error", error_message)
173
182
 
174
183
  def getLinkProtocols(self):
175
184
  return self.linkProtocols
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wslink
3
- Version: 2.2.1
3
+ Version: 2.3.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,25 +1,26 @@
1
1
  wslink/LICENSE,sha256=I44UH7kDVqxDLnnlOWw_hFL2Fz7RjQ_4vPzZv9NYgTU,1483
2
2
  wslink/__init__.py,sha256=qdLGVhzpEXBoraTnLr5S-8bX9oK8pZCQWe8rZRlwAco,2946
3
3
  wslink/chunking.py,sha256=1DJlGG6fjknGFrqPOtqUUc5tCrijldP7Kdx56d5e3Wg,7337
4
+ wslink/emitter.py,sha256=4nm5fOwfvSb5tHq5cZtUsF8YNpuphl05QZQzNHljfRU,1669
4
5
  wslink/launcher.py,sha256=8VMs3juObLkyGYQFNLjMoo4qFpKIcxWz0kS-af-DKO4,21170
5
- wslink/protocol.py,sha256=T3F_Y_pZ4qXUApjpDoi9zXDkDvp3qnlKQICj2jnPlsA,16744
6
+ wslink/protocol.py,sha256=IUvdYzXaaKP4qllJps6uQ8zvbipbH6MVwU6o6f60Kuc,17265
6
7
  wslink/publish.py,sha256=Xyv9piZT4HxO5l_SA7zeReH8t6tisVHUgU57Hjzgkcg,1595
7
8
  wslink/relay.py,sha256=E8Lzu2Ay7KbOheN1-ArAZawo8lLqdDgJXOZSBuMknYs,86
8
9
  wslink/server.py,sha256=yvhCjpzPOfhbZrpDvW9i4H_uSyuQAe3ZOP-BRBmgHQA,9326
9
10
  wslink/ssl_context.py,sha256=hNOJJCdrStws1Qf6vPvY4vTk9Bf8J5d90W3fS0cRv8o,2290
10
11
  wslink/uri.py,sha256=woCQ4yChUqTMg9IT6YYDtUYeKmCg7OUCEgeBGA-19DY,384
11
- wslink/websocket.py,sha256=wOuLfYeM72Ikzc9YA0qZjj6EfSXXQp-8p0uP-H9gapE,6004
12
+ wslink/websocket.py,sha256=T47xgY6fgQQqDqcxmZpamIEvhQtvw_TWGeF2Es1dIpI,6350
12
13
  wslink/backends/__init__.py,sha256=cyJGjm-YyBSyOEX81owyTbJ3YnrA6dB7--B4LnsEtHI,1214
13
- wslink/backends/aiohttp/__init__.py,sha256=Bc3uPVrhNXnn5Xt3Tck2Th-dsysz1VAnQdh8WzRn1y0,9253
14
+ wslink/backends/aiohttp/__init__.py,sha256=SSK5a_lxuEZGyW_mMMipgvAhJQQLhxAImR3jRokFchM,9301
14
15
  wslink/backends/aiohttp/launcher.py,sha256=gHNMvtgNHEwBN_QBRDSCrTp2B4K1PsfV81rKaHi7Cxo,8897
15
16
  wslink/backends/aiohttp/relay.py,sha256=oZAzIQTpsQaObWXaa-_VtoTOUQALC_QLDd9UvWspYaU,13311
16
17
  wslink/backends/generic/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOyNIxnGs,112
17
- wslink/backends/generic/core.py,sha256=JuLeT6by2NA7y3qQL_aZCYEsAREZmshDZAvSnO1T_bo,4363
18
+ wslink/backends/generic/core.py,sha256=-tlNgXdvEr8BrwosxBWlUJVk5yiGMLfbK3EWs5VEKNM,4358
18
19
  wslink/backends/jupyter/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOyNIxnGs,112
19
- wslink/backends/jupyter/core.py,sha256=PCQN-uZPFROnRv8B5dNwnwHV67o4Bpme3_Z6V-zbOUA,3864
20
+ wslink/backends/jupyter/core.py,sha256=F8R3uH4m6RHCrHHRiA5UAgDOLdyGbpuCSAgCZxANREk,2794
20
21
  wslink/backends/tornado/__init__.py,sha256=Qu65gWsd2xCSsxybnDtEDI5vMjHN-F5jgPZOyNIxnGs,112
21
22
  wslink/backends/tornado/core.py,sha256=tPMkkhWuO_ovkisVim0zcegwZKEAG4IRUdd_O_0a_R0,2157
22
- wslink-2.2.1.dist-info/METADATA,sha256=Ng1NUSdljWz0EDJg7yppAY29AZ2G-V3XbJDU0etWiLQ,3120
23
- wslink-2.2.1.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
24
- wslink-2.2.1.dist-info/top_level.txt,sha256=N0d8eqvhwhfW1p1yPTmvxlbzhjz7ZyhBfysNvaFqpQY,7
25
- wslink-2.2.1.dist-info/RECORD,,
23
+ wslink-2.3.0.dist-info/METADATA,sha256=hisMCyrZAwxLjXz24KZ74-VaFyHvusXzfpnzU0U5wJM,3120
24
+ wslink-2.3.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
25
+ wslink-2.3.0.dist-info/top_level.txt,sha256=N0d8eqvhwhfW1p1yPTmvxlbzhjz7ZyhBfysNvaFqpQY,7
26
+ wslink-2.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5