Skip to content

Commit

Permalink
Merge pull request #384 from kevin-bates/use-jupyter-server
Browse files Browse the repository at this point in the history
Use jupyter server as base provider
  • Loading branch information
Zsailer authored Jan 4, 2024
2 parents 6a6a49e + 414cfe2 commit d455f4e
Show file tree
Hide file tree
Showing 37 changed files with 1,792 additions and 1,769 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ jobs:
strategy:
matrix:
python:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"

runs-on: ubuntu-latest

Expand Down Expand Up @@ -70,11 +71,12 @@ jobs:
run: jupyter kernelgateway --help

- name: Run tests
run: nosetests --process-restartworker --with-coverage --cover-package=kernel_gateway
run: pytest -vv -W default --cov kernel_gateway --cov-branch --cov-report term-missing:skip-covered
env:
ASYNC_TEST_TIMEOUT: 10

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ sdist: ## Make a dist/*.tar.gz source distribution
test: TEST?=
test: ## Make a python3 test run
ifeq ($(TEST),)
$(SA) $(ENV) && nosetests
$(SA) $(ENV) && pytest -vv
else
# e.g., make test TEST="test_gatewayapp.TestGatewayAppConfig"
$(SA) $(ENV) && nosetests kernel_gateway.tests.$(TEST)
# e.g., make test TEST="test_gatewayapp.py::TestGatewayAppConfig"
$(SA) $(ENV) && pytest -vv kernel_gateway/tests/$(TEST)
endif

release: POST_SDIST=register upload
Expand Down
10 changes: 10 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
coverage:
status:
project:
default:
target: auto
threshold: 5%
patch:
default:
target: 50%
range: 80..100
104 changes: 104 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import os
import logging
import pytest
from binascii import hexlify
from traitlets.config import Config
from kernel_gateway.gatewayapp import KernelGatewayApp

pytest_plugins = ["pytest_jupyter.jupyter_core", "pytest_jupyter.jupyter_server"]


@pytest.fixture(scope="function")
def jp_configurable_serverapp(
jp_nbconvert_templates, # this fixture must precede jp_environ
jp_environ,
jp_server_config,
jp_argv,
jp_http_port,
jp_base_url,
tmp_path,
jp_root_dir,
jp_logging_stream,
jp_asyncio_loop,
io_loop,
):
"""Starts a Jupyter Server instance based on
the provided configuration values.
The fixture is a factory; it can be called like
a function inside a unit test. Here's a basic
example of how use this fixture:
.. code-block:: python
def my_test(jp_configurable_serverapp):
app = jp_configurable_serverapp(...)
...
"""
KernelGatewayApp.clear_instance()

def _configurable_serverapp(
config=jp_server_config,
base_url=jp_base_url,
argv=jp_argv,
http_port=jp_http_port,
**kwargs,
):
c = Config(config)

if "auth_token" not in c.KernelGatewayApp and not c.IdentityProvider.token:
default_token = hexlify(os.urandom(4)).decode("ascii")
c.IdentityProvider.token = default_token

app = KernelGatewayApp.instance(
# Set the log level to debug for testing purposes
log_level="DEBUG",
port=http_port,
port_retries=0,
base_url=base_url,
config=c,
**kwargs,
)
app.log.propagate = True
app.log.handlers = []
# Initialize app without httpserver
if jp_asyncio_loop.is_running():
app.initialize(argv=argv, new_httpserver=False)
else:

async def initialize_app():
app.initialize(argv=argv, new_httpserver=False)

jp_asyncio_loop.run_until_complete(initialize_app())
# Reroute all logging StreamHandlers away from stdin/stdout since pytest hijacks
# these streams and closes them at unfortunate times.
stream_handlers = [h for h in app.log.handlers if isinstance(h, logging.StreamHandler)]
for handler in stream_handlers:
handler.setStream(jp_logging_stream)
app.log.propagate = True
app.log.handlers = []
app.start_app()
return app

return _configurable_serverapp


@pytest.fixture(autouse=True)
def jp_server_cleanup(jp_asyncio_loop):
yield
app: KernelGatewayApp = KernelGatewayApp.instance()
try:
jp_asyncio_loop.run_until_complete(app.async_shutdown())
except (RuntimeError, SystemExit) as e:
print("ignoring cleanup error", e)
if hasattr(app, "kernel_manager"):
app.kernel_manager.context.destroy()
KernelGatewayApp.clear_instance()


@pytest.fixture
def jp_auth_header(jp_serverapp):
"""Configures an authorization header using the token from the serverapp fixture."""
return {"Authorization": f"token {jp_serverapp.identity_provider.token}"}
2 changes: 1 addition & 1 deletion docs/source/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ The Jupyter Kernel Gateway has the following features:
* Generation of [Swagger specs](http://swagger.io/introducing-the-open-api-initiative/)
for notebook-defined API in `notebook-http` mode
* A CLI for launching the kernel gateway: `jupyter kernelgateway OPTIONS`
* A Python 2.7 and 3.3+ compatible implementation
* A Python 3.8+ compatible implementation
58 changes: 58 additions & 0 deletions kernel_gateway/auth/identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""Gateway Identity Provider interface
This defines the _authentication_ layer of Jupyter Server,
to be used in combination with Authorizer for _authorization_.
"""
from traitlets import default
from tornado import web

from jupyter_server.auth.identity import IdentityProvider, User
from jupyter_server.base.handlers import JupyterHandler


class GatewayIdentityProvider(IdentityProvider):
"""
Interface for providing identity management and authentication for a Gateway server.
"""

@default("token")
def _token_default(self):
return self.parent.auth_token

@property
def auth_enabled(self):
if not self.token:
return False
return True

def should_check_origin(self, handler: JupyterHandler) -> bool:
"""Should the Handler check for CORS origin validation?
Origin check should be skipped for token-authenticated requests.
Returns:
- True, if Handler must check for valid CORS origin.
- False, if Handler should skip origin check since requests are token-authenticated.
"""
# Always check the origin unless operator configured gateway to allow any
return handler.settings["kg_allow_origin"] != "*"

def generate_anonymous_user(self, handler: web.RequestHandler) -> User:
"""Generate a random anonymous user.
For use when a single shared token is used,
but does not identify a user.
"""
name = display_name = f"Anonymous"
initials = "An"
color = None
return User(name.lower(), name, display_name, initials, None, color)

def is_token_authenticated(self, handler: web.RequestHandler) -> bool:
"""The default authentication flow of Gateway is token auth.
The only other option is no auth
"""
return True
7 changes: 5 additions & 2 deletions kernel_gateway/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
"""Tornado handlers for the base of the API."""

from tornado import web
import notebook.base.handlers as notebook_handlers
import jupyter_server.base.handlers as server_handlers
from ..mixins import TokenAuthorizationMixin, CORSMixin, JSONErrorsMixin


class APIVersionHandler(TokenAuthorizationMixin,
CORSMixin,
JSONErrorsMixin,
notebook_handlers.APIVersionHandler):
server_handlers.APIVersionHandler):
"""Extends the notebook server base API handler with token auth, CORS, and
JSON errors.
"""
pass


class NotFoundHandler(JSONErrorsMixin, web.RequestHandler):
"""Catches all requests and responds with 404 JSON messages.
Expand All @@ -28,6 +30,7 @@ class NotFoundHandler(JSONErrorsMixin, web.RequestHandler):
def prepare(self):
raise web.HTTPError(404)


default_handlers = [
(r'/api', APIVersionHandler),
(r'/(.*)', NotFoundHandler)
Expand Down
Loading

0 comments on commit d455f4e

Please sign in to comment.