Skip to content

Commit 63bb2a9

Browse files
authored
switch to jupyter_events (#862)
* switch to jupyter_events * update to jupyter_events 0.2.0 * remove unused import * fix payload in events unit tests * update to use latest jupyter_events * add unit test for extensions * get tests working
1 parent 0028483 commit 63bb2a9

File tree

11 files changed

+113
-109
lines changed

11 files changed

+113
-109
lines changed

jupyter_server/base/handlers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import prometheus_client
2121
from jinja2 import TemplateNotFound
2222
from jupyter_core.paths import is_hidden
23+
from jupyter_events import EventLogger
2324
from tornado import web
2425
from tornado.log import app_log
2526
from traitlets.config import Application
@@ -335,8 +336,8 @@ def config_manager(self):
335336
return self.settings["config_manager"]
336337

337338
@property
338-
def event_bus(self):
339-
return self.settings["event_bus"]
339+
def event_logger(self) -> EventLogger:
340+
return self.settings["event_logger"]
340341

341342
# ---------------------------------------------------------------
342343
# CORS

jupyter_server/serverapp.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from jupyter_client.session import Session
6262
from jupyter_core.application import JupyterApp, base_aliases, base_flags
6363
from jupyter_core.paths import jupyter_runtime_dir
64+
from jupyter_events.logger import EventLogger
6465
from nbformat.sign import NotebookNotary
6566
from traitlets import (
6667
Any,
@@ -123,7 +124,6 @@
123124
AsyncContentsManager,
124125
ContentsManager,
125126
)
126-
from jupyter_server.services.events.bus import EventBus
127127
from jupyter_server.services.kernels.kernelmanager import (
128128
AsyncMappingKernelManager,
129129
MappingKernelManager,
@@ -212,7 +212,7 @@ def __init__(
212212
session_manager,
213213
kernel_spec_manager,
214214
config_manager,
215-
event_bus,
215+
event_logger,
216216
extra_services,
217217
log,
218218
base_url,
@@ -248,7 +248,7 @@ def __init__(
248248
session_manager,
249249
kernel_spec_manager,
250250
config_manager,
251-
event_bus,
251+
event_logger,
252252
extra_services,
253253
log,
254254
base_url,
@@ -270,7 +270,7 @@ def init_settings(
270270
session_manager,
271271
kernel_spec_manager,
272272
config_manager,
273-
event_bus,
273+
event_logger,
274274
extra_services,
275275
log,
276276
base_url,
@@ -359,7 +359,7 @@ def init_settings(
359359
config_manager=config_manager,
360360
authorizer=authorizer,
361361
identity_provider=identity_provider,
362-
event_bus=event_bus,
362+
event_logger=event_logger,
363363
# handlers
364364
extra_services=extra_services,
365365
# Jupyter stuff
@@ -770,7 +770,7 @@ class ServerApp(JupyterApp):
770770
GatewaySessionManager,
771771
GatewayClient,
772772
Authorizer,
773-
EventBus,
773+
EventLogger,
774774
]
775775

776776
subcommands = dict(
@@ -1552,10 +1552,10 @@ def _default_kernel_spec_manager_class(self):
15521552
),
15531553
)
15541554

1555-
event_bus = Instance(
1556-
EventBus,
1555+
event_logger = Instance(
1556+
EventLogger,
15571557
allow_none=True,
1558-
help="An EventBus for emitting structured event data from Jupyter Server and extensions.",
1558+
help="An EventLogger for emitting structured event data from Jupyter Server and extensions.",
15591559
)
15601560

15611561
info_file = Unicode()
@@ -1948,9 +1948,9 @@ def init_logging(self):
19481948
logger.parent = self.log
19491949
logger.setLevel(self.log.level)
19501950

1951-
def init_eventbus(self):
1951+
def init_event_logger(self):
19521952
"""Initialize the Event Bus."""
1953-
self.event_bus = EventBus.instance(parent=self)
1953+
self.event_logger = EventLogger(parent=self)
19541954

19551955
def init_webapp(self):
19561956
"""initialize tornado webapp"""
@@ -2012,7 +2012,7 @@ def init_webapp(self):
20122012
self.session_manager,
20132013
self.kernel_spec_manager,
20142014
self.config_manager,
2015-
self.event_bus,
2015+
self.event_logger,
20162016
self.extra_services,
20172017
self.log,
20182018
self.base_url,
@@ -2487,7 +2487,7 @@ def initialize(
24872487
if find_extensions:
24882488
self.find_server_extensions()
24892489
self.init_logging()
2490-
self.init_eventbus()
2490+
self.init_event_logger()
24912491
self.init_server_extensions()
24922492

24932493
# Special case the starter extension and load
@@ -2814,8 +2814,6 @@ async def _cleanup(self):
28142814
await self.cleanup_kernels()
28152815
if getattr(self, "session_manager", None):
28162816
self.session_manager.close()
2817-
if getattr(self, "event_bus", None):
2818-
self.event_bus.clear_instance()
28192817

28202818
def start_ioloop(self):
28212819
"""Start the IO Loop."""

jupyter_server/services/events/bus.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

jupyter_server/services/events/handlers.py

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
33
.. versionadded:: 2.0
44
"""
5-
import logging
5+
import json
66
from datetime import datetime
77
from typing import Any, Dict, Optional
88

9-
from jupyter_telemetry.eventlog import _skip_message
10-
from pythonjsonlogger import jsonlogger
9+
from jupyter_events import EventLogger
1110
from tornado import web, websocket
1211

1312
from jupyter_server.auth import authorized
@@ -18,18 +17,6 @@
1817
AUTH_RESOURCE = "events"
1918

2019

21-
class WebSocketLoggingHandler(logging.Handler):
22-
"""Python logging handler that routes records to a Tornado websocket."""
23-
24-
def __init__(self, websocket, *args, **kwargs):
25-
super().__init__(*args, **kwargs)
26-
self.websocket = websocket
27-
28-
def emit(self, record):
29-
"""Emit the message across the websocket"""
30-
self.websocket.write_message(record.msg)
31-
32-
3320
class SubscribeWebsocket(
3421
JupyterHandler,
3522
websocket.WebSocketHandler,
@@ -58,26 +45,23 @@ async def get(self, *args, **kwargs):
5845
res = super().get(*args, **kwargs)
5946
await res
6047

48+
async def event_listener(self, logger: EventLogger, schema_id: str, data: dict) -> None:
49+
capsule = dict(schema_id=schema_id, **data)
50+
self.write_message(json.dumps(capsule))
51+
6152
def open(self):
6253
"""Routes events that are emitted by Jupyter Server's
6354
EventBus to a WebSocket client in the browser.
6455
"""
65-
self.logging_handler = WebSocketLoggingHandler(self)
66-
# Add a JSON formatter to the handler.
67-
formatter = jsonlogger.JsonFormatter(json_serializer=_skip_message)
68-
self.logging_handler.setFormatter(formatter)
69-
# To do: add an eventlog.add_handler method to jupyter_telemetry.
70-
self.event_bus.log.addHandler(self.logging_handler)
71-
self.event_bus.handlers.append(self.logging_handler)
56+
self.event_logger.add_listener(listener=self.event_listener)
7257

7358
def on_close(self):
74-
self.event_bus.log.removeHandler(self.logging_handler)
75-
self.event_bus.handlers.remove(self.logging_handler)
59+
self.event_logger.remove_listener(listener=self.event_listener)
7660

7761

7862
def validate_model(data: Dict[str, Any]) -> None:
7963
"""Validates for required fields in the JSON request body"""
80-
required_keys = {"schema_name", "version", "event"}
64+
required_keys = {"schema_id", "version", "data"}
8165
for key in required_keys:
8266
if key not in data:
8367
raise web.HTTPError(400, f"Missing `{key}` in the JSON request body.")
@@ -115,10 +99,9 @@ async def post(self):
11599

116100
try:
117101
validate_model(payload)
118-
self.event_bus.record_event(
119-
schema_name=payload.get("schema_name"),
120-
version=payload.get("version"),
121-
event=payload.get("event"),
102+
self.event_logger.emit(
103+
schema_id=payload.get("schema_id"),
104+
data=payload.get("data"),
122105
timestamp_override=get_timestamp(payload),
123106
)
124107
self.set_status(204)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dependencies = [
4444
"tornado>=6.1.0",
4545
"traitlets>=5.1",
4646
"websocket-client",
47-
"jupyter_telemetry"
47+
"jupyter_events>=0.4.0"
4848
]
4949

5050
[project.urls]

tests/extension/mockextensions/app.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22

3+
from jupyter_events import EventLogger
4+
from jupyter_events.schema_registry import SchemaRegistryException
35
from traitlets import List, Unicode
46

57
from jupyter_server.base.handlers import JupyterHandler
@@ -11,16 +13,28 @@
1113

1214
STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")
1315

14-
# Function that makes these extensions discoverable
15-
# by the test functions.
16+
EVENT_SCHEMA = """\
17+
$id: https://events.jupyter.org/mockapp/v1/test
18+
version: 1
19+
properties:
20+
msg:
21+
type: string
22+
required:
23+
- msg
24+
"""
1625

1726

27+
# Function that makes these extensions discoverable
28+
# by the test functions.
1829
def _jupyter_server_extension_points():
1930
return [{"module": __name__, "app": MockExtensionApp}]
2031

2132

2233
class MockExtensionHandler(ExtensionHandlerMixin, JupyterHandler):
2334
def get(self):
35+
self.event_logger.emit(
36+
schema_id="https://events.jupyter.org/mockapp/v1/test", data={"msg": "Hello, world!"}
37+
)
2438
self.finish(self.config.mock_trait)
2539

2640

@@ -45,6 +59,16 @@ class MockExtensionApp(ExtensionAppJinjaMixin, ExtensionApp):
4559
def get_extension_package():
4660
return "tests.extension.mockextensions"
4761

62+
def initialize_settings(self):
63+
# Only add this event if it hasn't already been added.
64+
# Log the error if it fails, but don't crash the app.
65+
try:
66+
elogger: EventLogger = self.serverapp.event_logger
67+
elogger.register_event_schema(EVENT_SCHEMA)
68+
except SchemaRegistryException as err:
69+
self.log.error(err)
70+
pass
71+
4872
def initialize_handlers(self):
4973
self.handlers.append(("/mock", MockExtensionHandler))
5074
self.handlers.append(("/mock_template", MockExtensionTemplateHandler))

tests/extension/test_app.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import json
2+
from io import StringIO
3+
from logging import StreamHandler
14
from typing import Any
25

36
import pytest
@@ -171,3 +174,18 @@ async def _stop(*args):
171174

172175
# check the shutdown method was called twice
173176
assert calls == 2
177+
178+
179+
async def test_events(jp_serverapp, jp_fetch):
180+
stream = StringIO()
181+
handler = StreamHandler(stream)
182+
jp_serverapp.event_logger.register_handler(handler)
183+
184+
await jp_fetch("mock")
185+
186+
handler.flush()
187+
output = json.loads(stream.getvalue())
188+
# Clear the sink.
189+
stream.truncate(0)
190+
stream.seek(0)
191+
assert output["msg"] == "Hello, world!"

tests/services/events/mock_event.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ type: object
77
properties:
88
event_message:
99
title: Event Messages
10+
categories:
11+
- unrestricted
1012
description: |
1113
Mock event message to read.
1214
required:

tests/services/events/mockextension/mock_extension.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@
77
class MockEventHandler(JupyterHandler):
88
def get(self):
99
# Emit an event.
10-
self.event_bus.record_event(
11-
schema_name="event.mockextension.jupyter.org/message",
12-
version=1,
13-
event={"event_message": "Hello world, from mock extension!"},
10+
self.event_logger.emit(
11+
schema_id="event.mockextension.jupyter.org/message",
12+
data={"event_message": "Hello world, from mock extension!"},
1413
)
1514

1615

1716
def _load_jupyter_server_extension(serverapp):
1817
# Register a schema with the EventBus
1918
schema_file = pathlib.Path(__file__).parent / "mock_extension_event.yaml"
20-
serverapp.event_bus.register_schema_file(schema_file)
19+
serverapp.event_logger.register_event_schema(schema_file)
2120
serverapp.web_app.add_handlers(
2221
".*$", [(url_path_join(serverapp.base_url, "/mock/event"), MockEventHandler)]
2322
)

tests/services/events/mockextension/mock_extension_event.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ type: object
77
properties:
88
event_message:
99
title: Event Message
10+
categories:
11+
- unrestricted
1012
description: |
1113
Mock event message to read.
1214
required:

0 commit comments

Comments
 (0)