├── .binder ├── environment.yml └── postBuild ├── .eslintignore ├── .eslintrc.js ├── .github ├── pull_request_template.md └── workflows │ ├── benchmark-report.yml │ ├── binder-on-pr.yml │ ├── build.yml │ ├── check-release.yml │ ├── enforce-labels.yml │ ├── main.yml │ ├── packaging.yml │ ├── prep-release.yml │ ├── publish-release.yml │ ├── ui-tests.yml │ └── update_galata_references.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierignore ├── .prettierrc ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE.md ├── docs ├── changelog.md ├── conf.py ├── contribute.md ├── customize.md ├── deploy.md ├── environment.yml ├── index.md ├── install.md ├── metadata-template-classic.png ├── metadata-theme-classic.png ├── using.md └── voila-logo.svg ├── etc └── jupyter │ ├── jupyter_notebook_config.d │ └── voila.json │ ├── jupyter_server_config.d │ └── voila.json │ └── nbconfig │ └── notebook.d │ └── voila.json ├── hatch_build.py ├── install.json ├── lerna.json ├── notebooks ├── basics.ipynb ├── bokeh.ipynb ├── bqplot.ipynb ├── dashboard.ipynb ├── gridspecLayout.ipynb ├── interactive.ipynb ├── ipympl.ipynb ├── ipyvolume.ipynb ├── jupyter_config.json ├── mimerenderers.ipynb ├── multiple_widgets.ipynb ├── query-strings.ipynb ├── reveal.ipynb ├── xleaflet.ipynb └── yaml.ipynb ├── package.json ├── packages ├── jupyterlab-preview │ ├── README.md │ ├── package.json │ ├── schema │ │ └── plugin.json │ ├── src │ │ ├── icons.ts │ │ ├── index.ts │ │ ├── preview.tsx │ │ └── svg.d.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ ├── index.js │ │ └── voila.svg │ └── tsconfig.json ├── voila │ ├── package.json │ ├── publicpath.js │ ├── src │ │ ├── app.ts │ │ ├── bootstrap.ts │ │ ├── global.d.ts │ │ ├── index.ts │ │ ├── main.ts │ │ ├── plugins │ │ │ ├── outputs │ │ │ │ ├── index.ts │ │ │ │ ├── plugins.ts │ │ │ │ ├── renderedcells.ts │ │ │ │ └── tools.ts │ │ │ ├── path.ts │ │ │ ├── themes │ │ │ │ ├── index.ts │ │ │ │ └── thememanager.ts │ │ │ ├── translator.ts │ │ │ └── tree │ │ │ │ ├── browser.ts │ │ │ │ ├── index.ts │ │ │ │ └── listing.ts │ │ ├── services │ │ │ ├── event.ts │ │ │ ├── kernelspec.ts │ │ │ ├── servicemanager.ts │ │ │ └── user.ts │ │ ├── sharedscope.ts │ │ ├── shell.ts │ │ ├── tools.ts │ │ ├── tree.ts │ │ ├── treebootstrap.ts │ │ └── voilaplugins.ts │ ├── style │ │ ├── base.css │ │ ├── index.css │ │ └── index.js │ ├── tsconfig.json │ └── webpack.config.js ├── widgets_manager7 │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── manager.ts │ └── tsconfig.json └── widgets_manager8 │ ├── README.md │ ├── package.json │ ├── src │ ├── index.ts │ └── manager.ts │ └── tsconfig.json ├── pyproject.toml ├── readthedocs.yml ├── requirements-visual-test.txt ├── scripts ├── bump-version.py └── reset-stable.sh ├── setup.py ├── share └── jupyter │ └── voila │ └── templates │ ├── base │ ├── 404.html │ ├── browser-open.html │ ├── error.html │ ├── jupyter_widgets.html.j2 │ ├── log.macro.html.j2 │ ├── page.html │ ├── spinner.macro.html.j2 │ ├── static │ │ └── require.min.js │ ├── tree.html │ └── voila_setup.macro.html.j2 │ ├── classic │ └── index.html.j2 │ ├── lab │ ├── browser-open.html │ ├── error.html │ ├── index.html.j2 │ ├── page.html │ ├── tree-lab.html │ └── tree.html │ └── reveal │ └── index.html.j2 ├── tests ├── app │ ├── __init__.py │ ├── cgi-test.py │ ├── config_paths_test.py │ ├── conftest.py │ ├── contents_handler_test.py │ ├── cwd_subdir_test.py │ ├── cwd_test.py │ ├── execute_cpp_test.py │ ├── execute_test.py │ ├── image_inlining_test.py │ ├── kernel_death_test.py │ ├── many_iopub_messages_test.py │ ├── nbextensions_test.py │ ├── no_kernelspec_test.py │ ├── no_metadata.py │ ├── no_strip_sources_test.py │ ├── non_existing_kernel_test.py │ ├── notebooks_test.py │ ├── page_config_hook_test.py │ ├── preheat_activation_test.py │ ├── preheat_configuration_test.py │ ├── preheat_default_kernel_env_test.py │ ├── preheat_multiple_notebooks_test.py │ ├── preheat_with_query_string_test.py │ ├── prelaunch_hook_papermill_test.py │ ├── prelaunch_hook_test.py │ ├── preprocessor_test.py │ ├── progressive_rendering_activation_test.py │ ├── progressive_rendering_test.py │ ├── serve_directory_test.py │ ├── show_traceback_test.py │ ├── shutdown_kernel_test.py │ ├── static_files_test.py │ ├── syntax_error_test.py │ ├── template_arg_test.py │ ├── template_cli_test.py │ ├── template_config_file_test.py │ ├── template_custom_test.py │ ├── template_sanity_test.py │ ├── timeout_test.py │ └── tree_test.py ├── configs │ ├── general │ │ ├── nbconfig │ │ │ └── notebook.d │ │ │ │ └── ipytest.json │ │ └── voila.json │ └── preheat │ │ └── voila.json ├── conftest.py ├── execute_output_test.py ├── notebooks │ ├── autokill.ipynb │ ├── cgi.ipynb │ ├── cwd.ipynb │ ├── file.txt │ ├── images.ipynb │ ├── jupyter.svg │ ├── many_iopub_messages.ipynb │ ├── no_kernelspec.ipynb │ ├── no_metadata.ipynb │ ├── non_existing_kernel.ipynb │ ├── other_comms.ipynb │ ├── output.ipynb │ ├── preheat │ │ ├── default_env_variables.ipynb │ │ ├── denylisted.ipynb │ │ ├── get_query_string.ipynb │ │ └── pre_heat.ipynb │ ├── print.ipynb │ ├── print.py │ ├── print.xcpp │ ├── print_cpp.ipynb │ ├── print_parameterized.ipynb │ ├── skip-voila-cell.ipynb │ ├── sleep.ipynb │ ├── sleep10seconds.ipynb │ ├── subdir │ │ └── cwd_subdir.ipynb │ └── syntax_error.ipynb ├── server │ ├── __init__.py │ ├── conftest.py │ ├── cwd_subdir_test.py │ ├── execute_cpp_test.py │ ├── execute_test.py │ ├── nbextensions_test.py │ ├── no_strip_sources_test.py │ ├── show_traceback_test.py │ ├── static_files_test.py │ └── tree_test.py ├── skip_template │ ├── setup.py │ └── share │ │ └── jupyter │ │ └── voila │ │ └── templates │ │ └── skip_template │ │ └── conf.json ├── template_prefixes │ ├── loader_test.py │ └── system │ │ ├── nbconvert │ │ └── templates │ │ │ ├── bar │ │ │ ├── index.tpl │ │ │ └── parent.tpl │ │ │ ├── default │ │ │ ├── index.tpl │ │ │ └── parent.tpl │ │ │ └── foo │ │ │ ├── conf.json │ │ │ └── index.tpl │ │ └── voila │ │ └── templates │ │ ├── default │ │ └── index.tpl │ │ └── foo │ │ └── index.tpl ├── test_template │ ├── setup.py │ └── share │ │ └── jupyter │ │ └── voila │ │ └── templates │ │ └── test_template │ │ ├── conf.json │ │ ├── index.html.j2 │ │ ├── static │ │ ├── only-in-test-template.js │ │ └── voila.js │ │ └── voila.tpl └── utils_test.py ├── tsconfig.eslint.json ├── tsconfigbase.json ├── ui-tests ├── package.json ├── playwright.config.js ├── tests │ ├── utils.ts │ ├── voila.test.ts │ └── voila.test.ts-snapshots │ │ ├── 404-classic-linux.png │ │ ├── 404-dark-linux.png │ │ ├── 404-linux.png │ │ ├── basics-classic-linux.png │ │ ├── basics-dark-linux.png │ │ ├── basics-linux.png │ │ ├── basics-miami-linux.png │ │ ├── bokeh-linux.png │ │ ├── bqplot-linux.png │ │ ├── gridspecLayout-linux.png │ │ ├── interactive-linux.png │ │ ├── ipympl-linux.png │ │ ├── mimerenderers-linux.png │ │ ├── multiple-widgets-linux.png │ │ ├── query-strings-linux.png │ │ ├── reveal-linux.png │ │ ├── voila-tree-classic-linux.png │ │ ├── voila-tree-dark-linux.png │ │ ├── voila-tree-light-linux.png │ │ ├── voila-tree-miami-linux.png │ │ └── yaml-linux.png ├── voila-benchmark-expected.json └── yarn.lock ├── voila-basics.gif ├── voila-bqplot.gif ├── voila-cling.gif ├── voila-sources.gif ├── voila ├── __init__.py ├── __main__.py ├── _version.py ├── app.py ├── configuration.py ├── execute.py ├── exporter.py ├── handler.py ├── notebook_renderer.py ├── paths.py ├── request_info_handler.py ├── server_extension.py ├── shutdown_kernel_handler.py ├── static │ └── extension.js ├── static_file_handler.py ├── tornado │ ├── contentshandler.py │ ├── execution_request_handler.py │ ├── handler.py │ ├── kernel_websocket_handler.py │ └── treehandler.py ├── treehandler.py ├── utils.py ├── voila_identity_provider.py └── voila_kernel_manager.py └── yarn.lock /.binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: voila 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - jupyterlab=4 6 | - ipywidgets=8 7 | - ipyvolume 8 | - bqplot 9 | - scipy 10 | - ipympl 11 | - python=3.11 12 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | jlpm && jlpm run build 6 | python -m pip install . 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | 3 | node_modules 4 | **/build 5 | **/lib 6 | **/node_modules 7 | **/mock_packages 8 | **/static 9 | **/labextensions 10 | **/typings 11 | **/schemas 12 | **/themes 13 | **/templates 14 | coverage 15 | *.map.js 16 | *.bundle.js 17 | *.voila.js 18 | 19 | # jetbrains IDE stuff 20 | .idea/ 21 | 22 | # ms IDE stuff 23 | .history/ 24 | .vscode/ 25 | 26 | ui-tests/playwright-report 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | commonjs: true, 6 | node: true, 7 | 'jest/globals': true 8 | }, 9 | root: true, 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:react/recommended', 15 | 'plugin:jest/recommended' 16 | ], 17 | parser: '@typescript-eslint/parser', 18 | parserOptions: { 19 | project: 'tsconfig.eslint.json', 20 | sourceType: 'module' 21 | }, 22 | plugins: ['@typescript-eslint', 'jest'], 23 | rules: { 24 | '@typescript-eslint/naming-convention': [ 25 | 'error', 26 | { 27 | selector: 'interface', 28 | format: ['PascalCase'], 29 | custom: { 30 | regex: '^I[A-Z]', 31 | match: true 32 | } 33 | } 34 | ], 35 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 36 | '@typescript-eslint/no-explicit-any': 'off', 37 | '@typescript-eslint/explicit-module-boundary-types': 'off', 38 | '@typescript-eslint/no-namespace': 'off', 39 | '@typescript-eslint/no-var-requires': 'off', 40 | '@typescript-eslint/no-use-before-define': 'off', 41 | '@typescript-eslint/no-empty-interface': 'off', 42 | '@typescript-eslint/quotes': [ 43 | 'error', 44 | 'single', 45 | { avoidEscape: true, allowTemplateLiterals: false } 46 | ], 47 | curly: ['error', 'all'], 48 | eqeqeq: 'error', 49 | 'prefer-arrow-callback': 'error', 50 | 'jest/no-done-callback': 'off' 51 | }, 52 | settings: { 53 | react: { 54 | version: 'detect' 55 | } 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## References 9 | 10 | 11 | 12 | 13 | 14 | ## Code changes 15 | 16 | 17 | 18 | ## User-facing changes 19 | 20 | 21 | 22 | 28 | 29 | ## Backwards-incompatible changes 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/benchmark-report.yml: -------------------------------------------------------------------------------- 1 | # Commenting on a PR requires write access 2 | # This script is taken from jupyterlab project. 3 | 4 | name: Comment on the pull request 5 | 6 | on: 7 | workflow_run: 8 | workflows: ['UI Tests'] 9 | types: 10 | - completed 11 | 12 | permissions: 13 | pull-requests: write 14 | 15 | jobs: 16 | upload: 17 | runs-on: ubuntu-latest 18 | if: > 19 | ${{ github.event.workflow_run.event == 'pull_request' && 20 | github.event.workflow_run.conclusion == 'success' }} 21 | steps: 22 | - name: 'Download artifact' 23 | uses: actions/github-script@v3.1.0 24 | with: 25 | script: | 26 | var artifacts = await github.actions.listWorkflowRunArtifacts({ 27 | owner: context.repo.owner, 28 | repo: context.repo.repo, 29 | run_id: ${{github.event.workflow_run.id }}, 30 | }); 31 | var matchArtifact = artifacts.data.artifacts.filter((artifact) => { 32 | return artifact.name == "voila-benchmark-report" 33 | })[0]; 34 | var download = await github.actions.downloadArtifact({ 35 | owner: context.repo.owner, 36 | repo: context.repo.repo, 37 | artifact_id: matchArtifact.id, 38 | archive_format: 'zip', 39 | }); 40 | var fs = require('fs'); 41 | fs.writeFileSync('${{github.workspace}}/benchmark-assets.zip', Buffer.from(download.data)); 42 | - run: unzip benchmark-assets.zip 43 | - name: 'Comment on PR' 44 | uses: actions/github-script@v3 45 | with: 46 | github-token: ${{ secrets.GITHUB_TOKEN }} 47 | script: | 48 | var fs = require('fs'); 49 | var issue_number = Number(fs.readFileSync('./NR')); 50 | var report = String(fs.readFileSync('./voila-benchmark.md')) 51 | 52 | // Get the existing comments. 53 | const {data: comments} = await github.issues.listComments({ 54 | owner: context.repo.owner, 55 | repo: context.repo.repo, 56 | issue_number: issue_number, 57 | }) 58 | 59 | // Find any comment already made by the bot. 60 | const botComments = comments.filter(comment => comment.user.id === 41898282) 61 | const botComment = botComments.find(cm => !cm.body.includes('Launch a Binder') ) 62 | if (botComment) { 63 | await github.issues.updateComment({ 64 | owner: context.repo.owner, 65 | repo: context.repo.repo, 66 | comment_id: botComment.id, 67 | body: report 68 | }) 69 | } else { 70 | await github.issues.createComment({ 71 | owner: context.repo.owner, 72 | repo: context.repo.repo, 73 | issue_number: issue_number, 74 | body: report 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/binder-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Binder Badge 2 | on: 3 | pull_request_target: 4 | types: [opened] 5 | 6 | jobs: 7 | binder: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - uses: jupyterlab/maintainer-tools/.github/actions/binder-link@v1 13 | with: 14 | github_token: ${{ secrets.github_token }} 15 | url_path: voila/tree 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Frontend Packages 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | schedule: 11 | - cron: '0 2 * * 1-5' # run on weekdays at 2:00am UTC 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | 24 | - name: Base Setup 25 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 26 | 27 | - name: Install Dependencies 28 | run: | 29 | python -m pip install -U jupyterlab~=4.0 jupyter_packaging~=0.10 "notebook<7" 30 | 31 | - name: Install the Voilà Preview JupyterLab extension 32 | run: | 33 | python -m pip install . 34 | 35 | - name: Check the extensions are installed 36 | run: | 37 | jupyter nbextension list 2>&1 | grep -ie "voila/extension.*enabled" - 38 | jupyter labextension list 2>&1 | grep -ie "@voila-dashboards/jupyterlab-preview.*enabled.*ok" - 39 | jupyter server extension list 2>&1 | grep -ie "voila\.server_extension.*enabled" - 40 | 41 | - name: Browser check 42 | run: | 43 | python -m jupyterlab.browser_check 44 | 45 | - name: Lint 46 | run: | 47 | jlpm 48 | jlpm run eslint:check 49 | jlpm run prettier:check 50 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | check_release: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | 24 | - name: Base Setup 25 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 26 | 27 | - name: Check Release 28 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 29 | with: 30 | token: ${{ secrets.GITHUB_TOKEN }} 31 | version_spec: next 32 | 33 | - name: Upload Distributions 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: voila-releaser-dist-${{ github.run_number }} 37 | path: .jupyter_releaser_checkout/dist 38 | -------------------------------------------------------------------------------- /.github/workflows/enforce-labels.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: enforce-triage-label 11 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 12 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 1: Prep Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version_spec: 6 | description: "New Version Specifier" 7 | default: "next" 8 | required: false 9 | branch: 10 | description: "The branch to target" 11 | required: false 12 | post_version_spec: 13 | description: "Post Version Specifier" 14 | required: false 15 | silent: 16 | description: "Set a placeholder in the changelog and don't publish the release." 17 | required: false 18 | type: boolean 19 | since: 20 | description: "Use PRs with activity since this date or git reference" 21 | required: false 22 | since_last_stable: 23 | description: "Use PRs with activity since the last stable git tag" 24 | required: false 25 | type: boolean 26 | jobs: 27 | prep_release: 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: write 31 | steps: 32 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 33 | 34 | - name: Prep Release 35 | id: prep-release 36 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | version_spec: ${{ github.event.inputs.version_spec }} 40 | silent: ${{ github.event.inputs.silent }} 41 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 42 | branch: ${{ github.event.inputs.branch }} 43 | since: ${{ github.event.inputs.since }} 44 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 45 | 46 | - name: "** Next Step **" 47 | run: | 48 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 49 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 2: Publish Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | branch: 6 | description: "The target branch" 7 | required: false 8 | release_url: 9 | description: "The URL of the draft GitHub release" 10 | required: false 11 | steps_to_skip: 12 | description: "Comma separated list of steps to skip" 13 | required: false 14 | 15 | jobs: 16 | publish_release: 17 | runs-on: ubuntu-latest 18 | environment: release 19 | permissions: 20 | id-token: write 21 | steps: 22 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 23 | 24 | - uses: actions/create-github-app-token@v1 25 | id: app-token 26 | with: 27 | app-id: ${{ vars.APP_ID }} 28 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 29 | 30 | - name: Populate Release 31 | id: populate-release 32 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 33 | with: 34 | token: ${{ steps.app-token.outputs.token }} 35 | branch: ${{ github.event.inputs.branch }} 36 | release_url: ${{ github.event.inputs.release_url }} 37 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 38 | 39 | - name: Finalize Release 40 | id: finalize-release 41 | env: 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 44 | with: 45 | token: ${{ steps.app-token.outputs.token }} 46 | release_url: ${{ steps.populate-release.outputs.release_url }} 47 | 48 | - name: "** Next Step **" 49 | if: ${{ success() }} 50 | run: | 51 | echo "Verify the final release" 52 | echo ${{ steps.finalize-release.outputs.release_url }} 53 | 54 | - name: "** Failure Message **" 55 | if: ${{ failure() }} 56 | run: | 57 | echo "Failed to Publish the Draft Release Url:" 58 | echo ${{ steps.populate-release.outputs.release_url }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | build 3 | dist 4 | _build 5 | docs/man/*.gz 6 | docs/source/config.rst 7 | node_modules 8 | *.py[co] 9 | __pycache__ 10 | *.egg-info 11 | *~ 12 | *.bak 13 | .ipynb_checkpoints 14 | .tox 15 | .DS_Store 16 | \#*# 17 | .#* 18 | .coverage 19 | 20 | *.swp 21 | *.map 22 | .idea/ 23 | .vscode/ 24 | Read the Docs 25 | config.rst 26 | 27 | /.project 28 | /.pydevproject 29 | 30 | package-lock.json 31 | 32 | share/jupyter/voila/templates/base/static/*voila.js 33 | share/jupyter/voila/templates/base/static/*treepage.js 34 | share/jupyter/voila/templates/base/static/*voila-style.js 35 | share/jupyter/voila/templates/base/static/*.woff 36 | share/jupyter/voila/templates/base/static/*.woff2 37 | share/jupyter/voila/templates/base/static/*.eot 38 | share/jupyter/voila/templates/base/static/*.svg 39 | share/jupyter/voila/templates/base/static/*.ttf 40 | share/jupyter/voila/templates/base/static/labvariables.css 41 | share/jupyter/voila/templates/base/static/materialcolors.css 42 | share/jupyter/voila/templates/base/static/*.LICENSE.txt 43 | 44 | 45 | lib 46 | 47 | voila/labextensions 48 | tsconfig.tsbuildinfo 49 | 50 | ui-tests/playwright-report 51 | ui-tests/test-results 52 | ui-tests/benchmark-results 53 | ui-tests/jlab_root 54 | 55 | .yarn/ 56 | 57 | share/jupyter/voila/schemas 58 | share/jupyter/voila/themes 59 | share/jupyter/voila/style.js 60 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | # pre-commit.ci will open PRs updating our hooks once a month 3 | autoupdate_schedule: monthly 4 | # skip any check that needs internet access 5 | skip: [prettier, eslint] 6 | 7 | repos: 8 | # Autoformat and linting, misc. details 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v5.0.0 11 | hooks: 12 | - id: forbid-new-submodules 13 | - id: end-of-file-fixer 14 | exclude: galata/.*-snapshots 15 | - id: check-case-conflict 16 | - id: requirements-txt-fixer 17 | - id: check-added-large-files 18 | - id: check-case-conflict 19 | - id: check-toml 20 | - id: check-yaml 21 | - id: debug-statements 22 | - id: check-builtin-literals 23 | - id: trailing-whitespace 24 | exclude: .bumpversion.cfg 25 | 26 | # Autoformat: Python code 27 | - repo: https://github.com/psf/black 28 | rev: 25.1.0 29 | hooks: 30 | - id: black 31 | 32 | - repo: https://github.com/astral-sh/ruff-pre-commit 33 | rev: v0.9.9 34 | hooks: 35 | - id: ruff 36 | args: ['--fix'] 37 | 38 | - repo: local 39 | hooks: 40 | - id: prettier 41 | name: prettier 42 | entry: 'yarn run prettier' 43 | language: node 44 | types_or: [json, ts, tsx, javascript, jsx, css] 45 | - id: eslint 46 | name: eslint 47 | entry: 'yarn run eslint' 48 | language: node 49 | types_or: [ts, tsx, javascript, jsx] 50 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | **/labextensions 6 | **/static 7 | build 8 | notebooks/ 9 | .vscode/ 10 | .pytest_cache 11 | share/ 12 | 13 | ui-tests/playwright-report 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none" 4 | } 5 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableImmutableInstalls: false 2 | 3 | nodeLinker: node-modules 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Voilà 2 | 3 | Voilà is a subproject of Project Jupyter and subject to the [Jupyter governance](https://github.com/jupyter/governance) and [Code of conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md). 4 | 5 | ## General Guidelines 6 | 7 | For general documentation about contributing to Jupyter projects, see the [Project Jupyter Contributor Documentation](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html). 8 | 9 | ## Community 10 | 11 | The Voilà team organizes public video meetings. The schedule for future meetings and minutes of past meetings can be found on our [team compass](https://voila-dashboards.github.io/). 12 | 13 | ## Setting up a development environment 14 | 15 | Check out the instructions in the [contributing documentation](https://voila.readthedocs.io/en/latest/contribute.html) to setup a local environment. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2018 Voilà contributors. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | a. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | b. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | c. Neither the name of the authors nor the names of the contributors to 17 | this package may be used to endorse or promote products 18 | derived from this software without specific prior written 19 | permission. 20 | 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | DAMAGE. 33 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of Voilà 2 | 3 | ## Using `jupyter_releaser` 4 | 5 | The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#typical-workflow). 6 | 7 | This repository contains the two workflows located under https://github.com/voila-dashboards/voila/actions: 8 | 9 | - Step 1: Prep Release 10 | - Step 2: Publish Release 11 | 12 | ## Bumping versions 13 | 14 | `voila` follows a similar bump strategy as in JupyterLab: 15 | 16 | https://github.com/jupyterlab/jupyterlab/blob/master/RELEASE.md#bump-version 17 | 18 | `jupyter_releaser` handles the bump automatically so it is not necessary to do it manually, as long as the spec is correctly specified in the workflow. 19 | 20 | ### Manual bump 21 | 22 | To manually bump the version, run: 23 | 24 | ```bash 25 | # install the dependencies 26 | python -m pip install -e ".[test,dev]" 27 | 28 | # bump the version 29 | python scripts/bump-version.py 30 | ``` 31 | 32 | Where `` can be one of the following: `patch`, `minor`, `major`, `release` or `next` (auto for `patch` or `minor`). 33 | 34 | ## Major JS bump 35 | 36 | When there is a breaking change in a JS package, the version of the package should be bumped by one major version. 37 | 38 | For example if the version of the preview extension was `2.1.0-alpha.1` and a breaking is introduced, bump to `3.0.0-alpha.0`. 39 | 40 | ## Releasing on conda-forge 41 | 42 | 1. Open a new PR on https://github.com/conda-forge/voila-feedstock to update the `version` and the `sha256` hash (see [example](https://github.com/conda-forge/voila-feedstock/pull/23/files)) 43 | 2. Wait for the tests 44 | 3. Merge the PR 45 | 46 | The new version will be available on `conda-forge` soon after. 47 | 48 | ### Making a new release of @voila-dashboards/jupyterlab-preview 49 | 50 | The prebuilt extension is already packaged in the main Python package. 51 | 52 | However we also publish it to `npm` to: 53 | 54 | - let other third-party extensions depend on `@voila-dashboards/jupyterlab-preview` 55 | - let users install from source if they would like to 56 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CHANGELOG.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Add dev disclaimer. 4 | _release = {} 5 | exec( 6 | compile(open("../voila/_version.py").read(), "../voila/_version.py", "exec"), 7 | _release, 8 | ) 9 | if _release["version_info"][-1] == "dev": 10 | rst_prolog = """ 11 | .. note:: 12 | 13 | This documentation is for a development version of Voilà. There may be 14 | significant differences from the latest stable release. 15 | 16 | """ 17 | 18 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 19 | 20 | html_theme = "pydata_sphinx_theme" 21 | html_theme_options = {"github_url": "https://github.com/voila-dashboards/voila"} 22 | 23 | 24 | extensions = [ 25 | "sphinx.ext.autodoc", 26 | "sphinx.ext.intersphinx", 27 | "sphinx.ext.napoleon", 28 | "sphinxcontrib.video", 29 | "myst_parser", 30 | ] 31 | 32 | myst_enable_extensions = ["colon_fence"] 33 | source_suffix = ".md" 34 | master_doc = "index" 35 | project = "voila" 36 | copyright = "2020, The Voilà Development Team" 37 | author = "The Voilà Development Team" 38 | version = ".".join(map(str, _release["version_info"][:2])) 39 | release = _release["__version__"] 40 | language = "en" 41 | 42 | html_logo = "voila-logo.svg" 43 | 44 | exclude_patterns = [] 45 | highlight_language = "python" 46 | pygments_style = "sphinx" 47 | todo_include_todos = False 48 | htmlhelp_basename = "voiladoc" 49 | 50 | intersphinx_mapping = {"python": ("https://docs.python.org", None)} 51 | 52 | myst_heading_anchors = 3 53 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | name: voila 2 | channels: 3 | - conda-forge 4 | - conda 5 | dependencies: 6 | - myst-parser 7 | - nodejs 8 | - jupyterlab 9 | - sphinx 10 | - pydata-sphinx-theme 11 | - pip: 12 | - sphinxcontrib-video 13 | - .. 14 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | % Copyright (c) 2018, Voilà Contributors 2 | % Copyright (c) 2018, QuantStack 3 | % 4 | % Distributed under the terms of the BSD 3-Clause License. 5 | % 6 | % The full license is in the file LICENSE, distributed with this software. 7 | 8 | :::{note} 9 | Want to turn your Voilà dashboard into a static webpage? Check out [Voici](https://github.com/voila-dashboards/voici)! 10 | The combination of [JupyterLite](https://jupyterlite.readthedocs.io) and Voilà 11 | ::: 12 | 13 | _From notebooks to standalone web applications and dashboards._ 14 | 15 | Voilà allows you to convert a Jupyter Notebook into an 16 | interactive dashboard that allows you to share your work with others. It 17 | is secure and customizable, giving you control over what your readers 18 | experience. 19 | 20 | For example, here's a dashboard created with Voilà. (You can 21 | try it interactively at the following Binder link) 22 | 23 | ```{image} https://mybinder.org/badge_logo.svg 24 | :target: https://mybinder.org/v2/gh/voila-dashboards/voila/stable?urlpath=voila%2Ftree%2Fnotebooks 25 | ``` 26 | 27 | ```{image} ../voila-basics.gif 28 | 29 | ``` 30 | 31 | # Table of contents 32 | 33 | For more information about Voilà, see the sections below. 34 | 35 | ```{toctree} 36 | :maxdepth: 2 37 | 38 | install 39 | using 40 | customize 41 | deploy 42 | changelog 43 | contribute 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | % Copyright (c) 2018, Voilà Contributors 2 | % Copyright (c) 2018, QuantStack 3 | % 4 | % Distributed under the terms of the BSD 3-Clause License. 5 | % 6 | % The full license is in the file LICENSE, distributed with this software. 7 | 8 | (install)= 9 | 10 | # Installing Voilà 11 | 12 | Voilà can be installed with the `mamba` or `conda` package manager 13 | 14 | ```bash 15 | mamba install -c conda-forge voila 16 | ``` 17 | 18 | or from `PyPI`: 19 | 20 | ```bash 21 | pip install voila 22 | ``` 23 | 24 | Once Voilà is installed, it can be used either as a Command-Line Interface, 25 | or as a Jupyter Server extension. See {ref}`using` for information on how to use Voilà. 26 | -------------------------------------------------------------------------------- /docs/metadata-template-classic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/docs/metadata-template-classic.png -------------------------------------------------------------------------------- /docs/metadata-theme-classic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/docs/metadata-theme-classic.png -------------------------------------------------------------------------------- /etc/jupyter/jupyter_notebook_config.d/voila.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "voila.server_extension": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /etc/jupyter/jupyter_server_config.d/voila.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "voila.server_extension": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /etc/jupyter/nbconfig/notebook.d/voila.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "voila/extension": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /hatch_build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from urllib.request import urlopen 4 | 5 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface 6 | 7 | JUPYTERLAB_APPUTILS_VERSION = "3.2.8" 8 | JUPYTERLAB_THEME_LIGHT_VERSION = "3.2.8" 9 | 10 | CSS_FILES = [ 11 | ( 12 | f"https://unpkg.com/@jupyterlab/apputils@{JUPYTERLAB_APPUTILS_VERSION}/style/materialcolors.css", 13 | "materialcolors.css", 14 | ), 15 | ( 16 | f"https://unpkg.com/@jupyterlab/theme-light-extension@{JUPYTERLAB_THEME_LIGHT_VERSION}/style/variables.css", 17 | "labvariables.css", 18 | ), 19 | ] 20 | 21 | 22 | class CustomBuildHook(BuildHookInterface): 23 | def initialize(self, version, build_data): 24 | for url, filename in CSS_FILES: 25 | directory = os.path.join( 26 | "share", "jupyter", "voila", "templates", "base", "static" 27 | ) 28 | dest = os.path.join(directory, filename) 29 | if not os.path.exists(directory): 30 | os.makedirs(directory) 31 | if not os.path.exists(".git") and os.path.exists(dest): 32 | # not running from git, nothing to do 33 | return 34 | print("Downloading CSS: %s" % url) 35 | try: 36 | css = urlopen(url).read() 37 | except Exception as e: 38 | msg = f"Failed to download css from {url}: {e}" 39 | print(msg, file=sys.stderr) 40 | 41 | if os.path.exists(dest): 42 | print("Already have CSS: %s, moving on." % dest) 43 | else: 44 | raise OSError("Need CSS to proceed.") 45 | return 46 | 47 | with open(dest, "wb") as f: 48 | f.write(css) 49 | print("Downloaded Notebook CSS to %s" % dest) 50 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "voila", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package voila" 5 | } 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "version": "independent" 4 | } 5 | -------------------------------------------------------------------------------- /notebooks/basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "source": [ 6 | "# So easy, *voilà*!\n", 7 | "\n", 8 | "In this example notebook, we demonstrate how Voilà can render Jupyter notebooks with interactions requiring a roundtrip to the kernel." 9 | ], 10 | "metadata": {} 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "source": [ 15 | "## Jupyter Widgets" 16 | ], 17 | "metadata": {} 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "source": [ 23 | "import ipywidgets as widgets\n", 24 | "\n", 25 | "slider = widgets.FloatSlider(description='$x$')\n", 26 | "text = widgets.FloatText(disabled=True, description='$x^2$')\n", 27 | "\n", 28 | "def compute(*ignore):\n", 29 | " text.value = str(slider.value ** 2)\n", 30 | "\n", 31 | "slider.observe(compute, 'value')\n", 32 | "\n", 33 | "slider.value = 4\n", 34 | "\n", 35 | "widgets.VBox([slider, text])" 36 | ], 37 | "outputs": [], 38 | "metadata": {} 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "source": [ 43 | "## Basic outputs of code cells" 44 | ], 45 | "metadata": {} 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "source": [ 51 | "import pandas as pd\n", 52 | "\n", 53 | "iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')\n", 54 | "iris" 55 | ], 56 | "outputs": [], 57 | "metadata": {} 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "source": [], 63 | "outputs": [], 64 | "metadata": {} 65 | } 66 | ], 67 | "metadata": { 68 | "kernelspec": { 69 | "display_name": "Python 3", 70 | "language": "python", 71 | "name": "python3" 72 | }, 73 | "language_info": { 74 | "codemirror_mode": { 75 | "name": "ipython", 76 | "version": 3 77 | }, 78 | "file_extension": ".py", 79 | "mimetype": "text/x-python", 80 | "name": "python", 81 | "nbconvert_exporter": "python", 82 | "pygments_lexer": "ipython3", 83 | "version": "3.8.5" 84 | } 85 | }, 86 | "nbformat": 4, 87 | "nbformat_minor": 4 88 | } 89 | -------------------------------------------------------------------------------- /notebooks/bqplot.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# So easy, *voilà*!\n", 8 | "\n", 9 | "In this example notebook, we demonstrate how Voilà can render custom Jupyter widgets such as [bqplot](https://github.com/bloomberg/bqplot). " 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import warnings\n", 19 | "warnings.filterwarnings('ignore')" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import numpy as np\n", 29 | "from bqplot import pyplot as plt\n", 30 | "\n", 31 | "plt.figure(1, title='Line Chart')\n", 32 | "np.random.seed(0)\n", 33 | "n = 200\n", 34 | "x = np.linspace(0.0, 10.0, n)\n", 35 | "y = np.cumsum(np.random.randn(n))\n", 36 | "plt.plot(x, y)\n", 37 | "plt.show()" 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "kernelspec": { 43 | "display_name": "Python 3", 44 | "language": "python", 45 | "name": "python3" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.7.3" 58 | } 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 2 62 | } 63 | -------------------------------------------------------------------------------- /notebooks/interactive.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# So easy, *voilà*!\n", 8 | "\n", 9 | "In this example notebook, we demonstrate how Voilà can render notebooks making use of ipywidget's `@interact`." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from ipywidgets import HBox, VBox, IntSlider, interactive_output\n", 19 | "from IPython.display import display\n", 20 | "\n", 21 | "a = IntSlider()\n", 22 | "b = IntSlider()\n", 23 | "\n", 24 | "def f(a, b):\n", 25 | " print(\"{} * {} = {}\".format(a, b, a * b))\n", 26 | "\n", 27 | "out = interactive_output(f, { \"a\": a, \"b\": b })\n", 28 | "\n", 29 | "display(HBox([VBox([a, b]), out]))" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [] 38 | } 39 | ], 40 | "metadata": { 41 | "kernelspec": { 42 | "display_name": "Python 3", 43 | "language": "python", 44 | "name": "python3" 45 | }, 46 | "language_info": { 47 | "codemirror_mode": { 48 | "name": "ipython", 49 | "version": 3 50 | }, 51 | "file_extension": ".py", 52 | "mimetype": "text/x-python", 53 | "name": "python", 54 | "nbconvert_exporter": "python", 55 | "pygments_lexer": "ipython3", 56 | "version": "3.7.3" 57 | } 58 | }, 59 | "nbformat": 4, 60 | "nbformat_minor": 2 61 | } 62 | -------------------------------------------------------------------------------- /notebooks/ipyvolume.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# So easy, *voilà*!\n", 8 | "\n", 9 | "In this example notebook, we demonstrate how Voilà can render custom Jupyter widgets such as [ipyvolume](https://github.com/maartenbreddels/ipyvolume). " 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import ipyvolume as ipv\n", 19 | "ipv.examples.example_ylm();" 20 | ] 21 | } 22 | ], 23 | "metadata": { 24 | "kernelspec": { 25 | "display_name": "Python 3", 26 | "language": "python", 27 | "name": "python3" 28 | }, 29 | "language_info": { 30 | "codemirror_mode": { 31 | "name": "ipython", 32 | "version": 3 33 | }, 34 | "file_extension": ".py", 35 | "mimetype": "text/x-python", 36 | "name": "python", 37 | "nbconvert_exporter": "python", 38 | "pygments_lexer": "ipython3", 39 | "version": "3.7.3" 40 | } 41 | }, 42 | "nbformat": 4, 43 | "nbformat_minor": 2 44 | } 45 | -------------------------------------------------------------------------------- /notebooks/jupyter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "VoilaExecutePreprocessor": { 3 | "cell_error_instruction" : "" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /notebooks/multiple_widgets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ebaa184b-765f-4261-9c5c-285bbde8fad5", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import ipywidgets as widgets\n", 11 | "def generate(n):\n", 12 | " widget = []\n", 13 | " for i in range(n):\n", 14 | " inner = []\n", 15 | " for j in range(n):\n", 16 | " inner.append(widgets.Button(description=f'{i*n+j+1}'))\n", 17 | " widget.append(widgets.HBox(inner))\n", 18 | "\n", 19 | " return widgets.VBox(widget)\n", 20 | "generate(20)" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "8b59589a-7c71-4b1a-896a-b1139cbcf70f", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [] 30 | } 31 | ], 32 | "metadata": { 33 | "kernelspec": { 34 | "display_name": "Python 3 (ipykernel)", 35 | "language": "python", 36 | "name": "python3" 37 | }, 38 | "language_info": { 39 | "codemirror_mode": { 40 | "name": "ipython", 41 | "version": 3 42 | }, 43 | "file_extension": ".py", 44 | "mimetype": "text/x-python", 45 | "name": "python", 46 | "nbconvert_exporter": "python", 47 | "pygments_lexer": "ipython3", 48 | "version": "3.9.6" 49 | } 50 | }, 51 | "nbformat": 4, 52 | "nbformat_minor": 5 53 | } 54 | -------------------------------------------------------------------------------- /notebooks/query-strings.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "ExecuteTime": { 8 | "end_time": "2020-08-18T12:06:29.243603Z", 9 | "start_time": "2020-08-18T12:06:29.240780Z" 10 | } 11 | }, 12 | "outputs": [], 13 | "source": [ 14 | "import os\n", 15 | "from urllib.parse import parse_qs\n", 16 | "import IPython.display" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": { 23 | "ExecuteTime": { 24 | "end_time": "2020-08-18T12:06:29.250356Z", 25 | "start_time": "2020-08-18T12:06:29.246161Z" 26 | } 27 | }, 28 | "outputs": [], 29 | "source": [ 30 | "query_string = os.environ.get('QUERY_STRING', '')\n", 31 | "parameters = parse_qs(query_string)\n", 32 | "print(\"query string parameters:\", parameters)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "ExecuteTime": { 40 | "end_time": "2020-08-18T12:06:29.258506Z", 41 | "start_time": "2020-08-18T12:06:29.253815Z" 42 | } 43 | }, 44 | "outputs": [], 45 | "source": [ 46 | "# parameters is a dict of lists\n", 47 | "username = parameters.get('username', ['Kim'])[0]\n", 48 | "print(f'Hi {username}')" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": { 55 | "ExecuteTime": { 56 | "end_time": "2020-08-18T12:06:29.275717Z", 57 | "start_time": "2020-08-18T12:06:29.261315Z" 58 | } 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "server = os.environ.get('SERVER_NAME', 'localhost') \n", 63 | "url = \"http://\" + server\n", 64 | "\n", 65 | "port = os.environ.get('SERVER_PORT', '')\n", 66 | "if port:\n", 67 | " url += \":\" + port\n", 68 | "\n", 69 | "path = os.environ.get('SCRIPT_NAME', '')\n", 70 | "url += path\n", 71 | "\n", 72 | " \n", 73 | "IPython.display.HTML(data=f\"\"\"\n", 74 | "Link to myself as user Riley\n", 75 | "\"\"\")" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [] 84 | } 85 | ], 86 | "metadata": { 87 | "kernelspec": { 88 | "display_name": "Python 3", 89 | "language": "python", 90 | "name": "python3" 91 | }, 92 | "language_info": { 93 | "codemirror_mode": { 94 | "name": "ipython", 95 | "version": 3 96 | }, 97 | "file_extension": ".py", 98 | "mimetype": "text/x-python", 99 | "name": "python", 100 | "nbconvert_exporter": "python", 101 | "pygments_lexer": "ipython3", 102 | "version": "3.7.3" 103 | } 104 | }, 105 | "nbformat": 4, 106 | "nbformat_minor": 4 107 | } 108 | -------------------------------------------------------------------------------- /notebooks/reveal.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "slideshow": { 8 | "slide_type": "slide" 9 | } 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "print('hi')" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import ipywidgets as widgets\n", 23 | "slider = widgets.FloatSlider(description='x')\n", 24 | "text = widgets.FloatText(disabled=True, description='$x^2$')\n", 25 | "text.disabled\n", 26 | "def compute(*ignore):\n", 27 | " text.value = str(slider.value**2)\n", 28 | "slider.observe(compute, 'value')\n", 29 | "slider.value = 14\n", 30 | "widgets.VBox([slider, text])" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "metadata": { 37 | "slideshow": { 38 | "slide_type": "slide" 39 | } 40 | }, 41 | "outputs": [], 42 | "source": [ 43 | "print('voila')" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": { 50 | "slideshow": { 51 | "slide_type": "subslide" 52 | } 53 | }, 54 | "outputs": [], 55 | "source": [ 56 | "1+2" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [] 65 | } 66 | ], 67 | "metadata": { 68 | "celltoolbar": "Slideshow", 69 | "kernelspec": { 70 | "display_name": "Python 3", 71 | "language": "python", 72 | "name": "python3" 73 | }, 74 | "language_info": { 75 | "codemirror_mode": { 76 | "name": "ipython", 77 | "version": 3 78 | }, 79 | "file_extension": ".py", 80 | "mimetype": "text/x-python", 81 | "name": "python", 82 | "nbconvert_exporter": "python", 83 | "pygments_lexer": "ipython3", 84 | "version": "3.6.4" 85 | }, 86 | "voila": { 87 | "template": "reveal" 88 | } 89 | }, 90 | "nbformat": 4, 91 | "nbformat_minor": 2 92 | } 93 | -------------------------------------------------------------------------------- /notebooks/yaml.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "d956aba2-61bc-43b8-810c-82aa31b2af44", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from IPython.display import Markdown\n", 11 | "import json\n", 12 | "import yaml\n", 13 | "\n", 14 | "STR_JSON=\"\"\"{\"hey\": {\n", 15 | " \"1\": \"hi\",\n", 16 | " \"2\": \"yo\",\n", 17 | " \"a\": {\n", 18 | " \"b\": [\n", 19 | " {\"value\": \"3\"},\n", 20 | " {\"value\": \"5\"},\n", 21 | " {\"value\": \"5\"}\n", 22 | " ]\n", 23 | " }\n", 24 | "}}\"\"\"\n", 25 | "\n", 26 | "parsed = json.loads(STR_JSON) \n", 27 | "s = yaml.dump(parsed, indent=2)\n", 28 | "display(Markdown(\"\\n```yaml\\n\" + s + \"\\n```\")) " 29 | ] 30 | } 31 | ], 32 | "metadata": { 33 | "kernelspec": { 34 | "display_name": "Python 3 (ipykernel)", 35 | "language": "python", 36 | "name": "python3" 37 | }, 38 | "language_info": { 39 | "codemirror_mode": { 40 | "name": "ipython", 41 | "version": 3 42 | }, 43 | "file_extension": ".py", 44 | "mimetype": "text/x-python", 45 | "name": "python", 46 | "nbconvert_exporter": "python", 47 | "pygments_lexer": "ipython3", 48 | "version": "3.11.4" 49 | } 50 | }, 51 | "nbformat": 4, 52 | "nbformat_minor": 5 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@voila-dashboards/voila-root", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://github.com/voila-dashboards/voila", 6 | "bugs": { 7 | "url": "https://github.com/voila-dashboards/voila/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/voila-dashboards/voila" 12 | }, 13 | "license": "BSD-3-Clause", 14 | "author": "Voilà contributors", 15 | "workspaces": { 16 | "packages": [ 17 | "packages/*" 18 | ] 19 | }, 20 | "scripts": { 21 | "build": "lerna run build", 22 | "build:prod": "lerna run build:prod", 23 | "build:test": "lerna run build:test", 24 | "clean": "lerna run clean", 25 | "eslint": "eslint . --ext .ts,.tsx --fix", 26 | "eslint:check": "eslint . --ext .ts,.tsx", 27 | "prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 28 | "prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 29 | "test": "lerna run test" 30 | }, 31 | "devDependencies": { 32 | "@typescript-eslint/eslint-plugin": "^5.56.0", 33 | "@typescript-eslint/parser": "^5.56.0", 34 | "eslint": "^8.36.0", 35 | "eslint-config-prettier": "^8.8.0", 36 | "eslint-plugin-jest": "^27.2.1", 37 | "eslint-plugin-prettier": "^4.2.1", 38 | "eslint-plugin-react": "^7.32.2", 39 | "jest": "^26.4.2", 40 | "jest-junit": "^11.1.0", 41 | "jest-raw-loader": "^1.0.1", 42 | "jest-summary-reporter": "^0.0.2", 43 | "lerna": "^7.0.0", 44 | "npm-run-all": "^4.1.5", 45 | "prettier": "^2.8.6", 46 | "rimraf": "^3.0.2", 47 | "shell-quote": "^1.7.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/README.md: -------------------------------------------------------------------------------- 1 | # @voila-dashboards/jupyterlab-preview 2 | 3 | A JupyterLab preview extension for Voilà. 4 | 5 | ## Prerequisites 6 | 7 | - JupyterLab 1.0+ 8 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@voila-dashboards/jupyterlab-preview", 3 | "version": "2.3.8", 4 | "description": "A JupyterLab preview extension for Voilà", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/voila-dashboards/voila", 11 | "bugs": { 12 | "url": "https://github.com/voila-dashboards/voila/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Voilà contributors", 16 | "files": [ 17 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 18 | "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 19 | "schema/*.json" 20 | ], 21 | "main": "lib/index.js", 22 | "types": "lib/index.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/voila-dashboards/voila.git" 26 | }, 27 | "style": "style/index.css", 28 | "styleModule": "style/index.js", 29 | "sideEffects": [ 30 | "style/*.css", 31 | "style/index.js" 32 | ], 33 | "scripts": { 34 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 35 | "build:labextension": "jupyter labextension build .", 36 | "build:labextension:dev": "jupyter labextension build --development True .", 37 | "build:lib": "tsc", 38 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 39 | "build:test": "tsc --build tsconfig.test.json", 40 | "clean": "jlpm run clean:lib && jlpm run clean:labextension", 41 | "clean:labextension": "rimraf ../../voila/labextensions/jupyterlab-preview", 42 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 43 | "test": "jest", 44 | "watch": "run-p watch:src watch:labextension", 45 | "watch:labextension": "jupyter labextension watch .", 46 | "watch:src": "tsc -w" 47 | }, 48 | "dependencies": { 49 | "@jupyterlab/application": "^4.0.0", 50 | "@jupyterlab/apputils": "^4.0.0", 51 | "@jupyterlab/coreutils": "^6.0.0", 52 | "@jupyterlab/docregistry": "^4.0.0", 53 | "@jupyterlab/fileeditor": "^4.0.0", 54 | "@jupyterlab/mainmenu": "^4.0.0", 55 | "@jupyterlab/notebook": "^4.0.0", 56 | "@jupyterlab/settingregistry": "^4.0.0", 57 | "@jupyterlab/ui-components": "^4.0.0", 58 | "@lumino/coreutils": "^2.0.0", 59 | "@lumino/signaling": "^2.0.0", 60 | "react": "^18.2.0", 61 | "react-dom": "^18.2.0" 62 | }, 63 | "devDependencies": { 64 | "@babel/core": "^7.10.2", 65 | "@babel/preset-env": "^7.10.2", 66 | "@jupyterlab/builder": "^4.0.0", 67 | "@jupyterlab/testutils": "^4.0.0", 68 | "@types/react": "^18.0.0", 69 | "@types/react-dom": "^18.0.0", 70 | "npm-run-all": "^4.1.5", 71 | "rimraf": "^2.6.1", 72 | "source-map-loader": "~1.0.2", 73 | "typescript": "~5.0.2" 74 | }, 75 | "jupyterlab": { 76 | "extension": true, 77 | "schemaDir": "schema", 78 | "outputDir": "../../voila/labextensions/jupyterlab-preview", 79 | "discovery": { 80 | "server": { 81 | "managers": [ 82 | "conda", 83 | "pip" 84 | ], 85 | "base": { 86 | "name": "voila" 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.setting-icon-class": "jp-VoilaIcon", 3 | "jupyter.lab.setting-icon-label": "Voilà Preview", 4 | "title": "Voilà Preview", 5 | "description": "Voilà Preview Extension", 6 | "properties": { 7 | "renderOnSave": { 8 | "title": "Render Preview on Save", 9 | "description": "Render the Voilà preview automatically after saving the notebook", 10 | "default": false, 11 | "type": "boolean" 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | } 17 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/src/icons.ts: -------------------------------------------------------------------------------- 1 | import { LabIcon } from '@jupyterlab/ui-components'; 2 | 3 | import voilaSvgStr from '../style/voila.svg'; 4 | 5 | export const voilaIcon = new LabIcon({ 6 | name: '@voila-dashboards/jupyterlab-preview:voila', 7 | svgstr: voilaSvgStr 8 | }); 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/src/svg.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const value: string; 3 | export default value; 4 | } 5 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/style/base.css: -------------------------------------------------------------------------------- 1 | .jp-VoilaIcon { 2 | background-image: var(--jp-icon-voila); 3 | } 4 | 5 | .jp-VoilaPreview-renderOnSave { 6 | align-items: center; 7 | display: flex; 8 | } 9 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/style/voila.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | Voila 11 | 15 | 16 | -------------------------------------------------------------------------------- /packages/jupyterlab-preview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/voila/publicpath.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | // We dynamically set the webpack public path based on the page config 5 | // settings from the JupyterLab app. We copy some of the pageconfig parsing 6 | // logic in @jupyterlab/coreutils below, since this must run before any other 7 | // files are loaded (including @jupyterlab/coreutils). 8 | 9 | /** 10 | * Get global configuration data for the Jupyter application. 11 | * 12 | * @param name - The name of the configuration option. 13 | * 14 | * @returns The config value or an empty string if not found. 15 | * 16 | * #### Notes 17 | * All values are treated as strings. 18 | * For browser based applications, it is assumed that the page HTML 19 | * includes a script tag with the id `jupyter-config-data` containing the 20 | * configuration as valid JSON. In order to support the classic Notebook, 21 | * we fall back on checking for `body` data of the given `name`. 22 | */ 23 | function getOption(name) { 24 | let configData = Object.create(null); 25 | // Use script tag if available. 26 | if (typeof document !== 'undefined' && document) { 27 | const el = document.getElementById('jupyter-config-data'); 28 | 29 | if (el) { 30 | configData = JSON.parse(el.textContent || '{}'); 31 | } 32 | } 33 | return configData[name] || ''; 34 | } 35 | 36 | // eslint-disable-next-line no-undef 37 | __webpack_public_path__ = getOption('fullStaticUrl') + '/'; 38 | -------------------------------------------------------------------------------- /packages/voila/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import('./main.js'); 2 | -------------------------------------------------------------------------------- /packages/voila/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /*eslint no-var: 0*/ 2 | 3 | declare function __webpack_init_sharing__(arg: any); 4 | declare var _JUPYTERLAB; 5 | declare var __webpack_share_scopes__: any; 6 | declare var jupyterapp: any; 7 | declare var themeLoaded: boolean; 8 | declare var cellLoaded: boolean; 9 | declare function voila_finish(); 10 | -------------------------------------------------------------------------------- /packages/voila/src/index.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Voilà contributors * 3 | * Copyright (c) 2018, QuantStack * 4 | * * 5 | * Distributed under the terms of the BSD 3-Clause License. * 6 | * * 7 | * The full license is in the file LICENSE, distributed with this software. * 8 | ****************************************************************************/ 9 | 10 | export * from './app'; 11 | export * from './shell'; 12 | export * from './voilaplugins'; 13 | export * from './tools'; 14 | export * from './plugins/tree/browser'; 15 | export * from './plugins/tree/listing'; 16 | export * from './plugins/themes/thememanager'; 17 | export * from './plugins/outputs/renderedcells'; 18 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/outputs/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | renderOutputsPlugin, 3 | renderOutputsProgressivelyPlugin 4 | } from './plugins'; 5 | 6 | export { renderOutputsPlugin, renderOutputsProgressivelyPlugin }; 7 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/outputs/renderedcells.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@lumino/messaging'; 2 | import { Widget } from '@lumino/widgets'; 3 | 4 | /** 5 | * Wrapper widget of rendered cells, this class converts the Lumino resize 6 | * message to a window event. It helps fix the zero-heigh issue of some 7 | * widgets 8 | * 9 | */ 10 | export class RenderedCells extends Widget { 11 | processMessage(msg: Message): void { 12 | super.processMessage(msg); 13 | switch (msg.type) { 14 | case 'resize': 15 | window.dispatchEvent(new Event('resize')); 16 | break; 17 | 18 | default: 19 | break; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/path.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Voilà contributors * 3 | * Copyright (c) 2018, QuantStack * 4 | * * 5 | * Distributed under the terms of the BSD 3-Clause License. * 6 | * * 7 | * The full license is in the file LICENSE, distributed with this software. * 8 | ****************************************************************************/ 9 | import { 10 | JupyterFrontEnd, 11 | JupyterFrontEndPlugin 12 | } from '@jupyterlab/application'; 13 | 14 | import { VoilaApp } from '../app'; 15 | 16 | /** 17 | * The default paths. 18 | */ 19 | export const pathsPlugin: JupyterFrontEndPlugin = { 20 | id: '@voila-dashboards/voila:paths', 21 | activate: ( 22 | app: JupyterFrontEnd 23 | ): JupyterFrontEnd.IPaths => { 24 | return (app as VoilaApp).paths; 25 | }, 26 | autoStart: true, 27 | provides: JupyterFrontEnd.IPaths 28 | }; 29 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/translator.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Voilà contributors * 3 | * Copyright (c) 2018, QuantStack * 4 | * * 5 | * Distributed under the terms of the BSD 3-Clause License. * 6 | * * 7 | * The full license is in the file LICENSE, distributed with this software. * 8 | ****************************************************************************/ 9 | import { 10 | JupyterFrontEnd, 11 | JupyterFrontEndPlugin 12 | } from '@jupyterlab/application'; 13 | import { ITranslator, TranslationManager } from '@jupyterlab/translation'; 14 | 15 | /** 16 | * A simplified Translator 17 | */ 18 | export const translatorPlugin: JupyterFrontEndPlugin = { 19 | id: '@voila-dashboards/voila:translator', 20 | activate: (app: JupyterFrontEnd): ITranslator => { 21 | const translationManager = new TranslationManager(); 22 | return translationManager; 23 | }, 24 | autoStart: true, 25 | provides: ITranslator 26 | }; 27 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/tree/browser.ts: -------------------------------------------------------------------------------- 1 | import { FileBrowser, DirListing } from '@jupyterlab/filebrowser'; 2 | import { VoilaDirListing } from './listing'; 3 | import { Widget } from '@lumino/widgets'; 4 | 5 | export class VoilaFileBrowser extends FileBrowser { 6 | constructor(options: VoilaFileBrowser.IOptions) { 7 | const { urlFactory, title, ...rest } = options; 8 | super(rest); 9 | (this.listing as VoilaDirListing).urlFactory = urlFactory; 10 | this.addClass('voila-FileBrowser'); 11 | 12 | const titleWidget = new Widget(); 13 | titleWidget.node.innerText = title; 14 | this.toolbar.addItem('title', titleWidget); 15 | } 16 | /** 17 | * Create the underlying DirListing instance. 18 | * 19 | * @param options - The DirListing constructor options. 20 | * 21 | * @returns The created DirListing instance. 22 | */ 23 | protected createDirListing(options: DirListing.IOptions): DirListing { 24 | return new VoilaDirListing(options); 25 | } 26 | } 27 | 28 | export namespace VoilaFileBrowser { 29 | export interface IOptions extends FileBrowser.IOptions { 30 | urlFactory: (path: string) => string; 31 | title: string; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/tree/index.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2023, Voilà contributors * 3 | * Copyright (c) 2023, QuantStack * 4 | * * 5 | * Distributed under the terms of the BSD 3-Clause License. * 6 | * * 7 | * The full license is in the file LICENSE, distributed with this software. * 8 | ****************************************************************************/ 9 | import { 10 | JupyterFrontEnd, 11 | JupyterFrontEndPlugin 12 | } from '@jupyterlab/application'; 13 | import { PageConfig, URLExt } from '@jupyterlab/coreutils'; 14 | import { DocumentManager } from '@jupyterlab/docmanager'; 15 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 16 | import { FilterFileBrowserModel } from '@jupyterlab/filebrowser'; 17 | import { Widget } from '@lumino/widgets'; 18 | 19 | import { VoilaFileBrowser } from './browser'; 20 | 21 | /** 22 | * The voila file browser provider. 23 | */ 24 | export const treeWidgetPlugin: JupyterFrontEndPlugin = { 25 | id: '@voila-dashboards/voila:tree-widget', 26 | description: 'Provides the file browser.', 27 | activate: (app: JupyterFrontEnd): void => { 28 | const docRegistry = new DocumentRegistry(); 29 | const docManager = new DocumentManager({ 30 | registry: docRegistry, 31 | manager: app.serviceManager, 32 | opener 33 | }); 34 | const fbModel = new FilterFileBrowserModel({ 35 | manager: docManager, 36 | refreshInterval: 2147483646 37 | }); 38 | const urlFactory = (path: string) => { 39 | const baseUrl = PageConfig.getBaseUrl(); 40 | const frontend = PageConfig.getOption('frontend'); 41 | const query = PageConfig.getOption('query'); 42 | return URLExt.join(baseUrl, frontend, 'render', path) + `?${query}`; 43 | }; 44 | const fb = new VoilaFileBrowser({ 45 | id: 'filebrowser', 46 | model: fbModel, 47 | urlFactory, 48 | title: 'Select items to open with Voilà.' 49 | }); 50 | 51 | fb.showFileCheckboxes = false; 52 | fb.showLastModifiedColumn = false; 53 | 54 | const spacerTop = new Widget(); 55 | spacerTop.addClass('spacer-top-widget'); 56 | app.shell.add(spacerTop, 'main'); 57 | 58 | app.shell.add(fb, 'main'); 59 | 60 | const spacerBottom = new Widget(); 61 | spacerBottom.addClass('spacer-bottom-widget'); 62 | app.shell.add(spacerBottom, 'main'); 63 | }, 64 | 65 | autoStart: true 66 | }; 67 | -------------------------------------------------------------------------------- /packages/voila/src/plugins/tree/listing.ts: -------------------------------------------------------------------------------- 1 | import { DirListing } from '@jupyterlab/filebrowser'; 2 | import { Contents } from '@jupyterlab/services'; 3 | import { showErrorMessage } from '@jupyterlab/apputils'; 4 | 5 | export class VoilaDirListing extends DirListing { 6 | get urlFactory(): VoilaDirListing.IUrlFactory | undefined { 7 | return this._urlFactory; 8 | } 9 | set urlFactory(f: VoilaDirListing.IUrlFactory | undefined) { 10 | this._urlFactory = f; 11 | } 12 | 13 | /** 14 | * Handle the opening of an item. 15 | */ 16 | protected handleOpen(item: Contents.IModel): void { 17 | if (item.type === 'directory') { 18 | const localPath = this.model.manager.services.contents.localPath( 19 | item.path 20 | ); 21 | this.model 22 | .cd(`/${localPath}`) 23 | .catch((error) => showErrorMessage('Open directory', error)); 24 | } else { 25 | const path = item.path; 26 | if (this.urlFactory) { 27 | window.open(this.urlFactory(path), '_blank'); 28 | } else { 29 | showErrorMessage('Open file', 'URL Factory is not defined'); 30 | } 31 | } 32 | } 33 | 34 | handleEvent(event: Event): void { 35 | if (event.type === 'click') { 36 | this.evtDblClick(event as MouseEvent); 37 | } 38 | } 39 | private _urlFactory: VoilaDirListing.IUrlFactory | undefined; 40 | } 41 | 42 | export namespace VoilaDirListing { 43 | export type IUrlFactory = (path: string) => string; 44 | } 45 | -------------------------------------------------------------------------------- /packages/voila/src/services/event.ts: -------------------------------------------------------------------------------- 1 | import { EventManager } from '@jupyterlab/services'; 2 | 3 | /** 4 | * Need https://github.com/jupyterlab/jupyterlab/pull/14770 for a better mock 5 | * 6 | * @export 7 | * @class VoilaEventManager 8 | * @extends {EventManager} 9 | */ 10 | export class VoilaEventManager extends EventManager { 11 | constructor(options: EventManager.IOptions = {}) { 12 | super(options); 13 | this.dispose(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/voila/src/services/kernelspec.ts: -------------------------------------------------------------------------------- 1 | import { BaseManager, KernelSpec } from '@jupyterlab/services'; 2 | import { ISpecModels } from '@jupyterlab/services/lib/kernelspec/restapi'; 3 | import { ISignal, Signal } from '@lumino/signaling'; 4 | 5 | export class VoilaKernelSpecManager 6 | extends BaseManager 7 | implements KernelSpec.IManager 8 | { 9 | specsChanged: ISignal = new Signal(this); 10 | connectionFailure: ISignal = new Signal(this); 11 | readonly specs: ISpecModels | null = null; 12 | 13 | refreshSpecs(): Promise { 14 | return Promise.resolve(); 15 | } 16 | 17 | get isReady(): boolean { 18 | return true; 19 | } 20 | 21 | get ready(): Promise { 22 | return Promise.resolve(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/voila/src/services/servicemanager.ts: -------------------------------------------------------------------------------- 1 | import { ServiceManager } from '@jupyterlab/services'; 2 | import { VoilaEventManager } from './event'; 3 | import { VoilaUserManager } from './user'; 4 | import { VoilaKernelSpecManager } from './kernelspec'; 5 | import { ContentsManager } from '@jupyterlab/services'; 6 | 7 | const alwaysTrue = () => true; 8 | 9 | /** 10 | * A custom service manager to disable non used services. 11 | * 12 | * @export 13 | * @class VoilaServiceManager 14 | * @extends {ServiceManager} 15 | */ 16 | export class VoilaServiceManager extends ServiceManager { 17 | constructor(options?: Partial) { 18 | super({ 19 | standby: options?.standby ?? alwaysTrue, 20 | kernelspecs: options?.kernelspecs ?? new VoilaKernelSpecManager({}), 21 | events: options?.events ?? new VoilaEventManager(), 22 | user: options?.user ?? new VoilaUserManager({}), 23 | contents: options?.contents ?? new ContentsManager() 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/voila/src/services/user.ts: -------------------------------------------------------------------------------- 1 | import { BaseManager, User } from '@jupyterlab/services'; 2 | import { ReadonlyJSONObject } from '@lumino/coreutils'; 3 | import { ISignal, Signal } from '@lumino/signaling'; 4 | 5 | export class VoilaUserManager extends BaseManager implements User.IManager { 6 | userChanged: ISignal = new Signal(this); 7 | connectionFailure: ISignal = new Signal(this); 8 | readonly identity: User.IIdentity | null = null; 9 | readonly permissions: ReadonlyJSONObject | null = null; 10 | 11 | refreshUser(): Promise { 12 | return Promise.resolve(); 13 | } 14 | 15 | get isReady(): boolean { 16 | return true; 17 | } 18 | 19 | get ready(): Promise { 20 | return Promise.resolve(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/voila/src/sharedscope.ts: -------------------------------------------------------------------------------- 1 | import '@jupyterlab/application'; 2 | import '@jupyterlab/coreutils'; 3 | import '@jupyterlab/console'; 4 | import '@jupyterlab/rendermime'; 5 | import '@jupyterlab/services'; 6 | import '@jupyterlab/statedb'; 7 | import '@jupyterlab/notebook'; 8 | import '@jupyterlab/mainmenu'; 9 | import '@jupyterlab/logconsole'; 10 | import '@lumino/algorithm'; 11 | import '@lumino/application'; 12 | import '@lumino/coreutils'; 13 | import '@lumino/datagrid'; 14 | import '@lumino/disposable'; 15 | import '@lumino/domutils'; 16 | import '@lumino/dragdrop'; 17 | import '@lumino/keyboard'; 18 | import '@lumino/messaging'; 19 | import '@lumino/polling'; 20 | import '@lumino/properties'; 21 | import '@lumino/signaling'; 22 | import '@lumino/virtualdom'; 23 | import '@lumino/widgets'; 24 | import 'react-dom'; 25 | -------------------------------------------------------------------------------- /packages/voila/src/treebootstrap.ts: -------------------------------------------------------------------------------- 1 | import('./tree.js'); 2 | -------------------------------------------------------------------------------- /packages/voila/src/voilaplugins.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Voilà contributors * 3 | * Copyright (c) 2018, QuantStack * 4 | * * 5 | * Distributed under the terms of the BSD 3-Clause License. * 6 | * * 7 | * The full license is in the file LICENSE, distributed with this software. * 8 | ****************************************************************************/ 9 | 10 | import { JupyterFrontEndPlugin } from '@jupyterlab/application'; 11 | import { pathsPlugin } from './plugins/path'; 12 | import { translatorPlugin } from './plugins/translator'; 13 | import { renderOutputsPlugin } from './plugins/outputs'; 14 | import { themePlugin, themesManagerPlugin } from './plugins/themes'; 15 | import { renderOutputsProgressivelyPlugin } from './plugins/outputs/index'; 16 | 17 | /** 18 | * Export the plugins as default. 19 | */ 20 | const plugins: JupyterFrontEndPlugin[] = [ 21 | pathsPlugin, 22 | translatorPlugin, 23 | renderOutputsPlugin, 24 | renderOutputsProgressivelyPlugin, 25 | themesManagerPlugin, 26 | themePlugin 27 | ]; 28 | 29 | export default plugins; 30 | 31 | export { 32 | pathsPlugin, 33 | translatorPlugin, 34 | renderOutputsPlugin, 35 | renderOutputsProgressivelyPlugin, 36 | themesManagerPlugin, 37 | themePlugin 38 | }; 39 | -------------------------------------------------------------------------------- /packages/voila/style/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0 !important; 3 | } 4 | div#main { 5 | height: 100vh; 6 | background-color: var(--jp-layout-color2); 7 | } 8 | div#rendered_cells { 9 | background-color: var(--jp-layout-color1); 10 | } 11 | div#voila-top-panel { 12 | min-height: var(--jp-private-menubar-height); 13 | display: flex; 14 | } 15 | div#voila-bottom-panel { 16 | min-height: var(--jp-private-menubar-height); 17 | display: flex; 18 | } 19 | div#rendered_cells { 20 | padding: var(--jp-notebook-padding); 21 | overflow: auto; 22 | } 23 | 24 | .voila-FileBrowser { 25 | max-width: 1000px; 26 | box-shadow: var(--jp-elevation-z4); 27 | } 28 | 29 | .voila-FileBrowser .jp-DirListing-item { 30 | border-bottom-style: solid; 31 | border-bottom-width: var(--jp-border-width); 32 | border-bottom-color: var(--jp-border-color0); 33 | padding: 10px 12px; 34 | } 35 | 36 | .voila-FileBrowser .jp-DirListing-itemText:focus { 37 | outline-style: none; 38 | } 39 | 40 | .spacer-top-widget { 41 | max-height: 50px; 42 | } 43 | 44 | .spacer-bottom-widget { 45 | max-height: 50px; 46 | } 47 | -------------------------------------------------------------------------------- /packages/voila/style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /packages/voila/style/index.js: -------------------------------------------------------------------------------- 1 | import '@lumino/widgets/style/index.js'; 2 | import '@jupyterlab/ui-components/style/index.js'; 3 | import '@jupyterlab/apputils/style/index.js'; 4 | import '@jupyterlab/rendermime/style/index.js'; 5 | import '@jupyterlab/docregistry/style/index.js'; 6 | import '@jupyterlab/markedparser-extension/style/index.js'; 7 | import '@jupyterlab/filebrowser/style/index.js'; 8 | import '@jupyterlab/mathjax-extension/style/index.js'; 9 | import './base.css'; 10 | -------------------------------------------------------------------------------- /packages/voila/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/widgets_manager7/README.md: -------------------------------------------------------------------------------- 1 | # @voila-dashboards/widgets-manager7 2 | 3 | The Jupyter-widgets manager for Voilà and ipywidgets 7. 4 | -------------------------------------------------------------------------------- /packages/widgets_manager7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@voila-dashboards/widgets-manager7", 3 | "version": "0.5.8", 4 | "description": "The Voilà jupyter-widgets manager for ipywidgets 7 support", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/voila-dashboards/voila", 11 | "bugs": { 12 | "url": "https://github.com/voila-dashboards/voila/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Voilà contributors", 16 | "files": [ 17 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/voila-dashboards/voila.git" 24 | }, 25 | "scripts": { 26 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 27 | "build:labextension": "jupyter labextension build .", 28 | "build:labextension:dev": "jupyter labextension build --development True .", 29 | "build:lib": "tsc", 30 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 31 | "build:test": "tsc --build tsconfig.test.json", 32 | "clean": "jlpm run clean:lib && jlpm run clean:labextension", 33 | "clean:labextension": "rimraf ../../voila/labextensions/widgets-manager7", 34 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 35 | "test": "jest", 36 | "watch": "run-p watch:src watch:labextension", 37 | "watch:labextension": "jupyter labextension watch .", 38 | "watch:src": "tsc -w" 39 | }, 40 | "dependencies": { 41 | "@jupyter-widgets/base": "^4.1.7", 42 | "@jupyter-widgets/controls": "^3.1.8", 43 | "@jupyter-widgets/jupyterlab-manager": "^3.1.11", 44 | "@jupyterlab/application": "^4.0.0", 45 | "@jupyterlab/coreutils": "^6.0.5", 46 | "@jupyterlab/rendermime": "^4.0.0", 47 | "@jupyterlab/services": "^7.0.0" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.10.2", 51 | "@babel/preset-env": "^7.10.2", 52 | "@jupyterlab/builder": "^4.0.0", 53 | "@jupyterlab/testutils": "^4.0.0", 54 | "@types/node": "^22.7.5", 55 | "npm-run-all": "^4.1.5", 56 | "rimraf": "^2.6.1", 57 | "source-map-loader": "~1.0.2", 58 | "typescript": "~5.0.2" 59 | }, 60 | "jupyterlab": { 61 | "extension": true, 62 | "outputDir": "../../voila/labextensions/widgets-manager7", 63 | "sharedPackages": { 64 | "@jupyter-widgets/base": { 65 | "bundled": true, 66 | "singleton": true 67 | }, 68 | "@jupyter-widgets/jupyterlab-manager": { 69 | "bundled": true, 70 | "singleton": true 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/widgets_manager7/src/manager.ts: -------------------------------------------------------------------------------- 1 | import { PromiseDelegate } from '@lumino/coreutils'; 2 | import { WidgetModel } from '@jupyter-widgets/base'; 3 | import { WidgetManager } from '@jupyter-widgets/jupyterlab-manager'; 4 | import { ISignal, Signal } from '@lumino/signaling'; 5 | import { INotebookModel } from '@jupyterlab/notebook'; 6 | import { Widget } from '@lumino/widgets'; 7 | import { MessageLoop } from '@lumino/messaging'; 8 | 9 | export class VoilaWidgetManager extends WidgetManager { 10 | register_model(model_id: string, modelPromise: Promise): void { 11 | super.register_model(model_id, modelPromise); 12 | this._registeredModels.add(model_id); 13 | this._modelRegistered.emit(model_id); 14 | } 15 | 16 | get kernel() { 17 | return this.context.sessionContext.session?.kernel; 18 | } 19 | 20 | get registeredModels(): ReadonlySet { 21 | return this._registeredModels; 22 | } 23 | 24 | get modelRegistered(): ISignal { 25 | return this._modelRegistered; 26 | } 27 | 28 | removeRegisteredModel(modelId: string) { 29 | this._registeredModels.delete(modelId); 30 | } 31 | 32 | async _loadFromKernel(): Promise { 33 | await super._loadFromKernel(); 34 | 35 | this._loadedFromKernel.resolve(); 36 | } 37 | 38 | get loadedModelsFromKernel(): Promise { 39 | return this._loadedFromKernel.promise; 40 | } 41 | 42 | async display_view(msg: any, view: any, options: any): Promise { 43 | if (options.el) { 44 | Widget.attach(view.pWidget, options.el); 45 | } 46 | if (view.el) { 47 | view.el.setAttribute('data-voila-jupyter-widget', ''); 48 | view.el.addEventListener('jupyterWidgetResize', (e: Event) => { 49 | MessageLoop.postMessage(view.pWidget, Widget.ResizeMessage.UnknownSize); 50 | }); 51 | } 52 | return view.pWidget; 53 | } 54 | 55 | restoreWidgets(notebook: INotebookModel): Promise { 56 | return Promise.resolve(); 57 | } 58 | 59 | private _loadedFromKernel: PromiseDelegate = new PromiseDelegate(); 60 | private _modelRegistered = new Signal(this); 61 | private _registeredModels = new Set(); 62 | } 63 | -------------------------------------------------------------------------------- /packages/widgets_manager7/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src", 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/widgets_manager8/README.md: -------------------------------------------------------------------------------- 1 | # @voila-dashboards/widgets-manager8 2 | 3 | The Jupyter-widgets manager for Voilà and ipywidgets 8. 4 | -------------------------------------------------------------------------------- /packages/widgets_manager8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@voila-dashboards/widgets-manager8", 3 | "version": "0.5.8", 4 | "description": "The Voilà jupyter-widgets manager for ipywidgets 8 support", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/voila-dashboards/voila", 11 | "bugs": { 12 | "url": "https://github.com/voila-dashboards/voila/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": "Voilà contributors", 16 | "files": [ 17 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}" 18 | ], 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/voila-dashboards/voila.git" 24 | }, 25 | "scripts": { 26 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 27 | "build:labextension": "jupyter labextension build .", 28 | "build:labextension:dev": "jupyter labextension build --development True .", 29 | "build:lib": "tsc", 30 | "build:prod": "jlpm run build:lib && jlpm run build:labextension", 31 | "build:test": "tsc --build tsconfig.test.json", 32 | "clean": "jlpm run clean:lib && jlpm run clean:labextension", 33 | "clean:labextension": "rimraf ../../voila/labextensions/widgets-manager8", 34 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 35 | "test": "jest", 36 | "watch": "run-p watch:src watch:labextension", 37 | "watch:labextension": "jupyter labextension watch .", 38 | "watch:src": "tsc -w" 39 | }, 40 | "dependencies": { 41 | "@jupyter-widgets/base": "^6.0.10", 42 | "@jupyter-widgets/jupyterlab-manager": "^5.0.13", 43 | "@jupyterlab/application": "^4.0.0", 44 | "@jupyterlab/coreutils": "^6.0.5", 45 | "@jupyterlab/rendermime": "^4.0.0", 46 | "@jupyterlab/services": "^7.0.0" 47 | }, 48 | "devDependencies": { 49 | "@babel/core": "^7.10.2", 50 | "@babel/preset-env": "^7.10.2", 51 | "@jupyterlab/builder": "^4.0.0", 52 | "@jupyterlab/testutils": "^4.0.0", 53 | "npm-run-all": "^4.1.5", 54 | "rimraf": "^2.6.1", 55 | "source-map-loader": "~1.0.2", 56 | "typescript": "~5.0.2" 57 | }, 58 | "jupyterlab": { 59 | "extension": true, 60 | "outputDir": "../../voila/labextensions/widgets-manager8", 61 | "sharedPackages": { 62 | "@jupyter-widgets/base": { 63 | "bundled": true, 64 | "singleton": true 65 | }, 66 | "@jupyter-widgets/jupyterlab-manager": { 67 | "bundled": true, 68 | "singleton": true 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/widgets_manager8/src/manager.ts: -------------------------------------------------------------------------------- 1 | import { WidgetModel } from '@jupyter-widgets/base'; 2 | import { KernelWidgetManager } from '@jupyter-widgets/jupyterlab-manager'; 3 | import { ISignal, Signal } from '@lumino/signaling'; 4 | 5 | export class VoilaWidgetManager extends KernelWidgetManager { 6 | register_model(model_id: string, modelPromise: Promise): void { 7 | super.register_model(model_id, modelPromise); 8 | this._registeredModels.add(model_id); 9 | this._modelRegistered.emit(model_id); 10 | } 11 | 12 | get registeredModels(): ReadonlySet { 13 | return this._registeredModels; 14 | } 15 | 16 | get modelRegistered(): ISignal { 17 | return this._modelRegistered; 18 | } 19 | 20 | removeRegisteredModel(modelId: string) { 21 | this._registeredModels.delete(modelId); 22 | } 23 | 24 | private _modelRegistered = new Signal(this); 25 | private _registeredModels = new Set(); 26 | } 27 | -------------------------------------------------------------------------------- /packages/widgets_manager8/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfigbase", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: 'ubuntu-20.04' 5 | tools: 6 | python: 'mambaforge-4.10' 7 | 8 | conda: 9 | environment: docs/environment.yml 10 | 11 | sphinx: 12 | configuration: docs/conf.py 13 | -------------------------------------------------------------------------------- /requirements-visual-test.txt: -------------------------------------------------------------------------------- 1 | bokeh 2 | bokeh_sampledata 3 | bqplot 4 | ipympl==0.9.2 5 | jupyterlab~=4.0 6 | jupyterlab-fasta 7 | matplotlib 8 | scipy 9 | vega_datasets 10 | #TODO Re-enable where these extension are updated. 11 | #jupyterlab_miami_nights==0.3.2 12 | -------------------------------------------------------------------------------- /scripts/reset-stable.sh: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | git checkout stable || git checkout -b stable 4 | git reset --hard origin/main 5 | 6 | # Update the stable branch to point the latest release 7 | if [[ ${RH_DRY_RUN:=true} != 'true' ]]; then 8 | git push origin stable -f 9 | fi 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # setup.py shim for use with applications that require it. 2 | __import__("setuptools").setup() 3 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/404.html: -------------------------------------------------------------------------------- 1 | {% extends "error.html" %} 2 | 3 | {% block error_detail %} 4 |

