diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c80e04d0..9d6ec246 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,81 +2,82 @@ name: Build on: push: - branches: main + branches: + - main pull_request: - branches: '*' + branches: + - '*' jobs: build: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install dependencies - run: python -m pip install -U "jupyterlab>=4.0.0,<5" + - name: Install dependencies + run: python -m pip install -U "jupyterlab>=4.0.0,<5" - - name: Lint the extension - run: | - set -eux - jlpm - jlpm run lint:check + - name: Lint the extension + run: | + set -eux + jlpm + jlpm run lint:check - - name: Build the extension - run: | - set -eux - python -m pip install .[test] + - name: Build the extension + run: | + set -eux + python -m pip install .[test] - jupyter labextension list - jupyter labextension list 2>&1 | grep -ie "ipylab.*OK" - python -m jupyterlab.browser_check + jupyter labextension list + jupyter labextension list 2>&1 | grep -ie "ipylab.*OK" + python -m jupyterlab.browser_check - - name: Package the extension - run: | - set -eux + - name: Package the extension + run: | + set -eux - pip install build - python -m build - pip uninstall -y "ipylab" jupyterlab + pip install build + python -m build + pip uninstall -y "ipylab" jupyterlab - - name: Upload extension packages - uses: actions/upload-artifact@v3 - with: - name: extension-artifacts - path: dist/ipylab* - if-no-files-found: error + - name: Upload extension packages + uses: actions/upload-artifact@v3 + with: + name: extension-artifacts + path: dist/ipylab* + if-no-files-found: error test_isolated: needs: build runs-on: ubuntu-latest steps: - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - architecture: 'x64' - - uses: actions/download-artifact@v3 - with: - name: extension-artifacts - - name: Install and Test - run: | - set -eux - # Remove NodeJS, twice to take care of system and locally installed node versions. - sudo rm -rf $(which node) - sudo rm -rf $(which node) - - pip install "jupyterlab>=4.0.0,<5" ipylab*.whl - - - jupyter labextension list - jupyter labextension list 2>&1 | grep -ie "ipylab.*OK" - python -m jupyterlab.browser_check --no-browser-test - + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + architecture: 'x64' + - uses: actions/download-artifact@v3 + with: + name: extension-artifacts + - name: Install and Test + run: | + set -eux + # Remove NodeJS, twice to take care of system and locally installed node versions. + sudo rm -rf $(which node) + sudo rm -rf $(which node) + + pip install "jupyterlab>=4.0.0,<5" ipylab*.whl + + + jupyter labextension list + jupyter labextension list 2>&1 | grep -ie "ipylab.*OK" + python -m jupyterlab.browser_check --no-browser-test check_links: name: Check Links diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index bc5e00f6..bc527328 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -1,9 +1,11 @@ name: Check Release on: push: - branches: ["main"] + branches: + - main pull_request: - branches: ["*"] + branches: + - '*' jobs: check_release: @@ -16,7 +18,6 @@ jobs: - name: Check Release uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: - token: ${{ secrets.GITHUB_TOKEN }} - name: Upload Distributions diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2ea191e1..0ad999a3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,34 +2,36 @@ name: Lint on: push: - branches: [ main ] + branches: + - main pull_request: - branches: '*' + branches: + - '*' jobs: lint_ts: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install JupyterLab - run: python -m pip install 'jupyterlab >=3.1,<4' - - name: Lint TypeScript - run: | - jlpm - jlpm run lint:check + - name: Checkout + uses: actions/checkout@v3 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install JupyterLab + run: python -m pip install 'jupyterlab >=4,<5' + - name: Lint TypeScript + run: | + jlpm + jlpm run lint:check lint_python: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install black - run: python -m pip install black - - name: Lint Python - run: | - black --check . + - name: Checkout + uses: actions/checkout@v3 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install ruff + run: python -m pip install ruff + - name: Lint Python + run: | + ruff --check . diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml index aa7edf2c..3ad1f256 100644 --- a/.github/workflows/packaging.yml +++ b/.github/workflows/packaging.yml @@ -2,9 +2,11 @@ name: Packaging on: push: - branches: [ main ] + branches: + - main pull_request: - branches: '*' + branches: + - '*' env: PIP_DISABLE_PIP_VERSION_CHECK: 1 @@ -17,29 +19,29 @@ jobs: build: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install dependencies - run: | - python -m pip install "jupyterlab>=4,<5" build - - name: Build pypi distributions - run: | - python -m build - - name: Build npm distributions - run: | - npm pack - cp *.tgz dist - - name: Build checksum file - run: | - cd dist - sha256sum * | tee SHA256SUMS - - name: Upload distributions - uses: actions/upload-artifact@v3 - with: - name: dist ${{ github.run_number }} - path: ./dist + - name: Checkout + uses: actions/checkout@v3 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install dependencies + run: | + python -m pip install "jupyterlab>=4,<5" build + - name: Build pypi distributions + run: | + python -m build + - name: Build npm distributions + run: | + npm pack + cp *.tgz dist + - name: Build checksum file + run: | + cd dist + sha256sum * | tee SHA256SUMS + - name: Upload distributions + uses: actions/upload-artifact@v3 + with: + name: dist ${{ github.run_number }} + path: ./dist install: runs-on: ${{ matrix.os }}-latest @@ -48,9 +50,9 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python: ['3.8', '3.11'] + python: ['3.10', '3.11'] include: - - python: '3.8' + - python: '3.10' dist: 'ipylab*.tar.gz' - python: '3.11' dist: 'ipylab*.whl' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4c8c852..21d7e615 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,6 +4,8 @@ ci: - prettier - eslint - stylelint + - jlpm-lint + - ruff-format-hatch-settings default_language_version: node: system repos: diff --git a/.vscode/settings.json b/.vscode/settings.json index 19b89112..e30d11e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,5 @@ }, "editor.formatOnSave": true, "python.terminal.activateEnvInCurrentTerminal": true, - "python.createEnvironment.trigger": "prompt", + "python.createEnvironment.trigger": "prompt" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9355fd..208512cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,12 @@ ### Enhancements made -- Upgraded to provide asynchronous bi-directional comms for performing operations +- Upgraded to provide asynchronous bi-directional comms for performing operations in the Frontend. Each operation returns a task that when complete returns the response or raise an error if unsuccessful. -- On the Python side `JupyterFrontEnd`, `CommandRegistry`, & `CommandPalette` -are all derived from `AsyncWidgetBase` and are now single instance objects. +- On the Python side `JupyterFrontEnd`, `CommandRegistry`, & `CommandPalette` + are all derived from `AsyncWidgetBase` and are now single instance objects. - On the JavaScript frontend side `JupyterFrontendModel`, `CommandRegistryModel`, - `CommandPalletModel`, extend the new `IpylabModel`. + `CommandPalletModel`, extend the new `IpylabModel`. ### Added @@ -24,6 +24,7 @@ are all derived from `AsyncWidgetBase` and are now single instance objects. - app.dialogs described [here](https://jupyterlab.readthedocs.io/en/stable/extension/ui_helpers.html#user-interface-helpers). ### Removed + - Callback functionality - Drop Python < 3.11 - Drop Jupyterlab < 4.0 diff --git a/README.md b/README.md index c75a5de8..fe409a6e 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,11 @@ jlpm && jlpm run build pip install pre-commit pre-commit install +# Use jlpm script to lint the JS +jlpm lint +#or +jlpm lint:check + ``` diff --git a/src/widgets/commands.ts b/src/widgets/commands.ts index 8f3693a0..be00ea1c 100644 --- a/src/widgets/commands.ts +++ b/src/widgets/commands.ts @@ -40,8 +40,9 @@ export class CommandRegistryModel extends IpylabModel { this._commands = IpylabModel.app.commands; super.initialize(attributes, options); this.on('comm_live_update', () => { - if (!this.comm_live) + if (!this.comm_live) { Private.customCommands.values().forEach(command => command.dispose()); + } }); this._commands.commandChanged.connect(this._sendCommandList, this); @@ -90,7 +91,9 @@ export class CommandRegistryModel extends IpylabModel { */ private _sendCommandList(sender?: object, args?: any): void { // this._commands.notifyCommandChanged(); - if (args && args.type != 'added' && args.type != 'removed') return; + if (args && args.type !== 'added' && args.type !== 'removed') { + return; + } this.set('commands', this._commands.listCommands()); this.save_changes(); } @@ -109,7 +112,9 @@ export class CommandRegistryModel extends IpylabModel { const { id, caption, label, iconClass, icon } = options; if (this._commands.hasCommand(id)) { const cmd = Private.customCommands.get(id); - if (cmd) cmd.dispose(); + if (cmd) { + cmd.dispose(); + } } let labIcon: LabIcon | null = null; @@ -150,7 +155,9 @@ export class CommandRegistryModel extends IpylabModel { private _removeCommand(command_id: string): null { if (Private.customCommands.has(command_id)) { const cmd = Private.customCommands.get(command_id); - if (cmd) cmd.dispose(); + if (cmd) { + cmd.dispose(); + } } this.save_changes(); return null; diff --git a/src/widgets/frontend.ts b/src/widgets/frontend.ts index 7ba8247e..9611c1a2 100644 --- a/src/widgets/frontend.ts +++ b/src/widgets/frontend.ts @@ -84,10 +84,12 @@ export class JupyterFrontEndModel extends IpylabModel { async operation(op: string, payload: any): Promise { function _get_result(result: any): any { - if (result.value === null) throw new Error('Cancelled'); + if (result.value === null) { + throw new Error('Cancelled'); + } return result.value; } - var result: any; + let result: any; switch (op) { case 'addToShell': return await this._addToShell(payload); @@ -100,9 +102,9 @@ export class JupyterFrontEndModel extends IpylabModel { return await InputDialog.getItem(payload).then(_get_result); case 'getNumber': return await InputDialog.getNumber(payload).then(_get_result); - case `getText`: + case 'getText': return await InputDialog.getText(payload).then(_get_result); - case `getPassword`: + case 'getPassword': return await InputDialog.getPassword(payload).then(_get_result); case 'showErrorMessage': await showErrorMessage(payload.title, payload.error, payload.buttons); @@ -139,7 +141,7 @@ export class JupyterFrontEndModel extends IpylabModel { const { serializedWidget, area, options } = payload; const model = await unpack_models(serializedWidget, this.widget_manager); const view = await this.widget_manager.create_view(model, {}); - var luminoWidget = view.luminoWidget; + let luminoWidget = view.luminoWidget; if (area === 'main') { luminoWidget = new IpylabMainAreaWidget({ content: view.luminoWidget, @@ -147,10 +149,14 @@ export class JupyterFrontEndModel extends IpylabModel { name: 'Ipylab' }); } - if (!luminoWidget.id) luminoWidget.id = DOMUtils.createDomID(); + if (!luminoWidget.id) { + luminoWidget.id = DOMUtils.createDomID(); + } this.shell.add(luminoWidget, area, options); model.on('comm_live_update', () => { - if (!model.comm_live) luminoWidget.dispose(); + if (!model.comm_live) { + luminoWidget.dispose(); + } }); return { id: luminoWidget.id }; } diff --git a/src/widgets/ipylab.ts b/src/widgets/ipylab.ts index 483955df..99ffe580 100644 --- a/src/widgets/ipylab.ts +++ b/src/widgets/ipylab.ts @@ -64,7 +64,9 @@ export class IpylabModel extends DOMWidgetModel { } check_closed() { - if (this.get('closed')) throw Error('This object is closed'); + if (this.get('closed')) { + throw Error('This object is closed'); + } } /** @@ -82,8 +84,11 @@ export class IpylabModel extends DOMWidgetModel { const [resolve, reject] = this._pending_backend_operation_callbacks.get(ipylab_FE); this._pending_backend_operation_callbacks.delete(ipylab_FE); - if (msg.error) reject(msg.error); - else resolve(msg); + if (msg.error) { + reject(msg.error); + } else { + resolve(msg); + } } else { // Backend operation (don't await it) this._do_operation_for_backend(msg); @@ -104,29 +109,32 @@ export class IpylabModel extends DOMWidgetModel { try { if (!operation) { - throw new Error(`operation not provided`); + throw new Error('operation not provided'); } if (!ipylab_BE) { - throw new Error(`ipylab_BE not provided}`); + throw new Error('ipylab_BE not provided}'); } - if (typeof operation != 'string') + if (typeof operation !== 'string') { throw new Error( `operation must be a string not ${typeof operation} operation='${operation}'` ); - + } + let result; if (operation === 'FE_execute') { - var result: JSONValue = await this._fe_execute(msg.kwgs); + result = await this._fe_execute(msg.kwgs); } else { - var result: JSONValue = await this.operation(operation, msg.kwgs); + result = await this.operation(operation, msg.kwgs); } - var buffers = null; + let buffers = null; if ((result as any)?.buffers) { buffers = (result as any).buffers; delete (result as any).buffers; } - if ((result as any)?.payload) result = (result as any).payload; + if ((result as any)?.payload) { + result = (result as any).payload; + } const content = { ipylab_BE: ipylab_BE, operation: operation, @@ -158,14 +166,16 @@ export class IpylabModel extends DOMWidgetModel { delete (payload as any).FE_execute; switch (mode) { case 'execute_method': { - let obj = this; - if (kwgs.widget) + let obj; + (obj as any) = this; + if (kwgs.widget) { obj = await unpack_models(kwgs.widget, this.widget_manager); + } const owner = getNestedObject( obj, kwgs.method.split('.').slice(0, -1).join('.') ); - var func = getNestedObject(this, kwgs.method) as Function; + let func = getNestedObject(this, kwgs.method) as Function; func = func.bind(owner, ...(payload as any).args); return await func(); } @@ -220,8 +230,10 @@ export class IpylabModel extends DOMWidgetModel { callbacks.set(ipylab_FE, [resolve, reject]); }); this.send(msg); - var result = await promise; - if (result === IpylabModel.OPERATION_DONE) result = null; + let result = await promise; + if (result === IpylabModel.OPERATION_DONE) { + result = null; + } return result; } diff --git a/src/widgets/main_area.ts b/src/widgets/main_area.ts index 249b1a01..46a96680 100644 --- a/src/widgets/main_area.ts +++ b/src/widgets/main_area.ts @@ -25,7 +25,8 @@ export class IpylabMainAreaWidget extends MainAreaWidget { constructor(options: IpylabMainAreaWidget.IOptions) { //TODO: support more parts of the MainAreaWidget - var { content, kernelId, name, path, basePath, type } = options; + const { content, kernelId, name, basePath, type } = options; + let path = options.path; super({ content: content }); if (!path) { path = PathExt.join(basePath || '', `${name}-${UUID.uuid4()}`); @@ -121,7 +122,7 @@ export class MainAreaModel extends IpylabModel { } async _load_main_area_widget(payload: any) { - var { area, options, className } = payload; + const { area, options, className } = payload; const content = this.get('content'); const view = await this.widget_manager.create_view(content, {}); const luminoWidget = new IpylabMainAreaWidget({ @@ -132,7 +133,9 @@ export class MainAreaModel extends IpylabModel { type: this.sessionContext.type }); luminoWidget.revealed; - if (className) luminoWidget.addClass(className); + if (className) { + luminoWidget.addClass(className); + } luminoWidget.disposed.connect(() => { this.set('loaded', false); this.save_changes(); @@ -148,7 +151,9 @@ export class MainAreaModel extends IpylabModel { } _unload_mainarea_widget() { - if (this._luminoWidget) this._luminoWidget.dispose(); + if (this._luminoWidget) { + this._luminoWidget.dispose(); + } this._close_console(); } async _open_console(options: any) { @@ -165,7 +170,9 @@ export class MainAreaModel extends IpylabModel { } ); // The console toobar takes up space and currently only provides a debugger - if (cp?.toolbar?.node) cp.node.removeChild(cp.toolbar.node); + if (cp?.toolbar?.node) { + cp.node.removeChild(cp.toolbar.node); + } await cp.sessionContext.ready; cp.disposed.connect(() => { if (this._consolePanel === cp) { @@ -187,7 +194,9 @@ export class MainAreaModel extends IpylabModel { } _close_console() { - if (this._consolePanel) this._consolePanel.dispose(); + if (this._consolePanel) { + this._consolePanel.dispose(); + } } /** diff --git a/src/widgets/palette.ts b/src/widgets/palette.ts index 65e88c11..437ac9fa 100644 --- a/src/widgets/palette.ts +++ b/src/widgets/palette.ts @@ -106,7 +106,9 @@ export class CommandPaletteModel extends IpylabModel { const itemId = `${id} | ${category}`; if (this._customItems.has(itemId)) { const cmd = this._customItems.get(itemId); - if (cmd) cmd.dispose(); + if (cmd) { + cmd.dispose(); + } } this._customItems.delete(itemId); return null; diff --git a/src/widgets/panel.ts b/src/widgets/panel.ts index 01afc48a..5f71c679 100644 --- a/src/widgets/panel.ts +++ b/src/widgets/panel.ts @@ -43,7 +43,9 @@ export class PanelModel extends BoxModel { close(comm_closed?: boolean): Promise { if (!this.get('closed')) { this.set('closed', true); - if (this.comm) this.save_changes(); + if (this.comm) { + this.save_changes(); + } } return super.close(comm_closed); } diff --git a/src/widgets/python_backend.ts b/src/widgets/python_backend.ts index 2692a410..10d06203 100644 --- a/src/widgets/python_backend.ts +++ b/src/widgets/python_backend.ts @@ -31,7 +31,9 @@ export class PythonBackendModel { execute: () => IpylabModel.python_backend.checkStart() } ); - if (this._palletItem) this._palletItem.dispose(); + if (this._palletItem) { + this._palletItem.dispose(); + } this._palletItem = IpylabModel.palette.addItem({ command: PythonBackendModel.checkstart, category: 'ipylab', diff --git a/src/widgets/utils.ts b/src/widgets/utils.ts index b5787154..7fb620ac 100644 --- a/src/widgets/utils.ts +++ b/src/widgets/utils.ts @@ -87,8 +87,12 @@ export async function newNotebook({ kernelName: kernelName }); await nb.sessionContext.ready; - if (name) await nb.sessionContext.session.setName(name); - if (path) await nb.sessionContext.session.setPath(path); + if (name) { + await nb.sessionContext.session.setName(name); + } + if (path) { + await nb.sessionContext.session.setPath(path); + } return nb; } @@ -182,18 +186,20 @@ function registerWidgets(manager: KernelWidgetManager) { * @returns */ export function getNestedObject(base: object, path: string): any { - var obj: Object = base; - var path_: String = ''; + let obj: object = base; + let path_: string = ''; const parts = path.split('.'); - var attr = ''; + let attr = ''; for (let i = 0; i < parts.length; i++) { attr = parts[i]; if (attr in obj) { obj = obj[attr as keyof typeof obj]; path_ = !path_ ? attr : `${path_}.${attr}`; - } else break; + } else { + break; + } } - if (path_ != path) { + if (path_ !== path) { throw new Error( `Failed to get the nested attribute ${path_}.${attr} ` + ` (base='${(base as any).name ?? 'unknown'}') ` @@ -207,7 +213,7 @@ export function getNestedObject(base: object, path: string): any { * @param code The function as a string: eg. 'function (a, b) { return a + b; }' * @returns */ -export function toFunction(code: string): Function { +export function toFunction(code: string) { return new Function('return ' + code)(); } @@ -224,7 +230,7 @@ export function transformObject( options: string | any, thisArg: object = null ): JSONValue { - const mode = typeof options == 'string' ? options : options.mode; + const mode = typeof options === 'string' ? options : options.mode; switch (mode) { case 'done': return IpylabModel.OPERATION_DONE; @@ -238,14 +244,14 @@ export function transformObject( // expects simple: {parts:['dotted.attribute']} // or advanced: {parts:[{path:'dotted.attribute', transform:'...' }] const result: { [key: string]: any } = new Object(); - for (var i = 0; i < options.parts.length; i++) { - if (typeof options.parts[i] == 'string') { + for (let i = 0; i < options.parts.length; i++) { + if (typeof options.parts[i] === 'string') { var path = options.parts[i]; var transform: any = 'raw'; } else { var { path, transform } = options.parts[i]; } - var part = getNestedObject(obj, path); + const part = getNestedObject(obj, path); result[path] = transformObject(part, transform); } return result; diff --git a/style/widget.css b/style/widget.css index f00ec222..200e8749 100644 --- a/style/widget.css +++ b/style/widget.css @@ -3,13 +3,11 @@ | Distributed under the terms of the Modified BSD License. |---------------------------------------------------------------------------- */ - .jp-SideAreaWidget { background: var(--jp-layout-color1); display: flex; } - .ipylab-panel { display: flex; box-sizing: border-box; @@ -18,7 +16,7 @@ width: 100%; margin: var(--jp-widgets-margin); color: var(--jp-widgets-color); - overflow: auto; + overflow: auto; } .ipylab-splitpanel { @@ -37,11 +35,11 @@ background-color: var(--jp-border-color2); } -.ipylab-main-area{ - display:block; +.ipylab-main-area { + display: block; height: 100%; padding: var(--jp-notebook-padding); outline: none; overflow: auto; - background: var(--jp-layout-color0); -} \ No newline at end of file + background: var(--jp-layout-color0); +}