diff --git a/.gitignore b/.gitignore index 4917b6f2..9757ef79 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ MANIFEST .coverage .cache .pytest_cache +.idea .spyderproject diff --git a/docs/source/installing.rst b/docs/source/installing.rst index 6eb41de1..251794cb 100644 --- a/docs/source/installing.rst +++ b/docs/source/installing.rst @@ -89,10 +89,14 @@ Installing Jupyter extensions If you want to use the development version of the notebook and lab extensions, you will also have to run the following commands after the pip dev install:: - jupyter serverextension enable --py nbdime [--sys-prefix/--user/--system] +> Note: only run one of the following two server commands, running both can cause issues in some cases - jupyter nbextension install --py nbdime [--sym-link] [--sys-prefix/--user/--system] - jupyter nbextension enable --py nbdime [--sys-prefix/--user/--system] + jupyter serverextension enable --py nbdime --sys-prefix # if developing for jupyter notebook + + jupyter server extension enable nbdime # if developing for jupyter lab or nbclassic + + jupyter nbextension install --py nbdime --sys-prefix [--sym-link] + jupyter nbextension enable --py nbdime --sys-prefix jupyter labextension link ./packages/nbdime --no-build jupyter labextension install ./packages/labextension diff --git a/docs/source/testing.rst b/docs/source/testing.rst index 340d2288..e5305323 100644 --- a/docs/source/testing.rst +++ b/docs/source/testing.rst @@ -11,7 +11,7 @@ Dependencies Install the test dependencies:: - pip install "nbdime[test]" + pip install .[test] Running tests locally --------------------- diff --git a/jupyter-config/jupyter_server_config.d/nbdime.json b/jupyter-config/jupyter_server_config.d/nbdime.json new file mode 100644 index 00000000..bbd17adc --- /dev/null +++ b/jupyter-config/jupyter_server_config.d/nbdime.json @@ -0,0 +1,7 @@ +{ + "ServerApp": { + "jpserver_extensions": { + "nbdime": true + } + } +} diff --git a/nbdime/__init__.py b/nbdime/__init__.py index c2d4d046..16526fec 100644 --- a/nbdime/__init__.py +++ b/nbdime/__init__.py @@ -18,12 +18,18 @@ def load_jupyter_server_extension(nb_server_app): _load_jupyter_server_extension(nb_server_app) +_load_jupyter_server_extension = load_jupyter_server_extension + + def _jupyter_server_extension_paths(): return [{ "module": "nbdime" }] +_jupyter_server_extension_points = _jupyter_server_extension_paths + + def _jupyter_nbextension_paths(): return [dict( section="notebook", @@ -41,4 +47,7 @@ def _jupyter_nbextension_paths(): "patch", "patch_notebook", "decide_merge", "merge_notebooks", "apply_decisions", "load_jupyter_server_extension", + "_load_jupyter_server_extension", + "_jupyter_server_extension_points", + "_jupyter_server_extension_paths", ] diff --git a/nbdime/_version.py b/nbdime/_version.py index 62c098b4..9ad427e0 100644 --- a/nbdime/_version.py +++ b/nbdime/_version.py @@ -1,2 +1,2 @@ -version_info = (2, 1, 1, 'dev') +version_info = (3, 0, 0, 'dev0') __version__ = ".".join(map(str, version_info)) diff --git a/nbdime/tests/conftest.py b/nbdime/tests/conftest.py index b965a5d3..d3d509f0 100644 --- a/nbdime/tests/conftest.py +++ b/nbdime/tests/conftest.py @@ -441,11 +441,14 @@ def _term(): -def create_server_extension_config(tmpdir_factory): +def create_server_extension_config(tmpdir_factory, cmd): + appname = 'NotebookApp' if cmd == 'notebook' else 'ServerApp' + filename = 'jupyter_notebook_config.json' if cmd == 'notebook' else 'jupyter_server_config.json' + config_entry = 'nbserver_extensions' if cmd == 'notebook' else 'jpserver_extensions' path = tmpdir_factory.mktemp('server-extension-config') config = { - "NotebookApp": { - "nbserver_extensions": { + appname: { + config_entry: { "nbdime": True } } @@ -453,13 +456,16 @@ def create_server_extension_config(tmpdir_factory): config_str = json.dumps(config) if isinstance(config_str, bytes): config_str = unicode(config_str) - path.join('jupyter_notebook_config.json').write_text(config_str, 'utf-8') + path.join(filename).write_text(config_str, 'utf-8') return str(path) -@fixture(scope='module') +@fixture(scope='module', params=('notebook', 'jupyter_server')) def server_extension_app(tmpdir_factory, request): + cmd = request.param + + appname = 'NotebookApp' if cmd == 'notebook' else 'ServerApp' def _kill_nb_app(): try: @@ -469,7 +475,7 @@ def _kill_nb_app(): pass popen_wait(process, 10) - config_dir = create_server_extension_config(tmpdir_factory) + config_dir = create_server_extension_config(tmpdir_factory, cmd) env = os.environ.copy() env.update({'JUPYTER_CONFIG_DIR': config_dir}) @@ -478,10 +484,10 @@ def _kill_nb_app(): os.chdir(root_dir) process = Popen([ - sys.executable, '-m', 'notebook', + sys.executable, '-m', cmd, '--port=%i' % port, '--ip=127.0.0.1', - '--no-browser', '--NotebookApp.token=%s' % TEST_TOKEN], + '--no-browser', '--%s.token=%s' % (appname, TEST_TOKEN)], env=env) request.addfinalizer(_kill_nb_app) diff --git a/nbdime/tests/test_git_filter_integration.py b/nbdime/tests/test_git_filter_integration.py index b090de9c..2de0146b 100644 --- a/nbdime/tests/test_git_filter_integration.py +++ b/nbdime/tests/test_git_filter_integration.py @@ -98,11 +98,7 @@ def test_apply_filter_invalid_filter(git_repo): def test_apply_filter_valid_filter(git_repo): - try: - call('cat --help') - filter_cmd = 'cat' - except (CalledProcessError, FileNotFoundError): - filter_cmd = 'findstr x*' + filter_cmd = 'findstr x*' if os.name == 'nt' else 'cat' path = pjoin(git_repo, 'diff.ipynb') gitattr = locate_gitattributes() with io.open(gitattr, 'a', encoding="utf8") as f: diff --git a/nbdime/tests/test_server_extension.py b/nbdime/tests/test_server_extension.py index 40d37269..5e8d2aad 100644 --- a/nbdime/tests/test_server_extension.py +++ b/nbdime/tests/test_server_extension.py @@ -8,6 +8,7 @@ import re import requests import shutil +import uuid import pytest @@ -149,7 +150,6 @@ def test_diff_api_checkpoint(tmpdir, filespath, server_extension_app): if os.sep == '\\': url_path = url_path.replace('\\', '/') - # Create checkpoint url = 'http://127.0.0.1:%i/api/contents/%s/checkpoints' % ( server_extension_app['port'], @@ -164,7 +164,8 @@ def test_diff_api_checkpoint(tmpdir, filespath, server_extension_app): url = 'http://127.0.0.1:%i/nbdime/api/diff' % server_extension_app['port'] r = requests.post( - url, headers=auth_header, + url, + headers=auth_header, data=json.dumps({ 'base': 'checkpoint:' + url_path, })) @@ -178,7 +179,7 @@ def test_diff_api_checkpoint(tmpdir, filespath, server_extension_app): @pytest.mark.timeout(timeout=WEB_TEST_TIMEOUT) def test_diff_api_symlink(git_repo2, server_extension_app, needs_symlink): root = server_extension_app['path'] - subdir = pjoin(root, 'has space', 'subdir') + subdir = pjoin(root, str(uuid.uuid4()), 'has space', 'subdir') os.makedirs(subdir) symlink = pjoin(subdir, 'link') with pushd(subdir): diff --git a/nbdime/webapp/nb_server_extension.py b/nbdime/webapp/nb_server_extension.py index 8e445bbd..2949583e 100644 --- a/nbdime/webapp/nb_server_extension.py +++ b/nbdime/webapp/nb_server_extension.py @@ -8,9 +8,31 @@ from jinja2 import ChoiceLoader, FileSystemLoader -from notebook.utils import url_path_join, to_os_path -from notebook.services.contents.checkpoints import GenericCheckpointsMixin -from notebook.services.contents.filecheckpoints import FileCheckpoints +from jupyter_server.utils import url_path_join, to_os_path + +generic_checkpoint_mixin_types = [] +file_checkpoint_mixin_types = [] + +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 from ..args import process_diff_flags @@ -148,11 +170,11 @@ def _get_checkpoint_notebooks(self, base): raise gen.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 = yield gen.maybe_future( 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 = yield gen.maybe_future( cm.checkpoints.checkpoint_path(checkpoint['id'], base)) base_nb = read_notebook(path, on_null='minimal') diff --git a/nbdime/webapp/nbdimeserver.py b/nbdime/webapp/nbdimeserver.py index 90fd5891..e4463a74 100644 --- a/nbdime/webapp/nbdimeserver.py +++ b/nbdime/webapp/nbdimeserver.py @@ -12,10 +12,10 @@ from jinja2 import FileSystemLoader, Environment import nbformat -from notebook.base.handlers import IPythonHandler, APIHandler -from notebook import DEFAULT_STATIC_FILES_PATH -from notebook.utils import url_path_join -from notebook.log import log_request +from jupyter_server.base.handlers import JupyterHandler, APIHandler +from jupyter_server import DEFAULT_STATIC_FILES_PATH +from jupyter_server.utils import url_path_join +from jupyter_server.log import log_request import requests from six import string_types from tornado import ioloop, web, escape, netutil, httpserver @@ -43,7 +43,7 @@ template_path = os.path.join(here, 'templates') -class NbdimeHandler(IPythonHandler): +class NbdimeHandler(JupyterHandler): def initialize(self, **params): self.params = params diff --git a/package.json b/package.json index 0614dc67..612fb710 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "watch": "tsc --build --watch" }, "devDependencies": { - "@jupyterlab/buildutils": "^2.0.0", + "@jupyterlab/buildutils": "^3.0.0", "lerna": "^3.14.1", "rimraf": "^2.6.3" } diff --git a/packages/labextension/package.json b/packages/labextension/package.json index dfebef1d..51757372 100644 --- a/packages/labextension/package.json +++ b/packages/labextension/package.json @@ -1,6 +1,6 @@ { "name": "nbdime-jupyterlab", - "version": "2.0.1", + "version": "2.1.0", "description": "A JupyterLab extension for showing Notebook diffs.", "keywords": [ "jupyter", @@ -38,22 +38,22 @@ "watch": "tsc --build --watch" }, "dependencies": { - "@jupyterlab/apputils": "^2.0.0", - "@jupyterlab/coreutils": "^4.0.0", - "@jupyterlab/nbformat": "^2.0.0", - "@jupyterlab/notebook": "^2.0.0", - "@jupyterlab/rendermime": "^2.0.0", - "@jupyterlab/services": "^5.0.0", - "@jupyterlab/settingregistry": "^2.0.0", + "@jupyterlab/apputils": "^2 || ^3", + "@jupyterlab/coreutils": "^4 || ^5", + "@jupyterlab/nbformat": "^2 || ^3", + "@jupyterlab/notebook": "^2 || ^3", + "@jupyterlab/rendermime": "^2 || ^3", + "@jupyterlab/services": "^5 || ^6", + "@jupyterlab/settingregistry": "^2 || ^3", "@lumino/algorithm": "^1.1.2", "@lumino/coreutils": "^1.3.0", "@lumino/disposable": "^1.1.2", "@lumino/widgets": "^1.6.0", - "nbdime": "^6.0.0" + "nbdime": "^6.1.0" }, "devDependencies": { - "@jupyterlab/application": "^2.0.0", - "@jupyterlab/docregistry": "^2.0.0", + "@jupyterlab/application": "^2 || ^3", + "@jupyterlab/docregistry": "^2 || ^3", "@lumino/commands": "^1.6.1", "mkdirp": "^0.5.1", "rimraf": "^2.6.3", diff --git a/packages/nbdime/package.json b/packages/nbdime/package.json index 88ee9038..fbd8a95e 100644 --- a/packages/nbdime/package.json +++ b/packages/nbdime/package.json @@ -1,6 +1,6 @@ { "name": "nbdime", - "version": "6.0.0", + "version": "6.1.0", "description": "Diff and merge of Jupyter Notebooks", "repository": { "type": "git", @@ -19,17 +19,16 @@ "test:chrome": "karma start --browsers=Chrome test/karma.conf.js", "test:debug": "karma start --browsers=Chrome --singleRun=false --debug=true test/karma-nocov.conf.js", "test:firefox": "karma start --browsers=Firefox test/karma.conf.js", - "test:ie": "karma start --browsers=IE test/karma.conf.js", "watch": "tsc --build --watch" }, "dependencies": { - "@jupyterlab/codeeditor": "^2.0.0", - "@jupyterlab/codemirror": "^2.0.0", - "@jupyterlab/coreutils": "^4.0.0", - "@jupyterlab/nbformat": "^2.0.0", - "@jupyterlab/outputarea": "^2.0.0", - "@jupyterlab/rendermime": "^2.0.0", - "@jupyterlab/services": "^5.0.0", + "@jupyterlab/codeeditor": "^2 || ^3", + "@jupyterlab/codemirror": "^2 || ^3", + "@jupyterlab/coreutils": "^4 || ^5", + "@jupyterlab/nbformat": "^2 || ^3", + "@jupyterlab/outputarea": "^2 || ^3", + "@jupyterlab/rendermime": "^2 || ^3", + "@jupyterlab/services": "^5 || ^6", "@lumino/algorithm": "^1.1.2", "@lumino/coreutils": "^1.3.0", "@lumino/dragdrop": "^1.3.0", @@ -38,24 +37,23 @@ "json-stable-stringify": "^1.0.1" }, "devDependencies": { - "@jupyterlab/apputils": "^2.0.0", + "@jupyterlab/apputils": "^2 || ^3", "@lumino/messaging": "^1.2.2", "@types/expect.js": "^0.3.29", "@types/json-stable-stringify": "^1.0.32", - "@types/mocha": "^5.2.6", - "@types/node": "^12.0.10", + "@types/mocha": "^8.2.0", + "@types/node": "^14.14.13", "@types/sanitizer": "^0.0.28", "expect.js": "^0.3.1", "fs-extra": "^8.1.0", - "karma": "^4.0.1", - "karma-chrome-launcher": "^2.2.0", - "karma-firefox-launcher": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-mocha": "^1.3.0", + "karma": "^5.2.3", + "karma-chrome-launcher": "^3.1.0", + "karma-firefox-launcher": "^2.1.0", + "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-typescript": "^4.0.0", "karma-typescript-es6-transform": "^4.0.0", - "mocha": "^6.0.2", + "mocha": "^8.2.1", "rimraf": "^2.6.3", "typescript": "^3.7.2" }, diff --git a/packages/nbdime/test/src/common/flexpanel.spec.ts b/packages/nbdime/test/src/common/flexpanel.spec.ts index e1a0a5fe..a169409e 100644 --- a/packages/nbdime/test/src/common/flexpanel.spec.ts +++ b/packages/nbdime/test/src/common/flexpanel.spec.ts @@ -29,15 +29,15 @@ import { */ function combinatorialTest( description: string, - steps: ((done: MochaDone) => void)[] | { [key: string]: ((done?: MochaDone) => void) }, - beforeEach?: (done: MochaDone) => void, - afterEach?: (done: MochaDone) => void) { + steps: ((done: Mocha.Done) => void)[] | { [key: string]: ((done?: Mocha.Done) => void) }, + beforeEach?: (done: Mocha.Done) => void, + afterEach?: (done: Mocha.Done) => void) { let stepNames: string[]; if (Array.isArray(steps)) { stepNames = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); } else { stepNames = Object.keys(steps); - let stepArray: ((done?: MochaDone) => void)[] = []; + let stepArray: ((done?: Mocha.Done) => void)[] = []; for (let key of stepNames) { stepArray.push(steps[key]); } diff --git a/packages/webapp/package.json b/packages/webapp/package.json index c425b36f..9d50c97e 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -1,6 +1,6 @@ { "name": "nbdime-webapp", - "version": "4.1.0", + "version": "4.2.0", "private": true, "license": "BSD-3-Clause", "main": "static/nbdime.js", @@ -13,26 +13,26 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^5.12.0", - "@jupyterlab/application": "^2.0.0", - "@jupyterlab/apputils": "^2.0.0", - "@jupyterlab/cells": "^2.0.0", - "@jupyterlab/codemirror": "^2.0.0", - "@jupyterlab/coreutils": "^4.0.0", - "@jupyterlab/mathjax2": "^2.0.0", - "@jupyterlab/nbformat": "^2.0.0", - "@jupyterlab/notebook": "^2.0.0", - "@jupyterlab/rendermime": "^2.0.0", - "@jupyterlab/theme-light-extension": "^2.0.0", + "@jupyterlab/application": "^2 || ^3", + "@jupyterlab/apputils": "^2 || ^3", + "@jupyterlab/cells": "^2 || ^3", + "@jupyterlab/codemirror": "^2 || ^3", + "@jupyterlab/coreutils": "^4 || ^5", + "@jupyterlab/mathjax2": "^2 || ^3", + "@jupyterlab/nbformat": "^2 || ^3", + "@jupyterlab/notebook": "^2 || ^3", + "@jupyterlab/rendermime": "^2 || ^3", + "@jupyterlab/theme-light-extension": "^2 || ^3", "@lumino/dragdrop": "^1.3.0", "@lumino/widgets": "^1.6.0", "alertify.js": "^1.0.12", "file-saver": "^2.0.1", - "nbdime": "^6.0.0" + "nbdime": "^6.1.0" }, "devDependencies": { "@types/file-saver": "^2.0.0", "@types/json-stable-stringify": "^1.0.32", - "@types/node": "^12.0.10", + "@types/node": "^14.14.13", "@types/sanitizer": "^0.0.28", "css-loader": "^3.0.0", "file-loader": "^4.0.0", diff --git a/setup.py b/setup.py index 73a73b74..cdafb1b7 100644 --- a/setup.py +++ b/setup.py @@ -115,7 +115,7 @@ 'tornado', 'requests', 'GitPython!=2.1.4, !=2.1.5, !=2.1.6', # For difftool taking git refs - 'notebook', + 'jupyter_server', 'jinja2>=2.9', ] @@ -125,8 +125,10 @@ 'pytest-cov', 'pytest-timeout', 'pytest-tornado', + 'jupyter_server[test]', 'jsonschema', 'mock', + 'notebook', 'requests', 'tabulate', # For profiling ],