{% trans %}You are requesting a page that does not exist!{% endtrans %}

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/browser-open.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% block title %} Opening Voilà {% endblock %} 4 | 5 | {% block meta %} 6 | 7 | {% endblock %} 8 | 9 | {% block body %} 10 |

11 | This page should redirect you to Voilà. If it doesn't, 12 | click here to go to Voilà. 13 |

14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/error.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% block stylesheets %} 4 | {{ super() }} 5 | 6 | 11 | {% endblock %} 12 | 13 | {% block body %} 14 |
15 |

{{ status_code }}: {{ status_message }}

16 | 17 | {% block error_detail %} 18 | {% endblock %} 19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/jupyter_widgets.html.j2: -------------------------------------------------------------------------------- 1 | {%- macro jupyter_widgets(widgets_cdn_url, html_manager_semver_range, widget_renderer_url='') -%} 2 | {%- endmacro %} 3 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/log.macro.html.j2: -------------------------------------------------------------------------------- 1 | {% macro js() %} 2 | 3 | 39 | {% endmacro %} 40 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %} Voilà {% endblock %} 8 | 9 | 10 | 11 | 12 | 13 | {% block stylesheets %} 14 | {% endblock %} 15 | 16 | 17 | 18 | {% block meta %} 19 | {% endblock %} 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 |
32 | {% block body %} 33 | {% endblock %} 34 |
35 | 36 | {% block scripts %} 37 | {% endblock %} 38 | 39 | 40 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/tree.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% block title %}{{ page_title }}{% endblock %} 4 | 5 | {% block stylesheets %} 6 | {{ super() }} 7 | 8 | {{ resources.include_js("static/voila-style.js") }} 9 | 10 | 52 | {% endblock %} 53 | 54 | {% block body %} 55 |
56 |
57 | Select items to open with {{ "Voilà" if frontend == "voila" else frontend.capitalize() }}. 58 |
59 |
60 | 61 |
    62 | {% if breadcrumbs|length > 1: %} 63 |
  • ..
  • 64 | {% endif %} 65 | 66 | {% for content in contents.content %} 67 | {% if content.type in ['notebook', 'file'] %} 68 |
  • {{content.name}}
  • 69 | {% endif %} 70 | {% if content.type == 'directory' %} 71 |
  • {{content.name}}
  • 72 | {% endif %} 73 | {% endfor %} 74 |
