pax_global_header00006660000000000000000000000064145261045030014512gustar00rootroot0000000000000052 comment=bdf9c274a7cc84250013c6fb3cdb09d9cae015e1 jupyterlab_server-2.25.2/000077500000000000000000000000001452610450300153515ustar00rootroot00000000000000jupyterlab_server-2.25.2/.git-blame-ignore-revs000066400000000000000000000002011452610450300214420ustar00rootroot00000000000000# Initial pre-commit reformat: https://github.com/jupyterlab/jupyterlab_server/pull/257 1b0e2577b123e3cf34e6e9490e0604ad4c8ba596 jupyterlab_server-2.25.2/.github/000077500000000000000000000000001452610450300167115ustar00rootroot00000000000000jupyterlab_server-2.25.2/.github/codeql/000077500000000000000000000000001452610450300201605ustar00rootroot00000000000000jupyterlab_server-2.25.2/.github/codeql/codeql-config.yml000066400000000000000000000001321452610450300234110ustar00rootroot00000000000000name: "My CodeQL config" queries: - uses: security-and-quality paths-ignore: - docs jupyterlab_server-2.25.2/.github/dependabot.yml000066400000000000000000000003151452610450300215400ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" jupyterlab_server-2.25.2/.github/workflows/000077500000000000000000000000001452610450300207465ustar00rootroot00000000000000jupyterlab_server-2.25.2/.github/workflows/codeql-analysis.yml000066400000000000000000000047231452610450300245670ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. name: "CodeQL" on: push: branches: [main] pull_request: # The branches below must be a subset of the branches above branches: [main] schedule: - cron: "0 8 * * 3" permissions: security-events: write jobs: analyze: name: Analyze runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] language: ["python"] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - name: Checkout repository uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 jupyterlab_server-2.25.2/.github/workflows/downstream.yml000066400000000000000000000010561452610450300236560ustar00rootroot00000000000000name: Test downstream projects on: push: branches: ["main"] pull_request: jobs: downstream: runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Test jupyterlab uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: jupyterlab test_command: "python -m jupyterlab.browser_check --no-browser-test" jupyterlab_server-2.25.2/.github/workflows/enforce-label.yml000066400000000000000000000005001452610450300241620ustar00rootroot00000000000000name: Enforce PR label on: pull_request: types: [labeled, unlabeled, opened, edited, synchronize] jobs: enforce-label: runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: enforce-triage-label uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 jupyterlab_server-2.25.2/.github/workflows/fix-license-header.yml000066400000000000000000000032761452610450300251350ustar00rootroot00000000000000name: Fix License Headers on: pull_request_target: jobs: header-license-fix: runs-on: ubuntu-20.04 permissions: contents: write pull-requests: write steps: - name: Checkout uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Configure git to use https run: git config --global hub.protocol https - name: Checkout the branch from the PR that triggered the job run: gh pr checkout ${{ github.event.pull_request.number }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Fix License Header # pin to include https://github.com/apache/skywalking-eyes/pull/168 uses: apache/skywalking-eyes/header@e19b828cea6a6027cceae78f05d81317347d21be with: mode: fix - name: List files changed id: files-changed shell: bash -l {0} run: | set -ex export CHANGES=$(git status --porcelain | tee modified.log | wc -l) cat modified.log # Remove the log otherwise it will be committed rm modified.log echo "N_CHANGES=${CHANGES}" >> $GITHUB_OUTPUT git diff - name: Commit any changes if: steps.files-changed.outputs.N_CHANGES != '0' shell: bash -l {0} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git pull --no-tags git add * git commit -m "Automatic application of license header" git config push.default upstream git push env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jupyterlab_server-2.25.2/.github/workflows/prep-release.yml000066400000000000000000000026571452610450300240670ustar00rootroot00000000000000name: "Step 1: Prep Release" on: workflow_dispatch: inputs: version_spec: description: "New Version Specifier" default: "next" required: false branch: description: "The branch to target" required: false post_version_spec: description: "Post Version Specifier" required: false since: description: "Use PRs with activity since this date or git reference" required: false since_last_stable: description: "Use PRs with activity since the last stable git tag" required: false type: boolean jobs: prep_release: runs-on: ubuntu-latest steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Prep Release id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: token: ${{ secrets.ADMIN_GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} post_version_spec: ${{ github.event.inputs.post_version_spec }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} since: ${{ github.event.inputs.since }} since_last_stable: ${{ github.event.inputs.since_last_stable }} - name: "** Next Step **" run: | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" jupyterlab_server-2.25.2/.github/workflows/publish-release.yml000066400000000000000000000034761452610450300245670ustar00rootroot00000000000000name: "Step 2: Publish Release" on: workflow_dispatch: inputs: branch: description: "The target branch" required: false release_url: description: "The URL of the draft GitHub release" required: false steps_to_skip: description: "Comma separated list of steps to skip" required: false jobs: publish_release: runs-on: ubuntu-latest steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Populate Release id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: token: ${{ secrets.ADMIN_GITHUB_TOKEN }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} release_url: ${{ github.event.inputs.release_url }} steps_to_skip: ${{ github.event.inputs.steps_to_skip }} - name: Finalize Release id: finalize-release env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} TWINE_USERNAME: __token__ NPM_TOKEN: ${{ secrets.NPM_TOKEN }} uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 with: token: ${{ secrets.ADMIN_GITHUB_TOKEN }} target: ${{ github.event.inputs.target }} release_url: ${{ steps.populate-release.outputs.release_url }} - name: "** Next Step **" if: ${{ success() }} run: | echo "Verify the final release" echo ${{ steps.finalize-release.outputs.release_url }} - name: "** Failure Message **" if: ${{ failure() }} run: | echo "Failed to Publish the Draft Release Url:" echo ${{ steps.populate-release.outputs.release_url }} jupyterlab_server-2.25.2/.github/workflows/tests.yml000066400000000000000000000111451452610450300226350ustar00rootroot00000000000000name: Tests on: push: branches: ["main"] pull_request: schedule: - cron: "0 8 * * *" defaults: run: shell: bash -eux {0} jobs: test: name: ${{ matrix.os }} ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.8", "3.12"] include: - os: windows-latest python-version: "3.9" - os: ubuntu-latest python-version: "pypy-3.9" - os: ubuntu-latest python-version: "3.10" - os: macos-latest python-version: "3.11" steps: - name: Checkout uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run the tests if: ${{ !startsWith(matrix.python-version, 'pypy') }} run: hatch run cov:test - name: Run the tests on pypy if: ${{ startsWith(matrix.python-version, 'pypy') }} run: hatch run cov:nowarn - uses: jupyterlab/maintainer-tools/.github/actions/upload-coverage@v1 coverage_report: name: Combine & check coverage needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1 with: fail_under: 80 test_lint: name: Test Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run Linters run: | hatch run typing:test hatch run lint:build pipx run interrogate -v . pipx run doc8 --max-line-length=200 docs: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - run: hatch run docs:build check_release: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Install Dependencies run: | pip install -e . - name: Check Release uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} test_miniumum_versions: name: Test Minimum Versions timeout-minutes: 20 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: minimum - name: Run the unit tests run: | hatch -vv run test:nowarn || hatch run test:nowarn --lf test_prereleases: name: Test Prereleases runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: pre - name: Run the tests run: | hatch run test:nowarn || hatch run test:nowarn --lf make_sdist: name: Make SDist runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 test_sdist: runs-on: ubuntu-latest needs: [make_sdist] name: Install from SDist and Test timeout-minutes: 20 steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1 with: test_command: hatch run test:test || hatch run test:test --lf - run: # Ensure that the pytest plugin is importable. python -c "from jupyterlab_server import pytest_plugin" check_links: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 tests_check: # This job does nothing and is only used for the branch protection if: always() needs: - coverage_report - test_lint - check_links - docs - check_release - test_miniumum_versions - test_prereleases - test_sdist runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} jupyterlab_server-2.25.2/.gitignore000066400000000000000000000005361452610450300173450ustar00rootroot00000000000000MANIFEST build dist _build docs/man/*.gz .cache *.py[co] __pycache__ *.egg-info *~ *.bak .ipynb_checkpoints .tox .DS_Store \#*# .#* .coverage .cache .pytest_cache # generated changelog docs/source/changelog.md # generated cli docs docs/source/api/app-config.rst # jetbrains IDE stuff *.iml .idea/ # ms IDE stuff *.code-workspace .history .vscode jupyterlab_server-2.25.2/.licenserc.yaml000066400000000000000000000007771452610450300202750ustar00rootroot00000000000000header: license: spdx-id: BSD-3-Clause copyright-owner: Jupyter Development Team software-name: JupyterLab content: | Copyright (c) Jupyter Development Team. Distributed under the terms of the Modified BSD License. paths-ignore: - "**/*.json" - "**/*.md" - "**/*.po" - "**/*.svg" - "**/*.yml" - "**/*.yaml" - "**/.*" - "**/MANIFEST.in" - "LICENSE" - "jupyterlab_server/test_data" - "jupyterlab_server/py.typed" comment: on-failure jupyterlab_server-2.25.2/.pre-commit-config.yaml000066400000000000000000000045731452610450300216430ustar00rootroot00000000000000ci: autoupdate_schedule: monthly autoupdate_commit_msg: "chore: update pre-commit hooks" repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-case-conflict - id: check-ast - id: check-docstring-first - id: check-executables-have-shebangs - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-json - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.27.1 hooks: - id: check-github-workflows - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 hooks: - id: mdformat additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/pre-commit/mirrors-prettier rev: "v3.1.0" hooks: - id: prettier types_or: [yaml, json] - repo: https://github.com/adamchainz/blacken-docs rev: "1.16.0" hooks: - id: blacken-docs additional_dependencies: [black==23.7.0] - repo: https://github.com/codespell-project/codespell rev: "v2.2.6" hooks: - id: codespell args: ["-L", "sur,nd"] - repo: https://github.com/pre-commit/pygrep-hooks rev: "v1.10.0" hooks: - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal - repo: https://github.com/pre-commit/mirrors-mypy rev: "v1.7.0" hooks: - id: mypy files: "^jupyterlab_server" stages: [manual] args: ["--install-types", "--non-interactive"] additional_dependencies: [ "traitlets>=5.3", "jupyter_server>=2.10.1", "openapi_core", "json5", "pytest", "werkzeug", "ruamel.yaml", "importlib_metadata", ] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.5 hooks: - id: ruff types_or: [python, jupyter] args: ["--fix", "--show-fixes"] - id: ruff-format types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie rev: "2023.10.27" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] jupyterlab_server-2.25.2/.readthedocs.yaml000066400000000000000000000003651452610450300206040ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: docs/source/conf.py python: install: # install itself with pip install . - method: pip path: . extra_requirements: - docs jupyterlab_server-2.25.2/CHANGELOG.md000066400000000000000000002375541452610450300172020ustar00rootroot00000000000000# Change Log ## 2.25.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.25.1...20a463e065f7cb38ee607cce95c30b6915c80f1a)) ### Maintenance and upkeep improvements - Update typings for Server 2.10.1 and mypy 1.7 [#425](https://github.com/jupyterlab/jupyterlab_server/pull/425) ([@blink1073](https://github.com/blink1073)) - Align JSON schema validator with one used on the frontend [#423](https://github.com/jupyterlab/jupyterlab_server/pull/423) ([@krassowski](https://github.com/krassowski)) - Update ruff config [#422](https://github.com/jupyterlab/jupyterlab_server/pull/422) ([@blink1073](https://github.com/blink1073)) ### Documentation improvements - Fix link-rot for Swagger view of OpenAPI spec [#424](https://github.com/jupyterlab/jupyterlab_server/pull/424) ([@nopdotcom](https://github.com/nopdotcom)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-11-08&to=2023-11-18&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-11-08..2023-11-18&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Akrassowski+updated%3A2023-11-08..2023-11-18&type=Issues) | [@nopdotcom](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Anopdotcom+updated%3A2023-11-08..2023-11-18&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2023-11-08..2023-11-18&type=Issues) ## 2.25.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.25.0...641ddbe75cf194cd343f9fba05226f365d36765a)) ### Bugs fixed - Add repository info when getting federated extensions [#417](https://github.com/jupyterlab/jupyterlab_server/pull/417) ([@nbowditch-einblick](https://github.com/nbowditch-einblick)) ### Maintenance and upkeep improvements - Update typings for server 2.10 [#421](https://github.com/jupyterlab/jupyterlab_server/pull/421) ([@blink1073](https://github.com/blink1073)) - chore: update pre-commit hooks [#420](https://github.com/jupyterlab/jupyterlab_server/pull/420) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up lint and ci [#418](https://github.com/jupyterlab/jupyterlab_server/pull/418) ([@blink1073](https://github.com/blink1073)) - Adopt ruff format [#415](https://github.com/jupyterlab/jupyterlab_server/pull/415) ([@blink1073](https://github.com/blink1073)) - Adopt sp-repo-review [#414](https://github.com/jupyterlab/jupyterlab_server/pull/414) ([@blink1073](https://github.com/blink1073)) - Update openapi-spec-validator requirement from \<0.7.0,>=0.6.0 to >=0.6.0,\<0.8.0 [#412](https://github.com/jupyterlab/jupyterlab_server/pull/412) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-09-12&to=2023-11-08&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-09-12..2023-11-08&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2023-09-12..2023-11-08&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2023-09-12..2023-11-08&type=Issues) | [@nbowditch-einblick](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Anbowditch-einblick+updated%3A2023-09-12..2023-11-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-09-12..2023-11-08&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2023-09-12..2023-11-08&type=Issues) ## 2.25.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.24.0...91b576d6a87e2fca0958e815c32391ebc8398945)) ### Maintenance and upkeep improvements - Bump openapi deps and support Python 3.8-3.12 [#410](https://github.com/jupyterlab/jupyterlab_server/pull/410) ([@blink1073](https://github.com/blink1073)) - Bump actions/checkout from 3 to 4 [#408](https://github.com/jupyterlab/jupyterlab_server/pull/408) ([@dependabot](https://github.com/dependabot)) - Bump apache/skywalking-eyes from 0.4.0 to 0.5.0 [#407](https://github.com/jupyterlab/jupyterlab_server/pull/407) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-07-24&to=2023-09-12&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-07-24..2023-09-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2023-07-24..2023-09-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-07-24..2023-09-12&type=Issues) ## 2.24.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.23.0...de8f61e8175b0ebbb0a38beedb2bba579e6efc72)) ### Enhancements made - add lab config for copy absolute path [#405](https://github.com/jupyterlab/jupyterlab_server/pull/405) ([@pauky](https://github.com/pauky)) ### Maintenance and upkeep improvements - Update openapi-spec-validator requirement from ~=0.5.1 to >=0.5.1,\<0.7.0 [#403](https://github.com/jupyterlab/jupyterlab_server/pull/403) ([@dependabot](https://github.com/dependabot)) - Fix lint error [#401](https://github.com/jupyterlab/jupyterlab_server/pull/401) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-06-13&to=2023-07-24&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-06-13..2023-07-24&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2023-06-13..2023-07-24&type=Issues) | [@pauky](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apauky+updated%3A2023-06-13..2023-07-24&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-06-13..2023-07-24&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2023-06-13..2023-07-24&type=Issues) ## 2.23.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.22.1...19aa4d68e92e24523c88a88c92ee9e621de8f427)) ### Enhancements made - Set Cache-control immutable for static files [#394](https://github.com/jupyterlab/jupyterlab_server/pull/394) ([@brichet](https://github.com/brichet)) ### Maintenance and upkeep improvements - Update mistune requirement from \<3 to \<4 [#399](https://github.com/jupyterlab/jupyterlab_server/pull/399) ([@dependabot](https://github.com/dependabot)) - Remove distuils pin [#397](https://github.com/jupyterlab/jupyterlab_server/pull/397) ([@blink1073](https://github.com/blink1073)) - Lint and typing updates [#395](https://github.com/jupyterlab/jupyterlab_server/pull/395) ([@blink1073](https://github.com/blink1073)) - Fix link to Docs [#393](https://github.com/jupyterlab/jupyterlab_server/pull/393) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-04-13&to=2023-06-13&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-04-13..2023-06-13&type=Issues) | [@brichet](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abrichet+updated%3A2023-04-13..2023-06-13&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2023-04-13..2023-06-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-04-13..2023-06-13&type=Issues) ## 2.22.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.22.0...584d2f0138697f8972402e72a744e84ac3d26137)) ### Maintenance and upkeep improvements - Fix coverage handling (#392 [#392](https://github.com/jupyterlab/jupyterlab_server/pull/392) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-03-31&to=2023-04-13&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-03-31..2023-04-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-03-31..2023-04-13&type=Issues) ## 2.22.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.21.0...cd829a8f5ca1c941cddd08dfcdbf1d8b13a7d433)) ### Enhancements made - Optional argument to get only the schema names [#385](https://github.com/jupyterlab/jupyterlab_server/pull/385) ([@brichet](https://github.com/brichet)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-03-22&to=2023-03-31&type=c)) [@brichet](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abrichet+updated%3A2023-03-22..2023-03-31&type=Issues) ## 2.21.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.20.0...48be03a73c11b2f800ad768aed30d417b248d98e)) ### Enhancements made - Add json5 support for page_config.json [#388](https://github.com/jupyterlab/jupyterlab_server/pull/388) ([@peytondmurray](https://github.com/peytondmurray)) ### Maintenance and upkeep improvements - Updates test dependencies [#387](https://github.com/jupyterlab/jupyterlab_server/pull/387) ([@brichet](https://github.com/brichet)) - Ignore warning from bind_sockets [#383](https://github.com/jupyterlab/jupyterlab_server/pull/383) ([@blink1073](https://github.com/blink1073)) - Bump actions/checkout from 2 to 3 [#379](https://github.com/jupyterlab/jupyterlab_server/pull/379) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-03-06&to=2023-03-22&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-03-06..2023-03-22&type=Issues) | [@brichet](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abrichet+updated%3A2023-03-06..2023-03-22&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2023-03-06..2023-03-22&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2023-03-06..2023-03-22&type=Issues) | [@peytondmurray](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apeytondmurray+updated%3A2023-03-06..2023-03-22&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-03-06..2023-03-22&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2023-03-06..2023-03-22&type=Issues) ## 2.20.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.19.0...4a08b8418248f4068a3ae0bdf4aa93c524130421)) ### Bugs fixed - Change periodic callback time from tuple to int (#376 [#376](https://github.com/jupyterlab/jupyterlab_server/pull/376) ([@rmoe](https://github.com/rmoe)) ### Maintenance and upkeep improvements - Pin openapi deps [#381](https://github.com/jupyterlab/jupyterlab_server/pull/381) ([@blink1073](https://github.com/blink1073)) - Revert "Fix some CodeQL notices" [#372](https://github.com/jupyterlab/jupyterlab_server/pull/372) ([@fcollonval](https://github.com/fcollonval)) - Fix some CodeQL notices [#371](https://github.com/jupyterlab/jupyterlab_server/pull/371) ([@fcollonval](https://github.com/fcollonval)) - Add more linting [#369](https://github.com/jupyterlab/jupyterlab_server/pull/369) ([@blink1073](https://github.com/blink1073)) - Clean up openapi compat and warnings [#368](https://github.com/jupyterlab/jupyterlab_server/pull/368) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-01-16&to=2023-03-06&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-01-16..2023-03-06&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2023-01-16..2023-03-06&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2023-01-16..2023-03-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2023-01-16..2023-03-06&type=Issues) | [@rmoe](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Armoe+updated%3A2023-01-16..2023-03-06&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2023-01-16..2023-03-06&type=Issues) ## 2.19.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.18.0...a226eaa4e0a55a167ef7ab6ef7da53a2013a3fb1)) ### Enhancements made - Don't change language environment variable [#366](https://github.com/jupyterlab/jupyterlab_server/pull/366) ([@fcollonval](https://github.com/fcollonval)) ### Maintenance and upkeep improvements - Fix test_process_app [#367](https://github.com/jupyterlab/jupyterlab_server/pull/367) ([@blink1073](https://github.com/blink1073)) - Sync lint deps [#365](https://github.com/jupyterlab/jupyterlab_server/pull/365) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2023-01-03&to=2023-01-16&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2023-01-03..2023-01-16&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2023-01-03..2023-01-16&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2023-01-03..2023-01-16&type=Issues) ## 2.18.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.17.0...a2528876192e34fa2856b8fb3941148c6447d591)) ### Enhancements made - Prepare for jupyter_server preferred_dir fix [#324](https://github.com/jupyterlab/jupyterlab_server/pull/324) ([@vidartf](https://github.com/vidartf)) ### Bugs fixed - Handle systems with `python3` [#362](https://github.com/jupyterlab/jupyterlab_server/pull/362) ([@blink1073](https://github.com/blink1073)) ### Maintenance and upkeep improvements - Clean up docs build [#363](https://github.com/jupyterlab/jupyterlab_server/pull/363) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-12-26&to=2023-01-03&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-12-26..2023-01-03&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-12-26..2023-01-03&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-12-26..2023-01-03&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-12-26..2023-01-03&type=Issues) | [@vidartf](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Avidartf+updated%3A2022-12-26..2023-01-03&type=Issues) ## 2.17.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.6...6870153670345c20e2b86a02e6a35394703fbbf5)) ### Maintenance and upkeep improvements - Do not repeat requirements [#361](https://github.com/jupyterlab/jupyterlab_server/pull/361) ([@blink1073](https://github.com/blink1073)) - Add support for newer openapi spec [#360](https://github.com/jupyterlab/jupyterlab_server/pull/360) ([@blink1073](https://github.com/blink1073)) - Add spelling and docstring enforcement [#358](https://github.com/jupyterlab/jupyterlab_server/pull/358) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-12-23&to=2022-12-26&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-12-23..2022-12-26&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-12-23..2022-12-26&type=Issues) ## 2.16.6 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.5...b8135b4a661f75560d39b52aab90f3e951884c39)) ### Bugs fixed - Restore print outputs in process [#357](https://github.com/jupyterlab/jupyterlab_server/pull/357) ([@blink1073](https://github.com/blink1073)) ### Maintenance and upkeep improvements - Bump tj-actions/changed-files from 34 to 35 [#355](https://github.com/jupyterlab/jupyterlab_server/pull/355) ([@dependabot](https://github.com/dependabot)) - Fix license header job [#354](https://github.com/jupyterlab/jupyterlab_server/pull/354) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-12-08&to=2022-12-23&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-12-08..2022-12-23&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-12-08..2022-12-23&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2022-12-08..2022-12-23&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-12-08..2022-12-23&type=Issues) ## 2.16.5 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.4...ee8db6a651c68fc1413935fa1d10d58a38f1ccc1)) ### Maintenance and upkeep improvements - Use pytest-jupyter [#351](https://github.com/jupyterlab/jupyterlab_server/pull/351) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-12-08&to=2022-12-08&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-12-08..2022-12-08&type=Issues) ## 2.16.4 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.3...2005945e4cbe81a1d7a52103557cefa6e1283592)) ### Maintenance and upkeep improvements - Add py.typed marker file [#349](https://github.com/jupyterlab/jupyterlab_server/pull/349) ([@blink1073](https://github.com/blink1073)) - Update docutils requirement from \<0.19 to \<0.20 [#348](https://github.com/jupyterlab/jupyterlab_server/pull/348) ([@dependabot](https://github.com/dependabot)) - Update openapi-spec-validator requirement from \<0.5 to \<0.6 [#347](https://github.com/jupyterlab/jupyterlab_server/pull/347) ([@dependabot](https://github.com/dependabot)) - Update mistune requirement from \<1 to \<3 [#345](https://github.com/jupyterlab/jupyterlab_server/pull/345) ([@dependabot](https://github.com/dependabot)) - Update jinja2 requirement from \<3.1.0 to \<3.2.0 [#344](https://github.com/jupyterlab/jupyterlab_server/pull/344) ([@dependabot](https://github.com/dependabot)) - Adopt ruff and address lint [#343](https://github.com/jupyterlab/jupyterlab_server/pull/343) ([@blink1073](https://github.com/blink1073)) - Fixup workflows [#339](https://github.com/jupyterlab/jupyterlab_server/pull/339) ([@blink1073](https://github.com/blink1073)) - CI Cleanup [#337](https://github.com/jupyterlab/jupyterlab_server/pull/337) ([@blink1073](https://github.com/blink1073)) ### Documentation improvements - Update codecov badge [#340](https://github.com/jupyterlab/jupyterlab_server/pull/340) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-11-11&to=2022-12-08&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-11-11..2022-12-08&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-11-11..2022-12-08&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2022-11-11..2022-12-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-11-11..2022-12-08&type=Issues) ## 2.16.3 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.2...cdc81451c3f187e8183f2e5b67be2e648501f979)) ### Maintenance and upkeep improvements - Fix handling of jupyter core warning [#335](https://github.com/jupyterlab/jupyterlab_server/pull/335) ([@blink1073](https://github.com/blink1073)) - Increase coverage [#334](https://github.com/jupyterlab/jupyterlab_server/pull/334) ([@blink1073](https://github.com/blink1073)) - Fix license header CI job [#333](https://github.com/jupyterlab/jupyterlab_server/pull/333) ([@fcollonval](https://github.com/fcollonval)) - Bump tj-actions/changed-files from 33 to 34 [#331](https://github.com/jupyterlab/jupyterlab_server/pull/331) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-10-31&to=2022-11-11&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-10-31..2022-11-11&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-10-31..2022-11-11&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2022-10-31..2022-11-11&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-10-31..2022-11-11&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-10-31..2022-11-11&type=Issues) ## 2.16.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.1...dbf016fb5e312b3c03e8438a80d6df4f40de48e3)) ### Bugs fixed - Terminals availability is not the responsibility of JupyterLab [#330](https://github.com/jupyterlab/jupyterlab_server/pull/330) ([@fcollonval](https://github.com/fcollonval)) ### Maintenance and upkeep improvements - Bump tj-actions/changed-files from 32 to 33 [#328](https://github.com/jupyterlab/jupyterlab_server/pull/328) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-10-19&to=2022-10-31&type=c)) [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-10-19..2022-10-31&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2022-10-19..2022-10-31&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-10-19..2022-10-31&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-10-19..2022-10-31&type=Issues) ## 2.16.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.16.0...11e0bb82dbaeb0cb45d16e18d80220003a15d459)) ### Maintenance and upkeep improvements - Prep for using releaser [#327](https://github.com/jupyterlab/jupyterlab_server/pull/327) ([@blink1073](https://github.com/blink1073)) - Maintenance cleanup [#325](https://github.com/jupyterlab/jupyterlab_server/pull/325) ([@blink1073](https://github.com/blink1073)) - Bump actions/upload-artifact from 2 to 3 [#322](https://github.com/jupyterlab/jupyterlab_server/pull/322) ([@dependabot](https://github.com/dependabot)) - Bump actions/download-artifact from 2 to 3 [#321](https://github.com/jupyterlab/jupyterlab_server/pull/321) ([@dependabot](https://github.com/dependabot)) - Bump actions/checkout from 2 to 3 [#320](https://github.com/jupyterlab/jupyterlab_server/pull/320) ([@dependabot](https://github.com/dependabot)) - Bump tj-actions/changed-files from 24 to 32 [#319](https://github.com/jupyterlab/jupyterlab_server/pull/319) ([@dependabot](https://github.com/dependabot)) - Fix workflows and add new ones [#318](https://github.com/jupyterlab/jupyterlab_server/pull/318) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-10-13&to=2022-10-19&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-10-13..2022-10-19&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-10-13..2022-10-19&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Adependabot+updated%3A2022-10-13..2022-10-19&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-10-13..2022-10-19&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-10-13..2022-10-19&type=Issues) ## 2.16.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.15.1...c278cb9bbda81a2730d1198f0a5fc708a18506d9)) ### Enhancements made - raise jupyter_server pin to include 2.x [#302](https://github.com/jupyterlab/jupyterlab_server/pull/302) ([@Zsailer](https://github.com/Zsailer)) ### Maintenance and upkeep improvements - CI Cleanup [#316](https://github.com/jupyterlab/jupyterlab_server/pull/316) ([@blink1073](https://github.com/blink1073)) - Ignore warnings in prerelease test [#312](https://github.com/jupyterlab/jupyterlab_server/pull/312) ([@blink1073](https://github.com/blink1073)) - Clean up pyproject and ci [#306](https://github.com/jupyterlab/jupyterlab_server/pull/306) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-08-23&to=2022-10-13&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-08-23..2022-10-13&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-08-23..2022-10-13&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-08-23..2022-10-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-08-23..2022-10-13&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3AZsailer+updated%3A2022-08-23..2022-10-13&type=Issues) ## 2.16.0rc0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.15.1...7d83684f3f94724c977692c2785d5ad81fac231e)) ### Enhancements made - raise jupyter_server pin to include 2.x [#302](https://github.com/jupyterlab/jupyterlab_server/pull/302) ([@Zsailer](https://github.com/Zsailer)) ### Maintenance and upkeep improvements - \[pre-commit.ci\] pre-commit autoupdate [#301](https://github.com/jupyterlab/jupyterlab_server/pull/301) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#300](https://github.com/jupyterlab/jupyterlab_server/pull/300) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-08-23&to=2022-09-09&type=c)) [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-08-23..2022-09-09&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-08-23..2022-09-09&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3AZsailer+updated%3A2022-08-23..2022-09-09&type=Issues) ## 2.15.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.15.0...6de991c68abad7041581dd5a7c292194d3b2dea7)) ### Maintenance and upkeep improvements - Add fix license header job [#298](https://github.com/jupyterlab/jupyterlab_server/pull/298) ([@fcollonval](https://github.com/fcollonval)) - \[pre-commit.ci\] pre-commit autoupdate [#297](https://github.com/jupyterlab/jupyterlab_server/pull/297) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Ignore deprecationwarning from jsonschema [#295](https://github.com/jupyterlab/jupyterlab_server/pull/295) ([@blink1073](https://github.com/blink1073)) - \[pre-commit.ci\] pre-commit autoupdate [#294](https://github.com/jupyterlab/jupyterlab_server/pull/294) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#293](https://github.com/jupyterlab/jupyterlab_server/pull/293) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#291](https://github.com/jupyterlab/jupyterlab_server/pull/291) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#290](https://github.com/jupyterlab/jupyterlab_server/pull/290) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#289](https://github.com/jupyterlab/jupyterlab_server/pull/289) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Documentation improvements - Only use `conda-forge` and not defaults for RTD [#296](https://github.com/jupyterlab/jupyterlab_server/pull/296) ([@afshin](https://github.com/afshin)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-07-05&to=2022-08-23&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aafshin+updated%3A2022-07-05..2022-08-23&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-07-05..2022-08-23&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-07-05..2022-08-23&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-07-05..2022-08-23&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-07-05..2022-08-23&type=Issues) ## 2.15.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.14.0...2d6ff4077a58bb9ac1531c4a7ad7669163e0ec99)) ### Maintenance and upkeep improvements - \[pre-commit.ci\] pre-commit autoupdate [#287](https://github.com/jupyterlab/jupyterlab_server/pull/287) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#286](https://github.com/jupyterlab/jupyterlab_server/pull/286) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Suppress tornado 6.2 beta warnings [#285](https://github.com/jupyterlab/jupyterlab_server/pull/285) ([@blink1073](https://github.com/blink1073)) - \[pre-commit.ci\] pre-commit autoupdate [#284](https://github.com/jupyterlab/jupyterlab_server/pull/284) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#283](https://github.com/jupyterlab/jupyterlab_server/pull/283) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Add compat for openapi-core 0.15 [#281](https://github.com/jupyterlab/jupyterlab_server/pull/281) ([@blink1073](https://github.com/blink1073)) - \[pre-commit.ci\] pre-commit autoupdate [#280](https://github.com/jupyterlab/jupyterlab_server/pull/280) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#279](https://github.com/jupyterlab/jupyterlab_server/pull/279) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#277](https://github.com/jupyterlab/jupyterlab_server/pull/277) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-05-18&to=2022-07-05&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-05-18..2022-07-05&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-05-18..2022-07-05&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-05-18..2022-07-05&type=Issues) ## 2.14.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.13.0...faaf10cbe5abcfa5e3f0e3f7a491d651ed998a26)) ### Bugs fixed - proper setup for the fallback logger [#269](https://github.com/jupyterlab/jupyterlab_server/pull/269) ([@rvalieris](https://github.com/rvalieris)) ### Maintenance and upkeep improvements - \[pre-commit.ci\] pre-commit autoupdate [#275](https://github.com/jupyterlab/jupyterlab_server/pull/275) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Switch to hatch backend [#274](https://github.com/jupyterlab/jupyterlab_server/pull/274) ([@blink1073](https://github.com/blink1073)) - \[pre-commit.ci\] pre-commit autoupdate [#272](https://github.com/jupyterlab/jupyterlab_server/pull/272) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Switch to flit build backend [#271](https://github.com/jupyterlab/jupyterlab_server/pull/271) ([@blink1073](https://github.com/blink1073)) - Allow bot PRs to be automatically labeled [#270](https://github.com/jupyterlab/jupyterlab_server/pull/270) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-04-19&to=2022-05-18&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-04-19..2022-05-18&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-04-19..2022-05-18&type=Issues) | [@kloczek](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Akloczek+updated%3A2022-04-19..2022-05-18&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-04-19..2022-05-18&type=Issues) | [@rvalieris](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Arvalieris+updated%3A2022-04-19..2022-05-18&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2022-04-19..2022-05-18&type=Issues) ## 2.13.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.12.0...5c123d20b8c1287b596eb50e18d127a851395840)) ### Bugs fixed - Audit uses of print [#261](https://github.com/jupyterlab/jupyterlab_server/pull/261) ([@blink1073](https://github.com/blink1073)) ### Maintenance and upkeep improvements - \[pre-commit.ci\] pre-commit autoupdate [#266](https://github.com/jupyterlab/jupyterlab_server/pull/266) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - \[pre-commit.ci\] pre-commit autoupdate [#265](https://github.com/jupyterlab/jupyterlab_server/pull/265) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Switch to importlib.metadata to list entry points [#264](https://github.com/jupyterlab/jupyterlab_server/pull/264) ([@fcollonval](https://github.com/fcollonval)) - Make some pre-commit hooks manual [#263](https://github.com/jupyterlab/jupyterlab_server/pull/263) ([@blink1073](https://github.com/blink1073)) - \[pre-commit.ci\] pre-commit autoupdate [#262](https://github.com/jupyterlab/jupyterlab_server/pull/262) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Add flake8 and handle warnings in tests [#259](https://github.com/jupyterlab/jupyterlab_server/pull/259) ([@blink1073](https://github.com/blink1073)) - Add git-blame-ignore-revs [#258](https://github.com/jupyterlab/jupyterlab_server/pull/258) ([@blink1073](https://github.com/blink1073)) - Apply Autoformats [#257](https://github.com/jupyterlab/jupyterlab_server/pull/257) ([@blink1073](https://github.com/blink1073)) - Add initial pre-commit [#256](https://github.com/jupyterlab/jupyterlab_server/pull/256) ([@blink1073](https://github.com/blink1073)) - update URLs, metadata, extras in setup.cfg [#255](https://github.com/jupyterlab/jupyterlab_server/pull/255) ([@bollwyvl](https://github.com/bollwyvl)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-03-28&to=2022-04-19&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-03-28..2022-04-19&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abollwyvl+updated%3A2022-03-28..2022-04-19&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-03-28..2022-04-19&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2022-03-28..2022-04-19&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Apre-commit-ci+updated%3A2022-03-28..2022-04-19&type=Issues) ## 2.12.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.11.2...d927c4cfcf75e3e61a9c1a37b46e68219bb2a324)) ### Enhancements made - Make openapi optional and lazy [#253](https://github.com/jupyterlab/jupyterlab_server/pull/253) ([@blink1073](https://github.com/blink1073)) - Adds notebook_starts_kernel option to make "canStartKernel" configurable [#248](https://github.com/jupyterlab/jupyterlab_server/pull/248) ([@jweill-aws](https://github.com/JasonWeill)) - Clean up tests and add public openapi_spec [#247](https://github.com/jupyterlab/jupyterlab_server/pull/247) ([@blink1073](https://github.com/blink1073)) ### Maintenance and upkeep improvements - Pin jinja for docs [#252](https://github.com/jupyterlab/jupyterlab_server/pull/252) ([@blink1073](https://github.com/blink1073)) - More CI Cleanup [#249](https://github.com/jupyterlab/jupyterlab_server/pull/249) ([@blink1073](https://github.com/blink1073)) ### Documentation improvements - Updates parameter description for notebook_starts_kernel [#250](https://github.com/jupyterlab/jupyterlab_server/pull/250) ([@jweill-aws](https://github.com/JasonWeill)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-03-17&to=2022-03-28&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-03-17..2022-03-28&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-03-17..2022-03-28&type=Issues) | [@jweill-aws](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ajweill-aws+updated%3A2022-03-17..2022-03-28&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2022-03-17..2022-03-28&type=Issues) ## 2.11.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.11.1...271f61678c43a7856058ef0adb422ed1070d76d6)) ### Bugs fixed - Resolve paths in tests [#245](https://github.com/jupyterlab/jupyterlab_server/pull/245) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-03-16&to=2022-03-17&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-03-16..2022-03-17&type=Issues) ## 2.11.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.11.0...681cd7a24fbe6425140d3cb8daabcf4c5f45a069)) ### Bugs fixed - Restore pytest_plugin [#243](https://github.com/jupyterlab/jupyterlab_server/pull/243) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-03-16&to=2022-03-16&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-03-16..2022-03-16&type=Issues) ## 2.11.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.10.3...e004d2d23cb298bc9ae8f1fcb04c47a71cfe7a3a)) ### Enhancements made - Also look in `static` for license files [#238](https://github.com/jupyterlab/jupyterlab_server/pull/238) ([@bollwyvl](https://github.com/bollwyvl)) ### Maintenance and upkeep improvements - Clean up CI [#241](https://github.com/jupyterlab/jupyterlab_server/pull/241) ([@blink1073](https://github.com/blink1073)) - Include Tests in Sdist But Not Wheel [#239](https://github.com/jupyterlab/jupyterlab_server/pull/239) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2022-01-07&to=2022-03-16&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2022-01-07..2022-03-16&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abollwyvl+updated%3A2022-01-07..2022-03-16&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2022-01-07..2022-03-16&type=Issues) ## 2.10.3 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.10.2...788e118e25cd7ad29a3041767201a0e3f66ba28e)) ### Bugs fixed - Fix workspace export without arguments [#236](https://github.com/jupyterlab/jupyterlab_server/pull/236) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-28&to=2022-01-07&type=c)) [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-12-28..2022-01-07&type=Issues) ## 2.10.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.10.1...cbe3b92d732b327a695d832c66f00e096d47ea2b)) ### Bugs fixed - Revert "Delay list -> dict conversation to end (#192)" [#234](https://github.com/jupyterlab/jupyterlab_server/pull/234) ([@blink1073](https://github.com/blink1073)) - Fix issue where preferredPath is incorrectly calculated [#233](https://github.com/jupyterlab/jupyterlab_server/pull/233) ([@mlucool](https://github.com/mlucool)) ### Maintenance and upkeep improvements - Add downstream test workflow [#232](https://github.com/jupyterlab/jupyterlab_server/pull/232) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-22&to=2021-12-28&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-12-22..2021-12-28&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-12-22..2021-12-28&type=Issues) | [@mlucool](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Amlucool+updated%3A2021-12-22..2021-12-28&type=Issues) ## 2.10.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.10.0...65027f3ca0a2c5846ba8381c47809e5d04eb05c7)) ### Bugs fixed - Restore back older export for workspaces [#229](https://github.com/jupyterlab/jupyterlab_server/pull/229) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-22&to=2021-12-22&type=c)) [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-12-22..2021-12-22&type=Issues) ## 2.10.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.9.0...6fa038f12ad7946e16c5e18665e8a88177080264)) ### Enhancements made - Move workspace management in jupyterlab_server [#227](https://github.com/jupyterlab/jupyterlab_server/pull/227) ([@fcollonval](https://github.com/fcollonval)) ### Bugs fixed - Delay list -> dict conversation to end [#192](https://github.com/jupyterlab/jupyterlab_server/pull/192) ([@vidartf](https://github.com/vidartf)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-12-13&to=2021-12-22&type=c)) [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-12-13..2021-12-22&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-12-13..2021-12-22&type=Issues) | [@vidartf](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Avidartf+updated%3A2021-12-13..2021-12-22&type=Issues) ## 2.9.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.2...ea75fc04f9a4d29b9c22c9ec769835df3ba6a0e4)) ### Enhancements made - Add `overrides.d` for settings defaults [#224](https://github.com/jupyterlab/jupyterlab_server/pull/224) ([@bollwyvl](https://github.com/bollwyvl)) - Add `page_config_hook` [#220](https://github.com/jupyterlab/jupyterlab_server/pull/220) ([@minrk](https://github.com/minrk)) ### Maintenance and upkeep improvements - Enforce labels on PRs [#221](https://github.com/jupyterlab/jupyterlab_server/pull/221) ([@blink1073](https://github.com/blink1073)) - Fix caching of conda env in CI [#218](https://github.com/jupyterlab/jupyterlab_server/pull/218) ([@blink1073](https://github.com/blink1073)) - `pyproject.toml`: clarify build system version [#222](https://github.com/jupyterlab/jupyterlab_server/pull/222) ([@adamjstewart](https://github.com/adamjstewart)) ### Documentation improvements - Fix typo in README [#216](https://github.com/jupyterlab/jupyterlab_server/pull/216) ([@Mithil467](https://github.com/Mithil467)) - Clean up Readme [#215](https://github.com/jupyterlab/jupyterlab_server/pull/215) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-09-25&to=2021-12-13&type=c)) [@adamjstewart](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aadamjstewart+updated%3A2021-09-25..2021-12-13&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-09-25..2021-12-13&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abollwyvl+updated%3A2021-09-25..2021-12-13&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-09-25..2021-12-13&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aminrk+updated%3A2021-09-25..2021-12-13&type=Issues) | [@Mithil467](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3AMithil467+updated%3A2021-09-25..2021-12-13&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2021-09-25..2021-12-13&type=Issues) ## 2.8.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.1...27cead5e506882c1cf999cb8ca48a94031064c9a)) ### Bugs fixed - Fallback to context-less translation on Python 3.7 [#213](https://github.com/jupyterlab/jupyterlab_server/pull/213) ([@krassowski](https://github.com/krassowski)) - Clean unneeded catch [#210](https://github.com/jupyterlab/jupyterlab_server/pull/210) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-09-07&to=2021-09-25&type=c)) [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-09-07..2021-09-25&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-09-07..2021-09-25&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Akrassowski+updated%3A2021-09-07..2021-09-25&type=Issues) ## 2.8.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.0...680f4fe1c8c7d1d7841e14562720d81a27e6d0ad)) ### Bugs fixed - Fall back to `DEFAULT_LOCALE` when translation settings schema is invalid in `get_current_locale` [#207](https://github.com/jupyterlab/jupyterlab_server/pull/207) ([@telamonian](https://github.com/telamonian)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-09-07&to=2021-09-07&type=c)) [@telamonian](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Atelamonian+updated%3A2021-09-07..2021-09-07&type=Issues) ## 2.8.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.7.2...407a8e997825e76b7f9c8992ee03206c21ea0fa0)) ### Enhancements made - Translate settings schema [#205](https://github.com/jupyterlab/jupyterlab_server/pull/205) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-23&to=2021-09-07&type=c)) [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-08-23..2021-09-07&type=Issues) ## 2.7.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.7.1...1508ff86421f1473ad388ac2d384bf986e326862)) ### Bugs fixed - Do not overwrite capitalization of region names [#202](https://github.com/jupyterlab/jupyterlab_server/pull/202) ([@krassowski](https://github.com/krassowski)) ### Maintenance and upkeep improvements - Use Check Links Action [#201](https://github.com/jupyterlab/jupyterlab_server/pull/201) ([@blink1073](https://github.com/blink1073)) ### Documentation improvements - Recommend `pytest --pyargs jupyterlab_server` [#203](https://github.com/jupyterlab/jupyterlab_server/pull/203) ([@krassowski](https://github.com/krassowski)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-17&to=2021-08-23&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-08-17..2021-08-23&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-08-17..2021-08-23&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Akrassowski+updated%3A2021-08-17..2021-08-23&type=Issues) ## 2.7.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.7.0...4c5f9c84fa4c1be2267712d2a5f28fe82bdf9047)) ### Bugs fixed - Fix reset user settings if validation failed [#199](https://github.com/jupyterlab/jupyterlab_server/pull/199) ([@fcollonval](https://github.com/fcollonval)) ### Maintenance and upkeep improvements - TST: support openapi-core 0.14 SpecPath [#198](https://github.com/jupyterlab/jupyterlab_server/pull/198) ([@bnavigator](https://github.com/bnavigator)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-11&to=2021-08-17&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-08-11..2021-08-17&type=Issues) | [@bnavigator](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abnavigator+updated%3A2021-08-11..2021-08-17&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-08-11..2021-08-17&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2021-08-11..2021-08-17&type=Issues) ## 2.7.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.6.2...919186b026f8c86f2f14c11318776e272a9dd629)) ### Maintenance and upkeep improvements - Switch to entrypoints package [#187](https://github.com/jupyterlab/jupyterlab_server/pull/187) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-03&to=2021-08-11&type=c)) [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-08-03..2021-08-11&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2021-08-03..2021-08-11&type=Issues) ## 2.6.2 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.6.1...1ac80fc439a8150de11bc470d370693cf5389781)) ### Enhancements made - PR: Add preferred dir option to config [#190](https://github.com/jupyterlab/jupyterlab_server/pull/190) ([@goanpeca](https://github.com/goanpeca)) ### Bugs fixed - PR: Update new_defaults dictionary instead of copy [#194](https://github.com/jupyterlab/jupyterlab_server/pull/194) ([@goanpeca](https://github.com/goanpeca)) ### Other merged PRs - Update tests workflow [#193](https://github.com/jupyterlab/jupyterlab_server/pull/193) ([@afshin](https://github.com/afshin)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-07-09&to=2021-08-03&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aafshin+updated%3A2021-07-09..2021-08-03&type=Issues) | [@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-07-09..2021-08-03&type=Issues) | [@goanpeca](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Agoanpeca+updated%3A2021-07-09..2021-08-03&type=Issues) ## 2.6.1 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.6.0...52f4cf92adb8b6dc3b350ce324619a231a9003ba)) ### Enhancements made - Merge overrides settings if values are dicts [#188](https://github.com/jupyterlab/jupyterlab_server/pull/188) ([@goanpeca](https://github.com/goanpeca)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-06-01&to=2021-07-09&type=c)) [@goanpeca](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Agoanpeca+updated%3A2021-06-01..2021-07-09&type=Issues) ## 2.6.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.5.2...167867d1b49b48735e57106bb13bcc7efd8f2dde)) ### Enhancements made - LicensesManager and API/CLI [#161](https://github.com/jupyterlab/jupyterlab_server/pull/161) ([@bollwyvl](https://github.com/bollwyvl)) ### Documentation improvements - Add changelog for 2.6.0 [#185](https://github.com/jupyterlab/jupyterlab_server/pull/185) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-05-17&to=2021-06-01&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-05-17..2021-06-01&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abollwyvl+updated%3A2021-05-17..2021-06-01&type=Issues) ## 2.5.1 ### Maintenance and upkeep improvements - Remove Packaging Dependency [#181](https://github.com/jupyterlab/jupyterlab_server/pull/181) ([@jtpio](https://github.com/jtpio)) ## 2.5.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/2.4.0...f5009812e86ec47e36b3d90569d1eaa4771c9939)) ### Enhancements made - Support for overrides.json5 [#179](https://github.com/jupyterlab/jupyterlab_server/pull/179) ([@mlucool](https://github.com/mlucool)) ### Documentation improvements - Make LabConfig configurable and add config API docs [#172](https://github.com/jupyterlab/jupyterlab_server/pull/172) ([@afshin](https://github.com/afshin)) ### Other merged PRs - Upgrade packaging [#178](https://github.com/jupyterlab/jupyterlab_server/pull/178) ([@jtpio](https://github.com/jtpio)) - Add more API docs [#177](https://github.com/jupyterlab/jupyterlab_server/pull/177) ([@jtpio](https://github.com/jtpio)) - Add cli config options documentation [#176](https://github.com/jupyterlab/jupyterlab_server/pull/176) ([@jtpio](https://github.com/jtpio)) - Add example request/responses to REST docs [#174](https://github.com/jupyterlab/jupyterlab_server/pull/174) ([@blink1073](https://github.com/blink1073)) - Add Swagger Docs [#173](https://github.com/jupyterlab/jupyterlab_server/pull/173) ([@jtpio](https://github.com/jtpio)) - Update Readme Badges [#171](https://github.com/jupyterlab/jupyterlab_server/pull/171) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-03-30&to=2021-04-26&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aafshin+updated%3A2021-03-30..2021-04-26&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-03-30..2021-04-26&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ajtpio+updated%3A2021-03-30..2021-04-26&type=Issues) | [@mlucool](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Amlucool+updated%3A2021-03-30..2021-04-26&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2021-03-30..2021-04-26&type=Issues) ## 2.4.0 ([Full Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/2.3.0...1a9681654e627afdb67ec5fe1d6581617b813743)) ### Merged PRs - Add Sphinx docs [#169](https://github.com/jupyterlab/jupyterlab_server/pull/169) ([@afshin](https://github.com/afshin)) - Cleanup unused imports [#165](https://github.com/jupyterlab/jupyterlab_server/pull/165) ([@jtpio](https://github.com/jtpio)) - Fill in missing changelog entries [#164](https://github.com/jupyterlab/jupyterlab_server/pull/164) ([@blink1073](https://github.com/blink1073)) - Improve documentation: Instructions for development and test setups [#130](https://github.com/jupyterlab/jupyterlab_server/pull/130) ([@ZelphirKaltstahl](https://github.com/ZelphirKaltstahl)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-02-19&to=2021-03-30&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Aafshin+updated%3A2021-02-19..2021-03-30&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-02-19..2021-03-30&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ajtpio+updated%3A2021-02-19..2021-03-30&type=Issues) | [@ZelphirKaltstahl](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3AZelphirKaltstahl+updated%3A2021-02-19..2021-03-30&type=Issues) ## 2.3.0 - Set file_url_prefix trait for opening notebooks directly [#162](https://github.com/jupyterlab/jupyterlab_server/pull/162) ([@afshin](https://github.com/afshin)) ## 2.2.1 - Alleviate invalid locale issue by only setting it if language pack exists [#159](https://github.com/jupyterlab/jupyterlab_server/pull/159) ([@krassowski](https://github.com/krassowski)) ## 2.2.0 - Add URL and description for federated extension data [#154](https://github.com/jupyterlab/jupyterlab_server/pull/154) ([@krassowski](https://github.com/krassowski)) ## 2.1.5 - Fix/cp949 encoding error [#158](https://github.com/jupyterlab/jupyterlab_server/pull/158) (@k-takanori) ## 2.1.4 - Reduce path length of installed package [#157](https://github.com/jupyterlab/jupyterlab_server/pull/157) ([@afshin](https://github.com/afshin)) ## 2.1.3 - Fix mathjax URL handling [#153](https://github.com/jupyterlab/jupyterlab_server/pull/153) ([@afshin](https://github.com/afshin)) ## 2.1.2 - Handle mathjax_url properly [#150](https://github.com/jupyterlab/jupyterlab_server/pull/150) ([@afshin](https://github.com/afshin)) ## 2.1.1 - Move open_browser to ProcessApp class [#149](https://github.com/jupyterlab/jupyterlab_server/pull/149) ([@jasongrout](https://github.com/jasongrout)) ## 2.1.0 - PR: Add translation managers for server side translation [#136](https://github.com/jupyterlab/jupyterlab_server/pull/136) ([@goanpeca](https://github.com/goanpeca)) ## 2.0.0 - Update jupyter*server requirement to 1.* instead of 1.1.\_ [#144](https://github.com/jupyterlab/jupyterlab_server/pull/144) ([@jasongrout](https://github.com/jasongrout)) - Unpin pytest version [#143](https://github.com/jupyterlab/jupyterlab_server/pull/143) ([@afshin](https://github.com/afshin)) - don't patch event loop for tornado 6.1+ [#142](https://github.com/jupyterlab/jupyterlab_server/pull/142) ([@bollwyvl](https://github.com/bollwyvl)) - Update for new jupyter_server pytest plugin interface [#141](https://github.com/jupyterlab/jupyterlab_server/pull/141) ([@afshin](https://github.com/afshin)) - Handle source extensions that are disabled [#140](https://github.com/jupyterlab/jupyterlab_server/pull/140) ([@afshin](https://github.com/afshin)) - Fix var name typo from prev commit [#139](https://github.com/jupyterlab/jupyterlab_server/pull/139) ([@ajbozarth](https://github.com/ajbozarth)) - Ensure there is a disabled_key [#138](https://github.com/jupyterlab/jupyterlab_server/pull/138) ([@afshin](https://github.com/afshin)) - Update page_config handling [#137](https://github.com/jupyterlab/jupyterlab_server/pull/137) ([@afshin](https://github.com/afshin)) - Remove out of date LICENSE reference to json_minify [#135](https://github.com/jupyterlab/jupyterlab_server/pull/135) ([@afshin](https://github.com/afshin)) - Respect environment for page_config.json [#133](https://github.com/jupyterlab/jupyterlab_server/pull/133) ([@afshin](https://github.com/afshin)) - Support a metadata file in the lab extension directory [#132](https://github.com/jupyterlab/jupyterlab_server/pull/132) ([@jasongrout](https://github.com/jasongrout)) - Fixed bug where disabled extensions still ran [#131](https://github.com/jupyterlab/jupyterlab_server/pull/131) ([@ajbozarth](https://github.com/ajbozarth)) - dynamic => federated [#127](https://github.com/jupyterlab/jupyterlab_server/pull/127) ([@afshin](https://github.com/afshin)) - Bump jupyter_server dependency [#126](https://github.com/jupyterlab/jupyterlab_server/pull/126) ([@afshin](https://github.com/afshin)) - Add schemas to settings list [#125](https://github.com/jupyterlab/jupyterlab_server/pull/125) ([@afshin](https://github.com/afshin)) - Add handling of incomplete dynamic extension data [#124](https://github.com/jupyterlab/jupyterlab_server/pull/124) ([@blink1073](https://github.com/blink1073)) - Clean up handling of config [#123](https://github.com/jupyterlab/jupyterlab_server/pull/123) ([@blink1073](https://github.com/blink1073)) - Allow default setting overrides to be configurable in jupyter config [#122](https://github.com/jupyterlab/jupyterlab_server/pull/122) ([@blink1073](https://github.com/blink1073)) - Cache lab extension assets by default [#121](https://github.com/jupyterlab/jupyterlab_server/pull/121) ([@jasongrout](https://github.com/jasongrout)) - Added support for fullStaticUrl and fix handling of base_url [#120](https://github.com/jupyterlab/jupyterlab_server/pull/120) ([@Zsailer](https://github.com/Zsailer)) - Update minimum python version to 3.6 [#119](https://github.com/jupyterlab/jupyterlab_server/pull/119) ([@jasongrout](https://github.com/jasongrout)) - app_version should be a trait [#117](https://github.com/jupyterlab/jupyterlab_server/pull/117) ([@echarles](https://github.com/echarles)) - Use tilde for server requirement [#116](https://github.com/jupyterlab/jupyterlab_server/pull/116) ([@blink1073](https://github.com/blink1073)) - fix spurious logging issue [#115](https://github.com/jupyterlab/jupyterlab_server/pull/115) ([@Zsailer](https://github.com/Zsailer)) - Change payload parsing to accept JSON5 as raw string in JSON payload [#114](https://github.com/jupyterlab/jupyterlab_server/pull/114) ([@blink1073](https://github.com/blink1073)) - Use blocked/allowed extensions naming in JupyterLab Server [#111](https://github.com/jupyterlab/jupyterlab_server/pull/111) ([@echarles](https://github.com/echarles)) - Fix open_browser for process apps [#110](https://github.com/jupyterlab/jupyterlab_server/pull/110) ([@blink1073](https://github.com/blink1073)) - Add handling of dynamic labextensions [#109](https://github.com/jupyterlab/jupyterlab_server/pull/109) ([@blink1073](https://github.com/blink1073)) - UTF-8 all over, test with big unicode string [#108](https://github.com/jupyterlab/jupyterlab_server/pull/108) ([@bollwyvl](https://github.com/bollwyvl)) - fix file mtime/ctime confusion, restore win3.8, patch event loop [#107](https://github.com/jupyterlab/jupyterlab_server/pull/107) ([@bollwyvl](https://github.com/bollwyvl)) - PR: Add other OS and update py versions [#102](https://github.com/jupyterlab/jupyterlab_server/pull/102) ([@goanpeca](https://github.com/goanpeca)) - Changes needed for improved single document mode [#101](https://github.com/jupyterlab/jupyterlab_server/pull/101) ([@ellisonbg](https://github.com/ellisonbg)) - PR: Add CI with github [#100](https://github.com/jupyterlab/jupyterlab_server/pull/100) ([@goanpeca](https://github.com/goanpeca)) - Add last_modified and created to settings and workspace API items [#99](https://github.com/jupyterlab/jupyterlab_server/pull/99) ([@bollwyvl](https://github.com/bollwyvl)) - PR: Add a translations handler [#96](https://github.com/jupyterlab/jupyterlab_server/pull/96) ([@goanpeca](https://github.com/goanpeca)) - JupyterLab Server as Server Extension [#79](https://github.com/jupyterlab/jupyterlab_server/pull/79) ([@echarles](https://github.com/echarles)) ## 1.2.0 - Expose settings API to other handlers. [#94](https://github.com/jupyterlab/jupyterlab_server/pull/94) ([@goanpeca](https://github.com/goanpeca)) ## 1.1.5 - Always wait for process to finish [#93](https://github.com/jupyterlab/jupyterlab_server/pull/93) ([@blink1073](https://github.com/blink1073)) - Backport PR #91 on branch 1.0.x (Clean up terminate logic) [#92](https://github.com/jupyterlab/jupyterlab_server/pull/92) ([@meeseeksmachine](https://github.com/meeseeksmachine)) - ensure the 'WHICH' command returns absolute path instead of relative path [#72](https://github.com/jupyterlab/jupyterlab_server/pull/72) ([@tgrout](https://github.com/tgrout)) ## v1.1.4 - Clean up terminate logic [#91](https://github.com/jupyterlab/jupyterlab_server/pull/91) ([@blink1073](https://github.com/blink1073)) ## v1.1.2 - Start a Change Log [#90](https://github.com/jupyterlab/jupyterlab_server/pull/90) ([@blink1073](https://github.com/blink1073)) - Backport PR #88 on branch 1.0.x (Kill the subprocess if it does not stop) [#89](https://github.com/jupyterlab/jupyterlab_server/pull/89) ([@meeseeksmachine](https://github.com/meeseeksmachine)) - Kill the subprocess if it does not stop [#88](https://github.com/jupyterlab/jupyterlab_server/pull/88) ([@blink1073](https://github.com/blink1073)) ## v1.1.1 - Do not try to close the watch process file handle [#85](https://github.com/jupyterlab/jupyterlab_server/pull/85) ([@blink1073](https://github.com/blink1073)) - Update nodejs error message to not give an outdated version. [#84](https://github.com/jupyterlab/jupyterlab_server/pull/84) ([@jasongrout](https://github.com/jasongrout)) ## v1.0.9 - Backport PR #91 on branch 1.0.x (Clean up terminate logic) [#92](https://github.com/jupyterlab/jupyterlab_server/pull/92) ([@meeseeksmachine](https://github.com/meeseeksmachine)) - Clean up terminate logic [#91](https://github.com/jupyterlab/jupyterlab_server/pull/91) ([@blink1073](https://github.com/blink1073)) ## v1.0.8 - Start a Change Log [#90](https://github.com/jupyterlab/jupyterlab_server/pull/90) ([@blink1073](https://github.com/blink1073)) - Backport PR #88 on branch 1.0.x (Kill the subprocess if it does not stop) [#89](https://github.com/jupyterlab/jupyterlab_server/pull/89) ([@meeseeksmachine](https://github.com/meeseeksmachine)) - Kill the subprocess if it does not stop [#88](https://github.com/jupyterlab/jupyterlab_server/pull/88) ([@blink1073](https://github.com/blink1073)) - Do not try to close the watch process file handle [#85](https://github.com/jupyterlab/jupyterlab_server/pull/85) ([@blink1073](https://github.com/blink1073)) - Update nodejs error message to not give an outdated version. [#84](https://github.com/jupyterlab/jupyterlab_server/pull/84) ([@jasongrout](https://github.com/jasongrout)) - Black and White Listings Handler [#82](https://github.com/jupyterlab/jupyterlab_server/pull/82) ([@echarles](https://github.com/echarles)) ## v1.0.7 - Fix URL prefixing for absolute URLs [#81](https://github.com/jupyterlab/jupyterlab_server/pull/81) ([@santiagobasulto](https://github.com/santiagobasulto)) ## v1.0.6 - Add .json.orig files to sdists [#78](https://github.com/jupyterlab/jupyterlab_server/pull/78) ([@toddrme2178](https://github.com/toddrme2178)) ## v1.0.5 - Require jinja2 2.10+ to fix extra escaping [#77](https://github.com/jupyterlab/jupyterlab_server/pull/77) ([@blink1073](https://github.com/blink1073)) ## v1.0.4 - Use escape instead of urlencode for urls [#76](https://github.com/jupyterlab/jupyterlab_server/pull/76) ([@blink1073](https://github.com/blink1073)) ## v1.0.3 - Escape template values in Jinja [#75](https://github.com/jupyterlab/jupyterlab_server/pull/75) ([@jasongrout](https://github.com/jasongrout)) ## v1.0.2 - Add store ID to page config [#74](https://github.com/jupyterlab/jupyterlab_server/pull/74) ([@saulshanabrook](https://github.com/saulshanabrook)) ## v1.0.1 - fix page config escaping [#73](https://github.com/jupyterlab/jupyterlab_server/pull/73) ([@nicorikken](https://github.com/nicorikken)) ## v1.0.0 - Use json5 to load settings files. [#71](https://github.com/jupyterlab/jupyterlab_server/pull/71) ([@ian-r-rose](https://github.com/ian-r-rose)) - Cleanup for 1.0 [#70](https://github.com/jupyterlab/jupyterlab_server/pull/70) ([@blink1073](https://github.com/blink1073)) - A 403.html file. Should close jupyterlab issue#6065 [#69](https://github.com/jupyterlab/jupyterlab_server/pull/69) ([@zerline](https://github.com/zerline)) ## v0.3.4 - Stop using base_url in workspace name. [#68](https://github.com/jupyterlab/jupyterlab_server/pull/68) ([@afshin](https://github.com/afshin)) - v0.3.3 [#67](https://github.com/jupyterlab/jupyterlab_server/pull/67) ([@afshin](https://github.com/afshin)) ## v0.13.1 - Cleanup [#56](https://github.com/jupyterlab/jupyterlab_server/pull/56) ([@blink1073](https://github.com/blink1073)) ## v0.13.0 - Switch to Python 3.5+ [#55](https://github.com/jupyterlab/jupyterlab_server/pull/55) ([@blink1073](https://github.com/blink1073)) - Switch to jupyter_server [#49](https://github.com/jupyterlab/jupyterlab_server/pull/49) ([@SylvainCorlay](https://github.com/SylvainCorlay)) ## v0.12.1 - Fix PY2 handing of potential non-unicode paths being slugified. [#54](https://github.com/jupyterlab/jupyterlab_server/pull/54) ([@afshin](https://github.com/afshin)) ## v0.11.2 - Improve use of process quiet flag [#46](https://github.com/jupyterlab/jupyterlab_server/pull/46) ([@vidartf](https://github.com/vidartf)) ## v0.11.0 - Include LICENSE file in wheels [#45](https://github.com/jupyterlab/jupyterlab_server/pull/45) ([@toddrme2178](https://github.com/toddrme2178)) - move process utilities from jupyterlab to jupyterlab_launcher [#44](https://github.com/jupyterlab/jupyterlab_server/pull/44) ([@ivanov](https://github.com/ivanov)) - Update test for tornado 5 [#43](https://github.com/jupyterlab/jupyterlab_server/pull/43) ([@blink1073](https://github.com/blink1073)) - Include plugin/schema name in error messages. [#39](https://github.com/jupyterlab/jupyterlab_server/pull/39) ([@afshin](https://github.com/afshin)) ## v0.10.5 - Include test assets in sdist [#42](https://github.com/jupyterlab/jupyterlab_server/pull/42) ([@hroncok](https://github.com/hroncok)) ## v0.10.3 - Fix the URL patterns: ujoin() creates slashes. [#38](https://github.com/jupyterlab/jupyterlab_server/pull/38) ([@afshin](https://github.com/afshin)) ## v0.10.2 - Bug fix: base_url was used twice. [#36](https://github.com/jupyterlab/jupyterlab_server/pull/36) ([@afshin](https://github.com/afshin)) ## v0.10.1 - Don't have NotFoundHandler match lab\* [#35](https://github.com/jupyterlab/jupyterlab_server/pull/35) ([@vidartf](https://github.com/vidartf)) ## v0.10.0 - Workspaces [#34](https://github.com/jupyterlab/jupyterlab_server/pull/34) ([@afshin](https://github.com/afshin)) ## v0.9.1 - Fix path handling on Windows [#33](https://github.com/jupyterlab/jupyterlab_server/pull/33) ([@blink1073](https://github.com/blink1073)) ## v0.8.0 - Add a theme handler [#32](https://github.com/jupyterlab/jupyterlab_server/pull/32) ([@blink1073](https://github.com/blink1073)) ## v0.7.0 - Require jsonschema library. [#30](https://github.com/jupyterlab/jupyterlab_server/pull/30) ([@afshin](https://github.com/afshin)) - Allow an admin to override extension schema defaults. [#29](https://github.com/jupyterlab/jupyterlab_server/pull/29) ([@afshin](https://github.com/afshin)) - Remove MathJax and Streamline Handler config [#27](https://github.com/jupyterlab/jupyterlab_server/pull/27) ([@blink1073](https://github.com/blink1073)) ## v0.6.0 - Add error message option [#26](https://github.com/jupyterlab/jupyterlab_server/pull/26) ([@blink1073](https://github.com/blink1073)) - Update settings handler to support JSON with comments. [#25](https://github.com/jupyterlab/jupyterlab_server/pull/25) ([@afshin](https://github.com/afshin)) ## v0.5.5 - Add console error on error page [#24](https://github.com/jupyterlab/jupyterlab_server/pull/24) ([@blink1073](https://github.com/blink1073)) ## v0.5.4 - Clean up static dir handling [#23](https://github.com/jupyterlab/jupyterlab_server/pull/23) ([@blink1073](https://github.com/blink1073)) ## v0.5.3 - Do not cache themes [#22](https://github.com/jupyterlab/jupyterlab_server/pull/22) ([@blink1073](https://github.com/blink1073)) ## v0.5.2 - Cleanup and handle no assets dir [#20](https://github.com/jupyterlab/jupyterlab_server/pull/20) ([@blink1073](https://github.com/blink1073)) ## v0.5.1 - Fix handling of user settings dir [#21](https://github.com/jupyterlab/jupyterlab_server/pull/21) ([@blink1073](https://github.com/blink1073)) ## v0.5.0 - Update settings handler to parse complex URL paths. [#19](https://github.com/jupyterlab/jupyterlab_server/pull/19) ([@afshin](https://github.com/afshin)) ## v0.4.2 - Remove trailing slash to fix theme relative urls [#18](https://github.com/jupyterlab/jupyterlab_server/pull/18) ([@blink1073](https://github.com/blink1073)) ## v0.4.1 - Do not cache the static files [#17](https://github.com/jupyterlab/jupyterlab_server/pull/17) ([@blink1073](https://github.com/blink1073)) ## v0.4.0 - Theme and settings [#16](https://github.com/jupyterlab/jupyterlab_server/pull/16) ([@blink1073](https://github.com/blink1073)) ## v0.3.0 - Switch to a static namespace for webpack files [#13](https://github.com/jupyterlab/jupyterlab_server/pull/13) ([@blink1073](https://github.com/blink1073)) ## v0.2.9 - We do not need to encode server provided urls [#12](https://github.com/jupyterlab/jupyterlab_server/pull/12) ([@blink1073](https://github.com/blink1073)) - Add simple travis file [#11](https://github.com/jupyterlab/jupyterlab_server/pull/11) ([@blink1073](https://github.com/blink1073)) ## v0.2.8 - Fix addition of public path handler [#9](https://github.com/jupyterlab/jupyterlab_server/pull/9) ([@blink1073](https://github.com/blink1073)) - Add backwards compatibility for public url [#8](https://github.com/jupyterlab/jupyterlab_server/pull/8) ([@blink1073](https://github.com/blink1073)) ## v0.2.7 - Fix handling of page config [#7](https://github.com/jupyterlab/jupyterlab_server/pull/7) ([@blink1073](https://github.com/blink1073)) ## v0.2.6 - Fix handling of base and ws urls [#6](https://github.com/jupyterlab/jupyterlab_server/pull/6) ([@blink1073](https://github.com/blink1073)) ## v0.2.4 - Escape strings in jupyter-config-data json [#5](https://github.com/jupyterlab/jupyterlab_server/pull/5) ([@seibs](https://github.com/seibs)) ## v0.2.1 - Fix handling of data [#4](https://github.com/jupyterlab/jupyterlab_server/pull/4) ([@blink1073](https://github.com/blink1073)) ## v0.2.0 - Clean up config handling [#3](https://github.com/jupyterlab/jupyterlab_server/pull/3) ([@blink1073](https://github.com/blink1073)) jupyterlab_server-2.25.2/CONTRIBUTING.md000066400000000000000000000045561452610450300176140ustar00rootroot00000000000000# Contributing If you're reading this section, you're probably interested in contributing to Jupyter. Welcome and thanks for your interest in contributing! Please take a look at the Contributor documentation, familiarize yourself with using the Jupyter Server, and introduce yourself on the mailing list and share what area of the project you are interested in working on. For general documentation about contributing to Jupyter projects, see the [Project Jupyter Contributor Documentation](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html). ## Development Install ```shell git clone https://github.com/jupyterlab/jupyterlab_server.git cd jupyterlab_server pip install -e . ``` ## Testing It is probably best to create a virtual environment to create a local test setup. There are multiple tools for creating a Python virtual environment out there from which you can choose the one you like best. To create a local test setup run the following commands (inside your virtual environment, if you chose to create one): ```shell git clone https://github.com/jupyterlab/jupyterlab_server.git cd jupyterlab_server pip install -e .[test] # install test dependencies hatch run cov:test # optionally, arguments of the pytest CLI can be added ``` ## Code Styling `jupyterlab_server` has adopted automatic code formatting so you shouldn't need to worry too much about your code style. As long as your code is valid, the pre-commit hook should take care of how it should look. `pre-commit` and its associated hooks will automatically be installed when you run `pip install -e ".[test]"` To install `pre-commit` manually, run the following:: ```shell pip install pre-commit pre-commit install ``` You can invoke the pre-commit hook by hand at any time with: ```shell pre-commit run ``` which should run any autoformatting on your code and tell you about any errors it couldn't fix automatically. You may also install [black integration](https://github.com/psf/black#editor-integration) into your text editor to format code automatically. If you have already committed files before setting up the pre-commit hook with `pre-commit install`, you can fix everything up using `pre-commit run --all-files`. You need to make the fixing commit yourself after that. Some of the hooks only run on CI by default, but you can invoke them by running with the `--hook-stage manual` argument. jupyterlab_server-2.25.2/LICENSE000066400000000000000000000027571452610450300163710ustar00rootroot00000000000000Copyright (c) 2015-2017, Project Jupyter Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jupyterlab_server-2.25.2/README.md000066400000000000000000000024341452610450300166330ustar00rootroot00000000000000# jupyterlab server [![Build Status](https://github.com/jupyterlab/jupyterlab_server/workflows/Tests/badge.svg?branch=master)](https://github.com/jupyterlab/jupyterlab_server/actions?query=branch%3Amaster+workflow%3A%22Tests%22) [![Documentation Status](https://readthedocs.org/projects/jupyterlab-server/badge/?version=stable)](http://jupyterlab-server.readthedocs.io/en/stable/) ## Motivation JupyterLab Server sits between JupyterLab and Jupyter Server, and provides a set of REST API handlers and utilities that are used by JupyterLab. It is a separate project in order to accommodate creating JupyterLab-like applications from a more limited scope. ## Install `pip install jupyterlab_server` To include optional `openapi` dependencies, use: `pip install jupyterlab_server[openapi]` To include optional `pytest_plugin` dependencies, use: `pip install jupyterlab_server[test]` ## Usage See the full documentation for [API docs](https://jupyterlab-server.readthedocs.io/en/stable/api/index.html) and [REST endpoint descriptions](https://jupyterlab-server.readthedocs.io/en/stable/api/rest.html). ## Extending the Application Subclass the `LabServerApp` and provide additional traits and handlers as appropriate for your application. ## Contribution Please see `CONTRIBUTING.md` for details. jupyterlab_server-2.25.2/RELEASE.md000066400000000000000000000012721452610450300167550ustar00rootroot00000000000000# Making a JupyterLab Server Release ## Using `jupyter_releaser` The recommended way to make a release is to use [`jupyter_releaser`](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html) ## Manual Release ### Set up ``` pip install pipx git pull origin $(git branch --show-current) git clean -dffx ``` ### Update the version and apply the tag ``` echo "Enter new version" read script_version pipx run hatch version ${script_version} git tag -a ${script_version} -m ${script_version} ``` ### Build the artifacts ``` rm -rf dist pipx run build . ``` ### Publish the artifacts to pypi ``` pipx run twine check dist/* pipx run twine upload dist/* ``` jupyterlab_server-2.25.2/docs/000077500000000000000000000000001452610450300163015ustar00rootroot00000000000000jupyterlab_server-2.25.2/docs/Makefile000066400000000000000000000013441452610450300177430ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. # Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) jupyterlab_server-2.25.2/docs/make.bat000066400000000000000000000015461452610450300177140ustar00rootroot00000000000000rem Copyright (c) Jupyter Development Team. rem Distributed under the terms of the Modified BSD License. @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd jupyterlab_server-2.25.2/docs/source/000077500000000000000000000000001452610450300176015ustar00rootroot00000000000000jupyterlab_server-2.25.2/docs/source/api/000077500000000000000000000000001452610450300203525ustar00rootroot00000000000000jupyterlab_server-2.25.2/docs/source/api/app.rst000066400000000000000000000005711452610450300216670ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. =========== Application =========== Module: :mod:`jupyterlab_server.app` ==================================== .. automodule:: jupyterlab_server.app .. currentmodule:: jupyterlab_server.app :class:`LabServerApp` --------------------- .. autoconfigurable:: LabServerApp jupyterlab_server-2.25.2/docs/source/api/config.rst000066400000000000000000000010611452610450300223470ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. ====== Config ====== Module: :mod:`jupyterlab_server.config` ======================================= .. automodule:: jupyterlab_server.config .. currentmodule:: jupyterlab_server.config .. autofunction:: get_package_url .. autofunction:: get_federated_extensions .. autofunction:: get_static_page_config .. autofunction:: get_page_config .. autofunction:: write_page_config :class:`LabConfig` --------------------- .. autoconfigurable:: LabConfig jupyterlab_server-2.25.2/docs/source/api/handlers.rst000066400000000000000000000034061452610450300227070ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. ======== Handlers ======== Module: :mod:`jupyterlab_server.handlers` ========================================= .. automodule:: jupyterlab_server.handlers .. currentmodule:: jupyterlab_server.handlers .. autoclass:: LabHandler :members: .. autofunction:: add_handlers .. autofunction:: is_url Module: :mod:`jupyterlab_server.listings_handler` ================================================= .. automodule:: jupyterlab_server.listings_handler .. currentmodule:: jupyterlab_server.listings_handler .. autoclass:: ListingsHandler :members: .. autofunction:: fetch_listings Module: :mod:`jupyterlab_server.settings_handler` ================================================= .. automodule:: jupyterlab_server.settings_handler .. currentmodule:: jupyterlab_server.settings_handler .. autoclass:: SettingsHandler :members: .. autofunction:: get_settings Module: :mod:`jupyterlab_server.themes_handler` ================================================= .. automodule:: jupyterlab_server.themes_handler .. currentmodule:: jupyterlab_server.themes_handler .. autoclass:: ThemesHandler :members: Module: :mod:`jupyterlab_server.translations_handler` ===================================================== .. automodule:: jupyterlab_server.translations_handler .. currentmodule:: jupyterlab_server.translations_handler .. autoclass:: TranslationsHandler :members: Module: :mod:`jupyterlab_server.workspaces_handler` ===================================================== .. automodule:: jupyterlab_server.workspaces_handler .. currentmodule:: jupyterlab_server.workspaces_handler .. autoclass:: WorkspacesHandler :members: .. autofunction:: slugify jupyterlab_server-2.25.2/docs/source/api/index.rst000066400000000000000000000005211452610450300222110ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. --------------------- JupyterLab Server API --------------------- JupyterLab Server API Reference: .. toctree:: :maxdepth: 1 :caption: Contents: app-config app config handlers process rest spec jupyterlab_server-2.25.2/docs/source/api/process.rst000066400000000000000000000012351452610450300225630ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. ======= Process ======= Module: :mod:`jupyterlab_server.process` ======================================== .. automodule:: jupyterlab_server.process .. currentmodule:: jupyterlab_server.process .. autoclass:: Process :members: .. autoclass:: WatchHelper :members: .. autofunction:: which Module: :mod:`jupyterlab_server.process_app` ============================================ .. automodule:: jupyterlab_server.process_app .. currentmodule:: jupyterlab_server.process_app :class:`ProcessApp` ------------------- .. autoconfigurable:: ProcessApp jupyterlab_server-2.25.2/docs/source/api/rest.rst000066400000000000000000000011541452610450300220620ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. -------- REST API -------- The same JupyterLab Server API spec, as found here, is available in an interactive form `here (on swagger's petstore) `__. The `OpenAPI Initiative`_ (fka Swagger™) is a project used to describe and document RESTful APIs. .. openapi:: ../../../jupyterlab_server/rest-api.yml :examples: .. _OpenAPI Initiative: https://www.openapis.org/ jupyterlab_server-2.25.2/docs/source/api/spec.rst000066400000000000000000000005771452610450300220470ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. ============= OpenAPI Specs ============= Module: :mod:`jupyterlab_server.spec` ===================================== .. automodule:: jupyterlab_server.spec .. currentmodule:: jupyterlab_server.spec .. autofunction:: get_openapi_spec .. autofunction:: get_openapi_spec_dict jupyterlab_server-2.25.2/docs/source/conf.py000066400000000000000000000077541452610450300211150ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import os.path as osp import shutil import sys HERE = osp.abspath(osp.dirname(__file__)) sys.path.insert(0, osp.join(HERE, "..", "..")) from jupyterlab_server import LabServerApp, _version # noqa # -- Project information ----------------------------------------------------- project = "JupyterLab Server" copyright = "2021, Project Jupyter" # noqa author = "Project Jupyter" # The short X.Y version. version = "%i.%i" % _version.version_info[:2] # The full version, including alpha/beta/rc tags. release = _version.__version__ # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "myst_parser", "autodoc_traits", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinxcontrib.openapi", "sphinx.ext.napoleon", "sphinx.ext.mathjax", "sphinx_copybutton", ] try: import enchant # type:ignore # noqa extensions += ["sphinxcontrib.spelling"] except ImportError: pass myst_enable_extensions = ["html_image"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # html_theme = "pydata_sphinx_theme" # Add an Edit this Page button html_theme_options = { "use_edit_page_button": True, "navigation_with_keys": False, } # Output for github to be used in links html_context = { "github_user": "jupyterlab", # Username "github_repo": "jupyterlab_server", # Repo name "github_version": "master", # Version "doc_path": "/docs/source/", # Path in the checkout to the docs root } # This option generates errors when methods do not have docstrings, # so disable numpydoc_show_class_members = False config_header = """\ .. _api-full-config: Config file and command line options ==================================== The JupyterLab Server can be run with a variety of command line arguments. A list of available options can be found below in the :ref:`options section `. Defaults for these options can also be set by creating a file named ``jupyter_jupyterlab_server_config.py`` in your Jupyter folder. The Jupyter folder is in your home directory, ``~/.jupyter``. To create a ``jupyter_jupyterlab_server_config.py`` file, with all the defaults commented out, you can use the following command line:: $ python -m jupyterlab_server --generate-config .. _options: Options ------- This list of options can be generated by running the following and hitting enter:: $ python -m jupyterlab_server --help-all """ def setup(app): dest = osp.join(HERE, "changelog.md") shutil.copy(osp.join(HERE, "..", "..", "CHANGELOG.md"), dest) destination = osp.join(HERE, "api/app-config.rst") with open(destination, "w") as f: f.write(config_header) f.write(LabServerApp().document_config_options()) jupyterlab_server-2.25.2/docs/source/index.rst000066400000000000000000000011271452610450300214430ustar00rootroot00000000000000.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. .. jupyterlab_server documentation master file, created by sphinx-quickstart on Tue Mar 30 03:25:58 2021. You can adapt this file completely to your liking, but it should at least contain the root ``toctree`` directive. Welcome to JupyterLab Server's documentation! ============================================= .. toctree:: :maxdepth: 1 :caption: Contents: changelog api/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` jupyterlab_server-2.25.2/jupyterlab_server/000077500000000000000000000000001452610450300211205ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/__init__.py000066400000000000000000000016261452610450300232360ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from typing import Any from ._version import __version__ from .app import LabServerApp from .handlers import LabConfig, LabHandler, add_handlers from .licenses_app import LicensesApp from .spec import get_openapi_spec, get_openapi_spec_dict # noqa from .translation_utils import translator from .workspaces_app import WorkspaceExportApp, WorkspaceImportApp, WorkspaceListApp from .workspaces_handler import WORKSPACE_EXTENSION, slugify __all__ = [ "__version__", "add_handlers", "LabConfig", "LabHandler", "LabServerApp", "LicensesApp", "slugify", "translator", "WORKSPACE_EXTENSION", "WorkspaceExportApp", "WorkspaceImportApp", "WorkspaceListApp", ] def _jupyter_server_extension_points() -> Any: return [{"module": "jupyterlab_server", "app": LabServerApp}] jupyterlab_server-2.25.2/jupyterlab_server/__main__.py000066400000000000000000000003701452610450300232120ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """CLI entry point for jupyterlab server.""" import sys from jupyterlab_server.app import main sys.exit(main()) # type:ignore[no-untyped-call] jupyterlab_server-2.25.2/jupyterlab_server/_version.py000066400000000000000000000010271452610450300233160ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """ store the current version info of the server. """ import re __version__ = "2.25.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) assert match is not None parts: list = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) version_info = tuple(parts) jupyterlab_server-2.25.2/jupyterlab_server/app.py000066400000000000000000000112671452610450300222610ustar00rootroot00000000000000"""JupyterLab Server Application""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from glob import glob from os.path import relpath from typing import Any from jupyter_server.extension.application import ExtensionApp, ExtensionAppJinjaMixin from jupyter_server.utils import url_path_join as ujoin from traitlets import Dict, Integer, Unicode, observe from ._version import __version__ from .handlers import LabConfig, add_handlers class LabServerApp(ExtensionAppJinjaMixin, LabConfig, ExtensionApp): """A Lab Server Application that runs out-of-the-box""" name = "jupyterlab_server" extension_url = "/lab" app_name = "JupyterLab Server Application" # type:ignore[assignment] file_url_prefix = "/lab/tree" # type:ignore[assignment] @property def app_namespace(self) -> str: # type:ignore[override] return self.name default_url = Unicode("/lab", help="The default URL to redirect to from `/`") # Should your extension expose other server extensions when launched directly? load_other_extensions = True app_version = Unicode("", help="The version of the application.").tag(default=__version__) blacklist_uris = Unicode( "", config=True, help="Deprecated, use `LabServerApp.blocked_extensions_uris`" ) blocked_extensions_uris = Unicode( "", config=True, help=""" A list of comma-separated URIs to get the blocked extensions list .. versionchanged:: 2.0.0 `LabServerApp.blacklist_uris` renamed to `blocked_extensions_uris` """, ) whitelist_uris = Unicode( "", config=True, help="Deprecated, use `LabServerApp.allowed_extensions_uris`" ) allowed_extensions_uris = Unicode( "", config=True, help=""" "A list of comma-separated URIs to get the allowed extensions list .. versionchanged:: 2.0.0 `LabServerApp.whitetlist_uris` renamed to `allowed_extensions_uris` """, ) listings_refresh_seconds = Integer( 60 * 60, config=True, help="The interval delay in seconds to refresh the lists" ) listings_request_options = Dict( {}, config=True, help="The optional kwargs to use for the listings HTTP requests \ as described on https://2.python-requests.org/en/v2.7.0/api/#requests.request", ) _deprecated_aliases = { "blacklist_uris": ("blocked_extensions_uris", "1.2"), "whitelist_uris": ("allowed_extensions_uris", "1.2"), } # Method copied from # https://github.com/jupyterhub/jupyterhub/blob/d1a85e53dccfc7b1dd81b0c1985d158cc6b61820/jupyterhub/auth.py#L143-L161 @observe(*list(_deprecated_aliases)) def _deprecated_trait(self, change: Any) -> None: """observer for deprecated traits""" old_attr = change.name new_attr, version = self._deprecated_aliases.get(old_attr) # type:ignore[misc] new_value = getattr(self, new_attr) if new_value != change.new: # only warn if different # protects backward-compatible config from warnings # if they set the same value under both names self.log.warning( "{cls}.{old} is deprecated in JupyterLab {version}, use {cls}.{new} instead".format( cls=self.__class__.__name__, old=old_attr, new=new_attr, version=version, ) ) setattr(self, new_attr, change.new) def initialize_settings(self) -> None: """Initialize the settings: set the static files as immutable, since they should have all hashed name. """ immutable_cache = set(self.settings.get("static_immutable_cache", [])) # Set lab static files as immutables immutable_cache.add(self.static_url_prefix) # Set extensions static files as immutables for extension_path in self.labextensions_path + self.extra_labextensions_path: extensions_url = [ ujoin(self.labextensions_url, relpath(path, extension_path)) for path in glob(f"{extension_path}/**/static", recursive=True) ] immutable_cache.update(extensions_url) self.settings.update({"static_immutable_cache": list(immutable_cache)}) def initialize_templates(self) -> None: """Initialize templates.""" self.static_paths = [self.static_dir] self.template_paths = [self.templates_dir] def initialize_handlers(self) -> None: """Initialize handlers.""" add_handlers(self.handlers, self) main = launch_new_instance = LabServerApp.launch_instance jupyterlab_server-2.25.2/jupyterlab_server/config.py000066400000000000000000000324001452610450300227360ustar00rootroot00000000000000"""JupyterLab Server config""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import json import os.path as osp from glob import iglob from itertools import chain from logging import Logger from os.path import join as pjoin from typing import Any import json5 # type:ignore[import-untyped] from jupyter_core.paths import SYSTEM_CONFIG_PATH, jupyter_config_dir, jupyter_path from jupyter_server.services.config.manager import ConfigManager, recursive_update from jupyter_server.utils import url_path_join as ujoin from traitlets import Bool, HasTraits, List, Unicode, default # ----------------------------------------------------------------------------- # Module globals # ----------------------------------------------------------------------------- DEFAULT_TEMPLATE_PATH = osp.join(osp.dirname(__file__), "templates") def get_package_url(data: dict[str, Any]) -> str: """Get the url from the extension data""" # homepage, repository are optional if "homepage" in data: url = data["homepage"] elif "repository" in data and isinstance(data["repository"], dict): url = data["repository"].get("url", "") else: url = "" return url def get_federated_extensions(labextensions_path: list[str]) -> dict[str, Any]: """Get the metadata about federated extensions""" federated_extensions = {} for ext_dir in labextensions_path: # extensions are either top-level directories, or two-deep in @org directories for ext_path in chain( iglob(pjoin(ext_dir, "[!@]*", "package.json")), iglob(pjoin(ext_dir, "@*", "*", "package.json")), ): with open(ext_path, encoding="utf-8") as fid: pkgdata = json.load(fid) if pkgdata["name"] not in federated_extensions: data = dict( name=pkgdata["name"], version=pkgdata["version"], description=pkgdata.get("description", ""), url=get_package_url(pkgdata), ext_dir=ext_dir, ext_path=osp.dirname(ext_path), is_local=False, dependencies=pkgdata.get("dependencies", dict()), jupyterlab=pkgdata.get("jupyterlab", dict()), ) # Add repository info if available if "repository" in pkgdata and "url" in pkgdata.get("repository", {}): data["repository"] = dict(url=pkgdata.get("repository").get("url")) install_path = osp.join(osp.dirname(ext_path), "install.json") if osp.exists(install_path): with open(install_path, encoding="utf-8") as fid: data["install"] = json.load(fid) federated_extensions[data["name"]] = data return federated_extensions def get_static_page_config( app_settings_dir: str | None = None, logger: Logger | None = None, level: str = "all" ) -> dict[str, Any]: """Get the static page config for JupyterLab Parameters ---------- logger: logger, optional An optional logging object level: string, optional ['all'] The level at which to get config: can be 'all', 'user', 'sys_prefix', or 'system' """ cm = _get_config_manager(level) return cm.get("page_config") # type:ignore[no-untyped-call] def load_config(path: str) -> Any: """Load either a json5 or a json config file. Parameters ---------- path : str Path to the file to be loaded Returns ------- Dict[Any, Any] Dictionary of json or json5 data """ with open(path, encoding="utf-8") as fid: if path.endswith(".json5"): return json5.load(fid) else: return json.load(fid) def get_page_config( # noqa: PLR0915 labextensions_path: list[str], app_settings_dir: str | None = None, logger: Logger | None = None ) -> dict[str, Any]: """Get the page config for the application handler""" # Build up the full page config page_config: dict = {} disabled_key = "disabledExtensions" # Start with the app_settings_dir as lowest priority if app_settings_dir: config_paths = [ pjoin(app_settings_dir, "page_config.json5"), pjoin(app_settings_dir, "page_config.json"), ] for path in config_paths: if osp.exists(path): data = load_config(path) # Convert lists to dicts for key in [disabled_key, "deferredExtensions"]: if key in data: data[key] = {key: True for key in data[key]} recursive_update(page_config, data) break # Get the traitlets config static_page_config = get_static_page_config(logger=logger, level="all") recursive_update(page_config, static_page_config) # Handle federated extensions that disable other extensions disabled_by_extensions_all = {} extensions = page_config["federated_extensions"] = [] federated_exts = get_federated_extensions(labextensions_path) # Ensure there is a disabled key page_config.setdefault(disabled_key, {}) for _, ext_data in federated_exts.items(): if "_build" not in ext_data["jupyterlab"]: if logger: logger.warning("%s is not a valid extension" % ext_data["name"]) continue extbuild = ext_data["jupyterlab"]["_build"] extension = {"name": ext_data["name"], "load": extbuild["load"]} if "extension" in extbuild: extension["extension"] = extbuild["extension"] if "mimeExtension" in extbuild: extension["mimeExtension"] = extbuild["mimeExtension"] if "style" in extbuild: extension["style"] = extbuild["style"] extensions.append(extension) # If there is disabledExtensions metadata, consume it. name = ext_data["name"] if ext_data["jupyterlab"].get(disabled_key): disabled_by_extensions_all[ext_data["name"]] = ext_data["jupyterlab"][disabled_key] # Handle source extensions that disable other extensions # Check for `jupyterlab`:`extensionMetadata` in the built application directory's package.json if app_settings_dir: app_dir = osp.dirname(app_settings_dir) package_data_file = pjoin(app_dir, "static", "package.json") if osp.exists(package_data_file): with open(package_data_file, encoding="utf-8") as fid: app_data = json.load(fid) all_ext_data = app_data["jupyterlab"].get("extensionMetadata", {}) for ext, ext_data in all_ext_data.items(): if ext in disabled_by_extensions_all: continue if ext_data.get(disabled_key): disabled_by_extensions_all[ext] = ext_data[disabled_key] disabled_by_extensions = {} for name in sorted(disabled_by_extensions_all): # skip if the extension itself is disabled by other config if page_config[disabled_key].get(name) is True: continue disabled_list = disabled_by_extensions_all[name] for item in disabled_list: disabled_by_extensions[item] = True rollup_disabled = disabled_by_extensions rollup_disabled.update(page_config.get(disabled_key, [])) page_config[disabled_key] = rollup_disabled # Convert dictionaries to lists to give to the front end for key, value in page_config.items(): if isinstance(value, dict): page_config[key] = [subkey for subkey in value if value[subkey]] return page_config def write_page_config(page_config: dict[str, Any], level: str = "all") -> None: """Write page config to disk""" cm = _get_config_manager(level) cm.set("page_config", page_config) # type:ignore[no-untyped-call] class LabConfig(HasTraits): """The lab application configuration object.""" app_name = Unicode("", help="The name of the application.").tag(config=True) app_version = Unicode("", help="The version of the application.").tag(config=True) app_namespace = Unicode("", help="The namespace of the application.").tag(config=True) app_url = Unicode("/lab", help="The url path for the application.").tag(config=True) app_settings_dir = Unicode("", help="The application settings directory.").tag(config=True) extra_labextensions_path = List( Unicode(), help="""Extra paths to look for federated JupyterLab extensions""" ).tag(config=True) labextensions_path = List( Unicode(), help="The standard paths to look in for federated JupyterLab extensions" ).tag(config=True) templates_dir = Unicode("", help="The application templates directory.").tag(config=True) static_dir = Unicode( "", help=( "The optional location of local static files. " "If given, a static file handler will be " "added." ), ).tag(config=True) labextensions_url = Unicode("", help="The url for federated JupyterLab extensions").tag( config=True ) settings_url = Unicode(help="The url path of the settings handler.").tag(config=True) user_settings_dir = Unicode( "", help=("The optional location of the user settings directory.") ).tag(config=True) schemas_dir = Unicode( "", help=( "The optional location of the settings " "schemas directory. If given, a handler will " "be added for settings." ), ).tag(config=True) workspaces_api_url = Unicode(help="The url path of the workspaces API.").tag(config=True) workspaces_dir = Unicode( "", help=( "The optional location of the saved " "workspaces directory. If given, a handler " "will be added for workspaces." ), ).tag(config=True) listings_url = Unicode(help="The listings url.").tag(config=True) themes_url = Unicode(help="The theme url.").tag(config=True) licenses_url = Unicode(help="The third-party licenses url.") themes_dir = Unicode( "", help=( "The optional location of the themes " "directory. If given, a handler will be added " "for themes." ), ).tag(config=True) translations_api_url = Unicode(help="The url path of the translations handler.").tag( config=True ) tree_url = Unicode(help="The url path of the tree handler.").tag(config=True) cache_files = Bool( True, help=("Whether to cache files on the server. This should be `True` except in dev mode."), ).tag(config=True) notebook_starts_kernel = Bool( True, help="Whether a notebook should start a kernel automatically." ).tag(config=True) copy_absolute_path = Bool( False, help="Whether getting a relative (False) or absolute (True) path when copying a path.", ).tag(config=True) @default("template_dir") def _default_template_dir(self) -> str: return DEFAULT_TEMPLATE_PATH @default("labextensions_url") def _default_labextensions_url(self) -> str: return ujoin(self.app_url, "extensions/") @default("labextensions_path") def _default_labextensions_path(self) -> list[str]: return jupyter_path("labextensions") @default("workspaces_url") def _default_workspaces_url(self) -> str: return ujoin(self.app_url, "workspaces/") @default("workspaces_api_url") def _default_workspaces_api_url(self) -> str: return ujoin(self.app_url, "api", "workspaces/") @default("settings_url") def _default_settings_url(self) -> str: return ujoin(self.app_url, "api", "settings/") @default("listings_url") def _default_listings_url(self) -> str: return ujoin(self.app_url, "api", "listings/") @default("themes_url") def _default_themes_url(self) -> str: return ujoin(self.app_url, "api", "themes/") @default("licenses_url") def _default_licenses_url(self) -> str: return ujoin(self.app_url, "api", "licenses/") @default("tree_url") def _default_tree_url(self) -> str: return ujoin(self.app_url, "tree/") @default("translations_api_url") def _default_translations_api_url(self) -> str: return ujoin(self.app_url, "api", "translations/") def _get_config_manager(level: str) -> ConfigManager: """Get the location of config files for the current context Returns the string to the environment """ allowed = ["all", "user", "sys_prefix", "system", "app", "extension"] if level not in allowed: msg = f"Page config level must be one of: {allowed}" raise ValueError(msg) config_name = "labconfig" if level == "all": return ConfigManager(config_dir_name=config_name) if level == "user": config_dir = jupyter_config_dir() elif level == "sys_prefix": # Delayed import since this gets monkey-patched in tests from jupyter_core.paths import ENV_CONFIG_PATH config_dir = ENV_CONFIG_PATH[0] else: config_dir = SYSTEM_CONFIG_PATH[0] full_config_path = osp.join(config_dir, config_name) return ConfigManager(read_config_path=[full_config_path], write_config_dir=full_config_path) jupyterlab_server-2.25.2/jupyterlab_server/handlers.py000066400000000000000000000321471452610450300233010ustar00rootroot00000000000000"""JupyterLab Server handlers""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import os import pathlib import warnings from functools import lru_cache from typing import TYPE_CHECKING, Any from urllib.parse import urlparse from jupyter_server.base.handlers import FileFindHandler, JupyterHandler from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin from jupyter_server.utils import url_path_join as ujoin from tornado import template, web from .config import LabConfig, get_page_config, recursive_update from .licenses_handler import LicensesHandler, LicensesManager from .listings_handler import ListingsHandler, fetch_listings from .settings_handler import SettingsHandler from .themes_handler import ThemesHandler from .translations_handler import TranslationsHandler from .workspaces_handler import WorkspacesHandler, WorkspacesManager if TYPE_CHECKING: from .app import LabServerApp # ----------------------------------------------------------------------------- # Module globals # ----------------------------------------------------------------------------- MASTER_URL_PATTERN = ( r"/(?P{}|doc)(?P/workspaces/[a-zA-Z0-9\-\_]+)?(?P/tree/.*)?" ) DEFAULT_TEMPLATE = template.Template( """ Error

