wslink 2.2.1__tar.gz → 2.3.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {wslink-2.2.1 → wslink-2.3.0}/PKG-INFO +1 -1
- {wslink-2.2.1 → wslink-2.3.0}/setup.cfg +1 -1
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/aiohttp/__init__.py +3 -1
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/generic/core.py +1 -1
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/jupyter/core.py +1 -41
- wslink-2.3.0/src/wslink/emitter.py +63 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/protocol.py +15 -3
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/websocket.py +12 -3
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink.egg-info/PKG-INFO +1 -1
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink.egg-info/SOURCES.txt +1 -0
- {wslink-2.2.1 → wslink-2.3.0}/MANIFEST.in +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/README.rst +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/setup.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/LICENSE +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/__init__.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/__init__.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/aiohttp/launcher.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/aiohttp/relay.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/generic/__init__.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/jupyter/__init__.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/tornado/__init__.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/backends/tornado/core.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/chunking.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/launcher.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/publish.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/relay.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/server.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/ssl_context.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink/uri.py +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink.egg-info/dependency_links.txt +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink.egg-info/requires.txt +0 -0
- {wslink-2.2.1 → wslink-2.3.0}/src/wslink.egg-info/top_level.txt +0 -0
@@ -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[
|
97
|
+
self.app[STATE_KEY] = {}
|
96
98
|
|
97
99
|
# -------------------------------------------------------------------------
|
98
100
|
# Server status
|
@@ -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__()
|
@@ -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)
|
@@ -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
|
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
|
-
|
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
|
-
|
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,
|
@@ -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
|
11
|
-
from
|
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
|
-
|
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
|
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
|
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
|
File without changes
|
File without changes
|