pax_global_header00006660000000000000000000000064141236421440014513gustar00rootroot0000000000000052 comment=113d89d901eb1a7128a384587e5ac22c6de09ff6 jupyterlab_server-2.8.2/000077500000000000000000000000001412364214400152735ustar00rootroot00000000000000jupyterlab_server-2.8.2/.github/000077500000000000000000000000001412364214400166335ustar00rootroot00000000000000jupyterlab_server-2.8.2/.github/workflows/000077500000000000000000000000001412364214400206705ustar00rootroot00000000000000jupyterlab_server-2.8.2/.github/workflows/tests.yml000066400000000000000000000074211412364214400225610ustar00rootroot00000000000000name: Tests on: push: branches: 'master' pull_request: branches: '*' defaults: run: shell: bash -l {0} jobs: test: name: ${{ matrix.PLATFORM }} py${{ matrix.PYTHON_VERSION }} runs-on: ${{ matrix.PLATFORM }}-latest env: CI: True PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} CODECOV_TOKEN: e025254a-fe54-4914-8890-0c26e7aa0d07 strategy: fail-fast: false matrix: PYTHON_VERSION: ['3.6', '3.9', 'pypy3'] PLATFORM: ['ubuntu', 'macos', 'windows'] exclude: - PLATFORM: windows PYTHON_VERSION: pypy3 steps: - name: Checkout uses: actions/checkout@v2 - name: Cache pip uses: actions/cache@v2 with: path: ~/.cache/pip key: ${{ runner.os }}-py-${{ matrix.PYTHON_VERSION }}-pip-${{ hashFiles('**/setup.py') }} - name: Cache conda uses: actions/cache@v2 with: path: ~/conda_pkgs_dir key: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles('etc/example-environment.yml') }} - name: Setup conda ${{ matrix.PYTHON_VERSION }} uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true python-version: ${{ matrix.python-version }} - name: Install python dependencies run: | conda install setuptools pip wheel - name: Install pywin32 on Windows if: ${{ runner.os == 'Windows' }} run: conda install pywin32 - name: Install project dependencies run: | pip install -v -e ".[test]" --cache-dir ~/.cache/pip - run: conda info - run: conda list - run: conda config --show - run: pip check - name: Run python tests # See `setup.cfg` for full test options run: | pytest --pyargs jupyterlab_server - name: Upload coverage run: | codecov - name: Build docs run: | set -eux pushd docs conda env create -f environment.yml conda activate jupyterlab_server_documentation pip install .. make html conda deactivate popd check_release: runs-on: ubuntu-latest strategy: matrix: group: [check_release, link_check] steps: - name: Checkout uses: actions/checkout@v2 - name: Install Python uses: actions/setup-python@v2 with: python-version: 3.9 architecture: "x64" - name: Get pip cache dir id: pip-cache run: | echo "::set-output name=dir::$(pip cache dir)" - name: Cache pip uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}-pip- - name: Cache checked links if: ${{ matrix.group == 'link_check' }} uses: actions/cache@v2 with: path: ~/.cache/pytest-link-check key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/*.md', '**/*.rst') }}-md-links restore-keys: | ${{ runner.os }}-linkcheck- - name: Upgrade packaging dependencies run: | pip install --upgrade pip setuptools wheel --user - name: Install Dependencies run: | pip install -e . - name: Check Release if: ${{ matrix.group == 'check_release' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Run Link Check if: ${{ matrix.group == 'link_check' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1 jupyterlab_server-2.8.2/.gitignore000066400000000000000000000005361412364214400172670ustar00rootroot00000000000000MANIFEST 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.8.2/CHANGELOG.md000066400000000000000000000736151412364214400171200ustar00rootroot00000000000000--- github_url: 'https://github.com/jupyterlab/jupyterlab_server/blob/master/CHANGELOG.md' --- # Change Log ## 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.8.2/CONTRIBUTING.md000066400000000000000000000012251412364214400175240ustar00rootroot00000000000000# 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 pytest --pyargs jupyterlab_server ``` jupyterlab_server-2.8.2/LICENSE000066400000000000000000000027571412364214400163130ustar00rootroot00000000000000Copyright (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.8.2/MANIFEST.in000066400000000000000000000010211412364214400170230ustar00rootroot00000000000000include *.md include LICENSE include jupyterlab_server/rest-api.yml include jupyterlab_server/templates/*.html recursive-include jupyterlab_server/tests *.json *.json.orig *.jupyterlab-workspace recursive-include docs *.* include docs/Makefile prune docs/build # prune translation test data to avoid long path limits on Windows prune jupyterlab_server/tests/translations # Patterns to exclude from any directory global-exclude *~ global-exclude *.pyc global-exclude *.pyo global-exclude .git global-exclude .ipynb_checkpoints jupyterlab_server-2.8.2/README.md000066400000000000000000000016211412364214400165520ustar00rootroot00000000000000# jupyterlab server [![Coverage](https://codecov.io/gh/jupyterlab/jupyterlab_server/branch/master/graph/badge.svg)](https://codecov.io/gh/jupyterlab/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/) ## Install `pip install jupyterlab_server` ## Usage The application author creates a JupyterLab build on their machine using the core JupyterLab application. They can then serve their files by subclassing the `LabServerApp` with the appropriate configuration and creating a Python entry point that launches the app. ## Contribution Please see `CONTRIBUTING.md` for details. jupyterlab_server-2.8.2/RELEASE.md000066400000000000000000000011361412364214400166760ustar00rootroot00000000000000# Making a JupyterLab Server Release ## Using `jupyter_releaser` The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). ## Manual Release ### Set up ``` pip install tbump twine build git pull origin $(git branch --show-current) git clean -dffx ``` ### Update the version and apply the tag ``` echo "Enter new version" read script_version tbump ${script_version} ``` ### Build the artifacts ``` rm -rf dist python -m build . ``` ### Publish the artifacts to pypi ``` twine check dist/* twine upload dist/* ``` jupyterlab_server-2.8.2/docs/000077500000000000000000000000001412364214400162235ustar00rootroot00000000000000jupyterlab_server-2.8.2/docs/Makefile000066400000000000000000000011761412364214400176700ustar00rootroot00000000000000# 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.8.2/docs/autogen_config.py000066400000000000000000000023131412364214400215630ustar00rootroot00000000000000#!/usr/bin/env python import os from jupyterlab_server import LabServerApp 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 """ # Handle local and RTD locations cwd = os.getcwd() if os.path.basename(cwd) == 'source': destination = os.path.join(cwd, 'api/app-config.rst') else: destination = os.path.join(cwd, 'source/api/app-config.rst') with open(destination, 'w') as f: f.write(header) f.write(LabServerApp().document_config_options()) jupyterlab_server-2.8.2/docs/environment.yml000066400000000000000000000003521412364214400213120ustar00rootroot00000000000000 name: jupyterlab_server_documentation channels: - conda-forge dependencies: - python=3.8 - sphinx<4.0 - sphinx-copybutton - pip - myst-parser - pip: - autodoc-traits - pydata_sphinx_theme - sphinxcontrib-openapi - numpydoc jupyterlab_server-2.8.2/docs/make.bat000066400000000000000000000013741412364214400176350ustar00rootroot00000000000000@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.8.2/docs/source/000077500000000000000000000000001412364214400175235ustar00rootroot00000000000000jupyterlab_server-2.8.2/docs/source/api/000077500000000000000000000000001412364214400202745ustar00rootroot00000000000000jupyterlab_server-2.8.2/docs/source/api/app.rst000066400000000000000000000004211412364214400216030ustar00rootroot00000000000000=========== Application =========== Module: :mod:`jupyterlab_server.app` ==================================== .. automodule:: jupyterlab_server.app .. currentmodule:: jupyterlab_server.app :class:`LabServerApp` --------------------- .. autoconfigurable:: LabServerApp jupyterlab_server-2.8.2/docs/source/api/config.rst000066400000000000000000000007111412364214400222720ustar00rootroot00000000000000====== 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.8.2/docs/source/api/handlers.rst000066400000000000000000000033041412364214400226260ustar00rootroot00000000000000======== 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: .. autofunction:: get_current_locale Module: :mod:`jupyterlab_server.workspaces_handler` ===================================================== .. automodule:: jupyterlab_server.workspaces_handler .. currentmodule:: jupyterlab_server.workspaces_handler .. autoclass:: WorkspacesHandler :members: .. autofunction:: slugify jupyterlab_server-2.8.2/docs/source/api/index.rst000066400000000000000000000003401412364214400221320ustar00rootroot00000000000000--------------------- JupyterLab Server API --------------------- JupyterLab Server API Reference: .. toctree:: :maxdepth: 1 :caption: Contents: app-config app config handlers process rest jupyterlab_server-2.8.2/docs/source/api/process.rst000066400000000000000000000010651412364214400225060ustar00rootroot00000000000000======= 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.8.2/docs/source/api/rest.rst000066400000000000000000000010041412364214400217760ustar00rootroot00000000000000-------- 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.8.2/docs/source/conf.py000066400000000000000000000057661412364214400210400ustar00rootroot00000000000000# 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 HERE = osp.abspath(osp.dirname(__file__)) # -- Project information ----------------------------------------------------- project = 'JupyterLab Server' copyright = '2021, Project Jupyter' author = 'Project Jupyter' # The full version, including alpha/beta/rc tags _version_py = osp.join(HERE, '..', '..', 'jupyterlab_server', '_version.py') version_ns = {} with open(_version_py, mode='r') as version_file: exec(version_file.read(), version_ns) # The short X.Y version. version = '%i.%i' % version_ns['version_info'][:2] # The full version, including alpha/beta/rc tags. release = version_ns['__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', 'numpydoc', 'autodoc_traits', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinxcontrib.openapi', 'sphinx.ext.napoleon', 'sphinx.ext.mathjax', 'sphinx_copybutton' ] 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, } # 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 def setup(app): dest = osp.join(HERE, 'changelog.md') shutil.copy(osp.join(HERE, '..', '..', 'CHANGELOG.md'), dest) with open(osp.join(HERE, '../autogen_config.py')) as f: exec(compile(f.read(), '../autogen_config.py', 'exec'), {}) jupyterlab_server-2.8.2/docs/source/index.rst000066400000000000000000000007551412364214400213730ustar00rootroot00000000000000.. 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.8.2/jupyterlab_server/000077500000000000000000000000001412364214400210425ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/__init__.py000066400000000000000000000012161412364214400231530ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from .app import LabServerApp from .licenses_app import LicensesApp from .handlers import add_handlers, LabHandler, LabConfig from .workspaces_handler import slugify, WORKSPACE_EXTENSION from ._version import __version__ __all__ = [ '__version__', 'add_handlers', 'LabConfig', 'LabHandler', 'LabServerApp', 'slugify', 'SETTINGS_EXTENSION', 'WORKSPACE_EXTENSION' ] def _jupyter_server_extension_points(): return [ { 'module': 'jupyterlab_server', 'app': LabServerApp } ] jupyterlab_server-2.8.2/jupyterlab_server/__main__.py000066400000000000000000000001051412364214400231300ustar00rootroot00000000000000 from jupyterlab_server.app import main import sys sys.exit(main()) jupyterlab_server-2.8.2/jupyterlab_server/_version.py000066400000000000000000000006171412364214400232440ustar00rootroot00000000000000""" store the current version info of the server. """ import re __version__ = '2.8.2' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' match = re.match(pattern, __version__) parts = [int(match[part]) for part in ['major', 'minor', 'patch']] if match['rest']: parts.append(match['rest']) version_info = tuple(parts) jupyterlab_server-2.8.2/jupyterlab_server/app.py000066400000000000000000000072571412364214400222070ustar00rootroot00000000000000# coding: utf-8 """JupyterLab Server Application""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from jupyter_server.extension.application import ExtensionApp, ExtensionAppJinjaMixin 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" file_url_prefix = "/lab/tree" @property def app_namespace(self): 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.', 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): """observer for deprecated traits""" old_attr = change.name new_attr, version = self._deprecated_aliases.get(old_attr) 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_templates(self): self.static_paths = [self.static_dir] self.template_paths = [self.templates_dir] def initialize_settings(self): settings = self.serverapp.web_app.settings # By default, make terminals available. settings.setdefault('terminals_available', True) def initialize_handlers(self): add_handlers(self.handlers, self) main = launch_new_instance = LabServerApp.launch_instance jupyterlab_server-2.8.2/jupyterlab_server/config.py000066400000000000000000000277671412364214400227040ustar00rootroot00000000000000# coding: utf-8 """JupyterLab Server config""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json import os.path as osp from glob import iglob from itertools import chain from os.path import join as pjoin from jupyter_core.paths import SYSTEM_CONFIG_PATH, jupyter_config_dir, jupyter_path from jupyter_server.services.config.manager import ConfigManager, recursive_update from traitlets import Bool, HasTraits, List, Unicode, default from .server import url_path_join as ujoin # ----------------------------------------------------------------------------- # Module globals # ----------------------------------------------------------------------------- DEFAULT_TEMPLATE_PATH = osp.join(osp.dirname(__file__), 'templates') def get_package_url(data): """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): """Get the metadata about federated extensions """ federated_extensions = dict() 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()) ) 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=None, logger=None, level='all'): """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') def get_page_config(labextensions_path, app_settings_dir=None, logger=None): """Get the page config for the application handler""" # Build up the full page config page_config = {} disabled_key = "disabledExtensions" # Start with the app_settings_dir as lowest priority if app_settings_dir: app_page_config = pjoin(app_settings_dir, 'page_config.json') if osp.exists(app_page_config): with open(app_page_config, encoding='utf-8') as fid: data = json.load(fid) # Convert lists to dicts for key in [disabled_key, "deferredExtensions"]: if key in data: data[key] = dict((key, True) for key in data[key]) recursive_update(page_config, data) # 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 = dict() 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, ext_data) in federated_exts.items(): if not '_build' in ext_data['jupyterlab']: logger.warn('%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 = dict() 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) == 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, level='all'): """Write page config to disk""" cm = _get_config_manager(level) cm.set('page_config', page_config) 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) @default('template_dir') def _default_template_dir(self): return DEFAULT_TEMPLATE_PATH @default('labextensions_url') def _default_labextensions_url(self): return ujoin(self.app_url, "extensions/") @default('labextensions_path') def _default_labextensions_path(self): return jupyter_path('labextensions') @default('workspaces_url') def _default_workspaces_url(self): return ujoin(self.app_url, 'workspaces/') @default('workspaces_api_url') def _default_workspaces_api_url(self): return ujoin(self.app_url, 'api', 'workspaces/') @default('settings_url') def _default_settings_url(self): return ujoin(self.app_url, 'api', 'settings/') @default('listings_url') def _default_listings_url(self): return ujoin(self.app_url, 'api', 'listings/') @default('themes_url') def _default_themes_url(self): return ujoin(self.app_url, 'api', 'themes/') @default('licenses_url') def _default_licenses_url(self): return ujoin(self.app_url, 'api', 'licenses/') @default('tree_url') def _default_tree_url(self): return ujoin(self.app_url, 'tree/') @default('translations_api_url') def _default_translations_api_url(self): return ujoin(self.app_url, 'api', 'translations/') @default('tree_url') def _default_tree_url(self): return ujoin(self.app_url, 'tree/') def _get_config_manager(level): """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: raise ValueError(f'Page config level must be one of: {allowed}') 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.8.2/jupyterlab_server/handlers.py000077500000000000000000000267341412364214400232330ustar00rootroot00000000000000# coding: utf-8 """JupyterLab Server handlers""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os from urllib.parse import urlparse from tornado import template, web from jupyter_server.extension.handler import ExtensionHandlerMixin, ExtensionHandlerJinjaMixin from .config import LabConfig, get_page_config, recursive_update from .listings_handler import ListingsHandler, fetch_listings from .server import FileFindHandler, JupyterHandler from .server import url_path_join as ujoin from .settings_handler import SettingsHandler from .themes_handler import ThemesHandler from .translations_handler import TranslationsHandler from .workspaces_handler import WorkspacesHandler from .licenses_handler import LicensesHandler, LicensesManager # ----------------------------------------------------------------------------- # Module globals # ----------------------------------------------------------------------------- MASTER_URL_PATTERN = '/(?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): """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.""" @web.authenticated @web.removeslash def get(self, mode = None, workspace = None, tree = 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/','') self.application.store_id = getattr(self.application, 'store_id', 0) config = LabConfig() app = self.extensionapp 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 compatibiity 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 server_root = os.path.normpath(os.path.expanduser(server_root)) try: page_config['preferredDir'] = self.serverapp.preferred_dir page_config['preferredPath'] = self.serverapp.preferred_dir.replace(server_root, "") except Exception: page_config['preferredDir'] = server_root page_config['preferredPath'] = '/' self.application.store_id += 1 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) # 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 # 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 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)) # Write the template with the config. tpl = self.render_template('index.html', page_config=page_config) self.write(tpl) class NotFoundHandler(LabHandler): def render_template(self, name, **ns): if 'page_config' in ns: ns['page_config'] = ns['page_config'].copy() ns['page_config']['notFoundUrl'] = self.request.path return super().render_template(name, **ns) def add_handlers(handlers, extension_app): """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 = { '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 = { 'path': 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 = settings_config.get('blocked_extensions_uris', '') allowed_extensions_uris = settings_config.get('allowed_extensions_uris', '') if (blocked_extensions_uris) and (allowed_extensions_uris): print('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 ListingsHandler.pc = ioloop.PeriodicCallback( lambda: fetch_listings(None), callback_time=ListingsHandler.listings_refresh_seconds * 1000, jitter=0.1 ) ListingsHandler.pc.start() 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): """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.8.2/jupyterlab_server/licenses_app.py000066400000000000000000000054141412364214400240650ustar00rootroot00000000000000"""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 traitlets import Bool, Unicode, Enum, Instance from jupyter_core.application import JupyterApp, base_aliases, base_flags from ._version import __version__ from .config import LabConfig from .licenses_handler import LicensesManager class LicensesApp(JupyterApp, LabConfig): 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, **kwargs): super().initialize(*args, **kwargs) self.init_licenses_manager() def init_licenses_manager(self): self.licenses_manager = LicensesManager( labextensions_path=sum( [self.labextensions_path + self.extra_labextensions_path], [] ), parent=self, ) def start(self): 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.8.2/jupyterlab_server/licenses_handler.py000066400000000000000000000221201412364214400247130ustar00rootroot00000000000000"""Manager and Tornado handlers for license reporting.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import mimetypes import json import re import csv import io from pathlib import Path from concurrent.futures import ThreadPoolExecutor from tornado import web, gen from traitlets import Unicode, List from traitlets.config import LoggingConfigurable from .server import APIHandler 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], help="the license report data in built app and federated extensions", ) @property def federated_extensions(self): """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. """ labextensions_path = sum( [ self.parent.labextensions_path, self.parent.extra_labextensions_path, ], [], ) return get_federated_extensions(labextensions_path) @gen.coroutine def report_async( self, report_format="markdown", bundles_pattern=".*", full_text=False ): """Asynchronous wrapper around the potentially slow job of locating and encoding all of the licenses """ report = yield self.executor.submit( self.report, report_format=report_format, bundles_pattern=bundles_pattern, full_text=full_text, ) return report def report(self, report_format, bundles_pattern, full_text): """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", ) def report_json(self, bundles): """create a JSON report TODO: SPDX """ return json.dumps({"bundles": bundles}, indent=2, sort_keys=True) def report_csv(self, bundles): """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, full_text=True): """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, bundle): """Return the content of a packages's license bundles""" bundle_json = {"packages": []} 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(): self.log.warning( "Third-party licenses not found for %s: %s", bundle, 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 return bundle_json def app_static_info(self): """get the static directory for this app This will usually be in `static_dir`, but may also appear in the parent of `static_dir`. """ 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=".*"): """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 and re.match(bundles_pattern, app_name): bundles[app_name] = self.license_bundle(app_path, app_name) if not bundles: self.log.warn("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): super(LicensesHandler, self).initialize() self.manager = manager @web.authenticated async def get(self, _args): """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 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) def finish(self, _mime_type, *args, **kwargs): """Overload the regular finish, which (sensibly) always sets JSON""" self.update_api_activity() self.set_header("Content-Type", _mime_type) return super(APIHandler, self).finish(*args, **kwargs) jupyterlab_server-2.8.2/jupyterlab_server/listings_handler.py000066400000000000000000000061511412364214400247500ustar00rootroot00000000000000"""Tornado handlers for listing extensions.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json import requests import tornado from .server import APIHandler LISTINGS_URL_SUFFIX='@jupyterlab/extensionmanager-extension/listings.json' def fetch_listings(logger): """Fetch the listings for the extension manager.""" if not logger: from traitlets import log logger = log.get_logger() if len(ListingsHandler.blocked_extensions_uris) > 0: blocked_extensions = [] for blocked_extensions_uri in ListingsHandler.blocked_extensions_uris: logger.info('Fetching blocked_extensions from {}'.format(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('Fetching allowed_extensions from {}'.format(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({ '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() # The list of allowed_extensions URIS. allowed_extensions_uris = set() # The blocked extensions extensions. blocked_extensions = [] # The allowed extensions extensions. allowed_extensions = [] # The provider request options to be used for the request library. listings_request_opts = {} # The PeriodicCallback that schedule the call to fetch_listings method. pc = None def get(self, path): """Get the listings for the extension manager.""" self.set_header('Content-Type', 'application/json') if path == LISTINGS_URL_SUFFIX: self.write(ListingsHandler.listings) else: raise tornado.web.HTTPError(400) jupyterlab_server-2.8.2/jupyterlab_server/process.py000066400000000000000000000201451412364214400230740ustar00rootroot00000000000000# coding: utf-8 """JupyterLab Server process handler""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import atexit import logging import os import re from shutil import which as _which import signal import subprocess import sys import threading import time import weakref from tornado import gen try: import pty except ImportError: pty = False if sys.platform == 'win32': list2cmdline = subprocess.list2cmdline else: def list2cmdline(cmd_list): import pipes return ' '.join(map(pipes.quote, cmd_list)) logging.basicConfig(format='%(message)s', level=logging.INFO) def which(command, env=None): """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 path = env.get('PATH') or os.defpath 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(object): """A wrapper for a child process. """ _procs = weakref.WeakSet() _pool = None def __init__(self, cmd, logger=None, cwd=None, kill_event=None, env=None, quiet=False): """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)): raise ValueError('Command must be given as a list') if kill_event and kill_event.is_set(): raise ValueError('Process aborted') self.logger = logger = logger or logging.getLogger('jupyterlab') self._last_line = '' if not quiet: self.logger.info('> ' + 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): """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.) except subprocess.TimeoutExpired: if os.name == 'nt': sig = signal.SIGBREAK else: sig = signal.SIGKILL if proc.poll() is None: os.kill(proc.pid, sig) finally: Process._procs.remove(self) return proc.wait() def wait(self): """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() raise ValueError('Process was aborted') time.sleep(1.) return self.terminate() @gen.coroutine def wait_async(self): """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() raise ValueError('Process was aborted') yield gen.sleep(1.) raise gen.Return(self.terminate()) def _create_process(self, **kwargs): """Create the process. """ cmd = 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) return proc @classmethod def _cleanup(cls): """Clean up the started subprocesses at exit. """ for proc in list(cls._procs): proc.terminate() class WatchHelper(Process): """A process helper for a watch process. """ def __init__(self, cmd, startup_regex, logger=None, cwd=None, kill_event=None, env=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(WatchHelper, self).__init__(cmd, logger=logger, cwd=cwd, kill_event=kill_event, env=env) if not pty: self._stdout = self.proc.stdout while 1: line = self._stdout.readline().decode('utf-8') if not line: raise RuntimeError('Process ended improperly') print(line.rstrip()) if re.match(startup_regex, line): break self._read_thread = threading.Thread(target=self._read_incoming) self._read_thread.setDaemon(True) self._read_thread.start() def terminate(self): """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: Process._procs.remove(self) return proc.returncode def _read_incoming(self): """Run in a thread to read stdout and print""" fileno = self._stdout.fileno() while 1: try: buf = os.read(fileno, 1024) except OSError as e: self.logger.debug('Read incoming error %s', e) return if not buf: return print(buf.decode('utf-8'), end='') def _create_process(self, **kwargs): """Create the watcher helper process. """ kwargs['bufsize'] = 0 if pty: master, slave = pty.openpty() kwargs['stderr'] = kwargs['stdout'] = slave kwargs['start_new_session'] = True self._stdout = os.fdopen(master, 'rb') else: kwargs['stdout'] = subprocess.PIPE 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(WatchHelper, self)._create_process(**kwargs) # Register the cleanup handler. atexit.register(Process._cleanup)jupyterlab_server-2.8.2/jupyterlab_server/process_app.py000066400000000000000000000026051412364214400237350ustar00rootroot00000000000000# coding: utf-8 """A lab app that runs a sub process for a demo or a test.""" import sys 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 def get_command(self): """Get the command and kwargs to run with `Process`. This is intended to be overridden. """ return ['python', '--version'], dict() def initialize_settings(self): """Start the application. """ IOLoop.current().add_callback(self._run_command) def initialize_handlers(self): add_handlers(self.handlers, self) def _run_command(self): 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): try: IOLoop.current().stop() sys.exit(future.result()) except Exception as e: self.log.error(str(e)) sys.exit(1) jupyterlab_server-2.8.2/jupyterlab_server/pytest_plugin.py000066400000000000000000000105271412364214400243270ustar00rootroot00000000000000import json import os import os.path as osp import shutil from os.path import join as pjoin import pytest from jupyterlab_server import LabServerApp from jupyterlab_server.app import LabServerApp pytest_plugins = [ "jupyter_server.pytest_plugin" ] def mkdir(tmp_path, *parts): path = tmp_path.joinpath(*parts) if not path.exists(): path.mkdir(parents=True) return path 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, jp_template_dir, app_settings_dir, user_settings_dir, schemas_dir, workspaces_dir, labextensions_dir ): def _make_labserver_extension_app(**kwargs): 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( os.path.abspath(os.path.dirname(__file__)), 'tests', '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( os.path.abspath(os.path.dirname(__file__)), 'tests', '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( os.path.abspath(os.path.dirname(__file__)), 'tests', '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. data = pjoin( os.path.abspath(os.path.dirname(__file__)), 'tests', 'workspaces') for item in os.listdir(data): src = pjoin(data, 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, make_labserver_extension_app): app = make_labserver_extension_app() app._link_jupyter_server_extension(jp_serverapp) app.initialize() return app jupyterlab_server-2.8.2/jupyterlab_server/rest-api.yml000066400000000000000000000230701412364214400233130ustar00rootroot00000000000000# see me at: http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterlab/jupyterlab_server/master/docs/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.8.2/jupyterlab_server/server.py000077500000000000000000000007031412364214400227250ustar00rootroot00000000000000from jupyter_server.base.handlers import ( # noqa APIHandler, FileFindHandler, json_errors, JupyterHandler ) from jupyter_server.extension.serverextension import ( # noqa GREEN_ENABLED, GREEN_OK, RED_DISABLED, RED_X ) from jupyter_server.serverapp import ServerApp, aliases, flags # noqa from jupyter_server.utils import url_escape, url_path_join # noqa from jupyter_server import _tz as tz jupyterlab_server-2.8.2/jupyterlab_server/settings_handler.py000077500000000000000000000056331412364214400247630ustar00rootroot00000000000000"""Tornado handlers for frontend config storage.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json 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 DEFAULT_LOCALE, translator class SettingsHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, SchemaHandler): def initialize( self, name, app_settings_dir, schemas_dir, settings_dir, labextensions_path, **kwargs ): SchemaHandler.initialize( self, app_settings_dir, schemas_dir, settings_dir, labextensions_path ) ExtensionHandlerMixin.initialize(self, name) @web.authenticated def get(self, schema_name=""): """Get setting(s)""" # 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) 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 ) # Print all warnings. for w in warnings: if w: self.log.warn(w) return self.finish(json.dumps(result)) @web.authenticated def put(self, schema_name): """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)) except (KeyError, TypeError) as e: raise web.HTTPError(400, invalid_payload_format_error) except ValidationError as e: raise web.HTTPError(400, validation_error % str(e)) self.set_status(204) jupyterlab_server-2.8.2/jupyterlab_server/settings_utils.py000077500000000000000000000356011412364214400245040ustar00rootroot00000000000000"""Frontend config storage helpers.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. from glob import glob import json import json5 from jsonschema import Draft4Validator as Validator, ValidationError from jupyter_server.services.config.manager import ConfigManager, recursive_update import os from tornado import web from .server import APIHandler, tz from .translation_utils import DEFAULT_LOCALE, L10N_SCHEMA_NAME, is_valid_locale # The JupyterLab settings file extension. SETTINGS_EXTENSION = '.jupyterlab-settings' def _get_schema(schemas_dir, schema_name, overrides, labextensions_path): """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))) 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))) version = _get_version(schemas_dir, schema_name) return schema, version def _get_user_settings(settings_dir, schema_name, schema): """ 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))) # 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, schema_name): """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, settings_dir, overrides, extension=".json", labextensions_path=None, translator=None, ): """ Returns a tuple containing: - the list of plugins, schemas, and their settings, respecting any defaults that may have been overridden. - the list of warnings that were generated when validating the user overrides against the schemas. """ settings = {} federated_settings = {} 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. 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 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, schema, overrides): """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, schema_name, make_dirs=False, extension='.json'): """ 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. """ parent_dir = root_dir 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) 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))) return path def _get_overrides(app_settings_dir): """Get overrides settings from `app_settings_dir`.""" overrides, error = {}, "" overrides_path = os.path.join(app_settings_dir, 'overrides.json') if not os.path.exists(overrides_path): overrides_path = os.path.join(app_settings_dir, 'overrides.json5') if os.path.exists(overrides_path): with open(overrides_path, encoding='utf-8') as fid: try: overrides = json5.load(fid) except Exception as e: error = e # Allow `default_settings_overrides.json` files in /labconfig dirs # to allow layering of defaults cm = ConfigManager(config_dir_name="labconfig") recursive_update(overrides, cm.get('default_setting_overrides')) return overrides, error def get_settings( app_settings_dir, schemas_dir, settings_dir, schema_name="", overrides=None, labextensions_path=None, translator=None, ): """ 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, 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, ) result = { "settings": settings_list, } return result, warnings def save_settings( schemas_dir, settings_dir, schema_name, raw_settings, overrides, labextensions_path=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, schemas_dir, settings_dir, labextensions_path, **kwargs ): 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.warn(overrides_warning % str(error)) def get_current_locale(self): """ 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 misshappen translation settings schema:\n%s" self.log.warn(schema_warning % str(e)) settings = {} current_locale = settings.get("settings", {}).get("locale", DEFAULT_LOCALE) if not is_valid_locale(current_locale): current_locale = DEFAULT_LOCALE return current_locale jupyterlab_server-2.8.2/jupyterlab_server/templates/000077500000000000000000000000001412364214400230405ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/templates/403.html000066400000000000000000000002721412364214400242350ustar00rootroot00000000000000 403 Forbidden