Cannot find template: "{{name}}"

In "{{path}}"

""" ) def is_url(url: str) -> bool: """Test whether a string is a full url (e.g. https://nasa.gov) https://stackoverflow.com/a/52455972 """ try: result = urlparse(url) return all([result.scheme, result.netloc]) except ValueError: return False class LabHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): """Render the JupyterLab View.""" @lru_cache() # noqa def get_page_config(self) -> dict[str, Any]: """Construct the page config object""" self.application.store_id = getattr( # type:ignore[attr-defined] self.application, "store_id", 0 ) config = LabConfig() app: LabServerApp = self.extensionapp # type:ignore[assignment] settings_dir = app.app_settings_dir # Handle page config data. page_config = self.settings.setdefault("page_config_data", {}) terminals = self.settings.get("terminals_available", False) server_root = self.settings.get("server_root_dir", "") server_root = server_root.replace(os.sep, "/") base_url = self.settings.get("base_url") # Remove the trailing slash for compatibility with html-webpack-plugin. full_static_url = self.static_url_prefix.rstrip("/") page_config.setdefault("fullStaticUrl", full_static_url) page_config.setdefault("terminalsAvailable", terminals) page_config.setdefault("ignorePlugins", []) page_config.setdefault("serverRoot", server_root) page_config["store_id"] = self.application.store_id # type:ignore[attr-defined] server_root = os.path.normpath(os.path.expanduser(server_root)) preferred_path = "" try: preferred_path = self.serverapp.contents_manager.preferred_dir except Exception: # FIXME: Remove fallback once CM.preferred_dir is ubiquitous. try: # Remove the server_root from app pref dir if self.serverapp.preferred_dir and self.serverapp.preferred_dir != server_root: preferred_path = ( pathlib.Path(self.serverapp.preferred_dir) .relative_to(server_root) .as_posix() ) except Exception: # noqa S110 pass # JupyterLab relies on an unset/default path being "/" page_config["preferredPath"] = preferred_path or "/" self.application.store_id += 1 # type:ignore[attr-defined] mathjax_config = self.settings.get("mathjax_config", "TeX-AMS_HTML-full,Safe") # TODO Remove CDN usage. mathjax_url = self.mathjax_url if not mathjax_url: mathjax_url = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js" page_config.setdefault("mathjaxConfig", mathjax_config) page_config.setdefault("fullMathjaxUrl", mathjax_url) # Put all our config in page_config for name in config.trait_names(): page_config[_camelCase(name)] = getattr(app, name) # Add full versions of all the urls for name in config.trait_names(): if not name.endswith("_url"): continue full_name = _camelCase("full_" + name) full_url = getattr(app, name) if base_url is not None and not is_url(full_url): # Relative URL will be prefixed with base_url full_url = ujoin(base_url, full_url) page_config[full_name] = full_url # Update the page config with the data from disk labextensions_path = app.extra_labextensions_path + app.labextensions_path recursive_update( page_config, get_page_config(labextensions_path, settings_dir, logger=self.log) ) # modify page config with custom hook page_config_hook = self.settings.get("page_config_hook", None) if page_config_hook: page_config = page_config_hook(self, page_config) return page_config @web.authenticated @web.removeslash def get( self, mode: str | None = None, workspace: str | None = None, tree: str | None = None ) -> None: """Get the JupyterLab html page.""" workspace = "default" if workspace is None else workspace.replace("/workspaces/", "") tree_path = "" if tree is None else tree.replace("/tree/", "") page_config = self.get_page_config() # Add parameters parsed from the URL if mode == "doc": page_config["mode"] = "single-document" else: page_config["mode"] = "multiple-document" page_config["workspace"] = workspace page_config["treePath"] = tree_path # Write the template with the config. tpl = self.render_template("index.html", page_config=page_config) # type:ignore[no-untyped-call] self.write(tpl) class NotFoundHandler(LabHandler): """A handler for page not found.""" @lru_cache() # noqa def get_page_config(self) -> dict[str, Any]: """Get the page config.""" page_config = super().get_page_config() page_config["notFoundUrl"] = self.request.path return page_config def add_handlers(handlers: list[Any], extension_app: LabServerApp) -> None: # noqa """Add the appropriate handlers to the web app.""" # Normalize directories. for name in LabConfig.class_trait_names(): if not name.endswith("_dir"): continue value = getattr(extension_app, name) setattr(extension_app, name, value.replace(os.sep, "/")) # Normalize urls # Local urls should have a leading slash but no trailing slash for name in LabConfig.class_trait_names(): if not name.endswith("_url"): continue value = getattr(extension_app, name) if is_url(value): continue if not value.startswith("/"): value = "/" + value if value.endswith("/"): value = value[:-1] setattr(extension_app, name, value) url_pattern = MASTER_URL_PATTERN.format(extension_app.app_url.replace("/", "")) handlers.append((url_pattern, LabHandler)) # Cache all or none of the files depending on the `cache_files` setting. no_cache_paths = [] if extension_app.cache_files else ["/"] # Handle federated lab extensions. labextensions_path = extension_app.extra_labextensions_path + extension_app.labextensions_path labextensions_url = ujoin(extension_app.labextensions_url, "(.*)") handlers.append( ( labextensions_url, FileFindHandler, {"path": labextensions_path, "no_cache_paths": no_cache_paths}, ) ) # Handle local settings. if extension_app.schemas_dir: settings_config: dict[str, Any] = { "app_settings_dir": extension_app.app_settings_dir, "schemas_dir": extension_app.schemas_dir, "settings_dir": extension_app.user_settings_dir, "labextensions_path": labextensions_path, } # Handle requests for the list of settings. Make slash optional. settings_path = ujoin(extension_app.settings_url, "?") handlers.append((settings_path, SettingsHandler, settings_config)) # Handle requests for an individual set of settings. setting_path = ujoin(extension_app.settings_url, "(?P.+)") handlers.append((setting_path, SettingsHandler, settings_config)) # Handle translations. # Translations requires settings as the locale source of truth is stored in it if extension_app.translations_api_url: # Handle requests for the list of language packs available. # Make slash optional. translations_path = ujoin(extension_app.translations_api_url, "?") handlers.append((translations_path, TranslationsHandler, settings_config)) # Handle requests for an individual language pack. translations_lang_path = ujoin(extension_app.translations_api_url, "(?P.*)") handlers.append((translations_lang_path, TranslationsHandler, settings_config)) # Handle saved workspaces. if extension_app.workspaces_dir: workspaces_config = {"manager": WorkspacesManager(extension_app.workspaces_dir)} # Handle requests for the list of workspaces. Make slash optional. workspaces_api_path = ujoin(extension_app.workspaces_api_url, "?") handlers.append((workspaces_api_path, WorkspacesHandler, workspaces_config)) # Handle requests for an individually named workspace. workspace_api_path = ujoin(extension_app.workspaces_api_url, "(?P.+)") handlers.append((workspace_api_path, WorkspacesHandler, workspaces_config)) # Handle local listings. settings_config = extension_app.settings.get("config", {}).get("LabServerApp", {}) blocked_extensions_uris: str = settings_config.get("blocked_extensions_uris", "") allowed_extensions_uris: str = settings_config.get("allowed_extensions_uris", "") if (blocked_extensions_uris) and (allowed_extensions_uris): warnings.warn( # noqa B028 "Simultaneous blocked_extensions_uris and allowed_extensions_uris is not supported. Please define only one of those." ) import sys sys.exit(-1) ListingsHandler.listings_refresh_seconds = settings_config.get( "listings_refresh_seconds", 60 * 60 ) ListingsHandler.listings_request_opts = settings_config.get("listings_request_options", {}) listings_url = ujoin(extension_app.listings_url) listings_path = ujoin(listings_url, "(.*)") if blocked_extensions_uris: ListingsHandler.blocked_extensions_uris = set(blocked_extensions_uris.split(",")) if allowed_extensions_uris: ListingsHandler.allowed_extensions_uris = set(allowed_extensions_uris.split(",")) fetch_listings(None) if ( len(ListingsHandler.blocked_extensions_uris) > 0 or len(ListingsHandler.allowed_extensions_uris) > 0 ): from tornado import ioloop callback_time = ListingsHandler.listings_refresh_seconds * 1000 ListingsHandler.pc = ioloop.PeriodicCallback( lambda: fetch_listings(None), # type:ignore[assignment] callback_time=callback_time, jitter=0.1, ) ListingsHandler.pc.start() # type:ignore[attr-defined] handlers.append((listings_path, ListingsHandler, {})) # Handle local themes. if extension_app.themes_dir: themes_url = extension_app.themes_url themes_path = ujoin(themes_url, "(.*)") handlers.append( ( themes_path, ThemesHandler, { "themes_url": themes_url, "path": extension_app.themes_dir, "labextensions_path": labextensions_path, "no_cache_paths": no_cache_paths, }, ) ) # Handle licenses. if extension_app.licenses_url: licenses_url = extension_app.licenses_url licenses_path = ujoin(licenses_url, "(.*)") handlers.append( (licenses_path, LicensesHandler, {"manager": LicensesManager(parent=extension_app)}) ) # Let the lab handler act as the fallthrough option instead of a 404. fallthrough_url = ujoin(extension_app.app_url, r".*") handlers.append((fallthrough_url, NotFoundHandler)) def _camelCase(base: str) -> str: # noqa """Convert a string to camelCase. https://stackoverflow.com/a/20744956 """ output = "".join(x for x in base.title() if x.isalpha()) return output[0].lower() + output[1:] jupyterlab_server-2.25.2/jupyterlab_server/licenses_app.py000066400000000000000000000056231452610450300241450ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """A license reporting CLI Mostly ready-to-use, the downstream must provide the location of the application's static resources. Licenses from an app's federated_extensions will also be discovered as configured in `labextensions_path` and `extra_labextensions_path`. from traitlets import default from jupyterlab_server import LicensesApp class MyLicensesApp(LicensesApp): version = "0.1.0" @default("static_dir") def _default_static_dir(self): return "my-static/" class MyApp(JupyterApp, LabConfig): ... subcommands = dict( licenses=(MyLicensesApp, MyLicensesApp.description.splitlines()[0]) ) """ from typing import Any from jupyter_core.application import JupyterApp, base_aliases, base_flags from traitlets import Bool, Enum, Instance, Unicode from ._version import __version__ from .config import LabConfig from .licenses_handler import LicensesManager class LicensesApp(JupyterApp, LabConfig): """A license management app.""" version = __version__ description = """ Report frontend licenses """ static_dir = Unicode("", config=True, help="The static directory from which to show licenses") full_text = Bool(False, config=True, help="Also print out full license text (if available)") report_format = Enum( ["markdown", "json", "csv"], "markdown", config=True, help="Reporter format" ) bundles_pattern = Unicode(".*", config=True, help="A regular expression of bundles to print") licenses_manager = Instance(LicensesManager) aliases = { **base_aliases, "bundles": "LicensesApp.bundles_pattern", "report-format": "LicensesApp.report_format", } flags = { **base_flags, "full-text": ( {"LicensesApp": {"full_text": True}}, "Print out full license text (if available)", ), "json": ( {"LicensesApp": {"report_format": "json"}}, "Print out report as JSON (implies --full-text)", ), "csv": ( {"LicensesApp": {"report_format": "csv"}}, "Print out report as CSV (implies --full-text)", ), } def initialize(self, *args: Any, **kwargs: Any) -> None: """Initialize the app.""" super().initialize(*args, **kwargs) self.init_licenses_manager() def init_licenses_manager(self) -> None: """Initialize the license manager.""" self.licenses_manager = LicensesManager( parent=self, ) def start(self) -> None: """Start the app.""" report = self.licenses_manager.report( report_format=self.report_format, full_text=self.full_text, bundles_pattern=self.bundles_pattern, )[0] print(report) self.exit(0) jupyterlab_server-2.25.2/jupyterlab_server/licenses_handler.py000066400000000000000000000244131452610450300250000ustar00rootroot00000000000000"""Manager and Tornado handlers for license reporting.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import asyncio import csv import io import json import mimetypes import re from concurrent.futures import ThreadPoolExecutor from pathlib import Path from typing import TYPE_CHECKING, Any from jupyter_server.base.handlers import APIHandler from tornado import web from traitlets import List, Unicode from traitlets.config import LoggingConfigurable from .config import get_federated_extensions # this is duplicated in @juptyerlab/builder DEFAULT_THIRD_PARTY_LICENSE_FILE = "third-party-licenses.json" UNKNOWN_PACKAGE_NAME = "UNKNOWN" if mimetypes.guess_extension("text/markdown") is None: # pragma: no cover # for python <3.8 https://bugs.python.org/issue39324 mimetypes.add_type("text/markdown", ".md") class LicensesManager(LoggingConfigurable): """A manager for listing the licenses for all frontend end code distributed by an application and any federated extensions """ executor = ThreadPoolExecutor(max_workers=1) third_party_licenses_files = List( Unicode(), default_value=[ DEFAULT_THIRD_PARTY_LICENSE_FILE, f"static/{DEFAULT_THIRD_PARTY_LICENSE_FILE}", ], help="the license report data in built app and federated extensions", ) @property def federated_extensions(self) -> dict[str, Any]: """Lazily load the currrently-available federated extensions. This is expensive, but probably the only way to be sure to get up-to-date license information for extensions installed interactively. """ if TYPE_CHECKING: from .app import LabServerApp assert isinstance(self.parent, LabServerApp) labextensions_path: list = sum( [ self.parent.labextensions_path, self.parent.extra_labextensions_path, ], [], ) return get_federated_extensions(labextensions_path) async def report_async( self, report_format: str = "markdown", bundles_pattern: str = ".*", full_text: bool = False ) -> tuple[str, str]: """Asynchronous wrapper around the potentially slow job of locating and encoding all of the licenses """ return await asyncio.wrap_future( self.executor.submit( self.report, report_format=report_format, bundles_pattern=bundles_pattern, full_text=full_text, ) ) def report(self, report_format: str, bundles_pattern: str, full_text: bool) -> tuple[str, str]: """create a human- or machine-readable report""" bundles = self.bundles(bundles_pattern=bundles_pattern) if report_format == "json": return self.report_json(bundles), "application/json" elif report_format == "csv": return self.report_csv(bundles), "text/csv" elif report_format == "markdown": return ( self.report_markdown(bundles, full_text=full_text), "text/markdown", ) msg = f"Unsupported report format {report_format}." raise ValueError(msg) def report_json(self, bundles: dict[str, Any]) -> str: """create a JSON report TODO: SPDX """ return json.dumps({"bundles": bundles}, indent=2, sort_keys=True) def report_csv(self, bundles: dict[str, Any]) -> str: """create a CSV report""" outfile = io.StringIO() fieldnames = ["name", "versionInfo", "licenseId", "extractedText"] writer = csv.DictWriter(outfile, fieldnames=["bundle", *fieldnames]) writer.writeheader() for bundle_name, bundle in bundles.items(): for package in bundle["packages"]: writer.writerow( { "bundle": bundle_name, **{field: package.get(field, "") for field in fieldnames}, } ) return outfile.getvalue() def report_markdown(self, bundles: dict[str, Any], full_text: bool = True) -> str: """create a markdown report""" lines = [] library_names = [ len(package.get("name", UNKNOWN_PACKAGE_NAME)) for bundle_name, bundle in bundles.items() for package in bundle.get("packages", []) ] longest_name = max(library_names) if library_names else 1 for bundle_name, bundle in bundles.items(): # TODO: parametrize template lines += [f"# {bundle_name}", ""] packages = bundle.get("packages", []) if not packages: lines += ["> No licenses found", ""] continue for package in packages: name = package.get("name", UNKNOWN_PACKAGE_NAME).strip() version_info = package.get("versionInfo", UNKNOWN_PACKAGE_NAME).strip() license_id = package.get("licenseId", UNKNOWN_PACKAGE_NAME).strip() extracted_text = package.get("extractedText", "") lines += [ "## " + ( "\t".join( [ f"""**{name}**""".ljust(longest_name), f"""`{version_info}`""".ljust(20), license_id, ] ) ) ] if full_text: if not extracted_text: lines += ["", "> No license text available", ""] else: lines += ["", "", "
", extracted_text, "
", ""] return "\n".join(lines) def license_bundle(self, path: Path, bundle: str | None) -> dict[str, Any]: """Return the content of a packages's license bundles""" bundle_json: dict = {"packages": []} checked_paths = [] for license_file in self.third_party_licenses_files: licenses_path = path / license_file self.log.debug("Loading licenses from %s", licenses_path) if not licenses_path.exists(): checked_paths += [licenses_path] continue try: file_json = json.loads(licenses_path.read_text(encoding="utf-8")) except Exception as err: self.log.warning( "Failed to open third-party licenses for %s: %s\n%s", bundle, licenses_path, err, ) continue try: bundle_json["packages"].extend(file_json["packages"]) except Exception as err: self.log.warning( "Failed to find packages for %s: %s\n%s", bundle, licenses_path, err, ) continue if not bundle_json["packages"]: self.log.warning("Third-party licenses not found for %s: %s", bundle, checked_paths) return bundle_json def app_static_info(self) -> tuple[Path | None, str | None]: """get the static directory for this app This will usually be in `static_dir`, but may also appear in the parent of `static_dir`. """ if TYPE_CHECKING: from .app import LabServerApp assert isinstance(self.parent, LabServerApp) path = Path(self.parent.static_dir) package_json = path / "package.json" if not package_json.exists(): parent_package_json = path.parent / "package.json" if parent_package_json.exists(): package_json = parent_package_json else: return None, None name = json.loads(package_json.read_text(encoding="utf-8"))["name"] return path, name def bundles(self, bundles_pattern: str = ".*") -> dict[str, Any]: """Read all of the licenses TODO: schema """ bundles = { name: self.license_bundle(Path(ext["ext_path"]), name) for name, ext in self.federated_extensions.items() if re.match(bundles_pattern, name) } app_path, app_name = self.app_static_info() if app_path is not None: assert app_name is not None if re.match(bundles_pattern, app_name): bundles[app_name] = self.license_bundle(app_path, app_name) if not bundles: self.log.warning("No license bundles found at all") return bundles class LicensesHandler(APIHandler): """A handler for serving licenses used by the application""" def initialize(self, manager: LicensesManager) -> None: """Initialize the handler.""" super().initialize() self.manager = manager @web.authenticated async def get(self, _args: Any) -> None: """Return all the frontend licenses""" full_text = bool(json.loads(self.get_argument("full_text", "true"))) report_format = self.get_argument("format", "json") bundles_pattern = self.get_argument("bundles", ".*") download = bool(json.loads(self.get_argument("download", "0"))) report, mime = await self.manager.report_async( report_format=report_format, bundles_pattern=bundles_pattern, full_text=full_text, ) if TYPE_CHECKING: from .app import LabServerApp assert isinstance(self.manager.parent, LabServerApp) if download: filename = "{}-licenses{}".format( self.manager.parent.app_name.lower(), mimetypes.guess_extension(mime) ) self.set_attachment_header(filename) self.write(report) await self.finish(_mime_type=mime) async def finish( # type:ignore[override] self, _mime_type: str, *args: Any, **kwargs: Any ) -> Any: """Overload the regular finish, which (sensibly) always sets JSON""" self.update_api_activity() self.set_header("Content-Type", _mime_type) return await super(APIHandler, self).finish(*args, **kwargs) jupyterlab_server-2.25.2/jupyterlab_server/listings_handler.py000066400000000000000000000071321452610450300250260ustar00rootroot00000000000000"""Tornado handlers for listing extensions.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import json from logging import Logger import requests import tornado from jupyter_server.base.handlers import APIHandler LISTINGS_URL_SUFFIX = "@jupyterlab/extensionmanager-extension/listings.json" def fetch_listings(logger: Logger | None) -> None: """Fetch the listings for the extension manager.""" if not logger: from traitlets import log logger = log.get_logger() # type:ignore[assignment] assert logger is not None if len(ListingsHandler.blocked_extensions_uris) > 0: blocked_extensions = [] for blocked_extensions_uri in ListingsHandler.blocked_extensions_uris: logger.info( f"Fetching blocked_extensions from {ListingsHandler.blocked_extensions_uris}" ) r = requests.request( "GET", blocked_extensions_uri, **ListingsHandler.listings_request_opts ) j = json.loads(r.text) for b in j["blocked_extensions"]: blocked_extensions.append(b) ListingsHandler.blocked_extensions = blocked_extensions if len(ListingsHandler.allowed_extensions_uris) > 0: allowed_extensions = [] for allowed_extensions_uri in ListingsHandler.allowed_extensions_uris: logger.info( f"Fetching allowed_extensions from {ListingsHandler.allowed_extensions_uris}" ) r = requests.request( "GET", allowed_extensions_uri, **ListingsHandler.listings_request_opts ) j = json.loads(r.text) for w in j["allowed_extensions"]: allowed_extensions.append(w) ListingsHandler.allowed_extensions = allowed_extensions ListingsHandler.listings = json.dumps( # type:ignore[attr-defined] { "blocked_extensions_uris": list(ListingsHandler.blocked_extensions_uris), "allowed_extensions_uris": list(ListingsHandler.allowed_extensions_uris), "blocked_extensions": ListingsHandler.blocked_extensions, "allowed_extensions": ListingsHandler.allowed_extensions, } ) class ListingsHandler(APIHandler): """An handler that returns the listings specs.""" """Below fields are class level fields that are accessed and populated by the initialization and the fetch_listings methods. Some fields are initialized before the handler creation in the handlers.py#add_handlers method. Having those fields predefined reduces the guards in the methods using them. """ # The list of blocked_extensions URIS. blocked_extensions_uris: set = set() # The list of allowed_extensions URIS. allowed_extensions_uris: set = set() # The blocked extensions extensions. blocked_extensions: list = [] # The allowed extensions extensions. allowed_extensions: list = [] # The provider request options to be used for the request library. listings_request_opts: dict = {} # The callback time for the periodic callback in seconds. listings_refresh_seconds: int # The PeriodicCallback that schedule the call to fetch_listings method. pc = None def get(self, path: str) -> None: """Get the listings for the extension manager.""" self.set_header("Content-Type", "application/json") if path == LISTINGS_URL_SUFFIX: self.write(ListingsHandler.listings) # type:ignore[attr-defined] else: raise tornado.web.HTTPError(400) jupyterlab_server-2.25.2/jupyterlab_server/process.py000066400000000000000000000224331452610450300231540ustar00rootroot00000000000000"""JupyterLab Server process handler""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import atexit import logging import os import re import signal import subprocess import sys import threading import time import weakref from logging import Logger from shutil import which as _which from typing import Any from tornado import gen try: import pty except ImportError: pty = None # type:ignore[assignment] if sys.platform == "win32": list2cmdline = subprocess.list2cmdline else: def list2cmdline(cmd_list: list[str]) -> str: """Shim for list2cmdline on posix.""" import shlex return " ".join(map(shlex.quote, cmd_list)) def which(command: str, env: dict[str, str] | None = None) -> str: """Get the full path to a command. Parameters ---------- command: str The command name or path. env: dict, optional The environment variables, defaults to `os.environ`. """ env = env or os.environ # type:ignore[assignment] path = env.get("PATH") or os.defpath # type:ignore[union-attr] command_with_path = _which(command, path=path) # Allow nodejs as an alias to node. if command == "node" and not command_with_path: command = "nodejs" command_with_path = _which("nodejs", path=path) if not command_with_path: if command in ["nodejs", "node", "npm"]: msg = "Please install Node.js and npm before continuing installation. You may be able to install Node.js from your package manager, from conda, or directly from the Node.js website (https://nodejs.org)." raise ValueError(msg) raise ValueError("The command was not found or was not " + "executable: %s." % command) return os.path.abspath(command_with_path) class Process: """A wrapper for a child process.""" _procs: weakref.WeakSet = weakref.WeakSet() _pool = None def __init__( self, cmd: list[str], logger: Logger | None = None, cwd: str | None = None, kill_event: threading.Event | None = None, env: dict[str, str] | None = None, quiet: bool = False, ) -> None: """Start a subprocess that can be run asynchronously. Parameters ---------- cmd: list The command to run. logger: :class:`~logger.Logger`, optional The logger instance. cwd: string, optional The cwd of the process. env: dict, optional The environment for the process. kill_event: :class:`~threading.Event`, optional An event used to kill the process operation. quiet: bool, optional Whether to suppress output. """ if not isinstance(cmd, (list, tuple)): msg = "Command must be given as a list" # type:ignore[unreachable] raise ValueError(msg) if kill_event and kill_event.is_set(): msg = "Process aborted" raise ValueError(msg) self.logger = logger or self.get_log() self._last_line = "" if not quiet: self.logger.info(f"> {list2cmdline(cmd)}") self.cmd = cmd kwargs = {} if quiet: kwargs["stdout"] = subprocess.DEVNULL self.proc = self._create_process(cwd=cwd, env=env, **kwargs) self._kill_event = kill_event or threading.Event() Process._procs.add(self) def terminate(self) -> int: """Terminate the process and return the exit code.""" proc = self.proc # Kill the process. if proc.poll() is None: os.kill(proc.pid, signal.SIGTERM) # Wait for the process to close. try: proc.wait(timeout=2.0) except subprocess.TimeoutExpired: if os.name == "nt": # noqa sig = signal.SIGBREAK # type:ignore[attr-defined] else: sig = signal.SIGKILL if proc.poll() is None: os.kill(proc.pid, sig) finally: if self in Process._procs: Process._procs.remove(self) return proc.wait() def wait(self) -> int: """Wait for the process to finish. Returns ------- The process exit code. """ proc = self.proc kill_event = self._kill_event while proc.poll() is None: if kill_event.is_set(): self.terminate() msg = "Process was aborted" raise ValueError(msg) time.sleep(1.0) return self.terminate() @gen.coroutine def wait_async(self) -> Any: """Asynchronously wait for the process to finish.""" proc = self.proc kill_event = self._kill_event while proc.poll() is None: if kill_event.is_set(): self.terminate() msg = "Process was aborted" raise ValueError(msg) yield gen.sleep(1.0) raise gen.Return(self.terminate()) def _create_process(self, **kwargs: Any) -> subprocess.Popen[str]: """Create the process.""" cmd = list(self.cmd) kwargs.setdefault("stderr", subprocess.STDOUT) cmd[0] = which(cmd[0], kwargs.get("env")) if os.name == "nt": kwargs["shell"] = True proc = subprocess.Popen(cmd, **kwargs) # noqa return proc @classmethod def _cleanup(cls: type[Process]) -> None: """Clean up the started subprocesses at exit.""" for proc in list(cls._procs): proc.terminate() def get_log(self) -> Logger: """Get our logger.""" if hasattr(self, "logger") and self.logger is not None: return self.logger # fallback logger self.logger = logging.getLogger("jupyterlab") self.logger.setLevel(logging.INFO) return self.logger class WatchHelper(Process): """A process helper for a watch process.""" def __init__( self, cmd: list[str], startup_regex: str, logger: Logger | None = None, cwd: str | None = None, kill_event: threading.Event | None = None, env: dict[str, str] | None = None, ) -> None: """Initialize the process helper. Parameters ---------- cmd: list The command to run. startup_regex: string The regex to wait for at startup. logger: :class:`~logger.Logger`, optional The logger instance. cwd: string, optional The cwd of the process. env: dict, optional The environment for the process. kill_event: callable, optional A function to call to check if we should abort. """ super().__init__(cmd, logger=logger, cwd=cwd, kill_event=kill_event, env=env) if pty is None: self._stdout = self.proc.stdout # type:ignore[unreachable] while 1: line = self._stdout.readline().decode("utf-8") # type:ignore[has-type] if not line: msg = "Process ended improperly" raise RuntimeError(msg) print(line.rstrip()) if re.match(startup_regex, line): break self._read_thread = threading.Thread(target=self._read_incoming, daemon=True) self._read_thread.start() def terminate(self) -> int: """Terminate the process.""" proc = self.proc if proc.poll() is None: if os.name != "nt": # Kill the process group if we started a new session. os.killpg(os.getpgid(proc.pid), signal.SIGTERM) else: os.kill(proc.pid, signal.SIGTERM) # Wait for the process to close. try: proc.wait() finally: if self in Process._procs: Process._procs.remove(self) return proc.returncode def _read_incoming(self) -> None: """Run in a thread to read stdout and print""" fileno = self._stdout.fileno() # type:ignore[has-type] while 1: try: buf = os.read(fileno, 1024) except OSError as e: self.logger.debug("Read incoming error %s", e) return None if not buf: return None print(buf.decode("utf-8"), end="") def _create_process(self, **kwargs: Any) -> subprocess.Popen[str]: """Create the watcher helper process.""" kwargs["bufsize"] = 0 if pty is not None: master, slave = pty.openpty() kwargs["stderr"] = kwargs["stdout"] = slave kwargs["start_new_session"] = True self._stdout = os.fdopen(master, "rb") # type:ignore[has-type] else: kwargs["stdout"] = subprocess.PIPE # type:ignore[unreachable] if os.name == "nt": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW kwargs["startupinfo"] = startupinfo kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP kwargs["shell"] = True return super()._create_process(**kwargs) # Register the cleanup handler. atexit.register(Process._cleanup) jupyterlab_server-2.25.2/jupyterlab_server/process_app.py000066400000000000000000000032631452610450300240140ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """A lab app that runs a sub process for a demo or a test.""" from __future__ import annotations import sys from typing import Any from jupyter_server.extension.application import ExtensionApp, ExtensionAppJinjaMixin from tornado.ioloop import IOLoop from .handlers import LabConfig, add_handlers from .process import Process class ProcessApp(ExtensionAppJinjaMixin, LabConfig, ExtensionApp): """A jupyterlab app that runs a separate process and exits on completion.""" load_other_extensions = True # Do not open a browser for process apps open_browser = False # type:ignore[assignment] def get_command(self) -> tuple[list[str], dict[str, Any]]: """Get the command and kwargs to run with `Process`. This is intended to be overridden. """ return [sys.executable, "--version"], {} def initialize_settings(self) -> None: """Start the application.""" IOLoop.current().add_callback(self._run_command) def initialize_handlers(self) -> None: """Initialize the handlers.""" add_handlers(self.handlers, self) # type:ignore[arg-type] def _run_command(self) -> None: command, kwargs = self.get_command() kwargs.setdefault("logger", self.log) future = Process(command, **kwargs).wait_async() IOLoop.current().add_future(future, self._process_finished) def _process_finished(self, future: Any) -> None: try: IOLoop.current().stop() sys.exit(future.result()) except Exception as e: self.log.error(str(e)) sys.exit(1) jupyterlab_server-2.25.2/jupyterlab_server/py.typed000066400000000000000000000000001452610450300226050ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/pytest_plugin.py000066400000000000000000000113341452610450300244020ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """pytest fixtures.""" from __future__ import annotations import json import os import os.path as osp import shutil from os.path import join as pjoin from pathlib import Path from typing import Any, Callable import pytest from jupyter_server.serverapp import ServerApp from jupyterlab_server import LabServerApp pytest_plugins = ["pytest_jupyter.jupyter_server"] def mkdir(tmp_path: Path, *parts: str) -> Path: """Util for making a directory.""" path = tmp_path.joinpath(*parts) if not path.exists(): path.mkdir(parents=True) return path HERE = os.path.abspath(os.path.dirname(__file__)) app_settings_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "app_settings")) user_settings_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "user_settings")) schemas_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "schemas")) workspaces_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "workspaces")) labextensions_dir = pytest.fixture(lambda tmp_path: mkdir(tmp_path, "labextensions_dir")) @pytest.fixture def make_labserver_extension_app( jp_root_dir: Path, jp_template_dir: Path, app_settings_dir: Path, user_settings_dir: Path, schemas_dir: Path, workspaces_dir: Path, labextensions_dir: Path, ) -> Callable[..., LabServerApp]: """Return a factory function for a labserver extension app.""" def _make_labserver_extension_app(**kwargs: Any) -> LabServerApp: """Factory function for lab server extension apps.""" return LabServerApp( static_dir=str(jp_root_dir), templates_dir=str(jp_template_dir), app_url="/lab", app_settings_dir=str(app_settings_dir), user_settings_dir=str(user_settings_dir), schemas_dir=str(schemas_dir), workspaces_dir=str(workspaces_dir), extra_labextensions_path=[str(labextensions_dir)], ) # Create the index files. index = jp_template_dir.joinpath("index.html") index.write_text( """ {{page_config['appName'] | e}} {# Copy so we do not modify the page_config with updates. #} {% set page_config_full = page_config.copy() %} {# Set a dummy variable - we just want the side effect of the update. #} {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} """ ) # Copy the schema files. src = pjoin(HERE, "test_data", "schemas", "@jupyterlab") dst = pjoin(str(schemas_dir), "@jupyterlab") if os.path.exists(dst): shutil.rmtree(dst) shutil.copytree(src, dst) # Create the federated extensions for name in ["apputils-extension", "codemirror-extension"]: target_name = name + "-federated" target = pjoin(str(labextensions_dir), "@jupyterlab", target_name) src = pjoin(HERE, "test_data", "schemas", "@jupyterlab", name) dst = pjoin(target, "schemas", "@jupyterlab", target_name) if osp.exists(dst): shutil.rmtree(dst) shutil.copytree(src, dst) with open(pjoin(target, "package.orig.json"), "w") as fid: data = dict(name=target_name, jupyterlab=dict(extension=True)) json.dump(data, fid) # Copy the overrides file. src = pjoin(HERE, "test_data", "app-settings", "overrides.json") dst = pjoin(str(app_settings_dir), "overrides.json") if os.path.exists(dst): os.remove(dst) shutil.copyfile(src, dst) # Copy workspaces. ws_path = pjoin(HERE, "test_data", "workspaces") for item in os.listdir(ws_path): src = pjoin(ws_path, item) dst = pjoin(str(workspaces_dir), item) if os.path.exists(dst): os.remove(dst) shutil.copy(src, str(workspaces_dir)) return _make_labserver_extension_app @pytest.fixture def labserverapp( jp_serverapp: ServerApp, make_labserver_extension_app: Callable[..., LabServerApp] ) -> LabServerApp: """A lab server app.""" app = make_labserver_extension_app() app._link_jupyter_server_extension(jp_serverapp) app.initialize() # type:ignore[no-untyped-call] return app jupyterlab_server-2.25.2/jupyterlab_server/rest-api.yml000066400000000000000000000231021452610450300233650ustar00rootroot00000000000000# see me at: https://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterlab/jupyterlab_server/main/jupyterlab_server/rest-api.yml#/default openapi: "3.0.3" info: title: JupyterLab Server description: The REST API for JupyterLab Server version: 1.0.0 license: name: BSD-3-Clause paths: /lab/api/listings/%40jupyterlab/extensionmanager-extension/listings.json: get: summary: Get Extension Listings Specs description: | Gets the list of extension metadata for the application responses: "200": description: The Extension Listing specs content: application/json: schema: properties: blocked_extension_uris: type: array description: list of blocked extension uris items: type: string allowed_extension_uris: type: array description: list of allowed extension uris items: type: string blocked_extensions: type: array description: list of blocked extensions items: $ref: "#/components/schemas/ListEntry" allowed_extensions: type: array description: list of blocked extensions items: $ref: "#/components/schemas/ListEntry" /lab/api/settings/: get: summary: Get Settings List description: | Gets the list of all application settings data responses: "200": description: The Application Settings Data content: application/json: schema: properties: settings: type: array description: List of application settings entries items: $ref: "#/components/schemas/SettingsEntry" /lab/api/settings/{schema_name}: parameters: - name: schema_name description: Schema Name in: path required: true schema: type: string get: summary: Get the settings data for a given schema description: | Gets the settings data for a given schema responses: "200": description: The Settings Data content: application/json: schema: $ref: "#/components/schemas/SettingsEntry" put: summary: Override the settings data for a given schema description: | Overrides the settings data for a given schema requestBody: required: true description: raw settings data content: application/json: schema: type: object properties: raw: type: string responses: "204": description: The setting has been updated /lab/api/themes/{theme_file}: parameters: - name: theme_file description: Theme file path in: path required: true schema: type: string get: summary: Get a static theme file description: | Gets the static theme file at a given path responses: "200": description: The Theme File /lab/api/translations/: get: summary: Get Translation Bundles description: | Gets the list of translation bundles responses: "200": description: The Extension Listing specs content: application/json: schema: type: object properties: data: type: object additionalProperties: $ref: "#/components/schemas/TranslationEntry" message: type: string /lab/api/translations/{locale}: parameters: - name: locale description: Locale name in: path required: true schema: type: string get: summary: Get the translation data for locale description: | Gets the translation data for a given locale responses: "200": description: The Local Data content: application/json: schema: type: object properties: data: type: object message: type: string /lab/api/workspaces/: get: summary: Get Workspace Data description: | Gets the list of workspace data responses: "200": description: The Workspace specs content: application/json: schema: type: object properties: workspaces: type: object properties: ids: type: array items: type: string values: type: array items: $ref: "#/components/schemas/Workspace" /lab/api/workspaces/{space_name}: parameters: - name: space_name description: Workspace name in: path required: true schema: type: string get: summary: Get the workspace data for name description: | Gets the workspace data for a given workspace name responses: "200": description: The Workspace Data content: application/json: schema: $ref: "#/components/schemas/Workspace" put: summary: Override the workspace data for a given name description: | Overrides the workspace data for a given workspace name requestBody: required: true description: raw workspace data content: application/json: schema: $ref: "#/components/schemas/Workspace" responses: "204": description: The workspace has been updated delete: summary: Delete the workspace data for a given name description: | Deletes the workspace data for a given workspace name responses: "204": description: The workspace has been deleted /lab/api/licenses/: get: summary: License report description: | Get the third-party licenses for the core application and all federated extensions parameters: - name: full_text description: Return full license texts in: query schema: type: boolean - name: format in: query description: The format in which to report licenses schema: type: string enum: - csv - json - markdown - name: bundles description: A regular expression to limit the names of bundles reported in: query schema: type: string - name: download in: query description: Whether to set a representative filename header schema: type: boolean responses: "200": description: A license report content: application/markdown: schema: type: string text/csv: schema: type: string application/json: schema: $ref: "#/components/schemas/LicenseBundles" components: schemas: ListEntry: type: object properties: name: type: string regexp: type: string type: type: string reason: type: string creation_date: type: string last_update_date: type: string SettingsEntry: type: object properties: id: type: string schema: type: object version: type: string raw: type: string settings: type: object warning: type: string nullable: true last_modified: type: string nullable: true created: type: string nullable: true TranslationEntry: type: object properties: data: type: object properties: displayName: type: string nativeName: type: string message: type: string Workspace: type: object properties: data: type: object metadata: type: object properties: id: type: string last_modified: type: string created: type: string LicenseBundles: type: object properties: bundles: type: object additionalProperties: type: object properties: packages: type: array items: type: object properties: extractedText: type: string licenseId: type: string name: type: string versionInfo: type: string jupyterlab_server-2.25.2/jupyterlab_server/server.py000066400000000000000000000012271452610450300230020ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Server api.""" # FIXME TODO Deprecated remove this file for the next major version # Downstream package must import those items from `jupyter_server` directly from jupyter_server import _tz as tz from jupyter_server.base.handlers import ( APIHandler, FileFindHandler, JupyterHandler, json_errors, ) from jupyter_server.extension.serverextension import ( GREEN_ENABLED, GREEN_OK, RED_DISABLED, RED_X, ) from jupyter_server.serverapp import ServerApp, aliases, flags from jupyter_server.utils import url_escape, url_path_join jupyterlab_server-2.25.2/jupyterlab_server/settings_handler.py000066400000000000000000000070551452610450300250360ustar00rootroot00000000000000"""Tornado handlers for frontend config storage.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import json from typing import Any from jsonschema import ValidationError from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin from tornado import web from .settings_utils import SchemaHandler, get_settings, save_settings from .translation_utils import translator class SettingsHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, SchemaHandler): """A settings API handler.""" def initialize( # type:ignore[override] self, name: str, app_settings_dir: str, schemas_dir: str, settings_dir: str, labextensions_path: list[str], **kwargs: Any, ) -> None: """Initialize the handler.""" SchemaHandler.initialize( self, app_settings_dir, schemas_dir, settings_dir, labextensions_path ) ExtensionHandlerMixin.initialize(self, name) @web.authenticated def get(self, schema_name: str = "") -> Any: """ Get setting(s) Parameters ---------- schema_name: str The id of a unique schema to send, added to the URL ## NOTES: An optional argument `ids_only=true` can be provided in the URL to get only the ids of the schemas instead of the content. """ # Need to be update here as translator locale is not change when a new locale is put # from frontend locale = self.get_current_locale() translator.set_locale(locale) ids_only = self.get_argument("ids_only", "") == "true" result, warnings = get_settings( self.app_settings_dir, self.schemas_dir, self.settings_dir, labextensions_path=self.labextensions_path, schema_name=schema_name, overrides=self.overrides, translator=translator.translate_schema, ids_only=ids_only, ) # Print all warnings. for w in warnings: if w: self.log.warning(w) return self.finish(json.dumps(result)) @web.authenticated def put(self, schema_name: str) -> None: """Update a setting""" overrides = self.overrides schemas_dir = self.schemas_dir settings_dir = self.settings_dir settings_error = "No current settings directory" invalid_json_error = "Failed parsing JSON payload: %s" invalid_payload_format_error = ( "Invalid format for JSON payload. Must be in the form {'raw': ...}" ) validation_error = "Failed validating input: %s" if not settings_dir: raise web.HTTPError(500, settings_error) raw_payload = self.request.body.strip().decode("utf-8") try: raw_settings = json.loads(raw_payload)["raw"] save_settings( schemas_dir, settings_dir, schema_name, raw_settings, overrides, self.labextensions_path, ) except json.decoder.JSONDecodeError as e: raise web.HTTPError(400, invalid_json_error % str(e)) from None except (KeyError, TypeError): raise web.HTTPError(400, invalid_payload_format_error) from None except ValidationError as e: raise web.HTTPError(400, validation_error % str(e)) from None self.set_status(204) jupyterlab_server-2.25.2/jupyterlab_server/settings_utils.py000066400000000000000000000420511452610450300245540ustar00rootroot00000000000000"""Frontend config storage helpers.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import json import os from glob import glob from typing import Any import json5 # type:ignore[import-untyped] from jsonschema import Draft7Validator as Validator from jsonschema import ValidationError from jupyter_server import _tz as tz from jupyter_server.base.handlers import APIHandler from jupyter_server.services.config.manager import ConfigManager, recursive_update from tornado import web from .translation_utils import DEFAULT_LOCALE, L10N_SCHEMA_NAME, SYS_LOCALE, is_valid_locale # The JupyterLab settings file extension. SETTINGS_EXTENSION = ".jupyterlab-settings" def _get_schema( schemas_dir: str, schema_name: str, overrides: dict[str, Any], labextensions_path: list[str] | None, ) -> tuple[dict[str, Any], str]: """Returns a dict containing a parsed and validated JSON schema.""" notfound_error = "Schema not found: %s" parse_error = "Failed parsing schema (%s): %s" validation_error = "Failed validating schema (%s): %s" path = None # Look for the setting in all of the labextension paths first # Use the first one if labextensions_path is not None: ext_name, _, plugin_name = schema_name.partition(":") for ext_path in labextensions_path: target = os.path.join(ext_path, ext_name, "schemas", ext_name, plugin_name + ".json") if os.path.exists(target): schemas_dir = os.path.join(ext_path, ext_name, "schemas") path = target break # Fall back on the default location if path is None: path = _path(schemas_dir, schema_name) if not os.path.exists(path): raise web.HTTPError(404, notfound_error % path) with open(path, encoding="utf-8") as fid: # Attempt to load the schema file. try: schema = json.load(fid) except Exception as e: name = schema_name raise web.HTTPError(500, parse_error % (name, str(e))) from None schema = _override(schema_name, schema, overrides) # Validate the schema. try: Validator.check_schema(schema) except Exception as e: name = schema_name raise web.HTTPError(500, validation_error % (name, str(e))) from None version = _get_version(schemas_dir, schema_name) return schema, version def _get_user_settings(settings_dir: str, schema_name: str, schema: Any) -> dict[str, Any]: """ Returns a dictionary containing the raw user settings, the parsed user settings, a validation warning for a schema, and file times. """ path = _path(settings_dir, schema_name, False, SETTINGS_EXTENSION) raw = "{}" settings = {} warning = None validation_warning = "Failed validating settings (%s): %s" parse_error = "Failed loading settings (%s): %s" last_modified = None created = None if os.path.exists(path): stat = os.stat(path) last_modified = tz.utcfromtimestamp(stat.st_mtime).isoformat() created = tz.utcfromtimestamp(stat.st_ctime).isoformat() with open(path, encoding="utf-8") as fid: try: # to load and parse the settings file. raw = fid.read() or raw settings = json5.loads(raw) except Exception as e: raise web.HTTPError(500, parse_error % (schema_name, str(e))) from None # Validate the parsed data against the schema. if len(settings): validator = Validator(schema) try: validator.validate(settings) except ValidationError as e: warning = validation_warning % (schema_name, str(e)) raw = "{}" settings = {} return dict( raw=raw, settings=settings, warning=warning, last_modified=last_modified, created=created ) def _get_version(schemas_dir: str, schema_name: str) -> str: """Returns the package version for a given schema or 'N/A' if not found.""" path = _path(schemas_dir, schema_name) package_path = os.path.join(os.path.split(path)[0], "package.json.orig") try: # to load and parse the package.json.orig file. with open(package_path, encoding="utf-8") as fid: package = json.load(fid) return package["version"] except Exception: return "N/A" def _list_settings( schemas_dir: str, settings_dir: str, overrides: dict[str, Any], extension: str = ".json", labextensions_path: list[str] | None = None, translator: Any = None, ids_only: bool = False, ) -> tuple[list[Any], list[Any]]: """ Returns a tuple containing: - the list of plugins, schemas, and their settings, respecting any defaults that may have been overridden if `ids_only=False`, otherwise a list of dict containing only the ids of plugins. - the list of warnings that were generated when validating the user overrides against the schemas. """ settings: dict[str, Any] = {} federated_settings: dict[str, Any] = {} warnings = [] if not os.path.exists(schemas_dir): warnings = ["Settings directory does not exist at %s" % schemas_dir] return ([], warnings) schema_pattern = schemas_dir + "/**/*" + extension schema_paths = [path for path in glob(schema_pattern, recursive=True)] schema_paths.sort() for schema_path in schema_paths: # Generate the schema_name used to request individual settings. rel_path = os.path.relpath(schema_path, schemas_dir) rel_schema_dir, schema_base = os.path.split(rel_path) _id = schema_name = ":".join( [rel_schema_dir, schema_base[: -len(extension)]] # Remove file extension. ).replace("\\", "/") # Normalize slashes. if ids_only: settings[_id] = dict(id=_id) else: schema, version = _get_schema(schemas_dir, schema_name, overrides, None) if translator is not None: schema = translator(schema) user_settings = _get_user_settings(settings_dir, schema_name, schema) if user_settings["warning"]: warnings.append(user_settings.pop("warning")) # Add the plugin to the list of settings. settings[_id] = dict(id=_id, schema=schema, version=version, **user_settings) if labextensions_path is not None: schema_paths = [] for ext_dir in labextensions_path: schema_pattern = ext_dir + "/**/schemas/**/*" + extension schema_paths.extend([path for path in glob(schema_pattern, recursive=True)]) schema_paths.sort() for schema_path_ in schema_paths: schema_path = schema_path_.replace(os.sep, "/") base_dir, rel_path = schema_path.split("schemas/") # Generate the schema_name used to request individual settings. rel_schema_dir, schema_base = os.path.split(rel_path) _id = schema_name = ":".join( [rel_schema_dir, schema_base[: -len(extension)]] # Remove file extension. ).replace("\\", "/") # Normalize slashes. # bail if we've already handled the highest federated setting if _id in federated_settings: continue if ids_only: federated_settings[_id] = dict(id=_id) else: schema, version = _get_schema( schemas_dir, schema_name, overrides, labextensions_path=labextensions_path ) user_settings = _get_user_settings(settings_dir, schema_name, schema) if user_settings["warning"]: warnings.append(user_settings.pop("warning")) # Add the plugin to the list of settings. federated_settings[_id] = dict( id=_id, schema=schema, version=version, **user_settings ) settings.update(federated_settings) settings_list = [settings[key] for key in sorted(settings.keys(), reverse=True)] return (settings_list, warnings) def _override( schema_name: str, schema: dict[str, Any], overrides: dict[str, Any] ) -> dict[str, Any]: """Override default values in the schema if necessary.""" if schema_name in overrides: defaults = overrides[schema_name] for key in defaults: if key in schema["properties"]: new_defaults = schema["properties"][key]["default"] # If values for defaults are dicts do a recursive update if isinstance(new_defaults, dict): recursive_update(new_defaults, defaults[key]) else: new_defaults = defaults[key] schema["properties"][key]["default"] = new_defaults else: schema["properties"][key] = dict(default=defaults[key]) return schema def _path( root_dir: str, schema_name: str, make_dirs: bool = False, extension: str = ".json" ) -> str: """ Returns the local file system path for a schema name in the given root directory. This function can be used to filed user overrides in addition to schema files. If the `make_dirs` flag is set to `True` it will create the parent directory for the calculated path if it does not exist. """ notfound_error = "Settings not found (%s)" write_error = "Failed writing settings (%s): %s" try: # to parse path, e.g. @jupyterlab/apputils-extension:themes. package_dir, plugin = schema_name.split(":") parent_dir = os.path.join(root_dir, package_dir) path = os.path.join(parent_dir, plugin + extension) except Exception: raise web.HTTPError(404, notfound_error % schema_name) from None if make_dirs and not os.path.exists(parent_dir): try: os.makedirs(parent_dir) except Exception as e: raise web.HTTPError(500, write_error % (schema_name, str(e))) from None return path def _get_overrides(app_settings_dir: str) -> tuple[dict[str, Any], str]: """Get overrides settings from `app_settings_dir`. The ordering of paths is: - {app_settings_dir}/overrides.d/*.{json,json5} (many, namespaced by package) - {app_settings_dir}/overrides.{json,json5} (singleton, owned by the user) """ overrides: dict[str, Any] error: str overrides, error = {}, "" overrides_d = os.path.join(app_settings_dir, "overrides.d") # find (and sort) the conf.d overrides files all_override_paths = sorted( [ *(glob(os.path.join(overrides_d, "*.json"))), *(glob(os.path.join(overrides_d, "*.json5"))), ] ) all_override_paths += [ os.path.join(app_settings_dir, "overrides.json"), os.path.join(app_settings_dir, "overrides.json5"), ] for overrides_path in all_override_paths: if not os.path.exists(overrides_path): continue with open(overrides_path, encoding="utf-8") as fid: try: if overrides_path.endswith(".json5"): path_overrides = json5.load(fid) else: path_overrides = json.load(fid) for plugin_id, config in path_overrides.items(): recursive_update(overrides.setdefault(plugin_id, {}), config) except Exception as e: error = e # type:ignore[assignment] # Allow `default_settings_overrides.json` files in /labconfig dirs # to allow layering of defaults cm = ConfigManager(config_dir_name="labconfig") for plugin_id, config in cm.get("default_setting_overrides").items(): # type:ignore[no-untyped-call] recursive_update(overrides.setdefault(plugin_id, {}), config) return overrides, error def get_settings( app_settings_dir: str, schemas_dir: str, settings_dir: str, schema_name: str = "", overrides: dict[str, Any] | None = None, labextensions_path: list[str] | None = None, translator: Any = None, ids_only: bool = False, ) -> tuple[dict[str, Any], list[Any]]: """ Get settings. Parameters ---------- app_settings_dir: Path to applications settings. schemas_dir: str Path to schemas. settings_dir: Path to settings. schema_name str, optional Schema name. Default is "". overrides: dict, optional Settings overrides. If not provided, the overrides will be loaded from the `app_settings_dir`. Default is None. labextensions_path: list, optional List of paths to federated labextensions containing their own schema files. translator: Callable[[Dict], Dict] or None, optional Translate a schema. It requires the schema dictionary and returns its translation Returns ------- tuple The first item is a dictionary with a list of setting if no `schema_name` was provided (only the ids if `ids_only=True`), otherwise it is a dictionary with id, raw, scheme, settings and version keys. The second item is a list of warnings. Warnings will either be a list of i) strings with the warning messages or ii) `None`. """ result = {} warnings = [] if overrides is None: overrides, _error = _get_overrides(app_settings_dir) if schema_name: schema, version = _get_schema(schemas_dir, schema_name, overrides, labextensions_path) if translator is not None: schema = translator(schema) user_settings = _get_user_settings(settings_dir, schema_name, schema) warnings = [user_settings.pop("warning")] result = {"id": schema_name, "schema": schema, "version": version, **user_settings} else: settings_list, warnings = _list_settings( schemas_dir, settings_dir, overrides, labextensions_path=labextensions_path, translator=translator, ids_only=ids_only, ) result = { "settings": settings_list, } return result, warnings def save_settings( schemas_dir: str, settings_dir: str, schema_name: str, raw_settings: str, overrides: dict[str, Any], labextensions_path: list[str] | None = None, ) -> None: """ Save ``raw_settings`` settings for ``schema_name``. Parameters ---------- schemas_dir: str Path to schemas. settings_dir: str Path to settings. schema_name str Schema name. raw_settings: str Raw serialized settings dictionary overrides: dict Settings overrides. labextensions_path: list, optional List of paths to federated labextensions containing their own schema files. """ payload = json5.loads(raw_settings) # Validate the data against the schema. schema, _ = _get_schema( schemas_dir, schema_name, overrides, labextensions_path=labextensions_path ) validator = Validator(schema) validator.validate(payload) # Write the raw data (comments included) to a file. path = _path(settings_dir, schema_name, True, SETTINGS_EXTENSION) with open(path, "w", encoding="utf-8") as fid: fid.write(raw_settings) class SchemaHandler(APIHandler): """Base handler for handler requiring access to settings.""" def initialize( self, app_settings_dir: str, schemas_dir: str, settings_dir: str, labextensions_path: list[str] | None, **kwargs: Any, ) -> None: """Initialize the handler.""" super().initialize(**kwargs) self.overrides, error = _get_overrides(app_settings_dir) self.app_settings_dir = app_settings_dir self.schemas_dir = schemas_dir self.settings_dir = settings_dir self.labextensions_path = labextensions_path if error: overrides_warning = "Failed loading overrides: %s" self.log.warning(overrides_warning % str(error)) def get_current_locale(self) -> str: """ Get the current locale as specified in the translation-extension settings. Returns ------- str The current locale string. Notes ----- If the locale setting is not available or not valid, it will default to jupyterlab_server.translation_utils.DEFAULT_LOCALE. """ try: settings, _ = get_settings( self.app_settings_dir, self.schemas_dir, self.settings_dir, schema_name=L10N_SCHEMA_NAME, overrides=self.overrides, labextensions_path=self.labextensions_path, ) except web.HTTPError as e: schema_warning = "Missing or misshapen translation settings schema:\n%s" self.log.warning(schema_warning % str(e)) settings = {} current_locale = settings.get("settings", {}).get("locale") or SYS_LOCALE if current_locale == "default": current_locale = SYS_LOCALE if not is_valid_locale(current_locale): current_locale = DEFAULT_LOCALE return current_locale jupyterlab_server-2.25.2/jupyterlab_server/spec.py000066400000000000000000000014711452610450300224270ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """OpenAPI spec utils.""" from __future__ import annotations import os import typing from pathlib import Path if typing.TYPE_CHECKING: from openapi_core.spec.paths import Spec HERE = Path(os.path.dirname(__file__)).resolve() def get_openapi_spec() -> Spec: """Get the OpenAPI spec object.""" from openapi_core.spec.paths import Spec openapi_spec_dict = get_openapi_spec_dict() return Spec.from_dict(openapi_spec_dict) # type:ignore[arg-type] def get_openapi_spec_dict() -> dict[str, typing.Any]: """Get the OpenAPI spec as a dictionary.""" from ruamel.yaml import YAML path = HERE / "rest-api.yml" yaml = YAML(typ="safe") return yaml.load(path.read_text(encoding="utf-8")) jupyterlab_server-2.25.2/jupyterlab_server/templates/000077500000000000000000000000001452610450300231165ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/templates/403.html000066400000000000000000000005031452610450300243100ustar00rootroot00000000000000 403 Forbidden

Sorry ..

.. you are not allowed to see this content!

jupyterlab_server-2.25.2/jupyterlab_server/templates/error.html000066400000000000000000000023031452610450300251330ustar00rootroot00000000000000 {% block title %}{{page_title | escape}{% endblock %} {% block favicon %}{% endblock %} {% block stylesheet %} {% endblock %} {% block site %}
{% block h1_error %}

{{status_code | escape }} : {{status_message | escape }}

{% endblock h1_error %} {% block error_detail %} {% if message %}

The error was:

{{message | escape }}
{% endif %} {% endblock %} {% endblock %} {% block script %} {% endblock script %} jupyterlab_server-2.25.2/jupyterlab_server/templates/index.html000066400000000000000000000025771452610450300251260ustar00rootroot00000000000000 {% block title %}{{page_title | escape }}{% endblock %} {% block stylesheet %} {% for css_file in css_files %} {% endfor %} {% endblock %} {# Copy so we do not modify the page_config with updates. #} {% set page_config_full = page_config.copy() %} {# Set a dummy variable - we just want the side effect of the update. #} {% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %} {% block favicon %} {% endblock %} {% for js_file in js_files %} {% endfor %} {% block meta %} {% endblock %} jupyterlab_server-2.25.2/jupyterlab_server/test_data/000077500000000000000000000000001452610450300230705ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/app-settings/000077500000000000000000000000001452610450300255065ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/app-settings/overrides.json000066400000000000000000000015141452610450300304040ustar00rootroot00000000000000{ "@jupyterlab/apputils-extension:themes": { "theme": "JupyterLab Dark", "codeCellConfig": { "lineNumbers": true } }, "@jupyterlab/unicode-extension:plugin": { "comment": "Here are some languages with unicode in their names: id: Bahasa Indonesia, ms: Bahasa Melayu, bs: Bosanski, ca: Català, cs: Čeština, da: Dansk, de: Deutsch, et: Eesti, en: English, es: Español, fil: Filipino, fr: Français, it: Italiano, hu: Magyar, nl: Nederlands, no: Norsk, pl: Polski, pt-br: Português (Brasil), pt: Português (Portugal), ro: Română, fi: Suomi, sv: Svenska, vi: Tiếng Việt, tr: Türkçe, el: Ελληνικά, ru: Русский, sr: Српски, uk: Українська, he: עברית, ar: العربية, th: ไทย, ko: 한국어, ja: 日本語, zh: 中文(中国), zh-tw: 中文(台灣)" } } jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/000077500000000000000000000000001452610450300245135ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/000077500000000000000000000000001452610450300267745ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/apputils-extension/000077500000000000000000000000001452610450300326475ustar00rootroot00000000000000themes.json000066400000000000000000000015361452610450300347550ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/apputils-extension{ "title": "Theme", "description": "Theme manager settings.", "properties": { "theme": { "type": "string", "title": "Selected Theme", "default": "JupyterLab Light" }, "codeCellConfig": { "title": "Code Cell Configuration", "description": "The configuration for all code cells.", "$ref": "#/definitions/editorConfig", "default": { "autoClosingBrackets": true, "cursorBlinkRate": 530, "fontFamily": null, "fontSize": null, "lineHeight": null, "lineNumbers": false, "lineWrap": "off", "matchBrackets": true, "readOnly": false, "insertSpaces": true, "tabSize": 4, "wordWrapColumn": 80, "rulers": [], "codeFolding": false, "lineWiseCopyCut": true } } }, "type": "object" } jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/codemirror-extension/000077500000000000000000000000001452610450300331535ustar00rootroot00000000000000commands.json000066400000000000000000000006171452610450300355740ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/codemirror-extension{ "jupyter.lab.setting-icon-class": "jp-TextEditorIcon", "jupyter.lab.setting-icon-label": "CodeMirror", "title": "CodeMirror", "description": "Text editor settings for all CodeMirror editors.", "properties": { "keyMap": { "type": "string", "title": "Key Map", "default": "default" }, "theme": { "type": "string", "title": "Theme", "default": "default" } }, "type": "object" } jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/shortcuts-extension/000077500000000000000000000000001452610450300330445ustar00rootroot00000000000000package.json.orig000066400000000000000000000001151452610450300362070ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/shortcuts-extension{ "name": "@jupyterlab/shortcuts-extension", "version": "test-version" } plugin.json000066400000000000000000000445051452610450300351660ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/shortcuts-extension{ "jupyter.lab.setting-icon-class": "jp-LauncherIcon", "jupyter.lab.setting-icon-label": "Keyboard Shortcuts", "title": "Keyboard Shortcuts", "description": "Keyboard shortcut settings for JupyterLab.", "properties": { "application:activate-next-tab": { "default": {}, "properties": { "command": { "default": "application:activate-next-tab" }, "keys": { "default": ["Ctrl Shift ]"] }, "selector": { "default": "body" } }, "type": "object" }, "application:activate-previous-tab": { "default": {}, "properties": { "command": { "default": "application:activate-previous-tab" }, "keys": { "default": ["Ctrl Shift ["] }, "selector": { "default": "body" } }, "type": "object" }, "application:toggle-mode": { "default": {}, "properties": { "command": { "default": "application:toggle-mode" }, "keys": { "default": ["Accel Shift Enter"] }, "selector": { "default": "body" } }, "type": "object" }, "command-palette:toggle": { "default": {}, "properties": { "command": { "default": "apputils:toggle-command-palette" }, "keys": { "default": ["Accel Shift C"] }, "selector": { "default": "body" } }, "type": "object" }, "completer:invoke-console": { "default": {}, "properties": { "command": { "default": "completer:invoke-console" }, "keys": { "default": ["Tab"] }, "selector": { "default": ".jp-CodeConsole-promptCell .jp-mod-completer-enabled" } }, "type": "object" }, "completer:invoke-notebook": { "default": {}, "properties": { "command": { "default": "completer:invoke-notebook" }, "keys": { "default": ["Tab"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode .jp-mod-completer-enabled" } }, "type": "object" }, "console:linebreak": { "default": {}, "properties": { "command": { "default": "console:linebreak" }, "keys": { "default": ["Ctrl Enter"] }, "selector": { "default": ".jp-CodeConsole-promptCell" } }, "type": "object" }, "console:run": { "default": {}, "properties": { "command": { "default": "console:run" }, "keys": { "default": ["Enter"] }, "selector": { "default": ".jp-CodeConsole-promptCell" } }, "type": "object" }, "console:run-forced": { "default": {}, "properties": { "command": { "default": "console:run-forced" }, "keys": { "default": ["Shift Enter"] }, "selector": { "default": ".jp-CodeConsole-promptCell" } }, "type": "object" }, "docmanager:close": { "default": {}, "properties": { "command": { "default": "docmanager:close" }, "keys": { "default": ["Ctrl Q"] }, "selector": { "default": ".jp-Activity" } }, "type": "object" }, "docmanager:create-launcher": { "default": {}, "properties": { "command": { "default": "docmanager:create-launcher" }, "keys": { "default": ["Accel Shift L"] }, "selector": { "default": "body" } }, "type": "object" }, "docmanager:save": { "default": {}, "properties": { "command": { "default": "docmanager:save" }, "keys": { "default": ["Accel S"] }, "selector": { "default": "body" } }, "type": "object" }, "filebrowser:toggle-main": { "default": {}, "properties": { "command": { "default": "filebrowser:toggle-main" }, "keys": { "default": ["Accel Shift F"] }, "selector": { "default": "body" } }, "type": "object" }, "fileeditor:run-code": { "default": {}, "properties": { "command": { "default": "fileeditor:run-code" }, "keys": { "default": ["Shift Enter"] }, "selector": { "default": ".jp-FileEditor" } }, "type": "object" }, "help:toggle": { "default": {}, "properties": { "command": { "default": "help:toggle" }, "keys": { "default": ["Ctrl Shift H"] }, "selector": { "default": "body" } }, "type": "object" }, "imageviewer:reset-zoom": { "default": {}, "properties": { "command": { "default": "imageviewer:reset-zoom" }, "keys": { "default": ["0"] }, "selector": { "default": ".jp-ImageViewer" } }, "type": "object" }, "imageviewer:zoom-in": { "default": {}, "properties": { "command": { "default": "imageviewer:zoom-in" }, "keys": { "default": ["="] }, "selector": { "default": ".jp-ImageViewer" } }, "type": "object" }, "imageviewer:zoom-out": { "default": {}, "properties": { "command": { "default": "imageviewer:zoom-out" }, "keys": { "default": ["-"] }, "selector": { "default": ".jp-ImageViewer" } }, "type": "object" }, "inspector:open": { "default": {}, "properties": { "command": { "default": "inspector:open" }, "keys": { "default": ["Accel I"] }, "selector": { "default": "body" } }, "type": "object" }, "notebook:change-cell-to-code": { "default": {}, "properties": { "command": { "default": "notebook:change-cell-to-code" }, "keys": { "default": ["Y"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-to-cell-heading-1": { "default": {}, "properties": { "command": { "default": "notebook:change-to-cell-heading-1" }, "keys": { "default": ["1"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-to-cell-heading-2": { "default": {}, "properties": { "command": { "default": "notebook:change-to-cell-heading-2" }, "keys": { "default": ["2"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-to-cell-heading-3": { "default": {}, "properties": { "command": { "default": "notebook:change-to-cell-heading-3" }, "keys": { "default": ["3"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-to-cell-heading-4": { "default": {}, "properties": { "command": { "default": "notebook:change-to-cell-heading-4" }, "keys": { "default": ["4"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-to-cell-heading-5": { "default": {}, "properties": { "command": { "default": "notebook:change-to-cell-heading-5" }, "keys": { "default": ["5"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-to-cell-heading-6": { "default": {}, "properties": { "command": { "default": "notebook:change-to-cell-heading-6" }, "keys": { "default": ["6"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-cell-to-markdown": { "default": {}, "properties": { "command": { "default": "notebook:change-cell-to-markdown" }, "keys": { "default": ["M"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:change-cell-to-raw": { "default": {}, "properties": { "command": { "default": "notebook:change-cell-to-raw" }, "keys": { "default": ["R"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:copy-cell": { "default": {}, "properties": { "command": { "default": "notebook:copy-cell" }, "keys": { "default": ["C"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:cut-cell": { "default": {}, "properties": { "command": { "default": "notebook:cut-cell" }, "keys": { "default": ["X"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:delete-cell": { "default": {}, "properties": { "command": { "default": "notebook:delete-cell" }, "keys": { "default": ["D", "D"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:enter-command-mode-1": { "default": {}, "properties": { "command": { "default": "notebook:enter-command-mode" }, "keys": { "default": ["Escape"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode" } }, "type": "object" }, "notebook:enter-command-mode-2": { "default": {}, "properties": { "command": { "default": "notebook:enter-command-mode" }, "keys": { "default": ["Ctrl M"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode" } }, "type": "object" }, "notebook:enter-edit-mode": { "default": {}, "properties": { "command": { "default": "notebook:enter-edit-mode" }, "keys": { "default": ["Enter"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:extend-marked-cells-above-1": { "default": {}, "properties": { "command": { "default": "notebook:extend-marked-cells-above" }, "keys": { "default": ["Shift ArrowUp"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:extend-marked-cells-above-2": { "default": {}, "properties": { "command": { "default": "notebook:extend-marked-cells-above" }, "keys": { "default": ["Shift K"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:extend-marked-cells-below-1": { "default": {}, "properties": { "command": { "default": "notebook:extend-marked-cells-below" }, "keys": { "default": ["Shift ArrowDown"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:extend-marked-cells-below-2": { "default": {}, "properties": { "command": { "default": "notebook:extend-marked-cells-below" }, "keys": { "default": ["Shift J"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:insert-cell-above": { "default": {}, "properties": { "command": { "default": "notebook:insert-cell-above" }, "keys": { "default": ["A"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:insert-cell-below": { "default": {}, "properties": { "command": { "default": "notebook:insert-cell-below" }, "keys": { "default": ["B"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:interrupt-kernel": { "default": {}, "properties": { "command": { "default": "notebook:interrupt-kernel" }, "keys": { "default": ["I", "I"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:merge-cells": { "default": {}, "properties": { "command": { "default": "notebook:merge-cells" }, "keys": { "default": ["Shift M"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:move-cursor-down-1": { "default": {}, "properties": { "command": { "default": "notebook:move-cursor-down" }, "keys": { "default": ["ArrowDown"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:move-cursor-down-2": { "default": {}, "properties": { "command": { "default": "notebook:move-cursor-down" }, "keys": { "default": ["J"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:move-cursor-up-1": { "default": {}, "properties": { "command": { "default": "notebook:move-cursor-up" }, "keys": { "default": ["ArrowUp"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:move-cursor-up-2": { "default": {}, "properties": { "command": { "default": "notebook:move-cursor-up" }, "keys": { "default": ["K"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:paste-cell": { "default": {}, "properties": { "command": { "default": "notebook:paste-cell" }, "keys": { "default": ["V"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:redo-cell-action": { "default": {}, "properties": { "command": { "default": "notebook:redo-cell-action" }, "keys": { "default": ["Shift Z"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:restart-kernel": { "default": {}, "properties": { "command": { "default": "notebook:restart-kernel" }, "keys": { "default": ["0", "0"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:run-cell-1": { "default": {}, "properties": { "command": { "default": "notebook:run-cell" }, "keys": { "default": ["Ctrl Enter"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:run-cell-2": { "default": {}, "properties": { "command": { "default": "notebook:run-cell" }, "keys": { "default": ["Ctrl Enter"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode" } }, "type": "object" }, "notebook:run-cell-and-insert-below-1": { "default": {}, "properties": { "command": { "default": "notebook:run-cell-and-insert-below" }, "keys": { "default": ["Alt Enter"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:run-cell-and-insert-below-2": { "default": {}, "properties": { "command": { "default": "notebook:run-cell-and-insert-below" }, "keys": { "default": ["Alt Enter"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode" } }, "type": "object" }, "notebook:run-cell-and-select-next-1": { "default": {}, "properties": { "command": { "default": "notebook:run-cell-and-select-next" }, "keys": { "default": ["Shift Enter"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode" } }, "type": "object" }, "notebook:run-cell-and-select-next-2": { "default": {}, "properties": { "command": { "default": "notebook:run-cell-and-select-next" }, "keys": { "default": ["Shift Enter"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:split-cell-at-cursor": { "default": {}, "properties": { "command": { "default": "notebook:split-cell-at-cursor" }, "keys": { "default": ["Ctrl Shift -"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode" } }, "type": "object" }, "notebook:toggle-all-cell-line-numbers": { "default": {}, "properties": { "command": { "default": "notebook:toggle-all-cell-line-numbers" }, "keys": { "default": ["Shift L"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:toggle-cell-line-numbers": { "default": {}, "properties": { "command": { "default": "notebook:toggle-cell-line-numbers" }, "keys": { "default": ["L"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "notebook:undo-cell-action": { "default": {}, "properties": { "command": { "default": "notebook:undo-cell-action" }, "keys": { "default": ["Z"] }, "selector": { "default": ".jp-Notebook:focus" } }, "type": "object" }, "settingeditor:open": { "default": {}, "properties": { "command": { "default": "settingeditor:open" }, "keys": { "default": ["Accel ,"] }, "selector": { "default": "body" } }, "type": "object" }, "tooltip:dismiss-console": { "default": {}, "properties": { "command": { "default": "tooltip:dismiss" }, "keys": { "default": ["Escape"] }, "selector": { "default": "body.jp-mod-tooltip .jp-CodeConsole" } }, "type": "object" }, "tooltip:dismiss-notebook": { "default": {}, "properties": { "command": { "default": "tooltip:dismiss" }, "keys": { "default": ["Escape"] }, "selector": { "default": "body.jp-mod-tooltip .jp-Notebook" } }, "type": "object" }, "tooltip:launch-console": { "default": {}, "properties": { "command": { "default": "tooltip:launch-console" }, "keys": { "default": ["Shift Tab"] }, "selector": { "default": ".jp-CodeConsole-promptCell .jp-InputArea-editor:not(.jp-mod-has-primary-selection)" } }, "type": "object" }, "tooltip:launch-notebook": { "default": {}, "properties": { "command": { "default": "tooltip:launch-notebook" }, "keys": { "default": ["Shift Tab"] }, "selector": { "default": ".jp-Notebook.jp-mod-editMode .jp-InputArea-editor:not(.jp-mod-has-primary-selection)" } }, "type": "object" } }, "oneOf": [{ "$ref": "#/definitions/shortcut" }], "type": "object", "definitions": { "shortcut": { "properties": { "command": { "type": "string" }, "keys": { "items": { "type": "string" }, "minItems": 1, "type": "array" }, "selector": { "type": "string" } }, "type": "object" } } } jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/translation-extension/000077500000000000000000000000001452610450300333445ustar00rootroot00000000000000plugin.json000066400000000000000000000006171452610450300354620ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/translation-extension{ "jupyter.lab.setting-icon": "ui-components:settings", "jupyter.lab.setting-icon-label": "Language", "title": "Language", "description": "Language settings.", "type": "object", "properties": { "locale": { "type": "string", "title": "Language locale", "description": "Set the interface display language. Examples: 'es_CO', 'fr'.", "default": "en" } } } jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/unicode-extension/000077500000000000000000000000001452610450300324345ustar00rootroot00000000000000plugin.json000066400000000000000000000017021452610450300345460ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/schemas/@jupyterlab/unicode-extension{ "jupyter.lab.setting-icon": "ui-components:unicode", "jupyter.lab.setting-icon-label": "Unicode", "title": "Unicode", "description": "Unicode", "type": "object", "properties": { "comment": { "type": "string", "title": "Comment", "description": "Here are some languages with unicode in their names: id: Bahasa Indonesia, ms: Bahasa Melayu, bs: Bosanski, ca: Català, cs: Čeština, da: Dansk, de: Deutsch, et: Eesti, en: English, es: Español, fil: Filipino, fr: Français, it: Italiano, hu: Magyar, nl: Nederlands, no: Norsk, pl: Polski, pt-br: Português (Brasil), pt: Português (Portugal), ro: Română, fi: Suomi, sv: Svenska, vi: Tiếng Việt, tr: Türkçe, el: Ελληνικά, ru: Русский, sr: Српски, uk: Українська, he: עברית, ar: العربية, th: ไทย, ko: 한국어, ja: 日本語, zh: 中文(中国), zh-tw: 中文(台灣)", "default": "no comment" } } } jupyterlab_server-2.25.2/jupyterlab_server/test_data/workspaces/000077500000000000000000000000001452610450300252515ustar00rootroot00000000000000jupyterlab_server-2.25.2/jupyterlab_server/test_data/workspaces/foo-2c26.jupyterlab-workspace000066400000000000000000000000501452610450300326000ustar00rootroot00000000000000{"data": {}, "metadata": {"id": "foo"}} jupyterlab_server-2.25.2/jupyterlab_server/test_data/workspaces/foo-92dd.jupyterlab-workspace000066400000000000000000000000531452610450300326710ustar00rootroot00000000000000{"data": {}, "metadata": {"id": "f/o/o/"}} jupyterlab_server-2.25.2/jupyterlab_server/test_utils.py000066400000000000000000000150121452610450300236700ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Testing utils.""" from __future__ import annotations import json import os import sys from http.cookies import SimpleCookie from pathlib import Path from urllib.parse import parse_qs, urlparse import tornado.httpclient import tornado.web from openapi_core import V30RequestValidator, V30ResponseValidator from openapi_core.spec.paths import Spec from openapi_core.validation.request.datatypes import RequestParameters from tornado.httpclient import HTTPRequest, HTTPResponse from werkzeug.datastructures import Headers, ImmutableMultiDict from jupyterlab_server.spec import get_openapi_spec HERE = Path(os.path.dirname(__file__)).resolve() with open(HERE / "test_data" / "app-settings" / "overrides.json", encoding="utf-8") as fid: big_unicode_string = json.load(fid)["@jupyterlab/unicode-extension:plugin"]["comment"] class TornadoOpenAPIRequest: """ Converts a torando request to an OpenAPI one """ def __init__(self, request: HTTPRequest, spec: Spec): """Initialize the request.""" self.request = request self.spec = spec if request.url is None: msg = "Request URL is missing" # type:ignore[unreachable] raise RuntimeError(msg) self._url_parsed = urlparse(request.url) cookie: SimpleCookie = SimpleCookie() cookie.load(request.headers.get("Set-Cookie", "")) cookies = {} for key, morsel in cookie.items(): cookies[key] = morsel.value # extract the path o = urlparse(request.url) # gets deduced by path finder against spec path: dict = {} self.parameters = RequestParameters( query=ImmutableMultiDict(parse_qs(o.query)), header=dict(request.headers), cookie=ImmutableMultiDict(cookies), path=path, ) @property def host_url(self) -> str: url = self.request.url return url[: url.index("/lab")] @property def path(self) -> str: # extract the best matching url # work around lack of support for path parameters which can contain slashes # https://github.com/OAI/OpenAPI-Specification/issues/892 url = None o = urlparse(self.request.url) for path_ in self.spec["paths"]: if url: continue # type:ignore[unreachable] has_arg = "{" in path_ path = path_[: path_.index("{")] if has_arg else path_ if path in o.path: u = o.path[o.path.index(path) :] if not has_arg and len(u) == len(path): url = u if has_arg and not u.endswith("/"): url = u[: len(path)] + r"foo" if url is None: msg = f"Could not find matching pattern for {o.path}" raise ValueError(msg) return url @property def method(self) -> str: method = self.request.method return method and method.lower() or "" @property def body(self) -> str | None: if self.request.body is None: return None # type:ignore[unreachable] if not isinstance(self.request.body, bytes): msg = "Request body is invalid" # type:ignore[unreachable] raise AssertionError(msg) return self.request.body.decode("utf-8") @property def mimetype(self) -> str: # Order matters because all tornado requests # include Accept */* which does not necessarily match the content type request = self.request return ( request.headers.get("Content-Type") or request.headers.get("Accept") or "application/json" ) class TornadoOpenAPIResponse: """A tornado open API response.""" def __init__(self, response: HTTPResponse): """Initialize the response.""" self.response = response @property def data(self) -> str: if not isinstance(self.response.body, bytes): msg = "Response body is invalid" # type:ignore[unreachable] raise AssertionError(msg) return self.response.body.decode("utf-8") @property def status_code(self) -> int: return int(self.response.code) @property def mimetype(self) -> str: return str(self.response.headers.get("Content-Type", "application/json")) @property def headers(self) -> Headers: return Headers(dict(self.response.headers)) def validate_request(response: HTTPResponse) -> None: """Validate an API request""" openapi_spec = get_openapi_spec() request = TornadoOpenAPIRequest(response.request, openapi_spec) V30RequestValidator(openapi_spec).validate(request) torn_response = TornadoOpenAPIResponse(response) V30ResponseValidator(openapi_spec).validate(request, torn_response) def maybe_patch_ioloop() -> None: """a windows 3.8+ patch for the asyncio loop""" if ( sys.platform.startswith("win") and tornado.version_info < (6, 1) and sys.version_info >= (3, 8) ): try: from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy except ImportError: pass # not affected else: from asyncio import get_event_loop_policy, set_event_loop_policy if type(get_event_loop_policy()) is WindowsProactorEventLoopPolicy: # WindowsProactorEventLoopPolicy is not compatible with tornado 6 # fallback to the pre-3.8 default of Selector set_event_loop_policy(WindowsSelectorEventLoopPolicy()) def expected_http_error( # noqa: PLR0911 error: Exception, expected_code: int, expected_message: str | None = None ) -> bool: """Check that the error matches the expected output error.""" e = error.value # type:ignore[attr-defined] if isinstance(e, tornado.web.HTTPError): if expected_code != e.status_code: return False if expected_message is not None and expected_message != str(e): return False return True elif any( [ isinstance(e, tornado.httpclient.HTTPClientError), isinstance(e, tornado.httpclient.HTTPError), ] ): if expected_code != e.code: return False if expected_message: message = json.loads(e.response.body.decode())["message"] if expected_message != message: return False return True return False jupyterlab_server-2.25.2/jupyterlab_server/themes_handler.py000066400000000000000000000067411452610450300244640ustar00rootroot00000000000000"""Tornado handlers for dynamic theme loading.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import os import re from glob import glob from typing import Any, Generator from urllib.parse import urlparse from jupyter_server.base.handlers import FileFindHandler from jupyter_server.utils import url_path_join as ujoin class ThemesHandler(FileFindHandler): """A file handler that mangles local urls in CSS files.""" def initialize( self, path: str | list[str], default_filename: str | None = None, no_cache_paths: list[str] | None = None, themes_url: str | None = None, labextensions_path: list[str] | None = None, **kwargs: Any, ) -> None: """Initialize the handler.""" # Get all of the available theme paths in order labextensions_path = labextensions_path or [] ext_paths = [] for ext_dir in labextensions_path: theme_pattern = ext_dir + "/**/themes" ext_paths.extend([path for path in glob(theme_pattern, recursive=True)]) # Add the core theme path last if not isinstance(path, list): path = [path] path = ext_paths + path FileFindHandler.initialize( self, path, default_filename=default_filename, no_cache_paths=no_cache_paths ) self.themes_url = themes_url def get_content( # type:ignore[override] self, abspath: str, start: int | None = None, end: int | None = None ) -> bytes | Generator[bytes, None, None]: """Retrieve the content of the requested resource which is located at the given absolute path. This method should either return a byte string or an iterator of byte strings. """ base, ext = os.path.splitext(abspath) if ext != ".css": return FileFindHandler.get_content(abspath, start, end) return self._get_css() def get_content_size(self) -> int: """Retrieve the total size of the resource at the given path.""" assert self.absolute_path is not None base, ext = os.path.splitext(self.absolute_path) if ext != ".css": return FileFindHandler.get_content_size(self) else: return len(self._get_css()) def _get_css(self) -> bytes: """Get the mangled css file contents.""" assert self.absolute_path is not None with open(self.absolute_path, "rb") as fid: data = fid.read().decode("utf-8") if not self.themes_url: return b"" basedir = os.path.dirname(self.path).replace(os.sep, "/") basepath = ujoin(self.themes_url, basedir) # Replace local paths with mangled paths. # We only match strings that are local urls, # e.g. `url('../foo.css')`, `url('images/foo.png')` pattern = r"url\('(.*)'\)|url\('(.*)'\)" def replacer(m: Any) -> Any: """Replace the matched relative url with the mangled url.""" group = m.group() # Get the part that matched part = next(g for g in m.groups() if g) # Ignore urls that start with `/` or have a protocol like `http`. parsed = urlparse(part) if part.startswith("/") or parsed.scheme: return group return group.replace(part, ujoin(basepath, part)) return re.sub(pattern, replacer, data).encode("utf-8") jupyterlab_server-2.25.2/jupyterlab_server/translation_utils.py000066400000000000000000000527261452610450300252640ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """ Localization utilities to find available language packs and packages with localization data. """ from __future__ import annotations import gettext import importlib import json import locale import os import re import sys import traceback from functools import lru_cache from typing import Any, Pattern import babel from packaging.version import parse as parse_version # See compatibility note on `group` keyword in https://docs.python.org/3/library/importlib.metadata.html#entry-points if sys.version_info < (3, 10): # pragma: no cover from importlib_metadata import entry_points else: # pragma: no cover from importlib.metadata import entry_points # Entry points JUPYTERLAB_LANGUAGEPACK_ENTRY = "jupyterlab.languagepack" JUPYTERLAB_LOCALE_ENTRY = "jupyterlab.locale" # Constants DEFAULT_LOCALE = "en" SYS_LOCALE = locale.getlocale()[0] or DEFAULT_LOCALE LOCALE_DIR = "locale" LC_MESSAGES_DIR = "LC_MESSAGES" DEFAULT_DOMAIN = "jupyterlab" L10N_SCHEMA_NAME = "@jupyterlab/translation-extension:plugin" PY37_OR_LOWER = sys.version_info[:2] <= (3, 7) _default_schema_context = "schema" _default_settings_context = "settings" _lab_i18n_config = "jupyter.lab.internationalization" # mapping of schema translatable string selectors to translation context DEFAULT_SCHEMA_SELECTORS = { "properties/.*/title": _default_settings_context, "properties/.*/description": _default_settings_context, "definitions/.*/properties/.*/title": _default_settings_context, "definitions/.*/properties/.*/description": _default_settings_context, "title": _default_schema_context, "description": _default_schema_context, # JupyterLab-specific r"jupyter\.lab\.setting-icon-label": _default_settings_context, r"jupyter\.lab\.menus/.*/label": "menu", r"jupyter\.lab\.toolbars/.*/label": "toolbar", } @lru_cache def _get_default_schema_selectors() -> dict[Pattern, str]: return { re.compile("^/" + pattern + "$"): context for pattern, context in DEFAULT_SCHEMA_SELECTORS.items() } def _prepare_schema_patterns(schema: dict) -> dict[Pattern, str]: return { **_get_default_schema_selectors(), **{ re.compile("^/" + selector + "$"): _default_schema_context for selector in schema.get(_lab_i18n_config, {}).get("selectors", []) }, } # --- Private process helpers # ---------------------------------------------------------------------------- def _get_installed_language_pack_locales() -> tuple[dict[str, Any], str]: """ Get available installed language pack locales. Returns ------- tuple A tuple, where the first item is the result and the second item any error messages. """ data = {} messages = [] for entry_point in entry_points(group=JUPYTERLAB_LANGUAGEPACK_ENTRY): try: data[entry_point.name] = os.path.dirname(entry_point.load().__file__) except Exception: # pragma: no cover messages.append(traceback.format_exc()) message = "\n".join(messages) return data, message def _get_installed_package_locales() -> tuple[dict[str, Any], str]: """ Get available installed packages containing locale information. Returns ------- tuple A tuple, where the first item is the result and the second item any error messages. The value for the key points to the root location the package. """ data = {} messages = [] for entry_point in entry_points(group=JUPYTERLAB_LOCALE_ENTRY): try: data[entry_point.name] = os.path.dirname(entry_point.load().__file__) except Exception: messages.append(traceback.format_exc()) message = "\n".join(messages) return data, message # --- Helpers # ---------------------------------------------------------------------------- def is_valid_locale(locale_: str) -> bool: """ Check if a `locale_` value is valid. Parameters ---------- locale_: str Language locale code. Notes ----- A valid locale is in the form language (See ISO-639 standard) and an optional territory (See ISO-3166 standard). Examples of valid locales: - English: DEFAULT_LOCALE - Australian English: "en_AU" - Portuguese: "pt" - Brazilian Portuguese: "pt_BR" Examples of invalid locales: - Australian Spanish: "es_AU" - Brazilian German: "de_BR" """ # Add exception for Norwegian if locale_ == "no_NO": return True valid = False try: babel.Locale.parse(locale_) valid = True except (babel.core.UnknownLocaleError, ValueError): # Expected error if the locale is unknown pass return valid def get_display_name(locale_: str, display_locale: str = DEFAULT_LOCALE) -> str: """ Return the language name to use with a `display_locale` for a given language locale. Parameters ---------- locale_: str The language name to use. display_locale: str, optional The language to display the `locale_`. Returns ------- str Localized `locale_` and capitalized language name using `display_locale` as language. """ locale_ = locale_ if is_valid_locale(locale_) else DEFAULT_LOCALE display_locale = display_locale if is_valid_locale(display_locale) else DEFAULT_LOCALE loc = babel.Locale.parse(locale_) display_name = loc.get_display_name(display_locale) if display_name: display_name = display_name[0].upper() + display_name[1:] return display_name # type:ignore[return-value] def merge_locale_data( language_pack_locale_data: dict[str, Any], package_locale_data: dict[str, Any] ) -> dict[str, Any]: """ Merge language pack data with locale data bundled in packages. Parameters ---------- language_pack_locale_data: dict The dictionary with language pack locale data. package_locale_data: dict The dictionary with package locale data. Returns ------- dict Merged locale data. """ result = language_pack_locale_data package_lp_metadata = language_pack_locale_data.get("", {}) package_lp_version = package_lp_metadata.get("version", None) package_lp_domain = package_lp_metadata.get("domain", None) package_metadata = package_locale_data.get("", {}) package_version = package_metadata.get("version", None) package_domain = package_metadata.get("domain", "None") if package_lp_version and package_version and package_domain == package_lp_domain: package_version = parse_version(package_version) package_lp_version = parse_version(package_lp_version) if package_version > package_lp_version: # If package version is more recent, then update keys of the language pack result = language_pack_locale_data.copy() result.update(package_locale_data) return result def get_installed_packages_locale(locale_: str) -> tuple[dict, str]: """ Get all jupyterlab extensions installed that contain locale data. Returns ------- tuple A tuple in the form `(locale_data_dict, message)`, where the `locale_data_dict` is an ordered list of available language packs: >>> {"package-name": locale_data, ...} Examples -------- - `entry_points={"jupyterlab.locale": "package-name = package_module"}` - `entry_points={"jupyterlab.locale": "jupyterlab-git = jupyterlab_git"}` """ found_package_locales, message = _get_installed_package_locales() packages_locale_data = {} messages = message.split("\n") if not message: for package_name, package_root_path in found_package_locales.items(): locales = {} try: locale_path = os.path.join(package_root_path, LOCALE_DIR) # Handle letter casing locales = { loc.lower(): loc for loc in os.listdir(locale_path) if os.path.isdir(os.path.join(locale_path, loc)) } except Exception: messages.append(traceback.format_exc()) if locale_.lower() in locales: locale_json_path = os.path.join( locale_path, locales[locale_.lower()], LC_MESSAGES_DIR, f"{package_name}.json", ) if os.path.isfile(locale_json_path): try: with open(locale_json_path, encoding="utf-8") as fh: packages_locale_data[package_name] = json.load(fh) except Exception: messages.append(traceback.format_exc()) return packages_locale_data, "\n".join(messages) # --- API # ---------------------------------------------------------------------------- def get_language_packs(display_locale: str = DEFAULT_LOCALE) -> tuple[dict, str]: """ Return the available language packs installed in the system. The returned information contains the languages displayed in the current locale. Parameters ---------- display_locale: str, optional Default is DEFAULT_LOCALE. Returns ------- tuple A tuple in the form `(locale_data_dict, message)`. """ found_locales, message = _get_installed_language_pack_locales() locales = {} messages = message.split("\n") if not message: invalid_locales = [] valid_locales = [] messages = [] for locale_ in found_locales: if is_valid_locale(locale_): valid_locales.append(locale_) else: invalid_locales.append(locale_) display_locale = display_locale if display_locale in valid_locales else DEFAULT_LOCALE locales = { DEFAULT_LOCALE: { "displayName": get_display_name(DEFAULT_LOCALE, display_locale), "nativeName": get_display_name(DEFAULT_LOCALE, DEFAULT_LOCALE), } } for locale_ in valid_locales: locales[locale_] = { "displayName": get_display_name(locale_, display_locale), "nativeName": get_display_name(locale_, locale_), } if invalid_locales: messages.append(f"The following locales are invalid: {invalid_locales}!") return locales, "\n".join(messages) def get_language_pack(locale_: str) -> tuple: """ Get a language pack for a given `locale_` and update with any installed package locales. Returns ------- tuple A tuple in the form `(locale_data_dict, message)`. Notes ----- We call `_get_installed_language_pack_locales` via a subprocess to guarantee the results represent the most up-to-date entry point information, which seems to be defined on interpreter startup. """ found_locales, message = _get_installed_language_pack_locales() found_packages_locales, message = get_installed_packages_locale(locale_) locale_data = {} messages = message.split("\n") if not message and is_valid_locale(locale_) and locale_ in found_locales: path = found_locales[locale_] for root, __, files in os.walk(path, topdown=False): for name in files: if name.endswith(".json"): pkg_name = name.replace(".json", "") json_path = os.path.join(root, name) try: with open(json_path, encoding="utf-8") as fh: merged_data = json.load(fh) except Exception: messages.append(traceback.format_exc()) # Load packages with locale data and merge them if pkg_name in found_packages_locales: pkg_data = found_packages_locales[pkg_name] merged_data = merge_locale_data(merged_data, pkg_data) locale_data[pkg_name] = merged_data # Check if package locales exist that do not exists in language pack for pkg_name, data in found_packages_locales.items(): if pkg_name not in locale_data: locale_data[pkg_name] = data return locale_data, "\n".join(messages) # --- Translators # ---------------------------------------------------------------------------- class TranslationBundle: """ Translation bundle providing gettext translation functionality. """ def __init__(self, domain: str, locale_: str): """Initialize the bundle.""" self._domain = domain self._locale = locale_ self._translator = gettext.NullTranslations() self.update_locale(locale_) def update_locale(self, locale_: str) -> None: """ Update the locale. Parameters ---------- locale_: str The language name to use. """ # TODO: Need to handle packages that provide their own .mo files self._locale = locale_ localedir = None if locale_ != DEFAULT_LOCALE: language_pack_module = f"jupyterlab_language_pack_{locale_}" try: mod = importlib.import_module(language_pack_module) assert mod.__file__ is not None localedir = os.path.join(os.path.dirname(mod.__file__), LOCALE_DIR) except Exception: # noqa S110 # no-op pass self._translator = gettext.translation( self._domain, localedir=localedir, languages=(self._locale,), fallback=True ) def gettext(self, msgid: str) -> str: """ Translate a singular string. Parameters ---------- msgid: str The singular string to translate. Returns ------- str The translated string. """ return self._translator.gettext(msgid) def ngettext(self, msgid: str, msgid_plural: str, n: int) -> str: """ Translate a singular string with pluralization. Parameters ---------- msgid: str The singular string to translate. msgid_plural: str The plural string to translate. n: int The number for pluralization. Returns ------- str The translated string. """ return self._translator.ngettext(msgid, msgid_plural, n) def pgettext(self, msgctxt: str, msgid: str) -> str: """ Translate a singular string with context. Parameters ---------- msgctxt: str The message context. msgid: str The singular string to translate. Returns ------- str The translated string. """ # Python 3.7 or lower does not offer translations based on context. # On these versions `pgettext` falls back to `gettext` if PY37_OR_LOWER: translation = self._translator.gettext(msgid) else: translation = self._translator.pgettext(msgctxt, msgid) return translation def npgettext(self, msgctxt: str, msgid: str, msgid_plural: str, n: int) -> str: """ Translate a singular string with context and pluralization. Parameters ---------- msgctxt: str The message context. msgid: str The singular string to translate. msgid_plural: str The plural string to translate. n: int The number for pluralization. Returns ------- str The translated string. """ # Python 3.7 or lower does not offer translations based on context. # On these versions `npgettext` falls back to `ngettext` if PY37_OR_LOWER: translation = self._translator.ngettext(msgid, msgid_plural, n) else: translation = self._translator.npgettext(msgctxt, msgid, msgid_plural, n) return translation # Shorthands def __(self, msgid: str) -> str: """ Shorthand for gettext. Parameters ---------- msgid: str The singular string to translate. Returns ------- str The translated string. """ return self.gettext(msgid) def _n(self, msgid: str, msgid_plural: str, n: int) -> str: """ Shorthand for ngettext. Parameters ---------- msgid: str The singular string to translate. msgid_plural: str The plural string to translate. n: int The number for pluralization. Returns ------- str The translated string. """ return self.ngettext(msgid, msgid_plural, n) def _p(self, msgctxt: str, msgid: str) -> str: """ Shorthand for pgettext. Parameters ---------- msgctxt: str The message context. msgid: str The singular string to translate. Returns ------- str The translated string. """ return self.pgettext(msgctxt, msgid) def _np(self, msgctxt: str, msgid: str, msgid_plural: str, n: int) -> str: """ Shorthand for npgettext. Parameters ---------- msgctxt: str The message context. msgid: str The singular string to translate. msgid_plural: str The plural string to translate. n: int The number for pluralization. Returns ------- str The translated string. """ return self.npgettext(msgctxt, msgid, msgid_plural, n) class translator: # noqa """ Translations manager. """ _TRANSLATORS: dict[str, TranslationBundle] = {} _LOCALE = SYS_LOCALE @staticmethod def normalize_domain(domain: str) -> str: """Normalize a domain name. Parameters ---------- domain: str Domain to normalize Returns ------- str Normalized domain """ return domain.replace("-", "_") @classmethod def set_locale(cls, locale_: str) -> None: """ Set locale for the translation bundles based on the settings. Parameters ---------- locale_: str The language name to use. """ if locale_ == cls._LOCALE: # Nothing to do bail early return if is_valid_locale(locale_): cls._LOCALE = locale_ for _, bundle in cls._TRANSLATORS.items(): bundle.update_locale(locale_) @classmethod def load(cls, domain: str) -> TranslationBundle: """ Load translation domain. The domain is usually the normalized ``package_name``. Parameters ---------- domain: str The translations domain. The normalized python package name. Returns ------- Translator A translator instance bound to the domain. """ norm_domain = translator.normalize_domain(domain) if norm_domain in cls._TRANSLATORS: trans = cls._TRANSLATORS[norm_domain] else: trans = TranslationBundle(norm_domain, cls._LOCALE) cls._TRANSLATORS[norm_domain] = trans return trans @staticmethod def _translate_schema_strings( translations: Any, schema: dict, prefix: str = "", to_translate: dict[Pattern, str] | None = None, ) -> None: """Translate a schema in-place.""" if to_translate is None: to_translate = _prepare_schema_patterns(schema) for key, value in schema.items(): path = prefix + "/" + key if isinstance(value, str): matched = False for pattern, context in to_translate.items(): # noqa if pattern.fullmatch(path): matched = True break if matched: schema[key] = translations.pgettext(context, value) elif isinstance(value, dict): translator._translate_schema_strings( translations, value, prefix=path, to_translate=to_translate, ) elif isinstance(value, list): for i, element in enumerate(value): if not isinstance(element, dict): continue translator._translate_schema_strings( translations, element, prefix=path + "[" + str(i) + "]", to_translate=to_translate, ) @staticmethod def translate_schema(schema: dict) -> dict: """Translate a schema. Parameters ---------- schema: dict The schema to be translated Returns ------- Dict The translated schema """ if translator._LOCALE == DEFAULT_LOCALE: return schema translations = translator.load( schema.get(_lab_i18n_config, {}).get("domain", DEFAULT_DOMAIN) ) new_schema = schema.copy() translator._translate_schema_strings(translations, new_schema) return new_schema jupyterlab_server-2.25.2/jupyterlab_server/translations_handler.py000066400000000000000000000042171452610450300257140ustar00rootroot00000000000000""" Translation handler. """ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import json import traceback from functools import partial import tornado from .settings_utils import SchemaHandler from .translation_utils import ( SYS_LOCALE, get_language_pack, get_language_packs, is_valid_locale, translator, ) class TranslationsHandler(SchemaHandler): """An API handler for translations.""" @tornado.web.authenticated async def get(self, locale: str | None = None) -> None: """ Get installed language packs. If `locale` is equals to "default", the default locale will be used. Parameters ---------- locale: str, optional If no locale is provided, it will list all the installed language packs. Default is `None`. """ data: dict data, message = {}, "" try: current_loop = tornado.ioloop.IOLoop.current() if locale is None: data, message = await current_loop.run_in_executor( None, partial(get_language_packs, display_locale=self.get_current_locale()), ) else: locale = locale or SYS_LOCALE if locale == "default": locale = SYS_LOCALE data, message = await current_loop.run_in_executor( None, partial(get_language_pack, locale) ) if data == {} and not message: if is_valid_locale(locale): message = f"Language pack '{locale}' not installed!" else: message = f"Language pack '{locale}' not valid!" elif is_valid_locale(locale): # only change locale if the language pack is installed and valid translator.set_locale(locale) except Exception: message = traceback.format_exc() self.set_status(200) self.finish(json.dumps({"data": data, "message": message})) jupyterlab_server-2.25.2/jupyterlab_server/workspaces_app.py000066400000000000000000000142711452610450300245200ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """A workspace management CLI""" from __future__ import annotations import json import sys import warnings from pathlib import Path from typing import Any from jupyter_core.application import JupyterApp from traitlets import Bool, Unicode from ._version import __version__ from .config import LabConfig from .workspaces_handler import WorkspacesManager # Default workspace ID # Needs to match PageConfig.defaultWorkspace define in packages/coreutils/src/pageconfig.ts DEFAULT_WORKSPACE = "default" class WorkspaceListApp(JupyterApp, LabConfig): """An app to list workspaces.""" version = __version__ description = """ Print all the workspaces available If '--json' flag is passed in, a single 'json' object is printed. If '--jsonlines' flag is passed in, 'json' object of each workspace separated by a new line is printed. If nothing is passed in, workspace ids list is printed. """ flags = dict( jsonlines=( {"WorkspaceListApp": {"jsonlines": True}}, ("Produce machine-readable JSON Lines output."), ), json=( {"WorkspaceListApp": {"json": True}}, ("Produce machine-readable JSON object output."), ), ) jsonlines = Bool( False, config=True, help=( "If True, the output will be a newline-delimited JSON (see https://jsonlines.org/) of objects, " "one per JupyterLab workspace, each with the details of the relevant workspace" ), ) json = Bool( False, config=True, help=( "If True, each line of output will be a JSON object with the " "details of the workspace." ), ) def initialize(self, *args: Any, **kwargs: Any) -> None: """Initialize the app.""" super().initialize(*args, **kwargs) self.manager = WorkspacesManager(self.workspaces_dir) def start(self) -> None: """Start the app.""" workspaces = self.manager.list_workspaces() if self.jsonlines: for workspace in workspaces: print(json.dumps(workspace)) elif self.json: print(json.dumps(workspaces)) else: for workspace in workspaces: print(workspace["metadata"]["id"]) class WorkspaceExportApp(JupyterApp, LabConfig): """A workspace export app.""" version = __version__ description = """ Export a JupyterLab workspace If no arguments are passed in, this command will export the default workspace. If a workspace name is passed in, this command will export that workspace. If no workspace is found, this command will export an empty workspace. """ def initialize(self, *args: Any, **kwargs: Any) -> None: """Initialize the app.""" super().initialize(*args, **kwargs) self.manager = WorkspacesManager(self.workspaces_dir) def start(self) -> None: """Start the app.""" if len(self.extra_args) > 1: # pragma: no cover warnings.warn("Too many arguments were provided for workspace export.") self.exit(1) raw = DEFAULT_WORKSPACE if not self.extra_args else self.extra_args[0] try: workspace = self.manager.load(raw) print(json.dumps(workspace)) except Exception: # pragma: no cover self.log.error(json.dumps(dict(data=dict(), metadata=dict(id=raw)))) class WorkspaceImportApp(JupyterApp, LabConfig): """A workspace import app.""" version = __version__ description = """ Import a JupyterLab workspace This command will import a workspace from a JSON file. The format of the file must be the same as what the export functionality emits. """ workspace_name = Unicode( None, config=True, allow_none=True, help=""" Workspace name. If given, the workspace ID in the imported file will be replaced with a new ID pointing to this workspace name. """, ) aliases = {"name": "WorkspaceImportApp.workspace_name"} def initialize(self, *args: Any, **kwargs: Any) -> None: """Initialize the app.""" super().initialize(*args, **kwargs) self.manager = WorkspacesManager(self.workspaces_dir) def start(self) -> None: """Start the app.""" if len(self.extra_args) != 1: # pragma: no cover self.log.info("One argument is required for workspace import.") self.exit(1) with self._smart_open() as fid: try: # to load, parse, and validate the workspace file. workspace = self._validate(fid) except Exception as e: # pragma: no cover self.log.info(f"{fid.name} is not a valid workspace:\n{e}") self.exit(1) try: workspace_path = self.manager.save(workspace["metadata"]["id"], json.dumps(workspace)) except Exception as e: # pragma: no cover self.log.info(f"Workspace could not be exported:\n{e!s}") self.exit(1) self.log.info(f"Saved workspace: {workspace_path!s}") def _smart_open(self) -> Any: file_name = self.extra_args[0] if file_name == "-": # pragma: no cover return sys.stdin else: file_path = Path(file_name).resolve() if not file_path.exists(): # pragma: no cover self.log.info(f"{file_name!s} does not exist.") self.exit(1) return file_path.open(encoding="utf-8") def _validate(self, data: Any) -> Any: workspace = json.load(data) if "data" not in workspace: msg = "The `data` field is missing." raise Exception(msg) # If workspace_name is set in config, inject the # name into the workspace metadata. if self.workspace_name is not None and self.workspace_name: workspace["metadata"] = {"id": self.workspace_name} elif "id" not in workspace["metadata"]: msg = "The `id` field is missing in `metadata`." raise Exception(msg) return workspace jupyterlab_server-2.25.2/jupyterlab_server/workspaces_handler.py000066400000000000000000000177351452610450300253650ustar00rootroot00000000000000"""Tornado handlers for frontend config storage.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations import hashlib import json import re import unicodedata import urllib from pathlib import Path from typing import Any from jupyter_server import _tz as tz from jupyter_server.base.handlers import APIHandler from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin from jupyter_server.utils import url_path_join as ujoin from tornado import web from traitlets.config import LoggingConfigurable # The JupyterLab workspace file extension. WORKSPACE_EXTENSION = ".jupyterlab-workspace" def _list_workspaces(directory: Path, prefix: str) -> list[dict[str, Any]]: """ Return the list of workspaces in a given directory beginning with the given prefix. """ workspaces: list = [] if not directory.exists(): return workspaces items = [ item for item in directory.iterdir() if item.name.startswith(prefix) and item.name.endswith(WORKSPACE_EXTENSION) ] items.sort() for slug in items: workspace_path: Path = directory / slug if workspace_path.exists(): workspace = _load_with_file_times(workspace_path) workspaces.append(workspace) return workspaces def _load_with_file_times(workspace_path: Path) -> dict: """ Load workspace JSON from disk, overwriting the `created` and `last_modified` metadata with current file stat information """ stat = workspace_path.stat() with workspace_path.open(encoding="utf-8") as fid: workspace = json.load(fid) workspace["metadata"].update( last_modified=tz.utcfromtimestamp(stat.st_mtime).isoformat(), created=tz.utcfromtimestamp(stat.st_ctime).isoformat(), ) return workspace def slugify( raw: str, base: str = "", sign: bool = True, max_length: int = 128 - len(WORKSPACE_EXTENSION) ) -> str: """ Use the common superset of raw and base values to build a slug shorter than max_length. By default, base value is an empty string. Convert spaces to hyphens. Remove characters that aren't alphanumerics underscores, or hyphens. Convert to lowercase. Strip leading and trailing whitespace. Add an optional short signature suffix to prevent collisions. Modified from Django utils: https://github.com/django/django/blob/master/django/utils/text.py """ raw = raw if raw.startswith("/") else "/" + raw signature = "" if sign: data = raw[1:] # Remove initial slash that always exists for digest. signature = "-" + hashlib.sha256(data.encode("utf-8")).hexdigest()[:4] base = (base if base.startswith("/") else "/" + base).lower() raw = raw.lower() common = 0 limit = min(len(base), len(raw)) while common < limit and base[common] == raw[common]: common += 1 value = ujoin(base[common:], raw) value = urllib.parse.unquote(value) value = unicodedata.normalize("NFKC", value).encode("ascii", "ignore").decode("ascii") value = re.sub(r"[^\w\s-]", "", value).strip() value = re.sub(r"[-\s]+", "-", value) return value[: max_length - len(signature)] + signature class WorkspacesManager(LoggingConfigurable): """A manager for workspaces.""" def __init__(self, path: str) -> None: """Initialize a workspaces manager with content in ``path``.""" super() if not path: msg = "Workspaces directory is not set" raise ValueError(msg) self.workspaces_dir = Path(path) def delete(self, space_name: str) -> None: """Remove a workspace ``space_name``.""" slug = slugify(space_name) workspace_path = self.workspaces_dir / (slug + WORKSPACE_EXTENSION) if not workspace_path.exists(): msg = f"Workspace {space_name!r} ({slug!r}) not found" raise FileNotFoundError(msg) # to delete the workspace file. workspace_path.unlink() def list_workspaces(self) -> list: """List all available workspaces.""" prefix = slugify("", sign=False) return _list_workspaces(self.workspaces_dir, prefix) def load(self, space_name: str) -> dict: """Load the workspace ``space_name``.""" slug = slugify(space_name) workspace_path = self.workspaces_dir / (slug + WORKSPACE_EXTENSION) if workspace_path.exists(): # to load and parse the workspace file. return _load_with_file_times(workspace_path) else: _id = space_name if space_name.startswith("/") else "/" + space_name return dict(data=dict(), metadata=dict(id=_id)) def save(self, space_name: str, raw: str) -> Path: """Save the ``raw`` data as workspace ``space_name``.""" if not self.workspaces_dir.exists(): self.workspaces_dir.mkdir(parents=True) workspace = {} # Make sure the data is valid JSON. try: decoder = json.JSONDecoder() workspace = decoder.decode(raw) except Exception as e: raise ValueError(str(e)) from e # Make sure metadata ID matches the workspace name. # Transparently support an optional initial root `/`. metadata_id = workspace["metadata"]["id"] metadata_id = metadata_id if metadata_id.startswith("/") else "/" + metadata_id metadata_id = urllib.parse.unquote(metadata_id) if metadata_id != "/" + space_name: message = f"Workspace metadata ID mismatch: expected {space_name!r} got {metadata_id!r}" raise ValueError(message) slug = slugify(space_name) workspace_path = self.workspaces_dir / (slug + WORKSPACE_EXTENSION) # Write the workspace data to a file. workspace_path.write_text(raw, encoding="utf-8") return workspace_path class WorkspacesHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, APIHandler): """A workspaces API handler.""" def initialize(self, name: str, manager: WorkspacesManager, **kwargs: Any) -> None: """Initialize the handler.""" super().initialize(name) self.manager = manager @web.authenticated def delete(self, space_name: str) -> None: """Remove a workspace""" if not space_name: raise web.HTTPError(400, "Workspace name is required for DELETE") try: self.manager.delete(space_name) return self.set_status(204) except FileNotFoundError as e: raise web.HTTPError(404, str(e)) from e except Exception as e: # pragma: no cover raise web.HTTPError(500, str(e)) from e @web.authenticated async def get(self, space_name: str = "") -> Any: """Get workspace(s) data""" try: if not space_name: workspaces = self.manager.list_workspaces() ids = [] values = [] for workspace in workspaces: ids.append(workspace["metadata"]["id"]) values.append(workspace) return self.finish(json.dumps({"workspaces": {"ids": ids, "values": values}})) workspace = self.manager.load(space_name) return self.finish(json.dumps(workspace)) except Exception as e: # pragma: no cover raise web.HTTPError(500, str(e)) from e @web.authenticated def put(self, space_name: str = "") -> None: """Update workspace data""" if not space_name: raise web.HTTPError(400, "Workspace name is required for PUT.") raw = self.request.body.strip().decode("utf-8") # Make sure the data is valid JSON. try: self.manager.save(space_name, raw) except ValueError as e: raise web.HTTPError(400, str(e)) from e except Exception as e: # pragma: no cover raise web.HTTPError(500, str(e)) from e self.set_status(204) jupyterlab_server-2.25.2/pyproject.toml000066400000000000000000000150351452610450300202710ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. [build-system] requires = ["hatchling>=1.7"] build-backend = "hatchling.build" [project] name = "jupyterlab_server" dynamic = ["version"] license = { file = "LICENSE" } description = "A set of server components for JupyterLab and JupyterLab like applications." keywords = ["jupyter", "jupyterlab"] classifiers = [ "Framework :: Jupyter", "Framework :: Jupyter :: JupyterLab", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Typing :: Typed"] requires-python = ">=3.8" dependencies = [ "babel>=2.10", "importlib_metadata>=4.8.3;python_version<\"3.10\"", "jinja2>=3.0.3", "json5>=0.9.0", "jsonschema>=4.18.0", "jupyter_server>=1.21,<3", "packaging>=21.3", "requests>=2.31", ] [[project.authors]] name = "Jupyter Development Team" email = "jupyter@googlegroups.com" [project.readme] file = "README.md" content-type = "text/markdown" [project.urls] Homepage = "https://jupyterlab-server.readthedocs.io" Documentation = "https://jupyterlab-server.readthedocs.io" Funding = "https://numfocus.org/donate-to-jupyter" Source = "https://github.com/jupyterlab/jupyterlab_server" Tracker = "https://github.com/jupyterlab/jupyterlab_server/issues" [project.optional-dependencies] docs = [ "autodoc-traits", "pydata_sphinx_theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi>0.8", "myst-parser", "mistune<4", "jinja2<3.2.0" ] openapi = [ "openapi_core~=0.18.0", "ruamel.yaml", ] test = [ "hatch", "ipykernel", "pytest-jupyter[server]>=0.6.2", "openapi_core~=0.18.0", "openapi-spec-validator>=0.6.0,<0.8.0", "sphinxcontrib_spelling", "requests_mock", "ruamel.yaml", "pytest>=7.0", "pytest-console-scripts", "pytest-cov", "pytest-timeout", "strict-rfc3339", "werkzeug", ] [tool.hatch.version] path = "jupyterlab_server/_version.py" validate-bump = false [tool.hatch.envs.docs] features = ["docs"] [tool.hatch.envs.docs.scripts] build = "make -C docs html SPHINXOPTS='-W'" [tool.hatch.envs.test] features = ["test"] [tool.hatch.envs.test.scripts] test = "python -m pytest -vv {args}" nowarn = "test -W default {args}" [tool.hatch.envs.cov] features = ["test"] dependencies = ["coverage", "pytest-cov"] [tool.hatch.envs.cov.scripts] test = "python -m pytest -vv --cov jupyterlab_server --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" [tool.hatch.envs.lint] detached = true dependencies = ["pre-commit"] [tool.hatch.envs.lint.scripts] build = [ "pre-commit run --all-files ruff", "pre-commit run --all-files ruff-format" ] [tool.hatch.envs.typing] dependencies = [ "pre-commit"] detached = true [tool.hatch.envs.typing.scripts] test = "pre-commit run --all-files --hook-stage manual mypy" [tool.pytest.ini_options] minversion = "6.0" xfail_strict = true log_cli_level = "info" addopts = [ "-ra", "--durations=10", "--color=yes", "--doctest-modules", "--showlocals", "--strict-markers", "--strict-config" ] testpaths = [ "tests/" ] timeout = 300 # Restore this setting to debug failures # timeout_method = "thread" filterwarnings = [ "error", "ignore:ServerApp.preferred_dir config is deprecated:FutureWarning", # From openapi_schema_validator "module:write property is deprecated:DeprecationWarning", "module:read property is deprecated:DeprecationWarning", # From tornado.netutil.bind_sockets "module:unclosed 100 characters) "E501", # SIM105 Use `contextlib.suppress(...)` "SIM105", # PLR0912 Too many branches "PLR0912", # PLR0913 Too many arguments to function call "PLR0913", # RUF012 Mutable class attributes should be annotated with `typing.ClassVar` "RUF012", # S101 Use of `assert` detected "S101" ] [tool.ruff.lint.per-file-ignores] # S101 Use of `assert` detected # A001 Variable `id` is shadowing a python builtin # PLR2004 Magic value used in comparison # F841 Local variable `list_data` is assigned to but never used # EM101 Exception must not use a string literal "tests/*" = ["S101", "A001", "F841", "EM101", "EM102", "EM103", "PLR2004"] # T201 `print` found "jupyterlab_server/licenses_app.py" = ["T201"] # T201 `print` found "jupyterlab_server/process.py" = ["T201"] # F401 `foo` imported but unused "jupyterlab_server/server.py" = ["F401"] # T201 `print` found "jupyterlab_server/workspaces_app.py" = ["B028", "T201"] # Invalid module name "tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/__init__.py" = ["N999"] [tool.interrogate] ignore-init-module=true ignore-private=true ignore-semiprivate=true ignore-property-decorators=true ignore-nested-functions=true ignore-nested-classes=true fail-under=100 exclude = ["tests", "docs"] [tool.coverage.run] relative_files = true source = ["jupyterlab_server"] [tool.repo-review] ignore = ["PY007", "GH102"] jupyterlab_server-2.25.2/tests/000077500000000000000000000000001452610450300165135ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/__init__.py000066400000000000000000000001451452610450300206240ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. jupyterlab_server-2.25.2/tests/conftest.py000066400000000000000000000003201452610450300207050ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os os.environ["JUPYTER_PLATFORM_DIRS"] = "1" pytest_plugins = ["jupyterlab_server.pytest_plugin"] jupyterlab_server-2.25.2/tests/test_config.py000066400000000000000000000022051452610450300213700ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json import os import json5 # type:ignore import pytest from jupyterlab_server.config import get_page_config @pytest.mark.parametrize( "lib,extension", ( (json, "json"), (json5, "json5"), ), ) def test_get_page_config(tmp_path, lib, extension): labext_path = [os.path.join(tmp_path, "ext")] settings_path = os.path.join(tmp_path, "settings") os.mkdir(settings_path) with open(os.path.join(settings_path, f"page_config.{extension}"), "w") as fid: data = dict(deferredExtensions=["foo"]) lib.dump(data, fid) static_dir = os.path.join(tmp_path, "static") os.mkdir(static_dir) with open(os.path.join(static_dir, "package.json"), "w") as fid: data2 = dict(jupyterlab=dict(extensionMetadata=dict(foo=dict(disabledExtensions=["bar"])))) json.dump(data2, fid) config = get_page_config(labext_path, settings_path) assert config == { "deferredExtensions": ["foo"], "federated_extensions": [], "disabledExtensions": ["bar"], } jupyterlab_server-2.25.2/tests/test_labapp.py000066400000000000000000000117611452610450300213710ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Basic tests for the lab handlers. """ import json import re from pathlib import Path import pytest import tornado.httpclient from jupyterlab_server.test_utils import expected_http_error @pytest.fixture def notebooks(jp_create_notebook, labserverapp): nbpaths = ( "notebook1.ipynb", "jlab_test_notebooks/notebook2.ipynb", "jlab_test_notebooks/level2/notebook3.ipynb", ) for nb in nbpaths: jp_create_notebook(nb) return nbpaths def extract_page_config(html): return json.loads( re.search( r'', html, ).group( # type: ignore "data" ) ) async def test_lab_handler(notebooks, jp_fetch): r = await jp_fetch("lab", "jlab_test_notebooks") assert r.code == 200 # Check that the lab template is loaded html = r.body.decode() assert "Files" in html assert "JupyterLab Server Application" in html async def test_page_config(labserverapp, jp_fetch): r = await jp_fetch("lab") assert r.code == 200 # Check that the lab template is loaded html = r.body.decode() page_config = extract_page_config(html) assert not page_config["treePath"] assert page_config["preferredPath"] == "/" def ispath(p): return p.endswith("Dir") or p.endswith("Path") or p == "serverRoot" nondirs = {k: v for k, v in page_config.items() if not ispath(k)} assert nondirs == { "appName": "JupyterLab Server Application", "appNamespace": "jupyterlab_server", "appUrl": "/lab", "appVersion": "", "baseUrl": "/a%40b/", "cacheFiles": True, "disabledExtensions": [], "federated_extensions": [], "fullAppUrl": "/a%40b/lab", "fullLabextensionsUrl": "/a%40b/lab/extensions", "fullLicensesUrl": "/a%40b/lab/api/licenses", "fullListingsUrl": "/a%40b/lab/api/listings", "fullMathjaxUrl": "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js", "fullSettingsUrl": "/a%40b/lab/api/settings", "fullStaticUrl": "/a%40b/static/jupyterlab_server", "fullThemesUrl": "/a%40b/lab/api/themes", "fullTranslationsApiUrl": "/a%40b/lab/api/translations", "fullTreeUrl": "/a%40b/lab/tree", "fullWorkspacesApiUrl": "/a%40b/lab/api/workspaces", "ignorePlugins": [], "labextensionsUrl": "/lab/extensions", "licensesUrl": "/lab/api/licenses", "listingsUrl": "/lab/api/listings", "mathjaxConfig": "TeX-AMS_HTML-full,Safe", "mode": "multiple-document", "notebookStartsKernel": True, "settingsUrl": "/lab/api/settings", "store_id": 0, "terminalsAvailable": True, "themesUrl": "/lab/api/themes", "translationsApiUrl": "/lab/api/translations", "treeUrl": "/lab/tree", "workspace": "default", "workspacesApiUrl": "/lab/api/workspaces", "wsUrl": "", } @pytest.fixture(scope="function") def serverapp_preferred_dir(jp_server_config, jp_root_dir): preferred_dir = Path(jp_root_dir, "my", "preferred_dir") preferred_dir.mkdir(parents=True, exist_ok=True) jp_server_config.ServerApp.preferred_dir = str(preferred_dir) return preferred_dir async def test_app_preferred_dir(serverapp_preferred_dir, labserverapp, jp_fetch): r = await jp_fetch("lab") assert r.code == 200 # Check that the lab template is loaded html = r.body.decode() page_config = extract_page_config(html) api_path = str(serverapp_preferred_dir.relative_to(labserverapp.serverapp.root_dir).as_posix()) assert page_config["preferredPath"] == api_path async def test_contents_manager_preferred_dir(jp_root_dir, labserverapp, jp_fetch): preferred_dir = Path(jp_root_dir, "my", "preferred_dir") preferred_dir.mkdir(parents=True, exist_ok=True) try: _ = labserverapp.serverapp.contents_manager.preferred_dir labserverapp.serverapp.contents_manager.preferred_dir = str(preferred_dir) except AttributeError: pytest.skip("Skipping contents manager test, trait not present") r = await jp_fetch("lab") assert r.code == 200 # Check that the lab template is loaded html = r.body.decode() page_config = extract_page_config(html) api_path = str(preferred_dir.relative_to(labserverapp.serverapp.root_dir).as_posix()) assert page_config["preferredPath"] == api_path async def test_notebook_handler(notebooks, jp_fetch): for nbpath in notebooks: r = await jp_fetch("lab", nbpath) assert r.code == 200 # Check that the lab template is loaded html = r.body.decode() assert "JupyterLab Server Application" in html async def test_404(notebooks, jp_fetch): with pytest.raises(tornado.httpclient.HTTPClientError) as e: await jp_fetch("foo") assert expected_http_error(e, 404) jupyterlab_server-2.25.2/tests/test_licenses_api.py000066400000000000000000000123611452610450300225650ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Test the Settings service API. """ import csv import io import json import mimetypes import mistune import pytest from jupyterlab_server import LicensesApp from jupyterlab_server.licenses_handler import DEFAULT_THIRD_PARTY_LICENSE_FILE, LicensesManager # utilities FULL_ENTRY = ( ("name", "@jupyterlab/foo"), ("versionInfo", "0.0.1"), ("licenseId", "BSD-3-Clause"), ("extractedText", "> license text goes here"), ) def _read_csv(csv_text): with io.StringIO() as csvfile: csvfile.write(csv_text) csvfile.seek(0) return [*csv.DictReader(csvfile)] def _make_static_dir(app, tmp_path, has_licenses=True, license_json=None, package_in_app=False): app_dir = tmp_path / "app" static_dir = app_dir / "static" static_dir.mkdir(parents=True) package_text = json.dumps({"name": "@jupyterlab/top", "version": "0.0.1"}) package_json = (app_dir if package_in_app else static_dir) / "package.json" package_json.write_text(package_text, encoding="utf-8") if has_licenses: (static_dir / DEFAULT_THIRD_PARTY_LICENSE_FILE).write_text( license_json or _good_license_json(), encoding="utf-8", ) app.static_dir = str(static_dir) def _good_license_json(): return json.dumps({"packages": [dict(FULL_ENTRY[:i]) for i in range(1 + len(FULL_ENTRY))]}) @pytest.fixture( params=[ ["application/json", "json", json.loads], ["text/csv", "csv", _read_csv], ["text/markdown", "markdown", mistune.markdown], ] ) def mime_format_parser(request): return request.param @pytest.fixture(params=[True, False]) def has_licenses(request): return request.param @pytest.fixture def licenses_app(tmp_path, has_licenses): app = LicensesApp() _make_static_dir(app, tmp_path, has_licenses) return app # the actual tests @pytest.mark.parametrize("has_static_dir", [True, False]) @pytest.mark.parametrize("full_text", ["true", "false"]) @pytest.mark.parametrize("bundles_pattern", ["", "@jupyterlab/.*", "nothing"]) async def test_get_license_report( mime_format_parser, has_static_dir, has_licenses, full_text, bundles_pattern, jp_fetch, labserverapp, tmp_path, ): if has_static_dir: _make_static_dir(labserverapp, tmp_path, has_licenses) mime, fmt, parse = mime_format_parser params = {"format": fmt, "full_text": full_text} if bundles_pattern: params["bundles"] = bundles_pattern r = await jp_fetch("lab", "api", "licenses/", params=params) assert r.code == 200 assert r.headers["Content-type"] == mime res = r.body.decode() assert parse(res) is not None async def test_download_license_report( jp_fetch, labserverapp, mime_format_parser, ): mime, fmt, parse = mime_format_parser params = {"format": fmt, "download": "1"} r = await jp_fetch("lab", "api", "licenses/", params=params) assert r.code == 200 assert r.headers["Content-type"] == mime extension = mimetypes.guess_extension(mime) assert extension, f"no extension guessed for {mime}" assert extension in r.headers["Content-Disposition"], f"{r.headers}" async def test_dev_mode_license_report( jp_fetch, labserverapp, tmp_path, ): _make_static_dir(labserverapp, tmp_path, package_in_app=True) r = await jp_fetch("lab", "api", "licenses/") assert r.code == 200 @pytest.mark.parametrize( "license_json", [ "// leading comment\n" + _good_license_json(), _good_license_json().replace("packages", "whatever"), ], ) async def test_malformed_license_report( license_json, jp_fetch, labserverapp, tmp_path, ): _make_static_dir(labserverapp, tmp_path, license_json=license_json) r = await jp_fetch("lab", "api", "licenses/") assert r.code == 200 async def test_licenses_cli(licenses_app, capsys, mime_format_parser): mime, fmt, parse = mime_format_parser args = [] if fmt != "markdown": args += [f"--{fmt}"] licenses_app.initialize(args) with pytest.raises(SystemExit) as exited: licenses_app.start() assert exited.type == SystemExit assert exited.value.code == 0 captured = capsys.readouterr() assert parse(captured.out) is not None @pytest.fixture def a_fake_labextension(tmp_path): """just enough of an extension to be parsed""" ext_name = "@an-org/an-extension" ext_path = tmp_path / ext_name package_data = {"name": ext_name} bundle_data = {"packages": [dict(FULL_ENTRY)]} package_json = ext_path / "package.json" third_party_licenses = ext_path / "static" / DEFAULT_THIRD_PARTY_LICENSE_FILE third_party_licenses.parent.mkdir(parents=True) package_json.write_text(json.dumps(package_data), encoding="utf-8") third_party_licenses.write_text(json.dumps(bundle_data), encoding="utf-8") yield ext_path, ext_name @pytest.fixture def a_licenses_manager(): yield LicensesManager() def test_labextension_bundle(a_fake_labextension, a_licenses_manager): ext_path, ext_name = a_fake_labextension bundle = a_licenses_manager.license_bundle(ext_path, ext_name) assert bundle["packages"][0]["name"] == dict(FULL_ENTRY)["name"] jupyterlab_server-2.25.2/tests/test_listings_api.py000066400000000000000000000020361452610450300226120ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json import requests_mock from jupyterlab_server.listings_handler import ListingsHandler, fetch_listings from jupyterlab_server.test_utils import validate_request async def test_get_listing(jp_fetch, labserverapp): url = r"lab/api/listings/@jupyterlab/extensionmanager-extension/listings.json" r = await jp_fetch(*url.split("/")) validate_request(r) def test_fetch_listings(): ListingsHandler.allowed_extensions_uris = ["http://foo"] # type:ignore ListingsHandler.blocked_extensions_uris = ["http://bar"] # type:ignore with requests_mock.Mocker() as m: data = dict(blocked_extensions=[]) # type:ignore m.get("http://bar", text=json.dumps(data)) data = dict(allowed_extensions=[]) m.get("http://foo", text=json.dumps(data)) fetch_listings(None) ListingsHandler.allowed_extensions_uris = [] # type:ignore ListingsHandler.blocked_extensions_uris = [] # type:ignore jupyterlab_server-2.25.2/tests/test_process.py000066400000000000000000000022741452610450300216070ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os import sys import warnings import pytest from jupyterlab_server.process import Process, WatchHelper, which from jupyterlab_server.process_app import ProcessApp def test_which(): assert which("jupyter") async def test_process(): p = Process([sys.executable, "--version"]) p.get_log().info("test") assert p.wait() == 0 p = Process([sys.executable, "--version"]) p.get_log().info("test") assert await p.wait_async() == 0 assert p.terminate() == 0 @pytest.mark.skipif(os.name == "nt", reason="Fails on Windows") async def test_watch_helper(): helper = WatchHelper([sys.executable, "-i"], ">>>") helper.terminate() helper.wait() def test_process_app(): class TestApp(ProcessApp): name = "tests" app = TestApp() app.initialize_server([]) try: app.initialize() with pytest.raises(SystemExit): app.start() # Kandle exception on older versions of server. except Exception as e: # Convert to warning so the test will pass on min version test. warnings.warn(str(e)) # noqa B028 jupyterlab_server-2.25.2/tests/test_settings_api.py000066400000000000000000000201141452610450300226130ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Test the Settings service API. """ import json from pathlib import Path import json5 # type:ignore import pytest from strict_rfc3339 import rfc3339_to_timestamp # type:ignore from tornado.httpclient import HTTPClientError from jupyterlab_server.test_utils import big_unicode_string, expected_http_error, validate_request async def test_get_settings_overrides_dicts(jp_fetch, labserverapp): # Check that values that are dictionaries in overrides.json are # merged with the schema. id = "@jupyterlab/apputils-extension:themes" r = await jp_fetch("lab", "api", "settings", id) validate_request(r) res = r.body.decode() data = json.loads(res) assert data["id"] == id schema = data["schema"] # Check that overrides.json file is respected. assert schema["properties"]["codeCellConfig"]["default"]["lineNumbers"] is True assert len(schema["properties"]["codeCellConfig"]["default"]) == 15 @pytest.mark.parametrize("ext", ["json", "json5"]) async def test_get_settings_overrides_d_dicts(jp_fetch, labserverapp, ext): # Check that values that are dictionaries in overrides.d/*.json are # merged with the schema. id = "@jupyterlab/apputils-extension:themes" overrides_d = Path(labserverapp.app_settings_dir) / "overrides.d" overrides_d.mkdir(exist_ok=True, parents=True) for i in range(10): text = json.dumps({id: {"codeCellConfig": {"cursorBlinkRate": 530 + i}}}) if ext == "json5": text += "\n// a comment" (overrides_d / f"foo-{i}.{ext}").write_text(text, encoding="utf-8") r = await jp_fetch("lab", "api", "settings", id) validate_request(r) res = r.body.decode() data = json.loads(res) assert data["id"] == id schema = data["schema"] # Check that the last overrides.d/*.json file is respected. assert schema["properties"]["codeCellConfig"]["default"]["cursorBlinkRate"] == 539 async def test_get_settings(jp_fetch, labserverapp): id = "@jupyterlab/apputils-extension:themes" r = await jp_fetch("lab", "api", "settings", id) validate_request(r) res = r.body.decode() data = json.loads(res) assert data["id"] == id schema = data["schema"] # Check that overrides.json file is respected. assert schema["properties"]["theme"]["default"] == "JupyterLab Dark" assert "raw" in res async def test_get_federated(jp_fetch, labserverapp): id = "@jupyterlab/apputils-extension-federated:themes" r = await jp_fetch("lab", "api", "settings", id) validate_request(r) res = r.body.decode() assert "raw" in res async def test_get_bad(jp_fetch, labserverapp): with pytest.raises(HTTPClientError) as e: await jp_fetch("foo") assert expected_http_error(e, 404) async def test_listing(jp_fetch, labserverapp): ids = [ "@jupyterlab/apputils-extension:themes", "@jupyterlab/apputils-extension-federated:themes", "@jupyterlab/codemirror-extension:commands", "@jupyterlab/codemirror-extension-federated:commands", "@jupyterlab/shortcuts-extension:plugin", "@jupyterlab/translation-extension:plugin", "@jupyterlab/unicode-extension:plugin", ] versions = ["N/A", "N/A", "test-version"] r = await jp_fetch("lab", "api", "settings/") validate_request(r) res = r.body.decode() response = json.loads(res) response_ids = [item["id"] for item in response["settings"]] response_schemas = [item["schema"] for item in response["settings"]] response_versions = [item["version"] for item in response["settings"]] assert set(response_ids) == set(ids) assert all(response_schemas) assert set(response_versions) == set(versions) last_modifieds = [item["last_modified"] for item in response["settings"]] createds = [item["created"] for item in response["settings"]] assert {None} == set(last_modifieds + createds) async def test_listing_ids(jp_fetch, labserverapp): ids = [ "@jupyterlab/apputils-extension:themes", "@jupyterlab/apputils-extension-federated:themes", "@jupyterlab/codemirror-extension:commands", "@jupyterlab/codemirror-extension-federated:commands", "@jupyterlab/shortcuts-extension:plugin", "@jupyterlab/translation-extension:plugin", "@jupyterlab/unicode-extension:plugin", ] r = await jp_fetch("lab", "api", "settings/", params={"ids_only": "true"}) validate_request(r) res = r.body.decode() response = json.loads(res) response_ids = [item["id"] for item in response["settings"]] # Checks the IDs list is correct assert set(response_ids) == set(ids) # Checks there is only the 'id' key in each item assert all( (len(item.keys()) == 1 and next(iter(item.keys())) == "id") for item in response["settings"] ) async def test_patch(jp_fetch, labserverapp): id = "@jupyterlab/shortcuts-extension:plugin" r = await jp_fetch( "lab", "api", "settings", id, method="PUT", body=json.dumps(dict(raw=json5.dumps(dict()))) ) validate_request(r) r = await jp_fetch( "lab", "api", "settings", id, method="GET", ) validate_request(r) data = json.loads(r.body.decode()) first_created = rfc3339_to_timestamp(data["created"]) first_modified = rfc3339_to_timestamp(data["last_modified"]) r = await jp_fetch( "lab", "api", "settings", id, method="PUT", body=json.dumps(dict(raw=json5.dumps(dict()))) ) validate_request(r) r = await jp_fetch( "lab", "api", "settings", id, method="GET", ) validate_request(r) data = json.loads(r.body.decode()) second_created = rfc3339_to_timestamp(data["created"]) second_modified = rfc3339_to_timestamp(data["last_modified"]) assert first_created <= second_created assert first_modified < second_modified r = await jp_fetch( "lab", "api", "settings/", method="GET", ) validate_request(r) data = json.loads(r.body.decode()) listing = data["settings"] list_data = next(item for item in listing if item["id"] == id) # TODO(@echarles) Check this... # assert list_data['created'] == data['created'] # assert list_data['last_modified'] == data['last_modified'] async def test_patch_unicode(jp_fetch, labserverapp): id = "@jupyterlab/unicode-extension:plugin" settings = dict(comment=big_unicode_string[::-1]) payload = dict(raw=json5.dumps(settings)) r = await jp_fetch("lab", "api", "settings", id, method="PUT", body=json.dumps(payload)) validate_request(r) r = await jp_fetch( "lab", "api", "settings", id, method="GET", ) validate_request(r) data = json.loads(r.body.decode()) assert data["settings"]["comment"] == big_unicode_string[::-1] async def test_patch_wrong_id(jp_fetch, labserverapp): with pytest.raises(HTTPClientError) as e: await jp_fetch("foo", method="PUT", body=json.dumps(dict(raw=json5.dumps(dict())))) assert expected_http_error(e, 404) async def test_patch_bad_data(jp_fetch, labserverapp): with pytest.raises(HTTPClientError) as e: settings = dict(keyMap=10) payload = dict(raw=json5.dumps(settings)) await jp_fetch("foo", method="PUT", body=json.dumps(payload)) assert expected_http_error(e, 404) async def test_patch_invalid_payload_format(jp_fetch, labserverapp): id = "@jupyterlab/apputils-extension:themes" with pytest.raises(HTTPClientError) as e: settings = dict(keyMap=10) payload = dict(foo=json5.dumps(settings)) await jp_fetch("lab", "api", "settings", id, method="PUT", body=json.dumps(payload)) assert expected_http_error(e, 400) async def test_patch_invalid_json(jp_fetch, labserverapp): id = "@jupyterlab/apputils-extension:themes" with pytest.raises(HTTPClientError) as e: payload_str = "eh" await jp_fetch("lab", "api", "settings", id, method="PUT", body=json.dumps(payload_str)) assert expected_http_error(e, 400) jupyterlab_server-2.25.2/tests/test_themes_api.py000066400000000000000000000022061452610450300222420ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from unittest.mock import Mock from tornado.httpserver import HTTPRequest from tornado.web import Application from jupyterlab_server.test_utils import validate_request from jupyterlab_server.themes_handler import ThemesHandler async def test_get_theme(jp_fetch, labserverapp): r = await jp_fetch("lab", "api", "themes", "@jupyterlab", "foo", "index.css") validate_request(r) def test_themes_handler(tmp_path): app = Application() request = HTTPRequest(connection=Mock()) data_path = f"{tmp_path}/test.txt" with open(data_path, "w") as fid: fid.write("hi") handler = ThemesHandler(app, request, path=str(tmp_path)) handler.absolute_path = data_path handler.get_content_size() handler.get_content("test.txt") css_path = f"{tmp_path}/test.css" with open(css_path, "w") as fid: fid.write("url('./foo.css')") handler.absolute_path = css_path handler.path = "/" handler.themes_url = "foo" content = handler.get_content(css_path) assert content == b"url('foo/./foo.css')" jupyterlab_server-2.25.2/tests/test_translation_api.py000066400000000000000000000163111452610450300233150ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Test the translations service API.""" import json import os import shutil import subprocess import sys import pytest from jupyterlab_server.test_utils import maybe_patch_ioloop, validate_request from jupyterlab_server.translation_utils import ( _get_installed_language_pack_locales, _get_installed_package_locales, get_display_name, get_installed_packages_locale, get_language_pack, get_language_packs, is_valid_locale, merge_locale_data, translator, ) maybe_patch_ioloop() # Constants HERE = os.path.abspath(os.path.dirname(__file__)) if not os.path.exists(os.path.join(HERE, "translations")): pytest.skip("skipping translation tests", allow_module_level=True) def setup_module(module): """setup any state specific to the execution of this module.""" for pkg in ["jupyterlab-some-package", "jupyterlab-language-pack-es_CO"]: src = os.path.join(HERE, "translations", pkg) subprocess.Popen([sys.executable, "-m", "pip", "install", src]).communicate() # noqa def teardown_module(module): """teardown any state that was previously setup.""" for pkg in ["jupyterlab-some-package", "jupyterlab-language-pack-es_CO"]: subprocess.Popen( [sys.executable, "-m", "pip", "uninstall", pkg, "-y"] # noqa ).communicate() @pytest.fixture(autouse=True) def before_after_test(schemas_dir, user_settings_dir, labserverapp): # Code that will run before any test. # Copy the schema files. test_data = os.path.join(HERE, "..", "jupyterlab_server", "test_data") test_data = os.path.abspath(test_data) src = os.path.join(test_data, "schemas", "@jupyterlab") dst = os.path.join(str(schemas_dir), "@jupyterlab") if os.path.exists(dst): shutil.rmtree(dst) shutil.copytree(src, dst) # Copy the overrides file. src = os.path.join(test_data, "app-settings", "overrides.json") dst = os.path.join(str(user_settings_dir), "overrides.json") if os.path.exists(dst): os.remove(dst) shutil.copyfile(src, dst) # A test function will be run at this point. yield # Code that will run after your test. # N/A async def test_get(jp_fetch): r = await jp_fetch("lab", "api", "translations/") validate_request(r) data = json.loads(r.body.decode())["data"] assert "en" in data async def test_get_locale(jp_fetch): locale = "es_CO" r = await jp_fetch("lab", "api", "translations", locale) validate_request(r) data = json.loads(r.body.decode())["data"] assert "jupyterlab" in data assert data["jupyterlab"][""]["language"] == locale assert "jupyterlab_some_package" in data assert data["jupyterlab_some_package"][""]["version"] == "0.1.0" assert data["jupyterlab_some_package"][""]["language"] == locale async def test_get_locale_bad(jp_fetch): r = await jp_fetch("lab", "api", "translations", "foo_BAR") validate_request(r) data = json.loads(r.body.decode())["data"] assert data == {} async def test_get_locale_not_installed(jp_fetch): r = await jp_fetch("lab", "api", "translations", "es_AR") validate_request(r) result = json.loads(r.body.decode()) assert "not installed" in result["message"] assert result["data"] == {} async def test_get_locale_not_valid(jp_fetch): r = await jp_fetch("lab", "api", "translations", "foo_BAR") validate_request(r) result = json.loads(r.body.decode()) assert "not valid" in result["message"] assert result["data"] == {} # --- Backend locale # ------------------------------------------------------------------------ async def test_backend_locale(jp_fetch): locale = "es_CO" await jp_fetch("lab", "api", "translations", locale) trans = translator.load("jupyterlab") result = trans.__("MORE ABOUT PROJECT JUPYTER") assert result == "Más sobre el proyecto jupyter" async def test_backend_locale_extension(jp_fetch): locale = "es_CO" await jp_fetch("lab", "api", "translations", locale) trans = translator.load("jupyterlab_some_package") result = trans.__("BOOM") assert result == "Foo bar 2" # --- Utils testing # ------------------------------------------------------------------------ def test_get_installed_language_pack_locales_passes(): data, message = _get_installed_language_pack_locales() assert "es_CO" in data assert not message def test_get_installed_package_locales(): data, message = _get_installed_package_locales() assert "jupyterlab_some_package" in data assert os.path.isdir(data["jupyterlab_some_package"]) assert not message def test_get_installed_packages_locale(): data, message = get_installed_packages_locale("es_CO") assert "jupyterlab_some_package" in data assert "" in data["jupyterlab_some_package"] assert not message def test_get_language_packs(): data, message = get_language_packs("en") assert "en" in data assert "es_CO" in data assert not message def test_get_language_pack(): data, message = get_language_pack("es_CO") assert "jupyterlab" in data assert "jupyterlab_some_package" in data assert "" in data["jupyterlab"] assert "" in data["jupyterlab_some_package"] assert not message # --- Utils # ------------------------------------------------------------------------ def test_merge_locale_data(): some_package_data_1 = { "": {"domain": "some_package", "version": "1.0.0"}, "FOO": ["BAR"], } some_package_data_2 = { "": {"domain": "some_package", "version": "1.1.0"}, "SPAM": ["BAR"], } some_package_data_3 = { "": {"domain": "some_different_package", "version": "1.4.0"}, "SPAM": ["BAR"], } # Package data 2 has a newer version so it should update the package data 1 result = merge_locale_data(some_package_data_1, some_package_data_2) assert "SPAM" in result assert "FOO" in result # Package data 2 has a older version so it should not update the package data 2 result = merge_locale_data(some_package_data_2, some_package_data_1) assert "SPAM" in result assert "FOO" not in result # Package data 3 is a different package (domain) so it should not update package data 2 result = merge_locale_data(some_package_data_2, some_package_data_3) assert result == some_package_data_2 def test_is_valid_locale_valid(): assert is_valid_locale("en") assert is_valid_locale("es") assert is_valid_locale("es_CO") def test_is_valid_locale_invalid(): assert not is_valid_locale("foo_SPAM") assert not is_valid_locale("bar") def test_get_display_name_valid(): assert get_display_name("en", "en") == "English" assert get_display_name("en", "es") == "Inglés" assert get_display_name("en", "es_CO") == "Inglés" assert get_display_name("en", "fr") == "Anglais" assert get_display_name("es", "en") == "Spanish" assert get_display_name("fr", "en") == "French" assert get_display_name("pl_pl", "en") == "Polish (Poland)" def test_get_display_name_invalid(): assert get_display_name("en", "foo") == "English" assert get_display_name("foo", "en") == "English" assert get_display_name("foo", "bar") == "English" jupyterlab_server-2.25.2/tests/test_translation_utils.py000066400000000000000000000014671452610450300237120ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from jupyterlab_server.translation_utils import ( TranslationBundle, get_installed_packages_locale, get_language_packs, translator, ) def test_get_installed_packages_locale(jp_environ): get_installed_packages_locale("es_co") def test_get_language_packs(jp_environ): get_language_packs("es_co") def test_translation_bundle(): bundle = TranslationBundle("foo", "bar") bundle.update_locale("fizz") bundle.gettext("hi") bundle.ngettext("hi", "his", 1) bundle.npgettext("foo", "bar", "bars", 2) bundle.pgettext("foo", "bar") def test_translator(): t = translator() t.load("foo") t.normalize_domain("bar") t.set_locale("fizz") t.translate_schema({}) jupyterlab_server-2.25.2/tests/test_workspaces_api.py000066400000000000000000000117751452610450300231510ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """Test the kernels service API.""" import json import os import pytest import tornado.httpclient from strict_rfc3339 import rfc3339_to_timestamp # type:ignore from jupyterlab_server.test_utils import ( big_unicode_string, expected_http_error, maybe_patch_ioloop, validate_request, ) maybe_patch_ioloop() async def test_delete(jp_fetch, labserverapp): orig = "f/o/o" copy = "baz" r = await jp_fetch("lab", "api", "workspaces", orig) validate_request(r) res = r.body.decode() data = json.loads(res) data["metadata"]["id"] = copy r2 = await jp_fetch("lab", "api", "workspaces", copy, method="PUT", body=json.dumps(data)) assert r2.code == 204 r3 = await jp_fetch( "lab", "api", "workspaces", copy, method="DELETE", ) assert r3.code == 204 with pytest.raises(tornado.httpclient.HTTPClientError) as e: await jp_fetch( "lab", "api", "workspaces", "does_not_exist", method="DELETE", ) assert expected_http_error(e, 404) with pytest.raises(tornado.httpclient.HTTPClientError) as e: await jp_fetch( "lab", "api", "workspaces", "", method="DELETE", ) assert expected_http_error(e, 400) async def test_get_non_existant(jp_fetch, labserverapp): id = "foo" r = await jp_fetch("lab", "api", "workspaces", id) validate_request(r) data = json.loads(r.body.decode()) r2 = await jp_fetch("lab", "api", "workspaces", id, method="PUT", body=json.dumps(data)) validate_request(r2) r3 = await jp_fetch("lab", "api", "workspaces", id) validate_request(r3) data = json.loads(r3.body.decode()) first_metadata = data["metadata"] first_created = rfc3339_to_timestamp(first_metadata["created"]) first_modified = rfc3339_to_timestamp(first_metadata["last_modified"]) r4 = await jp_fetch("lab", "api", "workspaces", id, method="PUT", body=json.dumps(data)) validate_request(r4) r5 = await jp_fetch("lab", "api", "workspaces", id) validate_request(r5) data = json.loads(r5.body.decode()) second_metadata = data["metadata"] second_created = rfc3339_to_timestamp(second_metadata["created"]) second_modified = rfc3339_to_timestamp(second_metadata["last_modified"]) assert first_created <= second_created assert first_modified < second_modified @pytest.mark.skipif(os.name == "nt", reason="Temporal failure on windows") async def test_get(jp_fetch, labserverapp): id = "foo" r = await jp_fetch("lab", "api", "workspaces", id) validate_request(r) data = json.loads(r.body.decode()) metadata = data["metadata"] assert metadata["id"] == id assert rfc3339_to_timestamp(metadata["created"]) assert rfc3339_to_timestamp(metadata["last_modified"]) r2 = await jp_fetch("lab", "api", "workspaces", id) validate_request(r2) data = json.loads(r.body.decode()) assert data["metadata"]["id"] == id async def test_listing(jp_fetch, labserverapp): # ID fields are from workspaces/*.jupyterlab-workspace listing = {"foo", "f/o/o/"} r = await jp_fetch("lab", "api", "workspaces/") validate_request(r) res = r.body.decode() data = json.loads(res) output = set(data["workspaces"]["ids"]) assert output == listing async def test_listing_dates(jp_fetch, labserverapp): r = await jp_fetch("lab", "api", "workspaces") data = json.loads(r.body.decode()) values = data["workspaces"]["values"] times: list = sum( ([ws["metadata"].get("last_modified"), ws["metadata"].get("created")] for ws in values), [] ) assert None not in times [rfc3339_to_timestamp(t) for t in times] async def test_put(jp_fetch, labserverapp): id = "foo" r = await jp_fetch("lab", "api", "workspaces", id) assert r.code == 200 res = r.body.decode() data = json.loads(res) data["metadata"]["big-unicode-string"] = big_unicode_string[::-1] r2 = await jp_fetch("lab", "api", "workspaces", id, method="PUT", body=json.dumps(data)) assert r2.code == 204 async def test_bad_put(jp_fetch, labserverapp): orig = "foo" copy = "bar" r = await jp_fetch("lab", "api", "workspaces", orig) assert r.code == 200 res = r.body.decode() data = json.loads(res) with pytest.raises(tornado.httpclient.HTTPClientError) as e: await jp_fetch("lab", "api", "workspaces", copy, method="PUT", body=json.dumps(data)) assert expected_http_error(e, 400) async def test_blank_put(jp_fetch, labserverapp): orig = "foo" r = await jp_fetch("lab", "api", "workspaces", orig) assert r.code == 200 res = r.body.decode() data = json.loads(res) with pytest.raises(tornado.httpclient.HTTPClientError) as e: await jp_fetch("lab", "api", "workspaces", method="PUT", body=json.dumps(data)) assert expected_http_error(e, 400) jupyterlab_server-2.25.2/tests/test_workspaces_app.py000066400000000000000000000040331452610450300231450ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json import os import sys from jupyterlab_server.workspaces_app import ( WorkspaceExportApp, WorkspaceImportApp, WorkspaceListApp, ) def test_workspace_apps(jp_environ, tmp_path): sys.argv = [sys.argv[0]] data = { "data": { "layout-restorer:data": { "main": { "dock": { "type": "tab-area", "currentIndex": 1, "widgets": ["notebook:Untitled1.ipynb"], }, "current": "notebook:Untitled1.ipynb", }, "down": {"size": 0, "widgets": []}, "left": { "collapsed": False, "current": "filebrowser", "widgets": [ "filebrowser", "running-sessions", "@jupyterlab/toc:plugin", "extensionmanager.main-view", ], }, "right": { "collapsed": True, "widgets": ["jp-property-inspector", "debugger-sidebar"], }, "relativeSizes": [0.17370242214532872, 0.8262975778546713, 0], }, "notebook:Untitled1.ipynb": { "data": {"path": "Untitled1.ipynb", "factory": "Notebook"} }, }, "metadata": {"id": "default"}, } data_file = os.path.join(tmp_path, "test.json") with open(data_file, "w") as fid: json.dump(data, fid) app = WorkspaceImportApp(workspaces_dir=str(tmp_path)) app.initialize() app.extra_args = [data_file] app.start() app1 = WorkspaceExportApp(workspaces_dir=str(tmp_path)) app1.initialize() app1.start() app2 = WorkspaceListApp(workspaces_dir=str(tmp_path)) app2.initialize() app2.start() app2.jsonlines = True app2.start() jupyterlab_server-2.25.2/tests/translations/000077500000000000000000000000001452610450300212345ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/000077500000000000000000000000001452610450300271005ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/MANIFEST.in000066400000000000000000000002351452610450300306360ustar00rootroot00000000000000recursive-include *.md recursive-include *.txt recursive-include jupyterlab_language_pack_es_CO *.json recursive-include jupyterlab_language_pack_es_CO *.mo jupyterlab_language_pack_es_CO/000077500000000000000000000000001452610450300351135ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO__init__.py000066400000000000000000000001451452610450300372240ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. locale/000077500000000000000000000000001452610450300363525ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_COes_CO/000077500000000000000000000000001452610450300373425ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/localeLC_MESSAGES/000077500000000000000000000000001452610450300411275ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_COjupyterlab.json000066400000000000000000000003121452610450300441770ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_CO/LC_MESSAGES{ "": { "domain": "jupyterlab", "language": "es_CO", "plural_forms": "nplurals=2; plural=(n != 1);", "version": "2.2.0" }, "ABOUT PROJECT JUPYTER": ["SOBRE EL PROYECTO JUPYTER"] } jupyterlab.mo000066400000000000000000000006261452610450300436510ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_CO/LC_MESSAGES,<PQ lwMORE ABOUT PROJECT JUPYTERProject-Id-Version: jupyterlab Content-Type: text/plain; charset=UTF-8 Plural-Forms: nplurals=2; plural=(n != 1); Language-Team: Spanish Language: es_CO PO-Revision-Date: Last-Translator: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Generator: Poedit 2.4.1 Más sobre el proyecto jupyterjupyterlab.po000066400000000000000000000006651452610450300436570ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_CO/LC_MESSAGESmsgid "" msgstr "" "Project-Id-Version: jupyterlab\n" "Content-Type: text/plain; charset=UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Language-Team: Spanish\n" "Language: es_CO\n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" "Last-Translator: \n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.1\n" #: /example msgid "MORE ABOUT PROJECT JUPYTER" msgstr "Más sobre el proyecto jupyter" jupyterlab_some_package.json000066400000000000000000000002641452610450300467030ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_CO/LC_MESSAGES{ "": { "domain": "jupyterlab_some_package", "language": "es_CO", "plural_forms": "nplurals=2; plural=(n != 1);", "version": "0.0.1" }, "BOOM": ["FOO BAR"] } jupyterlab_some_package.mo000066400000000000000000000005701452610450300463450ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_CO/LC_MESSAGES,<PQV nBOOMProject-Id-Version: jupyterlab_some_package Content-Type: text/plain; charset=UTF-8 Plural-Forms: nplurals=2; plural=(n != 1); Language-Team: Spanish Language: es_CO PO-Revision-Date: Last-Translator: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Generator: Poedit 2.4.1 Foo bar 2jupyterlab_some_package.po000066400000000000000000000006271452610450300463530ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_CO/LC_MESSAGESmsgid "" msgstr "" "Project-Id-Version: jupyterlab_some_package\n" "Content-Type: text/plain; charset=UTF-8\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Language-Team: Spanish\n" "Language: es_CO\n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" "Last-Translator: \n" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.1\n" #: /example msgid "BOOM" msgstr "Foo bar 2" jupyterlab_server-2.25.2/tests/translations/jupyterlab-language-pack-es_CO/setup.py000066400000000000000000000006341452610450300306150ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from setuptools import setup setup( name="jupyterlab_language_pack_es_CO", version="0.1.0", packages=["jupyterlab_language_pack_es_CO"], include_package_data=True, entry_points={ "jupyterlab.languagepack": [ "es_CO = jupyterlab_language_pack_es_CO", ] }, ) jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/000077500000000000000000000000001452610450300257475ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/MANIFEST.in000066400000000000000000000002171452610450300275050ustar00rootroot00000000000000recursive-include *.md recursive-include *.txt recursive-include jupyterlab_some_package *.json recursive-include jupyterlab_some_package *.mo jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/jupyterlab_some_package/000077500000000000000000000000001452610450300326265ustar00rootroot00000000000000__init__.py000066400000000000000000000001451452610450300346600ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/jupyterlab_some_package# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/jupyterlab_some_package/locale/000077500000000000000000000000001452610450300340655ustar00rootroot00000000000000es_CO/000077500000000000000000000000001452610450300347765ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/jupyterlab_some_package/localeLC_MESSAGES/000077500000000000000000000000001452610450300365635ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/jupyterlab_some_package/locale/es_COjupyterlab_some_package.json000066400000000000000000000002671452610450300443420ustar00rootroot00000000000000jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/jupyterlab_some_package/locale/es_CO/LC_MESSAGES{ "": { "domain": "jupyterlab_some_package", "language": "es_CO", "plural_forms": "nplurals=2; plural=(n != 1);", "version": "0.1.0" }, "BOOM": ["SHAKA LAKA"] } jupyterlab_server-2.25.2/tests/translations/jupyterlab-some-package/setup.py000066400000000000000000000005561452610450300274670ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from setuptools import setup setup( name="jupyterlab-some-package", version="0.1.0", packages=["jupyterlab_some_package"], include_package_data=True, entry_points={"jupyterlab.locale": ["jupyterlab_some_package = jupyterlab_some_package"]}, )