diff --git a/.gitignore b/.gitignore index 4917b6f2f..9757ef790 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 6eb41de15..251794cbd 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 340d22889..e5305323c 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 000000000..bbd17adc3 --- /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 c2d4d0464..16526fec8 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 62c098b46..9ad427e0a 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 b965a5d34..d3d509f0a 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 b090de9cc..2de0146b0 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 40d372694..5e8d2aad4 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 8e445bbd8..2949583ea 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 90fd5891f..e4463a74b 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 0614dc676..612fb7104 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 dfebef1dd..51757372c 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 88ee90382..fbd8a95ef 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 e1a0a5fe1..a169409e2 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 c425b36fa..9d50c97e4 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 73a73b749..cdafb1b7a 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 ],