Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add notebook 6 server extension support (w/tests) #656

Merged
merged 4 commits into from
Apr 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ jobs:
python -m pip install jupyterlab~=3.0
npm install -g codecov

- name: Use Node.js 14.x
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 18.x
- name: Get npm cache directory
id: npm-cache-dir
run: |
Expand All @@ -75,11 +75,21 @@ jobs:
CI: true
python:
name: Python
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 4
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
os: [ubuntu-latest]
include:
- python-version: '3.11'
jupyter_server-version: '<2'
- python-version: '3.11'
jupyter_server-version: '>=2'
- python-version: '3.7'
os: windows-latest
- python-version: '3.11'
os: windows-latest

steps:
- uses: actions/checkout@v2
Expand All @@ -95,16 +105,30 @@ jobs:
${{ runner.os }}-pip-
- name: Install dependencies
run: |
export NODE_OPTIONS="--openssl-legacy-provider"
python -m pip install codecov
python -m pip install --upgrade pip
python -m pip install jupyterlab~=3.0
python -m pip install --upgrade --upgrade-strategy=eager ".[test]"
- name: Test with pytest
python -m pip install jupyter_server${{ matrix.jupyter_server-version }}
- name: Test with pytest (Linux)
if: startsWith(matrix.os, 'ubuntu')
run: |
git config --global user.email CI@fake.com
git config --global user.name "CI"
pushd $(mktemp -d)
tmpdir=$(mktemp -d)
echo "TEST_TMPDIR=$tmpdir" >> $GITHUB_ENV
pushd $tmpdir
py.test -l --cov-report xml --cov=nbdime --pyargs nbdime
codecov
popd
- name: Test with pytest (Windows)
if: startsWith(matrix.os, 'windows')
run: |
git config --global user.email CI@fake.com
git config --global user.name "CI"
$hgconfig = "[ui]`r`nusername = CI <CI@fake.com>"
$hgconfig | Set-Content ($HOME + "\mercurial.ini")
echo "TEST_TMPDIR=." >> $Env:GITHUB_ENV
py.test -l --cov-report xml --cov=nbdime --pyargs nbdime
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
directory: ${{ env.TEST_TMPDIR }}
55 changes: 0 additions & 55 deletions appveyor.yml

This file was deleted.

7 changes: 4 additions & 3 deletions nbdime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

from functools import partial
from ._version import __version__

from .diffing import diff, diff_notebooks
from .patching import patch, patch_notebook
from .merging import merge_notebooks, decide_merge, apply_decisions


def load_jupyter_server_extension(nb_server_app):
def _load_jupyter_server_extension(nb_server_app, nb6_entrypoint=False):
# Wrap this here to avoid pulling in webapp in a normal run
from .webapp.nb_server_extension import _load_jupyter_server_extension
_load_jupyter_server_extension(nb_server_app)
_load_jupyter_server_extension(nb_server_app, nb6_entrypoint=nb6_entrypoint)


_load_jupyter_server_extension = load_jupyter_server_extension
load_jupyter_server_extension = partial(_load_jupyter_server_extension, nb6_entrypoint=True)


def _jupyter_server_extension_paths():
Expand Down
21 changes: 13 additions & 8 deletions nbdime/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def git_repo(tmpdir, request, filespath, needs_git, slow):
save_cwd = os.getcwd()
os.chdir(repo)
request.addfinalizer(lambda: os.chdir(save_cwd))
call('git init'.split())
call('git init -b master'.split())

# setup base branch
src = filespath
Expand Down Expand Up @@ -131,7 +131,7 @@ def git_repo2(tmpdir, request, filespath, needs_git, slow):
save_cwd = os.getcwd()
os.chdir(repo)
request.addfinalizer(lambda: os.chdir(save_cwd))
call('git init'.split())
call('git init -b master'.split())

# setup base branch
src = filespath
Expand Down Expand Up @@ -457,14 +457,11 @@ def create_server_extension_config(tmpdir_factory, cmd):
return str(path)


# TODO: Add back 'notebook' as param when NB 7.0 is out ?
@fixture(scope='module', params=('jupyter_server',))
@fixture(scope='module', params=('jupyter_server', 'notebook'))
def server_extension_app(tmpdir_factory, request):
cmd = request.param