75 | {% endblock %} 76 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/base/voila_setup.macro.html.j2: -------------------------------------------------------------------------------- 1 | {%- macro voila_setup_labextensions(base_url, labextensions) -%} 2 | 3 | {%- endmacro %} 4 | 5 | {# For backward compatibility #} 6 | {%- macro voila_setup(base_url, nbextensions) -%} 7 | {{ voila_setup_labextensions(base_url, labextensions) }} 8 | {%- endmacro %} 9 | 10 | {# Helper functions for updating the loading text #} 11 | {%- macro voila_setup_helper_functions() -%} 12 | 52 | {%- endmacro %} 53 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/lab/browser-open.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% block title %} Opening Voilà {% endblock %} 4 | 5 | {% block stylesheets %} 6 | {{ super() }} 7 | 8 | 18 | {% endblock %} 19 | 20 | {% block meta %} 21 | 22 | {% endblock %} 23 | 24 | {% block body %} 25 |

26 | This page should redirect you to Voilà. If it doesn't, 27 | click here to go to Voilà. 28 |

29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/lab/error.html: -------------------------------------------------------------------------------- 1 | {% extends "voila/templates/base/error.html" %} 2 | 3 | {% block stylesheets %} 4 | {{ super() }} 5 | 6 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/lab/page.html: -------------------------------------------------------------------------------- 1 | {%- extends 'voila/templates/base/page.html' -%} 2 | 3 | {% block stylesheets %} 4 | {% if include_css %} 5 | {% if theme == 'dark' %} 6 | {{ include_css("static/index.css") }} 7 | {{ include_css("static/theme-dark.css") }} 8 | {% elif theme == 'light' %} 9 | {{ include_css("static/index.css") }} 10 | {{ include_css("static/theme-light.css") }} 11 | {% else %} 12 | {{ include_css("static/index.css") }} 13 | {{ include_lab_theme(theme) }} 14 | {% endif %} 15 | {% endif %} 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /share/jupyter/voila/templates/lab/tree-lab.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | 3 | {% block title %}{{ page_title }}{% endblock %} 4 | 5 | {% block stylesheets %} 6 | {{ super() }} 7 | 8 | 70 | {% endblock %} 71 | 72 | {% block body %} 73 | 74 | {% set openInNewTab = 'target=_blank' %} 75 | 78 | 79 | 80 | {% set mainStyle = 'style="display: None;"' %} 81 | 82 |
83 | 84 | {% endblock %} 85 | -------------------------------------------------------------------------------- /tests/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/tests/app/__init__.py -------------------------------------------------------------------------------- /tests/app/cgi-test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | NOTEBOOK_PATH = "cgi.ipynb" 4 | 5 | 6 | @pytest.fixture 7 | def notebook_cgi_path(base_url): 8 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 9 | 10 | 11 | @pytest.fixture 12 | def voila_args(notebook_directory, voila_args_extra): 13 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 14 | 15 | 16 | async def test_cgi_using_query_parameters(http_server_client, notebook_cgi_path): 17 | response = await http_server_client.fetch(notebook_cgi_path + "?username=VOILA") 18 | assert response.code == 200 19 | assert "VOILA" in response.body.decode("utf-8") 20 | -------------------------------------------------------------------------------- /tests/app/config_paths_test.py: -------------------------------------------------------------------------------- 1 | # test all objects that should be configurable 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def voila_config_file_paths_arg(): 11 | path = os.path.join(BASE_DIR, "..", "configs", "general") 12 | return "--VoilaTest.config_file_paths=[%r]" % path 13 | 14 | 15 | def test_config_app(voila_app): 16 | assert voila_app.voila_configuration.template == "test_template" 17 | 18 | 19 | def test_config_kernel_manager(voila_app): 20 | assert voila_app.kernel_manager.cull_interval == 10 21 | 22 | 23 | def test_config_contents_manager(voila_app): 24 | assert voila_app.contents_manager.use_atomic_writing is False 25 | 26 | 27 | async def test_template(http_server_client, base_url): 28 | response = await http_server_client.fetch(base_url) 29 | assert response.code == 200 30 | assert "test_template.css" in response.body.decode("utf-8") 31 | assert "Hi Voilà" in response.body.decode("utf-8") 32 | -------------------------------------------------------------------------------- /tests/app/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | import voila.app 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | 9 | 10 | class VoilaTest(voila.app.Voila): 11 | def listen(self): 12 | pass # the ioloop is taken care of by the pytest-tornado framework 13 | 14 | 15 | @pytest.fixture 16 | def voila_config(): 17 | return lambda app: None 18 | 19 | 20 | @pytest.fixture 21 | def voila_args_extra(): 22 | return ["--VoilaExecutor.timeout=240"] 23 | 24 | 25 | @pytest.fixture 26 | def voila_config_file_paths_arg(): 27 | # we don't want the tests to use any configuration on the system 28 | return "--VoilaTest.config_file_paths=[]" 29 | 30 | 31 | @pytest.fixture 32 | def voila_args(voila_notebook, voila_args_extra, voila_config_file_paths_arg): 33 | debug_args = ( 34 | ["--VoilaTest.log_level=DEBUG"] 35 | if os.environ.get("VOILA_TEST_DEBUG", False) 36 | else [] 37 | ) 38 | return [voila_notebook, voila_config_file_paths_arg, *voila_args_extra, *debug_args] 39 | 40 | 41 | @pytest.fixture 42 | def preheat_mode(): 43 | """Fixture used to activate/deactivate pre-heat kernel mode. 44 | Override this fixture in test file if you want to activate 45 | preheat mode. 46 | """ 47 | return False 48 | 49 | 50 | @pytest.fixture 51 | def progressive_rendering_mode(): 52 | """Fixture used to activate/deactivate progressive rendering mode. 53 | Override this fixture in test file if you want to activate 54 | progressive rendering mode. 55 | """ 56 | return False 57 | 58 | 59 | @pytest.fixture 60 | def preheat_config(preheat_mode): 61 | return f"--preheat_kernel={preheat_mode}" 62 | 63 | 64 | @pytest.fixture 65 | def progressive_rendering_config(progressive_rendering_mode): 66 | return f"--progressive_rendering={progressive_rendering_mode}" 67 | 68 | 69 | @pytest.fixture 70 | def voila_app(voila_args, voila_config, preheat_config, progressive_rendering_config): 71 | voila_app = VoilaTest.instance() 72 | voila_app.initialize( 73 | [*voila_args, "--no-browser", preheat_config, progressive_rendering_config] 74 | ) 75 | voila_config(voila_app) 76 | voila_app.start() 77 | yield voila_app 78 | voila_app.stop() 79 | voila_app.clear_instance() 80 | 81 | 82 | @pytest.fixture 83 | def app(voila_app): 84 | return voila_app.app 85 | -------------------------------------------------------------------------------- /tests/app/contents_handler_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pytest 3 | import tornado 4 | 5 | 6 | @pytest.fixture 7 | def preheat_mode(): 8 | return False 9 | 10 | 11 | @pytest.fixture 12 | def contents_prefix(base_url): 13 | return base_url + "voila/api/contents" 14 | 15 | 16 | @pytest.fixture 17 | def voila_args(notebook_directory, voila_args_extra): 18 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 19 | 20 | 21 | @pytest.fixture 22 | def voila_args_extra(): 23 | return ['--VoilaConfiguration.extension_language_mapping={".py": "python"}'] 24 | 25 | 26 | async def test_contents_endpoint(http_server_client, contents_prefix): 27 | response = await http_server_client.fetch(contents_prefix) 28 | html_json = json.loads(response.body.decode("utf-8")) 29 | assert "content" in html_json 30 | assert len(html_json["content"]) > 0 31 | 32 | 33 | async def test_get_notebook(http_server_client, contents_prefix): 34 | response = await http_server_client.fetch(contents_prefix + "/autokill.ipynb") 35 | html_json = json.loads(response.body.decode("utf-8")) 36 | assert html_json["name"] == "autokill.ipynb" 37 | assert html_json["content"] is None 38 | 39 | 40 | async def test_get_not_allowed_file(http_server_client, contents_prefix): 41 | with pytest.raises(tornado.httpclient.HTTPClientError): 42 | await http_server_client.fetch(contents_prefix + "/print.xcpp") 43 | 44 | 45 | async def test_get_allowed_file(http_server_client, contents_prefix): 46 | response = await http_server_client.fetch(contents_prefix + "/print.py") 47 | html_json = json.loads(response.body.decode("utf-8")) 48 | assert html_json["name"] == "print.py" 49 | assert html_json["content"] is None 50 | -------------------------------------------------------------------------------- /tests/app/cwd_subdir_test.py: -------------------------------------------------------------------------------- 1 | # test serving a notebook 2 | import pytest 3 | 4 | NOTEBOOK_PATH = "subdir/cwd_subdir.ipynb" 5 | 6 | 7 | @pytest.fixture 8 | def cwd_subdir_notebook_url(base_url): 9 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 10 | 11 | 12 | @pytest.fixture 13 | def voila_args(notebook_directory, voila_args_extra): 14 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 15 | 16 | 17 | async def test_hello_world(http_server_client, cwd_subdir_notebook_url): 18 | response = await http_server_client.fetch(cwd_subdir_notebook_url) 19 | html_text = response.body.decode("utf-8") 20 | assert "check for the cwd" in html_text 21 | -------------------------------------------------------------------------------- /tests/app/cwd_test.py: -------------------------------------------------------------------------------- 1 | # tests the --template argument of voila 2 | import os 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture 8 | def voila_notebook(notebook_directory): 9 | return os.path.join(notebook_directory, "cwd.ipynb") 10 | 11 | 12 | async def test_template_cwd(http_server_client, base_url): 13 | response = await http_server_client.fetch(base_url) 14 | html_text = response.body.decode("utf-8") 15 | assert "check for the cwd" in html_text 16 | -------------------------------------------------------------------------------- /tests/app/execute_cpp_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | TEST_XEUS_CLING = os.environ.get("VOILA_TEST_XEUS_CLING", "") == "1" 6 | NOTEBOOK_PATH = "print.xcpp" 7 | 8 | 9 | @pytest.fixture 10 | def cpp_file_url(base_url, preheat_mode): 11 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 12 | 13 | 14 | @pytest.fixture 15 | def voila_args_extra(): 16 | return [ 17 | '--VoilaConfiguration.extension_language_mapping={".xcpp": "C++11"}', 18 | "--VoilaExecutor.timeout=240", 19 | ] 20 | 21 | 22 | @pytest.fixture 23 | def voila_args(notebook_directory, voila_args_extra, preheat_mode): 24 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 25 | 26 | 27 | @pytest.mark.skipif( 28 | not TEST_XEUS_CLING, reason="opt in to avoid having to install xeus-cling" 29 | ) 30 | async def test_non_existing_kernel(http_server_client, cpp_file_url): 31 | response = await http_server_client.fetch(cpp_file_url) 32 | assert response.code == 200 33 | assert "Hello Voilà, from c++" in response.body.decode("utf-8") 34 | -------------------------------------------------------------------------------- /tests/app/execute_test.py: -------------------------------------------------------------------------------- 1 | # test basics of voila running a notebook 2 | import asyncio 3 | import json 4 | import re 5 | from unittest import mock 6 | 7 | import tornado.web 8 | 9 | 10 | async def test_hello_world(http_server_client, base_url): 11 | response = await http_server_client.fetch(base_url) 12 | assert response.code == 200 13 | html_text = response.body.decode("utf-8") 14 | assert "Hi Voilà" in html_text 15 | assert "print(" not in html_text, "by default the source code should be stripped" 16 | assert ( 17 | "test_template.css" not in html_text 18 | ), "test_template should not be the default" 19 | 20 | 21 | async def test_no_execute_allowed( 22 | voila_app, app, http_server_client, base_url, http_server_port 23 | ): 24 | assert voila_app.app is app 25 | response = (await http_server_client.fetch(base_url)).body.decode("utf-8") 26 | pattern = r"""kernelId": ["']([0-9a-zA-Z-]+)["']""" 27 | groups = re.findall(pattern, response) 28 | kernel_id = groups[0] 29 | print(kernel_id, base_url) 30 | session_id = "445edd75-c6f5-45d2-8b58-5fe8f84a7123" 31 | url = f"ws://localhost:{http_server_port[1]}{base_url}api/kernels/{kernel_id}/channels?session_id={session_id}" 32 | conn = await tornado.websocket.websocket_connect(url) 33 | 34 | msg = { 35 | "header": { 36 | "msg_id": "8573fb401ac848aab63c3bf0081e9b65", 37 | "username": "username", 38 | "session": "7a7d94334ea745f888d9c479fa738d63", 39 | "msg_type": "execute_request", 40 | "version": "5.2", 41 | }, 42 | "metadata": {}, 43 | "content": { 44 | "code": "print('la')", 45 | "silent": False, 46 | "store_history": False, 47 | "user_expressions": {}, 48 | "allow_stdin": False, 49 | "stop_on_error": False, 50 | }, 51 | "buffers": [], 52 | "parent_header": {}, 53 | "channel": "shell", 54 | } 55 | with mock.patch.object(voila_app.log, "warning") as mock_warning: 56 | await conn.write_message(json.dumps(msg)) 57 | # make sure the warning method is called 58 | while not mock_warning.called: 59 | await asyncio.sleep(0.1) 60 | mock_warning.assert_called_with( 61 | 'Received message of type "execute_request", which is not allowed. Ignoring.' 62 | ) 63 | -------------------------------------------------------------------------------- /tests/app/image_inlining_test.py: -------------------------------------------------------------------------------- 1 | # tests the --template argument of voila 2 | import base64 3 | import os 4 | 5 | import pytest 6 | 7 | NOTEBOOK_PATH = "images.ipynb" 8 | 9 | 10 | @pytest.fixture 11 | def voila_notebook(notebook_directory): 12 | return os.path.join(notebook_directory, NOTEBOOK_PATH) 13 | 14 | 15 | async def test_image_inlining(http_server_client, base_url, notebook_directory): 16 | response = await http_server_client.fetch(base_url) 17 | html_text = response.body.decode("utf-8") 18 | 19 | assert "data:image/svg+xml;base64," in html_text 20 | assert "data:image/png;base64," in html_text 21 | 22 | # check if the external file is inline 23 | with open( 24 | os.path.join(notebook_directory, notebook_directory, "jupyter.svg"), "rb" 25 | ) as f: 26 | svg_data = f.read() 27 | svg_data_base64 = base64.b64encode(svg_data).decode("ascii") 28 | assert svg_data_base64 in html_text 29 | -------------------------------------------------------------------------------- /tests/app/kernel_death_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def voila_notebook(notebook_directory): 8 | return os.path.join(notebook_directory, "autokill.ipynb") 9 | 10 | 11 | @pytest.fixture 12 | def voila_args_extra(): 13 | return ["--debug"] 14 | 15 | 16 | @pytest.fixture 17 | def preheat_mode(): 18 | return False 19 | 20 | 21 | async def test_kernel_death(http_server_client, base_url): 22 | response = await http_server_client.fetch(base_url) 23 | html_text = response.body.decode("utf-8") 24 | assert "raise DeadKernelError" in html_text 25 | -------------------------------------------------------------------------------- /tests/app/many_iopub_messages_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | MAX_TIMEOUT_SECONDS = 240 6 | 7 | 8 | @pytest.fixture 9 | def preheat_mode(): 10 | return False 11 | 12 | 13 | @pytest.fixture 14 | def voila_args_extra(): 15 | return [f"--VoilaExecutor.timeout={MAX_TIMEOUT_SECONDS}"] 16 | 17 | 18 | @pytest.fixture 19 | def voila_notebook(notebook_directory): 20 | return os.path.join(notebook_directory, "many_iopub_messages.ipynb") 21 | 22 | 23 | @pytest.mark.filterwarnings("ignore") 24 | async def test_template_cwd(http_server_client, base_url): 25 | response = await http_server_client.fetch( 26 | base_url, request_timeout=MAX_TIMEOUT_SECONDS + 20 27 | ) 28 | html_text = response.body.decode("utf-8") 29 | assert "you should see me" in html_text 30 | -------------------------------------------------------------------------------- /tests/app/nbextensions_test.py: -------------------------------------------------------------------------------- 1 | # tests programmatic config of template system 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def voila_config(): 11 | def config(app): 12 | pass 13 | 14 | os.environ["JUPYTER_CONFIG_DIR"] = os.path.join(BASE_DIR, "../configs/general") 15 | yield config 16 | del os.environ["JUPYTER_CONFIG_DIR"] 17 | 18 | 19 | @pytest.fixture 20 | def voila_config_file_paths_arg(): 21 | # we don't want the tests to use any configuration on the system 22 | path = os.path.abspath(os.path.join(BASE_DIR, "../configs/general")) 23 | return "--VoilaTest.config_file_paths=[%r]" % path 24 | 25 | 26 | @pytest.mark.skip(reason="TODO: update for JupyterLab extensions") 27 | async def test_lists_extension(http_server_client, base_url): 28 | response = await http_server_client.fetch(base_url) 29 | assert response.code == 200 30 | html_text = response.body.decode("utf-8") 31 | assert "Hi Voilà" in html_text 32 | assert "ipytest/extension.js" in html_text 33 | -------------------------------------------------------------------------------- /tests/app/no_kernelspec_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | NOTEBOOK_PATH = "no_kernelspec.ipynb" 4 | 5 | 6 | @pytest.fixture 7 | def non_existing_kernel_notebook(base_url): 8 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 9 | 10 | 11 | @pytest.fixture 12 | def voila_args(notebook_directory, voila_args_extra): 13 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 14 | 15 | 16 | async def test_non_existing_kernel( 17 | http_server_client, 18 | non_existing_kernel_notebook, 19 | ): 20 | response = await http_server_client.fetch(non_existing_kernel_notebook) 21 | assert response.code == 200 22 | assert "Executing without a kernelspec" in response.body.decode("utf-8") 23 | -------------------------------------------------------------------------------- /tests/app/no_metadata.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | NOTEBOOK_PATH = "no_metadata.ipynb" 4 | 5 | 6 | @pytest.fixture 7 | def non_existing_notebook_metadata(base_url): 8 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 9 | 10 | 11 | @pytest.fixture 12 | def voila_args(notebook_directory, voila_args_extra): 13 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 14 | 15 | 16 | async def test_non_existing_metadata( 17 | http_server_client, non_existing_notebook_metadata 18 | ): 19 | response = await http_server_client.fetch(non_existing_notebook_metadata) 20 | assert response.code == 200 21 | assert "Executing without notebook metadata" in response.body.decode("utf-8") 22 | -------------------------------------------------------------------------------- /tests/app/no_strip_sources_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | BASE_DIR = os.path.dirname(__file__) 6 | 7 | 8 | @pytest.fixture 9 | def voila_args_extra(): 10 | return ["--VoilaConfiguration.strip_sources=False", "--VoilaExecutor.timeout=240"] 11 | 12 | 13 | async def test_no_strip_sources(http_server_client, base_url): 14 | response = await http_server_client.fetch(base_url) 15 | assert response.code == 200 16 | html_text = response.body.decode("utf-8") 17 | assert "Hi Voilà" in html_text 18 | assert "print" in html_text, "the source code should *NOT* be stripped" 19 | -------------------------------------------------------------------------------- /tests/app/non_existing_kernel_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | NOTEBOOK_PATH = "non_existing_kernel.ipynb" 4 | 5 | 6 | @pytest.fixture 7 | def non_existing_kernel_notebook(base_url): 8 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 9 | 10 | 11 | @pytest.fixture 12 | def voila_args(notebook_directory, voila_args_extra): 13 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 14 | 15 | 16 | async def test_non_existing_kernel(http_server_client, non_existing_kernel_notebook): 17 | response = await http_server_client.fetch(non_existing_kernel_notebook) 18 | assert response.code == 200 19 | assert "non-existing kernel" in response.body.decode("utf-8") 20 | -------------------------------------------------------------------------------- /tests/app/notebooks_test.py: -------------------------------------------------------------------------------- 1 | # simply tests if some notebooks execute 2 | import pytest 3 | 4 | NOTEBOOK_PATH = "other_comms.ipynb" 5 | 6 | 7 | @pytest.fixture 8 | def notebook_other_comms_path(base_url): 9 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 10 | 11 | 12 | @pytest.fixture 13 | def voila_args(notebook_directory, voila_args_extra): 14 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 15 | 16 | 17 | async def test_other_comms(http_server_client, notebook_other_comms_path): 18 | response = await http_server_client.fetch(notebook_other_comms_path) 19 | html_text = response.body.decode("utf-8") 20 | assert "This notebook executed" in html_text 21 | -------------------------------------------------------------------------------- /tests/app/page_config_hook_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def voila_notebook(notebook_directory): 11 | return os.path.join(notebook_directory, "print.ipynb") 12 | 13 | 14 | @pytest.fixture 15 | def voila_config(): 16 | def foo(current_page_config, **kwargs): 17 | current_page_config["foo"] = "my custom config" 18 | return current_page_config 19 | 20 | def config(app): 21 | app.voila_configuration.page_config_hook = foo 22 | 23 | return config 24 | 25 | 26 | async def test_prelaunch_hook(http_server_client, base_url): 27 | response = await http_server_client.fetch(base_url) 28 | assert response.code == 200 29 | assert "my custom config" in response.body.decode("utf-8") 30 | -------------------------------------------------------------------------------- /tests/app/preheat_configuration_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import time 4 | 5 | import pytest 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | NOTEBOOK_EXECUTION_TIME = 3 9 | NUMBER_PREHEATED_KERNEL = 2 10 | TIME_THRESHOLD = 1 11 | 12 | 13 | @pytest.fixture 14 | def voila_config_file_paths_arg(): 15 | path = os.path.join(BASE_DIR, "..", "configs", "preheat") 16 | return "--VoilaTest.config_file_paths=[%r]" % path 17 | 18 | 19 | @pytest.fixture 20 | def preheat_mode(): 21 | return True 22 | 23 | 24 | @pytest.fixture 25 | def voila_notebook(notebook_directory): 26 | return os.path.join(notebook_directory, "preheat", "pre_heat.ipynb") 27 | 28 | 29 | async def send_request(sc, url, wait=0): 30 | await asyncio.sleep(wait) 31 | real_time = time.time() 32 | response = await sc.fetch(url) 33 | real_time = time.time() - real_time 34 | html_text = response.body.decode("utf-8") 35 | return real_time, html_text 36 | 37 | 38 | async def test_refill_kernel_asynchronously(http_server_client, base_url): 39 | await asyncio.sleep(NUMBER_PREHEATED_KERNEL * NOTEBOOK_EXECUTION_TIME + 1) 40 | fast = [] 41 | slow = [] 42 | for _i in range(5 * NUMBER_PREHEATED_KERNEL): 43 | time, _ = await send_request(sc=http_server_client, url=base_url) 44 | if time < TIME_THRESHOLD: 45 | fast.append(time) 46 | else: 47 | slow.append(time) 48 | 49 | assert len(fast) > 1 50 | assert len(slow) > 1 51 | assert len(fast) + len(slow) == 5 * NUMBER_PREHEATED_KERNEL 52 | await asyncio.sleep(NOTEBOOK_EXECUTION_TIME + 1) 53 | 54 | 55 | async def test_env_variable_defined_in_kernel(http_server_client, base_url): 56 | await asyncio.sleep(NUMBER_PREHEATED_KERNEL * NOTEBOOK_EXECUTION_TIME + 1) 57 | _, text = await send_request(sc=http_server_client, url=base_url) 58 | assert "bar" in text 59 | await asyncio.sleep(NOTEBOOK_EXECUTION_TIME + 1) 60 | -------------------------------------------------------------------------------- /tests/app/preheat_default_kernel_env_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture() 8 | def voila_args_extra(): 9 | return ['--VoilaKernelManager.default_env_variables={"FOO": "BAR"}'] 10 | 11 | 12 | @pytest.fixture 13 | def preheat_mode(): 14 | return True 15 | 16 | 17 | @pytest.fixture 18 | def voila_notebook(notebook_directory): 19 | return os.path.join(notebook_directory, "preheat", "default_env_variables.ipynb") 20 | 21 | 22 | NOTEBOOK_EXECUTION_TIME = 2 23 | 24 | 25 | async def send_request(sc, url, wait=0): 26 | await asyncio.sleep(wait) 27 | response = await sc.fetch(url) 28 | return response.body.decode("utf-8") 29 | 30 | 31 | async def test_default_kernel_env_variable(http_server_client, base_url): 32 | html_text = await send_request( 33 | sc=http_server_client, url=base_url, wait=NOTEBOOK_EXECUTION_TIME + 1 34 | ) 35 | assert "BAR" in html_text 36 | -------------------------------------------------------------------------------- /tests/app/preheat_multiple_notebooks_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import time 4 | 5 | import pytest 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | NOTEBOOK_EXECUTION_TIME = 3 9 | NUMBER_PREHEATED_KERNEL = 2 10 | TIME_THRESHOLD = 1 11 | 12 | 13 | @pytest.fixture 14 | def voila_config_file_paths_arg(): 15 | path = os.path.join(BASE_DIR, "..", "configs", "preheat") 16 | return "--VoilaTest.config_file_paths=[%r]" % path 17 | 18 | 19 | @pytest.fixture 20 | def preheat_mode(): 21 | return True 22 | 23 | 24 | @pytest.fixture 25 | def voila_notebook(notebook_directory): 26 | return os.path.join(notebook_directory, "preheat") 27 | 28 | 29 | async def send_request(sc, url, wait=0): 30 | await asyncio.sleep(wait) 31 | real_time = time.time() 32 | response = await sc.fetch(url) 33 | real_time = time.time() - real_time 34 | html_text = response.body.decode("utf-8") 35 | return real_time, html_text 36 | 37 | 38 | async def test_render_notebook_with_heated_kernel(http_server_client, base_url): 39 | await asyncio.sleep(NUMBER_PREHEATED_KERNEL * NOTEBOOK_EXECUTION_TIME + 1) 40 | time, text = await send_request( 41 | sc=http_server_client, url=f"{base_url}voila/render/pre_heat.ipynb" 42 | ) 43 | 44 | assert "hello world" in text 45 | assert time < TIME_THRESHOLD 46 | await asyncio.sleep(NOTEBOOK_EXECUTION_TIME + 1) 47 | 48 | 49 | async def test_render_denylisted_notebook_with_nornal_kernel( 50 | http_server_client, base_url 51 | ): 52 | await asyncio.sleep(NUMBER_PREHEATED_KERNEL * NOTEBOOK_EXECUTION_TIME + 1) 53 | time, text = await send_request( 54 | sc=http_server_client, url=f"{base_url}voila/render/denylisted.ipynb" 55 | ) 56 | 57 | assert "hello world" in text 58 | assert time > TIME_THRESHOLD 59 | await asyncio.sleep(NOTEBOOK_EXECUTION_TIME + 1) 60 | -------------------------------------------------------------------------------- /tests/app/preheat_with_query_string_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import time 4 | 5 | import pytest 6 | 7 | 8 | @pytest.fixture(params=[['--Voila.base_url="/base/"'], []]) 9 | def using_base_url(request): 10 | return request.param 11 | 12 | 13 | @pytest.fixture(params=[['--Voila.server_url="/server/"'], []]) 14 | def using_server_url(request): 15 | return request.param 16 | 17 | 18 | @pytest.fixture() 19 | def voila_args_extra(http_server_port, using_server_url, using_base_url): 20 | return [f"--port={http_server_port[-1]}", *using_server_url, *using_base_url] 21 | 22 | 23 | @pytest.fixture 24 | def preheat_mode(): 25 | return True 26 | 27 | 28 | @pytest.fixture 29 | def voila_notebook(notebook_directory): 30 | return os.path.join(notebook_directory, "preheat", "get_query_string.ipynb") 31 | 32 | 33 | NOTEBOOK_EXECUTION_TIME = 2 34 | TIME_THRESHOLD = NOTEBOOK_EXECUTION_TIME 35 | 36 | 37 | async def send_request(sc, url, wait=0): 38 | await asyncio.sleep(wait) 39 | real_time = time.time() 40 | response = await sc.fetch(url) 41 | real_time = time.time() - real_time 42 | html_text = response.body.decode("utf-8") 43 | return real_time, html_text 44 | 45 | 46 | async def test_request_with_query( 47 | http_server_client, base_url, using_base_url, using_server_url 48 | ): 49 | """ 50 | We sent request with query parameter, `get_query_string` should 51 | return value. 52 | """ 53 | if len(using_server_url) > 0: 54 | url = "/server/?foo=bar" 55 | else: 56 | if len(using_base_url) > 0: 57 | url = "/base/?foo=bar" 58 | else: 59 | url = f"{base_url}?foo=bar" 60 | _, html_text = await send_request( 61 | sc=http_server_client, url=url, wait=NOTEBOOK_EXECUTION_TIME + 1 62 | ) 63 | assert "foo=bar" in html_text 64 | -------------------------------------------------------------------------------- /tests/app/prelaunch_hook_papermill_test.py: -------------------------------------------------------------------------------- 1 | # tests prelaunch hook config 2 | import os 3 | from urllib.parse import quote_plus 4 | 5 | import pytest 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | 9 | 10 | @pytest.fixture 11 | def voila_notebook(notebook_directory): 12 | return os.path.join(notebook_directory, "print_parameterized.ipynb") 13 | 14 | 15 | @pytest.fixture 16 | def voila_config(): 17 | def parameterize_with_papermill(req, notebook, cwd): 18 | import tornado 19 | 20 | # Grab parameters 21 | parameters = req.get_argument("parameters", {}) 22 | 23 | # try to convert to dict if not e.g. string/unicode 24 | if not isinstance(parameters, dict): 25 | try: 26 | parameters = tornado.escape.json_decode(parameters) 27 | except ValueError: 28 | parameters = None 29 | 30 | # if passed and a dict, use papermill to inject parameters 31 | if parameters and isinstance(parameters, dict): 32 | from papermill.parameterize import parameterize_notebook 33 | 34 | # setup for papermill 35 | # 36 | # these two blocks are done 37 | # to avoid triggering errors 38 | # in papermill's notebook 39 | # loading logic 40 | for cell in notebook.cells: 41 | if "tags" not in cell.metadata: 42 | cell.metadata.tags = [] 43 | if "papermill" not in notebook.metadata: 44 | notebook.metadata.papermill = {} 45 | 46 | # Parameterize with papermill 47 | return parameterize_notebook(notebook, parameters) 48 | 49 | def config(app): 50 | app.prelaunch_hook = parameterize_with_papermill 51 | 52 | return config 53 | 54 | 55 | async def test_prelaunch_hook_papermill(http_server_client, base_url): 56 | url = base_url + "?parameters=" + quote_plus('{"name":"Parameterized_Variable"}') 57 | response = await http_server_client.fetch(url) 58 | assert response.code == 200 59 | html_text = response.body.decode("utf-8") 60 | assert "Hi Parameterized_Variable" in html_text 61 | assert ( 62 | "test_template.css" not in html_text 63 | ), "test_template should not be the default" 64 | -------------------------------------------------------------------------------- /tests/app/prelaunch_hook_test.py: -------------------------------------------------------------------------------- 1 | # tests prelaunch hook config 2 | import os 3 | 4 | import pytest 5 | from nbformat import NotebookNode 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | 9 | 10 | @pytest.fixture 11 | def voila_notebook(notebook_directory): 12 | return os.path.join(notebook_directory, "print.ipynb") 13 | 14 | 15 | @pytest.fixture 16 | def voila_config(): 17 | def foo(req, notebook, cwd): 18 | argument = req.get_argument("test") 19 | notebook.cells.append( 20 | NotebookNode( 21 | { 22 | "cell_type": "code", 23 | "execution_count": 0, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": f'print("Hi prelaunch hook {argument}!")\n', 27 | } 28 | ) 29 | ) 30 | 31 | def config(app): 32 | app.prelaunch_hook = foo 33 | 34 | return config 35 | 36 | 37 | async def test_prelaunch_hook(http_server_client, base_url): 38 | response = await http_server_client.fetch( 39 | base_url + "?test=blerg", 40 | ) 41 | assert response.code == 200 42 | assert "Hi Voilà" in response.body.decode("utf-8") 43 | assert "Hi prelaunch hook blerg" in response.body.decode("utf-8") 44 | -------------------------------------------------------------------------------- /tests/app/preprocessor_test.py: -------------------------------------------------------------------------------- 1 | # tests the --template argument of Voilà 2 | import os 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture 8 | def voila_notebook(notebook_directory): 9 | return os.path.join(notebook_directory, "skip-voila-cell.ipynb") 10 | 11 | 12 | @pytest.fixture 13 | def voila_args_extra(): 14 | return ["--template=skip_template"] 15 | 16 | 17 | async def test_markdown_preprocessor(http_server_client, base_url): 18 | response = await http_server_client.fetch(base_url) 19 | assert response.code == 200 20 | html_text = response.body.decode("utf-8") 21 | assert "Hi Voilà cell" in html_text 22 | assert "Hi non Voilà cell" not in html_text 23 | -------------------------------------------------------------------------------- /tests/app/progressive_rendering_activation_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import voila.app 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def progressive_rendering_mode(): 8 | return True 9 | 10 | 11 | @pytest.fixture 12 | def preheat_mode(): 13 | return True 14 | 15 | 16 | @pytest.fixture 17 | def voila_notebook(notebook_directory): 18 | return os.path.join(notebook_directory, "preheat", "pre_heat.ipynb") 19 | 20 | 21 | class VoilaTest(voila.app.Voila): 22 | def listen(self): 23 | pass 24 | 25 | 26 | def test_voila(voila_args, voila_config, preheat_config, progressive_rendering_config): 27 | with pytest.raises(Exception) as e_info: 28 | voila_app = VoilaTest.instance() 29 | voila_app.initialize( 30 | [*voila_args, "--no-browser", preheat_config, progressive_rendering_config] 31 | ) 32 | voila_config(voila_app) 33 | voila_app.start() 34 | assert ( 35 | str(e_info.value) 36 | == "`preheat_kernel` and `progressive_rendering` are incompatible" 37 | ) 38 | -------------------------------------------------------------------------------- /tests/app/progressive_rendering_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import time 4 | 5 | import pytest 6 | 7 | 8 | @pytest.fixture 9 | def progressive_rendering_mode(): 10 | return True 11 | 12 | 13 | @pytest.fixture 14 | def voila_notebook(notebook_directory): 15 | return os.path.join(notebook_directory, "preheat", "pre_heat.ipynb") 16 | 17 | 18 | NOTEBOOK_EXECUTION_TIME = 2 19 | TIME_THRESHOLD = NOTEBOOK_EXECUTION_TIME 20 | 21 | 22 | async def send_request(sc, url, wait=0): 23 | await asyncio.sleep(wait) 24 | real_time = time.time() 25 | response = await sc.fetch(url) 26 | real_time = time.time() - real_time 27 | html_text = response.body.decode("utf-8") 28 | return real_time, html_text 29 | 30 | 31 | async def test_request(http_server_client, base_url): 32 | """ 33 | We send a request to server immediately, the returned HTML should 34 | not contain the output. 35 | """ 36 | time, text = await send_request(sc=http_server_client, url=base_url) 37 | assert '"progressiveRendering": true' in text 38 | assert "hello world" not in text 39 | assert time < NOTEBOOK_EXECUTION_TIME 40 | -------------------------------------------------------------------------------- /tests/app/serve_directory_test.py: -------------------------------------------------------------------------------- 1 | # test serving a notebook or python/c++ notebook 2 | import os 3 | 4 | import pytest 5 | 6 | TEST_XEUS_CLING = os.environ.get("VOILA_TEST_XEUS_CLING", "") == "1" 7 | 8 | 9 | @pytest.fixture 10 | def preheat_mode(): 11 | return False 12 | 13 | 14 | @pytest.fixture 15 | def voila_args(notebook_directory, voila_args_extra): 16 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 17 | 18 | 19 | async def test_print(http_server_client, print_notebook_url): 20 | print(print_notebook_url) 21 | response = await http_server_client.fetch(print_notebook_url) 22 | assert response.code == 200 23 | assert "Hi Voilà" in response.body.decode("utf-8") 24 | 25 | 26 | @pytest.fixture 27 | def voila_args_extra(): 28 | return ['--VoilaConfiguration.extension_language_mapping={".py": "python"}'] 29 | 30 | 31 | async def test_print_py(http_server_client, print_notebook_url): 32 | print(print_notebook_url) 33 | response = await http_server_client.fetch(print_notebook_url.replace("ipynb", "py")) 34 | assert response.code == 200 35 | assert "Hi Voilà" in response.body.decode("utf-8") 36 | 37 | 38 | @pytest.mark.skipif( 39 | not TEST_XEUS_CLING, reason="opt in to avoid having to install xeus-cling" 40 | ) 41 | async def test_print_julia_notebook(http_server_client, print_notebook_url): 42 | print(print_notebook_url) 43 | response = await http_server_client.fetch( 44 | print_notebook_url.replace(".ipynb", "_cpp.ipynb") 45 | ) 46 | assert response.code == 200 47 | assert "Hi Voilà, from c++" in response.body.decode("utf-8") 48 | -------------------------------------------------------------------------------- /tests/app/show_traceback_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | NOTEBOOK_PATH = "syntax_error.ipynb" 4 | 5 | 6 | @pytest.fixture(params=[True, False]) 7 | def show_tracebacks(request): 8 | return request.param 9 | 10 | 11 | @pytest.fixture 12 | def notebook_show_traceback_path(base_url): 13 | return base_url + f"voila/render/{NOTEBOOK_PATH}" 14 | 15 | 16 | @pytest.fixture 17 | def voila_args(notebook_directory, voila_args_extra, show_tracebacks): 18 | return [ 19 | "--VoilaTest.root_dir=%r" % notebook_directory, 20 | f"--VoilaConfiguration.show_tracebacks={show_tracebacks}", 21 | *voila_args_extra, 22 | ] 23 | 24 | 25 | async def test_syntax_error( 26 | http_server_client, notebook_show_traceback_path, show_tracebacks 27 | ): 28 | response = await http_server_client.fetch(notebook_show_traceback_path) 29 | assert response.code == 200 30 | output = response.body.decode("utf-8") 31 | if show_tracebacks: 32 | assert "this is a syntax error" in output, 'should show the "code"' 33 | assert ( 34 | "SyntaxError" in output and "invalid syntax" in output 35 | ), "should show the error" 36 | else: 37 | assert "There was an error when executing cell" in output 38 | assert "This should not be executed" not in output 39 | -------------------------------------------------------------------------------- /tests/app/shutdown_kernel_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def voila_config(): 7 | def config(voila_app): 8 | voila_app.tornado_settings["disable_check_xsrf"] = True 9 | 10 | return config 11 | 12 | 13 | async def test_shutdown_handler(http_server_client, base_url): 14 | response = await http_server_client.fetch(base_url) 15 | html_text = response.body.decode("utf-8") 16 | pattern = r"""kernelId": ["']([0-9a-zA-Z-]+)["']""" 17 | groups = re.findall(pattern, html_text) 18 | kernel_id = groups[0] 19 | shutdown_url = f"{base_url}voila/api/shutdown/{kernel_id}" 20 | shutdown_response = await http_server_client.fetch( 21 | shutdown_url, method="POST", body=b"" 22 | ) 23 | assert shutdown_response.code == 204 24 | -------------------------------------------------------------------------------- /tests/app/static_files_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import tornado 3 | 4 | from voila.static_file_handler import TemplateStaticFileHandler 5 | 6 | 7 | async def test_static_file_absolute_path(voila_app, app, base_url, http_server_client): 8 | response_lab = await http_server_client.fetch( 9 | f"{base_url}voila/templates/lab/static/voila.js" 10 | ) 11 | assert response_lab.code == 200 12 | abspath = TemplateStaticFileHandler.get_absolute_path(None, "lab/static/voila.js") 13 | with open(abspath, encoding="utf-8", errors="replace") as f: 14 | content = f.read() 15 | assert response_lab.body.decode("utf-8") == content 16 | 17 | 18 | async def test_static_file_not_found(voila_app, app, base_url, http_server_client): 19 | with pytest.raises(tornado.httpclient.HTTPClientError, match="HTTP 404.*"): 20 | await http_server_client.fetch( 21 | f"{base_url}voila/templates/lab/static/doesnotexist.js" 22 | ) 23 | 24 | 25 | async def test_static_file_availability_default( 26 | voila_app, app, base_url, http_server_client 27 | ): 28 | response_lab = await http_server_client.fetch( 29 | f"{base_url}voila/templates/lab/static/voila.js" 30 | ) 31 | response_default = await http_server_client.fetch( 32 | f"{base_url}voila/static/voila.js" 33 | ) 34 | assert response_lab.code == 200 35 | assert response_default.code == 200 36 | assert response_lab.body.decode("utf-8") == response_default.body.decode("utf-8") 37 | 38 | 39 | async def test_static_file_override(voila_app, app, base_url, http_server_client): 40 | response_lab = await http_server_client.fetch( 41 | f"{base_url}voila/templates/lab/static/voila.js" 42 | ) 43 | response_test_template = await http_server_client.fetch( 44 | f"{base_url}voila/templates/test_template/static/voila.js" 45 | ) 46 | assert response_lab.code == 200 47 | assert response_test_template.code == 200 48 | assert response_lab.body.decode("utf-8") != response_test_template.body.decode( 49 | "utf-8" 50 | ) 51 | 52 | 53 | async def test_static_file_other_template(voila_app, app, base_url, http_server_client): 54 | response = await http_server_client.fetch( 55 | f"{base_url}voila/templates/test_template/static/only-in-test-template.js" 56 | ) 57 | assert response.code == 200 58 | assert response.body.decode("utf-8") == "", "empty file expected" 59 | -------------------------------------------------------------------------------- /tests/app/syntax_error_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | NOTEBOOK_PATH = "syntax_error.ipynb" 6 | 7 | 8 | @pytest.fixture 9 | def voila_notebook(notebook_directory): 10 | return os.path.join(notebook_directory, NOTEBOOK_PATH) 11 | 12 | 13 | async def test_syntax_error(http_server_client, base_url): 14 | response = await http_server_client.fetch(base_url) 15 | assert response.code == 200 16 | output = response.body.decode("utf-8") 17 | assert "There was an error when executing cell" in output 18 | assert "This should not be executed" not in output 19 | -------------------------------------------------------------------------------- /tests/app/template_arg_test.py: -------------------------------------------------------------------------------- 1 | # tests the --template argument of Voilà 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def voila_args_extra(): 7 | return ["--template=test_template", "--VoilaExecutor.timeout=240"] 8 | 9 | 10 | async def test_template(http_server_client, base_url): 11 | response = await http_server_client.fetch(base_url) 12 | assert response.code == 200 13 | assert "test_template.css" in response.body.decode("utf-8") 14 | assert "Hi Voilà" in response.body.decode("utf-8") 15 | -------------------------------------------------------------------------------- /tests/app/template_cli_test.py: -------------------------------------------------------------------------------- 1 | # tests programmatic config of template system 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def voila_args_extra(): 11 | path_test_template = os.path.abspath( 12 | os.path.join( 13 | BASE_DIR, "../test_template/share/jupyter/voila/templates/test_template/" 14 | ) 15 | ) 16 | path_default = os.path.abspath( 17 | os.path.join(BASE_DIR, "../../share/jupyter/voila/templates/default") 18 | ) 19 | return [ 20 | "--template=test_template", 21 | "--VoilaTest.template_paths=[{!r}, {!r}]".format( 22 | path_test_template, path_default 23 | ), 24 | "--VoilaExecutor.timeout=240", 25 | ] 26 | 27 | 28 | async def test_template_test(http_server_client, base_url): 29 | response = await http_server_client.fetch(base_url) 30 | assert response.code == 200 31 | assert "test_template.css" in response.body.decode("utf-8") 32 | -------------------------------------------------------------------------------- /tests/app/template_config_file_test.py: -------------------------------------------------------------------------------- 1 | # tests config of template system from JSON file 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def voila_args_extra(): 11 | path_test_template = os.path.abspath( 12 | os.path.join( 13 | BASE_DIR, 14 | "../test_template/share/jupyter/voila/templates/test_template/nbconvert_templates", 15 | ) 16 | ) 17 | path_default = os.path.abspath( 18 | os.path.join( 19 | BASE_DIR, "../../share/jupyter/voila/templates/default/nbconvert_templates" 20 | ) 21 | ) 22 | return [ 23 | "--template=test_template", 24 | "--Voila.nbconvert_template_paths=[{!r}, {!r}]".format( 25 | path_test_template, path_default 26 | ), 27 | "--VoilaExecutor.timeout=240", 28 | ] 29 | 30 | 31 | async def test_template_test(http_server_client, base_url): 32 | response = await http_server_client.fetch(base_url) 33 | assert response.code == 200 34 | assert "test_template.css" in response.body.decode("utf-8") 35 | assert "Hi Voilà" in response.body.decode("utf-8") 36 | assert "test resource from config file" in response.body.decode("utf-8") 37 | -------------------------------------------------------------------------------- /tests/app/template_custom_test.py: -------------------------------------------------------------------------------- 1 | # tests programmatic config of template system 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def voila_args_extra(): 11 | return ["--template=test_template", "--VoilaExecutor.timeout=240"] 12 | 13 | 14 | @pytest.fixture 15 | def voila_config(): 16 | def config(app): 17 | path_test_template = os.path.abspath( 18 | os.path.join( 19 | BASE_DIR, "../test_template/share/jupyter/voila/templates/test_template" 20 | ) 21 | ) 22 | path_default = os.path.abspath( 23 | os.path.join(BASE_DIR, "../../share/jupyter/voila/templates/default") 24 | ) 25 | app.template_paths = [path_test_template, path_default] 26 | 27 | return config 28 | 29 | 30 | async def test_template(http_server_client, base_url): 31 | response = await http_server_client.fetch(base_url) 32 | assert response.code == 200 33 | assert "test_template.css" in response.body.decode("utf-8") 34 | assert "Hi Voilà" in response.body.decode("utf-8") 35 | -------------------------------------------------------------------------------- /tests/app/template_sanity_test.py: -------------------------------------------------------------------------------- 1 | # tests basic things the templates should have 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture(params=["lab", "classic"]) 10 | def voila_args_extra(request): 11 | return [f"--template={request.param}"] 12 | 13 | 14 | async def test_lists_extension(http_server_client, base_url, voila_app): 15 | response = await http_server_client.fetch(base_url) 16 | assert response.code == 200 17 | html_text = response.body.decode("utf-8") 18 | assert "/voila/static/voila.js" in html_text 19 | -------------------------------------------------------------------------------- /tests/app/timeout_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def voila_notebook(notebook_directory): 8 | return os.path.join(notebook_directory, "sleep.ipynb") 9 | 10 | 11 | @pytest.fixture 12 | def voila_args_extra(): 13 | return ["--VoilaExecutor.timeout=1", "--KernelManager.shutdown_wait_time=0.1"] 14 | 15 | 16 | async def test_timeout(http_server_client, base_url): 17 | response = await http_server_client.fetch(base_url) 18 | html_text = response.body.decode("utf-8") 19 | assert "Cell execution timed out" in html_text 20 | -------------------------------------------------------------------------------- /tests/app/tree_test.py: -------------------------------------------------------------------------------- 1 | # test tree rendering 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def preheat_mode(): 7 | return False 8 | 9 | 10 | @pytest.fixture 11 | def voila_args(notebook_directory, voila_args_extra): 12 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 13 | 14 | 15 | @pytest.fixture 16 | def voila_args_extra(): 17 | return [ 18 | '--VoilaConfiguration.extension_language_mapping={".xcpp": "C++11"}', 19 | "--VoilaExecutor.timeout=240", 20 | "--VoilaConfiguration.classic_tree=true", 21 | ] 22 | 23 | 24 | async def test_tree(http_server_client, base_url): 25 | response = await http_server_client.fetch(base_url) 26 | assert response.code == 200 27 | text = response.body.decode("utf-8") 28 | assert "print.ipynb" in text, "tree handler should render ipynb files" 29 | assert ( 30 | "print.xcpp" in text 31 | ), "tree handler should render xcpp files (due to extension_language_mapping)" 32 | assert ( 33 | "print.py" not in text 34 | ), "tree handler should not render .py files (due to extension_language_mapping)" 35 | -------------------------------------------------------------------------------- /tests/configs/general/nbconfig/notebook.d/ipytest.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "ipytest/extension": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/configs/general/voila.json: -------------------------------------------------------------------------------- 1 | { 2 | "VoilaConfiguration": { 3 | "template": "test_template" 4 | }, 5 | "MappingKernelManager": { 6 | "cull_interval": 10 7 | }, 8 | "LargeFileManager": { 9 | "use_atomic_writing": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/configs/preheat/voila.json: -------------------------------------------------------------------------------- 1 | { 2 | "VoilaConfiguration": { 3 | "preheat_kernel": true 4 | }, 5 | "VoilaKernelManager": { 6 | "preheat_denylist": ["denylisted.ipynb"], 7 | "kernel_pools_config": { 8 | "default": { 9 | "pool_size": 1 10 | }, 11 | "pre_heat.ipynb": { 12 | "pool_size": 2, 13 | "kernel_env_variables": { 14 | "foo": "bar" 15 | } 16 | } 17 | }, 18 | "fill_delay": 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # fixtures common for app and server 2 | import os 3 | import time 4 | 5 | import pytest 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | 9 | 10 | @pytest.fixture 11 | def base_url(): 12 | return "/" 13 | 14 | 15 | @pytest.fixture 16 | def notebook_directory(): 17 | return os.path.join(BASE_DIR, "notebooks") 18 | 19 | 20 | @pytest.fixture 21 | def print_notebook_url(base_url): 22 | return base_url + "voila/render/print.ipynb" 23 | 24 | 25 | @pytest.fixture 26 | def syntax_error_notebook_url(base_url): 27 | return base_url + "voila/render/syntax_error.ipynb" 28 | 29 | 30 | @pytest.fixture 31 | def voila_notebook(notebook_directory): 32 | return os.path.join(notebook_directory, "print.ipynb") 33 | 34 | 35 | @pytest.fixture(autouse=True) 36 | def sleep_between_tests(): 37 | yield 38 | time.sleep(1) 39 | -------------------------------------------------------------------------------- /tests/execute_output_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | from copy import deepcopy 3 | 4 | from nbformat import NO_CONVERT, read 5 | 6 | from voila.execute import executenb 7 | 8 | BASE_DIR = os.path.dirname(__file__) 9 | WIDGET_MIME_TYPE_VIEW = "application/vnd.jupyter.widget-view+json" 10 | WIDGET_MIME_TYPE_STATE = "application/vnd.jupyter.widget-state+json" 11 | 12 | 13 | # based on nbconvert.preprocessors.tests.test_execute.TestExecute 14 | # we cannot import it because pytest would then also execute those tests 15 | def normalize_output(output): 16 | """ 17 | Normalizes outputs for comparison. 18 | """ 19 | output = dict(output) 20 | if "metadata" in output: 21 | del output["metadata"] 22 | # tracebacks can be different per installation/python version 23 | if "traceback" in output: 24 | del output["traceback"] 25 | if "application/vnd.jupyter.widget-view+json" in output.get("data", {}): 26 | output["data"]["application/vnd.jupyter.widget-view+json"][ 27 | "model_id" 28 | ] = "" 29 | 30 | 31 | def normalize_outputs(outputs): 32 | for output in outputs: 33 | normalize_output(output) 34 | 35 | 36 | def test_execute_output(): 37 | path = os.path.join(BASE_DIR, "notebooks/output.ipynb") 38 | nb = read(path, NO_CONVERT) 39 | nb_voila = deepcopy(nb) 40 | executenb(nb_voila, timeout=30) 41 | 42 | widget_states = nb.metadata.widgets[WIDGET_MIME_TYPE_STATE]["state"] 43 | widget_states_voila = nb_voila.metadata.widgets[WIDGET_MIME_TYPE_STATE]["state"] 44 | 45 | for cell_voila, cell in zip(nb_voila.cells, nb.cells): 46 | for output_voila, output in zip(cell_voila.outputs, cell.outputs): 47 | if "data" in output and WIDGET_MIME_TYPE_VIEW in output["data"]: 48 | widget_id = output["data"][WIDGET_MIME_TYPE_VIEW]["model_id"] 49 | widget_id_voila = output_voila["data"][WIDGET_MIME_TYPE_VIEW][ 50 | "model_id" 51 | ] 52 | widget_state = widget_states[widget_id] 53 | widget_state_voila = widget_states_voila[widget_id_voila] 54 | # if the widget is an output widget, it has the outputs, which we also check 55 | assert normalize_outputs( 56 | widget_state.state.get("outputs", []) 57 | ) == normalize_outputs(widget_state_voila.state.get("outputs", [])) 58 | normalize_output(output_voila) 59 | normalize_output(output) 60 | assert output_voila == output 61 | -------------------------------------------------------------------------------- /tests/notebooks/autokill.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import signal\n", 11 | "pid = os.getpid()\n", 12 | "os.kill(pid, signal.SIGTERM)" 13 | ] 14 | } 15 | ], 16 | "metadata": { 17 | "kernelspec": { 18 | "display_name": "Python 3", 19 | "language": "python", 20 | "name": "python3" 21 | }, 22 | "language_info": { 23 | "codemirror_mode": { 24 | "name": "ipython", 25 | "version": 3 26 | }, 27 | "file_extension": ".py", 28 | "mimetype": "text/x-python", 29 | "name": "python", 30 | "nbconvert_exporter": "python", 31 | "pygments_lexer": "ipython3", 32 | "version": "3.9.1" 33 | } 34 | }, 35 | "nbformat": 4, 36 | "nbformat_minor": 4 37 | } 38 | -------------------------------------------------------------------------------- /tests/notebooks/cgi.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "ExecuteTime": { 8 | "end_time": "2020-08-18T11:55:18.544213Z", 9 | "start_time": "2020-08-18T11:55:18.541029Z" 10 | } 11 | }, 12 | "outputs": [], 13 | "source": [ 14 | "import os\n", 15 | "from urllib.parse import parse_qs" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "ExecuteTime": { 23 | "end_time": "2020-08-18T11:55:19.281230Z", 24 | "start_time": "2020-08-18T11:55:19.278088Z" 25 | } 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "query_string = os.environ.get('QUERY_STRING', '')\n", 30 | "parameters = parse_qs(query_string)\n", 31 | "print(\"query string parameters:\", parameters)" 32 | ] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.7.3" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 4 56 | } 57 | -------------------------------------------------------------------------------- /tests/notebooks/cwd.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "with open('./file.txt') as f:\n", 10 | " print(f.read())" 11 | ] 12 | } 13 | ], 14 | "metadata": { 15 | "kernelspec": { 16 | "display_name": "Python 3", 17 | "language": "python", 18 | "name": "python" 19 | }, 20 | "language_info": { 21 | "codemirror_mode": { 22 | "name": "ipython", 23 | "version": 3 24 | }, 25 | "file_extension": ".py", 26 | "mimetype": "text/x-python", 27 | "name": "python", 28 | "nbconvert_exporter": "python", 29 | "pygments_lexer": "ipython3", 30 | "version": "3.6.4" 31 | } 32 | }, 33 | "nbformat": 4, 34 | "nbformat_minor": 2 35 | } 36 | -------------------------------------------------------------------------------- /tests/notebooks/file.txt: -------------------------------------------------------------------------------- 1 | check for the cwd 2 | -------------------------------------------------------------------------------- /tests/notebooks/many_iopub_messages.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": false, 8 | "deletable": true, 9 | "editable": true 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "for i in range(500):\n", 14 | " display(i)\n", 15 | "display(\"you\" + \" should see me\")" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "collapsed": true 23 | }, 24 | "outputs": [], 25 | "source": [] 26 | } 27 | ], 28 | "metadata": { 29 | "kernelspec": { 30 | "display_name": "Python 3", 31 | "language": "python", 32 | "name": "python3" 33 | }, 34 | "language_info": { 35 | "codemirror_mode": { 36 | "name": "ipython", 37 | "version": 3 38 | }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython3", 44 | "version": "3.5.5" 45 | } 46 | }, 47 | "nbformat": 4, 48 | "nbformat_minor": 4 49 | } 50 | -------------------------------------------------------------------------------- /tests/notebooks/no_kernelspec.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "print('Executing without a kernelspec')" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | "language_info": { 15 | "codemirror_mode": { 16 | "name": "ipython", 17 | "version": 3 18 | }, 19 | "file_extension": ".py", 20 | "mimetype": "text/x-python", 21 | "name": "python", 22 | "nbconvert_exporter": "python", 23 | "pygments_lexer": "ipython3", 24 | "version": "3.6.7" 25 | } 26 | }, 27 | "nbformat": 4, 28 | "nbformat_minor": 2 29 | } 30 | -------------------------------------------------------------------------------- /tests/notebooks/no_metadata.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "print('Executing without notebook metadata')" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | }, 15 | "nbformat": 4, 16 | "nbformat_minor": 2 17 | } 18 | -------------------------------------------------------------------------------- /tests/notebooks/non_existing_kernel.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "print('Executing with non-existing kernel')" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | "kernelspec": { 15 | "display_name": "Xeus Python 2", 16 | "language": "python", 17 | "name": "xpython2" 18 | }, 19 | "language_info": { 20 | "codemirror_mode": { 21 | "name": "ipython", 22 | "version": 3 23 | }, 24 | "file_extension": ".py", 25 | "mimetype": "text/x-python", 26 | "name": "python", 27 | "nbconvert_exporter": "python", 28 | "pygments_lexer": "ipython2", 29 | "version": "2.7.14" 30 | } 31 | }, 32 | "nbformat": 4, 33 | "nbformat_minor": 2 34 | } 35 | -------------------------------------------------------------------------------- /tests/notebooks/other_comms.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%matplotlib notebook\n", 10 | "import matplotlib.pyplot as plt" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "fig = plt.figure()\n", 20 | "fig.show()" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "print('This notebook executed')" 30 | ] 31 | } 32 | ], 33 | "metadata": { 34 | "kernelspec": { 35 | "display_name": "Python 3", 36 | "language": "python", 37 | "name": "python" 38 | }, 39 | "language_info": { 40 | "codemirror_mode": { 41 | "name": "ipython", 42 | "version": 3 43 | }, 44 | "file_extension": ".py", 45 | "mimetype": "text/x-python", 46 | "name": "python", 47 | "nbconvert_exporter": "python", 48 | "pygments_lexer": "ipython3", 49 | "version": "3.6.7" 50 | } 51 | }, 52 | "nbformat": 4, 53 | "nbformat_minor": 2 54 | } 55 | -------------------------------------------------------------------------------- /tests/notebooks/preheat/default_env_variables.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "7efe3e8f-4b8d-4fd3-99d1-b06d79b88ae2", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import time\n", 11 | "time.sleep(2)\n", 12 | "print('hello world')" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "782377cd-2bc0-4d0d-8b63-74dcb7e9d645", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import os\n", 23 | "os.getenv('FOO')" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3.10.5 ('voila')", 30 | "language": "python", 31 | "name": "python3" 32 | } 33 | }, 34 | "nbformat": 4, 35 | "nbformat_minor": 5 36 | } 37 | -------------------------------------------------------------------------------- /tests/notebooks/preheat/denylisted.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "7efe3e8f-4b8d-4fd3-99d1-b06d79b88ae2", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import time\n", 11 | "time.sleep(2)\n", 12 | "print('hello world')" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "782377cd-2bc0-4d0d-8b63-74dcb7e9d645", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import os\n", 23 | "os.getenv('foo')" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "0e689ec5-708c-4cac-98ba-02b00411e41d", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3 (ipykernel)", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.9.6" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 5 56 | } 57 | -------------------------------------------------------------------------------- /tests/notebooks/preheat/get_query_string.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "7efe3e8f-4b8d-4fd3-99d1-b06d79b88ae2", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from voila.utils import get_query_string\n", 11 | "print(get_query_string())" 12 | ] 13 | } 14 | ], 15 | "metadata": { 16 | "kernelspec": { 17 | "display_name": "Python 3 (ipykernel)", 18 | "language": "python", 19 | "name": "python3" 20 | }, 21 | "language_info": { 22 | "codemirror_mode": { 23 | "name": "ipython", 24 | "version": 3 25 | }, 26 | "file_extension": ".py", 27 | "mimetype": "text/x-python", 28 | "name": "python", 29 | "nbconvert_exporter": "python", 30 | "pygments_lexer": "ipython3", 31 | "version": "3.9.7" 32 | } 33 | }, 34 | "nbformat": 4, 35 | "nbformat_minor": 5 36 | } 37 | -------------------------------------------------------------------------------- /tests/notebooks/preheat/pre_heat.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "7efe3e8f-4b8d-4fd3-99d1-b06d79b88ae2", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import time\n", 11 | "time.sleep(2)\n", 12 | "print('hello world')" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "782377cd-2bc0-4d0d-8b63-74dcb7e9d645", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import os\n", 23 | "os.getenv('foo')" 24 | ] 25 | } 26 | ], 27 | "metadata": { 28 | "kernelspec": { 29 | "display_name": "Python 3 (ipykernel)", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.9.7" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 5 48 | } 49 | -------------------------------------------------------------------------------- /tests/notebooks/print.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "print('Hi ' + 'Voilà!')" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | "kernelspec": { 15 | "display_name": "Python 3", 16 | "language": "python", 17 | "name": "python" 18 | }, 19 | "language_info": { 20 | "codemirror_mode": { 21 | "name": "ipython", 22 | "version": 3 23 | }, 24 | "file_extension": ".py", 25 | "mimetype": "text/x-python", 26 | "name": "python", 27 | "nbconvert_exporter": "python", 28 | "pygments_lexer": "ipython3", 29 | "version": "3.6.4" 30 | } 31 | }, 32 | "nbformat": 4, 33 | "nbformat_minor": 2 34 | } 35 | -------------------------------------------------------------------------------- /tests/notebooks/print.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # text_representation: 5 | # extension: .py 6 | # format_name: light 7 | # format_version: '1.4' 8 | # jupytext_version: 1.2.1 9 | # kernelspec: 10 | # display_name: Python 3 11 | # language: python 12 | # name: python 13 | # --- 14 | 15 | print("Hi Voilà!") 16 | -------------------------------------------------------------------------------- /tests/notebooks/print.xcpp: -------------------------------------------------------------------------------- 1 | // --- 2 | // jupyter: 3 | // jupytext: 4 | // text_representation: 5 | // extension: .cpp 6 | // format_name: light 7 | // format_version: '1.4' 8 | // jupytext_version: 1.2.1 9 | // kernelspec: 10 | // display_name: C++11 11 | // language: C++11 12 | // name: xcpp11 13 | // --- 14 | 15 | 16 | // this is not a valid .cpp file, since it does not have a main() 17 | // however, we can ask voila to execute this by using the xeus-cling kernel 18 | // or relying on jupytext 19 | 20 | #include 21 | using namespace std; 22 | 23 | cout << "Hello Voilà, from c++"; 24 | -------------------------------------------------------------------------------- /tests/notebooks/print_cpp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "#include \n", 10 | "using namespace std;" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "cout << \"Hi Voilà, from c++\";" 20 | ] 21 | } 22 | ], 23 | "metadata": { 24 | "kernelspec": { 25 | "display_name": "C++11", 26 | "language": "C++11", 27 | "name": "xcpp11" 28 | }, 29 | "language_info": { 30 | "codemirror_mode": "text/x-c++src", 31 | "file_extension": ".cpp", 32 | "mimetype": "text/x-c++src", 33 | "name": "c++", 34 | "version": "-std=c++11" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 2 39 | } 40 | -------------------------------------------------------------------------------- /tests/notebooks/print_parameterized.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "tags": [ 8 | "parameters" 9 | ] 10 | }, 11 | "outputs": [], 12 | "source": [ 13 | "name = 'Voila'" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "print('Hi ' + name + '!')" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "Python 3", 29 | "language": "python", 30 | "name": "python" 31 | }, 32 | "language_info": { 33 | "codemirror_mode": { 34 | "name": "ipython", 35 | "version": 3 36 | }, 37 | "file_extension": ".py", 38 | "mimetype": "text/x-python", 39 | "name": "python", 40 | "nbconvert_exporter": "python", 41 | "pygments_lexer": "ipython3", 42 | "version": "3.7.5" 43 | } 44 | }, 45 | "nbformat": 4, 46 | "nbformat_minor": 5 47 | } 48 | -------------------------------------------------------------------------------- /tests/notebooks/skip-voila-cell.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "print('Hi ' + 'Voilà cell!')" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": { 16 | "tags": [ 17 | "skip-voila" 18 | ] 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "print('Hi non ' + 'Voilà cell!')" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "celltoolbar": "Tags", 28 | "kernelspec": { 29 | "display_name": "Python 3", 30 | "language": "python", 31 | "name": "python3" 32 | }, 33 | "language_info": { 34 | "codemirror_mode": { 35 | "name": "ipython", 36 | "version": 3 37 | }, 38 | "file_extension": ".py", 39 | "mimetype": "text/x-python", 40 | "name": "python", 41 | "nbconvert_exporter": "python", 42 | "pygments_lexer": "ipython3", 43 | "version": "3.7.3" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 4 48 | } 49 | -------------------------------------------------------------------------------- /tests/notebooks/sleep.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import time\n", 10 | "time.sleep(10)" 11 | ] 12 | } 13 | ], 14 | "metadata": { 15 | "kernelspec": { 16 | "display_name": "Python 3", 17 | "language": "python", 18 | "name": "python3" 19 | }, 20 | "language_info": { 21 | "codemirror_mode": { 22 | "name": "ipython", 23 | "version": 3 24 | }, 25 | "file_extension": ".py", 26 | "mimetype": "text/x-python", 27 | "name": "python", 28 | "nbconvert_exporter": "python", 29 | "pygments_lexer": "ipython3", 30 | "version": "3.8.2" 31 | } 32 | }, 33 | "nbformat": 4, 34 | "nbformat_minor": 2 35 | } 36 | -------------------------------------------------------------------------------- /tests/notebooks/sleep10seconds.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import time\n", 10 | "time.sleep(10)\n", 11 | "print(\"Hi\")" 12 | ] 13 | } 14 | ], 15 | "metadata": { 16 | "kernelspec": { 17 | "display_name": "Python 3", 18 | "language": "python", 19 | "name": "python3" 20 | }, 21 | "language_info": { 22 | "codemirror_mode": { 23 | "name": "ipython", 24 | "version": 3 25 | }, 26 | "file_extension": ".py", 27 | "mimetype": "text/x-python", 28 | "name": "python", 29 | "nbconvert_exporter": "python", 30 | "pygments_lexer": "ipython3", 31 | "version": "3.7.3" 32 | } 33 | }, 34 | "nbformat": 4, 35 | "nbformat_minor": 4 36 | } 37 | -------------------------------------------------------------------------------- /tests/notebooks/subdir/cwd_subdir.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "with open('..//file.txt') as f:\n", 10 | " print(f.read())" 11 | ] 12 | } 13 | ], 14 | "metadata": { 15 | "kernelspec": { 16 | "display_name": "Python 3", 17 | "language": "python", 18 | "name": "python" 19 | }, 20 | "language_info": { 21 | "codemirror_mode": { 22 | "name": "ipython", 23 | "version": 3 24 | }, 25 | "file_extension": ".py", 26 | "mimetype": "text/x-python", 27 | "name": "python", 28 | "nbconvert_exporter": "python", 29 | "pygments_lexer": "ipython3", 30 | "version": "3.6.4" 31 | } 32 | }, 33 | "nbformat": 4, 34 | "nbformat_minor": 2 35 | } 36 | -------------------------------------------------------------------------------- /tests/notebooks/syntax_error.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "this is a syntax error" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "print('This should ' + 'not be executed')" 19 | ] 20 | } 21 | ], 22 | "metadata": { 23 | "kernelspec": { 24 | "display_name": "Python 3", 25 | "language": "python", 26 | "name": "python3" 27 | }, 28 | "language_info": { 29 | "codemirror_mode": { 30 | "name": "ipython", 31 | "version": 3 32 | }, 33 | "file_extension": ".py", 34 | "mimetype": "text/x-python", 35 | "name": "python", 36 | "nbconvert_exporter": "python", 37 | "pygments_lexer": "ipython3", 38 | "version": "3.8.2" 39 | } 40 | }, 41 | "nbformat": 4, 42 | "nbformat_minor": 4 43 | } 44 | -------------------------------------------------------------------------------- /tests/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/tests/server/__init__.py -------------------------------------------------------------------------------- /tests/server/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from jupyter_server.serverapp import ServerApp 5 | from tornado import httpserver 6 | 7 | BASE_DIR = os.path.dirname(__file__) 8 | 9 | 10 | @pytest.fixture 11 | def jupyter_server_config(): 12 | return lambda app: None 13 | 14 | 15 | @pytest.fixture 16 | def jupyter_server_args_extra(): 17 | return [] 18 | 19 | 20 | @pytest.fixture 21 | def jupyter_server_args(notebook_directory, jupyter_server_args_extra): 22 | debug_args = ( 23 | ["--ServerApp.log_level=DEBUG"] 24 | if os.environ.get("VOILA_TEST_DEBUG", False) 25 | else [] 26 | ) 27 | default_args = ["--ServerApp.token="] 28 | return [notebook_directory, *jupyter_server_args_extra, *debug_args, *default_args] 29 | 30 | 31 | @pytest.fixture 32 | def jupyter_server_app(jupyter_server_args, jupyter_server_config): 33 | jupyter_server_app = ServerApp.instance() 34 | # we monkey patch 35 | old_listen = httpserver.HTTPServer.listen 36 | httpserver.HTTPServer.listen = lambda *x, **y: None 37 | # NOTE: in voila's conftest.py we call config after initialize 38 | jupyter_server_config(jupyter_server_app) 39 | jupyter_server_app.initialize(jupyter_server_args) 40 | yield jupyter_server_app 41 | httpserver.HTTPServer.listen = old_listen 42 | ServerApp.clear_instance() 43 | 44 | 45 | @pytest.fixture 46 | def app(jupyter_server_app): 47 | return jupyter_server_app.web_app 48 | -------------------------------------------------------------------------------- /tests/server/cwd_subdir_test.py: -------------------------------------------------------------------------------- 1 | # NOTE: this is a duplicate of ../app/cwd_subdir_test.py 2 | # we might want to find a better pattern of executing test for the app and extension 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def cwd_subdir_notebook_url(base_url): 8 | return base_url + "voila/render/subdir/cwd_subdir.ipynb" 9 | 10 | 11 | @pytest.fixture 12 | def voila_args(notebook_directory, voila_args_extra): 13 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 14 | 15 | 16 | async def test_hello_world(http_server_client, cwd_subdir_notebook_url): 17 | response = await http_server_client.fetch(cwd_subdir_notebook_url) 18 | html_text = response.body.decode("utf-8") 19 | assert "check for the cwd" in html_text 20 | -------------------------------------------------------------------------------- /tests/server/execute_cpp_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | TEST_XEUS_CLING = os.environ.get("VOILA_TEST_XEUS_CLING", "") == "1" 6 | 7 | 8 | @pytest.fixture 9 | def cpp_file_url(base_url): 10 | return base_url + "voila/render/print.xcpp" 11 | 12 | 13 | @pytest.fixture 14 | def jupyter_server_args_extra(): 15 | return ['--VoilaConfiguration.extension_language_mapping={".xcpp": "C++11"}'] 16 | 17 | 18 | @pytest.fixture 19 | def voila_args(notebook_directory, voila_args_extra): 20 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 21 | 22 | 23 | @pytest.mark.skipif( 24 | not TEST_XEUS_CLING, reason="opt in to avoid having to install xeus-cling" 25 | ) 26 | async def test_non_existing_kernel(http_server_client, cpp_file_url): 27 | response = await http_server_client.fetch(cpp_file_url) 28 | assert response.code == 200 29 | assert "Hello Voilà, from c++" in response.body.decode("utf-8") 30 | -------------------------------------------------------------------------------- /tests/server/execute_test.py: -------------------------------------------------------------------------------- 1 | # test basics of Voilà running a notebook 2 | 3 | 4 | async def test_hello_world(http_server_client, print_notebook_url): 5 | response = await http_server_client.fetch(print_notebook_url) 6 | assert response.code == 200 7 | html_text = response.body.decode("utf-8") 8 | assert "Hi Voilà" in html_text 9 | assert "print(" not in html_text, "by default the source code should be stripped" 10 | assert ( 11 | "test_template.css" not in html_text 12 | ), "test_template should not be the default" 13 | -------------------------------------------------------------------------------- /tests/server/nbextensions_test.py: -------------------------------------------------------------------------------- 1 | # tests programmatic config of template system 2 | import os 3 | 4 | import pytest 5 | 6 | BASE_DIR = os.path.dirname(__file__) 7 | 8 | 9 | @pytest.fixture 10 | def jupyter_server_config(): 11 | def config(app): 12 | pass 13 | 14 | os.environ["JUPYTER_CONFIG_DIR"] = os.path.join( 15 | BASE_DIR, "..", "configs", "general" 16 | ) 17 | yield config 18 | del os.environ["JUPYTER_CONFIG_DIR"] 19 | 20 | 21 | @pytest.mark.xfail(reason="needs to be fixed") 22 | async def test_lists_extension(http_server_client, print_notebook_url): 23 | response = await http_server_client.fetch(print_notebook_url) 24 | assert response.code == 200 25 | html_text = response.body.decode("utf-8") 26 | assert "Hi Voilà" in html_text 27 | assert "ipytest/extension.js" in html_text 28 | -------------------------------------------------------------------------------- /tests/server/no_strip_sources_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | BASE_DIR = os.path.dirname(__file__) 6 | 7 | 8 | @pytest.fixture 9 | def jupyter_server_args_extra(): 10 | return ["--VoilaConfiguration.strip_sources=False"] 11 | 12 | 13 | async def test_hello_world(http_server_client, print_notebook_url): 14 | response = await http_server_client.fetch(print_notebook_url) 15 | assert response.code == 200 16 | html_text = response.body.decode("utf-8") 17 | assert "Hi Voilà" in html_text 18 | assert "print" in html_text, "the source code should *NOT* be stripped" 19 | -------------------------------------------------------------------------------- /tests/server/show_traceback_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(params=[True, False]) 5 | def show_tracebacks(request): 6 | return request.param 7 | 8 | 9 | @pytest.fixture 10 | def jupyter_server_args_extra(show_tracebacks): 11 | return [f"--VoilaConfiguration.show_tracebacks={show_tracebacks}"] 12 | 13 | 14 | async def test_syntax_error( 15 | http_server_client, syntax_error_notebook_url, show_tracebacks 16 | ): 17 | response = await http_server_client.fetch(syntax_error_notebook_url) 18 | assert response.code == 200 19 | output = response.body.decode("utf-8") 20 | if show_tracebacks: 21 | assert "this is a syntax error" in output, 'should show the "code"' 22 | assert ( 23 | "SyntaxError" in output and "invalid syntax" in output 24 | ), "should show the error" 25 | else: 26 | assert "There was an error when executing cell" in output 27 | assert "This should not be executed" not in output 28 | -------------------------------------------------------------------------------- /tests/server/static_files_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import tornado 3 | 4 | from voila.static_file_handler import TemplateStaticFileHandler 5 | 6 | 7 | async def test_static_file_absolute_path(app, base_url, http_server_client): 8 | response_lab = await http_server_client.fetch( 9 | f"{base_url}voila/templates/lab/static/voila.js" 10 | ) 11 | assert response_lab.code == 200 12 | abspath = TemplateStaticFileHandler.get_absolute_path(None, "lab/static/voila.js") 13 | with open(abspath, encoding="utf-8", errors="replace") as f: 14 | content = f.read() 15 | assert response_lab.body.decode("utf-8") == content 16 | 17 | 18 | async def test_static_file_not_found(app, base_url, http_server_client): 19 | with pytest.raises(tornado.httpclient.HTTPClientError, match="HTTP 404.*"): 20 | await http_server_client.fetch( 21 | f"{base_url}voila/templates/lab/static/doesnotexist.js" 22 | ) 23 | 24 | 25 | async def test_static_file_availability_default(app, base_url, http_server_client): 26 | response_lab = await http_server_client.fetch( 27 | f"{base_url}voila/templates/lab/static/voila.js" 28 | ) 29 | response_default = await http_server_client.fetch( 30 | f"{base_url}voila/static/voila.js" 31 | ) 32 | assert response_lab.code == 200 33 | assert response_default.code == 200 34 | assert response_lab.body.decode("utf-8") == response_default.body.decode("utf-8") 35 | 36 | 37 | async def test_static_file_override(app, base_url, http_server_client): 38 | response_lab = await http_server_client.fetch( 39 | f"{base_url}voila/templates/lab/static/voila.js" 40 | ) 41 | response_test_template = await http_server_client.fetch( 42 | f"{base_url}voila/templates/test_template/static/voila.js" 43 | ) 44 | assert response_lab.code == 200 45 | assert response_test_template.code == 200 46 | assert response_lab.body.decode("utf-8") != response_test_template.body.decode( 47 | "utf-8" 48 | ) 49 | 50 | 51 | async def test_static_file_other_template(app, base_url, http_server_client): 52 | response = await http_server_client.fetch( 53 | f"{base_url}voila/templates/test_template/static/only-in-test-template.js" 54 | ) 55 | assert response.code == 200 56 | assert response.body.decode("utf-8") == "", "empty file expected" 57 | -------------------------------------------------------------------------------- /tests/server/tree_test.py: -------------------------------------------------------------------------------- 1 | # test tree rendering 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def voila_args(notebook_directory, voila_args_extra): 7 | return ["--VoilaTest.root_dir=%r" % notebook_directory, *voila_args_extra] 8 | 9 | 10 | @pytest.fixture 11 | def jupyter_server_args_extra(): 12 | return [ 13 | '--VoilaConfiguration.extension_language_mapping={".xcpp": "C++11"}', 14 | "--VoilaConfiguration.classic_tree=true", 15 | ] 16 | 17 | 18 | async def test_tree(http_server_client, base_url): 19 | response = await http_server_client.fetch(base_url + "voila/tree") 20 | assert response.code == 200 21 | text = response.body.decode("utf-8") 22 | assert "print.ipynb" in text, "tree handler should render ipynb files" 23 | assert ( 24 | "print.xcpp" in text 25 | ), "tree handler should render xcpp files (due to extension_language_mapping)" 26 | assert ( 27 | "print.py" not in text 28 | ), "tree handler should not render .py files (due to extension_language_mapping)" 29 | -------------------------------------------------------------------------------- /tests/skip_template/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup 4 | 5 | data_files = [] 6 | for dirpath, _dirnames, filenames in os.walk("share/jupyter/voila/templates"): 7 | if filenames: 8 | data_files.append( 9 | (dirpath, [os.path.join(dirpath, filename) for filename in filenames]) 10 | ) 11 | 12 | 13 | setup( 14 | name="skip_template", 15 | version="0.0.1", 16 | description="Test template for Voilà", 17 | data_files=data_files, 18 | include_package_data=True, 19 | author="Voilà Development team", 20 | author_email="jupyter@googlegroups.com", 21 | ) 22 | -------------------------------------------------------------------------------- /tests/skip_template/share/jupyter/voila/templates/skip_template/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_template": "classic", 3 | "preprocessors": { 4 | "100-remove-skip-voila-cells": { 5 | "type": "nbconvert.preprocessors.tagremove.TagRemovePreprocessor", 6 | "remove_cell_tags": ["skip-voila"], 7 | "enabled": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/template_prefixes/loader_test.py: -------------------------------------------------------------------------------- 1 | """Tests loading template of jinja2 templates""" 2 | 3 | import os 4 | 5 | from jinja2 import Environment, FileSystemLoader 6 | 7 | from voila.paths import collect_paths 8 | 9 | HERE = os.path.dirname(__file__) 10 | 11 | ROOT_DIRS = [os.path.join(HERE, "user"), os.path.join(HERE, "system")] 12 | 13 | 14 | def test_loader_default_nbconvert(): 15 | paths = collect_paths(["nbconvert"], "default", root_dirs=ROOT_DIRS) 16 | loader = FileSystemLoader(paths) 17 | env = Environment(loader=loader) 18 | template = env.get_template("index.tpl") 19 | output = template.render() 20 | assert "this is block base:nested in nbconvert/default/index.tpl" in output 21 | 22 | 23 | def test_loader_foo(): 24 | paths = collect_paths(["voila", "nbconvert"], "foo", root_dirs=ROOT_DIRS) 25 | loader = FileSystemLoader(paths) 26 | env = Environment(loader=loader) 27 | template = env.get_template("index.tpl") 28 | output = template.render() 29 | assert "this is block base:nested in voila/default/index.tpl" in output 30 | assert "this is block base:nested in voila/foo/index.tpl" in output 31 | assert "this is block base:nested in nbconvert/foo/index.tpl" in output 32 | assert "this is block base:nested in nbconvert/default/index.tpl" not in output 33 | 34 | 35 | def test_loader_bar_voila(): 36 | paths = collect_paths(["voila", "nbconvert"], "bar", root_dirs=ROOT_DIRS) 37 | loader = FileSystemLoader(paths) 38 | env = Environment(loader=loader) 39 | template = env.get_template("index.tpl") 40 | output = template.render() 41 | assert "this is block base in nbconvert/bar/index.tpl" in output 42 | assert "this is block base in nbconvert/default/index.tpl" in output 43 | assert "this is block base:nested in voila/default/index.tpl" in output 44 | assert "this is block base:nested2 in nbconvert/default/index.tpl" in output 45 | assert "this is block common in nbconvert/bar/parent.tpl" in output 46 | 47 | 48 | def test_loader_bar_nbconvert(): 49 | paths = collect_paths(["nbconvert"], "bar", root_dirs=ROOT_DIRS) 50 | loader = FileSystemLoader(paths) 51 | env = Environment(loader=loader) 52 | template = env.get_template("index.tpl") 53 | output = template.render() 54 | assert "this is block base in nbconvert/bar/index.tpl" in output 55 | assert "this is block base in nbconvert/default/index.tpl" in output 56 | assert "this is block base:nested in nbconvert/default/index.tpl" in output 57 | assert "this is block base:nested2 in nbconvert/default/index.tpl" in output 58 | assert "this is block common in nbconvert/bar/parent.tpl" in output 59 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/nbconvert/templates/bar/index.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'default/index.tpl' -%} 2 | {% block base %} 3 | this is block base in nbconvert/bar/index.tpl 4 | {{ super() }} 5 | {% block nested %} 6 | {{ super() }} 7 | {% endblock %} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/nbconvert/templates/bar/parent.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'default/parent.tpl' -%} 2 | {% block common %} 3 | this is block common in nbconvert/bar/parent.tpl 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/nbconvert/templates/default/index.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'parent.tpl' -%} 2 | 3 | {% block resources %} 4 | for nbconvert we may want to inline 5 | {% endblock resources %} 6 | 7 | 8 | {% block user_override %} 9 | block for a user to override 10 | {% endblock user_override %} 11 | 12 | 13 | {% block base %} 14 | this is block base in nbconvert/default/index.tpl 15 | {% block nested %} 16 | this is block base:nested in nbconvert/default/index.tpl 17 | {% endblock %} 18 | 19 | {% block nested2 %} 20 | this is block base:nested2 in nbconvert/default/index.tpl 21 | {% endblock %} 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/nbconvert/templates/default/parent.tpl: -------------------------------------------------------------------------------- 1 | {% block base %} 2 | {% endblock %} 3 | {% block common %} 4 | this is block common in nbconvert/default/parent.tpl 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/nbconvert/templates/foo/conf.json: -------------------------------------------------------------------------------- 1 | { "base_template": "default" } 2 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/nbconvert/templates/foo/index.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'default/index.tpl' -%} 2 | {% block nested %} 3 | this is block base:nested in nbconvert/foo/index.tpl 4 | {{ super() }} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/voila/templates/default/index.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'nbconvert/templates/default/index.tpl' -%} 2 | 3 | {% block resources %} 4 | for voila we want serve the files 5 | Note that all template based on the default template will get this block! 6 | Even when they are not a voila specific template (e.g. nbconvert/bar) 7 | {% endblock resources %} 8 | 9 | {% block nested %} 10 | this is block base:nested in voila/default/index.tpl 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /tests/template_prefixes/system/voila/templates/foo/index.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'nbconvert/templates/foo/index.tpl' -%} 2 | {% block nested %} 3 | this is block base:nested in voila/foo/index.tpl 4 | {{ super() }} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /tests/test_template/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup 4 | 5 | data_files = [] 6 | for dirpath, _dirnames, filenames in os.walk("share/jupyter/voila/templates"): 7 | if filenames: 8 | data_files.append( 9 | (dirpath, [os.path.join(dirpath, filename) for filename in filenames]) 10 | ) 11 | 12 | 13 | setup( 14 | name="test_template", 15 | version="0.0.1", 16 | description="Test template for Voilà", 17 | data_files=data_files, 18 | include_package_data=True, 19 | author="Voilà Development team", 20 | author_email="jupyter@googlegroups.com", 21 | ) 22 | -------------------------------------------------------------------------------- /tests/test_template/share/jupyter/voila/templates/test_template/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "traitlet_configuration": { 3 | "resources": { 4 | "test_template": { 5 | "test_resource": "test resource from config file" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/test_template/share/jupyter/voila/templates/test_template/index.html.j2: -------------------------------------------------------------------------------- 1 | Hi Voilà! 2 | This is a test template, obviously 3 | 4 | 5 | 6 | Print value of extra resource for test template: 7 | {{resources.test_template.test_resource | default('default value', true)}} 8 | 9 | List extensions: 10 | {% for ext in resources.labextensions -%} 11 | "{{resources.base_url}}voila/labextensions/{{ ext }}.js", 12 | {% endfor %} 13 | -------------------------------------------------------------------------------- /tests/test_template/share/jupyter/voila/templates/test_template/static/only-in-test-template.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/tests/test_template/share/jupyter/voila/templates/test_template/static/only-in-test-template.js -------------------------------------------------------------------------------- /tests/test_template/share/jupyter/voila/templates/test_template/static/voila.js: -------------------------------------------------------------------------------- 1 | // this file overrides the default voila.js 2 | -------------------------------------------------------------------------------- /tests/test_template/share/jupyter/voila/templates/test_template/voila.tpl: -------------------------------------------------------------------------------- 1 | Hi Voilà! 2 | This is a test template, obviously 3 | 4 | 5 | 6 | 7 | List extensions: 8 | {% for ext in resources.labextensions -%} 9 | "{{resources.base_url}}voila/labextensions/{{ ext }}.js", 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /tests/utils_test.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Set 2 | from voila.utils import filter_extension 3 | 4 | 5 | federated_extensions = [{"name": "foo"}, {"name": "bar"}, {"name": "foo-bar"}] 6 | 7 | 8 | def list_to_set(inp: List[Dict]) -> Set: 9 | return set([x["name"] for x in inp]) 10 | 11 | 12 | def test_filter_extension(): 13 | extensions = filter_extension(federated_extensions=federated_extensions) 14 | assert list_to_set(extensions) == list_to_set(federated_extensions) 15 | 16 | 17 | def test_filter_extension_with_disabled(): 18 | disabled_extensions = ["bar"] 19 | extensions = filter_extension( 20 | federated_extensions=federated_extensions, 21 | disabled_extensions=disabled_extensions, 22 | ) 23 | assert list_to_set(extensions) == set(["foo", "foo-bar"]) 24 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfigbase", 3 | "include": ["packages/**/*", "ui-tests"], 4 | "compilerOptions": { 5 | "types": ["jest"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfigbase.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "strict": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "target": "es2017", 20 | "types": [] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ui-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voila-ui-tests", 3 | "version": "1.0.0", 4 | "description": "Voila UI Tests", 5 | "private": true, 6 | "scripts": { 7 | "start": "voila ../notebooks --no-browser --show_tracebacks True", 8 | "start:progressive_rendering": "voila ../notebooks --no-browser --show_tracebacks True --progressive_rendering=true", 9 | "start:detached": "yarn run start&", 10 | "test": "npx playwright test", 11 | "test:debug": "PWDEBUG=1 playwright test", 12 | "test:report": "http-server ./playwright-report -a localhost -o", 13 | "test:update": "npx playwright test --update-snapshots", 14 | "test:update:progressive_rendering": "PROGRESSIVE_RENDERING=true && npx playwright test --update-snapshots" 15 | }, 16 | "author": "Project Jupyter", 17 | "license": "BSD-3-Clause", 18 | "dependencies": { 19 | "@jupyterlab/galata": "^5.2.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ui-tests/playwright.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('@jupyterlab/galata/lib/playwright-config'); 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | timeout: 240000, 6 | reporter: [ 7 | [process.env.CI ? 'dot' : 'list'], 8 | [ 9 | '@jupyterlab/galata/lib/benchmarkReporter', 10 | { outputFile: 'voila-benchmark.json' } 11 | ], 12 | ['html'] 13 | ], 14 | use: { 15 | baseURL: 'http://localhost:8866', 16 | video: 'retain-on-failure' 17 | }, 18 | // Try one retry as some tests are flaky 19 | retries: 1, 20 | expect: { 21 | toHaveScreenshot: { 22 | maxDiffPixelRatio: 0.05 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /ui-tests/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { TestInfo } from '@playwright/test'; 2 | import { benchmark } from '@jupyterlab/galata'; 3 | import { performance } from 'perf_hooks'; 4 | 5 | export async function timeit(fn: () => Promise): Promise { 6 | const start = performance.now(); 7 | await fn(); 8 | const end = performance.now(); 9 | return Number(end - start).toFixed(1); 10 | } 11 | 12 | export function average(arr: any[]): string { 13 | const aver = 14 | arr.reduce((p, c) => parseFloat(p) + parseFloat(c), 0) / arr.length; 15 | return aver.toFixed(3); 16 | } 17 | 18 | export async function addBenchmarkToTest( 19 | notebookName: string, 20 | testFunction: () => Promise, 21 | testInfo: TestInfo, 22 | browserName: string, 23 | nSamples = 1 24 | ): Promise { 25 | const testTimeArray = []; 26 | const attachmentCommon = { 27 | nSamples: nSamples, 28 | browser: browserName, 29 | file: `${notebookName}.ipynb`, 30 | project: testInfo.project.name 31 | }; 32 | for (let idx = 0; idx < nSamples; idx++) { 33 | const testTime = await timeit(testFunction); 34 | testTimeArray.push(testTime); 35 | testInfo.attachments.push( 36 | benchmark.addAttachment({ 37 | ...attachmentCommon, 38 | test: 'Render', 39 | time: average(testTimeArray) 40 | }) 41 | ); 42 | await new Promise((r) => setTimeout(r, 500)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/404-classic-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/404-classic-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/404-dark-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/404-dark-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/404-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/404-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/basics-classic-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/basics-classic-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/basics-dark-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/basics-dark-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/basics-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/basics-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/basics-miami-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/basics-miami-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/bokeh-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/bokeh-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/bqplot-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/bqplot-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/gridspecLayout-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/gridspecLayout-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/interactive-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/interactive-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/ipympl-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/ipympl-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/mimerenderers-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/mimerenderers-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/multiple-widgets-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/multiple-widgets-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/query-strings-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/query-strings-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/reveal-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/reveal-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/voila-tree-classic-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/voila-tree-classic-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/voila-tree-dark-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/voila-tree-dark-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/voila-tree-light-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/voila-tree-light-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/voila-tree-miami-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/voila-tree-miami-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/voila.test.ts-snapshots/yaml-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/ui-tests/tests/voila.test.ts-snapshots/yaml-linux.png -------------------------------------------------------------------------------- /voila-basics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/voila-basics.gif -------------------------------------------------------------------------------- /voila-bqplot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/voila-bqplot.gif -------------------------------------------------------------------------------- /voila-cling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/voila-cling.gif -------------------------------------------------------------------------------- /voila-sources.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voila-dashboards/voila/14d44a1a8b03c0ebeb8d758390d611c71383b9ee/voila-sources.gif -------------------------------------------------------------------------------- /voila/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Copyright (c) 2018, Voilà Contributors # 3 | # Copyright (c) 2018, QuantStack # 4 | # # 5 | # Distributed under the terms of the BSD 3-Clause License. # 6 | # # 7 | # The full license is in the file LICENSE, distributed with this software. # 8 | ############################################################################# 9 | 10 | from ._version import __version__ # noqa 11 | from .server_extension import _load_jupyter_server_extension # noqa 12 | from .server_extension import load_jupyter_server_extension # noqa 13 | import warnings 14 | 15 | warnings.filterwarnings("default", category=DeprecationWarning, module="traitlets") 16 | 17 | 18 | def _jupyter_nbextension_paths(): 19 | return [ 20 | { 21 | "section": "notebook", 22 | "src": "static", 23 | "dest": "voila", 24 | "require": "voila/extension", 25 | } 26 | ] 27 | 28 | 29 | def _jupyter_labextension_paths(): 30 | return [ 31 | { 32 | "src": "labextensions/jupyterlab-preview", 33 | "dest": "@voila-dashboards/jupyterlab-preview", 34 | }, 35 | { 36 | "src": "labextensions/widgets-manager7", 37 | "dest": "../voila/labextensions/@voila-dashboards/widgets-manager7", 38 | }, 39 | { 40 | "src": "labextensions/widgets-manager8", 41 | "dest": "../voila/labextensions/@voila-dashboards/widgets-manager8", 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /voila/__main__.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Copyright (c) 2018, Voilà Contributors # 3 | # Copyright (c) 2018, QuantStack # 4 | # # 5 | # Distributed under the terms of the BSD 3-Clause License. # 6 | # # 7 | # The full license is in the file LICENSE, distributed with this software. # 8 | ############################################################################# 9 | 10 | if __name__ == "__main__": 11 | from voila.app import main 12 | 13 | main() 14 | -------------------------------------------------------------------------------- /voila/_version.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Copyright (c) 2018, Voilà Contributors # 3 | # Copyright (c) 2018, QuantStack # 4 | # # 5 | # Distributed under the terms of the BSD 3-Clause License. # 6 | # # 7 | # The full license is in the file LICENSE, distributed with this software. # 8 | ############################################################################# 9 | 10 | # Copyright (c) Jupyter Development Team. 11 | # Distributed under the terms of the Modified BSD License. 12 | 13 | import re 14 | from collections import namedtuple 15 | 16 | # Use "hatch version xx.yy.zz" to handle version changes 17 | __version__ = "0.5.8" 18 | 19 | # PEP440 version parser 20 | _version_regex = re.compile( 21 | r""" 22 | (?P\d+) 23 | \. 24 | (?P\d+) 25 | \. 26 | (?P\d+) 27 | (?P((a|b|rc|\.dev)))? 28 | (?P\d+)? 29 | """, 30 | re.VERBOSE, 31 | ) 32 | 33 | _version_fields = _version_regex.match(__version__).groupdict() # type:ignore 34 | 35 | VersionInfo = namedtuple( 36 | "VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"] 37 | ) 38 | 39 | version_info = VersionInfo( 40 | *[ 41 | int(_version_fields["major"]), 42 | int(_version_fields["minor"]), 43 | int(_version_fields["micro"]), 44 | _version_fields["releaselevel"] or "", 45 | _version_fields["serial"] or "", 46 | ] 47 | ) 48 | -------------------------------------------------------------------------------- /voila/request_info_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Dict 3 | 4 | from tornado.websocket import WebSocketHandler 5 | 6 | 7 | class RequestInfoSocketHandler(WebSocketHandler): 8 | """A websocket handler used to provide the request info 9 | associated with kernel ids in preheat kernel mode. 10 | 11 | Class variables 12 | --------------- 13 | - _waiters : A dictionary which holds the `websocket` connection 14 | associated with the kernel id. 15 | 16 | - cache : A dictionary which holds the request info associated 17 | with the kernel id. 18 | """ 19 | 20 | _waiters = {} 21 | _cache = {} 22 | 23 | def open(self, kernel_id: str) -> None: 24 | """Create a new websocket connection, this connection is 25 | identified by the kernel id. 26 | 27 | Args: 28 | kernel_id (str): Kernel id used by the notebook when it opens 29 | the websocket connection. 30 | """ 31 | RequestInfoSocketHandler._waiters[kernel_id] = self 32 | if kernel_id in self._cache: 33 | self.write_message(self._cache[kernel_id]) 34 | 35 | def on_close(self) -> None: 36 | for k_id, waiter in RequestInfoSocketHandler._waiters.items(): 37 | if waiter == self: 38 | break 39 | del RequestInfoSocketHandler._waiters[k_id] 40 | 41 | @classmethod 42 | def send_updates(cls: "RequestInfoSocketHandler", msg: Dict) -> None: 43 | """Class method used to dispatch the request info to the waiting 44 | notebook. This method is called in `VoilaHandler` when the request 45 | info becomes available. 46 | If this method is called before the opening of websocket connection, 47 | `msg` is stored in `_cache` and the message will be dispatched when 48 | a notebook with corresponding kernel id is connected. 49 | 50 | Args: 51 | - msg (Dict): this dictionary contains the `kernel_id` to identify 52 | the waiting notebook and `payload` is the request info. 53 | """ 54 | kernel_id = msg["kernel_id"] 55 | payload = msg["payload"] 56 | waiter = cls._waiters.get(kernel_id, None) 57 | if waiter is not None: 58 | try: 59 | waiter.write_message(payload) 60 | except Exception: 61 | logging.error("Error sending message", exc_info=True) 62 | else: 63 | cls._cache[kernel_id] = payload 64 | -------------------------------------------------------------------------------- /voila/shutdown_kernel_handler.py: -------------------------------------------------------------------------------- 1 | import tornado 2 | from jupyter_server.base.handlers import APIHandler 3 | from jupyter_core.utils import ensure_async 4 | 5 | 6 | class VoilaShutdownKernelHandler(APIHandler): 7 | """Handler to shut down kernel on page's `beforeunload` event.""" 8 | 9 | @tornado.web.authenticated 10 | async def post(self, kernel_id): 11 | await ensure_async(self.kernel_manager.shutdown_kernel(kernel_id)) 12 | self.set_status(204) 13 | self.finish() 14 | -------------------------------------------------------------------------------- /voila/static/extension.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, Voilà Contributors 3 | * Copyright (c) 2018, QuantStack 4 | * 5 | * Distributed under the terms of the BSD 3-Clause License. 6 | * 7 | * The full license is in the file LICENSE, distributed with this software. 8 | */ 9 | 10 | define(['jquery', 'base/js/namespace'], function($, Jupyter) { 11 | "use strict"; 12 | var open_voila = function() { 13 | Jupyter.notebook.save_notebook().then(function () { 14 | let voila_url = Jupyter.notebook.base_url + "voila/render/" + Jupyter.notebook.notebook_path; 15 | window.open(voila_url) 16 | }); 17 | } 18 | var load_ipython_extension = function() { 19 | Jupyter.toolbar.add_buttons_group([{ 20 | id : 'toggle_codecells', 21 | label : 'Voilà', 22 | icon : 'fa-desktop', 23 | callback : open_voila 24 | }]); 25 | }; 26 | return { 27 | load_ipython_extension : load_ipython_extension 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /voila/tornado/handler.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Copyright (c) 2018, Voilà Contributors # 3 | # Copyright (c) 2018, QuantStack # 4 | # # 5 | # Distributed under the terms of the BSD 3-Clause License. # 6 | # # 7 | # The full license is in the file LICENSE, distributed with this software. # 8 | ############################################################################# 9 | import tornado.web 10 | 11 | from ..handler import VoilaHandler 12 | 13 | 14 | class TornadoVoilaHandler(VoilaHandler): 15 | @tornado.web.authenticated 16 | async def get(self, path=None): 17 | gen = self.get_generator(path=path) 18 | async for html in gen: 19 | self.write(html) 20 | self.flush() 21 | -------------------------------------------------------------------------------- /voila/tornado/kernel_websocket_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Any, Dict, Optional, Union 3 | 4 | try: 5 | from jupyter_server.services.kernels.websocket import ( 6 | KernelWebsocketHandler as WebsocketHandler, 7 | ) 8 | except ImportError: 9 | from jupyter_server.services.kernels.handlers import ( 10 | ZMQChannelsHandler as WebsocketHandler, 11 | ) 12 | 13 | 14 | def read_header_from_binary_message(ws_msg: bytes) -> Optional[Dict]: 15 | """Read message header using the v1 protocol.""" 16 | 17 | offset_number = int.from_bytes(ws_msg[:8], "little") 18 | offsets = [ 19 | int.from_bytes(ws_msg[8 * (i + 1) : 8 * (i + 2)], "little") 20 | for i in range(offset_number) 21 | ] 22 | try: 23 | header = ws_msg[offsets[1] : offsets[2]].decode("utf-8") 24 | return json.loads(header) 25 | except Exception: 26 | return 27 | 28 | 29 | class VoilaKernelWebsocketHandler(WebsocketHandler): 30 | def write_message( 31 | self, message: Union[bytes, Dict[str, Any]], binary: bool = False 32 | ): 33 | if isinstance(message, bytes): 34 | header = read_header_from_binary_message(message) 35 | elif isinstance(message, dict): 36 | header = message.get("header", None) 37 | else: 38 | header = None 39 | 40 | if header and header.get("msg_type", None) == "execute_input": 41 | return # Ignore execute_input message 42 | 43 | return super().write_message(message, binary) 44 | -------------------------------------------------------------------------------- /voila/treehandler.py: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Copyright (c) 2018, Voilà Contributors # 3 | # Copyright (c) 2018, QuantStack # 4 | # # 5 | # Distributed under the terms of the BSD 3-Clause License. # 6 | # # 7 | # The full license is in the file LICENSE, distributed with this software. # 8 | ############################################################################# 9 | from jupyter_server.utils import url_escape, url_path_join 10 | 11 | from .handler import BaseVoilaHandler 12 | 13 | 14 | class VoilaTreeHandler(BaseVoilaHandler): 15 | def initialize(self, **kwargs): 16 | super().initialize(**kwargs) 17 | self.allowed_extensions = [ 18 | *list(self.voila_configuration.extension_language_mapping.keys()), 19 | ".ipynb", 20 | ] 21 | 22 | def validate_theme(self, theme: str, classic_tree: bool) -> str: 23 | """Check the compatibility between the requested theme and the tree page""" 24 | if classic_tree: 25 | supported_themes = ["dark", "light", "JupyterLab Dark", "JupyterLab Light"] 26 | if theme not in supported_themes: 27 | self.log.warn( 28 | "Custom JupyterLab theme is not supported in the classic tree, failback to the light theme!" 29 | ) 30 | return "light" 31 | else: 32 | if theme == "JupyterLab Dark": 33 | return "dark" 34 | if theme == "JupyterLab Light": 35 | return "light" 36 | return theme 37 | 38 | def get_template(self, name): 39 | """Return the jinja template object for a given name""" 40 | return self.settings["voila_jinja2_env"].get_template(name) 41 | 42 | def generate_breadcrumbs(self, path): 43 | breadcrumbs = [(url_path_join(self.base_url, "voila/tree"), "")] 44 | parts = path.split("/") 45 | for i in range(len(parts)): 46 | if parts[i]: 47 | link = url_path_join( 48 | self.base_url, 49 | "voila/tree", 50 | url_escape(url_path_join(*parts[: i + 1])), 51 | ) 52 | breadcrumbs.append((link, parts[i])) 53 | return breadcrumbs 54 | 55 | def generate_page_title(self, path): 56 | parts = path.split("/") 57 | if len(parts) > 3: # not too many parts 58 | parts = parts[-2:] 59 | page_title = url_path_join(*parts) 60 | if page_title: 61 | return page_title + "/" 62 | else: 63 | return "Voilà Home" 64 | -------------------------------------------------------------------------------- /voila/voila_identity_provider.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from jupyter_server.auth.identity import IdentityProvider 3 | from jupyter_server.auth.login import LoginFormHandler 4 | 5 | 6 | class VoilaLoginHandler(LoginFormHandler): 7 | def static_url( 8 | self, path: str, include_host: Optional[bool] = None, **kwargs: Any 9 | ) -> str: 10 | settings = { 11 | "static_url_prefix": "voila/static/", 12 | "static_path": None, 13 | } 14 | return settings.get("static_url_prefix", "/static/") + path 15 | 16 | 17 | class VoilaIdentityProvider(IdentityProvider): 18 | @property 19 | def auth_enabled(self) -> bool: 20 | """Return whether any auth is enabled""" 21 | return bool(self.token) 22 | --------------------------------------------------------------------------------