From 13922f6bb0dda944d30660abb5658e4a0d5ad22a Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Fri, 29 Apr 2022 16:58:15 -0700 Subject: [PATCH 1/9] basic event bus --- jupyter_server/base/handlers.py | 4 + jupyter_server/serverapp.py | 19 ++++ jupyter_server/services/events/__init__.py | 0 jupyter_server/services/events/bus.py | 12 +++ jupyter_server/services/events/handlers.py | 46 +++++++++ setup.cfg | 99 +++++++++++++++++++ tests/services/events/__init__.py | 0 tests/services/events/mock_event.yaml | 13 +++ .../services/events/mockextension/__init__.py | 6 ++ .../events/mockextension/mock_extension.py | 23 +++++ .../mockextension/mock_extension_event.yaml | 13 +++ tests/services/events/test_api.py | 32 ++++++ tests/services/events/test_extension.py | 24 +++++ 13 files changed, 291 insertions(+) create mode 100644 jupyter_server/services/events/__init__.py create mode 100644 jupyter_server/services/events/bus.py create mode 100644 jupyter_server/services/events/handlers.py create mode 100644 setup.cfg create mode 100644 tests/services/events/__init__.py create mode 100644 tests/services/events/mock_event.yaml create mode 100644 tests/services/events/mockextension/__init__.py create mode 100644 tests/services/events/mockextension/mock_extension.py create mode 100644 tests/services/events/mockextension/mock_extension_event.yaml create mode 100644 tests/services/events/test_api.py create mode 100644 tests/services/events/test_extension.py diff --git a/jupyter_server/base/handlers.py b/jupyter_server/base/handlers.py index f0c80173d7..62104a80bb 100644 --- a/jupyter_server/base/handlers.py +++ b/jupyter_server/base/handlers.py @@ -340,6 +340,10 @@ def kernel_spec_manager(self): def config_manager(self): return self.settings["config_manager"] + @property + def event_bus(self): + return self.settings["event_bus"] + # --------------------------------------------------------------- # CORS # --------------------------------------------------------------- diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 57dd76a365..c380291603 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -120,6 +120,7 @@ AsyncContentsManager, ContentsManager, ) +from jupyter_server.services.events.bus import EventBus from jupyter_server.services.kernels.kernelmanager import ( AsyncMappingKernelManager, MappingKernelManager, @@ -164,6 +165,7 @@ sessions=["jupyter_server.services.sessions.handlers"], shutdown=["jupyter_server.services.shutdown"], view=["jupyter_server.view.handlers"], + events=["jupyter_server.services.events.handlers"], ) # Added for backwards compatibility from classic notebook server. @@ -207,6 +209,7 @@ def __init__( session_manager, kernel_spec_manager, config_manager, + event_bus, extra_services, log, base_url, @@ -242,6 +245,7 @@ def __init__( session_manager, kernel_spec_manager, config_manager, + event_bus, extra_services, log, base_url, @@ -263,6 +267,7 @@ def init_settings( session_manager, kernel_spec_manager, config_manager, + event_bus, extra_services, log, base_url, @@ -354,6 +359,7 @@ def init_settings( config_manager=config_manager, authorizer=authorizer, identity_provider=identity_provider, + event_bus=event_bus, # handlers extra_services=extra_services, # Jupyter stuff @@ -769,6 +775,7 @@ class ServerApp(JupyterApp): GatewaySessionManager, GatewayClient, Authorizer, + EventBus, ] subcommands = dict( @@ -794,6 +801,7 @@ class ServerApp(JupyterApp): "sessions", "shutdown", "view", + "events", ) _log_formatter_cls = LogFormatter @@ -1561,6 +1569,11 @@ def _default_kernel_spec_manager_class(self): ), ) + event_bus = Instance( + EventBus, + help="An EventBus for emitting structured event data from Jupyter Server and extensions.", + ) + info_file = Unicode() @default("info_file") @@ -1906,6 +1919,10 @@ def init_logging(self): logger.parent = self.log logger.setLevel(self.log.level) + def init_eventbus(self): + """Initialize the Event Bus.""" + self.event_bus = EventBus.instance(parent=self) + def init_webapp(self): """initialize tornado webapp""" self.tornado_settings["allow_origin"] = self.allow_origin @@ -1970,6 +1987,7 @@ def init_webapp(self): self.session_manager, self.kernel_spec_manager, self.config_manager, + self.event_bus, self.extra_services, self.log, self.base_url, @@ -2436,6 +2454,7 @@ def initialize( if find_extensions: self.find_server_extensions() self.init_logging() + self.init_eventbus() self.init_server_extensions() # Special case the starter extension and load diff --git a/jupyter_server/services/events/__init__.py b/jupyter_server/services/events/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/jupyter_server/services/events/bus.py b/jupyter_server/services/events/bus.py new file mode 100644 index 0000000000..2fa2e6b1b9 --- /dev/null +++ b/jupyter_server/services/events/bus.py @@ -0,0 +1,12 @@ +from jupyter_telemetry.eventlog import EventLog +from traitlets.config import SingletonConfigurable + + +class EventBus(EventLog, SingletonConfigurable): + """A Jupyter EventLog as a Singleton, making it easy to + access from anywhere and log events. + """ + + def record_event(self, *args, **kwargs): + + super().record_event(*args, **kwargs) diff --git a/jupyter_server/services/events/handlers.py b/jupyter_server/services/events/handlers.py new file mode 100644 index 0000000000..de5068c1b9 --- /dev/null +++ b/jupyter_server/services/events/handlers.py @@ -0,0 +1,46 @@ +import logging + +import tornado.web +import tornado.websocket +from jupyter_telemetry.eventlog import _skip_message +from pythonjsonlogger import jsonlogger + +from jupyter_server.base.handlers import JupyterHandler + + +class TornadoWebSocketLoggingHandler(logging.Handler): + """Logging handler that routes records to a Tornado websocket.""" + + def __init__(self, websocket): + super().__init__() + self.websocket = websocket + + def emit(self, record): + """Emit the message across the websocket""" + self.websocket.write_message(record.msg) + + +class SubscribeWebsocket( + JupyterHandler, + tornado.websocket.WebSocketHandler, +): + @property + def event_bus(self): + return self.settings["event_bus"] + + def open(self): + self.logging_handler = TornadoWebSocketLoggingHandler(self) + # Add a JSON formatter to the handler. + formatter = jsonlogger.JsonFormatter(json_serializer=_skip_message) + self.logging_handler.setFormatter(formatter) + # To do: add an eventlog.add_handler method to jupyter_telemetry. + self.event_bus.log.addHandler(self.logging_handler) + self.event_bus.handlers.append(self.logging_handler) + + def on_close(self): + self.event_bus.log.removeHandler(self.logging_handler) + + +default_handlers = [ + (r"/api/events/subscribe", SubscribeWebsocket), +] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..d4c8ae9ee0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,99 @@ +[metadata] +name = jupyter_server +version = attr: jupyter_server.__version__ +description = The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications. +long_description = file: README.md +long_description_content_type = text/markdown +license_files = COPYING.md +author = Jupyter Development Team +author_email = jupyter@googlegroups.com +url = https://jupyter-server.readthedocs.io +platforms = Linux, Mac OS X, Windows +keywords = ipython, jupyter +classifiers = + Development Status :: 5 - Production/Stable + Framework :: Jupyter + Intended Audience :: Developers + Intended Audience :: Science/Research + Intended Audience :: System Administrators + License :: OSI Approved :: BSD License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 +project_urls = + Documentation = https://jupyter-server.readthedocs.io + Funding = https://numfocus.org/donate + Source = https://github.com/jupyter-server/jupyter_server + Tracker = https://github.com/jupyter-server/jupyter_server/issues + +[options] +zip_safe = False +include_package_data = True +packages = find: +package_dir = + "" = "jupyter_server" +python_requires = >=3.7 +install_requires = + anyio>=3.1.0,<4 + argon2-cffi + jinja2 + jupyter_client>=6.1.12 + jupyter_core>=4.7.0 + jupyter_server_terminals + nbconvert>=6.4.4 + nbformat>=5.2.0 + packaging + prometheus_client + pywinpty;os_name=='nt' + pyzmq>=17 + Send2Trash + terminado>=0.8.3 + tornado>=6.1.0 + traitlets>=5.1 + websocket-client + jupyter_telemetry + +[options.extras_require] +test = + coverage + ipykernel + pre-commit + pytest-console-scripts + pytest-cov + pytest-timeout + pytest-tornasync + pytest>=6.0 + requests + +[options.entry_points] +console_scripts = + jupyter-server = jupyter_server.serverapp:main + +[options.packages.find] +exclude = + docs.* + examples.* + tests + tests.* + +[flake8] +ignore = E501, W503, E402 +builtins = c, get_config +exclude = + .cache, + .github, + docs, + setup.py +enable-extensions = G +extend-ignore = + G001, G002, G004, G200, G201, G202, + # black adds spaces around ':' + E203, +per-file-ignores = + # B011: Do not call assert False since python -O removes these calls + # F841 local variable 'foo' is assigned to but never used + tests/*: B011, F841 diff --git a/tests/services/events/__init__.py b/tests/services/events/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/services/events/mock_event.yaml b/tests/services/events/mock_event.yaml new file mode 100644 index 0000000000..c25570e633 --- /dev/null +++ b/tests/services/events/mock_event.yaml @@ -0,0 +1,13 @@ +$id: event.mock.jupyter.com/message +version: 1 +title: Message +description: | + Emit a message +type: object +properties: + event_message: + title: Event Messages + description: | + Mock event message to read. +required: + - event_message diff --git a/tests/services/events/mockextension/__init__.py b/tests/services/events/mockextension/__init__.py new file mode 100644 index 0000000000..cf43b71980 --- /dev/null +++ b/tests/services/events/mockextension/__init__.py @@ -0,0 +1,6 @@ +# Function that makes these extensions discoverable +# by the test functions. +def _jupyter_server_extension_points(): + return [ + {"module": "tests.events.mock_extension"}, + ] diff --git a/tests/services/events/mockextension/mock_extension.py b/tests/services/events/mockextension/mock_extension.py new file mode 100644 index 0000000000..cf5af2d2d0 --- /dev/null +++ b/tests/services/events/mockextension/mock_extension.py @@ -0,0 +1,23 @@ +import pathlib + +from jupyter_server.base.handlers import JupyterHandler +from jupyter_server.utils import url_path_join + + +class MockEventHandler(JupyterHandler): + def get(self): + # Emit an event. + self.event_bus.record_event( + schema_name="event.mockextension.jupyter.com/message", + version=1, + event={"message": "Hello world, from mock extension!"}, + ) + + +def _load_jupyter_server_extension(serverapp): + # Register a schema with the EventBus + schema_file = pathlib.Path(__file__).parent / "mock_event_schema.yaml" + serverapp.event_bus.register_schema_file(schema_file) + serverapp.web_app.add_handlers( + ".*$", [(url_path_join(serverapp.base_url, "/mock/event"), MockEventHandler)] + ) diff --git a/tests/services/events/mockextension/mock_extension_event.yaml b/tests/services/events/mockextension/mock_extension_event.yaml new file mode 100644 index 0000000000..8a131b4151 --- /dev/null +++ b/tests/services/events/mockextension/mock_extension_event.yaml @@ -0,0 +1,13 @@ +$id: event.mockextension.jupyter.com/message +version: 1 +title: Message +description: | + Emit a message +type: object +properties: + event_message: + title: Event Message + description: | + Mock event message to read. +required: + - event_message diff --git a/tests/services/events/test_api.py b/tests/services/events/test_api.py new file mode 100644 index 0000000000..fe3aa32ead --- /dev/null +++ b/tests/services/events/test_api.py @@ -0,0 +1,32 @@ +import json +import pathlib + +import pytest + + +@pytest.fixture +def event_bus(jp_serverapp): + event_bus = jp_serverapp.event_bus + # Register the event schema defined in this directory. + schema_file = pathlib.Path(__file__).parent / "mock_event.yaml" + event_bus.register_schema_file(schema_file) + # + event_bus.allowed_schemas = ["event.mock.jupyter.com/message"] + return event_bus + + +async def test_subscribe_websocket(jp_ws_fetch, event_bus): + # Open a websocket connection. + ws = await jp_ws_fetch("/api/events/subscribe") + + event_bus.record_event( + schema_name="event.mock.jupyter.com/message", + version=1, + event={"event_message": "Hello, world!"}, + ) + message = await ws.read_message() + event_data = json.loads(message) + # Close websocket + ws.close() + + assert event_data.get("event_message") == "Hello, world!" diff --git a/tests/services/events/test_extension.py b/tests/services/events/test_extension.py new file mode 100644 index 0000000000..ed3f9d3f4a --- /dev/null +++ b/tests/services/events/test_extension.py @@ -0,0 +1,24 @@ +import json +import pathlib + +import pytest + + +@pytest.fixture +def event_bus(jp_serverapp): + event_bus = jp_serverapp.event_bus + event_bus.allowed_schemas = ["event.mockextension.jupyter.com/message"] + return event_bus + + +async def test_subscribe_websocket(jp_ws_fetch, jp_fetch, event_bus): + # Open a websocket connection. + ws = await jp_ws_fetch("/api/events/subscribe") + + await jp_fetch("/mock/event") + message = await ws.read_message() + event_data = json.loads(message) + # Close websocket + ws.close() + + assert event_data.get("event_message") == "Hello world, from mock extension!" From accadd0c49c6c3c059b1474c644013ab3ddfd4b5 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Fri, 29 Apr 2022 17:04:26 -0700 Subject: [PATCH 2/9] remove unnecessary super call --- jupyter_server/services/events/bus.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jupyter_server/services/events/bus.py b/jupyter_server/services/events/bus.py index 2fa2e6b1b9..0a4b06d3ac 100644 --- a/jupyter_server/services/events/bus.py +++ b/jupyter_server/services/events/bus.py @@ -6,7 +6,3 @@ class EventBus(EventLog, SingletonConfigurable): """A Jupyter EventLog as a Singleton, making it easy to access from anywhere and log events. """ - - def record_event(self, *args, **kwargs): - - super().record_event(*args, **kwargs) From 2696bd23b1cc35a82cbea81d760637afbde11a64 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Sat, 30 Apr 2022 17:33:53 -0700 Subject: [PATCH 3/9] add auth to the websocket --- jupyter_server/services/events/handlers.py | 41 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/jupyter_server/services/events/handlers.py b/jupyter_server/services/events/handlers.py index de5068c1b9..ddc601c790 100644 --- a/jupyter_server/services/events/handlers.py +++ b/jupyter_server/services/events/handlers.py @@ -1,15 +1,20 @@ +"""A Websocket Handler for emitting Jupyter server events. + +.. versionadded:: 2.0 +""" import logging -import tornado.web -import tornado.websocket from jupyter_telemetry.eventlog import _skip_message from pythonjsonlogger import jsonlogger +from tornado import web, websocket from jupyter_server.base.handlers import JupyterHandler +AUTH_RESOURCE = "events" + class TornadoWebSocketLoggingHandler(logging.Handler): - """Logging handler that routes records to a Tornado websocket.""" + """Python logging handler that routes records to a Tornado websocket.""" def __init__(self, websocket): super().__init__() @@ -22,13 +27,41 @@ def emit(self, record): class SubscribeWebsocket( JupyterHandler, - tornado.websocket.WebSocketHandler, + websocket.WebSocketHandler, ): + """Websocket Handler for listening to eve""" + + auth_resource = AUTH_RESOURCE + + def pre_get(self): + """Handles authentication/authorization when + attempting to subscribe to events emitted by + Jupyter Server's eventbus. + """ + # authenticate the request before opening the websocket + user = self.current_user + if user is None: + self.log.warning("Couldn't authenticate WebSocket connection") + raise web.HTTPError(403) + + # authorize the user. + if not self.authorizer.is_authorized(self, user, "execute", "events"): + raise web.HTTPError(403) + + async def get(self, *args, **kwargs): + self.pre_get() + res = super().get(*args, **kwargs) + await res + @property def event_bus(self): + """Jupyter Server's event bus that emits structured event data.""" return self.settings["event_bus"] def open(self): + """Routes events that are emitted by Jupyter Server's + EventBus to a WebSocket client in the browser. + """ self.logging_handler = TornadoWebSocketLoggingHandler(self) # Add a JSON formatter to the handler. formatter = jsonlogger.JsonFormatter(json_serializer=_skip_message) From 411a7cfce49eddebb2e914cb40c2d2c634414cf5 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Sat, 30 Apr 2022 17:34:50 -0700 Subject: [PATCH 4/9] Clean up the eventlog singleton on server shutdown --- jupyter_server/serverapp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index c380291603..111dbf6a19 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -2781,6 +2781,8 @@ async def _cleanup(self): await self.cleanup_kernels() if getattr(self, "session_manager", None): self.session_manager.close() + if getattr(self, "event_bus", None): + self.event_bus.clear_instance() def start_ioloop(self): """Start the IO Loop.""" From 8de47043fe46ab7f15ff17ece1f94fccb3dc36da Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Sat, 30 Apr 2022 17:35:07 -0700 Subject: [PATCH 5/9] get tests working --- jupyter_server/services/events/bus.py | 11 ++++++++-- .../services/events/mockextension/__init__.py | 6 ++++- .../events/mockextension/mock_extension.py | 4 ++-- tests/services/events/test_extension.py | 22 +++++++++++++------ 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/jupyter_server/services/events/bus.py b/jupyter_server/services/events/bus.py index 0a4b06d3ac..0cbdbf07e9 100644 --- a/jupyter_server/services/events/bus.py +++ b/jupyter_server/services/events/bus.py @@ -1,8 +1,15 @@ +"""An EventBus for use in the Jupyter server. + +.. versionadded:: 2.0 +""" +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. from jupyter_telemetry.eventlog import EventLog from traitlets.config import SingletonConfigurable class EventBus(EventLog, SingletonConfigurable): - """A Jupyter EventLog as a Singleton, making it easy to - access from anywhere and log events. + """A singleton eventlog that behaves as an event + bus for emitting Jupyter Server (and extension) + event data. """ diff --git a/tests/services/events/mockextension/__init__.py b/tests/services/events/mockextension/__init__.py index cf43b71980..ed7c0e9d37 100644 --- a/tests/services/events/mockextension/__init__.py +++ b/tests/services/events/mockextension/__init__.py @@ -1,6 +1,10 @@ +from .mock_extension import _load_jupyter_server_extension + # Function that makes these extensions discoverable # by the test functions. + + def _jupyter_server_extension_points(): return [ - {"module": "tests.events.mock_extension"}, + {"module": "tests.services.events.mockextension"}, ] diff --git a/tests/services/events/mockextension/mock_extension.py b/tests/services/events/mockextension/mock_extension.py index cf5af2d2d0..9d46554063 100644 --- a/tests/services/events/mockextension/mock_extension.py +++ b/tests/services/events/mockextension/mock_extension.py @@ -10,13 +10,13 @@ def get(self): self.event_bus.record_event( schema_name="event.mockextension.jupyter.com/message", version=1, - event={"message": "Hello world, from mock extension!"}, + event={"event_message": "Hello world, from mock extension!"}, ) def _load_jupyter_server_extension(serverapp): # Register a schema with the EventBus - schema_file = pathlib.Path(__file__).parent / "mock_event_schema.yaml" + schema_file = pathlib.Path(__file__).parent / "mock_extension_event.yaml" serverapp.event_bus.register_schema_file(schema_file) serverapp.web_app.add_handlers( ".*$", [(url_path_join(serverapp.base_url, "/mock/event"), MockEventHandler)] diff --git a/tests/services/events/test_extension.py b/tests/services/events/test_extension.py index ed3f9d3f4a..a47943c636 100644 --- a/tests/services/events/test_extension.py +++ b/tests/services/events/test_extension.py @@ -1,24 +1,32 @@ import json -import pathlib import pytest @pytest.fixture -def event_bus(jp_serverapp): - event_bus = jp_serverapp.event_bus - event_bus.allowed_schemas = ["event.mockextension.jupyter.com/message"] - return event_bus +def jp_server_config(): + config = { + "ServerApp": { + "jpserver_extensions": {"tests.services.events.mockextension": True}, + }, + "EventBus": {"allowed_schemas": ["event.mockextension.jupyter.com/message"]}, + } + return config -async def test_subscribe_websocket(jp_ws_fetch, jp_fetch, event_bus): - # Open a websocket connection. +async def test_subscribe_websocket(jp_ws_fetch, jp_fetch): + # Open an event listener websocket ws = await jp_ws_fetch("/api/events/subscribe") + # Hit the extension endpoint that emits an event await jp_fetch("/mock/event") + + # Check the event listener for a message message = await ws.read_message() event_data = json.loads(message) + # Close websocket ws.close() + # Verify that an event message was received. assert event_data.get("event_message") == "Hello world, from mock extension!" From 4e194f679aba6d8d83df9512974d0303687db329 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Sat, 30 Apr 2022 17:47:32 -0700 Subject: [PATCH 6/9] allow empty event bus --- jupyter_server/serverapp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 111dbf6a19..a5f9b99d10 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -1571,6 +1571,7 @@ def _default_kernel_spec_manager_class(self): event_bus = Instance( EventBus, + allow_none=True, help="An EventBus for emitting structured event data from Jupyter Server and extensions.", ) From 71747afa691887b7d4112f2dab19b926e3808a0d Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Sat, 30 Apr 2022 17:59:01 -0700 Subject: [PATCH 7/9] ignore flake 8 error in tests --- pyproject.toml | 1 + setup.cfg | 99 ------------------- .../services/events/mockextension/__init__.py | 2 +- 3 files changed, 2 insertions(+), 100 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 30da9d4446..104ff3a5b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "tornado>=6.1.0", "traitlets>=5.1", "websocket-client", + "jupyter_telemetry" ] [project.readme] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d4c8ae9ee0..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,99 +0,0 @@ -[metadata] -name = jupyter_server -version = attr: jupyter_server.__version__ -description = The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications. -long_description = file: README.md -long_description_content_type = text/markdown -license_files = COPYING.md -author = Jupyter Development Team -author_email = jupyter@googlegroups.com -url = https://jupyter-server.readthedocs.io -platforms = Linux, Mac OS X, Windows -keywords = ipython, jupyter -classifiers = - Development Status :: 5 - Production/Stable - Framework :: Jupyter - Intended Audience :: Developers - Intended Audience :: Science/Research - Intended Audience :: System Administrators - License :: OSI Approved :: BSD License - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 -project_urls = - Documentation = https://jupyter-server.readthedocs.io - Funding = https://numfocus.org/donate - Source = https://github.com/jupyter-server/jupyter_server - Tracker = https://github.com/jupyter-server/jupyter_server/issues - -[options] -zip_safe = False -include_package_data = True -packages = find: -package_dir = - "" = "jupyter_server" -python_requires = >=3.7 -install_requires = - anyio>=3.1.0,<4 - argon2-cffi - jinja2 - jupyter_client>=6.1.12 - jupyter_core>=4.7.0 - jupyter_server_terminals - nbconvert>=6.4.4 - nbformat>=5.2.0 - packaging - prometheus_client - pywinpty;os_name=='nt' - pyzmq>=17 - Send2Trash - terminado>=0.8.3 - tornado>=6.1.0 - traitlets>=5.1 - websocket-client - jupyter_telemetry - -[options.extras_require] -test = - coverage - ipykernel - pre-commit - pytest-console-scripts - pytest-cov - pytest-timeout - pytest-tornasync - pytest>=6.0 - requests - -[options.entry_points] -console_scripts = - jupyter-server = jupyter_server.serverapp:main - -[options.packages.find] -exclude = - docs.* - examples.* - tests - tests.* - -[flake8] -ignore = E501, W503, E402 -builtins = c, get_config -exclude = - .cache, - .github, - docs, - setup.py -enable-extensions = G -extend-ignore = - G001, G002, G004, G200, G201, G202, - # black adds spaces around ':' - E203, -per-file-ignores = - # B011: Do not call assert False since python -O removes these calls - # F841 local variable 'foo' is assigned to but never used - tests/*: B011, F841 diff --git a/tests/services/events/mockextension/__init__.py b/tests/services/events/mockextension/__init__.py index ed7c0e9d37..b19cb18a2e 100644 --- a/tests/services/events/mockextension/__init__.py +++ b/tests/services/events/mockextension/__init__.py @@ -1,4 +1,4 @@ -from .mock_extension import _load_jupyter_server_extension +from .mock_extension import _load_jupyter_server_extension # noqa: F401 # Function that makes these extensions discoverable # by the test functions. From dd0d807f81d6531353d28af1924175e011ab961f Mon Sep 17 00:00:00 2001 From: Zachary Sailer Date: Wed, 18 May 2022 13:48:33 -0700 Subject: [PATCH 8/9] Update jupyter_server/services/events/handlers.py Co-authored-by: Afshin Taylor Darian --- jupyter_server/services/events/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_server/services/events/handlers.py b/jupyter_server/services/events/handlers.py index ddc601c790..2b271cb62d 100644 --- a/jupyter_server/services/events/handlers.py +++ b/jupyter_server/services/events/handlers.py @@ -29,7 +29,7 @@ class SubscribeWebsocket( JupyterHandler, websocket.WebSocketHandler, ): - """Websocket Handler for listening to eve""" + """Websocket handler for subscribing to events""" auth_resource = AUTH_RESOURCE From cadc063e4768ecc40ce1672e50b39a0d9202c065 Mon Sep 17 00:00:00 2001 From: Zach Sailer Date: Thu, 19 May 2022 07:51:02 -0700 Subject: [PATCH 9/9] review comments --- jupyter_server/services/events/handlers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jupyter_server/services/events/handlers.py b/jupyter_server/services/events/handlers.py index 2b271cb62d..f861f4227d 100644 --- a/jupyter_server/services/events/handlers.py +++ b/jupyter_server/services/events/handlers.py @@ -13,11 +13,11 @@ AUTH_RESOURCE = "events" -class TornadoWebSocketLoggingHandler(logging.Handler): +class WebSocketLoggingHandler(logging.Handler): """Python logging handler that routes records to a Tornado websocket.""" - def __init__(self, websocket): - super().__init__() + def __init__(self, websocket, *args, **kwargs): + super().__init__(*args, **kwargs) self.websocket = websocket def emit(self, record): @@ -62,7 +62,7 @@ def open(self): """Routes events that are emitted by Jupyter Server's EventBus to a WebSocket client in the browser. """ - self.logging_handler = TornadoWebSocketLoggingHandler(self) + self.logging_handler = WebSocketLoggingHandler(self) # Add a JSON formatter to the handler. formatter = jsonlogger.JsonFormatter(json_serializer=_skip_message) self.logging_handler.setFormatter(formatter) @@ -72,6 +72,7 @@ def open(self): def on_close(self): self.event_bus.log.removeHandler(self.logging_handler) + self.event_bus.handlers.remove(self.logging_handler) default_handlers = [