if cmd == 'notebook':
token_config_location = 'NotebookApp'
else:
def _get_version(pkg):
v = None
try:
from importlib.metadata import version
Expand All @@ -473,7 +470,15 @@ def server_extension_app(tmpdir_factory, request):
import pkg_resources
v = pkg_resources.get_distribution('jupyter_server').version
from packaging import version
if version.parse(v).major >= 2:
return version.parse(v)

if cmd == 'notebook':
token_config_location = 'NotebookApp'
if _get_version('notebook').major <= 6 and _get_version('jupyter_server').major >= 2:
skip('Do not test with notebook<=6 + jupyter_server>=2')
else:
from packaging import version
if _get_version('jupyter_server').major >= 2:
token_config_location = 'IdentityProvider'
else:
token_config_location = 'ServerApp'
Expand Down
43 changes: 38 additions & 5 deletions nbdime/webapp/nb_server_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,27 @@

from jupyter_server.utils import url_path_join, to_os_path, ensure_async

generic_checkpoint_mixin_types = []
file_checkpoint_mixin_types = []

from jupyter_server.services.contents.checkpoints import GenericCheckpointsMixin
from jupyter_server.services.contents.filecheckpoints import FileCheckpoints
try:
from jupyter_server.services.contents.checkpoints import GenericCheckpointsMixin as jpserver_GenericCheckpointsMixin
from jupyter_server.services.contents.filecheckpoints import FileCheckpoints as jpserver_FileCheckpoints
generic_checkpoint_mixin_types.append(jpserver_GenericCheckpointsMixin)
file_checkpoint_mixin_types.append(jpserver_FileCheckpoints)
except ModuleNotFoundError:
pass

try:
from notebook.services.contents.checkpoints import GenericCheckpointsMixin as nbserver_GenericCheckpointsMixin
from notebook.services.contents.filecheckpoints import FileCheckpoints as nbserver_FileCheckpoints
generic_checkpoint_mixin_types.append(nbserver_GenericCheckpointsMixin)
file_checkpoint_mixin_types.append(nbserver_FileCheckpoints)
except ModuleNotFoundError:
pass

generic_checkpoint_mixin_types = tuple(generic_checkpoint_mixin_types)
file_checkpoint_mixin_types = tuple(file_checkpoint_mixin_types)


from tornado.web import HTTPError, escape, authenticated, gen
Expand Down Expand Up @@ -151,11 +169,11 @@ async def _get_checkpoint_notebooks(self, base):
return remote_nb, remote_nb
self.log.debug('Checkpoints: %r', checkpoints)
checkpoint = checkpoints[0]
if isinstance(cm.checkpoints, GenericCheckpointsMixin):
if isinstance(cm.checkpoints, generic_checkpoint_mixin_types):
checkpoint_model = await ensure_async(
cm.checkpoints.get_notebook_checkpoint(checkpoint, base))
base_nb = checkpoint_model['content']
elif isinstance(cm.checkpoints, FileCheckpoints):
elif isinstance(cm.checkpoints, file_checkpoint_mixin_types):
path = await ensure_async(
cm.checkpoints.checkpoint_path(checkpoint['id'], base))
base_nb = read_notebook(path, on_null='minimal')
Expand Down Expand Up @@ -292,13 +310,28 @@ def post(self):
self.finish(data)


def _load_jupyter_server_extension(nb_server_app):
def _load_jupyter_server_extension(nb_server_app, nb6_entrypoint=False):
"""
Called when the extension is loaded.

Args:
nb_server_app (NotebookWebApplication): handle to the Notebook webserver instance.
"""
if nb6_entrypoint:
# We're using the old notebook 6 extenstion entry point
# In this case, we only support jupyter_server < 2, so fail if >=2
from jupyter_server._version import __version__
try:
from packaging.version import parse, Version
if parse(__version__) >= Version('2.0.0'):
nb_server_app.log.critical(
"You must use Jupyter Server v1 to load nbdime as a classic notebook server extension. "
f"You have v{__version__} installed.\nYou can fix this by executing:\n"
" pip install -U \"jupyter-server<2.0.0\""
)
return
except Exception: # noqa
pass
web_app = nb_server_app.web_app