Sorry ..

.. you are not allowed to see this content!

jupyterlab_server-2.8.2/jupyterlab_server/templates/error.html000066400000000000000000000023031412364214400250550ustar00rootroot00000000000000 {% 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.8.2/jupyterlab_server/templates/index.html000066400000000000000000000023201412364214400250320ustar00rootroot00000000000000 {% 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.8.2/jupyterlab_server/tests/000077500000000000000000000000001412364214400222045ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/__init__.py000066400000000000000000000000001412364214400243030ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/app-settings/000077500000000000000000000000001412364214400246225ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/app-settings/overrides.json000066400000000000000000000015131412364214400275170ustar00rootroot00000000000000{ "@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.8.2/jupyterlab_server/tests/conftest.py000066400000000000000000000001361412364214400244030ustar00rootroot00000000000000pytest_plugins = [ "jupyter_server.pytest_plugin", "jupyterlab_server.pytest_plugin" ]jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/000077500000000000000000000000001412364214400236275ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/@jupyterlab/000077500000000000000000000000001412364214400261105ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/@jupyterlab/apputils-extension/000077500000000000000000000000001412364214400317635ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/@jupyterlab/apputils-extension/themes.json000066400000000000000000000015221412364214400341430ustar00rootroot00000000000000{ "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.8.2/jupyterlab_server/tests/schemas/@jupyterlab/codemirror-extension/000077500000000000000000000000001412364214400322675ustar00rootroot00000000000000commands.json000066400000000000000000000006171412364214400347100ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/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.8.2/jupyterlab_server/tests/schemas/@jupyterlab/shortcuts-extension/000077500000000000000000000000001412364214400321605ustar00rootroot00000000000000package.json.orig000066400000000000000000000001151412364214400353230ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/@jupyterlab/shortcuts-extension{ "name": "@jupyterlab/shortcuts-extension", "version": "test-version" } jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/@jupyterlab/shortcuts-extension/plugin.json000066400000000000000000000444761412364214400343700ustar00rootroot00000000000000{ "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.8.2/jupyterlab_server/tests/schemas/@jupyterlab/translation-extension/000077500000000000000000000000001412364214400324605ustar00rootroot00000000000000plugin.json000066400000000000000000000006171412364214400345760ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/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.8.2/jupyterlab_server/tests/schemas/@jupyterlab/unicode-extension/000077500000000000000000000000001412364214400315505ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/schemas/@jupyterlab/unicode-extension/plugin.json000066400000000000000000000017021412364214400337410ustar00rootroot00000000000000{ "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.8.2/jupyterlab_server/tests/test_labapp.py000066400000000000000000000021711412364214400250550ustar00rootroot00000000000000"""Basic tests for the lab handlers. """ import pytest import tornado from .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 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_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.8.2/jupyterlab_server/tests/test_licenses_api.py000066400000000000000000000104011412364214400262470ustar00rootroot00000000000000"""Test the Settings service API. """ import io import csv import json import mimetypes import pytest import mistune from .. import LicensesApp from ..licenses_handler import DEFAULT_THIRD_PARTY_LICENSE_FILE # 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", ) setattr(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, mime_format_parser, jp_fetch, labserverapp, tmp_path, ): _make_static_dir(labserverapp, tmp_path, license_json=license_json) mime, fmt, parse = mime_format_parser 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 jupyterlab_server-2.8.2/jupyterlab_server/tests/test_listings_api.py000066400000000000000000000003561412364214400263060ustar00rootroot00000000000000 from .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) jupyterlab_server-2.8.2/jupyterlab_server/tests/test_settings_api.py000077500000000000000000000143201412364214400263110ustar00rootroot00000000000000"""Test the Settings service API. """ import pytest import json import json5 import tornado from strict_rfc3339 import rfc3339_to_timestamp from .utils import expected_http_error from .utils import maybe_patch_ioloop, big_unicode_string from .utils import 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 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(tornado.httpclient.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_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 = [item for item in listing if item['id'] == id][0] # 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(tornado.httpclient.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(tornado.httpclient.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(tornado.httpclient.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(tornado.httpclient.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.8.2/jupyterlab_server/tests/test_themes_api.py000066400000000000000000000003031412364214400257270ustar00rootroot00000000000000 from .utils import validate_request async def test_get_theme(jp_fetch, labserverapp): r = await jp_fetch("lab", "api", "themes", "@jupyterlab", "foo", "index.css") validate_request(r) jupyterlab_server-2.8.2/jupyterlab_server/tests/test_translation_api.py000066400000000000000000000163421412364214400270120ustar00rootroot00000000000000"""Test the translations service API.""" import json import os import pytest import shutil import subprocess import sys from .utils import expected_http_error from ..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) from .utils import maybe_patch_ioloop from .utils import validate_request 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() 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']).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. src = os.path.join(HERE, '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(HERE, '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" r = 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" r = 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 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 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 message == "" def test_get_language_packs(): data, message = get_language_packs("en") assert "en" in data assert "es_CO" in data assert 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 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.8.2/jupyterlab_server/tests/test_workspaces_api.py000077500000000000000000000110031412364214400266250ustar00rootroot00000000000000"""Test the kernels service API.""" import json import os import pytest import shutil import tornado from strict_rfc3339 import rfc3339_to_timestamp from .utils import expected_http_error, maybe_patch_ioloop, big_unicode_string from .utils import 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 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 = set(['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 = 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.8.2/jupyterlab_server/tests/translations/000077500000000000000000000000001412364214400247255ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO/000077500000000000000000000000001412364214400325715ustar00rootroot00000000000000MANIFEST.in000066400000000000000000000002351412364214400342500ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_COrecursive-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/000077500000000000000000000000001412364214400406045ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO__init__.py000066400000000000000000000000001412364214400427030ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_COlocale/000077500000000000000000000000001412364214400420435ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_COes_CO/000077500000000000000000000000001412364214400430335ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/localeLC_MESSAGES/000077500000000000000000000000001412364214400446205ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO/jupyterlab_language_pack_es_CO/locale/es_COjupyterlab.json000066400000000000000000000003551412364214400476770ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.mo000066400000000000000000000006261412364214400473420ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.po000066400000000000000000000006651412364214400473500ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.json000066400000000000000000000003271412364214400523740ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.mo000066400000000000000000000005701412364214400520360ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.po000066400000000000000000000006271412364214400520440ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.8.2/jupyterlab_server/tests/translations/jupyterlab-language-pack-es_CO/setup.py000066400000000000000000000004401412364214400343010ustar00rootroot00000000000000from 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.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/000077500000000000000000000000001412364214400314405ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/MANIFEST.in000066400000000000000000000002171412364214400331760ustar00rootroot00000000000000recursive-include *.md recursive-include *.txt recursive-include jupyterlab_some_package *.json recursive-include jupyterlab_some_package *.mo jupyterlab_some_package/000077500000000000000000000000001412364214400362405ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package__init__.py000066400000000000000000000000001412364214400403370ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/jupyterlab_some_packagelocale/000077500000000000000000000000001412364214400374775ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/jupyterlab_some_packagees_CO/000077500000000000000000000000001412364214400404675ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/jupyterlab_some_package/localeLC_MESSAGES/000077500000000000000000000000001412364214400422545ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/jupyterlab_some_package/locale/es_COjupyterlab_some_package.json000066400000000000000000000003321412364214400500240ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/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.8.2/jupyterlab_server/tests/translations/jupyterlab-some-package/setup.py000066400000000000000000000004261412364214400331540ustar00rootroot00000000000000from 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"] }, ) jupyterlab_server-2.8.2/jupyterlab_server/tests/utils.py000077500000000000000000000125061412364214400237250ustar00rootroot00000000000000import errno import json import os import sys from contextlib import contextmanager from http.cookies import SimpleCookie from pathlib import Path from urllib.parse import parse_qs, urlparse, urljoin from openapi_core.validation.request.datatypes import ( RequestParameters, OpenAPIRequest ) from openapi_core.validation.response.datatypes import OpenAPIResponse from openapi_core import create_spec from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator import requests from ruamel.yaml import YAML import tornado here = os.path.dirname(__file__) with open( os.path.join(here, 'app-settings', 'overrides.json'), encoding='utf-8' ) as fpt: big_unicode_string = json.load(fpt)["@jupyterlab/unicode-extension:plugin"]["comment"] def wrap_request(request, spec): """Wrap a tornado request as an open api request""" # Extract cookie dict from cookie header cookie = 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) # 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 for path in spec['paths']: if url: continue has_arg = '{' in path if has_arg: path = path[:path.index('{')] 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: raise ValueError(f'Could not find matching pattern for {o.path}') # gets deduced by path finder against spec path = {} # Order matters because all tornado requests # include Accept */* which does not necessarily match the content type mimetype = request.headers.get('Content-Type') or \ request.headers.get('Accept') or 'application/json' parameters = RequestParameters( query=parse_qs(o.query), header=dict(request.headers), cookie=cookies, path=path, ) return OpenAPIRequest( full_url_pattern=url, method=request.method.lower(), parameters=parameters, body=request.body, mimetype=mimetype, ) def wrap_response(response): """Wrap a tornado response as an open api response""" mimetype = response.headers.get('Content-Type') or 'application/json' return OpenAPIResponse( data=response.body, status_code=response.code, mimetype=mimetype, ) def validate_request(response): """Validate an API request""" path = (Path(__file__) / '../../rest-api.yml').resolve() yaml = YAML(typ='safe') spec_dict = yaml.load(path.read_text(encoding='utf-8')) spec = create_spec(spec_dict) validator = RequestValidator(spec) request = wrap_request(response.request, spec) result = validator.validate(request) print(result.errors) result.raise_for_errors() validator = ResponseValidator(spec) response = wrap_response(response) result = validator.validate(request, response) print(result.errors) result.raise_for_errors() def maybe_patch_ioloop(): """ a windows 3.8+ patch for the asyncio loop """ if sys.platform.startswith("win") and tornado.version_info < (6, 1): if sys.version_info >= (3, 8): import asyncio try: from asyncio import ( WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy, ) except ImportError: pass # not affected else: if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: # WindowsProactorEventLoopPolicy is not compatible with tornado 6 # fallback to the pre-3.8 default of Selector asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) def expected_http_error(error, expected_code, expected_message=None): """Check that the error matches the expected output error.""" e = error.value 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 @contextmanager def assert_http_error(status, msg=None): try: yield except requests.HTTPError as e: real_status = e.response.status_code assert real_status == status, \ "Expected status %d, got %d" % (status, real_status) if msg: assert msg in str(e), e else: assert False, "Expected HTTP error status" jupyterlab_server-2.8.2/jupyterlab_server/tests/workspaces/000077500000000000000000000000001412364214400243655ustar00rootroot00000000000000jupyterlab_server-2.8.2/jupyterlab_server/tests/workspaces/foo-2c26.jupyterlab-workspace000066400000000000000000000000501412364214400317140ustar00rootroot00000000000000{"data": {}, "metadata": {"id": "foo"}} jupyterlab_server-2.8.2/jupyterlab_server/tests/workspaces/foo-92dd.jupyterlab-workspace000066400000000000000000000000531412364214400320050ustar00rootroot00000000000000{"data": {}, "metadata": {"id": "f/o/o/"}} jupyterlab_server-2.8.2/jupyterlab_server/themes_handler.py000066400000000000000000000057761412364214400244150ustar00rootroot00000000000000"""Tornado handlers for dynamic theme loading.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os import re from glob import glob from os import path as osp from urllib.parse import urlparse from .server import FileFindHandler from .server import url_path_join as ujoin class ThemesHandler(FileFindHandler): """A file handler that mangles local urls in CSS files.""" def initialize(self, path, default_filename=None, no_cache_paths=None, themes_url=None, labextensions_path=None, **kwargs): # 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(self, abspath, start=None, end=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 = osp.splitext(abspath) if ext != '.css': return FileFindHandler.get_content(abspath, start, end) return self._get_css() def get_content_size(self): """Retrieve the total size of the resource at the given path.""" base, ext = osp.splitext(self.absolute_path) if ext != '.css': return FileFindHandler.get_content_size(self) else: return len(self._get_css()) def _get_css(self): """Get the mangled css file contents.""" with open(self.absolute_path, 'rb') as fid: data = fid.read().decode('utf-8') basedir = osp.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\('(.*)'\)|" r'url\("(.*)"\)') def replacer(m): """Replace the matched relative url with the mangled url.""" group = m.group() # Get the part that matched part = [g for g in m.groups() if g][0] # 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.8.2/jupyterlab_server/translation_utils.py000066400000000000000000000540101412364214400251720ustar00rootroot00000000000000""" Localization utilities to find available language packs and packages with localization data. """ import gettext import importlib import json import os import re import sys import traceback from functools import lru_cache from typing import Dict, Pattern import babel import entrypoints from packaging.version import parse as parse_version from typing import Tuple # Entry points JUPYTERLAB_LANGUAGEPACK_ENTRY = "jupyterlab.languagepack" JUPYTERLAB_LOCALE_ENTRY = "jupyterlab.locale" # Constants DEFAULT_LOCALE = "en" 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 "jupyter\.lab\.setting-icon-label": _default_settings_context, "jupyter\.lab\.menus/.*/label": "menu", "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(): """ Get available installed language pack locales. Returns ------- tuple A tuple, where the first item is the result and the second item any error messages. Notes ----- This functions are meant to be called via a subprocess to guarantee the results represent the most up-to-date entry point information, which seems to be defined on interpreter startup. """ data = {} messages = [] for entry_point in entrypoints.get_group_all(JUPYTERLAB_LANGUAGEPACK_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 def _get_installed_package_locales(): """ 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. Notes ----- This functions are meant to be called via a subprocess to guarantee the results represent the most up-to-date entry point information, which seems to be defined on interpreter startup. """ data = {} messages = [] for entry_point in entrypoints.get_group_all(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 def _main(): """ Run functions in this file in a subprocess and prints to stdout the results. """ data = {} message = "" if len(sys.argv) == 2: func_name = sys.argv[-1] func = globals().get(func_name, None) if func: try: data, message = func() except Exception: message = traceback.format_exc() else: message = "Invalid number of arguments!" sys.stdout.write(json.dumps({"data": data, "message": 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" """ valid = False try: babel.Locale.parse(locale) valid = True except babel.core.UnknownLocaleError: pass except ValueError: 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 def merge_locale_data(language_pack_locale_data, package_locale_data): """ 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, "{0}.json".format(package_name), ) if os.path.isfile(locale_json_path): try: with open(locale_json_path, "r", 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( "The following locales are invalid: {0}!".format(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): if 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, "r", 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): self._domain = domain self._locale = locale self.update_locale(locale) def update_locale(self, locale: str): """ Update the locale environment variables. 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) localedir = os.path.join(os.path.dirname(mod.__file__), LOCALE_DIR) except Exception: pass gettext.bindtextdomain(self._domain, localedir=localedir) def gettext(self, msgid: str) -> str: """ Translate a singular string. Parameters ---------- msgid: str The singular string to translate. Returns ------- str The translated string. """ return gettext.dgettext(self._domain, 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 gettext.dngettext(self._domain, 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 = gettext.dgettext(self._domain, msgid) else: translation = gettext.dpgettext(self._domain, 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 = gettext.dngettext(self._domain, msgid, msgid_plural, n) else: translation = gettext.dnpgettext(self._domain, 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: """ Translations manager. """ _TRANSLATORS = {} _LOCALE = DEFAULT_LOCALE @staticmethod def _update_env(locale: str): """ Update the locale environment variables based on the settings. Parameters ---------- locale: str The language name to use. """ for key in ["LANGUAGE", "LANG"]: os.environ[key] = f"{locale}.UTF-8" @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): """ 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 translator._update_env(locale) for domain, 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, schema: dict, prefix: str = "", to_translate: Dict[Pattern, str] = 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(): 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, schema.copy()) return new_schema if __name__ == "__main__": _main() jupyterlab_server-2.8.2/jupyterlab_server/translations_handler.py000066400000000000000000000031731412364214400256360ustar00rootroot00000000000000# Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. """ Translation handler. """ import json import traceback import tornado from tornado import gen from .settings_utils import SchemaHandler from .translation_utils import get_language_pack, get_language_packs, is_valid_locale, translator class TranslationsHandler(SchemaHandler): @gen.coroutine @tornado.web.authenticated def get(self, locale=""): """ Get installed language packs. Parameters ---------- locale: str, optional If no locale is provided, it will list all the installed language packs. Default is `""`. """ data, message = {}, "" try: if locale == "": data, message = get_language_packs( display_locale=self.get_current_locale() ) else: data, message = get_language_pack(locale) if data == {} and message == "": if is_valid_locale(locale): message = "Language pack '{}' not installed!".format(locale) else: message = "Language pack '{}' not valid!".format(locale) else: # only change locale if the language pack is installed and valid if is_valid_locale(locale): 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.8.2/jupyterlab_server/workspaces_handler.py000066400000000000000000000156231412364214400253010ustar00rootroot00000000000000"""Tornado handlers for frontend config storage.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import hashlib import json import os import re import unicodedata import urllib from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin from tornado import web from .server import APIHandler, tz from .server import url_path_join as ujoin # The JupyterLab workspace file extension. WORKSPACE_EXTENSION = '.jupyterlab-workspace' def _list_workspaces(directory, prefix): """ Return the list of workspaces in a given directory beginning with the given prefix. """ workspaces = { 'ids': [], 'values': [] } if not os.path.exists(directory): return workspaces items = [item for item in os.listdir(directory) if item.startswith(prefix) and item.endswith(WORKSPACE_EXTENSION)] items.sort() for slug in items: workspace_path = os.path.join(directory, slug) if os.path.exists(workspace_path): try: workspace = _load_with_file_times(workspace_path) workspaces.get('ids').append(workspace['metadata']['id']) workspaces.get('values').append(workspace) except Exception as e: raise web.HTTPError(500, str(e)) return workspaces def _load_with_file_times(workspace_path): """ Load workspace JSON from disk, overwriting the `created` and `last_modified` metadata with current file stat information """ stat = os.stat(workspace_path) with open(workspace_path, 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, base='', sign=True, max_length=128 - len(WORKSPACE_EXTENSION)): """ 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 WorkspacesHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, APIHandler): def initialize(self, name, path, workspaces_url=None, **kwargs): super().initialize(name) self.workspaces_dir = path def ensure_directory(self): """Return the workspaces directory if set or raise error if not set""" if not self.workspaces_dir: raise web.HTTPError(500, 'Workspaces directory is not set') return self.workspaces_dir @web.authenticated def delete(self, space_name): """Remove a workspace""" directory = self.ensure_directory() if not space_name: raise web.HTTPError(400, 'Workspace name is required for DELETE') slug = slugify(space_name) workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION) if not os.path.exists(workspace_path): raise web.HTTPError(404, 'Workspace %r (%r) not found' % (space_name, slug)) try: # to delete the workspace file. os.remove(workspace_path) return self.set_status(204) except Exception as e: raise web.HTTPError(500, str(e)) @web.authenticated def get(self, space_name=''): """Get workspace(s) data""" directory = self.ensure_directory() if not space_name: prefix = slugify('', sign=False) workspaces = _list_workspaces(directory, prefix) return self.finish(json.dumps(dict(workspaces=workspaces))) slug = slugify(space_name) workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION) if os.path.exists(workspace_path): try: # to load and parse the workspace file. workspace = _load_with_file_times(workspace_path) except Exception as e: raise web.HTTPError(500, str(e)) else: id = (space_name if space_name.startswith('/') else '/' + space_name) workspace = dict(data=dict(), metadata=dict(id=id)) return self.finish(json.dumps(workspace)) @web.authenticated def put(self, space_name=''): """Update workspace data""" if not space_name: raise web.HTTPError(400, 'Workspace name is required for PUT.') directory = self.ensure_directory() if not os.path.exists(directory): try: os.makedirs(directory) except Exception as e: raise web.HTTPError(500, str(e)) raw = self.request.body.strip().decode('utf-8') workspace = dict() # Make sure the data is valid JSON. try: decoder = json.JSONDecoder() workspace = decoder.decode(raw) except Exception as e: raise web.HTTPError(400, str(e)) # Make sure metadata ID matches the workspace name. # Transparently support an optional inital 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 = ('Workspace metadata ID mismatch: expected %r got %r' % (space_name, metadata_id)) raise web.HTTPError(400, message) slug = slugify(space_name) workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION) # Write the workspace data to a file. with open(workspace_path, 'w', encoding='utf-8') as fid: fid.write(raw) self.set_status(204) jupyterlab_server-2.8.2/pyproject.toml000066400000000000000000000016101412364214400202050ustar00rootroot00000000000000[build-system] requires = ["jupyter_packaging~=0.9,<2", "jupyter_server"] build-backend = "setuptools.build_meta" [tool.jupyter-releaser] skip = ["check-links"] [tool.check-manifest] ignore = ["tbump.toml", ".*", "*.yml", "docs/source/api/app-config.rst", "docs/source/changelog.md"] ignore-bad-ideas = ["jupyterlab_server/tests/translations/**/*.mo"] [tool.tbump.version] current = "2.8.2" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? ''' [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" [[tool.tbump.file]] src = "jupyterlab_server/_version.py" [tool.pytest.ini_options] addopts = "--color=yes -s --tb=long -svv --cov jupyterlab_server --cov-report term-missing --cov-report term:skip-covered" filterwarnings = ["ignore::DeprecationWarning:notebook", "ignore::DeprecationWarning:traitlets"] jupyterlab_server-2.8.2/readthedocs.yml000066400000000000000000000003151412364214400203020ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/source/conf.py conda: environment: docs/environment.yml python: version: 3.8 install: # install jupyterlab_server itself - method: pip path: . jupyterlab_server-2.8.2/setup.cfg000066400000000000000000000024451412364214400171210ustar00rootroot00000000000000[metadata] name = jupyterlab_server version = attr: jupyterlab_server._version.__version__ description = A set of server components for JupyterLab and JupyterLab like applications . long_description = file: README.md long_description_content_type = text/markdown license_file = LICENSE author = Jupyter Development Team author_email = jupyter@googlegroups.com url = https://jupyter.org platforms = Linux, Mac OS X, Windows keywords = jupyter, jupyterlab classifiers = Intended Audience :: Developers Intended Audience :: System Administrators Intended Audience :: Science/Research License :: OSI Approved :: BSD License Programming Language :: Python Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 [options] zip_safe = False include_package_data = True packages = find: python_requires = >=3.6 install_requires = babel entrypoints>=0.2.2 jinja2>=2.10 json5 jsonschema>=3.0.1 packaging requests jupyter_server~=1.4 [options.extras_require] test = codecov; ipykernel; pytest>=5.3.2; pytest-cov; jupyter_server[test]; openapi_core~=0.14.0; pytest-console-scripts; strict-rfc3339; ruamel.yaml; wheel [options.packages.find] exclude = ['docs*'] jupyterlab_server-2.8.2/setup.py000066400000000000000000000000451412364214400170040ustar00rootroot00000000000000from setuptools import setup setup()