From 254f0eeae0f3740f0250ce67e67f5243914d73b7 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 24 Mar 2023 11:10:01 +0000 Subject: [PATCH 1/4] Add back support for nb6 Adds support for notebook 6 by loudly failing if trying to load nbdime as an nb6 server extension if jupyter_server version >= 2. --- nbdime/__init__.py | 7 +++-- nbdime/tests/conftest.py | 21 ++++++++------ nbdime/webapp/nb_server_extension.py | 43 ++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/nbdime/__init__.py b/nbdime/__init__.py index 168efd17..7ffc34d0 100644 --- a/nbdime/__init__.py +++ b/nbdime/__init__.py @@ -3,6 +3,7 @@ # 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 @@ -10,13 +11,13 @@ 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(): diff --git a/nbdime/tests/conftest.py b/nbdime/tests/conftest.py index cd118fb2..4681f5d4 100644 --- a/nbdime/tests/conftest.py +++ b/nbdime/tests/conftest.py @@ -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 @@ -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 @@ -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 @@ -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' diff --git a/nbdime/webapp/nb_server_extension.py b/nbdime/webapp/nb_server_extension.py index 01847388..a1d17120 100644 --- a/nbdime/webapp/nb_server_extension.py +++ b/nbdime/webapp/nb_server_extension.py @@ -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 @@ -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') @@ -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'] From d78fdeba7e5935537c30596330f5b72cb65b0a45 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 24 Mar 2023 11:32:05 +0000 Subject: [PATCH 2/4] Test with both jupyter_server 2 and 1 --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 86bd5007..d82c0607 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -80,6 +80,11 @@ jobs: max-parallel: 4 matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + include: + - python-version: '3.11' + jupyter_server-version: '<2' + - python-version: '3.11' + jupyter_server-version: '>=2' steps: - uses: actions/checkout@v2 @@ -100,6 +105,7 @@ jobs: python -m pip install --upgrade pip python -m pip install jupyterlab~=3.0 python -m pip install --upgrade --upgrade-strategy=eager ".[test]" + python -m pip install jupyter_server${{ matrix.jupyter_server-version }} - name: Test with pytest run: | git config --global user.email CI@fake.com From e9b2156eda49cd1daa23454bb48a9e37ae2d3fab Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Wed, 12 Apr 2023 01:58:28 +0100 Subject: [PATCH 3/4] Update dev tooling node 18, webpack 5, etc. --- .github/workflows/tests.yml | 5 ++--- appveyor.yml | 2 +- package.json | 2 +- packages/labextension/package.json | 12 ++++++------ packages/nbdime/jest.config.js | 3 ++- packages/nbdime/package.json | 15 ++++++++------- packages/nbdime/test/jest-setup-files.js | 1 + packages/webapp/package.json | 23 +++++++++++------------ packages/webapp/webpack.config.js | 12 ++++++------ 9 files changed, 38 insertions(+), 37 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d82c0607..1ede2f8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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: | @@ -100,7 +100,6 @@ 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 diff --git a/appveyor.yml b/appveyor.yml index 741f1589..72f2af8d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ skip_branch_with_pr: true # environment variables environment: - nodejs_version: "14" + nodejs_version: "18" matrix: - PYTHON: "C:\\Python37-x64" PYTHON_VERSION: "3.7.x" diff --git a/package.json b/package.json index 311c84b1..12b27056 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,6 @@ "devDependencies": { "@jupyterlab/buildutils": "^3.0.0", "lerna": "^4.0.0", - "rimraf": "^2.6.3" + "rimraf": "^5.0.0" } } diff --git a/packages/labextension/package.json b/packages/labextension/package.json index bcff799e..f699f3a6 100644 --- a/packages/labextension/package.json +++ b/packages/labextension/package.json @@ -18,6 +18,8 @@ }, "license": "BSD-3-Clause", "author": "Project Jupyter", + "main": "lib/index.js", + "typings": "lib/index.d.ts", "files": [ "lib/*.js", "lib/*.js.map", @@ -25,18 +27,16 @@ "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" @@ -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": { diff --git a/packages/nbdime/jest.config.js b/packages/nbdime/jest.config.js index 903e886c..3668202b 100644 --- a/packages/nbdime/jest.config.js +++ b/packages/nbdime/jest.config.js @@ -6,6 +6,7 @@ tsOptions["rootDir"] = null; tsOptions["inlineSourceMap"] = true; module.exports = { + testEnvironment: 'jsdom', automock: false, moduleNameMapper: { '\\.(css|less|sass|scss)$': 'identity-obj-proxy', @@ -16,7 +17,7 @@ module.exports = { setupFiles: ['/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 diff --git a/packages/nbdime/package.json b/packages/nbdime/package.json index 500916b0..b2994829 100644 --- a/packages/nbdime/package.json +++ b/packages/nbdime/package.json @@ -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": { diff --git a/packages/nbdime/test/jest-setup-files.js b/packages/nbdime/test/jest-setup-files.js index c158ca42..9ddcf3ba 100644 --- a/packages/nbdime/test/jest-setup-files.js +++ b/packages/nbdime/test/jest-setup-files.js @@ -1 +1,2 @@ global.fetch = require('jest-fetch-mock'); +global.crypto = require('crypto'); diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 48d4c8ab..0739bcc0 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -4,8 +4,8 @@ "license": "BSD-3-Clause", "main": "static/nbdime.js", "scripts": { - "build": "webpack -p", - "build:dev": "webpack -d", + "build": "webpack --mode=production", + "build:dev": "webpack --mode=development", "clean": "rimraf build && rimraf ../../nbdime/webapp/static", "profile": "webpack --profile --json > webpack-stats.json", "watch": "webpack --watch" @@ -31,18 +31,17 @@ "devDependencies": { "@types/file-saver": "^2.0.0", "@types/json-stable-stringify": "^1.0.32", - "@types/node": "^14.14.13", + "@types/node": "^18.15.0", "@types/sanitizer": "^0.0.28", - "css-loader": "^3.0.0", - "file-loader": "^4.0.0", - "rimraf": "^2.6.3", - "source-map-loader": "^0.2.4", - "style-loader": "^0.23.1", - "ts-loader": "^8.2.0", + "css-loader": "^6.7.3", + "file-loader": "^6.2.0", + "rimraf": "^5.0.0", + "source-map-loader": "^4.0.1", + "style-loader": "^3.3.2", + "ts-loader": "^9.4.2", "typescript": "^4.9.0", - "url-loader": "^2.0.1", - "webpack": "^4.29.6", - "webpack-cli": "^3.2.3" + "webpack": "^5.78.0", + "webpack-cli": "^5.0.1" }, "peerDependencies": { "codemirror": "^5.0.0" diff --git a/packages/webapp/webpack.config.js b/packages/webapp/webpack.config.js index 2fdd8e91..1615af22 100644 --- a/packages/webapp/webpack.config.js +++ b/packages/webapp/webpack.config.js @@ -17,13 +17,13 @@ module.exports = { { test: /\.js$/, loader: "source-map-loader" }, { test: /\.html$/, loader: 'file-loader' }, // jquery-ui loads some images - { test: /\.(jpg|png|gif)$/, loader: 'file-loader' }, + { test: /\.(jpg|png|gif)$/, type: 'asset/resource' }, // required to load font-awesome - { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, - { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, - { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' }, - { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' }, - { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' } + { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, + { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, + { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, + { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, type: 'asset/resource' }, + { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' } ], }, resolve: { From df0353a14db9110feb5809745912a102c70b6751 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Wed, 12 Apr 2023 17:59:47 +0100 Subject: [PATCH 4/4] Update CI Try to use a more modern appveyor image, so we get newer git version --- .github/workflows/tests.yml | 31 +++++++++++++++++---- appveyor.yml | 55 ------------------------------------- 2 files changed, 25 insertions(+), 61 deletions(-) delete mode 100644 appveyor.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1ede2f8b..a2adf915 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,16 +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 @@ -100,16 +105,30 @@ jobs: ${{ runner.os }}-pip- - name: Install dependencies run: | - 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]" python -m pip install jupyter_server${{ matrix.jupyter_server-version }} - - name: Test with pytest + - 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 " + $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 }} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 72f2af8d..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Do not build feature branch with open Pull Requests -skip_branch_with_pr: true - -# environment variables -environment: - nodejs_version: "18" - matrix: - - PYTHON: "C:\\Python37-x64" - PYTHON_VERSION: "3.7.x" - PYTHON_MAJOR: 3 - PYTHON_ARCH: "64" - - PYTHON: "C:\\Python38-x64" - PYTHON_VERSION: "3.8.x" - PYTHON_MAJOR: 3 - PYTHON_ARCH: "64" - -# build cache to preserve files/folders between builds -cache: - - '%AppData%/npm-cache' - -# scripts that run after cloning repository -install: - # Install node: - - ps: Install-Product node $env:nodejs_version - # Ensure python scripts are from right version: - - 'SET "PATH=%PYTHON%\Scripts;%PYTHON%;%PATH%"' - # Install coverage utilities: - - 'pip install codecov jupyterlab~=3.0' - # Package up sdist for our package: - - 'python setup.py sdist' - - 'for /f %%i in (''dir .\dist\nbdime-*.tar.gz /B'') do set fname=%%i' - # Install our package: - - 'pip install --upgrade --upgrade-strategy=eager ".\dist\%fname%[test]"' - -build: off - -# scripts to run before tests -before_test: - - git config --global user.email appveyor@fake.com - - git config --global user.name "AppVeyor CI" - # Mercurial config: - - ps: '$hgconfig = "[ui]`r`nusername = AppVeyor CI "' - - ps: '$hgconfig | Set-Content ($HOME + "\mercurial.ini")' - - set "tmptestdir=%tmp%\nbdime-%RANDOM%" - - mkdir "%tmptestdir%" - - cd "%tmptestdir%" - - -# to run your custom scripts instead of automatic tests -test_script: - - 'py.test -l --cov-report xml:"%APPVEYOR_BUILD_FOLDER%\coverage.xml" --cov=nbdime --pyargs nbdime' - -on_success: - - cd "%APPVEYOR_BUILD_FOLDER%" - - codecov -X gcov --file "%APPVEYOR_BUILD_FOLDER%\coverage.xml"