env = web_app.settings['jinja2_env']
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"devDependencies": {
"@jupyterlab/buildutils": "^3.0.0",
"lerna": "^4.0.0",
"rimraf": "^2.6.3"
"rimraf": "^5.0.0"
}
}
12 changes: 6 additions & 6 deletions packages/labextension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@
},
"license": "BSD-3-Clause",
"author": "Project Jupyter",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"files": [
"lib/*.js",
"lib/*.js.map",
"lib/*.d.ts",
"style/*.css",
"schema/*.json"
],
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"scripts": {
"build": "npm run build:lib && npm run build:labextension",
"build:dev": "npm run build:lib && jupyter labextension build --development True .",
"build:labextension": "npm run build:labextension:source && npm run build:labextension:prebuilt",
"build:labextension:source": "rimraf dist && mkdirp dist && cd dist && npm pack ..",
"build:labextension:prebuilt": "rimraf ../../nbdime/labextension && mkdirp ../../nbdime/labextension && jupyter labextension build .",
"build:labextension:source": "rimraf dist && mkdirp dist && cd dist && npm pack ..",
"build:lib": "tsc --build",
"clean": "npm run clean:lib && npm run clean:labextension",
"clean:lib": "rimraf tsconfig.tsbuildinfo lib",
"clean:labextension": "rimraf ../../nbdime/labextension",
"clean:lib": "rimraf tsconfig.tsbuildinfo lib",
"prepublishOnly": "npm run build",
"update": "rimraf node_modules/nbdime && npm install && npm run build",
"watch": "tsc --build --watch"
Expand All @@ -60,8 +60,8 @@
"@jupyterlab/builder": "^3.0.0",
"@jupyterlab/docregistry": "^2 || ^3",
"@lumino/commands": "^1.6.1",
"mkdirp": "^0.5.1",
"rimraf": "^2.6.3",
"mkdirp": "^3.0.0",
"rimraf": "^5.0.0",
"typescript": "^4.9.0"
},
"jupyterlab": {
Expand Down
3 changes: 2 additions & 1 deletion packages/nbdime/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tsOptions["rootDir"] = null;
tsOptions["inlineSourceMap"] = true;

module.exports = {
testEnvironment: 'jsdom',
automock: false,
moduleNameMapper: {
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
Expand All @@ -16,7 +17,7 @@ module.exports = {
setupFiles: ['<rootDir>/test/jest-setup-files.js'],
testPathIgnorePatterns: ['/lib/', '/node_modules/'],
testRegex: '/test/src/.*.spec.ts$',
transformIgnorePatterns: ['/node_modules/(?!((@jupyterlab|y-protocols|lib0)/.*))'],
transformIgnorePatterns: ['/node_modules/(?!((@jupyterlab|y-protocols|yjs|@jupyter/ydoc|lib0)/.*))'],
globals: {
'ts-jest': {
tsconfig: tsOptions
Expand Down
15 changes: 8 additions & 7 deletions packages/nbdime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@
"@babel/preset-env": "^7.5.0",
"@jupyterlab/apputils": "^2 || ^3",
"@lumino/messaging": "^1.2.2",
"@types/jest": "^29.5.0",
"@types/json-stable-stringify": "^1.0.32",
"@types/jest": "^26.0.0",
"@types/node": "^14.14.13",
"@types/node": "^18.15.0",
"@types/sanitizer": "^0.0.28",
"fs-extra": "^8.1.0",
"fs-extra": "^11.1.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.0.0",
"jest-fetch-mock": "^1.6.6",
"ts-jest": "^26.0.0",
"rimraf": "^2.6.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"jest-fetch-mock": "^3.0.3",
"rimraf": "^5.0.0",
"ts-jest": "^29.1.0",
"typescript": "^4.9.0"
},
"peerDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/nbdime/test/jest-setup-files.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
global.fetch = require('jest-fetch-mock');
global.crypto = require('crypto');
Loading