diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index b1c953d5ac..0000000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "notebook/static/components" -} \ No newline at end of file diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index be28522243..870668b4ef 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -15,6 +15,7 @@ jobs: strategy: matrix: group: [check_release, link_check] + fail-fast: false steps: - name: Checkout uses: actions/checkout@v2 @@ -60,6 +61,7 @@ jobs: uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 with: token: ${{ secrets.GITHUB_TOKEN }} + version_spec: 100.100.100 - name: Check Links if: ${{ matrix.group == 'link_check' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0f0faba1e4..a350a4289c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -37,7 +37,7 @@ jobs: ${{ runner.os }}-pip- - name: Install the Python dependencies run: | - pip install -e .[test] codecov + pip install -e .[test] pip install -r docs/doc-requirements.txt wget https://github.com/jgm/pandoc/releases/download/1.19.1/pandoc-1.19.1-1-amd64.deb && sudo dpkg -i pandoc-1.19.1-1-amd64.deb - name: List installed packages diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml index 354a0468d7..d09f468975 100644 --- a/.github/workflows/enforce-label.yml +++ b/.github/workflows/enforce-label.yml @@ -1,3 +1,6 @@ +# Pull requests to this repo should have a triage label, +# this workflow ensures that this is the case. + name: Enforce PR label on: diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml deleted file mode 100644 index 9ce5424e20..0000000000 --- a/.github/workflows/js.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Linux JS Tests - -on: - push: - branches: '*' - pull_request: - branches: '*' - -jobs: - build: - runs-on: ${{ matrix.os }}-latest - strategy: - fail-fast: false - matrix: - os: [ubuntu, macos] - group: [notebook, base, services] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Set up Node - uses: actions/setup-node@v1 - with: - node-version: '12.x' - - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - name: Cache pip on Linux - uses: actions/cache@v1 - if: startsWith(runner.os, 'Linux') - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python }}-${{ hashFiles('**/requirements.txt', 'setup.py') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python }} - - - name: Install dependencies - run: | - pip install --upgrade pip - pip install --upgrade setuptools wheel - npm install - npm install -g casperjs@1.1.3 phantomjs-prebuilt@2.1.7 - pip install .[test] - - - name: Run Tests - run: | - python -m notebook.jstest ${{ matrix.group }} diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 282ea1fd85..e509155cd5 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -22,7 +22,7 @@ jobs: architecture: 'x64' - name: Upgrade packaging dependencies run: | - pip install --upgrade pip setuptools wheel --user + python -m pip install --upgrade pip setuptools wheel --user - name: Get pip cache dir id: pip-cache run: | @@ -37,7 +37,7 @@ jobs: ${{ runner.os }}-pip- - name: Install the Python dependencies run: | - pip install -e .[test] codecov + pip install -e .[test] - name: List installed packages run: | pip freeze @@ -48,6 +48,3 @@ jobs: - name: Run Integration Tests run: | pytest -v notebook/tests/test_notebookapp_integration.py --integration_tests - - name: Coverage - run: | - codecov diff --git a/.gitignore b/.gitignore index f040e4811c..7bfef17bab 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ docs/man/*.gz docs/source/api/generated docs/source/config.rst docs/gh-pages -notebook/i18n/*/LC_MESSAGES/*.mo -notebook/i18n/*/LC_MESSAGES/nbjs.json notebook/static/components notebook/static/style/*.min.css* notebook/static/*/js/built/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..0952f479dc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,2 @@ +repos: [] + diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a072eecb..9166759234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,211 @@ Use `pip install pip --upgrade` to upgrade pip. Check pip version with +## 6.5.5 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.4...fe35f939b5a2c256cbc6822a60ea728d6f5d4251)) + +### Bugs fixed + +- Pin jupyter_client<8 and pyzmq<25 [#6749](https://github.com/jupyter/notebook/pull/6749) ([@RRosio](https://github.com/RRosio)) + +### Documentation improvements + +- Add impending deprecation notice and fix link for API [#6920](https://github.com/jupyter/notebook/pull/6920) ([@RRosio](https://github.com/RRosio)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2023-04-06&to=2023-07-27&type=c)) + +[@andrii-i](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aandrii-i+updated%3A2023-04-06..2023-07-27&type=Issues) | [@brichet](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Abrichet+updated%3A2023-04-06..2023-07-27&type=Issues) | [@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2023-04-06..2023-07-27&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2023-04-06..2023-07-27&type=Issues) | [@henryiii](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ahenryiii+updated%3A2023-04-06..2023-07-27&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2023-04-06..2023-07-27&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akrassowski+updated%3A2023-04-06..2023-07-27&type=Issues) | [@parmentelat](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aparmentelat+updated%3A2023-04-06..2023-07-27&type=Issues) | [@RRosio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARRosio+updated%3A2023-04-06..2023-07-27&type=Issues) | [@tonyfast](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Atonyfast+updated%3A2023-04-06..2023-07-27&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ayuvipanda+updated%3A2023-04-06..2023-07-27&type=Issues) + + + +## 6.5.4 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.3...428e00bef821acb42f13843fb2949d225d081d56)) + +### Enhancements made + +- Add show_banner trait to control the banner display [#6808](https://github.com/jupyter/notebook/pull/6808) ([@echarles](https://github.com/echarles)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2023-03-06&to=2023-04-06&type=c)) + +[@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2023-03-06..2023-04-06&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2023-03-06..2023-04-06&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2023-03-06..2023-04-06&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akrassowski+updated%3A2023-03-06..2023-04-06&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ayuvipanda+updated%3A2023-03-06..2023-04-06&type=Issues) + +## 6.5.3 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.2...7939fc595db4ffc3482031365d17ef72a02c085e)) + +### Enhancements made + +- Add a banner and log to information about the migration to Notebook 7 plan [#6742](https://github.com/jupyter/notebook/pull/6742) ([@echarles](https://github.com/echarles)) +- Add sys_info to page template for 6.5.x [#6668](https://github.com/jupyter/notebook/pull/6668) ([@juhasch](https://github.com/juhasch)) + +### Bugs fixed + +- Add .mo and .json files for translations [#6728](https://github.com/jupyter/notebook/pull/6728) ([@frenzymadness](https://github.com/frenzymadness)) +- Apply PR #6609 to 6.5.x (Fix rename_file and delete_file to handle hidden files properly) [#6660](https://github.com/jupyter/notebook/pull/6660) ([@yacchin1205](https://github.com/yacchin1205)) + +### Other merged PRs + +- Fix ru_RU translation [#6745](https://github.com/jupyter/notebook/pull/6745) ([@andrii-i](https://github.com/andrii-i)) +- Update kernel translation [#6744](https://github.com/jupyter/notebook/pull/6744) ([@JasonWeill](https://github.com/JasonWeill)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-10-30&to=2023-03-06&type=c)) + +[@andrii-i](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aandrii-i+updated%3A2022-10-30..2023-03-06&type=Issues) | [@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2022-10-30..2023-03-06&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afcollonval+updated%3A2022-10-30..2023-03-06&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afrenzymadness+updated%3A2022-10-30..2023-03-06&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2022-10-30..2023-03-06&type=Issues) | [@JasonWeill](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AJasonWeill+updated%3A2022-10-30..2023-03-06&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2022-10-30..2023-03-06&type=Issues) | [@juhasch](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajuhasch+updated%3A2022-10-30..2023-03-06&type=Issues) | [@RRosio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARRosio+updated%3A2022-10-30..2023-03-06&type=Issues) | [@venkatasg](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Avenkatasg+updated%3A2022-10-30..2023-03-06&type=Issues) | [@yacchin1205](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ayacchin1205+updated%3A2022-10-30..2023-03-06&type=Issues) + +## 6.5.2 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.1...8a00144fa9afc26ff9a29c9abf12db4014f52293)) + +### Bugs fixed + +- Ensure custom preload is correctly handled [#6580](https://github.com/jupyter/notebook/pull/6580) ([@echarles](https://github.com/echarles)) +- Fix: jQuery-UI 404 Error by updating dependency path in static template [#6578](https://github.com/jupyter/notebook/pull/6578) ([@RRosio](https://github.com/RRosio)) + +### Maintenance and upkeep improvements + +- Depend on nbclassic 0.4.7 [#6593](https://github.com/jupyter/notebook/pull/6593) ([@echarles](https://github.com/echarles)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-10-13&to=2022-10-30&type=c)) + +[@bnavigator](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Abnavigator+updated%3A2022-10-13..2022-10-30&type=Issues) | [@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2022-10-13..2022-10-30&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afcollonval+updated%3A2022-10-13..2022-10-30&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2022-10-13..2022-10-30&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2022-10-13..2022-10-30&type=Issues) | [@RRosio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARRosio+updated%3A2022-10-13..2022-10-30&type=Issues) + +## 6.5.1 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.0...67546dad676025b70b8b5f061c42ed31029f5dac)) + +### Merged PRs + +- fix: pin temporary to nbclassic 0.4.5 [#6570](https://github.com/jupyter/notebook/pull/6570) ([@echarles](https://github.com/echarles)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-10-13&to=2022-10-13&type=c)) + +[@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2022-10-13..2022-10-13&type=Issues) + +## 6.5.0 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.0rc0...3c7afbdff6ec33e61531b3cbe3bc20b8440d3181)) + +### Bugs fixed + +- Forward port of #6461 - Fix a typo in exception text [#6545](https://github.com/jupyter/notebook/pull/6545) ([@krassowski](https://github.com/krassowski)) +- Normalise `os_path` [#6540](https://github.com/jupyter/notebook/pull/6540) ([@krassowski](https://github.com/krassowski)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-08-30&to=2022-10-13&type=c)) + +[@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2022-08-30..2022-10-13&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akrassowski+updated%3A2022-08-30..2022-10-13&type=Issues) + +## 6.5.0rc0 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.5.0b0...6d7109a6f39b8ad92d06ebf34e8dbbca5c9cbaf6)) + +### Merged PRs + +- Update redirect logic and tests [#6511](https://github.com/jupyter/notebook/pull/6511) ([@RRosio](https://github.com/RRosio)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-07-29&to=2022-08-30&type=c)) + +[@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2022-07-29..2022-08-30&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2022-07-29..2022-08-30&type=Issues) | [@RRosio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARRosio+updated%3A2022-07-29..2022-08-30&type=Issues) + +## 6.5.0b0 + +No merged PRs + +## 6.5.0a0 + +([Full Changelog](https://github.com/jupyter/notebook/compare/6.4.12...87d57658aaeccaffb5242a3b7b95702636922e8c)) + +### Maintenance and upkeep improvements + +- Selenium test updates [#6484](https://github.com/jupyter/notebook/pull/6484) ([@ericsnekbytes](https://github.com/ericsnekbytes)) +- Make notebook 6.5.x point to nbclassic static assets [#6474](https://github.com/jupyter/notebook/pull/6474) ([@ericsnekbytes](https://github.com/ericsnekbytes)) + +### Documentation improvements + +- Update contributing docs to reflect changes to build process [#6488](https://github.com/jupyter/notebook/pull/6488) ([@RRosio](https://github.com/RRosio)) +- Fix Check Release/link_check CI Job [#6485](https://github.com/jupyter/notebook/pull/6485) ([@RRosio](https://github.com/RRosio)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-06-07&to=2022-07-26&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2022-06-07..2022-07-26&type=Issues) | [@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2022-06-07..2022-07-26&type=Issues) | [@ericsnekbytes](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aericsnekbytes+updated%3A2022-06-07..2022-07-26&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2022-06-07..2022-07-26&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2022-06-07..2022-07-26&type=Issues) | [@ofek](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aofek+updated%3A2022-06-07..2022-07-26&type=Issues) | [@RRosio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARRosio+updated%3A2022-06-07..2022-07-26&type=Issues) + +## 6.4.12 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.11...6.4.12) + +- Address security advisory [GHSA-v7vq-3x77-87vg](https://github.com/jupyter/notebook/security/advisories/GHSA-v7vq-3x77-87vg) + +## 6.4.11 + +([Full Changelog](https://github.com/jupyter/notebook/compare/6.4.10...3911672959fcd35cf4a1b1ad7c9c8a5651c17ae6)) + +### Bugs fixed + +- Update further to ipykernel comm refactoring [#6358](https://github.com/jupyter/notebook/pull/6358) ([@echarles](https://github.com/echarles)) + +### Maintenance and upkeep improvements + +- Add testpath to the test dependencies. [#6357](https://github.com/jupyter/notebook/pull/6357) ([@echarles](https://github.com/echarles)) +- Temporary workaround to fix js-tests related to sanitizer js loading by phantomjs [#6356](https://github.com/jupyter/notebook/pull/6356) ([@echarles](https://github.com/echarles)) +- Use place-hold.it instead of plaecehold.it to create image placeholders [#6320](https://github.com/jupyter/notebook/pull/6320) ([@echarles](https://github.com/echarles)) +- Migrate to python 3.7+ [#6260](https://github.com/jupyter/notebook/pull/6260) - Fixes [#6256](https://github.com/jupyter/notebook/pull/6256) ([@penguinolog](https://github.com/penguinolog)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-03-15&to=2022-04-18&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2022-03-15..2022-04-18&type=Issues) | [@echarles](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aecharles+updated%3A2022-03-15..2022-04-18&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afcollonval+updated%3A2022-03-15..2022-04-18&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2022-03-15..2022-04-18&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2022-03-15..2022-04-18&type=Issues) | [@penguinolog](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Apenguinolog+updated%3A2022-03-15..2022-04-18&type=Issues) + +## 6.4.9 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.7...9e3a7001117e64a24ead07b888bd055fdd66faf3)) + +### Maintenance and upkeep improvements + +- Update links and fix check-release [#6310](https://github.com/jupyter/notebook/pull/6310) ([@blink1073](https://github.com/blink1073)) +- Update 6.4.x branch with some missing commits [#6308](https://github.com/jupyter/notebook/pull/6308) ([@kycutler](https://github.com/kycutler)) + +### Other merged PRs + +- Specify minimum version of nbconvert required [#6286](https://github.com/jupyter/notebook/pull/6286) ([@adamjstewart](https://github.com/adamjstewart)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-01-12&to=2022-03-14&type=c)) + +[@adamjstewart](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aadamjstewart+updated%3A2022-01-12..2022-03-14&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2022-01-12..2022-03-14&type=Issues) | [@github-actions](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Agithub-actions+updated%3A2022-01-12..2022-03-14&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2022-01-12..2022-03-14&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2022-01-12..2022-03-14&type=Issues) | [@kycutler](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akycutler+updated%3A2022-01-12..2022-03-14&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AZsailer+updated%3A2022-01-12..2022-03-14&type=Issues) + +## 6.4.8 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.7...479902d83a691253e0cff8439a33577e82408317)) + +### Bugs fixed + +- Fix to remove potential memory leak on Jupyter Notebooks ZMQChannelHandler code [#6251](https://github.com/jupyter/notebook/pull/6251) ([@Vishwajeet0510](https://github.com/Vishwajeet0510)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2022-01-12&to=2022-01-25&type=c)) + +[@Vishwajeet0510](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AVishwajeet0510+updated%3A2022-01-12..2022-01-25&type=Issues) + ## 6.4.7 ([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.6...b77b5e38b8fa1a20150d7fa4d735dbf1c4f00418)) @@ -40,8 +245,6 @@ Use `pip install pip --upgrade` to upgrade pip. Check pip version with [@antoinecarme](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aantoinecarme+updated%3A2021-11-16..2022-01-12&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2021-11-16..2022-01-12&type=Issues) | [@ccw630](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Accw630+updated%3A2021-11-16..2022-01-12&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2021-11-16..2022-01-12&type=Issues) | [@LiHua-Official](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ALiHua-Official+updated%3A2021-11-16..2022-01-12&type=Issues) | [@penguinolog](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Apenguinolog+updated%3A2021-11-16..2022-01-12&type=Issues) | [@tornaria](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Atornaria+updated%3A2021-11-16..2022-01-12&type=Issues) - - ## 6.4.6 ([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.5...160c27d3c23dafe8b42240571db21b0d5cbae2fe)) @@ -1320,7 +1523,7 @@ instructions. recursively, unlike normal config values, to make it load more consistently with frontend extensions([2108](https://github.com/jupyter/notebook/pull/2108)). - The notebook server now supports the [bundler - API](https://jupyter-notebook.readthedocs.io/en/latest/extending/bundler_extensions.html) + API](https://jupyter-notebook.readthedocs.io/en/stable/extending/bundler_extensions.html) from the [jupyter_cms incubator project](https://github.com/jupyter-incubator/contentmanagement) ([1579](https://github.com/jupyter/notebook/pull/1579)). - The notebook server now provides information about kernel activity diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6f947cb58c..5445df8cb8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,33 +20,10 @@ __ https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.htm Setting Up a Development Environment ------------------------------------ -Installing Node.js and npm -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Building the Notebook from its GitHub source code requires some tools to -create and minify JavaScript components and the CSS, -specifically Node.js and Node's package manager, ``npm``. -It should be node version ≥ 6.0. - -If you use ``conda``, you can get them with:: - - conda install -c conda-forge nodejs - -If you use `Homebrew `_ on Mac OS X:: - - brew install node - -Installation on Linux may vary, but be aware that the `nodejs` or `npm` packages -included in the system package repository may be too old to work properly. - -You can also use the installer from the `Node.js website `_. - - Installing the Jupyter Notebook ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Once you have installed the dependencies mentioned above, use the following -steps:: +Use the following steps:: pip install --upgrade setuptools pip git clone https://github.com/jupyter/notebook @@ -71,52 +48,28 @@ If you are working in development mode, you will see that your version of Jupyte Troubleshooting the Installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you do not see that your Jupyter Notebook is not running on dev mode, it's possible that you are +If you do not see that your Jupyter Notebook is running on dev mode, it's possible that you are running other instances of Jupyter Notebook. You can try the following steps: 1. Uninstall all instances of the notebook package. These include any installations you made using pip or conda. 2. Run ``python3 -m pip install -e .`` in the notebook repository to install the notebook from there. -3. Run ``npm run build`` to make sure the Javascript and CSS are updated and compiled. -4. Launch with ``python3 -m notebook --port 8989``, and check that the browser is pointing to ``localhost:8989`` +3. Launch with ``python3 -m notebook --port 8989``, and check that the browser is pointing to ``localhost:8989`` (rather than the default 8888). You don't necessarily have to launch with port 8989, as long as you use a port that is neither the default nor in use, then it should be fine. -5. Verify the installation with the steps in the previous section. - - -Rebuilding JavaScript and CSS -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -There is a build step for the JavaScript and CSS in the notebook. -To make sure that you are working with up-to-date code, you will need to run -this command whenever there are changes to JavaScript or LESS sources:: - - npm run build +4. Verify the installation with the steps in the previous section. -**IMPORTANT:** Don't forget to run ``npm run build`` after switching branches. -When switching between branches of different versions (e.g. ``4.x`` and -``master``), run ``pip install -e .``. If you have tried the above and still -find that the notebook is not reflecting the current source code, try cleaning -the repo with ``git clean -xfd`` and reinstalling with ``pip install -e .``. +If you have tried the above and still find that the notebook is not reflecting the current source code, +try cleaning the repo with ``git clean -xfd`` and reinstalling with ``pip install -e .``. -Development Tip -""""""""""""""" -When doing development, you can use this command to automatically rebuild -JavaScript and LESS sources as they are modified:: - - npm run build:watch - -Git Hooks -""""""""" - -If you want to automatically update dependencies and recompile JavaScript and -CSS after checking out a new commit, you can install post-checkout and -post-merge hooks which will do it for you:: +Modifying the JavaScript and CSS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - git-hooks/install-hooks.sh +The build process for this version of notebook grabs the static assets +from the nbclassic package. Frontend changes should be made in the `nbclassic repository`_. -See ``git-hooks/README.md`` for more details. +.. _nbclassic repository: https://github.com/jupyter/nbclassic Running Tests @@ -137,27 +90,6 @@ If you want coverage statistics as well, you can run:: py.test --cov notebook -v --pyargs notebook -JavaScript Tests -^^^^^^^^^^^^^^^^ - -To run the JavaScript tests, you will need to have PhantomJS and CasperJS -installed:: - - npm install -g casperjs phantomjs-prebuilt - -Then, to run the JavaScript tests:: - - python -m notebook.jstest [group] - -where ``[group]`` is an optional argument that is a path relative to -``notebook/tests/``. -For example, to run all tests in ``notebook/tests/notebook``:: - - python -m notebook.jstest notebook - -or to run just ``notebook/tests/notebook/deletecell.js``:: - - python -m notebook.jstest notebook/deletecell.js Building the Documentation diff --git a/MANIFEST.in b/MANIFEST.in index c28b820105..64b17e3a88 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,13 +3,10 @@ include CONTRIBUTING.rst include README.md include CHANGELOG.md include package.json -include bower.json -include .bowerrc include pyproject.toml include setup.py include setupbase.py include Dockerfile -include *.js graft tools graft notebook/tests diff --git a/README.md b/README.md index 8f6c2c7491..a69283458d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) [![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) -[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/stable/?badge=latest) [![codecov](https://codecov.io/gh/jupyter/notebook/branch/master/graph/badge.svg)](https://codecov.io/gh/jupyter/notebook) The Jupyter notebook is a web-based notebook environment for interactive @@ -39,7 +39,7 @@ discrete repos. You can find the installation documentation for the [Jupyter platform, on ReadTheDocs](https://jupyter.readthedocs.io/en/latest/install.html). The documentation for advanced usage of Jupyter notebook can be found -[here](https://jupyter-notebook.readthedocs.io/en/latest/). +[here](https://jupyter-notebook.readthedocs.io/en/stable/). For a local installation, make sure you have [pip installed](https://pip.readthedocs.io/en/stable/installing/) and run: @@ -69,8 +69,8 @@ If you are interested in contributing to the project, see [`CONTRIBUTING.rst`](C ## Resources - [Project Jupyter website](https://jupyter.org) - [Online Demo at jupyter.org/try](https://jupyter.org/try) -- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/stable/) - [Korean Version of Installation](https://github.com/ChungJooHo/Jupyter_Kor_doc/) -- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html ) - [Issues](https://github.com/jupyter/notebook/issues) - [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) diff --git a/bower.json b/bower.json deleted file mode 100644 index 393db02ad1..0000000000 --- a/bower.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "jupyter-notebook-deps", - "version": "0.0.1", - "dependencies": { - "backbone": "components/backbone#~1.2", - "bootstrap": "bootstrap#~3.4", - "bootstrap-tour": "0.9.0", - "codemirror": "components/codemirror#5.56.0+components1", - "create-react-class": "https://cdn.jsdelivr.net/npm/create-react-class@15.6.3/create-react-class.min.js", - "es6-promise": "~1.0", - "font-awesome": "components/font-awesome#~4.7.0", - "jed": "~1.1.1", - "jquery": "components/jquery#~3.5.0", - "jquery-typeahead": "~2.10.6", - "jquery-ui": "components/jqueryui#~1.12", - "marked": "~0.7", - "MathJax": "^2.7.4", - "moment": "~2.19.3", - "react": "~16.0.0", - "requirejs": "~2.2", - "requirejs-text": "~2.0.15", - "requirejs-plugins": "~1.0.3", - "text-encoding": "~0.1", - "underscore": "components/underscore#~1.8.3", - "xterm.js": "https://unpkg.com/xterm@~3.1.0/dist/xterm.js", - "xterm.js-css": "https://unpkg.com/xterm@~3.1.0/dist/xterm.css", - "xterm.js-fit": "https://unpkg.com/xterm@~3.1.0/dist/addons/fit/fit.js" - } -} diff --git a/docs-translations/hi-IN/README.md b/docs-translations/hi-IN/README.md index e234e3dac7..1e85490e66 100644 --- a/docs-translations/hi-IN/README.md +++ b/docs-translations/hi-IN/README.md @@ -2,7 +2,7 @@ [![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) [![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) -[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/stable/?badge=latest) @@ -40,7 +40,7 @@ IPython कोडबेस का बिग स्प्लिट ™। IPython आप के लिए स्थापना प्रलेखन पा सकते हैं [बृहस्पति मंच, ReadTheDocs पर](https://jupyter.readthedocs.io/en/latest/install.html). जुपिटर नोटबुक के उन्नत उपयोग के लिए दस्तावेज पाया जा सकता है -[यहाँ](https://jupyter-notebook.readthedocs.io/en/latest/). +[यहाँ](https://jupyter-notebook.readthedocs.io/en/stable/). स्थानीय स्थापना के लिए, सुनिश्चित करें कि आपके पास है [pip स्थापित](https://pip.readthedocs.io/en/stable/installing/) और भाग खड़ा हुआ: @@ -70,8 +70,8 @@ IPython कोडबेस का बिग स्प्लिट ™। IPython ## साधन - [Project Jupyter website](https://jupyter.org) - [Online Demo at jupyter.org/try](https://jupyter.org/try) -- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/stable/) - [Korean Version of Installation](https://github.com/ChungJooHo/Jupyter_Kor_doc/) -- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html ) - [Issues](https://github.com/jupyter/notebook/issues) -- [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) \ No newline at end of file +- [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) diff --git a/docs-translations/ja-JP/README.md b/docs-translations/ja-JP/README.md index 4a344ce26e..9262cdbecc 100644 --- a/docs-translations/ja-JP/README.md +++ b/docs-translations/ja-JP/README.md @@ -2,7 +2,7 @@ [![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) [![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) -[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/stable/?badge=latest) 英語版のリンク : [[English Version](http://github.com/jupyter/notebook/)] @@ -21,7 +21,7 @@ Jupyter Notebookは、Project Jupyter用の言語に依存しないHTMLノート ## インストール [Jupyter platform, on ReadTheDocs](https://jupyter.readthedocs.io/en/latest/install.html)から、インストールドキュメントをご覧になれます。 -Jupyter Notebookの発展的な使用方法に関するドキュメントは、[こちら](https://jupyter-notebook.readthedocs.io/en/latest/)をご覧ください。 +Jupyter Notebookの発展的な使用方法に関するドキュメントは、[こちら](https://jupyter-notebook.readthedocs.io/en/stable/)をご覧ください。 ローカルへのインストールの場合、[pip](https://pip.readthedocs.io/en/stable/installing/)をインストールしていることを確認し、以下のコマンドを実行してください。 @@ -51,7 +51,7 @@ Jupyter Notebookをリモートで起動する前に、いくつかの構成が - [Project Jupyter website](https://jupyter.org) - [Online Demo at try.jupyter.org](https://try.jupyter.org) -- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] -- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/stable/) +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html ) - [Issues](https://github.com/jupyter/notebook/issues) - [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 4fad2a8dac..a54b996648 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -2,7 +2,7 @@ [![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) [![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) -[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](http://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](http://jupyter-notebook.readthedocs.io/en/stable/?badge=stable) English 버전 링크 : [[English Version](http://github.com/jupyter/notebook/)] @@ -23,7 +23,7 @@ IPython 3는 *IPython notebook* 과 같은 사용자 언어에 독립적인 코 You can find the installation documentation for the [Jupyter platform, on ReadTheDocs](https://jupyter.readthedocs.io/en/latest/install.html). 조금 더 심화된 Jupyter notebook의 사용은 다음 주소에서 볼 수 있습니다. -[here](https://jupyter-notebook.readthedocs.io/en/latest/). +[here](https://jupyter-notebook.readthedocs.io/en/stable/). 설치를 위해서는 [pip installed](https://pip.readthedocs.io/en/stable/installing/) 가 있는지 확인한 후 다음을 실행해주세요: @@ -49,7 +49,7 @@ You can find the installation documentation for the ## 자료 - [Project Jupyter website](https://jupyter.org) - [Online Demo at try.jupyter.org](https://try.jupyter.org) -- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] -- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/stable/) +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html ) - [Issues](https://github.com/jupyter/notebook/issues) - [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 13da7f722b..5bee3f7d4c 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -2,7 +2,7 @@ [![Google Group](https://img.shields.io/badge/-Google%20Group-lightgrey.svg)](https://groups.google.com/forum/#!forum/jupyter) [![Build Status](https://travis-ci.org/jupyter/notebook.svg?branch=master)](https://travis-ci.org/jupyter/notebook) -[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/jupyter-notebook/badge/?version=latest)](https://jupyter-notebook.readthedocs.io/en/stable/?badge=latest) @@ -40,7 +40,7 @@ IPython代码库的Big Split™。 IPython 3是最后一个主要的整体 您可以找到以下安装文件 [Jupiter论坛,在ReadTheDocs上](https://jupyter.readthedocs.io/en/latest/install.html). 可以找到有关Jupiter笔记本的高级使用的文档 -[这里](https://jupyter-notebook.readthedocs.io/en/latest/). +[这里](https://jupyter-notebook.readthedocs.io/en/stable/). 对于本地安装,请确保您已经 [pip 成立时间](https://pip.readthedocs.io/en/stable/installing/) 并运行: @@ -70,8 +70,8 @@ IPython代码库的Big Split™。 IPython 3是最后一个主要的整体 ## 资源 - [Project Jupyter website](https://jupyter.org) - [Online Demo at jupyter.org/try](https://jupyter.org/try) -- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-notebook/latest/jupyter-notebook.pdf)] +- [Documentation for Jupyter notebook](https://jupyter-notebook.readthedocs.io/en/stable/) - [Korean Version of Installation](https://github.com/ChungJooHo/Jupyter_Kor_doc/) -- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] +- [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html ) - [Issues](https://github.com/jupyter/notebook/issues) -- [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) \ No newline at end of file +- [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) diff --git a/docs/doc-requirements.txt b/docs/doc-requirements.txt index bb439ddcf2..3b6551624c 100644 --- a/docs/doc-requirements.txt +++ b/docs/doc-requirements.txt @@ -3,3 +3,4 @@ sphinx-rtd-theme nbsphinx sphinxcontrib_github_alt myst_parser +ipython_genutils diff --git a/docs/environment.yml b/docs/environment.yml index 52ef4df581..38825c6e07 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -13,8 +13,10 @@ dependencies: - sphinx - terminado - myst-parser +- nbclassic>=0.4.7 - pip: - nbsphinx - Send2Trash - prometheus_client - sphinxcontrib_github_alt + - ipython_genutils diff --git a/docs/source/conf.py b/docs/source/conf.py index 1aa7613b42..9482f01205 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -319,7 +319,7 @@ # -- Options for link checks ---------------------------------------------- linkcheck_ignore = [ - 'http://127\.0\.0\.1/*' + r'http://127\.0\.0\.1/*' ] diff --git a/docs/source/config_overview.rst b/docs/source/config_overview.rst index ccb64a2751..96356d07aa 100644 --- a/docs/source/config_overview.rst +++ b/docs/source/config_overview.rst @@ -64,8 +64,8 @@ Notebook front-end client Notebook extensions ------------------- -- `Distributing Jupyter Extensions as Python Packages `_ -- `Extending the Notebook `_ +- `Distributing Jupyter Extensions as Python Packages `_ +- `Extending the Notebook `_ :ref:`Security in Jupyter notebooks: ` Since security diff --git a/docs/source/examples/Notebook/Distributing Jupyter Extensions as Python Packages.ipynb b/docs/source/examples/Notebook/Distributing Jupyter Extensions as Python Packages.ipynb index c0dd7daaec..13a2e08e6f 100644 --- a/docs/source/examples/Notebook/Distributing Jupyter Extensions as Python Packages.ipynb +++ b/docs/source/examples/Notebook/Distributing Jupyter Extensions as Python Packages.ipynb @@ -134,7 +134,7 @@ "\n", "> Of course, in addition to the files listed, there are number of other files one needs to build a proper package. Here are some good resources:\n", "- [The Hitchhiker's Guide to Packaging](https://the-hitchhikers-guide-to-packaging.readthedocs.io/en/latest/quickstart.html)\n", - "- [Repository Structure and Python](https://kenreitz.org/essays/2013/01/27/repository-structure-and-python) by Kenneth Reitz\n", + "- [Structure of the Repository](https://docs.python-guide.org/writing/structure/) by Kenneth Reitz and Real Python\n", "\n", "> How you distribute them, too, is important:\n", "- [Packaging and Distributing Projects](https://python-packaging-user-guide.readthedocs.io/tutorials/distributing-packages/)\n", diff --git a/docs/source/examples/Notebook/examples_index.rst b/docs/source/examples/Notebook/examples_index.rst index 61bddadd07..9339074573 100644 --- a/docs/source/examples/Notebook/examples_index.rst +++ b/docs/source/examples/Notebook/examples_index.rst @@ -5,7 +5,7 @@ Notebook Examples The pages in this section are all converted notebook files. You can also `view these notebooks on nbviewer`__. -__ https://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/ +__ https://nbviewer.jupyter.org/github/jupyter/notebook/blob/6.4.x/ docs/source/examples/Notebook/ .. toctree:: diff --git a/docs/source/extending/frontend_extensions.rst b/docs/source/extending/frontend_extensions.rst index 6c6c783602..7aade33c93 100644 --- a/docs/source/extending/frontend_extensions.rst +++ b/docs/source/extending/frontend_extensions.rst @@ -1,3 +1,14 @@ +.. warning:: + + **DEPRECATED APIs** + This documentation is for the Notebook 6 API which will soon be deprecated. + For up to date information on Notebook 7, please visit the latest version of the `Notebook documentation`_ or the `JEP for Notebook 7`_. + If you experience issues or are not yet able to migrate to Notebook 7, please visit the `NbClassic documentation`_ + +.. _JEP for Notebook 7: https://jupyter.org/enhancement-proposals/79-notebook-v7/notebook-v7.html +.. _Notebook documentation: https://jupyter-notebook.readthedocs.io/en/latest +.. _Nbclassic documentation: https://nbclassic.readthedocs.io/en/latest/nbclassic.html + Custom front-end extensions =========================== diff --git a/docs/source/extending/handlers.rst b/docs/source/extending/handlers.rst index bc38cdb004..e37fa15bec 100644 --- a/docs/source/extending/handlers.rst +++ b/docs/source/extending/handlers.rst @@ -3,7 +3,7 @@ Custom request handlers The notebook webserver can be interacted with using a well `defined RESTful -API `__. +API `__. You can define custom RESTful API handlers in addition to the ones provided by the notebook. As described below, to define a custom handler you need to first write a notebook server extension. Then, in the diff --git a/docs/source/frontend_config.rst b/docs/source/frontend_config.rst index 0ce675b6b4..f74378ca37 100644 --- a/docs/source/frontend_config.rst +++ b/docs/source/frontend_config.rst @@ -89,3 +89,11 @@ taking various value depending on the page where the configuration is issued. ``
`` can take various values like ``notebook``, ``tree``, and ``editor``. A ``common`` section contains configuration settings shared by all pages. + +Persisting configuration settings +--------------------------------- + +A banner might be shown to users to inform them about news or updates. This +banner can be hidden launching the server with the show_banner trait.:: + + jupyter notebook --NotebookApp.show_banner=False diff --git a/docs/source/links.txt b/docs/source/links.txt index 0306b047fb..f6503b7544 100644 --- a/docs/source/links.txt +++ b/docs/source/links.txt @@ -19,7 +19,7 @@ .. Main Jupyter notebook links .. _Notebook Basics: notebook_p2_ -.. _notebook_p2: https://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Notebook%20Basics.ipynb +.. _notebook_p2: https://nbviewer.jupyter.org/github/jupyter/notebook/blob/6.4.x/docs/source/examples/Notebook/Notebook%20Basics.ipynb .. _Running Code in the Jupyter Notebook: notebook_p1_ .. _notebook_p1: https://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Running%20Code.ipynb @@ -38,4 +38,4 @@ .. _Plotting with Matplotlib: notebook_p3_ .. _notebook_p3: https://nbviewer.jupyter.org/github/ipython/ipython/blob/master/examples/IPython%20Kernel/Plotting%20in%20the%20Notebook.ipynb -.. _Working with Markdown Cells: https://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Working%20With%20Markdown%20Cells.ipynb +.. _Working with Markdown Cells: https://nbviewer.jupyter.org/github/jupyter/notebook/blob/6.4.x/docs/source/examples/Notebook/Working%20With%20Markdown%20Cells.ipynb diff --git a/docs/source/notebook.rst b/docs/source/notebook.rst index fb1e0195c5..f5db5f95b2 100644 --- a/docs/source/notebook.rst +++ b/docs/source/notebook.rst @@ -234,7 +234,7 @@ drop-down on the toolbar (which will be "Code", initially), or via For more information on the different things you can do in a notebook, see the `collection of examples -`_. +`_. Code cells ~~~~~~~~~~ diff --git a/docs/source/template.tpl b/docs/source/template.tpl index 411ef7c68d..8c9d4af9e9 100644 --- a/docs/source/template.tpl +++ b/docs/source/template.tpl @@ -2,7 +2,7 @@ {% macro notebooklink() -%} -`View the original notebook on nbviewer `__ +`View the original notebook on nbviewer `__ {%- endmacro %} diff --git a/notebook/__init__.py b/notebook/__init__.py index c72096f26b..6c71e6e7b6 100644 --- a/notebook/__init__.py +++ b/notebook/__init__.py @@ -1,8 +1,6 @@ """The Jupyter HTML Notebook""" import os -# Packagers: modify this line if you store the notebook static files elsewhere -DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static") # Packagers: modify the next line if you store the notebook template files # elsewhere diff --git a/notebook/__main__.py b/notebook/__main__.py index ee339caebf..c11733204c 100644 --- a/notebook/__main__.py +++ b/notebook/__main__.py @@ -1,4 +1,3 @@ - if __name__ == '__main__': from notebook import notebookapp as app app.launch_new_instance() diff --git a/notebook/_sysinfo.py b/notebook/_sysinfo.py index 951ffd1014..4abeadcedb 100644 --- a/notebook/_sysinfo.py +++ b/notebook/_sysinfo.py @@ -55,10 +55,10 @@ def pkg_commit_hash(pkg_path): if repo_commit: return 'repository', repo_commit.strip().decode('ascii') else: - return u'', u'' + return '', '' par_path = p.dirname(par_path) - - return u'', u'' + + return '', '' def pkg_info(pkg_path): diff --git a/notebook/_version.py b/notebook/_version.py index 6c5729f54d..9016d2744a 100644 --- a/notebook/_version.py +++ b/notebook/_version.py @@ -5,16 +5,12 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.5.0.dev0' +__version__ = '6.5.5' # 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']) + parts.append(match['rest']) version_info = tuple(parts) - -# Downstream maintainer, when running `python.setup.py jsversion`, -# the version string is propagated to the JavaScript files, do not forget to -# patch the JavaScript files in `.postN` release done by distributions. diff --git a/notebook/auth/__main__.py b/notebook/auth/__main__.py index ff413b534e..26c0a8f2a0 100644 --- a/notebook/auth/__main__.py +++ b/notebook/auth/__main__.py @@ -25,14 +25,17 @@ def set_password(args): } }) if not args.quiet: - print("password stored in config dir: %s" % jupyter_config_dir()) + print(f"password stored in config dir: {jupyter_config_dir()}") def main(argv): parser = argparse.ArgumentParser(argv[0]) subparsers = parser.add_subparsers() parser_password = subparsers.add_parser('password', help='sets a password for your notebook server') - parser_password.add_argument("password", help="password to set, if not given, a password will be queried for (NOTE: this may not be safe)", - nargs="?") + parser_password.add_argument( + "password", + help="password to set, if not given, a password will be queried for (NOTE: this may not be safe)", + nargs="?" + ) parser_password.add_argument("--quiet", help="suppress messages", action="store_true") parser_password.set_defaults(function=set_password) args = parser.parse_args(argv[1:]) diff --git a/notebook/auth/login.py b/notebook/auth/login.py index 16c4e7a356..f1600f95fd 100644 --- a/notebook/auth/login.py +++ b/notebook/auth/login.py @@ -6,7 +6,7 @@ import re import os -from urllib.parse import urlparse +from urllib.parse import urlparse, urlunparse import uuid @@ -42,21 +42,24 @@ def _redirect_safe(self, url, default=None): # instead of %5C, causing `\\` to behave as `//` url = url.replace("\\", "%5C") parsed = urlparse(url) - if parsed.netloc or not (parsed.path + '/').startswith(self.base_url): + path_only = urlunparse(parsed._replace(netloc='', scheme='')) + if url != path_only or not (parsed.path + '/').startswith(self.base_url): # require that next_url be absolute path within our path allow = False # OR pass our cross-origin check - if parsed.netloc: + if url != path_only: # if full URL, run our cross-origin check: - origin = '%s://%s' % (parsed.scheme, parsed.netloc) + origin = f'{parsed.scheme}://{parsed.netloc}' origin = origin.lower() - if self.allow_origin: + if origin == f'{self.request.protocol}://{self.request.host}': + allow = True + elif self.allow_origin: allow = self.allow_origin == origin elif self.allow_origin_pat: allow = bool(self.allow_origin_pat.match(origin)) if not allow: # not allowed, use default - self.log.warning("Not allowing login redirect to %r" % url) + self.log.warning(f"Not allowing login redirect to {url!r}") url = default self.redirect(url) @@ -73,13 +76,13 @@ def hashed_password(self): def passwd_check(self, a, b): return passwd_check(a, b) - + def post(self): - typed_password = self.get_argument('password', default=u'') - new_password = self.get_argument('new_password', default=u'') + typed_password = self.get_argument('password', default='') + new_password = self.get_argument('new_password', default='') + - if self.get_login_available(self.settings): if self.passwd_check(self.hashed_password, typed_password) and not new_password: self.set_login_cookie(self, uuid.uuid4().hex) @@ -89,7 +92,7 @@ def post(self): config_dir = self.settings.get('config_dir') config_file = os.path.join(config_dir, 'jupyter_notebook_config.json') set_password(new_password, config_file=config_file) - self.log.info("Wrote hashed password to %s" % config_file) + self.log.info(f"Wrote hashed password to {config_file}") else: self.set_status(401) self._render(message={'error': 'Invalid credentials'}) @@ -197,7 +200,7 @@ def get_user(cls, handler): @classmethod def get_user_token(cls, handler): """Identify the user based on a token in the URL or Authorization header - + Returns: - uuid if authenticated - None if not @@ -245,7 +248,7 @@ def password_from_settings(cls, settings): If there is no configured password, an empty string will be returned. """ - return settings.get('password', u'') + return settings.get('password', '') @classmethod def get_login_available(cls, settings): diff --git a/notebook/auth/security.py b/notebook/auth/security.py index cc9723901b..a043b63e30 100644 --- a/notebook/auth/security.py +++ b/notebook/auth/security.py @@ -5,7 +5,6 @@ from contextlib import contextmanager import getpass import hashlib -import io import json import os import random @@ -71,7 +70,7 @@ def passwd(passphrase=None, algorithm='argon2'): return ':'.join((algorithm, cast_unicode(h, 'ascii'))) else: h = hashlib.new(algorithm) - salt = ('%0' + str(salt_len) + 'x') % random.getrandbits(4 * salt_len) + salt = f"{random.getrandbits(4 * salt_len):0{salt_len}x}" h.update(cast_bytes(passphrase, 'utf-8') + str_to_bytes(salt, 'ascii')) return ':'.join((algorithm, salt, h.hexdigest())) @@ -135,7 +134,7 @@ def passwd_check(hashed_passphrase, passphrase): def persist_config(config_file=None, mode=0o600): """Context manager that can be used to modify a config object - On exit of the context manager, the config will be written back to disk, + On exit of the context manager, the config will be written back to disk, by default with user-only (600) permissions. """ @@ -152,20 +151,20 @@ def persist_config(config_file=None, mode=0o600): yield config - with io.open(config_file, 'w', encoding='utf8') as f: + with open(config_file, 'w', encoding='utf8') as f: f.write(cast_unicode(json.dumps(config, indent=2))) try: os.chmod(config_file, mode) except Exception as e: tb = traceback.format_exc() - warnings.warn("Failed to set permissions on %s:\n%s" % (config_file, tb), + warnings.warn(f"Failed to set permissions on {config_file}:\n{tb}", RuntimeWarning) def set_password(password=None, config_file=None): """Ask user for password, store it in notebook json configuration file""" - + hashed_password = passwd(password) with persist_config(config_file) as config: diff --git a/notebook/auth/tests/test_login.py b/notebook/auth/tests/test_login.py index 2b5574204a..9120b539d4 100644 --- a/notebook/auth/tests/test_login.py +++ b/notebook/auth/tests/test_login.py @@ -31,6 +31,8 @@ def test_next_bad(self): "//host" + self.url_prefix + "tree", "https://google.com", "/absolute/not/base_url", + "///jupyter.org", + "/\\some-host", ): url = self.login(next=bad_next) self.assertEqual(url, self.url_prefix) @@ -39,10 +41,14 @@ def test_next_bad(self): def test_next_ok(self): for next_path in ( "tree/", - "//" + self.url_prefix + "tree", + self.base_url() + "has/host", "notebooks/notebook.ipynb", "tree//something", ): - expected = self.url_prefix + next_path + if "://" in next_path: + expected = next_path + else: + expected = self.url_prefix + next_path + actual = self.login(next=expected) self.assertEqual(actual, expected) diff --git a/notebook/auth/tests/test_security.py b/notebook/auth/tests/test_security.py index cf748cda82..b042fcbfa9 100644 --- a/notebook/auth/tests/test_security.py +++ b/notebook/auth/tests/test_security.py @@ -18,8 +18,8 @@ def test_bad(): def test_passwd_check_unicode(): # GH issue #4524 - phash = u'sha1:23862bc21dd3:7a415a95ae4580582e314072143d9c382c491e4f' - assert passwd_check(phash, u"łe¶ŧ←↓→") - phash = (u'argon2:$argon2id$v=19$m=10240,t=10,p=8$' - u'qjjDiZUofUVVnrVYxacnbA$l5pQq1bJ8zglGT2uXP6iOg') - assert passwd_check(phash, u"łe¶ŧ←↓→") + phash = 'sha1:23862bc21dd3:7a415a95ae4580582e314072143d9c382c491e4f' + assert passwd_check(phash, "łe¶ŧ←↓→") + phash = ('argon2:$argon2id$v=19$m=10240,t=10,p=8$' + 'qjjDiZUofUVVnrVYxacnbA$l5pQq1bJ8zglGT2uXP6iOg') + assert passwd_check(phash, "łe¶ŧ←↓→") diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 216480291d..41ba1be6e1 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -59,7 +59,7 @@ class AuthenticatedHandler(web.RequestHandler): @property def content_security_policy(self): """The default Content-Security-Policy header - + Can be overridden by defining Content-Security-Policy in settings['headers'] """ if 'Content-Security-Policy' in self.settings.get('headers', {}): @@ -132,7 +132,7 @@ def get_current_user(self): def skip_check_origin(self): """Ask my login_handler if I should skip the origin_check - + For example: in the default LoginHandler, if a request is token-authenticated, origin checking should be skipped. """ @@ -152,11 +152,9 @@ def token_authenticated(self): @property def cookie_name(self): - default_cookie_name = non_alphanum.sub('-', 'username-{}'.format( - self.request.host - )) + default_cookie_name = non_alphanum.sub('-', f'username-{self.request.host}') return self.settings.get('cookie_name', default_cookie_name) - + @property def logged_in(self): """Is a user currently logged in?""" @@ -188,14 +186,14 @@ def login_available(self): class IPythonHandler(AuthenticatedHandler): """IPython-specific extensions to authenticated handling - + Mostly property shortcuts to IPython-specific settings. """ @property def ignore_minified_js(self): """Wether to user bundle in template. (*.min files) - + Mainly use for development and avoid file recompilation """ return self.settings.get('ignore_minified_js', False) @@ -203,7 +201,7 @@ def ignore_minified_js(self): @property def config(self): return self.settings.get('config', None) - + @property def log(self): """use the IPython log by default, falling back on tornado's logger""" @@ -213,23 +211,23 @@ def log(self): def jinja_template_vars(self): """User-supplied values to supply to jinja templates.""" return self.settings.get('jinja_template_vars', {}) - + #--------------------------------------------------------------- # URLs #--------------------------------------------------------------- - + @property def version_hash(self): """The version hash to use for cache hints for static files""" return self.settings.get('version_hash', '') - + @property def mathjax_url(self): url = self.settings.get('mathjax_url', '') if not url or url_is_absolute(url): return url return url_path_join(self.base_url, url) - + @property def mathjax_config(self): return self.settings.get('mathjax_config', 'TeX-AMS-MML_HTMLorMML-full,Safe') @@ -246,16 +244,20 @@ def default_url(self): def ws_url(self): return self.settings.get('websocket_url', '') + @property + def show_banner(self): + return self.settings.get('show_banner', True) + @property def contents_js_source(self): self.log.debug("Using contents: %s", self.settings.get('contents_js_source', 'services/contents')) return self.settings.get('contents_js_source', 'services/contents') - + #--------------------------------------------------------------- # Manager objects #--------------------------------------------------------------- - + @property def kernel_manager(self): return self.settings['kernel_manager'] @@ -263,15 +265,15 @@ def kernel_manager(self): @property def contents_manager(self): return self.settings['contents_manager'] - + @property def session_manager(self): return self.settings['session_manager'] - + @property def terminal_manager(self): return self.settings['terminal_manager'] - + @property def kernel_spec_manager(self): return self.settings['kernel_spec_manager'] @@ -283,22 +285,22 @@ def config_manager(self): #--------------------------------------------------------------- # CORS #--------------------------------------------------------------- - + @property def allow_origin(self): """Normal Access-Control-Allow-Origin""" return self.settings.get('allow_origin', '') - + @property def allow_origin_pat(self): """Regular expression version of allow_origin""" return self.settings.get('allow_origin_pat', None) - + @property def allow_credentials(self): """Whether to set Access-Control-Allow-Credentials""" return self.settings.get('allow_credentials', False) - + def set_default_headers(self): """Add CORS headers, if defined""" super().set_default_headers() @@ -320,19 +322,16 @@ def set_default_headers(self): if self.allow_credentials: self.set_header("Access-Control-Allow-Credentials", 'true') - + def set_attachment_header(self, filename): """Set Content-Disposition: attachment header As a method to ensure handling of filename encoding """ escaped_filename = url_escape(filename) - self.set_header('Content-Disposition', - 'attachment;' - " filename*=utf-8''{utf8}" - .format( - utf8=escaped_filename, - ) + self.set_header( + 'Content-Disposition', + f"attachment; filename*=utf-8''{escaped_filename}" ) def get_origin(self): @@ -424,7 +423,7 @@ def check_referer(self): return True # apply cross-origin checks to Referer: - origin = "{}://{}".format(referer_url.scheme, referer_url.netloc) + origin = f"{referer_url.scheme}://{referer_url.netloc}" if self.allow_origin: allow = self.allow_origin == origin elif self.allow_origin_pat: @@ -453,7 +452,7 @@ def check_xsrf_cookie(self): if not self.check_referer(): referer = self.request.headers.get('Referer') if referer: - msg = "Blocking Cross Origin request from {}.".format(referer) + msg = f"Blocking Cross Origin request from {referer}." else: msg = "Blocking request from unknown origin" raise web.HTTPError(403, msg) from e @@ -505,16 +504,16 @@ def prepare(self): #--------------------------------------------------------------- # template rendering #--------------------------------------------------------------- - + def get_template(self, name): """Return the jinja template object for a given name""" return self.settings['jinja2_env'].get_template(name) - + def render_template(self, name, **ns): ns.update(self.template_namespace) template = self.get_template(name) return template.render(**ns) - + @property def template_namespace(self): return dict( @@ -535,21 +534,22 @@ def template_namespace(self): xsrf_token=self.xsrf_token.decode('utf8'), nbjs_translations=json.dumps(combine_translations( self.request.headers.get('Accept-Language', ''))), + show_banner=self.show_banner, **self.jinja_template_vars ) - + def get_json_body(self): """Return the body of the request as JSON data.""" if not self.request.body: return None # Do we need to call body.decode('utf-8') here? - body = self.request.body.strip().decode(u'utf-8') + body = self.request.body.strip().decode('utf-8') try: model = json.loads(body) except Exception as e: self.log.debug("Bad JSON: %r", body) self.log.error("Couldn't parse JSON", exc_info=True) - raise web.HTTPError(400, u'Invalid JSON in body of request') from e + raise web.HTTPError(400, 'Invalid JSON in body of request') from e return model def write_error(self, status_code, **kwargs): @@ -565,12 +565,12 @@ def write_error(self, status_code, **kwargs): message = exception.log_message % exception.args except Exception: pass - + # construct the custom reason, if defined reason = getattr(exception, 'reason', '') if reason: status_message = reason - + # build template namespace ns = dict( status_code=status_code, @@ -582,7 +582,7 @@ def write_error(self, status_code, **kwargs): self.set_header('Content-Type', 'text/html') # render the template try: - html = self.render_template('%s.html' % status_code, **ns) + html = self.render_template(f'{status_code}.html', **ns) except TemplateNotFound: html = self.render_template('error.html', **ns) @@ -741,15 +741,15 @@ def set_headers(self): # disable browser caching, rely on 304 replies for savings if "v" not in self.request.arguments: self.add_header("Cache-Control", "no-cache") - + def compute_etag(self): return None - + def validate_absolute_path(self, root, absolute_path): """Validate and return the absolute path. - + Requires tornado 3.1 - + Adding to tornado's own handling, forbids the serving of hidden files. """ abs_path = super().validate_absolute_path(root, absolute_path) @@ -762,12 +762,12 @@ def validate_absolute_path(self, root, absolute_path): def json_errors(method): """Decorate methods with this to return GitHub style JSON errors. - + This should be used on any JSON API on any handler method that can raise HTTPErrors. - + This will grab the latest HTTPError exception using sys.exc_info and then: - + 1. Set the HTTP status code based on the HTTPError 2. Create and return a JSON body with a message field describing the error in a human readable form. @@ -793,31 +793,31 @@ def wrapper(self, *args, **kwargs): class FileFindHandler(IPythonHandler, web.StaticFileHandler): """subclass of StaticFileHandler for serving files from a search path""" - + # cache search results, don't search for files more than once _static_paths = {} - + def set_headers(self): super().set_headers() # disable browser caching, rely on 304 replies for savings if "v" not in self.request.arguments or \ any(self.request.path.startswith(path) for path in self.no_cache_paths): self.set_header("Cache-Control", "no-cache") - + def initialize(self, path, default_filename=None, no_cache_paths=None): self.no_cache_paths = no_cache_paths or [] - + if isinstance(path, string_types): path = [path] - + self.root = tuple( os.path.abspath(os.path.expanduser(p)) + os.sep for p in path ) self.default_filename = default_filename - + def compute_etag(self): return None - + @classmethod def get_absolute_path(cls, roots, path): """locate a file to serve on our static file search path""" @@ -826,25 +826,25 @@ def get_absolute_path(cls, roots, path): return cls._static_paths[path] try: abspath = os.path.abspath(filefind(path, roots)) - except IOError: + except OSError: # IOError means not found return '' - + cls._static_paths[path] = abspath - + log().debug("Path %s served from %s"%(path, abspath)) return abspath - + def validate_absolute_path(self, root, absolute_path): """check if the file should be served (raises 404, 403, etc.)""" if absolute_path == '': raise web.HTTPError(404) - + for root in self.root: if (absolute_path + os.sep).startswith(root): break - + return super().validate_absolute_path(root, absolute_path) @@ -874,11 +874,11 @@ def get(self): class FilesRedirectHandler(IPythonHandler): """Handler for redirecting relative URLs to the /files/ handler""" - + @staticmethod def redirect_to_files(self, path): """make redirect logic a reusable static method - + so it can be called from other handlers. """ cm = self.contents_manager @@ -903,7 +903,7 @@ def redirect_to_files(self, path): url = url_path_join(self.base_url, 'files', url_escape(path)) self.log.debug("Redirecting %s to %s", self.request.path, url) self.redirect(url) - + def get(self, path=''): return self.redirect_to_files(self, path) diff --git a/notebook/base/zmqhandlers.py b/notebook/base/zmqhandlers.py index c4d4554a89..85937ef6ec 100644 --- a/notebook/base/zmqhandlers.py +++ b/notebook/base/zmqhandlers.py @@ -87,7 +87,7 @@ def deserialize_binary_message(bmsg): WS_PING_INTERVAL = 30000 -class WebSocketMixin(object): +class WebSocketMixin: """Mixin for common websocket options""" ping_callback = None last_ping = 0 @@ -167,7 +167,7 @@ def open(self, *args, **kwargs): self.send_ping, self.ping_interval, ) self.ping_callback.start() - return super(WebSocketMixin, self).open(*args, **kwargs) + return super().open(*args, **kwargs) def send_ping(self): """send a ping to keep the websocket alive""" @@ -249,7 +249,7 @@ def _on_zmq_reply(self, stream, msg_list): try: msg = self._reserialize_reply(msg_list, channel=channel) except Exception: - self.log.critical("Malformed message: %r" % msg_list, exc_info=True) + self.log.critical(f"Malformed message: {msg_list!r}", exc_info=True) return try: diff --git a/notebook/bundler/bundlerextensions.py b/notebook/bundler/bundlerextensions.py index 2ac346f971..2f218ebdec 100644 --- a/notebook/bundler/bundlerextensions.py +++ b/notebook/bundler/bundlerextensions.py @@ -17,14 +17,14 @@ def _get_bundler_metadata(module): """Gets the list of bundlers associated with a Python package. - + Returns a tuple of (the module, [{ 'name': 'unique name of the bundler', 'label': 'file menu item label for the bundler', 'module_name': 'dotted package/module name containing the bundler', 'group': 'download or deploy parent menu item' }]) - + Parameters ---------- @@ -34,16 +34,16 @@ def _get_bundler_metadata(module): """ m = import_item(module) if not hasattr(m, '_jupyter_bundlerextension_paths'): - raise KeyError('The Python module {} does not contain a valid bundlerextension'.format(module)) + raise KeyError(f'The Python module {module} does not contain a valid bundlerextension') bundlers = m._jupyter_bundlerextension_paths() return m, bundlers def _set_bundler_state(name, label, module_name, group, state, user=True, sys_prefix=False, logger=None): """Set whether a bundler is enabled or disabled. - + Returns True if the final state is the one requested. - + Parameters ---------- name : string @@ -68,14 +68,10 @@ def _set_bundler_state(name, label, module_name, group, state, config_dir = os.path.join( _get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig') cm = BaseJSONConfigManager(config_dir=config_dir) - + if logger: - logger.info("{} {} bundler {}...".format( - "Enabling" if state else "Disabling", - name, - module_name - )) - + logger.info(f"{'Enabling' if state else 'Disabling'} {name} bundler {module_name}...") + if state: cm.update(BUNDLER_SECTION, { BUNDLER_SUBSECTION: { @@ -96,13 +92,13 @@ def _set_bundler_state(name, label, module_name, group, state, return (cm.get(BUNDLER_SECTION) .get(BUNDLER_SUBSECTION, {}) .get(name) is not None) == state - + def _set_bundler_state_python(state, module, user, sys_prefix, logger=None): """Enables or disables bundlers defined in a Python package. - + Returns a list of whether the state was achieved for each bundler. - + Parameters ---------- state : Bool @@ -129,9 +125,9 @@ def _set_bundler_state_python(state, module, user, sys_prefix, logger=None): def enable_bundler_python(module, user=True, sys_prefix=False, logger=None): """Enables bundlers defined in a Python package. - + Returns whether each bundle defined in the packaged was enabled or not. - + Parameters ---------- module : str @@ -147,12 +143,12 @@ def enable_bundler_python(module, user=True, sys_prefix=False, logger=None): """ return _set_bundler_state_python(True, module, user, sys_prefix, logger=logger) - + def disable_bundler_python(module, user=True, sys_prefix=False, logger=None): """Disables bundlers defined in a Python package. - + Returns whether each bundle defined in the packaged was enabled or not. - + Parameters ---------- module : str @@ -176,13 +172,13 @@ class ToggleBundlerExtensionApp(BaseExtensionApp): description = "Enable/disable a bundlerextension in configuration." user = Bool(True, config=True, help="Apply the configuration only for the current user (default)") - + _toggle_value = None - + def _config_file_name_default(self): """The default config file name.""" return 'jupyter_notebook_config' - + def toggle_bundler_python(self, module): """Toggle some extensions in an importable Python module. @@ -210,25 +206,25 @@ def start(self): if self.python: self.toggle_bundler_python(self.extra_args[0]) else: - raise NotImplementedError('Cannot install bundlers from non-Python packages') + raise NotImplementedError('Cannot install bundlers from non-Python packages') class EnableBundlerExtensionApp(ToggleBundlerExtensionApp): """An App that enables bundlerextensions""" name = "jupyter bundlerextension enable" description = """ Enable a bundlerextension in frontend configuration. - + Usage jupyter bundlerextension enable [--system|--sys-prefix] """ _toggle_value = True - + class DisableBundlerExtensionApp(ToggleBundlerExtensionApp): """An App that disables bundlerextensions""" name = "jupyter bundlerextension disable" description = """ Disable a bundlerextension in frontend configuration. - + Usage jupyter bundlerextension disable [--system|--sys-prefix] """ @@ -240,15 +236,15 @@ class ListBundlerExtensionApp(BaseExtensionApp): name = "jupyter nbextension list" version = __version__ description = "List all nbextensions known by the configuration system" - + def list_nbextensions(self): """List all the nbextensions""" config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()] - + print("Known bundlerextensions:") - + for config_dir in config_dirs: - head = u' config dir: {}'.format(config_dir) + head = f' config dir: {config_dir}' head_shown = False cm = BaseJSONConfigManager(parent=self, config_dir=config_dir) @@ -258,18 +254,16 @@ def list_nbextensions(self): # only show heading if there is an nbextension here print(head) head_shown = True - + for bundler_id, info in data['bundlerextensions'].items(): label = info.get('label') module = info.get('module_name') if label is None or module is None: - msg = u' {} {}'.format(bundler_id, RED_DISABLED) + msg = f' {bundler_id} {RED_DISABLED}' else: - msg = u' "{}" from {} {}'.format( - label, module, GREEN_ENABLED - ) + msg = f' "{label}" from {module} {GREEN_ENABLED}' print(msg) - + def start(self): """Perform the App's functions as configured""" self.list_nbextensions() @@ -299,7 +293,7 @@ def start(self): # The above should have called a subcommand and raised NoStart; if we # get here, it didn't, so we should self.log.info a message. subcmds = ", ".join(sorted(self.subcommands)) - sys.exit("Please supply at least one subcommand: %s" % subcmds) + sys.exit(f"Please supply at least one subcommand: {subcmds}") main = BundlerExtensionApp.launch_instance diff --git a/notebook/bundler/handlers.py b/notebook/bundler/handlers.py index 868dd78324..213ea8aa84 100644 --- a/notebook/bundler/handlers.py +++ b/notebook/bundler/handlers.py @@ -60,8 +60,7 @@ def get(self, path): try: bundler = self.get_bundler(bundler_id) except KeyError as e: - raise web.HTTPError(400, 'Bundler %s not enabled' % - bundler_id) from e + raise web.HTTPError(400, f'Bundler {bundler_id} not enabled') from e module_name = bundler['module_name'] try: @@ -70,12 +69,11 @@ def get(self, path): except UnicodeEncodeError: # Encode unicode as utf-8 in python2 else import_item fails module_name = module_name.encode('utf-8') - + try: bundler_mod = import_item(module_name) except ImportError as e: - raise web.HTTPError(500, 'Could not import bundler %s ' % - bundler_id) from e + raise web.HTTPError(500, f'Could not import bundler {bundler_id} ') from e # Let the bundler respond in any way it sees fit and assume it will # finish the request diff --git a/notebook/bundler/tarball_bundler.py b/notebook/bundler/tarball_bundler.py index c112841482..74861e7a78 100644 --- a/notebook/bundler/tarball_bundler.py +++ b/notebook/bundler/tarball_bundler.py @@ -20,7 +20,7 @@ def _jupyter_bundlerextension_paths(): def bundle(handler, model): """Create a compressed tarball containing the notebook document. - + Parameters ---------- handler : tornado.web.RequestHandler @@ -31,8 +31,8 @@ def bundle(handler, model): notebook_filename = model['name'] notebook_content = nbformat.writes(model['content']).encode('utf-8') notebook_name = os.path.splitext(notebook_filename)[0] - tar_filename = '{}.tar.gz'.format(notebook_name) - + tar_filename = f'{notebook_name}.tar.gz' + info = tarfile.TarInfo(notebook_filename) info.size = len(notebook_content) diff --git a/notebook/bundler/tests/test_bundler_api.py b/notebook/bundler/tests/test_bundler_api.py index 6c251e5e99..0192160241 100644 --- a/notebook/bundler/tests/test_bundler_api.py +++ b/notebook/bundler/tests/test_bundler_api.py @@ -3,7 +3,6 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -import io from os.path import join as pjoin from notebook.tests.launchnotebook import NotebookTestBase @@ -14,7 +13,7 @@ from unittest.mock import patch - + def bundle(handler, model): """Bundler test stub. Echo the notebook path.""" handler.finish(model['path']) @@ -25,17 +24,17 @@ class BundleAPITest(NotebookTestBase): def setup_class(cls): """Make a test notebook. Borrowed from nbconvert test. Assumes the class teardown will clean it up in the end.""" - super(BundleAPITest, cls).setup_class() + super().setup_class() nbdir = cls.notebook_dir nb = new_notebook() - nb.cells.append(new_markdown_cell(u'Created by test')) - cc1 = new_code_cell(source=u'print(2*6)') - cc1.outputs.append(new_output(output_type="stream", text=u'12')) + nb.cells.append(new_markdown_cell('Created by test')) + cc1 = new_code_cell(source='print(2*6)') + cc1.outputs.append(new_output(output_type="stream", text='12')) nb.cells.append(cc1) - - with io.open(pjoin(nbdir, 'testnb.ipynb'), 'w', + + with open(pjoin(nbdir, 'testnb.ipynb'), 'w', encoding='utf-8') as f: write(nb, f, version=4) @@ -68,7 +67,7 @@ def test_bundler_import_error(self): mock.assert_called_with('fake_bundler') self.assertEqual(resp.status_code, 500) self.assertIn('Could not import bundler fake_bundler', resp.text) - + def test_bundler_invoke(self): """Should respond with 200 and output from test bundler stub""" with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock: diff --git a/notebook/config_manager.py b/notebook/config_manager.py index 137cbd691c..a8e4ee26b5 100644 --- a/notebook/config_manager.py +++ b/notebook/config_manager.py @@ -5,7 +5,6 @@ import errno import glob -import io import json import os import copy @@ -97,7 +96,7 @@ def get(self, section_name, include_root=True): data = {} for path in paths: if os.path.isfile(path): - with io.open(path, encoding='utf-8') as f: + with open(path, encoding='utf-8') as f: recursive_update(data, json.load(f)) return data @@ -117,7 +116,7 @@ def set(self, section_name, data): # in order to avoid writing half-finished corrupted data to disk. json_content = json.dumps(data, indent=2) - f = io.open(filename, 'w', encoding='utf-8') + f = open(filename, 'w', encoding='utf-8') with f: f.write(json_content) diff --git a/notebook/edit/handlers.py b/notebook/edit/handlers.py index 9ed9a9c380..2a22337adc 100644 --- a/notebook/edit/handlers.py +++ b/notebook/edit/handlers.py @@ -13,7 +13,7 @@ class EditorHandler(IPythonHandler): def get(self, path): path = path.strip('/') if not self.contents_manager.file_exists(path): - raise web.HTTPError(404, u'File does not exist: %s' % path) + raise web.HTTPError(404, f'File does not exist: {path}') basename = path.rsplit('/', 1)[-1] self.write(self.render_template('edit.html', @@ -24,5 +24,5 @@ def get(self, path): ) default_handlers = [ - (r"/edit%s" % path_regex, EditorHandler), -] \ No newline at end of file + (fr"/edit{path_regex}", EditorHandler), +] diff --git a/notebook/extensions.py b/notebook/extensions.py index 8efa65724b..882ca4cfb4 100644 --- a/notebook/extensions.py +++ b/notebook/extensions.py @@ -65,7 +65,11 @@ class BaseExtensionApp(JupyterApp): def _verbose_changed(self): """Warn about verbosity changes""" import warnings - warnings.warn("`verbose` traits of `{}` has been deprecated, has no effects and will be removed in notebook 5.0.".format(type(self).__name__), DeprecationWarning) + warnings.warn( + f"`verbose` traits of `{type(self).__name__}` has been deprecated, " + f"has no effects and will be removed in notebook 5.0.", + DeprecationWarning + ) def _log_format_default(self): """A default format for messages""" diff --git a/notebook/gateway/handlers.py b/notebook/gateway/handlers.py index d774ba39e2..56ade796d5 100644 --- a/notebook/gateway/handlers.py +++ b/notebook/gateway/handlers.py @@ -101,7 +101,7 @@ def write_message(self, message, binary=False): super().write_message(message, binary=binary) elif self.log.isEnabledFor(logging.DEBUG): msg_summary = WebSocketChannelsHandler._get_message_summary(json_decode(utf8(message))) - self.log.debug("Notebook client closed websocket connection - message dropped: {}".format(msg_summary)) + self.log.debug(f"Notebook client closed websocket connection - message dropped: {msg_summary}") def on_close(self): self.log.debug("Closing websocket connection %s", self.request.path) @@ -112,14 +112,16 @@ def on_close(self): def _get_message_summary(message): summary = [] message_type = message['msg_type'] - summary.append('type: {}'.format(message_type)) + summary.append(f'type: {message_type}') if message_type == 'status': - summary.append(', state: {}'.format(message['content']['execution_state'])) + summary.append(f', state: {message["content"]["execution_state"]}') elif message_type == 'error': - summary.append(', {}:{}:{}'.format(message['content']['ename'], - message['content']['evalue'], - message['content']['traceback'])) + summary.append( + f', {message["content"]["ename"]}:' + f'{message["content"]["evalue"]}:' + f'{message["content"]["traceback"]}' + ) else: summary.append(', ...') # don't display potentially sensitive data @@ -146,7 +148,7 @@ def _connect(self, kernel_id): GatewayClient.instance().ws_url, GatewayClient.instance().kernels_endpoint, url_escape(kernel_id), 'channels' ) - self.log.info('Connecting to {}'.format(ws_url)) + self.log.info(f'Connecting to {ws_url}') kwargs = {} kwargs = GatewayClient.instance().load_connection_args(**kwargs) @@ -158,7 +160,7 @@ def _connection_done(self, fut): if not self.disconnected and fut.exception() is None: # prevent concurrent.futures._base.CancelledError self.ws = fut.result() self.retry = 0 - self.log.debug("Connection is ready: ws: {}".format(self.ws)) + self.log.debug(f"Connection is ready: ws: {self.ws}") else: self.log.warning("Websocket connection has been closed via client disconnect or due to error. " "Kernel with ID '{}' may not be terminated on GatewayClient: {}". @@ -172,7 +174,7 @@ def _disconnect(self): elif not self.ws_future.done(): # Cancel pending connection. Since future.cancel() is a noop on tornado, we'll track cancellation locally self.ws_future.cancel() - self.log.debug("_disconnect: future cancelled, disconnected: {}".format(self.disconnected)) + self.log.debug(f"_disconnect: future cancelled, disconnected: {self.disconnected}") @gen.coroutine def _read_messages(self, callback): @@ -183,10 +185,10 @@ def _read_messages(self, callback): try: message = yield self.ws.read_message() except Exception as e: - self.log.error("Exception reading message from websocket: {}".format(e)) # , exc_info=True) + self.log.error(f"Exception reading message from websocket: {e}") # , exc_info=True) if message is None: if not self.disconnected: - self.log.warning("Lost connection to Gateway: {}".format(self.kernel_id)) + self.log.warning(f"Lost connection to Gateway: {self.kernel_id}") break callback(message) # pass back to notebook client (see self.on_open and WebSocketChannelsHandler.open) else: # ws cancelled - stop reading @@ -231,7 +233,7 @@ def _write_message(self, message): if not self.disconnected and self.ws is not None: self.ws.write_message(message) except Exception as e: - self.log.error("Exception writing message to websocket: {}".format(e)) # , exc_info=True) + self.log.error(f"Exception writing message to websocket: {e}") # , exc_info=True) def on_close(self): """Web socket closed event.""" @@ -247,8 +249,10 @@ def get(self, kernel_name, path, include_body=True): ksm = self.kernel_spec_manager kernel_spec_res = yield ksm.get_kernel_spec_resource(kernel_name, path) if kernel_spec_res is None: - self.log.warning("Kernelspec resource '{}' for '{}' not found. Gateway may not support" - " resource serving.".format(path, kernel_name)) + self.log.warning( + f"Kernelspec resource '{path}' for '{kernel_name}' not found. " + f"Gateway may not support resource serving." + ) else: self.set_header("Content-Type", mimetypes.guess_type(path)[0]) self.finish(kernel_spec_res) @@ -258,6 +262,6 @@ def get(self, kernel_name, path, include_body=True): from ..services.kernelspecs.handlers import kernel_name_regex default_handlers = [ - (r"/api/kernels/%s/channels" % _kernel_id_regex, WebSocketChannelsHandler), - (r"/kernelspecs/%s/(?P.*)" % kernel_name_regex, GatewayResourceHandler), + (fr"/api/kernels/{_kernel_id_regex}/channels", WebSocketChannelsHandler), + (fr"/kernelspecs/{kernel_name_regex}/(?P.*)", GatewayResourceHandler), ] diff --git a/notebook/gateway/managers.py b/notebook/gateway/managers.py index b2ae3b30a2..0ec2c41fb6 100644 --- a/notebook/gateway/managers.py +++ b/notebook/gateway/managers.py @@ -46,7 +46,7 @@ def _url_validate(self, proposal): # Ensure value, if present, starts with 'http' if value is not None and len(value) > 0: if not str(value).lower().startswith('http'): - raise TraitError("GatewayClient url must start with 'http': '%r'" % value) + raise TraitError(f"GatewayClient url must start with 'http': '{value!r}'") return value ws_url = Unicode(default_value=None, allow_none=True, config=True, @@ -71,7 +71,7 @@ def _ws_url_validate(self, proposal): # Ensure value, if present, starts with 'ws' if value is not None and len(value) > 0: if not str(value).lower().startswith('ws'): - raise TraitError("GatewayClient ws_url must start with 'ws': '%r'" % value) + raise TraitError(f"GatewayClient ws_url must start with 'ws': '{value!r}'") return value kernels_endpoint_default_value = '/api/kernels' @@ -276,7 +276,7 @@ def init_static_args(self): self._static_args['headers'] = json.loads(self.headers) if 'Authorization' not in self._static_args['headers'].keys(): self._static_args['headers'].update({ - 'Authorization': 'token {}'.format(self.auth_token) + 'Authorization': f'token {self.auth_token}' }) self._static_args['connect_timeout'] = self.connect_timeout self._static_args['request_timeout'] = self.request_timeout @@ -322,19 +322,22 @@ async def gateway_request(endpoint, **kwargs): except ConnectionRefusedError as e: raise web.HTTPError( 503, - "Connection refused from Gateway server url '{}'. Check to be sure the" - " Gateway instance is running.".format(GatewayClient.instance().url) + f"Connection refused from Gateway server url '{GatewayClient.instance().url}'. " + f"Check to be sure the Gateway instance is running." ) from e except HTTPError as e: # This can occur if the host is valid (e.g., foo.com) but there's nothing there. - raise web.HTTPError(e.code, "Error attempting to connect to Gateway server url '{}'. " - "Ensure gateway url is valid and the Gateway instance is running.". - format(GatewayClient.instance().url)) from e + raise web.HTTPError( + e.code, + f"Error attempting to connect to Gateway server url '{GatewayClient.instance().url}'. " + f"Ensure gateway url is valid and the Gateway instance is running." + ) from e except gaierror as e: raise web.HTTPError( 404, - "The Gateway server specified in the gateway_url '{}' doesn't appear to be valid. Ensure gateway " - "url is valid and the Gateway instance is running.".format(GatewayClient.instance().url) + f"The Gateway server specified in the gateway_url '{GatewayClient.instance().url}' " + f"doesn't appear to be valid. " + f"Ensure gateway url is valid and the Gateway instance is running." ) from e return response @@ -392,7 +395,7 @@ async def start_kernel(self, kernel_id=None, path=None, **kwargs): kwargs['cwd'] = self.cwd_for_path(path) kernel_name = kwargs.get('kernel_name', 'python3') kernel_url = self._get_kernel_endpoint_url() - self.log.debug("Request new kernel at: %s" % kernel_url) + self.log.debug(f"Request new kernel at: {kernel_url}") # Let KERNEL_USERNAME take precedent over http_user config option. if os.environ.get('KERNEL_USERNAME') is None and GatewayClient.instance().http_user: @@ -412,12 +415,12 @@ async def start_kernel(self, kernel_id=None, path=None, **kwargs): ) kernel = json_decode(response.body) kernel_id = kernel['id'] - self.log.info("Kernel started: %s" % kernel_id) - self.log.debug("Kernel args: %r" % kwargs) + self.log.info(f"Kernel started: {kernel_id}") + self.log.debug(f"Kernel args: {kwargs!r}") else: kernel = await self.get_kernel(kernel_id) kernel_id = kernel['id'] - self.log.info("Using existing kernel: %s" % kernel_id) + self.log.info(f"Using existing kernel: {kernel_id}") self._kernels[kernel_id] = kernel return kernel_id @@ -431,12 +434,12 @@ async def get_kernel(self, kernel_id=None, **kwargs): The uuid of the kernel. """ kernel_url = self._get_kernel_endpoint_url(kernel_id) - self.log.debug("Request kernel at: %s" % kernel_url) + self.log.debug(f"Request kernel at: {kernel_url}") try: response = await gateway_request(kernel_url, method='GET') except web.HTTPError as error: if error.status_code == 404: - self.log.warn("Kernel not found at: %s" % kernel_url) + self.log.warn(f"Kernel not found at: {kernel_url}") self.remove_kernel(kernel_id) kernel = None else: @@ -611,7 +614,7 @@ async def get_kernel_spec(self, kernel_name, **kwargs): The name of the kernel. """ kernel_spec_url = self._get_kernelspecs_endpoint_url(kernel_name=str(kernel_name)) - self.log.debug("Request kernel spec at: %s" % kernel_spec_url) + self.log.debug(f"Request kernel spec at: {kernel_spec_url}") try: response = await gateway_request(kernel_spec_url, method='GET') except web.HTTPError as error: @@ -640,7 +643,7 @@ async def get_kernel_spec_resource(self, kernel_name, path): The name of the desired resource """ kernel_spec_resource_url = url_path_join(self.base_resource_endpoint, str(kernel_name), str(path)) - self.log.debug("Request kernel spec resource '{}' at: {}".format(path, kernel_spec_resource_url)) + self.log.debug(f"Request kernel spec resource '{path}' at: {kernel_spec_resource_url}") try: response = await gateway_request(kernel_spec_resource_url, method='GET') except web.HTTPError as error: diff --git a/notebook/i18n/__init__.py b/notebook/i18n/__init__.py index 83f65c5fd9..0f836d2eeb 100644 --- a/notebook/i18n/__init__.py +++ b/notebook/i18n/__init__.py @@ -2,7 +2,6 @@ """ from collections import defaultdict import errno -import io import json from os.path import dirname, join as pjoin import re @@ -58,9 +57,9 @@ def parse_accept_lang_header(accept_lang): def load(language, domain='nbjs'): """Load translations from an nbjs.json file""" try: - f = io.open(pjoin(I18N_DIR, language, 'LC_MESSAGES', 'nbjs.json'), + f = open(pjoin(I18N_DIR, language, 'LC_MESSAGES', 'nbjs.json'), encoding='utf-8') - except IOError as e: + except OSError as e: if e.errno != errno.ENOENT: raise return {} diff --git a/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.json b/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.json new file mode 100644 index 0000000000..4442834bfe --- /dev/null +++ b/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.json @@ -0,0 +1,1355 @@ +{ + "domain": "nbjs", + "locale_data": { + "nbjs": { + "": { + "domain": "nbjs", + "plural_forms": "nplurals=2; plural=(n > 1);", + "lang": "fr_FR" + }, + "Manually edit the JSON below to manipulate the metadata for this cell.": [ + "Éditer manuellement le JSON ci-dessous pour manipuler les méta-données de cette cellule" + ], + "Manually edit the JSON below to manipulate the metadata for this notebook.": [ + "Éditer manuellement le JSON ci-dessous pour manipuler les méta-données de ce notebook." + ], + " We recommend putting custom metadata attributes in an appropriately named substructure, so they don't conflict with those of others.": [ + " Il est recommandé de placer les attributs personnalisés de méta-données dans une sous-structure nommée de manière appropriée, afin qu'ils n'interfèrent pas avec ceux des autres." + ], + "Edit the metadata": [ + "Éditer les méta-données" + ], + "Edit Notebook Metadata": [ + "Éditer les méta-données du Notebook" + ], + "Edit Cell Metadata": [ + "Éditer les méta-données de la cellule" + ], + "Cancel": [ + "Annuler" + ], + "Edit": [ + "Éditer" + ], + "OK": [ + "OK" + ], + "Apply": [ + "Appliquer" + ], + "WARNING: Could not save invalid JSON.": [ + "ATTENTION: Impossible de sauvegarder du JSON invalide." + ], + "There are no attachments for this cell.": [ + "Il n'y a pas de pièce-jointe à cette cellule." + ], + "Current cell attachments": [ + "Pièce-jointes actuelles de la cellule" + ], + "Attachments": [ + "Pièces jointes" + ], + "Restore": [ + "Restaurer" + ], + "Delete": [ + "Supprimer" + ], + "Edit attachments": [ + "Modifier les pièces jointes" + ], + "Edit Notebook Attachments": [ + "Modifier les pièces jointes du Notebook" + ], + "Edit Cell Attachments": [ + "Modifier les pièces jointes de la cellule" + ], + "Select a file to insert.": [ + "Sélectionner un fichier à insérer." + ], + "Select a file": [ + "Sélectionner un fichier" + ], + "You are using Jupyter notebook.": [ + "Vous utilisez un notebook Jupyter." + ], + "The version of the notebook server is: ": [ + "La version du serveur de notebook est : " + ], + "The server is running on this version of Python:": [ + "Le serveur utilise la version de Python :" + ], + "Waiting for kernel to be available...": [ + "En attente de disponibilité du noyau..." + ], + "Server Information:": [ + "Information du serveur :" + ], + "Current Kernel Information:": [ + "Information du Noyau courant :" + ], + "Could not access sys_info variable for version information.": [ + "Impossible d'accéder à la variable sys_info pour l'information relative aux versions." + ], + "Cannot find sys_info!": [ + "Impossible de trouver sys_info !" + ], + "About Jupyter Notebook": [ + "À propos de Jupyter Notebook" + ], + "unable to contact kernel": [ + "impossible de joindre le noyau" + ], + "toggle rtl layout": [ + "Changer le sens d'organisation de l'interface" + ], + "Toggle the screen directionality between left-to-right and right-to-left": [ + "Changer le sens d'organisation des éléments d'interface entre gauche-à-droite et droite-à-gauche" + ], + "edit command mode keyboard shortcuts": [ + "modifier les raccourcis clavier du mode commande" + ], + "Open a dialog to edit the command mode keyboard shortcuts": [ + "Ouvrir un dialogue pour éditer les racourcis clavier du mode commande" + ], + "restart kernel": [ + "redémarrer le noyau" + ], + "restart the kernel (no confirmation dialog)": [ + "redémarrer le noyau (sans confirmation)" + ], + "confirm restart kernel": [ + "confirmer le redémarrage du noyau" + ], + "restart the kernel (with dialog)": [ + "redémarrer le noyau (avec confirmation)" + ], + "restart kernel and run all cells": [ + "redémarrer le noyau et exécuter toutes les cellules" + ], + "restart the kernel, then re-run the whole notebook (no confirmation dialog)": [ + "redémarrer le noyau, et ré-exécuter tout le notebook (sans confirmation)" + ], + "confirm restart kernel and run all cells": [ + "confirmer le redémarrage du noyau et l'exécution des cellules" + ], + "restart the kernel, then re-run the whole notebook (with dialog)": [ + "redémarrer le noyau, et ré-exécuter tout le notebook (sans confirmation)" + ], + "restart kernel and clear output": [ + "redémarrer le noyau, et effacer les sorties" + ], + "restart the kernel and clear all output (no confirmation dialog)": [ + "redémarrer le noyau et effacer les sorties (sans confirmation)" + ], + "confirm restart kernel and clear output": [ + "confirmer le redémarrage du noyau et l'effacement des sorties" + ], + "restart the kernel and clear all output (with dialog)": [ + "redémarrer le noyau et effacer les sorties (avec confirmation)" + ], + "interrupt the kernel": [ + "interrompre le noyau" + ], + "run cell and select next": [ + "exécuter la cellule et sélectionner la suivante" + ], + "run cell, select below": [ + "exécuter la cellule, sélectionner la suivante" + ], + "run selected cells": [ + "exécuter les cellules sélectionnées" + ], + "run cell and insert below": [ + "exécuter la cellule et insérer après" + ], + "run all cells": [ + "exécuter toutes les cellules" + ], + "run all cells above": [ + "exécuter toutes les cellules précédentes" + ], + "run all cells below": [ + "Exécuter toutes les cellules suivantes" + ], + "enter command mode": [ + "ouvrir le mode commande" + ], + "insert image": [ + "insérer une image" + ], + "cut cell attachments": [ + "couper les pièces-jointes de la cellule" + ], + "copy cell attachments": [ + "copier les pièces-jointes de la cellule" + ], + "paste cell attachments": [ + "coller les pièces-jointes de la cellule" + ], + "split cell at cursor": [ + "séparer la cellule au niveau du curseur" + ], + "enter edit mode": [ + "activer le mode d'édition" + ], + "select previous cell": [ + "sélectionner la cellule précédente" + ], + "select cell above": [ + "sélectionner la cellule précédente" + ], + "select next cell": [ + "sélectionner la cellule suivante" + ], + "select cell below": [ + "sélectionner la cellule suivante" + ], + "extend selection above": [ + "étendre la sélection vers le haut" + ], + "extend selected cells above": [ + "étendre les cellules sélectionnées vers le haut" + ], + "extend selection below": [ + "étendre la sélection vers le bas" + ], + "extend selected cells below": [ + "étendre les cellules sélectionnées vers le bas" + ], + "cut selected cells": [ + "couper les cellules sélectionnées" + ], + "copy selected cells": [ + "copier les cellules sélectionnées" + ], + "paste cells above": [ + "coller les cellules avant" + ], + "paste cells below": [ + "coller les cellules après" + ], + "insert cell above": [ + "insérer une cellule avant" + ], + "insert cell below": [ + "insérer une cellule après" + ], + "change cell to code": [ + "transformer en cellule de code" + ], + "change cell to markdown": [ + "transformer celllule en markdown" + ], + "change cell to raw": [ + "transformer en texte brut (pour NBConvert)" + ], + "change cell to heading 1": [ + "transformer en titre de niveau 1" + ], + "change cell to heading 2": [ + "transformer en titre de niveau 2" + ], + "change cell to heading 3": [ + "transformer en titre de niveau 3" + ], + "change cell to heading 4": [ + "transformer en titre de niveau 4" + ], + "change cell to heading 5": [ + "transformer en titre de niveau 5" + ], + "change cell to heading 6": [ + "transformer en titre de niveau 6" + ], + "toggle cell output": [ + "afficher/masquer la sortie de cellule" + ], + "toggle output of selected cells": [ + "afficher/masquer la sortie des cellules sélectionnées" + ], + "toggle cell scrolling": [ + "afficher/masquer la barre de défilement de la sortie de la cellule" + ], + "toggle output scrolling of selected cells": [ + "afficher/masquer la barre de défilement des la sortie des cellules sélectionnées" + ], + "clear cell output": [ + "effacer la sortie de la cellule" + ], + "clear output of selected cells": [ + "effacer la sortie des cellules sélectionnées" + ], + "move cells down": [ + "déplacer les cellules vers le bas" + ], + "move selected cells down": [ + "déplacer les cellules sélectionnées vers le bas" + ], + "move cells up": [ + "déplacer les cellules vers le haut" + ], + "move selected cells up": [ + "déplacer les cellules sélectionnées vers le haut" + ], + "toggle line numbers": [ + "afficher/masquer les numéros de ligne" + ], + "show keyboard shortcuts": [ + "afficher les raccourcis clavier" + ], + "delete cells": [ + "supprimer les cellules" + ], + "delete selected cells": [ + "supprimer les cellules sélectionnées" + ], + "undo cell deletion": [ + "annuler la suppression de cellule" + ], + "merge cell with previous cell": [ + "fusionner la cellule avec la précédente" + ], + "merge cell above": [ + "fusionner la cellule avec les précédentes" + ], + "merge cell with next cell": [ + "fusionner la cellule avec la suivante" + ], + "merge cell below": [ + "fusionner la cellule avec les suivantes" + ], + "merge selected cells": [ + "fusionnnner les cellules sélectionnées" + ], + "merge cells": [ + "fusionnnner les cellules" + ], + "merge selected cells, or current cell with cell below if only one cell is selected": [ + "fusionner les cellules sélectionnées, ou la cellule courante avec la suivante si une unique cellule est sélectionnée" + ], + "show command pallette": [ + "afficher la palette de commandes" + ], + "open the command palette": [ + "ouvrir la palette de commandes" + ], + "toggle all line numbers": [ + "afficher/masquer tous les numéros de ligne" + ], + "toggles line numbers in all cells, and persist the setting": [ + "afficher/masquer les numéros de ligne dans toutes ces cellules, et s'en souvenir dans les paramètres" + ], + "show all line numbers": [ + "afficher/masquer tous les numéros de ligne" + ], + "show line numbers in all cells, and persist the setting": [ + "afficher les numéros de ligne dans toutes les cellules, et s'en souvenir dans les paramètres" + ], + "hide all line numbers": [ + "masquer tous les numéros de ligne" + ], + "hide line numbers in all cells, and persist the setting": [ + "masquer les numéros de ligne dans toutes les cellules, et s'en souvenir dans les paramètres" + ], + "toggle header": [ + "afficher/masquer l'en-tête" + ], + "switch between showing and hiding the header": [ + "afficher/masquer l'en-tête" + ], + "show the header": [ + "afficher l'en-tête" + ], + "hide the header": [ + "masquer l'en-tête" + ], + "toggle toolbar": [ + "afficher/masquer la barre d'outils" + ], + "switch between showing and hiding the toolbar": [ + "afficher/masquer la barre d'outils" + ], + "show the toolbar": [ + "afficher la barre d'outils" + ], + "hide the toolbar": [ + "masquer la barre d'outils" + ], + "close the pager": [ + "fermer le paginateur" + ], + "ignore": [ + "ignorer" + ], + "move cursor up": [ + "déplacer le curseur vers le haut" + ], + "move cursor down": [ + "déplacer le curseur vers le bas" + ], + "scroll notebook down": [ + "faire défiler le notebook vers le bas" + ], + "scroll notebook up": [ + "faire défiler le notebook vers le haut" + ], + "scroll cell center": [ + "faire défiler la cellule courante au centre" + ], + "Scroll the current cell to the center": [ + "Faire défiler la cellule courante au centre" + ], + "scroll cell top": [ + "faire défiler la cellule en haut" + ], + "Scroll the current cell to the top": [ + "Faire défiler la cellule courante en haut" + ], + "duplicate notebook": [ + "dupliquer le notebook" + ], + "Create and open a copy of the current notebook": [ + "Créer et ouvrir une copie du notebook courant" + ], + "trust notebook": [ + "faire confiance à ce notebook" + ], + "Trust the current notebook": [ + "Faire confiance au notebook courant" + ], + "rename notebook": [ + "renommer le notebook" + ], + "Rename the current notebook": [ + "Renommer le notebook courant" + ], + "toggle all cells output collapsed": [ + "afficher/masquer toutes les sorties en mode compact" + ], + "Toggle the hidden state of all output areas": [ + "Afficher/masquer toutes les sorties" + ], + "toggle all cells output scrolled": [ + "afficher/masquer toutes les sorties en mode défilement" + ], + "Toggle the scrolling state of all output areas": [ + "Activer/désactiver le défilement de toutes les sorties" + ], + "clear all cells output": [ + "effacer le contenu de toutes les sorties de cellules" + ], + "Clear the content of all the outputs": [ + "Effacer le contenu de toutes les sorties" + ], + "save notebook": [ + "enregistrer le notebook" + ], + "Save and Checkpoint": [ + "Créer une nouvelle sauvegarde" + ], + "Warning: accessing Cell.cm_config directly is deprecated.": [ + "Attention : accéder à Cell.cm_config directement est déprécié." + ], + "Unrecognized cell type: %s": [ + "Type de cellule non reconnu : %s" + ], + "Unrecognized cell type": [ + "Type de cellule non reconnu" + ], + "Error in cell toolbar callback %s": [ + "Erreur dans le callback %s de la barre d'outil de cellule" + ], + "Clipboard types: %s": [ + "Types de données dans le presse-papier : %s" + ], + "Dialog for paste from system clipboard": [ + "Boîte de dialogue pour coller depuis le presse-papier du système" + ], + "Ctrl-V": [ + "Ctrl-V" + ], + "Cmd-V": [ + "Cmd-V" + ], + "Press %s again to paste": [ + "Appuyer sur %s à nouveau pour coller" + ], + "Why is this needed? ": [ + "Pourquoi ce comportement ?" + ], + "We can't get paste events in this browser without a text box. ": [ + "Il n'est pas possible de capturer les évènements « coller » dans le navigateur sans champ de texte" + ], + "There's an invisible text box focused in this dialog.": [ + "Cette boîte de dialogue contient un champ de texte invisible." + ], + "%s to paste": [ + "%s pour coller" + ], + "Can't execute cell since kernel is not set.": [ + "Impossible d'exécuter cette cellule car aucun noyau n'est choisi." + ], + "In": [ + "Entrée" + ], + "Could not find a kernel matching %s. Please select a kernel:": [ + "Impossible de trouver un noyau correspondant à %s. Merci de sélectionner un noyau :" + ], + "Continue Without Kernel": [ + "Poursuivre sans noyau" + ], + "Set Kernel": [ + "Choisir le noyau" + ], + "Kernel not found": [ + "Noyau introuvable" + ], + "Creating Notebook Failed": [ + "La création du notebook a échoué" + ], + "The error was: %s": [ + "L'erreur est : %s" + ], + "Run": [ + "Exécuter" + ], + "Code": [ + "Code" + ], + "Markdown": [ + "Markdown" + ], + "Raw NBConvert": [ + "Texte Brut (pour NBConvert)" + ], + "Heading": [ + "Titre" + ], + "unrecognized cell type:": [ + "type de cellule non reconnu :" + ], + "Failed to retrieve MathJax from '%s'": [ + "Impossible de récupérer MathJax depuis '%s'" + ], + "Math/LaTeX rendering will be disabled.": [ + "Le rendu Math/LaTex sera désactivé." + ], + "Trusted Notebook": [ + "Notebook de confiance" + ], + "Trust Notebook": [ + "Faire confiance à ce Notebook" + ], + "None": [ + "Aucun(e)" + ], + "No checkpoints": [ + "Pas de point de sauvegarde" + ], + "Opens in a new window": [ + "Ouvrir dans une nouvelle fenêtre" + ], + "Autosave in progress, latest changes may be lost.": [ + "Auto-sauvegarde en cours, les dernières modifications pourraient être perdues." + ], + "Unsaved changes will be lost.": [ + "Les modifications non sauvegardées seront perdues." + ], + "The Kernel is busy, outputs may be lost.": [ + "Le noyau est occupé, les sorties pourraient être perdues." + ], + "This notebook is version %1$s, but we only fully support up to %2$s.": [ + "Ce notebook est prévu pour la version %1$s du logiciel, mais nous supportons au maximum la version %2$s." + ], + "You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available.": [ + "Vous pouvez continuer à travailler avec ce notebook, mais les types de cellules et de sorties introduits dans les versions ultérieures du logiciel ne seront pas disponibles." + ], + "Restart and Run All Cells": [ + "Relancer et exécuter toutes les cellules" + ], + "Restart and Clear All Outputs": [ + "Relancer et effacer toutes les sorties" + ], + "Restart": [ + "Relancer" + ], + "Continue Running": [ + "Poursuivre l'exécution" + ], + "Reload": [ + "Recharger" + ], + "Overwrite": [ + "Écraser" + ], + "Trust": [ + "Faire confiance" + ], + "Revert": [ + "Rétablir" + ], + "Newer Notebook": [ + "Notebook plus récent" + ], + "Use markdown headings": [ + "Utiliser les titres markdown" + ], + "Jupyter no longer uses special heading cells. Instead, write your headings in Markdown cells using # characters:": [ + "Jupyter n'utilise plus de cellules spéciales pour les titres. À la place, utiliser la syntaxe de titre dans des cellules Markdown avec les caractères # :" + ], + "## This is a level 2 heading": [ + "## Ceci est un titre de niveau 2" + ], + "Restart kernel and re-run the whole notebook?": [ + "Redémarrer le noyau et ré-exécuter l'ensemble du noteboook ?" + ], + "Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost.": [ + "Êtes-vous certain de vouloir redémarrer le noyau actuel et ré-exécuter l'ensemble du notebook ? Toutes les variables et sorties seront perdues." + ], + "Restart kernel and clear all output?": [ + "Redémarrer le noyau et effacer toutes les sorties ?" + ], + "Do you want to restart the current kernel and clear all output? All variables and outputs will be lost.": [ + "Souhaitez-vous redémarrer le noyau actuel et effacer toutes les sorties ? Toutes les variables et les sorties seront perdues." + ], + "Restart kernel?": [ + "Redémarrer le noyau ?" + ], + "Do you want to restart the current kernel? All variables will be lost.": [ + "Souhaitez-vous redémarrer le noyau actuel ? Toutes les variables seront perdues." + ], + "Shutdown kernel?": [ + "Arrêter le noyau ?" + ], + "Do you want to shutdown the current kernel? All variables will be lost.": [ + "Souhaitez-vous redémarrer le noyau actuel ? Toutes les variables seront perdues." + ], + "Notebook changed": [ + "Notebook modifié" + ], + "The notebook file has changed on disk since the last time we opened or saved it. Do you want to overwrite the file on disk with the version open here, or load the version on disk (reload the page) ?": [ + "Le fichier du notebook a changé sur le disque depuis que vous l'avez ouvert ou sauvegardé. Souhaitez-vous écraser le fichier sur le disque avec la version ouverte ici ou charger la version présente sur le disque (recharge la page) ?" + ], + "Notebook validation failed": [ + "La validation du notebook a échoué" + ], + "The save operation succeeded, but the notebook does not appear to be valid. The validation error was:": [ + "La sauvegarde a réussi, mais le notebook semble invalide. L'erreur de validation est :" + ], + "A trusted Jupyter notebook may execute hidden malicious code when you open it. Selecting trust will immediately reload this notebook in a trusted state. For more information, see the Jupyter security documentation: ": [ + "Un notebook Jupyter auquel vous faites confiance peut exécuter du code malicieux quand vous l'ouvrez. Choisir de faire confiance à ce notebook va le recharger immédiatement en mode confiance. Pour davantage d'information, voir la section sécurité dans la documentation de Jupyter." + ], + "here": [ + "ici" + ], + "Trust this notebook?": [ + "Faire confiance à ce notebook ?" + ], + "Notebook failed to load": [ + "Le chargement du notebook a échoué" + ], + "The error was: ": [ + "L'erreur est : " + ], + "See the error console for details.": [ + "Voir la console d'erreur pour davantage de détails." + ], + "The notebook also failed validation:": [ + "La validation du notebook a échoué :" + ], + "An invalid notebook may not function properly. The validation error was:": [ + "Un notebook non valide peut dysfonctionner. L'erreur de validation est :" + ], + "This notebook has been converted from an older notebook format to the current notebook format v(%s).": [ + "Ce notebook a été converti depuis un format plus ancien de notebook vers le format actuel v(%s)." + ], + "This notebook has been converted from a newer notebook format to the current notebook format v(%s).": [ + "Ce notebook a été converti depuis un format plus récent de notebook vers le format actuel v(%s)." + ], + "The next time you save this notebook, the current notebook format will be used.": [ + "Au prochain enregistrement de ce notebook, le format actuel de notebook sera utilisé" + ], + "Older versions of Jupyter may not be able to read the new format.": [ + "D'anciennes version de Jupyter peuvent ne pas être en mesure de lire le nouveau format." + ], + "Some features of the original notebook may not be available.": [ + "Certaines fonctionalités du notebook d'origine peuvent ne pas être disponibles." + ], + "To preserve the original version, close the notebook without saving it.": [ + "Pour préserver la version originale, fermer le notebook sans l'enregistrer." + ], + "Notebook converted": [ + "Notebook converti" + ], + "(No name)": [ + "(Sans nom)" + ], + "An unknown error occurred while loading this notebook. This version can load notebook formats %s or earlier. See the server log for details.": [ + "Une erreur inconnue s'est produite pendant le chargement de ce notebook. Cette version peut charger des formats de notebooks %s ou plus ancien. Voir les journaux du serveur pour davantage d'information." + ], + "Error loading notebook": [ + "Erreur pendant le chargement du notebook" + ], + "Are you sure you want to revert the notebook to the latest checkpoint?": [ + "Êtes-vous certain de vouloir restaurer la dernière sauvegarde ?" + ], + "This cannot be undone.": [ + "Impossible d'annuler." + ], + "The checkpoint was last updated at:": [ + "Dernière sauvegarde à : " + ], + "Revert notebook to checkpoint": [ + "Restaurer le notebook à une sauvegarde antérieure" + ], + "Edit Mode": [ + "Mode Édition" + ], + "Command Mode": [ + "Mode Commande" + ], + "Kernel Created": [ + "Noyau créé" + ], + "Connecting to kernel": [ + "Connexion au noyau" + ], + "Not Connected": [ + "Non connecté" + ], + "click to reconnect": [ + "cliquer pour reconnecter" + ], + "Restarting kernel": [ + "Noyau en cours de redémarrage" + ], + "Kernel Restarting": [ + "Noyau en cours de redémarrage" + ], + "The kernel appears to have died. It will restart automatically.": [ + "Le noyau semble planté. Il va redémarrer automatiquement." + ], + "Dead kernel": [ + "Noyau planté" + ], + "Kernel Dead": [ + "Noyau planté" + ], + "Interrupting kernel": [ + "Noyau en cours d'interruption" + ], + "No Connection to Kernel": [ + "Pas de connexion au noyau" + ], + "A connection to the notebook server could not be established. The notebook will continue trying to reconnect. Check your network connection or notebook server configuration.": [ + "La connexion au serveur de notebook ne peut pas être établie. Le notebook va continuer ses tentatives. Vérifiez votre connexion réseau ou les paramètres du serveur de notebook." + ], + "Connection failed": [ + "Échec de la connexion" + ], + "No kernel": [ + "Pas de noyau" + ], + "Kernel is not running": [ + "Le noyau n'est pas actif" + ], + "Don't Restart": [ + "Ne pas redémarrer" + ], + "Try Restarting Now": [ + "Essayer de redémarrer maintenant" + ], + "The kernel has died, and the automatic restart has failed. It is possible the kernel cannot be restarted. If you are not able to restart the kernel, you will still be able to save the notebook, but running code will no longer work until the notebook is reopened.": [ + "Le noyau a planté, et le redémarrage automatique a échoué. Il est possible que le noyau ne puisse pas être relancé. Si c'est le cas, vous pourrez toujours sauvegarder le notebook, mais l'exécution de code ne fonctionnera pas jusqu'à la ré-ouverture du notebook." + ], + "No Kernel": [ + "Pas de Noyau" + ], + "Failed to start the kernel": [ + "Échec du démarrage du noyau" + ], + "Kernel Busy": [ + "Noyau occupé" + ], + "Kernel starting, please wait...": [ + "Noyau en cours de démarrage, patientez…" + ], + "Kernel Idle": [ + "Noyau inactif" + ], + "Kernel ready": [ + "Noyau prêt" + ], + "Using kernel: ": [ + "Noyau utilisé : " + ], + "Only candidate for language: %1$s was %2$s.": [ + "Unique candidat pour le langage : %1$s était %2$s." + ], + "Loading notebook": [ + "Chargement du notebook en cours" + ], + "Notebook loaded": [ + "Notebook chargé" + ], + "Saving notebook": [ + "Enregistrement du notebook en cours" + ], + "Notebook saved": [ + "Notebook enregistré" + ], + "Notebook save failed": [ + "L'enregistrement du notebook a échoué" + ], + "Notebook copy failed": [ + "La copie du notebook a échoué" + ], + "Checkpoint created": [ + "Sauvegarde créée" + ], + "Checkpoint failed": [ + "Échec de la sauvegarde" + ], + "Checkpoint deleted": [ + "Sauvegarde supprimée" + ], + "Checkpoint delete failed": [ + "Échec de la suppression de la sauvegarde" + ], + "Restoring to checkpoint...": [ + "Restauration de la sauvegarde..." + ], + "Checkpoint restore failed": [ + "La restauration de la sauvegarde a échoué" + ], + "Autosave disabled": [ + "Sauvegarde automatique désactivée" + ], + "Saving every %d sec.": [ + "Sauvegarde toutes les %d sec." + ], + "Trusted": [ + "Fiable" + ], + "Not Trusted": [ + "Non fiable" + ], + "click to expand output": [ + "cliquer pour afficher toute la sortie" + ], + "click to expand output; double click to hide output": [ + "cliquer pour afficher toute la sortie ; double-cliquer pour masquer la sortie" + ], + "click to unscroll output; double click to hide": [ + "cliquer pour faire défiler la sortie vers le haut ; double-cliquer pour masquer" + ], + "click to scroll output; double click to hide": [ + "cliquer pour faire défiler la sortie ; double-cliquer pour masquer" + ], + "Javascript error adding output!": [ + "Erreur JavaScript pendant l'écriture de la sortie !" + ], + "See your browser Javascript console for more details.": [ + "Voir la console JavaScript de votre navigateur pour plus d'informations." + ], + "Out[%d]:": [ + "Sortie[%d] :" + ], + "Unrecognized output: %s": [ + "Sortie non reconnue : %s" + ], + "Open the pager in an external window": [ + "Ouvrir le paginateur dans une fenêtre externe" + ], + "Close the pager": [ + "Fermer le paginateur" + ], + "Jupyter Pager": [ + "Paginateur de Jupyter" + ], + "go to cell start": [ + "aller au début de la cellule" + ], + "go to cell end": [ + "aller à la fin de la cellule" + ], + "go one word left": [ + "se déplacer d'un mot vers la gauche" + ], + "go one word right": [ + "Se déplacer d'un mot vers la droite" + ], + "delete word before": [ + "supprimer le mot précédent" + ], + "delete word after": [ + "supprimer le mot suivant" + ], + "code completion or indent": [ + "complétion de code ou indentation" + ], + "tooltip": [ + "info-bulle" + ], + "indent": [ + "indenter" + ], + "dedent": [ + "dé-indenter" + ], + "select all": [ + "tout sélectionner" + ], + "undo": [ + "annuler" + ], + "redo": [ + "refaire" + ], + "Shift": [ + "Maj" + ], + "Alt": [ + "Alt" + ], + "Up": [ + "Haut" + ], + "Down": [ + "Bas" + ], + "Left": [ + "Gauche" + ], + "Right": [ + "Droite" + ], + "Tab": [ + "Tab" + ], + "Caps Lock": [ + "Verr. Maj." + ], + "Esc": [ + "Esc" + ], + "Ctrl": [ + "Ctrl" + ], + "Enter": [ + "Entrée" + ], + "Page Up": [ + "Page Préc." + ], + "Page Down": [ + "Page Suiv." + ], + "Home": [ + "Accueil" + ], + "End": [ + "Fin" + ], + "Space": [ + "Espace" + ], + "Backspace": [ + "Retour arrière" + ], + "Minus": [ + "Moins" + ], + "PageUp": [ + "PagePréc." + ], + "The Jupyter Notebook has two different keyboard input modes.": [ + "Le Notebook Jupyter offre deux modes de saisie claivier." + ], + "Edit mode allows you to type code or text into a cell and is indicated by a green cell border.": [ + "Mode Édition permet de saisir du code ou du texte dans une cellule et se reconnaît à la bordure verte de la cellule." + ], + "Command mode binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin.": [ + "Mode Commande déclenche au clavier des actions au niveau du notebook et se reconnaît à la bordure grise de la cellule, avec une marge bleue sur la droite." + ], + "Close": [ + "Fermer" + ], + "Keyboard shortcuts": [ + "Raccourcis clavier" + ], + "Command": [ + "Commande" + ], + "Control": [ + "Contrôles" + ], + "Option": [ + "Option" + ], + "Return": [ + "Retour" + ], + "Command Mode (press %s to enable)": [ + "Mode Commande (presser %s pour l'activer)" + ], + "Edit Shortcuts": [ + "Modifier les Raccourcis Clavier" + ], + "edit command-mode keyboard shortcuts": [ + "modifier les raccourcis clavier du mode commande" + ], + "Edit Mode (press %s to enable)": [ + "Mode Édition (presser %s pour l'activer)" + ], + "Autosave Failed!": [ + "Échec de la sauvegarde automatique !" + ], + "Rename": [ + "Renommer" + ], + "Enter a new notebook name:": [ + "Saisir le nouveau nom du notebook" + ], + "Rename Notebook": [ + "Renommer le Notebook" + ], + "Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:": [ + "Nom de notebook invalide. Les noms de notebooks doivent posséder au moins un caractère et peuvent contenir tous les caractères sauf « /\\ ». Merci de saisir un nouveau nom de notebook :" + ], + "Renaming...": [ + "Renommage en cours..." + ], + "Unknown error": [ + "Erreur inconnue" + ], + "no checkpoint": [ + "aucune sauvegarde" + ], + "Last Checkpoint: %s": [ + "Dernière Sauvegarde : %s" + ], + "(unsaved changes)": [ + "(modifié)" + ], + "(autosaved)": [ + "(auto-sauvegardé)" + ], + "Warning: too many matches (%d). Some changes might not be shown or applied.": [ + "Attention : trop de correspondances (%d). Certains changements peuvent ne pas être affichés ou appliqués." + ], + "%d match": [ + "%d correspondance", + "%d correspondances" + ], + "More than 100 matches, aborting": [ + "Plus de 100 correspondances, annulation" + ], + "Use regex (JavaScript regex syntax)": [ + "Utiliser des regex (syntaxe des regex JavaScript)" + ], + "Replace in selected cells": [ + "Remplacer dans les cellules sélectionnées" + ], + "Match case": [ + "Sensible à la casse" + ], + "Find": [ + "Rechercher" + ], + "Replace": [ + "Remplacer" + ], + "No matches, invalid or empty regular expression": [ + "Aucune correspondance trouvée, expression régulière vide ou invalide." + ], + "Replace All": [ + "Tout Remplacer" + ], + "Find and Replace": [ + "Rechercher et Remplacer" + ], + "find and replace": [ + "rechercher et remplacer" + ], + "Write raw LaTeX or other formats here, for use with nbconvert. It will not be rendered in the notebook. When passing through nbconvert, a Raw Cell's content is added to the output unmodified.": [ + "Écrivez ici du code Latex brut ou d'autres formats, pour usage avec nbconvert. Il ne sera pas rendu dans le notebook. En utilisant nbconvert, le contenu d'une cellule brute est ajouté tel-quel à la sortie." + ], + "Grow the tooltip vertically (press shift-tab twice)": [ + "Agrandir l'info-bulle verticallement (presser Maj-Tab deux fois)" + ], + "show the current docstring in pager (press shift-tab 4 times)": [ + "montrer la chaîne de documentation courante dans le paginateur (presser shift-tab 4 fois)" + ], + "Open in Pager": [ + "Ouvrir dans le Paginateur" + ], + "Tooltip will linger for 10 seconds while you type": [ + "L'info-bulle restera affichée 10 secondes pendant votre saisie" + ], + "Welcome to the Notebook Tour": [ + "Bienvenue dans la visite du Notebook" + ], + "You can use the left and right arrow keys to go backwards and forwards.": [ + "Vous pouvez utiliser les touches gauche et droite pour continuer ou revenir en arrière." + ], + "Filename": [ + "Nom du fichier" + ], + "Click here to change the filename for this notebook.": [ + "Cliquer ici pour changer le nom du fichier de ce notebook." + ], + "Notebook Menubar": [ + "Barre de Menu du Notebook" + ], + "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with.": [ + "La barre de menu a des menus pour les actions concernant le notebook, ses cellules, et le noyau avec lequel il communique." + ], + "Notebook Toolbar": [ + "Barre d'outils du Notebook" + ], + "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information.": [ + "La barre d'outils a des boutons pour les actions les plus fréquentes. Survoler les boutons à la souris pour plus d'information." + ], + "Mode Indicator": [ + "Indicateur de mode" + ], + "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.": [ + "Le Notebook offre deux modes : Édition et Commande. Dans cette zone, un indicateur peut vous indiquer dans quel mode vous êtes." + ], + "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area.": [ + "Actuellement, vous êtes en mode Commande, et de nombreux raccourcis clavier sont disponibles. Dans ce mode, aucune icône n'est affichée dans la zone d'indication." + ], + "Pressing Enter or clicking in the input text area of the cell switches to Edit Mode.": [ + "Presser Entrée ou cliquer dans la zone de saisie de la cellule bascule vers le Mode Édition." + ], + "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell.": [ + "Notez que la bordure autour de la cellule active a changé de couleur. Saisir du texte au clavier l'insérera dans la cellule active." + ], + "Back to Command Mode": [ + "Retourner au Mode Commande" + ], + "Pressing Esc or clicking outside of the input text area takes you back to Command Mode.": [ + "Presser Esc ou cliquer en dehors de la zone de saisie de la cellule vous ramène au Mode Commande." + ], + "Keyboard Shortcuts": [ + "Raccourcis Clavier" + ], + "You can click here to get a list of all of the keyboard shortcuts.": [ + "Vous pouvez cliquer ici pour afficher une liste de tous les raccourcis clavier." + ], + "Kernel Indicator": [ + "Indicateur de Noyau" + ], + "This is the Kernel indicator. It looks like this when the Kernel is idle.": [ + "Ceci est l'indicateur de Noyau. Il a cet aspect quand le Noyau est inactif." + ], + "The Kernel indicator looks like this when the Kernel is busy.": [ + "L'indicateur de Noyau a cet aspect quand le Noyau est actif." + ], + "Interrupting the Kernel": [ + "Interrompre le Noyau" + ], + "To cancel a computation in progress, you can click here.": [ + "Pour annuler une exécution en cours, vous pouvez cliquer ici." + ], + "Notification Area": [ + "Zone de notification" + ], + "Messages in response to user actions (Save, Interrupt, etc.) appear here.": [ + "Les messages en retour d'actions utilisateur (Enregistrement, Interruption, etc.) s'affichent ici." + ], + "End of Tour": [ + "Fin de la visite" + ], + "This concludes the Jupyter Notebook User Interface Tour.": [ + "C'est la fin de cette visite guidée de l'Interface utilisateur du Notebook Jupyter." + ], + "Edit Attachments": [ + "Modifier les Pièces-Jointes" + ], + "Cell": [ + "Cellule" + ], + "Edit Metadata": [ + "Éditer les Méta-Données" + ], + "Custom": [ + "Personnalisé" + ], + "Set the MIME type of the raw cell:": [ + "Définir le type MIME de la cellule en texte brut :" + ], + "Raw Cell MIME Type": [ + "Type MIME de la Cellule en Texte Brut" + ], + "Raw NBConvert Format": [ + "Format du Texte Brut" + ], + "Raw Cell Format": [ + "Format de la Cellule Texte Brut" + ], + "Slide": [ + "Diapo" + ], + "Sub-Slide": [ + "Sous-Diapo" + ], + "Fragment": [ + "Extrait" + ], + "Skip": [ + "Sauter" + ], + "Notes": [ + "Notes" + ], + "Slide Type": [ + "Type de diapo" + ], + "Slideshow": [ + "Diaporama" + ], + "Add tag": [ + "Ajouter un mot-clé" + ], + "Edit the list of tags below. All whitespace is treated as tag separators.": [ + "Modifier la liste de mots-clés ci-dessous. Les espaces sont considérés comme des séparateurs." + ], + "Edit the tags": [ + "Modifier les mots-clés" + ], + "Edit Tags": [ + "Modifier Mots-Clés" + ], + "Shutdown": [ + "Arrêter" + ], + "Create a new notebook with %s": [ + "Créer un nouveau notebook avec %s" + ], + "An error occurred while creating a new notebook.": [ + "Une erreur s'est produite à la création du notebook" + ], + "Creating File Failed": [ + "La création du fichier a échoué" + ], + "An error occurred while creating a new file.": [ + "Une erreur est survenue à la création du nouveau fichier." + ], + "Creating Folder Failed": [ + "La création du répertoire a échoué" + ], + "An error occurred while creating a new folder.": [ + "Une erreur est survenue à la création du nouveau répertoire." + ], + "Failed to read file": [ + "Échec de lecture du fichier" + ], + "Failed to read file %s": [ + "Échec de lecture du fichier %s" + ], + "The file size is %d MB. Do you still want to upload it?": [ + "Le fichier pèse %d MB. Êtes-vous certain de vouloir le téléverser ?" + ], + "Large file size warning": [ + "Avertissement de taille de fichier élevée" + ], + "Server error: ": [ + "Erreur serveur :" + ], + "The notebook list is empty.": [ + "La liste des notebooks est vide." + ], + "Click here to rename, delete, etc.": [ + "Cliquer ici pour renommer, supprimer, etc." + ], + "Running": [ + "Actif" + ], + "Enter a new file name:": [ + "Saisir le nom du nouveau fichier :" + ], + "Enter a new directory name:": [ + "Saisir le nom du nouveau répertoire :" + ], + "Enter a new name:": [ + "Saisir un nouveau nom :" + ], + "Rename file": [ + "Renommer le fichier" + ], + "Rename directory": [ + "Renommer le répertoire" + ], + "Rename notebook": [ + "Renommer le notebook" + ], + "Move": [ + "Déplacer" + ], + "An error occurred while renaming \"%1$s\" to \"%2$s\".": [ + "Une erreur s'est produite pendant le renommage de « %1$s » vers « %2$s »." + ], + "Rename Failed": [ + "Échec du Renommage" + ], + "Enter a new destination directory path for this item:": [ + "Saisir un nouveau chemin de destination pour cet élément :", + "Saisir un nouveau chemin de destination pour ces %d éléments :" + ], + "Move an Item": [ + "Déplacer un élément", + "Déplacer %d éléments" + ], + "An error occurred while moving \"%1$s\" from \"%2$s\" to \"%3$s\".": [ + "Une erreur s'est produite lors du déplacement de « %1$s » de « %2$s » vers « %3$s »." + ], + "Move Failed": [ + "Échec du déplacement" + ], + "Are you sure you want to permanently delete: \"%s\"?": [ + "Êtes-vous certain de vouloir supprimer définitivement « %s  » ?", + "Êtes-vous certain de vouloir supprimer définitivement les « %d  » fichiers ou répertoires sélectionnés ?" + ], + "An error occurred while deleting \"%s\".": [ + "Une erreur s'est produite pendant la suppression de « %s »." + ], + "Delete Failed": [ + "Échec de la suppression" + ], + "Are you sure you want to duplicate: \"%s\"?": [ + "Êtes-vous certain de vouloir dupliquer « %s » ?", + "Êtes-vous certain de vouloir dupliquer les « %d » fichiers sélectionnés ?" + ], + "Duplicate": [ + "Dupliquer" + ], + "An error occurred while duplicating \"%s\".": [ + "Une erreur s'est produite pendant la duplication de « %s »." + ], + "Duplicate Failed": [ + "Échec de la duplication" + ], + "Upload": [ + "Téléverser" + ], + "Invalid file name": [ + "Nom de fichier invalide" + ], + "File names must be at least one character and not start with a period": [ + "Les noms des fichier doivent compter au moins un caractère et ne doivent pas commencer avec un point." + ], + "Cannot upload invalid Notebook": [ + "Impossible de téléverser un Notebook invalide" + ], + "There is already a file named \"%s\". Do you want to replace it?": [ + "Il y a déjà un fichier nommé « %s ». Souhaitez-vous le remplacer ?" + ], + "Replace file": [ + "Remplacer le fichier" + ] + } + } +} \ No newline at end of file diff --git a/notebook/i18n/fr_FR/LC_MESSAGES/nbui.mo b/notebook/i18n/fr_FR/LC_MESSAGES/nbui.mo new file mode 100644 index 0000000000..e0e1ef903c Binary files /dev/null and b/notebook/i18n/fr_FR/LC_MESSAGES/nbui.mo differ diff --git a/notebook/i18n/fr_FR/LC_MESSAGES/notebook.mo b/notebook/i18n/fr_FR/LC_MESSAGES/notebook.mo new file mode 100644 index 0000000000..97bdb60bae Binary files /dev/null and b/notebook/i18n/fr_FR/LC_MESSAGES/notebook.mo differ diff --git a/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.json b/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.json new file mode 100644 index 0000000000..11ab71f8e2 --- /dev/null +++ b/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.json @@ -0,0 +1,1353 @@ +{ + "domain": "nbjs", + "locale_data": { + "nbjs": { + "": { + "domain": "nbjs" + }, + "Manually edit the JSON below to manipulate the metadata for this cell.": [ + "このセルのメタデータを操作するには以下のJSONを手動で編集してください。" + ], + "Manually edit the JSON below to manipulate the metadata for this notebook.": [ + "このノートブックのメタデータを操作するには以下のJSONを手動で編集してください。" + ], + " We recommend putting custom metadata attributes in an appropriately named substructure, so they don't conflict with those of others.": [ + " カスタムメタデータ属性は適切な名前のサブ構造に含め他の属性と競合しないようにすることをお勧めします。" + ], + "Edit the metadata": [ + "メタデータの編集" + ], + "Edit Notebook Metadata": [ + "ノートブックのメタデータを編集" + ], + "Edit Cell Metadata": [ + "セルのメタデータを編集" + ], + "Cancel": [ + "キャンセル" + ], + "Edit": [ + "編集" + ], + "OK": [ + "OK" + ], + "Apply": [ + "適用" + ], + "WARNING: Could not save invalid JSON.": [ + "警告: 不正な JSON のため保存できませんでした。" + ], + "There are no attachments for this cell.": [ + "このセルには添付ファイルはありません。" + ], + "Current cell attachments": [ + "現在のセルの添付ファイル" + ], + "Attachments": [ + "添付ファイル" + ], + "Restore": [ + "復元" + ], + "Delete": [ + "削除" + ], + "Edit attachments": [ + "添付ファイルを編集" + ], + "Edit Notebook Attachments": [ + "ノートブックの添付ファイルを編集" + ], + "Edit Cell Attachments": [ + "セルの添付ファイルを編集" + ], + "Select a file to insert.": [ + "挿入するファイルを選択して下さい。" + ], + "Select a file": [ + "ファイルを選択" + ], + "You are using Jupyter notebook.": [ + "Jupyter notebook を使用しています。" + ], + "The version of the notebook server is: ": [ + "ノートブックサーバのバージョン" + ], + "The server is running on this version of Python:": [ + "サーバはこのバージョンの Python で稼働しています:" + ], + "Waiting for kernel to be available...": [ + "利用可能なカーネルを待機しています..." + ], + "Server Information:": [ + "サーバ情報:" + ], + "Current Kernel Information:": [ + "現在のカーネル情報:" + ], + "Could not access sys_info variable for version information.": [ + "バージョン情報のための sys_info 変数にアクセスできませんでした。" + ], + "Cannot find sys_info!": [ + "sys_info が見つかりません!" + ], + "About Jupyter Notebook": [ + "Jupyter Notebook について" + ], + "unable to contact kernel": [ + "カーネルと通信できません" + ], + "toggle rtl layout": [ + "rtl レイアウトをトグル" + ], + "Toggle the screen directionality between left-to-right and right-to-left": [ + "画面の表示方向を、左から右、と右から左、でトグル" + ], + "edit command mode keyboard shortcuts": [ + "コマンドモードキーボードショートカットを編集" + ], + "Open a dialog to edit the command mode keyboard shortcuts": [ + "コマンドモードのキーボードショートカットを編集するためにダイアログを開きます" + ], + "restart kernel": [ + "カーネルを再起動" + ], + "restart the kernel (no confirmation dialog)": [ + "カーネルを再起動 (確認ダイアログ無し)" + ], + "confirm restart kernel": [ + "カーネルを再起動の確認" + ], + "restart the kernel (with dialog)": [ + "カーネルを再起動 (確認ダイアログあり)" + ], + "restart kernel and run all cells": [ + "カーネルを再起動し全てのセルを実行" + ], + "restart the kernel, then re-run the whole notebook (no confirmation dialog)": [ + "カーネルを再起動しノートブック全体を再実行 (確認ダイアログ無し)" + ], + "confirm restart kernel and run all cells": [ + "カーネルを再起動し全てのセルを実行する確認" + ], + "restart the kernel, then re-run the whole notebook (with dialog)": [ + "カーネルを再起動しノートブック全体を再実行 (確認ダイアログあり)" + ], + "restart kernel and clear output": [ + "カーネルを再起動し出力をクリア" + ], + "restart the kernel and clear all output (no confirmation dialog)": [ + "カーネルを再起動し全ての出力をクリア (確認ダイアログ無し)" + ], + "confirm restart kernel and clear output": [ + "カーネルを再起動し出力をクリアする確認" + ], + "restart the kernel and clear all output (with dialog)": [ + "カーネルを再起動し出力をクリア (確認ダイアログあり)" + ], + "interrupt the kernel": [ + "カーネルを中断" + ], + "run cell and select next": [ + "セルを実行し次を選択" + ], + "run cell, select below": [ + "セルを実行し下を選択" + ], + "run selected cells": [ + "選択されているセルを実行" + ], + "run cell and insert below": [ + "セルを実行し下に挿入" + ], + "run all cells": [ + "全てのセルを実行" + ], + "run all cells above": [ + "ここまでのセルを実行" + ], + "run all cells below": [ + "以下の全てのセルを実行" + ], + "enter command mode": [ + "コマンドラインモードに移る" + ], + "insert image": [ + "画像の挿入" + ], + "cut cell attachments": [ + "セルの添付ファイルを切り取り" + ], + "copy cell attachments": [ + "セルの添付ファイルをコピー" + ], + "paste cell attachments": [ + "セルの添付ファイルをペースト" + ], + "split cell at cursor": [ + "カーソル位置でセルを分割" + ], + "enter edit mode": [ + "編集モードに移る" + ], + "select previous cell": [ + "1つ前のセルを選択" + ], + "select cell above": [ + "上のセルを選択" + ], + "select next cell": [ + "次のセルを選択" + ], + "select cell below": [ + "下のセルを選択" + ], + "extend selection above": [ + "選択を上に拡大" + ], + "extend selected cells above": [ + "セルの選択を上に拡大" + ], + "extend selection below": [ + "選択を下に拡大" + ], + "extend selected cells below": [ + "セルの選択を下に拡大" + ], + "cut selected cells": [ + "選択されているセルを切り取り" + ], + "copy selected cells": [ + "選択されているセルをコピー" + ], + "paste cells above": [ + "上にセルをペースト" + ], + "paste cells below": [ + "下にセルをペースト" + ], + "insert cell above": [ + "上にセルを追加" + ], + "insert cell below": [ + "下にセルを追加" + ], + "change cell to code": [ + "セルをコードに変更" + ], + "change cell to markdown": [ + "セルを markdown に変更" + ], + "change cell to raw": [ + "セルを raw に変更" + ], + "change cell to heading 1": [ + "セルを表題1に変更" + ], + "change cell to heading 2": [ + "セルを表題2に変更" + ], + "change cell to heading 3": [ + "セルを表題3に変更" + ], + "change cell to heading 4": [ + "セルを表題4に変更" + ], + "change cell to heading 5": [ + "セルを表題5に変更" + ], + "change cell to heading 6": [ + "セルを表題6に変更" + ], + "toggle cell output": [ + "セルの出力をトグル" + ], + "toggle output of selected cells": [ + "選択中のセルの出力をトグル" + ], + "toggle cell scrolling": [ + "セルのスクロールをトグル" + ], + "toggle output scrolling of selected cells": [ + "選択中のセルの出力スクロールをトグル" + ], + "clear cell output": [ + "セルの出力をクリア" + ], + "clear output of selected cells": [ + "選択しているセルの出力をクリア" + ], + "move cells down": [ + "セルを下に移動" + ], + "move selected cells down": [ + "選択しているセルを下に移動" + ], + "move cells up": [ + "セルを上に移動" + ], + "move selected cells up": [ + "選択しているセルを上に移動" + ], + "toggle line numbers": [ + "行番号をトグル" + ], + "show keyboard shortcuts": [ + "キーボードショートカットを表示" + ], + "delete cells": [ + "セルを削除" + ], + "delete selected cells": [ + "選択されているセルを削除" + ], + "undo cell deletion": [ + "セルの削除を取り消す" + ], + "merge cell with previous cell": [ + "1つ前のアクティブセルとマージ" + ], + "merge cell above": [ + "上のセルとマージ" + ], + "merge cell with next cell": [ + "次のセルとマージ" + ], + "merge cell below": [ + "下のセルとマージ" + ], + "merge selected cells": [ + "選択されているセルをマージ" + ], + "merge cells": [ + "セルをマージ" + ], + "merge selected cells, or current cell with cell below if only one cell is selected": [ + "選択中のセル、または選択されていない場合は現在のセル以降をマージする" + ], + "show command pallette": [ + "コマンドパレットの表示" + ], + "open the command palette": [ + "コマンドパレットを開く" + ], + "toggle all line numbers": [ + "全ての行番号をトグル" + ], + "toggles line numbers in all cells, and persist the setting": [ + "全てのセルの行番号をトグルし設定を保存" + ], + "show all line numbers": [ + "全ての行番号を保存" + ], + "show line numbers in all cells, and persist the setting": [ + "全てのセルで行番号を表示し設定を保存" + ], + "hide all line numbers": [ + "全ての行番号を非表示" + ], + "hide line numbers in all cells, and persist the setting": [ + "すべてのセルの行番号を非表示にし設定を保持します" + ], + "toggle header": [ + "ヘッダをトグル" + ], + "switch between showing and hiding the header": [ + "ヘッダの表示と非表示を切り替えます" + ], + "show the header": [ + "ヘッダを表示" + ], + "hide the header": [ + "ヘッダを非表示" + ], + "toggle toolbar": [ + "ツールバーをトグル" + ], + "switch between showing and hiding the toolbar": [ + "ツールバーの表示と非表示を切り替える" + ], + "show the toolbar": [ + "ツールバーを表示" + ], + "hide the toolbar": [ + "ツールバーを非表示" + ], + "close the pager": [ + "ページャを閉じる" + ], + "ignore": [ + "無視" + ], + "move cursor up": [ + "カーソルを上に移動" + ], + "move cursor down": [ + "カーソルを下に移動" + ], + "scroll notebook down": [ + "ノートブックを下にスクロール" + ], + "scroll notebook up": [ + "ノートブックを上にスクロール" + ], + "scroll cell center": [ + "セルを中央までスクロール" + ], + "Scroll the current cell to the center": [ + "現在のセルが中央になる様にスクロール" + ], + "scroll cell top": [ + "セルを最上部までスクロール" + ], + "Scroll the current cell to the top": [ + "現在のセルが最上部になる様にスクロール" + ], + "duplicate notebook": [ + "ノートブックの複製" + ], + "Create and open a copy of the current notebook": [ + "現在のノートブックのコピーを作成し開く" + ], + "trust notebook": [ + "ノートブックを信頼" + ], + "Trust the current notebook": [ + "現在のノートブックを信頼する" + ], + "rename notebook": [ + "ノートブックをリネーム" + ], + "Rename the current notebook": [ + "現在のノートブックをリネームする" + ], + "toggle all cells output collapsed": [ + "" + ], + "Toggle the hidden state of all output areas": [ + "" + ], + "toggle all cells output scrolled": [ + "" + ], + "Toggle the scrolling state of all output areas": [ + "" + ], + "clear all cells output": [ + "全てのセル出力をクリア" + ], + "Clear the content of all the outputs": [ + "全ての出力コンテンツをクリア" + ], + "save notebook": [ + "ノートブックの保存" + ], + "Save and Checkpoint": [ + "保存とチェックポイント" + ], + "Warning: accessing Cell.cm_config directly is deprecated.": [ + "警告: Cell.cm_config に直接アクセスすることは非推奨です。" + ], + "Unrecognized cell type: %s": [ + "認識できないセル種別: %s" + ], + "Unrecognized cell type": [ + "認識できないセル種別" + ], + "Error in cell toolbar callback %s": [ + "セルツールバーのコールバック %s でエラー" + ], + "Clipboard types: %s": [ + "クリップボードの種別: %s" + ], + "Dialog for paste from system clipboard": [ + "システムのクリップボードから張り付け" + ], + "Ctrl-V": [ + "" + ], + "Cmd-V": [ + "" + ], + "Press %s again to paste": [ + "ペーストするには %s をもう一度押します" + ], + "Why is this needed? ": [ + "これが必要ですか?" + ], + "We can't get paste events in this browser without a text box. ": [ + "このブラウザではテキストボックスなしに貼り付けイベントを受け取ることができません。" + ], + "There's an invisible text box focused in this dialog.": [ + "このダイアログには目に見えないテキストボックスがあります。" + ], + "%s to paste": [ + "ペーストするには %s" + ], + "Can't execute cell since kernel is not set.": [ + "カーネルが存在しないのでセルを実行できません。" + ], + "In": [ + "入力" + ], + "Could not find a kernel matching %s. Please select a kernel:": [ + "%s にマッチするカーネルが見つかりませんでした。カーネルを選択して下さい:" + ], + "Continue Without Kernel": [ + "カーネル無しで続行" + ], + "Set Kernel": [ + "カーネルの設定" + ], + "Kernel not found": [ + "カーネルが見つかりません" + ], + "Creating Notebook Failed": [ + "ノートブックの作成に失敗しました" + ], + "The error was: %s": [ + "エラー: %s" + ], + "Run": [ + "実行" + ], + "Code": [ + "コード" + ], + "Markdown": [ + "" + ], + "Raw NBConvert": [ + "" + ], + "Heading": [ + "ヘッダ" + ], + "unrecognized cell type:": [ + "不明なセル種別:" + ], + "Failed to retrieve MathJax from '%s'": [ + "%s から MathJax を取得できませんでした" + ], + "Math/LaTeX rendering will be disabled.": [ + "Math/LaTeX レンダリングは無効になります。" + ], + "Trusted Notebook": [ + "信頼されたノートブック" + ], + "Trust Notebook": [ + "ノートブックを信頼する" + ], + "None": [ + "" + ], + "No checkpoints": [ + "チェックポイントはありません" + ], + "Opens in a new window": [ + "新しいウィンドウで開く" + ], + "Autosave in progress, latest changes may be lost.": [ + "自動保存が実行中です。最後の変更は失われるかもしれません。" + ], + "Unsaved changes will be lost.": [ + "未保存の変更は破棄されます。" + ], + "The Kernel is busy, outputs may be lost.": [ + "カーネルがビジーです。出力が欠けるかもしれません。" + ], + "This notebook is version %1$s, but we only fully support up to %2$s.": [ + "このノートブックのバージョンは %1$s です。しかし完全なサポートは %2$s だけになります。" + ], + "You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available.": [ + "このノートブックはまだ動作しますがこれ以降のノートブックバージョンで導入されたセルおよび出力タイプは利用できなくなります。" + ], + "Restart and Run All Cells": [ + "再起動と全ての出力をクリア" + ], + "Restart and Clear All Outputs": [ + "再起動と全ての出力をクリア" + ], + "Restart": [ + "再起動" + ], + "Continue Running": [ + "実行を続行" + ], + "Reload": [ + "リロード" + ], + "Overwrite": [ + "上書き" + ], + "Trust": [ + "信頼" + ], + "Revert": [ + "復元" + ], + "Newer Notebook": [ + "新しいノートブック" + ], + "Use markdown headings": [ + "Markdown のヘッダを使用" + ], + "Jupyter no longer uses special heading cells. Instead, write your headings in Markdown cells using # characters:": [ + "Jupyter は特別な見出しセルを使用しなくなりました。代わりに # 文字を使用して Markdown セルに見出しを書きいて下さい:" + ], + "## This is a level 2 heading": [ + "## これはレベル2のヘッダです" + ], + "Restart kernel and re-run the whole notebook?": [ + "カーネルを再起動しノートブック全体を再実行する" + ], + "Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost.": [ + "現在のカーネルを再起動してノートブック全体を再実行しますか?全ての変数と出力は失われます。" + ], + "Restart kernel and clear all output?": [ + "カーネルを再起動し全ての出力をクリアしますか?" + ], + "Do you want to restart the current kernel and clear all output? All variables and outputs will be lost.": [ + "現在のカーネルを再起動してすべての出力をクリアしますか?全ての変数と出力は失われます。" + ], + "Restart kernel?": [ + "カーネルを再起動しますか?" + ], + "Do you want to restart the current kernel? All variables will be lost.": [ + "現在のカーネルを再起動しますか?全ての変数は失われます。" + ], + "Shutdown kernel?": [ + "カーネルをシャットダウンしますか?" + ], + "Do you want to shutdown the current kernel? All variables will be lost.": [ + "現在のカーネルをシャットダウンしますか?すべての変数は失われます。" + ], + "Notebook changed": [ + "ノートブックは変更されました" + ], + "The notebook file has changed on disk since the last time we opened or saved it. Do you want to overwrite the file on disk with the version open here, or load the version on disk (reload the page) ?": [ + "" + ], + "Notebook validation failed": [ + "ノートブックの検証に失敗しました" + ], + "The save operation succeeded, but the notebook does not appear to be valid. The validation error was:": [ + "保存操作は成功しましたがノートブックは有効ではないようです。検証エラー:" + ], + "A trusted Jupyter notebook may execute hidden malicious code when you open it. Selecting trust will immediately reload this notebook in a trusted state. For more information, see the Jupyter security documentation: ": [ + "信頼された Jupyter ノートブックは開いた際に隠された悪意のあるコードが実行される可能性があります。信頼を選択すると直ちにリロードされこのノートブックは信頼できる状態になりします。詳細については、Jupyter のセキュリティドキュメントを参照してください。" + ], + "here": [ + "ここ" + ], + "Trust this notebook?": [ + "このノートブックを信頼しますか?" + ], + "Notebook failed to load": [ + "ノートブックの読み込みに失敗しました" + ], + "The error was: ": [ + "エラー: " + ], + "See the error console for details.": [ + "詳しくはエラーコンソールを参照して下さい。" + ], + "The notebook also failed validation:": [ + "ノートブックの検証に失敗しました:" + ], + "An invalid notebook may not function properly. The validation error was:": [ + "無効なノートブックは正しく機能しない可能性があります。検証エラーは次の通りです:" + ], + "This notebook has been converted from an older notebook format to the current notebook format v(%s).": [ + "このノートブックは古いノートブックの形式から現在の形式 v(%s) に変換されました。" + ], + "This notebook has been converted from a newer notebook format to the current notebook format v(%s).": [ + "このノートブックは新しいノートブックの形式から現在の形式 v(%s) に変換されました。" + ], + "The next time you save this notebook, the current notebook format will be used.": [ + "次回このノートブックを保存する時は現在のノートブックの形式が使用されます。" + ], + "Older versions of Jupyter may not be able to read the new format.": [ + "古いバージョンの Jupyter は新しい形式を読むことができない場合があります。" + ], + "Some features of the original notebook may not be available.": [ + "オリジナルのノートブックの中には利用できない機能が幾つかあります。" + ], + "To preserve the original version, close the notebook without saving it.": [ + "元のバージョンを残すには保存せずにノートブックを閉じます" + ], + "Notebook converted": [ + "ノートブックは変換されました" + ], + "(No name)": [ + "(無題)" + ], + "An unknown error occurred while loading this notebook. This version can load notebook formats %s or earlier. See the server log for details.": [ + "このノートブックの読み込み中に不明なエラーが発生しました。このバージョンはノートブックの形式 %s とそれ以前のバージョンをロードできます。 詳細についてはサーバのログを参照して下さい。" + ], + "Error loading notebook": [ + "ノートブックの読み込み中にエラー" + ], + "Are you sure you want to revert the notebook to the latest checkpoint?": [ + "本当に最終チェックポイントへノートブックを復元しますか?" + ], + "This cannot be undone.": [ + "この操作は取り消せません。" + ], + "The checkpoint was last updated at:": [ + "チェックポイントはこの時間に変更されました:" + ], + "Revert notebook to checkpoint": [ + "ノートブックをチェックポイントへ復元" + ], + "Edit Mode": [ + "編集モード" + ], + "Command Mode": [ + "コマンドモード" + ], + "Kernel Created": [ + "カーネルが作成されました" + ], + "Connecting to kernel": [ + "カーネルに接続中" + ], + "Not Connected": [ + "接続されていません" + ], + "click to reconnect": [ + "クリックして再接続" + ], + "Restarting kernel": [ + "カーネルを再起動中" + ], + "Kernel Restarting": [ + "カーネルは再起動中です" + ], + "The kernel appears to have died. It will restart automatically.": [ + "カーネルが異常終了した様です。自動的に再起動します。" + ], + "Dead kernel": [ + "カーネルが異常終了!" + ], + "Kernel Dead": [ + "カーネルが異常終了" + ], + "Interrupting kernel": [ + "カーネルを中断します!" + ], + "No Connection to Kernel": [ + "カーネルへの接続がありません" + ], + "A connection to the notebook server could not be established. The notebook will continue trying to reconnect. Check your network connection or notebook server configuration.": [ + "ノートブックサーバへの接続を確立できませんでした。ノートブックは再接続を試みます。ネットワーク接続またはノートブックサーバの設定を確認してください。" + ], + "Connection failed": [ + "接続に失敗しました" + ], + "No kernel": [ + "カーネルが見つかりません" + ], + "Kernel is not running": [ + "カーネルは起動していません" + ], + "Don't Restart": [ + "再起動しない" + ], + "Try Restarting Now": [ + "再起動を試みます" + ], + "The kernel has died, and the automatic restart has failed. It is possible the kernel cannot be restarted. If you are not able to restart the kernel, you will still be able to save the notebook, but running code will no longer work until the notebook is reopened.": [ + "カーネルが異常終了し自動再起動が失敗しました。カーネルを再起動できない可能性があります。カーネルを再起動できない場合でもノートブックを保存することはできますがコードの実行はノートブックを開き直すまで機能しなくなります。" + ], + "No Kernel": [ + "カーネルが見つかりません" + ], + "Failed to start the kernel": [ + "カーネルの起動に失敗しました" + ], + "Kernel Busy": [ + "カーネルがビジー" + ], + "Kernel starting, please wait...": [ + "カーネルを起動しています。お待ちください..." + ], + "Kernel Idle": [ + "カーネルは休止状態" + ], + "Kernel ready": [ + "カーネルの準備が完了" + ], + "Using kernel: ": [ + "カーネルを使用しています: " + ], + "Only candidate for language: %1$s was %2$s.": [ + "唯一の言語の候補: %1$s は %2$s." + ], + "Loading notebook": [ + "ノートブックを読み込んでいます" + ], + "Notebook loaded": [ + "ノートブックが読み込まれました" + ], + "Saving notebook": [ + "ノートブックを保存しています" + ], + "Notebook saved": [ + "ノートブックが保存されました" + ], + "Notebook save failed": [ + "ノートブックの保存に失敗しました" + ], + "Notebook copy failed": [ + "ノートブックのコピーに失敗しました" + ], + "Checkpoint created": [ + "チェックポイントが作成されました" + ], + "Checkpoint failed": [ + "チェックポイントの失敗" + ], + "Checkpoint deleted": [ + "チェックポイントの削除" + ], + "Checkpoint delete failed": [ + "チェックポイントの削除に失敗しました" + ], + "Restoring to checkpoint...": [ + "チェックポイントの復元..." + ], + "Checkpoint restore failed": [ + "チェックポイントの復元に失敗しました" + ], + "Autosave disabled": [ + "自動保存は無効" + ], + "Saving every %d sec.": [ + "%d 秒で保存" + ], + "Trusted": [ + "信頼済み" + ], + "Not Trusted": [ + "信頼されていません" + ], + "click to expand output": [ + "出力を広げるにはクリックします" + ], + "click to expand output; double click to hide output": [ + "出力を広げるにはクリックします; ダブルクリックで非表示" + ], + "click to unscroll output; double click to hide": [ + "出力のスクロールを止めるためにクリック; ダブルクリックで非表示" + ], + "click to scroll output; double click to hide": [ + "出力をスクロールするためにクリック; ダブルクリックで非表示" + ], + "Javascript error adding output!": [ + "Javascript エラーが出力に追加されました!" + ], + "See your browser Javascript console for more details.": [ + "より詳細を知るにはブラウザの Javascript コンソールを確認して下さい。" + ], + "Out[%s]:": [ + "出力[%s]:" + ], + "Unrecognized output: %s": [ + "認識できない出力: %s" + ], + "Open the pager in an external window": [ + "外部ウィンドウでページャを開く" + ], + "Close the pager": [ + "ページャを閉じる" + ], + "Jupyter Pager": [ + "Jupyter ページャ" + ], + "go to cell start": [ + "セルの先頭に移動" + ], + "go to cell end": [ + "セルの末尾に移動" + ], + "go one word left": [ + "単語1つ左に移動" + ], + "go one word right": [ + "単語1つ右に移動" + ], + "delete word before": [ + "前の単語を削除" + ], + "delete word after": [ + "続く単語を削除" + ], + "code completion or indent": [ + "コード補完またはインデント" + ], + "tooltip": [ + "ツールチップ" + ], + "indent": [ + "インデント" + ], + "dedent": [ + "インデント解除" + ], + "select all": [ + "全てを選択" + ], + "undo": [ + "元に戻す" + ], + "redo": [ + "やり直し" + ], + "Shift": [ + "" + ], + "Alt": [ + "" + ], + "Up": [ + "" + ], + "Down": [ + "" + ], + "Left": [ + "" + ], + "Right": [ + "" + ], + "Tab": [ + "" + ], + "Caps Lock": [ + "" + ], + "Esc": [ + "" + ], + "Ctrl": [ + "" + ], + "Enter": [ + "" + ], + "Page Up": [ + "" + ], + "Page Down": [ + "" + ], + "Home": [ + "" + ], + "End": [ + "" + ], + "Space": [ + "" + ], + "Backspace": [ + "" + ], + "Minus": [ + "" + ], + "PageUp": [ + "" + ], + "The Jupyter Notebook has two different keyboard input modes.": [ + "Jupyter Notebook は異なる2つのキーボード入力モードを持っています。" + ], + "Edit mode allows you to type code or text into a cell and is indicated by a green cell border.": [ + "編集モード は緑色のセル枠で表示され、セルにコードまたはテキストを入力できます。" + ], + "Command mode binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin.": [ + "コマンドモード は青い左マージンを持つ灰色のセル枠で表示され、ノートブックレベルのコマンドにキーボードをバインドします。" + ], + "Close": [ + "閉じる" + ], + "Keyboard shortcuts": [ + "キーボードショートカット" + ], + "Command": [ + "" + ], + "Control": [ + "" + ], + "Option": [ + "" + ], + "Return": [ + "" + ], + "Command Mode (press %s to enable)": [ + "コマンドモード (有効にするには %s を押下)" + ], + "Edit Shortcuts": [ + "ショートカットを編集" + ], + "edit command-mode keyboard shortcuts": [ + "コマンドモードのキーボードショートカットを編集" + ], + "Edit Mode (press %s to enable)": [ + "編集モード (有効にするには %s を押下)" + ], + "Autosave Failed!": [ + "自動保存に失敗!" + ], + "Rename": [ + "リネーム" + ], + "Enter a new notebook name:": [ + "新しいノートブックの名前を入力:" + ], + "Rename Notebook": [ + "ノートブックのリネーム" + ], + "Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:": [ + "不正なノートブックの名前です。ノートブックの名前は :/\\ を除く1文字以上でなければなりません。新しいノートブックの名前を入力して下さい:" + ], + "Renaming...": [ + "リネーム中..." + ], + "Unknown error": [ + "不明なエラー" + ], + "no checkpoint": [ + "チェックポイントはありません" + ], + "Last Checkpoint: %s": [ + "最終チェックポイント: %s" + ], + "(unsaved changes)": [ + "(未保存の変更)" + ], + "(autosaved)": [ + "(自動保存)" + ], + "Warning: too many matches (%d). Some changes might not be shown or applied.": [ + "警告: マッチが多すぎます (%d 個)。幾らかの変更は表示または適用されません。" + ], + "%d match": [ + "%d 個にマッチ", + "%d 個にマッチ" + ], + "More than 100 matches, aborting": [ + "100 個以上にマッチしたため中断します" + ], + "Use regex (JavaScript regex syntax)": [ + "正規表現の使用 (JavaScript 正規表現)" + ], + "Replace in selected cells": [ + "選択中のセルを入れ替え" + ], + "Match case": [ + "大文字と小文字の両方にマッチ" + ], + "Find": [ + "検索" + ], + "Replace": [ + "置換" + ], + "No matches, invalid or empty regular expression": [ + "マッチしません。不正または空の正規表現です" + ], + "Replace All": [ + "全て置換" + ], + "Find and Replace": [ + "検索と置換" + ], + "find and replace": [ + "検索と置換" + ], + "Write raw LaTeX or other formats here, for use with nbconvert. It will not be rendered in the notebook. When passing through nbconvert, a Raw Cell's content is added to the output unmodified.": [ + "nbconvert で使うために生の LaTeX や他の形式をここに書いてください。これらはノートブックには表示されません。nbconvert に渡されると Raw Cell の内容が変更されずに出力に追加されます。" + ], + "Grow the tooltip vertically (press shift-tab twice)": [ + "ツールチップを垂直方向に広げる(shift-tabを2回押す)" + ], + "show the current docstring in pager (press shift-tab 4 times)": [ + "現在の docstring をページャで表示します(shift-tabを4回押します)" + ], + "Open in Pager": [ + "ページャで開く" + ], + "Tooltip will linger for 10 seconds while you type": [ + "ツールチップは入力中の 10 秒間表示されます" + ], + "Welcome to the Notebook Tour": [ + "ノートブックツアーへようこそ" + ], + "You can use the left and right arrow keys to go backwards and forwards.": [ + "先に進んだり前に戻るにはカーソルの左と右を使う事ができます。" + ], + "Filename": [ + "ファイル名" + ], + "Click here to change the filename for this notebook.": [ + "このノートブックのファイル名を変更するにはここをクリックします。" + ], + "Notebook Menubar": [ + "ノートブックメニューバー" + ], + "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with.": [ + "メニューバーにはノートブック、そのセル、およびそれらが通信するカーネルに対するアクションのためのメニューがあります。" + ], + "Notebook Toolbar": [ + "ノートブックツールバー" + ], + "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information.": [ + "ツールバーには最も一般的な操作のボタンが置かれます。 詳細については各ボタンの上にマウスを移動してください。" + ], + "Mode Indicator": [ + "モードインジケータ" + ], + "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.": [ + "ノートブックには編集モードとコマンドモードの2つのモードがあります。この領域にはどのモードにいるのかを示すインジケータが表示されます。" + ], + "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area.": [ + "現在あなたはコマンドモードにいるので多くのキーボードショートカットが利用可能です。 このモードではインジケータ領域にアイコンは表示されません。" + ], + "Pressing Enter or clicking in the input text area of the cell switches to Edit Mode.": [ + "Enterを押下、セルのテキストの入力エリアをクリックすると編集モードに切り替わります。" + ], + "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell.": [ + "現在アクティブなセルの周囲の境界線の色が変わることに注目してください。文字を入力すると現在アクティブなセルにテキストが挿入されます。" + ], + "Back to Command Mode": [ + "コマンドモードに戻る" + ], + "Pressing Esc or clicking outside of the input text area takes you back to Command Mode.": [ + "Escを押下、またはテキストの入力エリアの外側をクリックするとコマンドモードに戻ります。" + ], + "Keyboard Shortcuts": [ + "キーボードショートカット" + ], + "You can click here to get a list of all of the keyboard shortcuts.": [ + "ここをクリックすると全てのキーボードショートカットの一覧が表示されます。" + ], + "Kernel Indicator": [ + "カーネルインジケータ" + ], + "This is the Kernel indicator. It looks like this when the Kernel is idle.": [ + "これはカーネルインジケータです。カーネルがアイドル状態のときはこの様に表示されます。" + ], + "The Kernel indicator looks like this when the Kernel is busy.": [ + "カーネルインジケータはカーネルがビジーのときはこのように表示されます。" + ], + "Interrupting the Kernel": [ + "カーネルの中断" + ], + "To cancel a computation in progress, you can click here.": [ + "実行中の計算をキャンセルするにはここをクリックして下さい。" + ], + "Notification Area": [ + "通知エリア" + ], + "Messages in response to user actions (Save, Interrupt, etc.) appear here.": [ + "ユーザーアクション (保存、割り込みなど) に応じたメッセージがここに表示されます。" + ], + "End of Tour": [ + "ツアーの終わり" + ], + "This concludes the Jupyter Notebook User Interface Tour.": [ + "これで Jupyter Notebook のユーザーインタフェースのツアーは終了です。" + ], + "Edit Attachments": [ + "添付ファイルを編集" + ], + "Cell": [ + "セル" + ], + "Edit Metadata": [ + "メタデータを編集" + ], + "Custom": [ + "カスタム" + ], + "Set the MIME type of the raw cell:": [ + "Raw セルの MIME タイプを設定:" + ], + "Raw Cell MIME Type": [ + "Raw セル MIME タイプ" + ], + "Raw NBConvert Format": [ + "Raw NBConvert 書式" + ], + "Raw Cell Format": [ + "Raw セル書式" + ], + "Slide": [ + "スライド" + ], + "Sub-Slide": [ + "サブスライド" + ], + "Fragment": [ + "フラグメント" + ], + "Skip": [ + "スキップ" + ], + "Notes": [ + "ノート" + ], + "Slide Type": [ + "スライドタイプ" + ], + "Slideshow": [ + "スライドショー" + ], + "Add tag": [ + "タグを追加" + ], + "Edit the list of tags below. All whitespace is treated as tag separators.": [ + "以下のタグ一覧を編集。全ての空白はタグのセパレータとして扱われます。" + ], + "Edit the tags": [ + "タグを編集" + ], + "Edit Tags": [ + "タグを編集" + ], + "Shutdown": [ + "シャットダウン" + ], + "Create a new notebook with %s": [ + "新しいノートブック %s を作成" + ], + "An error occurred while creating a new notebook.": [ + "新しいノートブックの作成中にエラーが発生しました。" + ], + "Creating File Failed": [ + "ファイルの作成に失敗" + ], + "An error occurred while creating a new file.": [ + "新しいファイルの作成中に失敗しました。" + ], + "Creating Folder Failed": [ + "フォルダの作成に失敗" + ], + "An error occurred while creating a new folder.": [ + "新しいフォルダの作成中にエラーが発生しました。" + ], + "Failed to read file": [ + "ファイルの読み込みに失敗しました" + ], + "Failed to read file %s": [ + "%s の読み込みに失敗しました" + ], + "The file size is %d MB. Do you still want to upload it?": [ + "ファイルサイズは %d MB です。本当にアップロードしますか?" + ], + "Large file size warning": [ + "大きいファイルサイズの警告" + ], + "Server error: ": [ + "サーバエラー:" + ], + "The notebook list is empty.": [ + "ノートブック一覧は空です。" + ], + "Click here to rename, delete, etc.": [ + "リネーム、削除、その他を実行するにはここをクリック" + ], + "Running": [ + "実行中" + ], + "Enter a new file name:": [ + "新しいファイル名を入力:" + ], + "Enter a new directory name:": [ + "新しいディレクトリ名を入力:" + ], + "Enter a new name:": [ + "新しい名前を入力:" + ], + "Rename file": [ + "ファイルのリネーム" + ], + "Rename directory": [ + "ディレクトリのリネーム" + ], + "Rename notebook": [ + "ノートブックのリネーム" + ], + "Move": [ + "移動" + ], + "An error occurred while renaming \"%1$s\" to \"%2$s\".": [ + "\"%1$s\" を \"%2$s\" にリネーム中にエラーが発生しました。" + ], + "Rename Failed": [ + "リネームの失敗" + ], + "Enter a new destination directory path for this item:": [ + "このアイテムの移動先を入力して下さい:", + "%d 個のアイテムの移動先を入力して下さい:" + ], + "Move an Item": [ + "アイテムの移動", + "%d 個のアイテムの移動" + ], + "An error occurred while moving \"%1$s\" from \"%2$s\" to \"%3$s\".": [ + "\"%1$s\" を \"%2$s\" から \"%3$s\" に移動する際に失敗しました。" + ], + "Move Failed": [ + "ファイルの移動に失敗" + ], + "Are you sure you want to permanently delete: \"%s\"?": [ + "本当に \"%s\" を完全に削除しますか?", + "本当に %d 個のファイルまたはディレクトリを完全に削除しますか?" + ], + "An error occurred while deleting \"%s\".": [ + "\"%s\" を削除中にエラーが発生しました。" + ], + "Delete Failed": [ + "削除に失敗" + ], + "Are you sure you want to duplicate: \"%s\"?": [ + "本当に \"%s\" を複製しますか?", + "本当に %d 個のファイルを複製しますか?" + ], + "Duplicate": [ + "複製" + ], + "An error occurred while duplicating \"%s\".": [ + "\"%s\" の複製中に失敗" + ], + "Duplicate Failed": [ + "複製に失敗" + ], + "Upload": [ + "アップロード" + ], + "Invalid file name": [ + "ファイル名が不正です" + ], + "File names must be at least one character and not start with a period": [ + "ファイル名はピリオドで始まっていない1文字以上の名前でなければなりません" + ], + "Cannot upload invalid Notebook": [ + "不正なノートブックのためアップロードできません" + ], + "There is already a file named \"%s\". Do you want to replace it?": [ + "既に \"%s\" という名前のファイルが存在します。入れ替えますか?" + ], + "Replace file": [ + "ファイルの入れ替え" + ] + } + } +} \ No newline at end of file diff --git a/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.po b/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.po index aea692a163..5e103ede5f 100644 --- a/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.po +++ b/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.po @@ -152,7 +152,7 @@ msgstr "Jupyter Notebook について" #: notebook/static/notebook/js/about.js:47 msgid "unable to contact kernel" -msgstr "kernel と通信できません" +msgstr "カーネルと通信できません" #: notebook/static/notebook/js/actions.js:69 msgid "toggle rtl layout" diff --git a/notebook/i18n/ja_JP/LC_MESSAGES/nbui.mo b/notebook/i18n/ja_JP/LC_MESSAGES/nbui.mo new file mode 100644 index 0000000000..88df164c4a Binary files /dev/null and b/notebook/i18n/ja_JP/LC_MESSAGES/nbui.mo differ diff --git a/notebook/i18n/ja_JP/LC_MESSAGES/notebook.mo b/notebook/i18n/ja_JP/LC_MESSAGES/notebook.mo new file mode 100644 index 0000000000..e82098d284 Binary files /dev/null and b/notebook/i18n/ja_JP/LC_MESSAGES/notebook.mo differ diff --git a/notebook/i18n/nl/LC_MESSAGES/nbui.mo b/notebook/i18n/nl/LC_MESSAGES/nbui.mo index 3e762e8b1e..3f7b987da1 100644 Binary files a/notebook/i18n/nl/LC_MESSAGES/nbui.mo and b/notebook/i18n/nl/LC_MESSAGES/nbui.mo differ diff --git a/notebook/i18n/nl/LC_MESSAGES/notebook.mo b/notebook/i18n/nl/LC_MESSAGES/notebook.mo index 404e443fe4..c24fab0f82 100644 Binary files a/notebook/i18n/nl/LC_MESSAGES/notebook.mo and b/notebook/i18n/nl/LC_MESSAGES/notebook.mo differ diff --git a/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.json b/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.json new file mode 100644 index 0000000000..39756fef13 --- /dev/null +++ b/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.json @@ -0,0 +1,1360 @@ +{ + "domain": "nbjs", + "locale_data": { + "nbjs": { + "": { + "domain": "nbjs", + "plural_forms": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);", + "lang": "ru_RU" + }, + "Manually edit the JSON below to manipulate the metadata for this cell.": [ + "Отредактируйте JSON ниже вручную для управления метаданными этой ячейки." + ], + "Manually edit the JSON below to manipulate the metadata for this notebook.": [ + "Отредактируйте JSON ниже вручную для управления метаданными этого блокнота." + ], + " We recommend putting custom metadata attributes in an appropriately named substructure, so they don't conflict with those of others.": [ + " Мы рекомендуем поместить пользовательские атрибуты метаданных в подструктуру с соответствующим именем, чтобы они не конфликтовали с атрибутами других объектов." + ], + "Edit the metadata": [ + "Редактировать метаданные" + ], + "Edit Notebook Metadata": [ + "Редактировать метаданные блокнота" + ], + "Edit Cell Metadata": [ + "Редактировать метаданные ячейки" + ], + "Cancel": [ + "Отмена" + ], + "Edit": [ + "Редактировать" + ], + "OK": [ + "ОК" + ], + "Apply": [ + "Применить" + ], + "WARNING: Could not save invalid JSON.": [ + "ПРЕДУПРЕЖДЕНИЕ: не удалось сохранить недопустимый JSON." + ], + "There are no attachments for this cell.": [ + "Для этой ячейки нет никаких вложений." + ], + "Current cell attachments": [ + "Вложения текущей ячейки" + ], + "Attachments": [ + "Вложения" + ], + "Restore": [ + "Восстановить" + ], + "Delete": [ + "Удалить" + ], + "Edit attachments": [ + "Редактировать вложения" + ], + "Edit Notebook Attachments": [ + "Редактировать вложения блокнота" + ], + "Edit Cell Attachments": [ + "Редактировать вложения ячейки" + ], + "Select a file to insert.": [ + "Выберите файл для вставки." + ], + "Select a file": [ + "Выберите файл" + ], + "You are using Jupyter notebook.": [ + "Вы используете блокнот Jupyter." + ], + "The version of the notebook server is: ": [ + "Версия сервера блокнотов: " + ], + "The server is running on this version of Python:": [ + "Сервер работает на этой версии Python:" + ], + "Waiting for kernel to be available...": [ + "Ожидание доступности ядра..." + ], + "Server Information:": [ + "Информация сервера:" + ], + "Current Kernel Information:": [ + "Информация о текущем ядре:" + ], + "Could not access sys_info variable for version information.": [ + "Невозможно получить доступ к переменной sys_info для получения информации о версии." + ], + "Cannot find sys_info!": [ + "Невозможно найти sys_info!" + ], + "About Jupyter Notebook": [ + "О блокноте Jupyter" + ], + "unable to contact kernel": [ + "невозможно связаться с ядром" + ], + "toggle rtl layout": [ + "переключить макет rtl" + ], + "Toggle the screen directionality between left-to-right and right-to-left": [ + "Переключить направление экрана между слева направо и справа налево" + ], + "edit command mode keyboard shortcuts": [ + "редактировать сочетания клавиш командного режима" + ], + "Open a dialog to edit the command mode keyboard shortcuts": [ + "Открыть диалоговое окно для редактирования сочетаний клавиш командного режима" + ], + "restart kernel": [ + "перезапустить ядро" + ], + "restart the kernel (no confirmation dialog)": [ + "перезапустить ядро (без диалогового окна подтверждения)" + ], + "confirm restart kernel": [ + "подтвердить перезапуск ядра" + ], + "restart the kernel (with dialog)": [ + "перезапустить ядро (с диалоговым окном)" + ], + "restart kernel and run all cells": [ + "перезапустить ядро и запустить все ячейки" + ], + "restart the kernel, then re-run the whole notebook (no confirmation dialog)": [ + "перезапустить ядро, а затем перезапустить весь блокнот (без диалогового окна подтверждения)" + ], + "confirm restart kernel and run all cells": [ + "подтвердить перезапуск ядра и запуск всех ячеек" + ], + "restart the kernel, then re-run the whole notebook (with dialog)": [ + "перезапустить ядро, а затем перезапустить весь блокнот (с диалогом подтверждения)" + ], + "restart kernel and clear output": [ + "перезапуск ядра и очистка вывода" + ], + "restart the kernel and clear all output (no confirmation dialog)": [ + "перезапустить ядро и очистить все выходные данные (диалог подтверждения отсутствует)" + ], + "confirm restart kernel and clear output": [ + "подтвердить перезапуск ядра и очистку вывода" + ], + "restart the kernel and clear all output (with dialog)": [ + "перезапустить ядро и очистить все выходные данные (с диалогом подтверждения)" + ], + "interrupt the kernel": [ + "прервать работу ядра" + ], + "run cell and select next": [ + "запустить ячейку и выбрать следующую" + ], + "run cell, select below": [ + "запустить ячейку, выбрать ниже" + ], + "run selected cells": [ + "запустить выбранные ячейки" + ], + "run cell and insert below": [ + "запустить ячейку и вставить ниже" + ], + "run all cells": [ + "запустить все ячейки" + ], + "run all cells above": [ + "запустить все ячейки выше" + ], + "run all cells below": [ + "запустить все ячейки ниже" + ], + "enter command mode": [ + "войти в командный режим" + ], + "insert image": [ + "вставить изображение" + ], + "cut cell attachments": [ + "вырезать вложения ячейки" + ], + "copy cell attachments": [ + "копировать вложения ячейки" + ], + "paste cell attachments": [ + "вставить вложения ячейки" + ], + "split cell at cursor": [ + "разделить ячейку на которую указывает курсор" + ], + "enter edit mode": [ + "войти в режим редактирования" + ], + "select previous cell": [ + "выбрать предыдущую ячейку" + ], + "select cell above": [ + "выбрать ячейку выше" + ], + "select next cell": [ + "выбрать следующую ячейку" + ], + "select cell below": [ + "выбрать ячейку ниже" + ], + "extend selection above": [ + "расширить выбор выше" + ], + "extend selected cells above": [ + "расширить выделенные ячейки выше" + ], + "extend selection below": [ + "расширить выбор ниже" + ], + "extend selected cells below": [ + "расширить выделенные ячейки ниже" + ], + "cut selected cells": [ + "вырезать выбранные ячейки" + ], + "copy selected cells": [ + "копировать выбранные ячейки" + ], + "paste cells above": [ + "вставить ячейки выше" + ], + "paste cells below": [ + "вставить ячейки ниже" + ], + "insert cell above": [ + "вставить ячейку выше" + ], + "insert cell below": [ + "вставить ячейку ниже" + ], + "change cell to code": [ + "изменить ячейку на код" + ], + "change cell to markdown": [ + "изменить ячейку на markdown" + ], + "change cell to raw": [ + "изменить ячейку на raw" + ], + "change cell to heading 1": [ + "изменить ячейку на заголовок 1" + ], + "change cell to heading 2": [ + "изменить ячейку на заголовок 2" + ], + "change cell to heading 3": [ + "изменить ячейку на заголовок 3" + ], + "change cell to heading 4": [ + "изменить ячейку на заголовок 4" + ], + "change cell to heading 5": [ + "изменить ячейку на заголовок 5" + ], + "change cell to heading 6": [ + "изменить ячейку на заголовок 6" + ], + "toggle cell output": [ + "переключить вывод ячейки" + ], + "toggle output of selected cells": [ + "переключить вывод выбранных ячеек" + ], + "toggle cell scrolling": [ + "переключить прокрутку ячейки" + ], + "toggle output scrolling of selected cells": [ + "переключить прокрутку вывода выбранных ячеек" + ], + "clear cell output": [ + "очистить вывод ячейки" + ], + "clear output of selected cells": [ + "очистить вывод выбранных ячеек" + ], + "move cells down": [ + "переместить ячейки вниз" + ], + "move selected cells down": [ + "переместить выбранные ячейки вниз" + ], + "move cells up": [ + "переместить ячейки вверх" + ], + "move selected cells up": [ + "переместить выбранные ячейки вверх" + ], + "toggle line numbers": [ + "переключить номера строк" + ], + "show keyboard shortcuts": [ + "показать сочетания клавиш клавиатуры" + ], + "delete cells": [ + "удалить ячейки" + ], + "delete selected cells": [ + "удалить выбранные ячейки" + ], + "undo cell deletion": [ + "отменить удаление ячейки" + ], + "merge cell with previous cell": [ + "объединить ячейку с предыдущей ячейкой" + ], + "merge cell above": [ + "объединить ячейку выше" + ], + "merge cell with next cell": [ + "объединить ячейку со следующей ячейкой" + ], + "merge cell below": [ + "объединить ячейку ниже" + ], + "merge selected cells": [ + "объединить выбранные ячейки" + ], + "merge cells": [ + "объединить ячейки" + ], + "merge selected cells, or current cell with cell below if only one cell is selected": [ + "объединить выбранные ячейки или текущую с ячейкой ниже, если выбрана только одна ячейка" + ], + "show command pallette": [ + "показать палитру команд" + ], + "open the command palette": [ + "открыть палитру команд" + ], + "toggle all line numbers": [ + "переключить все номера строк" + ], + "toggles line numbers in all cells, and persist the setting": [ + "переключить номера строк во всех ячейках и сохранить настройки" + ], + "show all line numbers": [ + "показать все номера строк" + ], + "show line numbers in all cells, and persist the setting": [ + "показать номера строк во всех ячейках и сохранить настройки" + ], + "hide all line numbers": [ + "скрыть все номера строк" + ], + "hide line numbers in all cells, and persist the setting": [ + "скрыть номера строк во всех ячейках и сохранить настройки" + ], + "toggle header": [ + "переключить заголовок" + ], + "switch between showing and hiding the header": [ + "переключить заголовок между отображением и скрытием заголовка" + ], + "show the header": [ + "показать заголовок" + ], + "hide the header": [ + "скрыть заголовок" + ], + "toggle toolbar": [ + "переключить панель инструментов" + ], + "switch between showing and hiding the toolbar": [ + "переключить между отображением и скрытием панели инструментов" + ], + "show the toolbar": [ + "показать панель инструментов" + ], + "hide the toolbar": [ + "скрыть панель инструментов" + ], + "close the pager": [ + "закрыть пейджер" + ], + "ignore": [ + "игнорировать" + ], + "move cursor up": [ + "переместить курсор вверх" + ], + "move cursor down": [ + "переместить курсор вниз" + ], + "scroll notebook down": [ + "прокрутить блокнот вниз" + ], + "scroll notebook up": [ + "прокрутить блокнот вверх" + ], + "scroll cell center": [ + "прокрутить ячейку в центр" + ], + "Scroll the current cell to the center": [ + "Прокрутить текущую ячейку к центру" + ], + "scroll cell top": [ + "прокрутить ячейку вверх" + ], + "Scroll the current cell to the top": [ + "Прокрутить текущую ячейку к верху" + ], + "duplicate notebook": [ + "дублировать блокнот" + ], + "Create and open a copy of the current notebook": [ + "Создать и открыть копию текущего блокнота" + ], + "trust notebook": [ + "доверять блокноту" + ], + "Trust the current notebook": [ + "Доверять текущему блокноту" + ], + "rename notebook": [ + "переименовать блокнот" + ], + "Rename the current notebook": [ + "Переименовать текущий блокнот" + ], + "toggle all cells output collapsed": [ + "переключить сворачивание вывода всех ячеек" + ], + "Toggle the hidden state of all output areas": [ + "Переключить скрытое состояние всех областей вывода" + ], + "toggle all cells output scrolled": [ + "переключить прокрутку вывода всех ячеек" + ], + "Toggle the scrolling state of all output areas": [ + "Переключить состояние прокрутки всех областей вывода" + ], + "clear all cells output": [ + "очистить вывод всех ячеек" + ], + "Clear the content of all the outputs": [ + "Очистить содержимое всех выводов" + ], + "save notebook": [ + "сохранить блокнот" + ], + "Save and Checkpoint": [ + "Сохранить и поставить контрольную точку" + ], + "Warning: accessing Cell.cm_config directly is deprecated.": [ + "Предупреждение: прямой доступ к Cell.cm_config устарел." + ], + "Unrecognized cell type: %s": [ + "Нераспознанный тип ячейки: %s" + ], + "Unrecognized cell type": [ + "Нераспознанный тип ячейки" + ], + "Error in cell toolbar callback %s": [ + "Ошибка в обратном вызове панели инструментов ячейки %s" + ], + "Clipboard types: %s": [ + "Типы буфера обмена: %s" + ], + "Dialog for paste from system clipboard": [ + "Диалоговое окно для вставки из системного буфера обмена" + ], + "Ctrl-V": [ + "Ctrl-V" + ], + "Cmd-V": [ + "Cmd-V" + ], + "Press %s again to paste": [ + "Нажмите %s еще раз, чтобы вставить" + ], + "Why is this needed? ": [ + "Зачем это нужно? " + ], + "We can't get paste events in this browser without a text box. ": [ + "Мы не можем получить прошлые события в этом браузере без текстового поля. " + ], + "There's an invisible text box focused in this dialog.": [ + "В этом диалоговом окне есть сфокусированное невидимое текстовое поле." + ], + "%s to paste": [ + "%s для вставки" + ], + "Can't execute cell since kernel is not set.": [ + "Невозможно выполнить ячейку, так как ядро не установлено." + ], + "In": [ + "Ввод" + ], + "Could not find a kernel matching %s. Please select a kernel:": [ + "Не удалось найти ядро, соответствующее %s. Пожалуйста, выберите ядро:" + ], + "Continue Without Kernel": [ + "Продолжить без ядра" + ], + "Set Kernel": [ + "Установить ядро" + ], + "Kernel not found": [ + "Ядро не найдено" + ], + "Creating Notebook Failed": [ + "Ошибка создания блокнота" + ], + "The error was: %s": [ + "Ошибка в: %s" + ], + "Run": [ + "Запуск" + ], + "Code": [ + "Код" + ], + "Markdown": [ + "Markdown" + ], + "Raw NBConvert": [ + "Необработанный NBConvert" + ], + "Heading": [ + "Заголовок" + ], + "unrecognized cell type:": [ + "нераспознанный тип ячейки:" + ], + "Failed to retrieve MathJax from '%s'": [ + "Не удалось получить MathJax из '%s'" + ], + "Math/LaTeX rendering will be disabled.": [ + "Рендеринг Math/LaTeX будет отключен." + ], + "Trusted Notebook": [ + "Доверенный блокнот" + ], + "Trust Notebook": [ + "Доверять блокноту" + ], + "None": [ + "Отсутствует" + ], + "No checkpoints": [ + "Контрольные точки отсутствуют" + ], + "Opens in a new window": [ + "Открывается в новом окне" + ], + "Autosave in progress, latest changes may be lost.": [ + "Автосохранение продолжается, последние изменения могут быть потеряны." + ], + "Unsaved changes will be lost.": [ + "Несохраненные изменения будут потеряны." + ], + "The Kernel is busy, outputs may be lost.": [ + "Ядро занято, вывод может быть потерян." + ], + "This notebook is version %1$s, but we only fully support up to %2$s.": [ + "Этот блокнот имеет версию %1$s, но мы полностью поддерживаем только до %2$s." + ], + "You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available.": [ + "Вы все еще можете работать с этим блокнотом, но типы ячеек и выходных данных, представленные в более поздних версиях блокнота, будут недоступны." + ], + "Restart and Run All Cells": [ + "Перезапустить и запустить все ячейки" + ], + "Restart and Clear All Outputs": [ + "Перезапустить и очистить все выводы" + ], + "Restart": [ + "Перезапустить" + ], + "Continue Running": [ + "Продолжить запуск" + ], + "Reload": [ + "Перезагрузить" + ], + "Overwrite": [ + "Перезаписать" + ], + "Trust": [ + "Доверять" + ], + "Revert": [ + "Откат" + ], + "Newer Notebook": [ + "Новый блокнот" + ], + "Use markdown headings": [ + "Использовать заголовки markdown" + ], + "Jupyter no longer uses special heading cells. Instead, write your headings in Markdown cells using # characters:": [ + "Jupyter больше не использует специальные ячейки заголовка. Вместо этого указывайте заголовки в ячейках Markdown, используя символы #:" + ], + "## This is a level 2 heading": [ + "## Это заголовок 2 уровня" + ], + "Restart kernel and re-run the whole notebook?": [ + "Перезапустить ядро и перезапустить все блокноты?" + ], + "Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost.": [ + "Вы уверены, что хотите перезапустить текущее ядро и повторно выполнить весь блокнот? Все переменные и выходные данные будут потеряны." + ], + "Restart kernel and clear all output?": [ + "Перезапустить ядро и очистить весь вывод?" + ], + "Do you want to restart the current kernel and clear all output? All variables and outputs will be lost.": [ + "Вы хотите перезапустить текущее ядро и очистить весь вывод? Все переменные и выводы будут потеряны." + ], + "Restart kernel?": [ + "Перезапустить ядро?" + ], + "Do you want to restart the current kernel? All variables will be lost.": [ + "Вы хотите перезапустить текущее ядро? Все переменные будут потеряны." + ], + "Shutdown kernel?": [ + "Выключить ядро?" + ], + "Do you want to shutdown the current kernel? All variables will be lost.": [ + "Вы хотите выключить текущее ядро? Все переменные будут потеряны." + ], + "Notebook changed": [ + "Блокнот изменен" + ], + "The notebook file has changed on disk since the last time we opened or saved it. Do you want to overwrite the file on disk with the version open here, or load the version on disk (reload the page) ?": [ + "Файл блокнота изменился на диске с момента его последнего открытия или сохранения. Вы хотите перезаписать файл на диске открытой здесь версией или загрузить версию с диска (перезагрузить страницу)?" + ], + "Notebook validation failed": [ + "Ошибка проверки блокнота" + ], + "The save operation succeeded, but the notebook does not appear to be valid. The validation error was:": [ + "Операция сохранения прошла успешно, но блокнот, похоже, недействителен. Ошибка проверки:" + ], + "A trusted Jupyter notebook may execute hidden malicious code when you open it. Selecting trust will immediately reload this notebook in a trusted state. For more information, see the Jupyter security documentation: ": [ + "Доверенный блокнот Jupyter может выполнять скрытый вредоносный код при открытии. При выборе параметра доверие этот блокнот будет немедленно перезагружен в доверенное состояние. Дополнительные сведения см. в документации по безопасности Jupiter: " + ], + "here": [ + "здесь" + ], + "Trust this notebook?": [ + "Доверять этому блокноту?" + ], + "Notebook failed to load": [ + "Ошибка загрузки блокнота" + ], + "The error was: ": [ + "Ошибка в: " + ], + "See the error console for details.": [ + "Дополнительные сведения см. в консоли ошибок." + ], + "The notebook also failed validation:": [ + "Блокнот также не прошел проверку:" + ], + "An invalid notebook may not function properly. The validation error was:": [ + "Неисправный блокнот может работать неправильно. Ошибка проверки:" + ], + "This notebook has been converted from an older notebook format to the current notebook format v(%s).": [ + "Этот блокнот был преобразован из более старого формата блокнота в текущий формат v(%s)." + ], + "This notebook has been converted from a newer notebook format to the current notebook format v(%s).": [ + "Этот блокнот был преобразован из более нового формата блокнота в текущий формат v(%s)." + ], + "The next time you save this notebook, the current notebook format will be used.": [ + "При следующем сохранении этого блокнота будет использоваться текущий формат блокнота." + ], + "Older versions of Jupyter may not be able to read the new format.": [ + "Старые версии Jupiter могут быть не в состоянии прочитать новый формат." + ], + "Some features of the original notebook may not be available.": [ + "Некоторые функции оригинального блокнота могут быть недоступны." + ], + "To preserve the original version, close the notebook without saving it.": [ + "Чтобы сохранить исходную версию, закройте блокнот не сохраняя его." + ], + "Notebook converted": [ + "Блокнот преобразован" + ], + "(No name)": [ + "(Без имени)" + ], + "An unknown error occurred while loading this notebook. This version can load notebook formats %s or earlier. See the server log for details.": [ + "При загрузке этого блокнота произошла неизвестная ошибка. Эта версия может загружать форматы блокнотов %s или более ранние. Дополнительные сведения см. в журнале сервера." + ], + "Error loading notebook": [ + "Ошибка загрузки блокнота" + ], + "Are you sure you want to revert the notebook to the latest checkpoint?": [ + "Вы уверены, что хотите вернуть блокнот к последней контрольной точке?" + ], + "This cannot be undone.": [ + "Этого уже не исправить." + ], + "The checkpoint was last updated at:": [ + "Последний раз контрольная точка обновлялась:" + ], + "Revert notebook to checkpoint": [ + "Откатить блокнот до контрольной точки" + ], + "Edit Mode": [ + "Режим редактирования" + ], + "Command Mode": [ + "Командный режим" + ], + "Kernel Created": [ + "Ядро создано" + ], + "Connecting to kernel": [ + "Подключение к ядру" + ], + "Not Connected": [ + "Не подключено" + ], + "click to reconnect": [ + "нажмите для переподключения" + ], + "Restarting kernel": [ + "Перезапуск ядра" + ], + "Kernel Restarting": [ + "Перезапуск ядра" + ], + "The kernel appears to have died. It will restart automatically.": [ + "Ядро, по-видимому, умерло. Оно будет перезапущено автоматически." + ], + "Dead kernel": [ + "Мёртвое ядро" + ], + "Kernel Dead": [ + "Ядро мертво" + ], + "Interrupting kernel": [ + "Прерывание ядра" + ], + "No Connection to Kernel": [ + "Нет связи с ядром" + ], + "A connection to the notebook server could not be established. The notebook will continue trying to reconnect. Check your network connection or notebook server configuration.": [ + "Не удалось установить соединение с сервером блокнота. Блокнот будет продолжать попытки повторного подключения. Проверьте сетевое подключение или конфигурацию сервера блокнота." + ], + "Connection failed": [ + "Ошибка подключения" + ], + "No kernel": [ + "Ядро отсутствует" + ], + "Kernel is not running": [ + "Ядро не запущено" + ], + "Don't Restart": [ + "Не перезагружать" + ], + "Try Restarting Now": [ + "Попробуйте перезагрузить сейчас" + ], + "The kernel has died, and the automatic restart has failed. It is possible the kernel cannot be restarted. If you are not able to restart the kernel, you will still be able to save the notebook, but running code will no longer work until the notebook is reopened.": [ + "Ядро умерло и автоматический перезапуск не удался. Вполне возможно, что ядро не может быть перезапущено. Если вам не удастся перезапустить ядро - вы все равно сможете сохранить блокнот, но запущенный код больше не будет работать до тех пор, пока блокнот не будет снова открыт." + ], + "No Kernel": [ + "Ядро отсутствует" + ], + "Failed to start the kernel": [ + "Не удалось запустить ядро" + ], + "Kernel Busy": [ + "Ядро занято" + ], + "Kernel starting, please wait...": [ + "Перезапуск ядра, пожалуйста подождите..." + ], + "Kernel Idle": [ + "Ядро бездействует" + ], + "Kernel ready": [ + "Ядро готово" + ], + "Using kernel: ": [ + "Использование ядра: " + ], + "Only candidate for language: %1$s was %2$s.": [ + "Единственный кандидат на язык: %1$s был %2$s." + ], + "Loading notebook": [ + "Загрузка блокнота" + ], + "Notebook loaded": [ + "Блокнот загружен" + ], + "Saving notebook": [ + "Сохранение блокнота" + ], + "Notebook saved": [ + "Блокнот сохранен" + ], + "Notebook save failed": [ + "Ошибка сохранения блокнота" + ], + "Notebook copy failed": [ + "Ошибка копирования блокнота" + ], + "Checkpoint created": [ + "Контрольная точка создана" + ], + "Checkpoint failed": [ + "Ошибка контрольной точки" + ], + "Checkpoint deleted": [ + "Контрольная точка удалена" + ], + "Checkpoint delete failed": [ + "Ошибка удаления контрольной точки" + ], + "Restoring to checkpoint...": [ + "Восстановление до контрольной точки..." + ], + "Checkpoint restore failed": [ + "Не удалось восстановить контрольную точку" + ], + "Autosave disabled": [ + "Автосохранение отключено" + ], + "Saving every %d sec.": [ + "Сохранение каждые %d секунд." + ], + "Trusted": [ + "Доверенный" + ], + "Not Trusted": [ + "Не доверять" + ], + "click to expand output": [ + "щелкните чтобы развернуть вывод" + ], + "click to expand output; double click to hide output": [ + "щелкните чтобы развернуть вывод; двойной щелчок чтобы скрыть вывод" + ], + "click to unscroll output; double click to hide": [ + "щелчок для прокрутки вывода; двойной щелчок - чтобы скрыть" + ], + "click to scroll output; double click to hide": [ + "щелчок для прокрутки вывода; двойной щелчок - чтобы скрыть" + ], + "Javascript error adding output!": [ + "Ошибка Javascript при добавлении вывода!" + ], + "See your browser Javascript console for more details.": [ + "Дополнительные сведения см. в разделе консоль Javascript вашего браузера." + ], + "Out[%d]:": [ + "Вывод[%d]:" + ], + "Unrecognized output: %s": [ + "Нераспознанный вывод: %s" + ], + "Open the pager in an external window": [ + "Открыть пейджер во внешнем окне" + ], + "Close the pager": [ + "Закрыть пейджер" + ], + "Jupyter Pager": [ + "Пейджер Jupyter" + ], + "go to cell start": [ + "перейти к началу ячейки" + ], + "go to cell end": [ + "перейти к концу ячейки" + ], + "go one word left": [ + "перейти на слово влево" + ], + "go one word right": [ + "перейти на слово вправо" + ], + "delete word before": [ + "удалить слово перед" + ], + "delete word after": [ + "удалить слово после" + ], + "code completion or indent": [ + "завершение кода или отступ" + ], + "tooltip": [ + "всплывающая подсказка" + ], + "indent": [ + "отступ" + ], + "dedent": [ + "dedent" + ], + "select all": [ + "выбрать всё" + ], + "undo": [ + "отменить" + ], + "redo": [ + "повторить" + ], + "Shift": [ + "Shift" + ], + "Alt": [ + "Alt" + ], + "Up": [ + "Вверх" + ], + "Down": [ + "Вниз" + ], + "Left": [ + "Лево" + ], + "Right": [ + "Право" + ], + "Tab": [ + "Tab" + ], + "Caps Lock": [ + "CapsLock" + ], + "Esc": [ + "Esc" + ], + "Ctrl": [ + "Ctrl" + ], + "Enter": [ + "Enter" + ], + "Page Up": [ + "Страница вверх" + ], + "Page Down": [ + "Страница вниз" + ], + "Home": [ + "Домой" + ], + "End": [ + "Конец" + ], + "Space": [ + "Пробел" + ], + "Backspace": [ + "Бэкспейс" + ], + "Minus": [ + "Минус" + ], + "PageUp": [ + "СтраницаВверх" + ], + "The Jupyter Notebook has two different keyboard input modes.": [ + "Jupyter Notebook имеет два различных режима ввода с клавиатуры." + ], + "Edit mode allows you to type code or text into a cell and is indicated by a green cell border.": [ + "Режим редактирования позволяет вводить код или текст в ячейку и обозначается зеленой рамкой ячейки." + ], + "Command mode binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin.": [ + "Командный режим связывает клавиатуру с командами уровня блокнота и обозначается серой рамкой ячейки с синим левым краем." + ], + "Close": [ + "Закрыть" + ], + "Keyboard shortcuts": [ + "Сочетания клавиш" + ], + "Command": [ + "Команда" + ], + "Control": [ + "Управление" + ], + "Option": [ + "Параметр" + ], + "Return": [ + "Возврат" + ], + "Command Mode (press %s to enable)": [ + "Командный режим (нажмите %s для включения)" + ], + "Edit Shortcuts": [ + "Редактировать ярлыки" + ], + "edit command-mode keyboard shortcuts": [ + "редактировать сочетания клавиш командного-режима" + ], + "Edit Mode (press %s to enable)": [ + "Режим редактирования (нажмите %s для включения)" + ], + "Autosave Failed!": [ + "Автосохранение не удалось!" + ], + "Rename": [ + "Переименование" + ], + "Enter a new notebook name:": [ + "Введите новое имя блокнота:" + ], + "Rename Notebook": [ + "Переименовать блокнот" + ], + "Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:": [ + "Недействительное имя блокнота. Имена блокнотов должны иметь 1 или более символов и могут содержать любые символы, кроме :/\\. Пожалуйста, введите новое имя блокнота:" + ], + "Renaming...": [ + "Переименование..." + ], + "Unknown error": [ + "Неизвестная ошибка" + ], + "no checkpoint": [ + "контрольная точка отсутствует" + ], + "Last Checkpoint: %s": [ + "Последняя контрольная точка: %s" + ], + "(unsaved changes)": [ + "(несохраненные изменения)" + ], + "(autosaved)": [ + "(автосохранение)" + ], + "Warning: too many matches (%d). Some changes might not be shown or applied.": [ + "Предупреждение: слишком много совпадений (%d). Некоторые изменения могут быть не показаны или не применены." + ], + "%d match": [ + "%d совпадение", + "%d совпадения", + "%d совпадений" + ], + "More than 100 matches, aborting": [ + "Более 100 совпадений, прерывание" + ], + "Use regex (JavaScript regex syntax)": [ + "Используйте регулярное выражение (синтаксис регулярных выражений JavaScript)" + ], + "Replace in selected cells": [ + "Заменить в выбранных ячейках" + ], + "Match case": [ + "Учитывать регистр" + ], + "Find": [ + "Поиск" + ], + "Replace": [ + "Замена" + ], + "No matches, invalid or empty regular expression": [ + "Нет совпадений, недопустимое или пустое регулярное выражение" + ], + "Replace All": [ + "Заменить всё" + ], + "Find and Replace": [ + "Поиск и замена" + ], + "find and replace": [ + "поиск и замена" + ], + "Write raw LaTeX or other formats here, for use with nbconvert. It will not be rendered in the notebook. When passing through nbconvert, a Raw Cell's content is added to the output unmodified.": [ + "Пишите сюда необработанный LaTeX или другие форматы для использования с nbconvert. Он не будет отображаться в блокноте. При прохождении через nbconvert содержимое необработанных ячеек добавляется к выводу неизмененным." + ], + "Grow the tooltip vertically (press shift-tab twice)": [ + "Растяните всплывающую подсказку вертикально (нажмите shift-tab дважды)" + ], + "show the current docstring in pager (press shift-tab 4 times)": [ + "показать текущую строку документа в пейджере (нажмите shift-tab 4 раза)" + ], + "Open in Pager": [ + "Открыть в пейджере" + ], + "Tooltip will linger for 10 seconds while you type": [ + "Всплывающая подсказка будет задерживаться в течение 10 секунд, пока вы набираете текст" + ], + "Welcome to the Notebook Tour": [ + "Добро пожаловать в тур по Notebook" + ], + "You can use the left and right arrow keys to go backwards and forwards.": [ + "Вы можете использовать клавиши со стрелками влево и вправо чтобы двигаться назад и вперед." + ], + "Filename": [ + "Имя файла" + ], + "Click here to change the filename for this notebook.": [ + "Нажмите здесь чтобы изменить имя файла для этого блокнота." + ], + "Notebook Menubar": [ + "Строка меню блокнота" + ], + "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with.": [ + "Панель меню имеет меню для действий с блокнотом, ее ячейками и ядром, с которым она взаимодействует." + ], + "Notebook Toolbar": [ + "Панель инструментов блокнота" + ], + "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information.": [ + "На панели инструментов есть кнопки для наиболее распространенных действий. Наведите курсор мыши на каждую кнопку для получения дополнительной информации." + ], + "Mode Indicator": [ + "Индикатор режима" + ], + "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.": [ + "Notebook имеет два режима работы: режим редактирования и командный режим. В этой области может появиться индикатор, сообщающий вам, в каком режиме вы находитесь." + ], + "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area.": [ + "Прямо сейчас вы находитесь в командном режиме и многие сочетания клавиш доступны. В этом режиме значок в области индикатора не отображается." + ], + "Pressing Enter or clicking in the input text area of the cell switches to Edit Mode.": [ + "Нажатие кнопки Enter или щелчок в области ввода текста ячейки переключает ее в режим редактирования." + ], + "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell.": [ + "Обратите внимание, что граница вокруг текущей активной ячейки изменила цвет. Ввод приведет к вставке текста в текущую активную ячейку." + ], + "Back to Command Mode": [ + "Возврат к командному режиму" + ], + "Pressing Esc or clicking outside of the input text area takes you back to Command Mode.": [ + "Нажатие кнопки Esc или щелчок за пределами области ввода текста возвращает вас в командный режим." + ], + "Keyboard Shortcuts": [ + "Сочетания клавиш" + ], + "You can click here to get a list of all of the keyboard shortcuts.": [ + "Вы можете нажать здесь, чтобы получить список всех сочетаний клавиш." + ], + "Kernel Indicator": [ + "Индикатор ядра" + ], + "This is the Kernel indicator. It looks like this when the Kernel is idle.": [ + "Это и есть индикатор ядра. Он выглядит так, когда ядро простаивает." + ], + "The Kernel indicator looks like this when the Kernel is busy.": [ + "Индикатор ядра выглядит так, когда ядро занято." + ], + "Interrupting the Kernel": [ + "Прерывание ядра" + ], + "To cancel a computation in progress, you can click here.": [ + "Чтобы отменить выполняемые вычисления - вы можете нажать здесь." + ], + "Notification Area": [ + "Область уведомлений" + ], + "Messages in response to user actions (Save, Interrupt, etc.) appear here.": [ + "Сообщения в ответ на действия пользователя (сохранение, прерывание и т. д.) появятся здесь." + ], + "End of Tour": [ + "Конец обзора" + ], + "This concludes the Jupyter Notebook User Interface Tour.": [ + "На этом завершается экскурсия по пользовательскому интерфейсу Jupyter Notebook." + ], + "Edit Attachments": [ + "Редактировать вложения" + ], + "Cell": [ + "Ячейка" + ], + "Edit Metadata": [ + "Редактировать метаданные" + ], + "Custom": [ + "Пользовательский" + ], + "Set the MIME type of the raw cell:": [ + "Установите тип MIME необработанной ячейки:" + ], + "Raw Cell MIME Type": [ + "Необработанный MIME-тип ячейки" + ], + "Raw NBConvert Format": [ + "Необработанный формат NBConvert" + ], + "Raw Cell Format": [ + "Необработанный формат ячейки" + ], + "Slide": [ + "Слайд" + ], + "Sub-Slide": [ + "Под-слайд" + ], + "Fragment": [ + "Фрагмент" + ], + "Skip": [ + "Пропустить" + ], + "Notes": [ + "Примечания" + ], + "Slide Type": [ + "Тип слайда" + ], + "Slideshow": [ + "Слайд-шоу" + ], + "Add tag": [ + "Добавить тег" + ], + "Edit the list of tags below. All whitespace is treated as tag separators.": [ + "Отредактируйте список тегов ниже. Все пробелы рассматриваются как разделители тегов." + ], + "Edit the tags": [ + "Редактировать теги" + ], + "Edit Tags": [ + "Редактировать теги" + ], + "Shutdown": [ + "Выключение" + ], + "Create a new notebook with %s": [ + "Создать новый блокнот с %s" + ], + "An error occurred while creating a new notebook.": [ + "При создании нового блокнота произошла ошибка." + ], + "Creating File Failed": [ + "Ошибка создания файла" + ], + "An error occurred while creating a new file.": [ + "При создании нового файла произошла ошибка." + ], + "Creating Folder Failed": [ + "Ошибка создания папки" + ], + "An error occurred while creating a new folder.": [ + "При создании новой папки произошла ошибка." + ], + "Failed to read file": [ + "Ошибка чтения файла" + ], + "Failed to read file %s": [ + "Ошибка чтения файла %s" + ], + "The file size is %d MB. Do you still want to upload it?": [ + "Размер файла - %d MB. Вы все еще хотите загрузить его?" + ], + "Large file size warning": [ + "Предупреждение о большом размере файла" + ], + "Server error: ": [ + "Ошибка сервера: " + ], + "The notebook list is empty.": [ + "Список блокнотов пуст." + ], + "Click here to rename, delete, etc.": [ + "Нажмите здесь, чтобы переименовать, удалить и т.д." + ], + "Running": [ + "Запустить" + ], + "Enter a new file name:": [ + "Введите новое имя файла:" + ], + "Enter a new directory name:": [ + "Введите новое название каталога:" + ], + "Enter a new name:": [ + "Введите новое имя:" + ], + "Rename file": [ + "Переименовать файл" + ], + "Rename directory": [ + "Переименовать директорию" + ], + "Rename notebook": [ + "Переименовать блокнот" + ], + "Move": [ + "Перемещение" + ], + "An error occurred while renaming \"%1$s\" to \"%2$s\".": [ + "Произошла ошибка при переименовании \"%1$s\" в \"%2$s\"." + ], + "Rename Failed": [ + "Ошибка переименования" + ], + "Enter a new destination directory path for this item:": [ + "Введите новый путь к каталогу назначения для этого %d элемента:", + "Введите новый путь к каталогу назначения для этих %d элементов:", + "Введите новый путь к каталогу назначения для этих %d элементов:" + ], + "Move an Item": [ + "Переместить %d элемент", + "Переместить %d элемента", + "Переместить %d элементов" + ], + "An error occurred while moving \"%1$s\" from \"%2$s\" to \"%3$s\".": [ + "Произошла ошибка при перемещении \"%1$s\" из \"%2$s\" в \"%3$s\"." + ], + "Move Failed": [ + "Ошибка перемещения" + ], + "Are you sure you want to permanently delete: \"%s\"?": [ + "Вы уверены, что хотите навсегда удалить файл или папку \"%s\"?", + "Вы уверены, что хотите навсегда удалить %d выбранных файла или папки?", + "Вы уверены, что хотите навсегда удалить %d выбранных файлов или папок?" + ], + "An error occurred while deleting \"%s\".": [ + "Произошла ошибка при удалении \"%s\"." + ], + "Delete Failed": [ + "Ошибка удаления" + ], + "Are you sure you want to duplicate: \"%s\"?": [ + "Вы уверены, что хотите скопировать файл \"%s\"?", + "Вы уверены, что хотите скопировать %d выбранных файла?", + "Вы уверены, что хотите скопировать %d выбранных файлов?" + ], + "Duplicate": [ + "Скопировать" + ], + "An error occurred while duplicating \"%s\".": [ + "Произошла ошибка при копировании \"%s\"." + ], + "Duplicate Failed": [ + "Ошибка дублирования" + ], + "Upload": [ + "Загрузить" + ], + "Invalid file name": [ + "Неверное имя файла" + ], + "File names must be at least one character and not start with a period": [ + "Имена файлов должны состоять как минимум из одного символа и не начинаться с точки" + ], + "Cannot upload invalid Notebook": [ + "Невозможно загрузить недействительный Notebook" + ], + "There is already a file named \"%s\". Do you want to replace it?": [ + "Там уже имеется файл с именем \"%s\". Вы хотите заменить его?" + ], + "Replace file": [ + "Заменить файл" + ] + } + } +} \ No newline at end of file diff --git a/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.po b/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.po index 644d2e18b1..0013c8d683 100644 --- a/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.po +++ b/notebook/i18n/ru_RU/LC_MESSAGES/nbjs.po @@ -23,14 +23,14 @@ msgstr "" #: notebook/static/base/js/dialog.js:161 msgid "Manually edit the JSON below to manipulate the metadata for this cell." msgstr "" -"Отредактируйте JSON ниже вручную для управления метаданными для этой ячейки." +"Отредактируйте JSON ниже вручную для управления метаданными этой ячейки." #: notebook/static/base/js/dialog.js:163 msgid "" "Manually edit the JSON below to manipulate the metadata for this notebook." msgstr "" -"Отредактируйте JSON ниже вручную для управления метаданными для этого " -"блокнота." +"Отредактируйте JSON ниже вручную для управления метаданными этого блокнота." + #: notebook/static/base/js/dialog.js:165 msgid "" @@ -112,7 +112,7 @@ msgstr "Редактировать вложения" #: notebook/static/base/js/dialog.js:348 msgid "Edit Notebook Attachments" -msgstr "Редактировать вложения блокнотов" +msgstr "Редактировать вложения блокнота" #: notebook/static/base/js/dialog.js:350 msgid "Edit Cell Attachments" @@ -175,7 +175,7 @@ msgstr "переключить макет rtl" #: notebook/static/notebook/js/actions.js:70 msgid "" "Toggle the screen directionality between left-to-right and right-to-left" -msgstr "Переключение направления экрана между слева направо и справа налево" +msgstr "Переключить направление экрана между слева направо и справа налево" #: notebook/static/notebook/js/actions.js:76 msgid "edit command mode keyboard shortcuts" @@ -184,16 +184,16 @@ msgstr "редактировать сочетания клавиш команд #: notebook/static/notebook/js/actions.js:77 msgid "Open a dialog to edit the command mode keyboard shortcuts" msgstr "" -"Откройте диалоговое окно для редактирования сочетаний клавиш командного " +"Открыть диалоговое окно для редактирования сочетаний клавиш командного " "режима" #: notebook/static/notebook/js/actions.js:97 msgid "restart kernel" -msgstr "перезапуск ядра" +msgstr "перезапустить ядро" #: notebook/static/notebook/js/actions.js:98 msgid "restart the kernel (no confirmation dialog)" -msgstr "перезапуск ядра (диалог подтверждения отсутствует)" +msgstr "перезапустить ядро (без диалогового окна подтверждения)" #: notebook/static/notebook/js/actions.js:106 msgid "confirm restart kernel" @@ -201,18 +201,18 @@ msgstr "подтвердить перезапуск ядра" #: notebook/static/notebook/js/actions.js:107 msgid "restart the kernel (with dialog)" -msgstr "перезапуск ядра (с диалоговым окном)" +msgstr "перезапустить ядро (с диалоговым окном)" #: notebook/static/notebook/js/actions.js:113 msgid "restart kernel and run all cells" -msgstr "перезапуск ядра и запуск всех ячеек" +msgstr "перезапустить ядро и запустить все ячейки" #: notebook/static/notebook/js/actions.js:114 msgid "" "restart the kernel, then re-run the whole notebook (no confirmation dialog)" msgstr "" -"перезапустить ядро, а затем перезапустить весь блокнот (диалог подтверждения " -"отсутствует)" +"перезапустить ядро, а затем перезапустить весь блокнот (без диалогового окна " +"подтверждения)" #: notebook/static/notebook/js/actions.js:120 msgid "confirm restart kernel and run all cells" @@ -269,22 +269,22 @@ msgstr "запустить ячейку и вставить ниже" #: notebook/static/notebook/js/actions.js:175 #: notebook/static/notebook/js/actions.js:176 msgid "run all cells" -msgstr "запуск всех ячеек" +msgstr "запустить все ячейки" #: notebook/static/notebook/js/actions.js:183 #: notebook/static/notebook/js/actions.js:184 msgid "run all cells above" -msgstr "запуск всех ячеек выше" +msgstr "запустить все ячейки выше" #: notebook/static/notebook/js/actions.js:190 #: notebook/static/notebook/js/actions.js:191 msgid "run all cells below" -msgstr "запуск всех ячеек ниже" +msgstr "запустить все ячейки ниже" #: notebook/static/notebook/js/actions.js:197 #: notebook/static/notebook/js/actions.js:198 msgid "enter command mode" -msgstr "вход в командный режим" +msgstr "войти в командный режим" #: notebook/static/notebook/js/actions.js:205 #: notebook/static/notebook/js/actions.js:206 @@ -299,7 +299,7 @@ msgstr "вырезать вложения ячейки" #: notebook/static/notebook/js/actions.js:221 #: notebook/static/notebook/js/actions.js:222 msgid "copy cell attachments" -msgstr "копирование вложений ячейки" +msgstr "копировать вложения ячейки" #: notebook/static/notebook/js/actions.js:229 #: notebook/static/notebook/js/actions.js:230 @@ -309,12 +309,12 @@ msgstr "вставить вложения ячейки" #: notebook/static/notebook/js/actions.js:237 #: notebook/static/notebook/js/actions.js:238 msgid "split cell at cursor" -msgstr "разбить ячейку при наведении курсора" +msgstr "разделить ячейку на которую указывает курсор" #: notebook/static/notebook/js/actions.js:245 #: notebook/static/notebook/js/actions.js:246 msgid "enter edit mode" -msgstr "вход в режим редактирования" +msgstr "войти в режим редактирования" #: notebook/static/notebook/js/actions.js:253 msgid "select previous cell" @@ -425,7 +425,7 @@ msgstr "изменить ячейку на заголовок 6" #: notebook/static/notebook/js/actions.js:437 msgid "toggle cell output" -msgstr "переключение вывода ячейки" +msgstr "переключить вывод ячейки" #: notebook/static/notebook/js/actions.js:438 msgid "toggle output of selected cells" @@ -529,11 +529,11 @@ msgstr "открыть палитру команд" #: notebook/static/notebook/js/actions.js:557 msgid "toggle all line numbers" -msgstr "переключение всех номеров строк" +msgstr "переключить все номера строк" #: notebook/static/notebook/js/actions.js:558 msgid "toggles line numbers in all cells, and persist the setting" -msgstr "переключает номера строк во всех ячейках и сохраняет настройку" +msgstr "переключить номера строк во всех ячейках и сохранить настройки" #: notebook/static/notebook/js/actions.js:569 msgid "show all line numbers" @@ -541,7 +541,7 @@ msgstr "показать все номера строк" #: notebook/static/notebook/js/actions.js:570 msgid "show line numbers in all cells, and persist the setting" -msgstr "показать номера строк во всех ячейках и сохранить настройку" +msgstr "показать номера строк во всех ячейках и сохранить настройки" #: notebook/static/notebook/js/actions.js:579 msgid "hide all line numbers" @@ -554,11 +554,11 @@ msgstr "скрыть номера строк во всех ячейках и с # заголовок переключателя #: notebook/static/notebook/js/actions.js:589 msgid "toggle header" -msgstr "переключение заголовка" +msgstr "переключить заголовок" #: notebook/static/notebook/js/actions.js:590 msgid "switch between showing and hiding the header" -msgstr "переключение между отображением и скрытием заголовка" +msgstr "переключить заголовок между отображением и скрытием заголовка" #: notebook/static/notebook/js/actions.js:605 #: notebook/static/notebook/js/actions.js:606 @@ -576,7 +576,7 @@ msgstr "переключить панель инструментов" #: notebook/static/notebook/js/actions.js:647 msgid "switch between showing and hiding the toolbar" -msgstr "переключение между отображением и скрытием панели инструментов" +msgstr "переключить между отображением и скрытием панели инструментов" #: notebook/static/notebook/js/actions.js:660 #: notebook/static/notebook/js/actions.js:661 @@ -586,7 +586,7 @@ msgstr "показать панель инструментов" #: notebook/static/notebook/js/actions.js:669 #: notebook/static/notebook/js/actions.js:670 msgid "hide the toolbar" -msgstr "скрыть всплывающую подсказку" +msgstr "скрыть панель инструментов" #: notebook/static/notebook/js/actions.js:678 #: notebook/static/notebook/js/actions.js:679 @@ -659,7 +659,7 @@ msgstr "Переименовать текущий блокнот" #: notebook/static/notebook/js/actions.js:813 msgid "toggle all cells output collapsed" -msgstr "переключение сворачивания вывода всех ячеек" +msgstr "переключить сворачивание вывода всех ячеек" #: notebook/static/notebook/js/actions.js:814 msgid "Toggle the hidden state of all output areas" @@ -667,7 +667,7 @@ msgstr "Переключить скрытое состояние всех обл #: notebook/static/notebook/js/actions.js:820 msgid "toggle all cells output scrolled" -msgstr "переключение прокрутки вывода всех ячеек" +msgstr "переключить прокрутку вывода всех ячеек" #: notebook/static/notebook/js/actions.js:821 msgid "Toggle the scrolling state of all output areas" @@ -687,7 +687,7 @@ msgstr "сохранить блокнот" #: notebook/static/notebook/js/actions.js:836 msgid "Save and Checkpoint" -msgstr "Сохранение и контрольная точка" +msgstr "Сохранить и поставить контрольную точку" #: notebook/static/notebook/js/cell.js:79 msgid "Warning: accessing Cell.cm_config directly is deprecated." @@ -740,7 +740,7 @@ msgstr "" #: notebook/static/notebook/js/clipboard.js:119 msgid "There's an invisible text box focused in this dialog." -msgstr "В этом диалоговом окне есть невидимое текстовое поле." +msgstr "В этом диалоговом окне есть сфокусированное невидимое текстовое поле." #: notebook/static/notebook/js/clipboard.js:125 #, python-format @@ -835,7 +835,7 @@ msgstr "Контрольные точки отсутствуют" #: notebook/static/notebook/js/menubar.js:465 msgid "Opens in a new window" -msgstr "Откроется в новом окне" +msgstr "Открывается в новом окне" #: notebook/static/notebook/js/notebook.js:431 msgid "Autosave in progress, latest changes may be lost." @@ -864,15 +864,15 @@ msgstr "" #: notebook/static/notebook/js/notebook.js:470 msgid "Restart and Run All Cells" -msgstr "Перезапуск и запуск всех ячеек" +msgstr "Перезапустить и запустить все ячейки" #: notebook/static/notebook/js/notebook.js:471 msgid "Restart and Clear All Outputs" -msgstr "Перезапуск и очистка всего вывода" +msgstr "Перезапустить и очистить все выводы" #: notebook/static/notebook/js/notebook.js:472 msgid "Restart" -msgstr "Перезапуск" +msgstr "Перезапустить" #: notebook/static/notebook/js/notebook.js:473 msgid "Continue Running" @@ -880,11 +880,11 @@ msgstr "Продолжить запуск" #: notebook/static/notebook/js/notebook.js:474 msgid "Reload" -msgstr "Перезагрузка" +msgstr "Перезагрузить" #: notebook/static/notebook/js/notebook.js:476 msgid "Overwrite" -msgstr "Перезапись" +msgstr "Перезаписать" #: notebook/static/notebook/js/notebook.js:477 msgid "Trust" @@ -896,7 +896,7 @@ msgstr "Откат" #: notebook/static/notebook/js/notebook.js:483 msgid "Newer Notebook" -msgstr "Новый Notebook" +msgstr "Новый блокнот" #: notebook/static/notebook/js/notebook.js:1548 msgid "Use markdown headings" @@ -1015,7 +1015,7 @@ msgstr "Дополнительные сведения см. в консоли о #: notebook/static/notebook/js/notebook.js:2995 msgid "The notebook also failed validation:" -msgstr "Блокнот также не прошла проверку:" +msgstr "Блокнот также не прошел проверку:" #: notebook/static/notebook/js/notebook.js:2997 msgid "" @@ -1143,7 +1143,7 @@ msgstr "Ядро, по-видимому, умерло. Оно будет пер #: notebook/static/notebook/js/notificationarea.js:198 #: notebook/static/notebook/js/notificationarea.js:218 msgid "Dead kernel" -msgstr "Убить ядро" +msgstr "Мёртвое ядро" #: notebook/static/notebook/js/notificationarea.js:141 #: notebook/static/notebook/js/notificationarea.js:219 diff --git a/notebook/i18n/ru_RU/LC_MESSAGES/nbui.mo b/notebook/i18n/ru_RU/LC_MESSAGES/nbui.mo new file mode 100644 index 0000000000..03d6cbf8a0 Binary files /dev/null and b/notebook/i18n/ru_RU/LC_MESSAGES/nbui.mo differ diff --git a/notebook/i18n/ru_RU/LC_MESSAGES/notebook.mo b/notebook/i18n/ru_RU/LC_MESSAGES/notebook.mo new file mode 100644 index 0000000000..de6adbe415 Binary files /dev/null and b/notebook/i18n/ru_RU/LC_MESSAGES/notebook.mo differ diff --git a/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.json b/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.json new file mode 100644 index 0000000000..c1ea7785ff --- /dev/null +++ b/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.json @@ -0,0 +1,1377 @@ +{ + "domain": "nbjs", + "locale_data": { + "nbjs": { + "": { + "domain": "nbjs" + }, + "Manually edit the JSON below to manipulate the metadata for this cell.": [ + "手动编辑下面的 JSON 代码来修改块元数据。" + ], + "Manually edit the JSON below to manipulate the metadata for this notebook.": [ + "手动编辑下面的 JSON 代码来修改笔记本元数据。" + ], + " We recommend putting custom metadata attributes in an appropriately named substructure, so they don't conflict with those of others.": [ + "我们建议将自定义的元数据属性放入适当的子结构中,这样就不会与其他的子结构发生冲突。" + ], + "Edit the metadata": [ + "编辑元数据" + ], + "Edit Notebook Metadata": [ + "编辑Notebook元数据" + ], + "Edit Cell Metadata": [ + "编辑单元格元数据" + ], + "Cancel": [ + "取消" + ], + "Edit": [ + "编辑" + ], + "OK": [ + "确定" + ], + "Apply": [ + "应用" + ], + "WARNING: Could not save invalid JSON.": [ + "警告: 不能保存无效的JSON。" + ], + "There are no attachments for this cell.": [ + "这个单元格没有附件。" + ], + "Current cell attachments": [ + "当前单元格附件" + ], + "Attachments": [ + "附件" + ], + "Restore": [ + "重新保存" + ], + "Delete": [ + "删除" + ], + "Edit attachments": [ + "编辑附件" + ], + "Edit Notebook Attachments": [ + "编辑笔记本附件" + ], + "Edit Cell Attachments": [ + "编辑单元格附件" + ], + "Select a file to insert.": [ + "选择文件插入" + ], + "Select a file": [ + "选择文件" + ], + "You are using Jupyter notebook.": [ + "您正在使用 Jupyter Notebook。" + ], + "The version of the notebook server is: ": [ + "该 notebook 服务的版本是:" + ], + "The server is running on this version of Python:": [ + "该服务运行中使用的 Python 版本为:" + ], + "Waiting for kernel to be available...": [ + "等待内核可用..." + ], + "Server Information:": [ + "服务信息:" + ], + "Current Kernel Information:": [ + "当前内核信息:" + ], + "Could not access sys_info variable for version information.": [ + "无法访问 sys_info 变量来获取版本信息。" + ], + "Cannot find sys_info!": [ + "找不到 sys_info!" + ], + "About Jupyter Notebook": [ + "关于 Jupyter Notebook" + ], + "unable to contact kernel": [ + "不能连接到内核" + ], + "toggle rtl layout": [ + "切换 RTL 布局" + ], + "Toggle the screen directionality between left-to-right and right-to-left": [ + "切换左至右或右至左的屏幕方向" + ], + "edit command mode keyboard shortcuts": [ + "编辑命令模式键盘快捷键" + ], + "Open a dialog to edit the command mode keyboard shortcuts": [ + "打开窗口来编辑快捷键" + ], + "restart kernel": [ + "重启内核" + ], + "restart the kernel (no confirmation dialog)": [ + "重启内核(无确认对话框)" + ], + "confirm restart kernel": [ + "确定重启内核" + ], + "restart the kernel (with dialog)": [ + "重启内核(带确认对话框)" + ], + "restart kernel and run all cells": [ + "重启内核并且运行所有单元格" + ], + "restart the kernel, then re-run the whole notebook (no confirmation dialog)": [ + "重启服务,然后重新运行整个笔记本(无确认对话框)" + ], + "confirm restart kernel and run all cells": [ + "确认重启内核并且运行所有单元格" + ], + "restart the kernel, then re-run the whole notebook (with dialog)": [ + "重启内核, 然后重新运行整个notebook(带确认对话框)" + ], + "restart kernel and clear output": [ + "重启内核并且清空输出" + ], + "restart the kernel and clear all output (no confirmation dialog)": [ + "重启内核并且清空所有输出(无确认对话框)" + ], + "confirm restart kernel and clear output": [ + "确认重启内核并且清空输出" + ], + "restart the kernel and clear all output (with dialog)": [ + "重启内核并且清空所有输出(带确认对话框)" + ], + "interrupt the kernel": [ + "中断内核" + ], + "run cell and select next": [ + "运行单元格并且选择下一个单元格" + ], + "run cell, select below": [ + "运行单元格, 选择下面的单元格" + ], + "run selected cells": [ + "运行选中的单元格" + ], + "run cell and insert below": [ + "运行单元格并且在下面插入单元格" + ], + "run all cells": [ + "运行所有的单元格" + ], + "run all cells above": [ + "运行上面所有的单元格" + ], + "run all cells below": [ + "运行下面所有的单元格" + ], + "enter command mode": [ + "进入命令行模式" + ], + "insert image": [ + "插入图片" + ], + "cut cell attachments": [ + "剪切单元格的附件" + ], + "copy cell attachments": [ + "复制单元格的附件" + ], + "paste cell attachments": [ + "粘贴单元格的附件" + ], + "split cell at cursor": [ + "在光标处分割单元格" + ], + "enter edit mode": [ + "进入编辑模式" + ], + "select previous cell": [ + "选择上一个单元格" + ], + "select cell above": [ + "选择上面的单元格" + ], + "select next cell": [ + "选择下一个单元格" + ], + "select cell below": [ + "选择下面的单元格" + ], + "extend selection above": [ + "扩展上面的单元格" + ], + "extend selected cells above": [ + "扩展上面选择的单元格" + ], + "extend selection below": [ + "扩展下面的单元格" + ], + "extend selected cells below": [ + "扩展下面选择的单元格" + ], + "cut selected cells": [ + "剪切选择的单元格" + ], + "copy selected cells": [ + "复制选择的单元格" + ], + "paste cells above": [ + "粘贴到上面" + ], + "paste cells below": [ + "粘贴到下面" + ], + "insert cell above": [ + "在上面插入单元格" + ], + "insert cell below": [ + "在下面插入单元格" + ], + "change cell to code": [ + "把单元格变成代码快" + ], + "change cell to markdown": [ + "把单元格变成 Markdown" + ], + "change cell to raw": [ + "清除单元格格式" + ], + "change cell to heading 1": [ + "把单元格变成标题 1" + ], + "change cell to heading 2": [ + "把单元格变成标题 2" + ], + "change cell to heading 3": [ + "把单元格变成标题 3" + ], + "change cell to heading 4": [ + "把单元格变成标题 4" + ], + "change cell to heading 5": [ + "把单元格变成标题 5" + ], + "change cell to heading 6": [ + "把单元格变成标题 6" + ], + "toggle cell output": [ + "显示/隐藏单元格输出" + ], + "toggle output of selected cells": [ + "显示/隐藏选定单元格的输出" + ], + "toggle cell scrolling": [ + "切换单元格为滚动" + ], + "toggle output scrolling of selected cells": [ + "切换选中单元格的输出为滚动" + ], + "clear cell output": [ + "清空所有单元格输出" + ], + "clear output of selected cells": [ + "清空已选择单元格的输出" + ], + "move cells down": [ + "下移单元格" + ], + "move selected cells down": [ + "下移选中单元格" + ], + "move cells up": [ + "上移单元格" + ], + "move selected cells up": [ + "上移选中单元格" + ], + "toggle line numbers": [ + "切换行号" + ], + "show keyboard shortcuts": [ + "显示键盘快捷键" + ], + "delete cells": [ + "删除单元格" + ], + "delete selected cells": [ + "删除选中单元格" + ], + "undo cell deletion": [ + "撤销删除单元格" + ], + "merge cell with previous cell": [ + "合并上一个单元格" + ], + "merge cell above": [ + "合并上面的单元格" + ], + "merge cell with next cell": [ + "合并下一个单元格" + ], + "merge cell below": [ + "合并下面的单元格" + ], + "merge selected cells": [ + "合并选中的单元格" + ], + "merge cells": [ + "合并单元格" + ], + "merge selected cells, or current cell with cell below if only one cell is selected": [ + "合并选中单元格, 如果只有一个单元格被选中" + ], + "show command pallette": [ + "显示命令配置" + ], + "open the command palette": [ + "打开命令配置" + ], + "toggle all line numbers": [ + "切换所有行号" + ], + "toggles line numbers in all cells, and persist the setting": [ + "在所有单元格中切换行号,并保持设置" + ], + "show all line numbers": [ + "显示行号" + ], + "show line numbers in all cells, and persist the setting": [ + "在所有单元格中显示行号,并保持设置" + ], + "hide all line numbers": [ + "隐藏行号" + ], + "hide line numbers in all cells, and persist the setting": [ + "隐藏行号并保持设置" + ], + "toggle header": [ + "切换标题" + ], + "switch between showing and hiding the header": [ + "切换显示和隐藏标题" + ], + "show the header": [ + "显示标题" + ], + "hide the header": [ + "隐藏标题" + ], + "toggle toolbar": [ + "切换工具栏" + ], + "switch between showing and hiding the toolbar": [ + "切换显示/隐藏工具栏" + ], + "show the toolbar": [ + "显示工具栏" + ], + "hide the toolbar": [ + "隐藏工具栏" + ], + "close the pager": [ + "关闭分页器" + ], + "ignore": [ + "忽略" + ], + "move cursor up": [ + "光标上移" + ], + "move cursor down": [ + "光标下移" + ], + "scroll notebook down": [ + "向下滚动" + ], + "scroll notebook up": [ + "向上滚动" + ], + "scroll cell center": [ + "滚动单元格到中间" + ], + "Scroll the current cell to the center": [ + "把当前单元格滚动到中间" + ], + "scroll cell top": [ + "滚动单元格到顶" + ], + "Scroll the current cell to the top": [ + "将当前单元格滚动到顶部" + ], + "duplicate notebook": [ + "制作笔记本副本" + ], + "Create and open a copy of the current notebook": [ + "创建并打开当前笔记本的一个副本" + ], + "trust notebook": [ + "信任笔记本" + ], + "Trust the current notebook": [ + "信任当前笔记本" + ], + "rename notebook": [ + "重命名笔记本" + ], + "Rename the current notebook": [ + "重命名当前笔记本" + ], + "toggle all cells output collapsed": [ + "切换折叠所有单元格的输出" + ], + "Toggle the hidden state of all output areas": [ + "切换所有输出区域的隐藏状态" + ], + "toggle all cells output scrolled": [ + "切换所有单元格输出的滚动状态" + ], + "Toggle the scrolling state of all output areas": [ + "切换所有输出区域的滚动状态" + ], + "clear all cells output": [ + "清空所有单元格输出" + ], + "Clear the content of all the outputs": [ + "清空所有的输出内容" + ], + "save notebook": [ + "保存笔记本" + ], + "Save and Checkpoint": [ + "保存并建立检查点" + ], + "Warning: accessing Cell.cm_config directly is deprecated.": [ + "警告: 直接访问 Cell.cm_config 已经被弃用了。" + ], + "Unrecognized cell type: %s": [ + "未知的单元格类型: %s" + ], + "Unrecognized cell type": [ + "未知的单元格类型" + ], + "Error in cell toolbar callback %s": [ + "工具栏调用 %s 出现错误" + ], + "Clipboard types: %s": [ + "剪贴板类型: %s" + ], + "Dialog for paste from system clipboard": [ + "从系统剪切板粘贴" + ], + "Ctrl-V": [ + "" + ], + "Cmd-V": [ + "" + ], + "Press %s again to paste": [ + "再按一次 %s 来粘贴" + ], + "Why is this needed? ": [ + "为什么需要它?" + ], + "We can't get paste events in this browser without a text box. ": [ + "在浏览器里没有文本框我们不能粘贴. " + ], + "There's an invisible text box focused in this dialog.": [ + "在这个对话框中有一个不可见的文本框." + ], + "%s to paste": [ + "%s 来粘贴" + ], + "Can't execute cell since kernel is not set.": [ + "当前不能执行单元格代码,因为内核还没有准备好。" + ], + "In": [ + "" + ], + "Could not find a kernel matching %s. Please select a kernel:": [ + "找不到匹配 %s 的内核。请选择一个内核:" + ], + "Continue Without Kernel": [ + "无内核继续运行" + ], + "Set Kernel": [ + "设置内核" + ], + "Kernel not found": [ + "找不到内核" + ], + "Creating Notebook Failed": [ + "创建笔记本失败" + ], + "The error was: %s": [ + "错误: %s" + ], + "Run": [ + "运行" + ], + "Code": [ + "代码" + ], + "Markdown": [ + "Markdown" + ], + "Raw NBConvert": [ + "原生 NBConvert" + ], + "Heading": [ + "标题" + ], + "unrecognized cell type:": [ + "未识别的单元格类型:" + ], + "Failed to retrieve MathJax from '%s'": [ + "未能从 '%s' 中检索 MathJax" + ], + "Math/LaTeX rendering will be disabled.": [ + "Math/LaTeX 渲染将被禁用。" + ], + "Trusted Notebook": [ + "可信的笔记本" + ], + "Trust Notebook": [ + "信任笔记本" + ], + "None": [ + "无" + ], + "No checkpoints": [ + "没有检查点" + ], + "Opens in a new window": [ + "在新窗口打开" + ], + "Autosave in progress, latest changes may be lost.": [ + "自动保存进行中,最新的改变可能会丢失。" + ], + "Unsaved changes will be lost.": [ + "未保存的修改将会丢失。" + ], + "The Kernel is busy, outputs may be lost.": [ + "内核正忙,输出也许会丢失。" + ], + "This notebook is version %1$s, but we only fully support up to %2$s.": [ + "该笔记本使用了版本 %1$s,但是我们只支持到 %2$s." + ], + "You can still work with this notebook, but cell and output types introduced in later notebook versions will not be available.": [ + "您仍然可以使用该笔记本,但是在新版本中引入的单元和输出类型将不可用。" + ], + "Restart and Run All Cells": [ + "重启并运行所有代码块" + ], + "Restart and Clear All Outputs": [ + "重启并清空所有输出" + ], + "Restart": [ + "重启" + ], + "Continue Running": [ + "继续运行" + ], + "Reload": [ + "重载" + ], + "Overwrite": [ + "重写" + ], + "Trust": [ + "信任" + ], + "Revert": [ + "恢复" + ], + "Newer Notebook": [ + "新笔记本" + ], + "Use markdown headings": [ + "使用 Markdown 标题" + ], + "Jupyter no longer uses special heading cells. Instead, write your headings in Markdown cells using # characters:": [ + "Jupyter 不再使用特殊的标题单元格。请在 Markdown 单元格中使用 # 字符来写标题:" + ], + "## This is a level 2 heading": [ + "## 这是一个二级标题" + ], + "Restart kernel and re-run the whole notebook?": [ + "重新启动内核并重新运行整个笔记本?" + ], + "Are you sure you want to restart the current kernel and re-execute the whole notebook? All variables and outputs will be lost.": [ + "您确定要重新启动当前的内核并重新执行整个笔记本吗?所有的变量和输出都将丢失。" + ], + "Restart kernel and clear all output?": [ + "重启内核并且清空输出?" + ], + "Do you want to restart the current kernel and clear all output? All variables and outputs will be lost.": [ + "您是否希望重新启动当前的内核并清除所有输出?所有的变量和输出都将丢失。" + ], + "Restart kernel?": [ + "重启内核?" + ], + "Do you want to restart the current kernel? All variables will be lost.": [ + "如果重启内核,所有变量都会丢失。是否重启?" + ], + "Shutdown kernel?": [ + "关闭内核?" + ], + "Do you want to shutdown the current kernel? All variables will be lost.": [ + "如果关闭内核,所有变量都会丢失。是否关闭?" + ], + "Notebook changed": [ + "笔记本改变了" + ], + "The notebook file has changed on disk since the last time we opened or saved it. Do you want to overwrite the file on disk with the version open here, or load the version on disk (reload the page)?": [ + "自从上次我们打开或保存它以来,笔记本文件已经在磁盘上发生了变化。您希望用这里打开的版本覆盖磁盘上的版本,还是加载磁盘上的版本(刷新页面)?" + ], + "Notebook validation failed": [ + "Notebook 校验失败" + ], + "The save operation succeeded, but the notebook does not appear to be valid. The validation error was:": [ + "保存操作成功了,但是这个笔记本看起来并不有效。校验错误:" + ], + "A trusted Jupyter notebook may execute hidden malicious code when you open it. Selecting trust will immediately reload this notebook in a trusted state. For more information, see the Jupyter security documentation: ": [ + "当你打开一个可信任的 Jupyter 笔记本时,它可能会执行隐藏的恶意代码。选择信任将立即在一个可信的状态中重新加载这个笔记本。要了解更多信息,请参阅 Jupyter 安全文档:" + ], + "here": [ + "这里" + ], + "Trust this notebook?": [ + "信任这个笔记本?" + ], + "Notebook failed to load": [ + "笔记本加载失败" + ], + "The error was: ": [ + "错误: " + ], + "See the error console for details.": [ + "有关详细信息,请参阅错误控制台。" + ], + "The notebook also failed validation:": [ + "这个笔记本校验也失败了:" + ], + "An invalid notebook may not function properly. The validation error was:": [ + "无效的笔记本可能无法正常运行。校验错误:" + ], + "This notebook has been converted from an older notebook format to the current notebook format v(%s).": [ + "本笔记本已从较旧的笔记本格式转换为当前的笔记本格式 v(%s)。" + ], + "This notebook has been converted from a newer notebook format to the current notebook format v(%s).": [ + "这个笔记本已经从一种新的笔记本格式转换为当前的笔记本格式 v(%s)。" + ], + "The next time you save this notebook, the current notebook format will be used.": [ + "下次你保存这个笔记本时,当前的笔记本格式将会被使用。" + ], + "Older versions of Jupyter may not be able to read the new format.": [ + "旧版本的 Jupyter 可能无法读取新格式。" + ], + "Some features of the original notebook may not be available.": [ + "原笔记本的一些特性可能无法使用。" + ], + "To preserve the original version, close the notebook without saving it.": [ + "为了保存原始版本,关闭笔记本而不保存它。" + ], + "Notebook converted": [ + "已转换笔记本" + ], + "(No name)": [ + "(没有名字)" + ], + "An unknown error occurred while loading this notebook. This version can load notebook formats %s or earlier. See the server log for details.": [ + "加载本笔记本时出现了一个未知的错误。这个版本可以加载 %s 或更早的笔记本。有关详细信息,请参阅服务器日志。" + ], + "Error loading notebook": [ + "加载笔记本出错" + ], + "Are you sure you want to revert the notebook to the latest checkpoint?": [ + "确定将笔记本恢复至最近的检查点?" + ], + "This cannot be undone.": [ + "该操作不能被还原。" + ], + "The checkpoint was last updated at:": [ + "笔记本的最新检查点更新于:" + ], + "Revert notebook to checkpoint": [ + "恢复笔记本至检查点" + ], + "Edit Mode": [ + "编辑模式" + ], + "Command Mode": [ + "命令模式" + ], + "Kernel Created": [ + "内核已创建" + ], + "Connecting to kernel": [ + "正在连接内核" + ], + "Not Connected": [ + "未连接" + ], + "click to reconnect": [ + "点击重连" + ], + "Restarting kernel": [ + "重启内核" + ], + "Kernel Restarting": [ + "内核正在重启" + ], + "The kernel appears to have died. It will restart automatically.": [ + "内核似乎挂掉了,它很快将自动重启。" + ], + "Dead kernel": [ + "挂掉的内核" + ], + "Kernel Dead": [ + "内核挂掉" + ], + "Interrupting kernel": [ + "正在中断内核" + ], + "No Connection to Kernel": [ + "没有连接到内核" + ], + "A connection to the notebook server could not be established. The notebook will continue trying to reconnect. Check your network connection or notebook server configuration.": [ + "无法建立到笔记本服务器的连接。 我们会继续尝试重连。请检查网络连接还有服务配置。" + ], + "Connection failed": [ + "连接失败" + ], + "No kernel": [ + "没有内核" + ], + "Kernel is not running": [ + "内核没有运行" + ], + "Don't Restart": [ + "不要重启" + ], + "Try Restarting Now": [ + "现在尝试重启" + ], + "The kernel has died, and the automatic restart has failed. It is possible the kernel cannot be restarted. If you are not able to restart the kernel, you will still be able to save the notebook, but running code will no longer work until the notebook is reopened.": [ + "内核已经死亡,自动重启也失败了。可能是内核不能重新启动。如果您不能重新启动内核,您仍然能够保存笔记本,但笔记本要重新打开才能运行代码。" + ], + "No Kernel": [ + "没有内核" + ], + "Failed to start the kernel": [ + "启动内核失败" + ], + "Kernel Busy": [ + "内核正忙" + ], + "Kernel starting, please wait...": [ + "内核正在启动,请等待..." + ], + "Kernel Idle": [ + "内核空闲" + ], + "Kernel ready": [ + "内核就绪" + ], + "Using kernel: ": [ + "使用内核:" + ], + "Only candidate for language: %1$s was %2$s.": [ + "只支持语言: %1$s - %2$s." + ], + "Loading notebook": [ + "加载笔记本" + ], + "Notebook loaded": [ + "笔记本已加载" + ], + "Saving notebook": [ + "保存笔记本" + ], + "Notebook saved": [ + "笔记本已保存" + ], + "Notebook save failed": [ + "笔记本保存失败" + ], + "Notebook copy failed": [ + "笔记本复制失败" + ], + "Checkpoint created": [ + "检查点已创建" + ], + "Checkpoint failed": [ + "检查点创建失败" + ], + "Checkpoint deleted": [ + "检查点已删除" + ], + "Checkpoint delete failed": [ + "检查点删除失败" + ], + "Restoring to checkpoint...": [ + "正在恢复至检查点..." + ], + "Checkpoint restore failed": [ + "检查点恢复失败" + ], + "Autosave disabled": [ + "自动保存失败" + ], + "Saving every %d sec.": [ + "每隔 %s 秒保存一次。" + ], + "Trusted": [ + "可信" + ], + "Not Trusted": [ + "不可信" + ], + "click to expand output": [ + "点击展开输出" + ], + "click to expand output; double click to hide output": [ + "点击展开输出;双击隐藏输出" + ], + "click to unscroll output; double click to hide": [ + "单击取消滚动输出;双击隐藏" + ], + "click to scroll output; double click to hide": [ + "点击滚动输出;双击隐藏" + ], + "Javascript error adding output!": [ + "添加输出时 Javascript 出错了!" + ], + "See your browser Javascript console for more details.": [ + "更多细节请参见您的浏览器 Javascript 控制台。" + ], + "Out[%d]:": [ + "" + ], + "Unrecognized output: %s": [ + "未识别的输出: %s" + ], + "Open the pager in an external window": [ + "在外部窗口打开分页器" + ], + "Close the pager": [ + "关闭分页器" + ], + "Jupyter Pager": [ + "Jupyter 分页器" + ], + "go to cell start": [ + "跳到单元格起始处" + ], + "go to cell end": [ + "跳到单元格最后" + ], + "go one word left": [ + "往左跳一个单词" + ], + "go one word right": [ + "往右跳一个单词" + ], + "delete word before": [ + "删除前面的单词" + ], + "delete word after": [ + "删除后面的单词" + ], + "redo": [ + "重做" + ], + "redo selection": [ + "重新选择" + ], + "emacs-style line kill": [ + "Emacs 风格的 Line Kill" + ], + "delete line left of cursor": [ + "删除光标左边一行" + ], + "delete line right of cursor": [ + "删除光标右边一行" + ], + "code completion or indent": [ + "代码补全或缩进" + ], + "tooltip": [ + "工具提示" + ], + "indent": [ + "缩进" + ], + "dedent": [ + "取消缩进" + ], + "select all": [ + "全选" + ], + "undo": [ + "撤销" + ], + "comment": [ + "注释" + ], + "delete whole line": [ + "删除整行" + ], + "undo selection": [ + "撤销选择" + ], + "toggle overwrite flag": [ + "切换重写标志" + ], + "Shift": [ + "Shift" + ], + "Alt": [ + "Alt" + ], + "Up": [ + "上" + ], + "Down": [ + "下" + ], + "Left": [ + "左" + ], + "Right": [ + "右" + ], + "Tab": [ + "Tab" + ], + "Caps Lock": [ + "大写锁定" + ], + "Esc": [ + "Esc" + ], + "Ctrl": [ + "Ctrl" + ], + "Enter": [ + "Enter" + ], + "Page Up": [ + "上一页" + ], + "Page Down": [ + "下一页" + ], + "Home": [ + "Home" + ], + "End": [ + "End" + ], + "Space": [ + "空格" + ], + "Backspace": [ + "退格" + ], + "Minus": [ + "" + ], + "PageUp": [ + "上一页" + ], + "The Jupyter Notebook has two different keyboard input modes.": [ + "Jupyter 笔记本有两种不同的键盘输入模式。" + ], + "Edit mode allows you to type code or text into a cell and is indicated by a green cell border.": [ + "编辑模式允许您将代码或文本输入到一个单元格中,并通过一个绿色边框的单元格来表示" + ], + "Command mode binds the keyboard to notebook level commands and is indicated by a grey cell border with a blue left margin.": [ + "命令模式将键盘与笔记本级命令绑定在一起,并通过一个灰框、左边距蓝色的单元格显示。" + ], + "Close": [ + "关闭" + ], + "Keyboard shortcuts": [ + "键盘快捷键" + ], + "Command": [ + "命令" + ], + "Control": [ + "控制" + ], + "Option": [ + "选项" + ], + "Return": [ + "返回" + ], + "Command Mode (press %s to enable)": [ + "命令行模式(按 %s 生效)" + ], + "Edit Shortcuts": [ + "编辑快捷键" + ], + "edit command-mode keyboard shortcuts": [ + "编辑命令模式键盘快捷键" + ], + "Edit Mode (press %s to enable)": [ + "编辑模式(按 %s 生效)" + ], + "Autosave Failed!": [ + "自动保存失败!" + ], + "Rename": [ + "重命名" + ], + "Enter a new notebook name:": [ + "请输入新的笔记本名称:" + ], + "Rename Notebook": [ + "重命名笔记本" + ], + "Invalid notebook name. Notebook names must have 1 or more characters and can contain any characters except :/\\. Please enter a new notebook name:": [ + "无效的笔记本名称。笔记本名称不能为空,并且不能包含\":/.\"。请输入一个新的笔记本名称:" + ], + "Renaming...": [ + "正在重命名…" + ], + "Unknown error": [ + "未知错误" + ], + "no checkpoint": [ + "没有检查点" + ], + "Last Checkpoint: %s": [ + "最新检查点: %s " + ], + "(unsaved changes)": [ + "(更改未保存)" + ], + "(autosaved)": [ + "(已自动保存)" + ], + "Warning: too many matches (%d). Some changes might not be shown or applied.": [ + "警告:太多的匹配(%d)。有些更改可能不会被显示或应用." + ], + "%d match": [ + "%d 匹配", + "%d 匹配" + ], + "More than 100 matches, aborting": [ + "超过 100 个匹配, 中止" + ], + "Use regex (JavaScript regex syntax)": [ + "使用正则表达式(JavaScript 正则表达式语法)" + ], + "Replace in selected cells": [ + "在选中单元格中替换" + ], + "Match case": [ + "匹配大小写" + ], + "Find": [ + "查找" + ], + "Replace": [ + "替换" + ], + "No matches, invalid or empty regular expression": [ + "无匹配,表达式无效或表达式为空" + ], + "Replace All": [ + "全部替换" + ], + "Find and Replace": [ + "查找并且替换" + ], + "find and replace": [ + "查找并且替换" + ], + "Write raw LaTeX or other formats here, for use with nbconvert. It will not be rendered in the notebook. When passing through nbconvert, a Raw Cell's content is added to the output unmodified.": [ + "在这里直接写 LaTeX 或者其它格式的文本来配合 nbconvert。笔记本不会渲染它。传给 nbconvert 时,原始单元格的内容会被完好地加进输出。" + ], + "Grow the tooltip vertically (press shift-tab twice)": [ + "纵向展开工具提示(按两次 Shift+Tab)" + ], + "show the current docstring in pager (press shift-tab 4 times)": [ + "在分页器中显示当前的文档字符串(按四次 Shift+Tab)" + ], + "Open in Pager": [ + "在分页器中打开" + ], + "Tooltip will linger for 10 seconds while you type": [ + "当您键入时,工具提示会停留十秒" + ], + "Welcome to the Notebook Tour": [ + "欢迎来到 Notebook 导览" + ], + "You can use the left and right arrow keys to go backwards and forwards.": [ + "你可以使用左右箭头键来前后移动" + ], + "Filename": [ + "文件名" + ], + "Click here to change the filename for this notebook.": [ + "点击这里修改笔记本的文件名" + ], + "Notebook Menubar": [ + "笔记本菜单栏" + ], + "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with.": [ + "菜单栏上的菜单可以用来操作笔记本、单元格和与笔记本通信的内核。" + ], + "Notebook Toolbar": [ + "笔记本工具栏" + ], + "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information.": [ + "工具栏有最常见操作的按钮。将鼠标悬停在每个按钮上以获得更多信息。" + ], + "Mode Indicator": [ + "模式指示器" + ], + "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.": [ + "笔记本有两种模式:编辑模式和命令模式。在这个区域,一个指示器可以显示你在哪个模式。" + ], + "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area.": [ + "现在你处于命令模式,许多快捷键都可以使用。在该模式下,指示区域中没有显示图标。" + ], + "Pressing Enter or clicking in the input text area of the cell switches to Edit Mode.": [ + "按下Enter或者点击输入文本区域来切换到编辑模式. " + ], + "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell.": [ + "您会发现当前活动单元格周围的边框改变了颜色。键入将在当前活动单元格中插入文本." + ], + "Back to Command Mode": [ + "回到命令模式" + ], + "Pressing Esc or clicking outside of the input text area takes you back to Command Mode.": [ + "按下Esc或者点击输入框外面来返回到命令模式。" + ], + "Keyboard Shortcuts": [ + "键盘快捷键" + ], + "You can click here to get a list of all of the keyboard shortcuts.": [ + "点击这里获得所有键盘快捷键" + ], + "Kernel Indicator": [ + "内核指示器" + ], + "This is the Kernel indicator. It looks like this when the Kernel is idle.": [ + "这是内核指示器。当内核空闲时,它看起来就像这样。" + ], + "The Kernel indicator looks like this when the Kernel is busy.": [ + "内核指示器在内核繁忙时看起来是这样的。" + ], + "Interrupting the Kernel": [ + "内核中断" + ], + "To cancel a computation in progress, you can click here.": [ + "要取消正在进行的计算任务,您可以点击这里。" + ], + "Notification Area": [ + "任务栏通知区" + ], + "Messages in response to user actions (Save, Interrupt, etc.) appear here.": [ + "响应用户操作(保存,中断等)的消息出现在这里。" + ], + "End of Tour": [ + "结束导览" + ], + "This concludes the Jupyter Notebook User Interface Tour.": [ + "Jupyter 笔记本用户界面之旅到此为止。" + ], + "Edit Attachments": [ + "编辑附件" + ], + "Cell": [ + "单元格" + ], + "Edit Metadata": [ + "编辑元数据" + ], + "Custom": [ + "自定义" + ], + "Set the MIME type of the raw cell:": [ + "设置原始单元格的 MIME 类型:" + ], + "Raw Cell MIME Type": [ + "原始单元格的 MIME 类型" + ], + "Raw NBConvert Format": [ + "原始 NBConvert 类型" + ], + "Raw Cell Format": [ + "原始单元格格式" + ], + "Slide": [ + "幻灯片" + ], + "Sub-Slide": [ + "子幻灯片" + ], + "Fragment": [ + "碎片" + ], + "Skip": [ + "跳过" + ], + "Notes": [ + "代码" + ], + "Slide Type": [ + "幻灯片类型" + ], + "Slideshow": [ + "幻灯片" + ], + "Add tag": [ + "添加标签" + ], + "Edit the list of tags below. All whitespace is treated as tag separators.": [ + "编辑下面的标签列表。所有空格都被当作标记分隔符。" + ], + "Edit the tags": [ + "编辑标签" + ], + "Edit Tags": [ + "编辑标签" + ], + "Shutdown": [ + "关闭" + ], + "Create a new notebook with %s": [ + "创建新的笔记本 %s" + ], + "An error occurred while creating a new notebook.": [ + "创建新笔记本时出错。" + ], + "Creating File Failed": [ + "创建文件失败" + ], + "An error occurred while creating a new file.": [ + "创建新文件时出错。" + ], + "Creating Folder Failed": [ + "创建文件夹失败" + ], + "An error occurred while creating a new folder.": [ + "创建新文件夹时出错。" + ], + "Failed to read file": [ + "读取文件失败" + ], + "Failed to read file %s": [ + "读取文件 %s 失败了" + ], + "The file size is %d MB. Do you still want to upload it?": [ + "文件大小为 %d MB,依然上传?" + ], + "Large file size warning": [ + "请注意文件大小" + ], + "Server error: ": [ + "服务出现错误:" + ], + "The notebook list is empty.": [ + "笔记本列表为空。" + ], + "Click here to rename, delete, etc.": [ + "点击这里进行重命名或删除等操作" + ], + "Running": [ + "运行" + ], + "Enter a new file name:": [ + "请输入一个新的文件名:" + ], + "Enter a new directory name:": [ + "请输入一个新的路径:" + ], + "Enter a new name:": [ + "请输入新名字:" + ], + "Rename file": [ + "文件重命名" + ], + "Rename directory": [ + "重命名路径" + ], + "Rename notebook": [ + "重命名笔记本" + ], + "Move": [ + "移动" + ], + "An error occurred while renaming \"%1$s\" to \"%2$s\".": [ + "当把 \"%1$s\" 重命名为 \"%2$s\" 时出现错误." + ], + "Rename Failed": [ + "重命名失败" + ], + "Enter a new destination directory path for this item:": [ + "为笔记本选择一个新的路径:", + "为选中的 %d 笔记本选择一个新的路径:" + ], + "Move an Item": [ + "移动一个文件", + "移动 %d 个文件" + ], + "An error occurred while moving \"%1$s\" from \"%2$s\" to \"%3$s\".": [ + "当把 \"%1$s\" 从 \"%2$s\" 移动到 \"%3$s\" 时出现错误." + ], + "Move Failed": [ + "移动失败" + ], + "Are you sure you want to permanently delete: \"%s\"?": [ + "确定永久删除 \"%s\"?", + "确定永久删除选中的 %d 个文件或文件夹?" + ], + "An error occurred while deleting \"%s\".": [ + "当删除 \"%s\" 时, 出现错误。" + ], + "Delete Failed": [ + "删除失败" + ], + "Are you sure you want to duplicate: \"%s\"?": [ + "确定制作 \"%s\" 的副本?", + "确定制作选中的 %d 个文件的副本?" + ], + "Duplicate": [ + "制作副本" + ], + "An error occurred while duplicating \"%s\".": [ + "制作 \"%s\" 的副本时出现错误。" + ], + "Duplicate Failed": [ + "制作副本失败" + ], + "Upload": [ + "上传" + ], + "Invalid file name": [ + "无效的文件名" + ], + "File names must be at least one character and not start with a period": [ + "文件名不能为空,并且不能以句号开始,除下划线以外的符号都不能开头" + ], + "Cannot upload invalid Notebook": [ + "无法上传无效的笔记本" + ], + "There is already a file named \"%s\". Do you want to replace it?": [ + "已经存在一个名为 \"%s\" 的文件,替换现有文件?" + ], + "Replace file": [ + "替换文件" + ] + } + } +} \ No newline at end of file diff --git a/notebook/i18n/zh_CN/LC_MESSAGES/nbui.mo b/notebook/i18n/zh_CN/LC_MESSAGES/nbui.mo new file mode 100644 index 0000000000..59c2e3b2b7 Binary files /dev/null and b/notebook/i18n/zh_CN/LC_MESSAGES/nbui.mo differ diff --git a/notebook/i18n/zh_CN/LC_MESSAGES/notebook.mo b/notebook/i18n/zh_CN/LC_MESSAGES/notebook.mo new file mode 100644 index 0000000000..df097102cf Binary files /dev/null and b/notebook/i18n/zh_CN/LC_MESSAGES/notebook.mo differ diff --git a/notebook/jstest.py b/notebook/jstest.py index 2bb318af31..91c5977b7a 100644 --- a/notebook/jstest.py +++ b/notebook/jstest.py @@ -60,7 +60,7 @@ def run(self): self.buffer.write(chunk) if self.echo: sys.stdout.write(bytes_to_str(chunk)) - + os.close(self.readfd) os.close(self.writefd) @@ -87,7 +87,7 @@ def halt(self): self.join() -class TestController(object): +class TestController: """Run tests in a subprocess """ #: str, test group to be executed. @@ -110,7 +110,7 @@ def __init__(self): def setup(self): """Create temporary directories etc. - + This is only called when we know the test group will be run. Things created here may be cleaned up by self.cleanup(). """ @@ -138,11 +138,11 @@ def wait(self): def print_extra_info(self): """Print extra information about this test run. - + If we're running in parallel and showing the concise view, this is only called if the test group fails. Otherwise, it's called before the test group is started. - + The base implementation does nothing, but it can be overridden by subclasses. """ @@ -155,7 +155,7 @@ def cleanup_process(self): return # Process doesn't exist, or is already dead. try: - print('Cleaning up stale PID: %d' % subp.pid) + print(f'Cleaning up stale PID: {subp.pid}') subp.kill() except: # (OSError, WindowsError) ? # This is just a best effort, if we fail or the process was @@ -193,7 +193,7 @@ def all_js_groups(): class JSController(TestController): """Run CasperJS tests """ - + requirements = ['casperjs'] def __init__(self, section, xunit=True, engine='phantomjs', url=None): @@ -210,7 +210,7 @@ def __init__(self, section, xunit=True, engine='phantomjs', url=None): js_test_dir = get_js_test_dir() includes = '--includes=' + os.path.join(js_test_dir,'util.js') test_cases = os.path.join(js_test_dir, self.section) - self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine] + self.cmd = ['casperjs', 'test', includes, test_cases, f'--engine={self.engine}'] def setup(self): self.ipydir = TemporaryDirectory() @@ -226,8 +226,8 @@ def setup(self): self.dirs.append(self.home) self.dirs.append(self.config_dir) self.dirs.append(self.nbdir) - os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub ∂ir1', u'sub ∂ir 1a'))) - os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub ∂ir2', u'sub ∂ir 1b'))) + os.makedirs(os.path.join(self.nbdir.name, os.path.join('sub ∂ir1', 'sub ∂ir 1a'))) + os.makedirs(os.path.join(self.nbdir.name, os.path.join('sub ∂ir2', 'sub ∂ir 1b'))) if self.xunit: self.add_xunit() @@ -240,22 +240,22 @@ def setup(self): alive = False if alive: - self.cmd.append("--url=%s" % self.url) + self.cmd.append(f"--url={self.url}") else: - raise Exception('Could not reach "%s".' % self.url) + raise Exception(f'Could not reach "{self.url}".') else: # start the ipython notebook, so we get the port number self.server_port = 0 self._init_server() if self.server_port: - self.cmd.append('--url=http://localhost:%i%s' % (self.server_port, self.base_url)) + self.cmd.append(f'--url=http://localhost:{self.server_port:d}{self.base_url}') else: # don't launch tests if the server didn't start self.cmd = [sys.executable, '-c', 'raise SystemExit(1)'] def add_xunit(self): xunit_file = os.path.abspath(self.section.replace('/','.') + '.xunit.xml') - self.cmd.append('--xunit=%s' % xunit_file) + self.cmd.append(f'--xunit={xunit_file}') def launch(self, buffer_output): # If the engine is SlimerJS, we need to buffer the output because @@ -281,7 +281,7 @@ def wait(self, *pargs, **kwargs): return ret def print_extra_info(self): - print("Running tests with notebook directory %r" % self.nbdir.name) + print(f"Running tests with notebook directory {self.nbdir.name!r}") @property def will_run(self): @@ -295,7 +295,7 @@ def _init_server(self): '--no-browser', '--notebook-dir', self.nbdir.name, '--NotebookApp.token=', - '--NotebookApp.base_url=%s' % self.base_url, + f'--NotebookApp.base_url={self.base_url}', ] # ipc doesn't work on Windows, and darwin has crazy-long temp paths, # which run afoul of ipc's maximum path length. @@ -313,11 +313,12 @@ def _init_server(self): ) with patch.dict('os.environ', {'HOME': self.home.name}): runtime_dir = jupyter_runtime_dir() - self.server_info_file = os.path.join(runtime_dir, - 'nbserver-%i.json' % self.server.pid + self.server_info_file = os.path.join( + runtime_dir, + f'nbserver-{self.server.pid}.json' ) self._wait_for_server() - + def _wait_for_server(self): """Wait 30 seconds for the notebook server to start""" for i in range(300): @@ -333,17 +334,18 @@ def _wait_for_server(self): else: return time.sleep(0.1) - print("Notebook server-info file never arrived: %s" % self.server_info_file, + print( + f"Notebook server-info file never arrived: {self.server_info_file}", file=sys.stderr ) - + def _failed_to_start(self): """Notebook server exited prematurely""" captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') print("Notebook failed to start: ", file=sys.stderr) print(self.server_command) print(captured, file=sys.stderr) - + def _load_server_info(self): """Notebook server started, load connection info from JSON""" with open(self.server_info_file) as f: @@ -374,10 +376,11 @@ def cleanup(self): try: popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT) except TimeoutExpired: - print("Notebook server still running (%s)" % self.server_info_file, + print( + f"Notebook server still running ({self.server_info_file})", file=sys.stderr ) - + self.stream_capturer.halt() TestController.cleanup(self) @@ -399,11 +402,11 @@ def prepare_controllers(options): def do_run(controller, buffer_output=True): """Setup and run a test controller. - + If buffer_output is True, no output is displayed, to avoid it appearing interleaved. In this case, the caller is responsible for displaying test output on failure. - + Returns ------- controller : TestController @@ -443,7 +446,7 @@ def _add(name, value): _add('Platform', inf['platform']) width = max(len(n) for (n,v) in out) - out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out] + out = [f"{n:<{width}}: {v}\n" for (n, v) in out] avail = [] not_avail = [] @@ -468,7 +471,7 @@ def _add(name, value): def run_jstestall(options): """Run the entire Javascript test suite. - + This function constructs TestControllers and runs them in subprocesses. Parameters @@ -552,16 +555,15 @@ def justify(ltext, rtext, width=70, fill='-'): print('_'*70) print('Test suite completed for system with the following information:') print(report()) - took = "Took %.3fs." % t_tests + took = f"Took {t_tests:.3f}s." print('Status: ', end='') if not failed: - print('OK (%d test groups).' % nrunners, took) + print(f'OK ({nrunners} test groups).', took) else: # If anything went wrong, point out what command to rerun manually to # see the actual errors and individual summary failed_sections = [c.section for c in failed] - print('ERROR - {} out of {} test groups failed ({}).'.format(nfail, - nrunners, ', '.join(failed_sections)), took) + print(f'ERROR - {nfail} out of {nrunners} test groups failed ({", ".join(failed_sections)}).', took) print() print('You may wish to rerun these, with:') print(' python -m notebook.jstest', *failed_sections) diff --git a/notebook/kernelspecs/handlers.py b/notebook/kernelspecs/handlers.py index be768b5ab7..b9c6af4b49 100644 --- a/notebook/kernelspecs/handlers.py +++ b/notebook/kernelspecs/handlers.py @@ -15,7 +15,7 @@ def get(self, kernel_name, path, include_body=True): self.root = ksm.get_kernel_spec(kernel_name).resource_dir except KeyError as e: raise web.HTTPError(404, - u'Kernel spec %s not found' % kernel_name) from e + f'Kernel spec {kernel_name} not found') from e self.log.debug("Serving kernel resource from: %s", self.root) return web.StaticFileHandler.get(self, path, include_body=include_body) @@ -24,5 +24,5 @@ def head(self, kernel_name, path): return self.get(kernel_name, path, include_body=False) default_handlers = [ - (r"/kernelspecs/%s/(?P.*)" % kernel_name_regex, KernelSpecResourceHandler), -] \ No newline at end of file + (fr"/kernelspecs/{kernel_name_regex}/(?P.*)", KernelSpecResourceHandler), +] diff --git a/notebook/log.py b/notebook/log.py index eb246bc400..a495db0641 100644 --- a/notebook/log.py +++ b/notebook/log.py @@ -29,26 +29,30 @@ def log_request(handler, log=access_log, log_json=False): log_method = log.warning else: log_method = log.error - + request_time = 1000.0 * request.request_time() ns = dict( status=status, method=request.method, ip=request.remote_ip, uri=request.uri, - request_time=float('%.2f' % request_time), + request_time=float(f'{request_time:.2f}'), ) msg = "{status} {method} {uri} ({ip}) {request_time:f}ms" if status >= 400: # log bad referers ns['referer'] = request.headers.get('Referer', 'None') - msg = msg + ' referer={referer}' + msg += ' referer={referer}' if status >= 500 and status != 502: - # log all headers if it caused an error + # Log a subset of the headers if it caused an error. + headers = {} + for header in ['Host', 'Accept', 'Referer', 'User-Agent']: + if header in request.headers: + headers[header] = request.headers[header] if log_json: - log_method("", extra=dict(props=dict(request.headers))) + log_method("", extra=dict(props=headers)) else: - log_method(json.dumps(dict(request.headers), indent=2)) + log_method(json.dumps(dict(headers), indent=2)) if log_json: log_method("", extra=dict(props=ns)) else: diff --git a/notebook/nbconvert/handlers.py b/notebook/nbconvert/handlers.py index 24a3f093fb..c5c8de252a 100644 --- a/notebook/nbconvert/handlers.py +++ b/notebook/nbconvert/handlers.py @@ -61,19 +61,19 @@ def get_exporter(format, **kwargs): try: from nbconvert.exporters.base import get_exporter except ImportError as e: - raise web.HTTPError(500, "Could not import nbconvert: %s" % e) from e + raise web.HTTPError(500, f"Could not import nbconvert: {e}") from e try: Exporter = get_exporter(format) except KeyError as e: # should this be 400? - raise web.HTTPError(404, u"No exporter for format: %s" % format) from e + raise web.HTTPError(404, f"No exporter for format: {format}") from e try: return Exporter(**kwargs) except Exception as e: app_log.exception("Could not construct Exporter: %s", Exporter) - raise web.HTTPError(500, "Could not construct Exporter: %s" % e) from e + raise web.HTTPError(500, f"Could not construct Exporter: {e}") from e class NbconvertFileHandler(IPythonHandler): @@ -132,7 +132,7 @@ def get(self, format, path): ) except Exception as e: self.log.exception("nbconvert failed: %s", e) - raise web.HTTPError(500, "nbconvert failed: %s" % e) from e + raise web.HTTPError(500, f"nbconvert failed: {e}") from e if respond_zip(self, name, output, resources): return @@ -145,7 +145,7 @@ def get(self, format, path): # MIME type if exporter.output_mimetype: self.set_header('Content-Type', - '%s; charset=utf-8' % exporter.output_mimetype) + f'{exporter.output_mimetype}; charset=utf-8') self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') self.finish(output) @@ -173,7 +173,7 @@ def post(self, format): "config_dir": self.application.settings['config_dir'], }) except Exception as e: - raise web.HTTPError(500, "nbconvert failed: %s" % e) from e + raise web.HTTPError(500, f"nbconvert failed: {e}") from e if respond_zip(self, name, output, resources): return @@ -181,7 +181,7 @@ def post(self, format): # MIME type if exporter.output_mimetype: self.set_header('Content-Type', - '%s; charset=utf-8' % exporter.output_mimetype) + f'{exporter.output_mimetype}; charset=utf-8') self.finish(output) @@ -194,7 +194,6 @@ def post(self, format): default_handlers = [ - (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), - (r"/nbconvert/%s%s" % (_format_regex, path_regex), - NbconvertFileHandler), + (fr"/nbconvert/{_format_regex}", NbconvertPostHandler), + (fr"/nbconvert/{_format_regex}{path_regex}", NbconvertFileHandler), ] diff --git a/notebook/nbconvert/tests/test_nbconvert_handlers.py b/notebook/nbconvert/tests/test_nbconvert_handlers.py index e5af13c0c4..93b83738c1 100644 --- a/notebook/nbconvert/tests/test_nbconvert_handlers.py +++ b/notebook/nbconvert/tests/test_nbconvert_handlers.py @@ -1,10 +1,8 @@ -import io import json import os from os.path import join as pjoin import shutil -import requests import pytest from notebook.utils import url_path_join from notebook.tests.launchnotebook import NotebookTestBase, assert_http_error @@ -13,8 +11,6 @@ new_notebook, new_markdown_cell, new_code_cell, new_output, ) -from ipython_genutils.testing.decorators import onlyif_cmds_exist - from base64 import encodebytes @@ -25,7 +21,7 @@ def cmd_exists(cmd): return True -class NbconvertAPI(object): +class NbconvertAPI: """Wrapper for nbconvert API calls.""" def __init__(self, request): self.request = request @@ -72,16 +68,16 @@ def cleanup_dir(): nb = new_notebook() - nb.cells.append(new_markdown_cell(u'Created by test ³')) - cc1 = new_code_cell(source=u'print(2*6)') - cc1.outputs.append(new_output(output_type="stream", text=u'12')) + nb.cells.append(new_markdown_cell('Created by test ³')) + cc1 = new_code_cell(source='print(2*6)') + cc1.outputs.append(new_output(output_type="stream", text='12')) cc1.outputs.append(new_output(output_type="execute_result", data={'image/png' : png_green_pixel}, execution_count=1, )) nb.cells.append(cc1) - with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', + with open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', encoding='utf-8') as f: write(nb, f, version=4) @@ -94,13 +90,13 @@ def cleanup_dir(): def test_from_file(self): r = self.nbconvert_api.from_file('html', 'foo', 'testnb.ipynb') self.assertEqual(r.status_code, 200) - self.assertIn(u'text/html', r.headers['Content-Type']) - self.assertIn(u'Created by test', r.text) - self.assertIn(u'print', r.text) + self.assertIn('text/html', r.headers['Content-Type']) + self.assertIn('Created by test', r.text) + self.assertIn('print', r.text) r = self.nbconvert_api.from_file('python', 'foo', 'testnb.ipynb') - self.assertIn(u'text/x-python', r.headers['Content-Type']) - self.assertIn(u'print(2*6)', r.text) + self.assertIn('text/x-python', r.headers['Content-Type']) + self.assertIn('print(2*6)', r.text) @pytest.mark.skipif( not cmd_exists('pandoc'), @@ -126,8 +122,8 @@ def test_from_file_download(self): ) def test_from_file_zip(self): r = self.nbconvert_api.from_file('latex', 'foo', 'testnb.ipynb', download=True) - self.assertIn(u'application/zip', r.headers['Content-Type']) - self.assertIn(u'.zip', r.headers['Content-Disposition']) + self.assertIn('application/zip', r.headers['Content-Type']) + self.assertIn('.zip', r.headers['Content-Disposition']) @pytest.mark.skipif( not cmd_exists('pandoc'), @@ -138,13 +134,13 @@ def test_from_post(self): r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) self.assertEqual(r.status_code, 200) - self.assertIn(u'text/html', r.headers['Content-Type']) - self.assertIn(u'Created by test', r.text) - self.assertIn(u'print', r.text) + self.assertIn('text/html', r.headers['Content-Type']) + self.assertIn('Created by test', r.text) + self.assertIn('print', r.text) r = self.nbconvert_api.from_post(format='python', nbmodel=nbmodel) - self.assertIn(u'text/x-python', r.headers['Content-Type']) - self.assertIn(u'print(2*6)', r.text) + self.assertIn('text/x-python', r.headers['Content-Type']) + self.assertIn('print(2*6)', r.text) @pytest.mark.skipif( not cmd_exists('pandoc'), @@ -154,5 +150,5 @@ def test_from_post_zip(self): nbmodel = self.request('GET', 'api/contents/foo/testnb.ipynb').json() r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) - self.assertIn(u'application/zip', r.headers['Content-Type']) - self.assertIn(u'.zip', r.headers['Content-Disposition']) + self.assertIn('application/zip', r.headers['Content-Type']) + self.assertIn('.zip', r.headers['Content-Disposition']) diff --git a/notebook/nbextensions.py b/notebook/nbextensions.py index aac752cce9..3619f791ef 100644 --- a/notebook/nbextensions.py +++ b/notebook/nbextensions.py @@ -35,7 +35,7 @@ def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None, sys_prefix=False): """Check whether nbextension files have been installed - + Returns True if all files are found, False if any are missing. Parameters @@ -58,11 +58,11 @@ def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None, sys # make sure nbextensions dir exists if not os.path.exists(nbext): return False - + if isinstance(files, string_types): # one file given, turn it into a list files = [files] - + return all(os.path.exists(pjoin(nbext, f)) for f in files) @@ -72,14 +72,14 @@ def install_nbextension(path, overwrite=False, symlink=False, logger=None, sys_prefix=False ): """Install a Javascript extension for the notebook - + Stages files and/or directories into the nbextensions directory. By default, this compares modification time, and only stages files that need updating. If `overwrite` is specified, matching files are purged before proceeding. - + Parameters ---------- - + path : path to file, directory, zip or tarball archive, or URL to install By default, the file will be installed with its base name, so '/path/to/foo' will install to 'nbextensions/foo'. See the destination argument below to change this. @@ -116,14 +116,14 @@ def install_nbextension(path, overwrite=False, symlink=False, nbext = _get_nbextension_dir(user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir) # make sure nbextensions dir exists ensure_dir_exists(nbext) - + # forcing symlink parameter to False if os.symlink does not exist (e.g., on Windows machines running python 2) if not hasattr(os, 'symlink'): symlink = False - + if isinstance(path, (list, tuple)): raise TypeError("path must be a string pointing to a single extension to install; call this function multiple times to install multiple extensions") - + path = cast_unicode_py2(path) if path.startswith(('https://', 'http://')): @@ -134,7 +134,7 @@ def install_nbextension(path, overwrite=False, symlink=False, filename = urlparse(path).path.split('/')[-1] local_path = os.path.join(td, filename) if logger: - logger.info("Downloading: %s -> %s" % (path, local_path)) + logger.info(f"Downloading: {path} -> {local_path}") urlretrieve(path, local_path) # now install from the local copy full_dest = install_nbextension(local_path, overwrite=overwrite, symlink=symlink, @@ -145,7 +145,7 @@ def install_nbextension(path, overwrite=False, symlink=False, if destination: raise ValueError("Cannot give destination for archives") if logger: - logger.info("Extracting: %s -> %s" % (path, nbext)) + logger.info(f"Extracting: {path} -> {nbext}") if path.endswith('.zip'): archive = zipfile.ZipFile(path) @@ -162,7 +162,7 @@ def install_nbextension(path, overwrite=False, symlink=False, full_dest = normpath(pjoin(nbext, destination)) if overwrite and os.path.lexists(full_dest): if logger: - logger.info("Removing: %s" % full_dest) + logger.info(f"Removing: {full_dest}") if os.path.isdir(full_dest) and not os.path.islink(full_dest): shutil.rmtree(full_dest) else: @@ -172,7 +172,7 @@ def install_nbextension(path, overwrite=False, symlink=False, path = os.path.abspath(path) if not os.path.exists(full_dest): if logger: - logger.info("Symlinking: %s -> %s" % (full_dest, path)) + logger.info(f"Symlinking: {full_dest} -> {path}") os.symlink(path, full_dest) elif os.path.isdir(path): path = pjoin(os.path.abspath(path), '') # end in path separator @@ -180,7 +180,7 @@ def install_nbextension(path, overwrite=False, symlink=False, dest_dir = pjoin(full_dest, parent[len(path):]) if not os.path.exists(dest_dir): if logger: - logger.info("Making directory: %s" % dest_dir) + logger.info(f"Making directory: {dest_dir}") os.makedirs(dest_dir) for file_name in files: src = pjoin(parent, file_name) @@ -210,7 +210,7 @@ def install_nbextension_python(module, overwrite=False, symlink=False, dest = nbext['dest'] if logger: - logger.info("Installing %s -> %s" % (src, dest)) + logger.info(f"Installing {src} -> {dest}") full_dest = install_nbextension( src, overwrite=overwrite, symlink=symlink, user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir, @@ -222,16 +222,16 @@ def install_nbextension_python(module, overwrite=False, symlink=False, return full_dests -def uninstall_nbextension(dest, require=None, user=False, sys_prefix=False, prefix=None, +def uninstall_nbextension(dest, require=None, user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, logger=None): """Uninstall a Javascript extension of the notebook - - Removes staged files and/or directories in the nbextensions directory and + + Removes staged files and/or directories in the nbextensions directory and removes the extension from the frontend config. - + Parameters ---------- - + dest : str path to file, directory, zip or tarball archive, or URL to install name the nbextension is installed to. For example, if destination is 'foo', then @@ -256,12 +256,12 @@ def uninstall_nbextension(dest, require=None, user=False, sys_prefix=False, pref full_dest = pjoin(nbext, dest) if os.path.lexists(full_dest): if logger: - logger.info("Removing: %s" % full_dest) + logger.info(f"Removing: {full_dest}") if os.path.isdir(full_dest) and not os.path.islink(full_dest): shutil.rmtree(full_dest) else: os.remove(full_dest) - + # Look through all of the config sections making sure that the nbextension # doesn't exist. config_dir = os.path.join(_get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig') @@ -281,7 +281,7 @@ def _find_uninstall_nbextension(filename, logger=None): path = pjoin(nbext, filename) if os.path.lexists(path): if logger: - logger.info("Removing: %s" % path) + logger.info(f"Removing: {path}") if os.path.isdir(path) and not os.path.islink(path): shutil.rmtree(path) else: @@ -295,7 +295,7 @@ def uninstall_nbextension_python(module, user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, logger=None): """Uninstall an nbextension bundled in a Python package. - + See parameters of `install_nbextension_python` """ m, nbexts = _get_nbextension_metadata(module) @@ -303,8 +303,8 @@ def uninstall_nbextension_python(module, dest = nbext['dest'] require = nbext['require'] if logger: - logger.info("Uninstalling {} {}".format(dest, require)) - uninstall_nbextension(dest, require, user=user, sys_prefix=sys_prefix, + logger.info(f"Uninstalling {dest} {require}") + uninstall_nbextension(dest, require, user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir, logger=logger) @@ -335,11 +335,7 @@ def _set_nbextension_state(section, require, state, _get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig') cm = BaseJSONConfigManager(config_dir=config_dir) if logger: - logger.info("{} {} extension {}...".format( - "Enabling" if state else "Disabling", - section, - require - )) + logger.info(f"{'Enabling' if state else 'Disabling'} {section} extension {require}...") cm.update(section, {"load_extensions": {require: state}}) validate_nbextension(require, logger=logger) @@ -408,7 +404,7 @@ def enable_nbextension(section, require, user=True, sys_prefix=False, def disable_nbextension(section, require, user=True, sys_prefix=False, logger=None): """Disable a named nbextension - + Returns True if the final state is the one requested. Parameters @@ -478,7 +474,7 @@ def enable_nbextension_python(module, user=True, sys_prefix=False, def disable_nbextension_python(module, user=True, sys_prefix=False, logger=None): """Disable some nbextensions associated with a Python module. - + Returns True if the final state is the one requested. Parameters @@ -516,27 +512,27 @@ def validate_nbextension(require, logger=None): js_exists = False for exts in jupyter_path('nbextensions'): # Does the Javascript entrypoint actually exist on disk? - js = u"{}.js".format(os.path.join(exts, *require.split("/"))) + js = f"{os.path.join(exts, *require.split('/'))}.js" js_exists = os.path.exists(js) if js_exists: break - require_tmpl = u" - require? {} {}" + require_tmpl = " - require? {} {}" if js_exists: infos.append(require_tmpl.format(GREEN_OK, require)) else: warnings.append(require_tmpl.format(RED_X, require)) - + if logger: if warnings: - logger.warning(u" - Validating: problems found:") + logger.warning(" - Validating: problems found:") for msg in warnings: logger.warning(msg) for msg in infos: logger.info(msg) else: - logger.info(u" - Validating: {}".format(GREEN_OK)) - + logger.info(f" - Validating: {GREEN_OK}") + return warnings @@ -567,19 +563,19 @@ def validate_nbextension_python(spec, full_dest, logger=None): section = spec.get("section", None) if section in NBCONFIG_SECTIONS: - infos.append(u" {} section: {}".format(GREEN_OK, section)) + infos.append(f" {GREEN_OK} section: {section}") else: - warnings.append(u" {} section: {}".format(RED_X, section)) + warnings.append(f" {RED_X} section: {section}") require = spec.get("require", None) if require is not None: require_path = os.path.join( full_dest[0:-len(spec["dest"])], - u"{}.js".format(require)) + f"{require}.js") if os.path.exists(require_path): - infos.append(u" {} require: {}".format(GREEN_OK, require_path)) + infos.append(f" {GREEN_OK} require: {require_path}") else: - warnings.append(u" {} require: {}".format(RED_X, require_path)) + warnings.append(f" {RED_X} require: {require_path}") if logger: if warnings: @@ -588,9 +584,9 @@ def validate_nbextension_python(spec, full_dest, logger=None): logger.warning(msg) for msg in infos: logger.info(msg) - logger.warning(u"Full spec: {}".format(spec)) + logger.warning(f"Full spec: {spec}") else: - logger.info(u"- Validating: {}".format(GREEN_OK)) + logger.info(f"- Validating: {GREEN_OK}") return warnings @@ -633,24 +629,24 @@ def validate_nbextension_python(spec, full_dest, logger=None): class InstallNBExtensionApp(BaseExtensionApp): """Entry point for installing notebook extensions""" description = """Install Jupyter notebook extensions - + Usage - + jupyter nbextension install path|url [--user|--sys-prefix] - + This copies a file or a folder into the Jupyter nbextensions directory. If a URL is given, it will be downloaded. If an archive is given, it will be extracted into nbextensions. If the requested files are already up to date, no action is taken unless --overwrite is specified. """ - + examples = """ jupyter nbextension install /path/to/myextension """ aliases = aliases flags = flags - + overwrite = Bool(False, config=True, help="Force overwrite of existing files") symlink = Bool(False, config=True, help="Create symlinks instead of copying files") @@ -662,7 +658,7 @@ class InstallNBExtensionApp(BaseExtensionApp): def _config_file_name_default(self): """The default config file name.""" return 'jupyter_notebook_config' - + def install_extensions(self): """Perform the installation of nbextension(s)""" if len(self.extra_args)>1: @@ -675,7 +671,7 @@ def install_extensions(self): else: install = install_nbextension kwargs = {'destination': self.destination} - + full_dests = install(self.extra_args[0], overwrite=self.overwrite, symlink=self.symlink, @@ -689,14 +685,12 @@ def install_extensions(self): if full_dests: self.log.info( - u"\nTo initialize this nbextension in the browser every time" - " the notebook (or other app) loads:\n\n" - " jupyter nbextension enable {}{}{}{}\n".format( - self.extra_args[0] if self.python else "", - " --user" if self.user else "", - " --py" if self.python else "", - " --sys-prefix" if self.sys_prefix else "" - ) + f"\nTo initialize this nbextension in the browser every time" + f" the notebook (or other app) loads:\n\n" + f" jupyter nbextension enable {self.extra_args[0] if self.python else ''}" + f"{' --user' if self.user else ''}" + f"{' --py' if self.python else ''}" + f"{' --sys-prefix' if self.sys_prefix else ''}\n" ) def start(self): @@ -714,9 +708,9 @@ class UninstallNBExtensionApp(BaseExtensionApp): """Entry point for uninstalling notebook extensions""" version = __version__ description = """Uninstall Jupyter notebook extensions - + Usage - + jupyter nbextension uninstall path/url path/url/entrypoint jupyter nbextension uninstall --py pythonPackageName @@ -734,12 +728,12 @@ class UninstallNBExtensionApp(BaseExtensionApp): It will uninstall nbextensions listed in that module, but not the module itself (which you should uninstall using a package manager such as pip). """ - + examples = """ jupyter nbextension uninstall dest/dir dest/dir/extensionjs jupyter nbextension uninstall --py extensionPyPackage """ - + aliases = { "prefix" : "UninstallNBExtensionApp.prefix", "nbextensions" : "UninstallNBExtensionApp.nbextensions_dir", @@ -748,7 +742,7 @@ class UninstallNBExtensionApp(BaseExtensionApp): flags = BaseExtensionApp.flags.copy() flags['system'] = ({'UninstallNBExtensionApp': {'system': True}}, "Uninstall specifically from systemwide installation directory") - + prefix = Unicode('', config=True, help="Installation prefix. Overrides --user, --sys-prefix and --system" ) @@ -759,7 +753,7 @@ class UninstallNBExtensionApp(BaseExtensionApp): system = Bool(False, config=True, help="Uninstall specifically from systemwide installation directory" ) - + def _config_file_name_default(self): """The default config file name.""" return 'jupyter_notebook_config' @@ -800,7 +794,7 @@ def find_uninstall_extension(self): changed = _find_uninstall_nbextension(name, logger=self.log) if not changed: - print("No installed extension %r found." % name) + print(f"No installed extension {name!r} found.") if self.require: for section in NBCONFIG_SECTIONS: @@ -837,13 +831,13 @@ class ToggleNBExtensionApp(BaseExtensionApp): user = Bool(True, config=True, help="Apply the configuration only for the current user (default)") aliases = {'section': 'ToggleNBExtensionApp.section'} - + _toggle_value = None def _config_file_name_default(self): """The default config file name.""" return 'jupyter_notebook_config' - + def toggle_nbextension_python(self, module): """Toggle some extensions in an importable Python module. @@ -878,7 +872,7 @@ def toggle_nbextension(self, require): return toggle(self.section, require, user=self.user, sys_prefix=self.sys_prefix, logger=self.log) - + def start(self): if not self.extra_args: sys.exit('Please specify an nbextension/package to enable or disable') @@ -895,7 +889,7 @@ class EnableNBExtensionApp(ToggleNBExtensionApp): name = "jupyter nbextension enable" description = """ Enable an nbextension in frontend configuration. - + Usage jupyter nbextension enable [--system|--sys-prefix] """ @@ -907,7 +901,7 @@ class DisableNBExtensionApp(ToggleNBExtensionApp): name = "jupyter nbextension disable" description = """ Disable an nbextension in frontend configuration. - + Usage jupyter nbextension disable [--system|--sys-prefix] """ @@ -919,15 +913,15 @@ class ListNBExtensionsApp(BaseExtensionApp): name = "jupyter nbextension list" version = __version__ description = "List all nbextensions known by the configuration system" - + def list_nbextensions(self): """List all the nbextensions""" config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()] - + print("Known nbextensions:") - + for config_dir in config_dirs: - head = u' config dir: {}'.format(config_dir) + head = f' config dir: {config_dir}' head_shown = False cm = BaseJSONConfigManager(parent=self, config_dir=config_dir) @@ -938,15 +932,13 @@ def list_nbextensions(self): # only show heading if there is an nbextension here print(head) head_shown = True - print(u' {} section'.format(section)) - + print(f' {section} section') + for require, enabled in data['load_extensions'].items(): - print(u' {} {}'.format( - require, - GREEN_ENABLED if enabled else RED_DISABLED)) + print(f' {require} {GREEN_ENABLED if enabled else RED_DISABLED}') if enabled: validate_nbextension(require, logger=self.log) - + def start(self): """Perform the App's functions as configured""" self.list_nbextensions() @@ -982,7 +974,7 @@ def start(self): # The above should have called a subcommand and raised NoStart; if we # get here, it didn't, so we should self.log.info a message. subcmds = ", ".join(sorted(self.subcommands)) - sys.exit("Please supply at least one subcommand: %s" % subcmds) + sys.exit(f"Please supply at least one subcommand: {subcmds}") main = NBExtensionApp.launch_instance @@ -1012,10 +1004,10 @@ def _should_copy(src, dest, logger=None): # we add a fudge factor to work around a bug in python 2.x # that was fixed in python 3.x: https://bugs.python.org/issue12904 if logger: - logger.warn("Out of date: %s" % dest) + logger.warn(f"Out of date: {dest}") return True if logger: - logger.info("Up to date: %s" % dest) + logger.info(f"Up to date: {dest}") return False @@ -1034,7 +1026,7 @@ def _maybe_copy(src, dest, logger=None): """ if _should_copy(src, dest, logger=logger): if logger: - logger.info("Copying: %s -> %s" % (src, dest)) + logger.info(f"Copying: {src} -> {dest}") shutil.copy2(src, dest) @@ -1051,7 +1043,7 @@ def _safe_is_tarfile(path): """ try: return tarfile.is_tarfile(path) - except IOError: + except OSError: return False @@ -1076,15 +1068,16 @@ def _get_nbextension_dir(user=False, sys_prefix=False, prefix=None, nbextensions ('nbextensions_dir', nbextensions_dir), ('sys_prefix', sys_prefix), ] - conflicting_set = ['{}={!r}'.format(n, v) for n, v in conflicting if v] + conflicting_set = [f'{n}={v!r}' for n, v in conflicting if v] if len(conflicting_set) > 1: raise ArgumentConflict( - "cannot specify more than one of user, sys_prefix, prefix, or nbextensions_dir, but got: {}" - .format(', '.join(conflicting_set))) + f"cannot specify more than one of user, sys_prefix, prefix, or nbextensions_dir, " + f"but got: {', '.join(conflicting_set)}" + ) if user: - nbext = pjoin(jupyter_data_dir(), u'nbextensions') + nbext = pjoin(jupyter_data_dir(), 'nbextensions') elif sys_prefix: - nbext = pjoin(ENV_JUPYTER_PATH[0], u'nbextensions') + nbext = pjoin(ENV_JUPYTER_PATH[0], 'nbextensions') elif prefix: nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions') elif nbextensions_dir: @@ -1113,8 +1106,10 @@ def _get_nbextension_metadata(module): """ m = import_item(module) if not hasattr(m, '_jupyter_nbextension_paths'): - raise KeyError('The Python module {} is not a valid nbextension, ' - 'it is missing the `_jupyter_nbextension_paths()` method.'.format(module)) + raise KeyError( + f'The Python module {module} is not a valid nbextension, ' + f'it is missing the `_jupyter_nbextension_paths()` method.' + ) nbexts = m._jupyter_nbextension_paths() return m, nbexts diff --git a/notebook/notebook/handlers.py b/notebook/notebook/handlers.py index 638931d6be..a725810054 100644 --- a/notebook/notebook/handlers.py +++ b/notebook/notebook/handlers.py @@ -46,13 +46,12 @@ def get_frontend_exporters(): # Ensure export_from_notebook is explicitly defined & not inherited if ux_name is not None and ux_name != super_uxname: - display = _('{} ({})'.format(ux_name, - exporter_instance.file_extension)) + display = _(f'{ux_name} ({exporter_instance.file_extension})') frontend_exporters.append(ExporterInfo(name, display)) # Ensure default_exporters are in frontend_exporters if not already # This protects against nbconvert versions lower than 5.5 - names = set(exporter.name.lower() for exporter in frontend_exporters) + names = {exporter.name.lower() for exporter in frontend_exporters} for exporter in default_exporters: if exporter.name not in names: frontend_exporters.append(exporter) @@ -74,11 +73,11 @@ class NotebookHandler(IPythonHandler): @web.authenticated @gen.coroutine def get(self, path): - """get renders the notebook template if a name is given, or + """get renders the notebook template if a name is given, or redirects to the '/files/' handler if the name is not given.""" path = path.strip('/') cm = self.contents_manager - + # will raise 404 on not found try: model = yield maybe_future(cm.get(path, content=False)) @@ -109,6 +108,6 @@ def get(self, path): default_handlers = [ - (r"/notebooks%s" % path_regex, NotebookHandler), + (fr"/notebooks{path_regex}", NotebookHandler), ] diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index e07bfcf507..e656f72802 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -14,7 +14,6 @@ import hmac import importlib import inspect -import io import ipaddress import json import logging @@ -67,11 +66,12 @@ from notebook import ( DEFAULT_NOTEBOOK_PORT, - DEFAULT_STATIC_FILES_PATH, DEFAULT_TEMPLATE_PATH_LIST, __version__, ) - +import nbclassic +# Packagers: modify this line if you store the notebook static files elsewhere +DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(nbclassic.__file__), "static") from .base.handlers import Template404, RedirectWithParams from .log import log_request @@ -115,7 +115,6 @@ url_path_join, urldecode_unix_socket_path, urlencode_unix_socket, - urlencode_unix_socket_path, urljoin, ) from .traittypes import TypeFromClasses @@ -186,6 +185,20 @@ def __init__(self, jupyter_app, kernel_manager, contents_manager, if settings['autoreload']: log.info('Autoreload enabled: the webapp will restart when any Python src file changes.') + print(""" + _ _ _ _ + | | | |_ __ __| |__ _| |_ ___ + | |_| | '_ \/ _` / _` | _/ -_) + \___/| .__/\__,_\__,_|\__\___| + |_| + +Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions. + +https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html + +Please note that updating to Notebook 7 might break some of your extensions. +""") + super().__init__(handlers, **settings) def init_settings(self, jupyter_app, kernel_manager, contents_manager, @@ -217,13 +230,13 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager, env.install_gettext_translations(nbui, newstyle=False) if dev_mode: - DEV_NOTE_NPM = """It looks like you're running the notebook from source. + DEV_NOTE_NPM = f"""It looks like you're running the notebook from source. If you're working on the Javascript of the notebook, try running - %s + {'npm run build:watch'} in another terminal window to have the system incrementally - watch and build the notebook's JavaScript for you, as you make changes.""" % 'npm run build:watch' + watch and build the notebook's JavaScript for you, as you make changes.""" log.info(DEV_NOTE_NPM) if sys_info['commit_source'] == 'repository': @@ -265,6 +278,7 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager, }, version_hash=version_hash, ignore_minified_js=jupyter_app.ignore_minified_js, + show_banner=jupyter_app.show_banner, # rate limits iopub_msg_rate_limit=jupyter_app.iopub_msg_rate_limit, @@ -426,7 +440,7 @@ def _config_file_default(self): def start(self): from .auth.security import set_password set_password(config_file=self.config_file) - self.log.info("Wrote hashed password to %s" % self.config_file) + self.log.info(f"Wrote hashed password to {self.config_file}") def shutdown_server(server_info, timeout=5, log=None): @@ -505,9 +519,9 @@ class NbserverStopApp(JupyterApp): description="Stop currently running notebook server." port = Integer(DEFAULT_NOTEBOOK_PORT, config=True, - help="Port of the server to be killed. Default %s" % DEFAULT_NOTEBOOK_PORT) + help=f"Port of the server to be killed. Default {DEFAULT_NOTEBOOK_PORT}") - sock = Unicode(u'', config=True, + sock = Unicode('', config=True, help="UNIX socket of the server to be killed.") def parse_command_line(self, argv=None): @@ -523,7 +537,7 @@ def shutdown_server(self, server): return shutdown_server(server, log=self.log) def _shutdown_or_exit(self, target_endpoint, server): - print("Shutting down server on %s..." % target_endpoint) + print(f"Shutting down server on {target_endpoint}...") server_stopped = self.shutdown_server(server) if not server_stopped and sys.platform.startswith('win'): # the pid check on Windows appears to be unreliable, so fetch another @@ -533,13 +547,13 @@ def _shutdown_or_exit(self, target_endpoint, server): if server not in servers: server_stopped = True if not server_stopped: - sys.exit("Could not stop server on %s" % target_endpoint) + sys.exit(f"Could not stop server on {target_endpoint}") @staticmethod def _maybe_remove_unix_socket(socket_path): try: os.unlink(socket_path) - except (OSError, IOError): + except OSError: pass def start(self): @@ -563,7 +577,7 @@ def start(self): else: current_endpoint = self.sock or self.port print( - "There is currently no server running on {}".format(current_endpoint), + f"There is currently no server running on {current_endpoint}", file=sys.stderr ) print("Ports/sockets currently in use:", file=sys.stderr) @@ -753,7 +767,7 @@ def _default_log_datefmt(self): @default('log_format') def _default_log_format(self): """override default log format to include time""" - return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s" + return "%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s" ignore_minified_js = Bool(False, config=True, @@ -834,7 +848,7 @@ def _default_ip(self): s = socket.socket() try: s.bind(('localhost', 0)) - except socket.error as e: + except OSError as e: self.log.warning(_("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s"), e) return '127.0.0.1' else: @@ -844,11 +858,11 @@ def _default_ip(self): @validate('ip') def _validate_ip(self, proposal): value = proposal['value'] - if value == u'*': - value = u'' + if value == '*': + value = '' return value - custom_display_url = Unicode(u'', config=True, + custom_display_url = Unicode('', config=True, help=_("""Override URL shown to users. Replace actual URL, including protocol, address, port and base URL, @@ -883,7 +897,7 @@ def port_retries_default(self): return int(os.getenv(self.port_retries_env, self.port_retries_default_value)) - sock = Unicode(u'', config=True, + sock = Unicode('', config=True, help=_("The UNIX socket the notebook server will listen on.") ) @@ -914,15 +928,15 @@ def _validate_sock_mode(self, proposal): return value - certfile = Unicode(u'', config=True, + certfile = Unicode('', config=True, help=_("""The full path to an SSL/TLS certificate file.""") ) - keyfile = Unicode(u'', config=True, + keyfile = Unicode('', config=True, help=_("""The full path to a private key file for usage with SSL/TLS.""") ) - client_ca = Unicode(u'', config=True, + client_ca = Unicode('', config=True, help=_("""The full path to a certificate authority certificate for SSL/TLS client authentication.""") ) @@ -947,7 +961,7 @@ def _default_cookie_secret_file(self): @default('cookie_secret') def _default_cookie_secret(self): if os.path.exists(self.cookie_secret_file): - with io.open(self.cookie_secret_file, 'rb') as f: + with open(self.cookie_secret_file, 'rb') as f: key = f.read() else: key = encodebytes(os.urandom(32)) @@ -960,7 +974,7 @@ def _write_cookie_secret_file(self, secret): """write my secret to my secret_file""" self.log.info(_("Writing notebook server cookie secret to %s"), self.cookie_secret_file) try: - with io.open(self.cookie_secret_file, 'wb') as f: + with open(self.cookie_secret_file, 'wb') as f: f.write(secret) except OSError as e: self.log.error(_("Failed to write cookie secret to %s: %s"), @@ -995,12 +1009,12 @@ def _token_default(self): return os.getenv('JUPYTER_TOKEN') if os.getenv('JUPYTER_TOKEN_FILE'): self._token_generated = False - with io.open(os.getenv('JUPYTER_TOKEN_FILE'), "r") as token_file: + with open(os.getenv('JUPYTER_TOKEN_FILE')) as token_file: return token_file.read() if self.password: # no token if password is enabled self._token_generated = False - return u'' + return '' else: self._token_generated = True return binascii.hexlify(os.urandom(24)).decode('ascii') @@ -1051,7 +1065,7 @@ def _default_min_open_files_limit(self): def _token_changed(self, change): self._token_generated = False - password = Unicode(u'', config=True, + password = Unicode('', config=True, help="""Hashed password to use for web authentication. To generate, type in a python/IPython shell: @@ -1158,6 +1172,12 @@ def _default_allow_remote(self): """ ) + show_banner = Bool(True, config=True, + help="""Whether the banner is displayed on the page. + + By default, the banner is displayed. + """) + open_browser = Bool(True, config=True, help="""Whether to open in a browser after starting. The specific browser used is platform dependent and @@ -1166,7 +1186,7 @@ def _default_allow_remote(self): (NotebookApp.browser) configuration option. """) - browser = Unicode(u'', config=True, + browser = Unicode('', config=True, help="""Specify what command to use to invoke a web browser when opening the notebook. If not specified, the default browser will be determined by the `webbrowser` @@ -1252,7 +1272,7 @@ def _update_webapp_settings(self, change): def _update_enable_mathjax(self, change): """set mathjax url to empty if mathjax is disabled""" if not change['new']: - self.mathjax_url = u'' + self.mathjax_url = '' base_url = Unicode('/', config=True, help='''The base URL for the notebook server. @@ -1351,7 +1371,7 @@ def nbextensions_path(self): @default('mathjax_url') def _default_mathjax_url(self): if not self.enable_mathjax: - return u'' + return '' static_url_prefix = self.tornado_settings.get("static_url_prefix", "static") return url_path_join(static_url_prefix, 'components', 'MathJax', 'MathJax.js') @@ -1360,7 +1380,7 @@ def _update_mathjax_url(self, change): new = change['new'] if new and not self.enable_mathjax: # enable_mathjax=False overrides mathjax_url - self.mathjax_url = u'' + self.mathjax_url = '' else: self.log.info(_("Using MathJax: %s"), new) @@ -1701,7 +1721,7 @@ def init_resources(self): if hard < soft: hard = soft self.log.debug( - 'Raising open file limit: soft {}->{}; hard {}->{}'.format(old_soft, soft, old_hard, hard) + f'Raising open file limit: soft {old_soft}->{soft}; hard {old_hard}->{hard}' ) resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard)) @@ -1803,7 +1823,7 @@ def _bind_http_server_unix(self): try: sock = bind_unix_socket(self.sock, mode=int(self.sock_mode.encode(), 8)) self.http_server.add_socket(sock) - except socket.error as e: + except OSError as e: if e.errno == errno.EADDRINUSE: self.log.warning(_('The socket %s is already in use.') % self.sock) return False @@ -1820,7 +1840,7 @@ def _bind_http_server_tcp(self): for port in random_ports(self.port, self.port_retries+1): try: self.http_server.listen(port, self.ip) - except socket.error as e: + except OSError as e: eacces = (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)) if sys.platform == 'cygwin': # Cygwin has a bug that causes EPERM to be returned in this @@ -1886,7 +1906,7 @@ def connection_url(self): return self._tcp_url(ip) def _unix_sock_url(self, token=None): - return '%s%s' % (urlencode_unix_socket(self.sock), self.base_url) + return f'{urlencode_unix_socket(self.sock)}{self.base_url}' def _tcp_url(self, ip, port=None): proto = 'https' if self.certfile else 'http' @@ -2411,7 +2431,7 @@ def list_running_servers(runtime_dir=None): for file_name in os.listdir(runtime_dir): if re.match('nbserver-(.+).json', file_name): - with io.open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f: + with open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f: info = json.load(f) # Simple check whether that process is really still running diff --git a/notebook/prometheus/log_functions.py b/notebook/prometheus/log_functions.py index a67a252ade..f82e0f970e 100644 --- a/notebook/prometheus/log_functions.py +++ b/notebook/prometheus/log_functions.py @@ -19,6 +19,6 @@ def prometheus_log_method(handler): """ HTTP_REQUEST_DURATION_SECONDS.labels( method=handler.request.method, - handler='{}.{}'.format(handler.__class__.__module__, type(handler).__name__), + handler=f'{handler.__class__.__module__}.{type(handler).__name__}', status_code=handler.get_status() ).observe(handler.request.request_time()) diff --git a/notebook/serverextensions.py b/notebook/serverextensions.py index 72020a24f5..7a56a8fca8 100644 --- a/notebook/serverextensions.py +++ b/notebook/serverextensions.py @@ -59,14 +59,14 @@ def toggle_serverextension_python(import_name, enabled=None, parent=None, if logger: if new_enabled: - logger.info(u"Enabling: %s" % (import_name)) + logger.info(f"Enabling: {import_name}") else: - logger.info(u"Disabling: %s" % (import_name)) + logger.info(f"Disabling: {import_name}") server_extensions[import_name] = new_enabled if logger: - logger.info(u"- Writing config: {}".format(config_dir)) + logger.info(f"- Writing config: {config_dir}") cm.update("jupyter_notebook_config", cfg) @@ -104,13 +104,13 @@ def validate_serverextension(import_name, logger=None): except Exception: logger.warning("Error loading server extension %s", import_name) - import_msg = u" {} is {} importable?" + import_msg = " {} is {} importable?" if func is not None: infos.append(import_msg.format(GREEN_OK, import_name)) else: warnings.append(import_msg.format(RED_X, import_name)) - post_mortem = u" {} {} {}" + post_mortem = " {} {} {}" if logger: if warnings: [logger.info(info) for info in infos] @@ -159,13 +159,13 @@ class ToggleServerExtensionApp(BaseExtensionApp): """A base class for enabling/disabling extensions""" name = "jupyter serverextension enable/disable" description = "Enable/disable a server extension using frontend configuration files." - + flags = flags user = Bool(True, config=True, help="Whether to do a user install") sys_prefix = Bool(False, config=True, help="Use the sys.prefix as the prefix") python = Bool(False, config=True, help="Install from a Python package") - + def toggle_server_extension(self, import_name): """Change the status of a named server extension. @@ -215,7 +215,7 @@ class EnableServerExtensionApp(ToggleServerExtensionApp): name = "jupyter serverextension enable" description = """ Enable a serverextension in configuration. - + Usage jupyter serverextension enable [--system|--sys-prefix] """ @@ -227,7 +227,7 @@ class DisableServerExtensionApp(ToggleServerExtensionApp): name = "jupyter serverextension disable" description = """ Disable a serverextension in configuration. - + Usage jupyter serverextension disable [--system|--sys-prefix] """ @@ -254,11 +254,9 @@ def list_server_extensions(self): .setdefault("nbserver_extensions", {}) ) if server_extensions: - print(u'config dir: {}'.format(config_dir)) + print(f'config dir: {config_dir}') for import_name, enabled in server_extensions.items(): - print(u' {} {}'.format( - import_name, - GREEN_ENABLED if enabled else RED_DISABLED)) + print(f' {import_name} {GREEN_ENABLED if enabled else RED_DISABLED}') validate_serverextension(import_name, self.log) def start(self): @@ -293,7 +291,7 @@ def start(self): # The above should have called a subcommand and raised NoStart; if we # get here, it didn't, so we should self.log.info a message. subcmds = ", ".join(sorted(self.subcommands)) - sys.exit("Please supply at least one subcommand: %s" % subcmds) + sys.exit(f"Please supply at least one subcommand: {subcmds}") main = ServerExtensionApp.launch_instance @@ -324,7 +322,7 @@ def _get_server_extension_metadata(module): """ m = import_item(module) if not hasattr(m, '_jupyter_server_extension_paths'): - raise KeyError(u'The Python module {} does not include any valid server extensions'.format(module)) + raise KeyError(f'The Python module {module} does not include any valid server extensions') return m, m._jupyter_server_extension_paths() if __name__ == '__main__': diff --git a/notebook/services/config/handlers.py b/notebook/services/config/handlers.py index aae6480757..899eb4cf22 100644 --- a/notebook/services/config/handlers.py +++ b/notebook/services/config/handlers.py @@ -3,9 +3,6 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import json -import os -import io -import errno from tornado import web from ...base.handlers import APIHandler @@ -35,5 +32,5 @@ def patch(self, section_name): section_name_regex = r"(?P\w+)" default_handlers = [ - (r"/api/config/%s" % section_name_regex, ConfigHandler), + (fr"/api/config/{section_name_regex}", ConfigHandler), ] diff --git a/notebook/services/config/tests/test_config_api.py b/notebook/services/config/tests/test_config_api.py index a4df8f40fc..151f0ea6cc 100644 --- a/notebook/services/config/tests/test_config_api.py +++ b/notebook/services/config/tests/test_config_api.py @@ -2,13 +2,11 @@ import json -import requests - from notebook.utils import url_path_join from notebook.tests.launchnotebook import NotebookTestBase -class ConfigAPI(object): +class ConfigAPI: """Wrapper for notebook API calls.""" def __init__(self, request): self.request = request diff --git a/notebook/services/contents/checkpoints.py b/notebook/services/contents/checkpoints.py index c29a669c22..fd80ef8c0e 100644 --- a/notebook/services/contents/checkpoints.py +++ b/notebook/services/contents/checkpoints.py @@ -53,7 +53,7 @@ def delete_all_checkpoints(self, path): self.delete_checkpoint(checkpoint['id'], path) -class GenericCheckpointsMixin(object): +class GenericCheckpointsMixin: """ Helper for creating Checkpoints subclasses that can be used with any ContentsManager. @@ -90,7 +90,7 @@ def create_checkpoint(self, contents_mgr, path): path, ) else: - raise HTTPError(500, u'Unexpected type %s' % type) + raise HTTPError(500, f'Unexpected type {type}') def restore_checkpoint(self, contents_mgr, checkpoint_id, path): """Restore a checkpoint.""" @@ -100,7 +100,7 @@ def restore_checkpoint(self, contents_mgr, checkpoint_id, path): elif type == 'file': model = self.get_file_checkpoint(checkpoint_id, path) else: - raise HTTPError(500, u'Unexpected type %s' % type) + raise HTTPError(500, f'Unexpected type {type}') contents_mgr.save(model, path) # Required Methods diff --git a/notebook/services/contents/filecheckpoints.py b/notebook/services/contents/filecheckpoints.py index 5a9c835749..540091fd50 100644 --- a/notebook/services/contents/filecheckpoints.py +++ b/notebook/services/contents/filecheckpoints.py @@ -50,7 +50,7 @@ def _root_dir_default(self): # ContentsManager-dependent checkpoint API def create_checkpoint(self, contents_mgr, path): """Create a checkpoint.""" - checkpoint_id = u'checkpoint' + checkpoint_id = 'checkpoint' src_path = contents_mgr._get_os_path(path) dest_path = self.checkpoint_path(checkpoint_id, path) self._copy(src_path, dest_path) @@ -107,11 +107,7 @@ def checkpoint_path(self, checkpoint_id, path): parent, name = ('/' + path).rsplit('/', 1) parent = parent.strip('/') basename, ext = os.path.splitext(name) - filename = u"{name}-{checkpoint_id}{ext}".format( - name=basename, - checkpoint_id=checkpoint_id, - ext=ext, - ) + filename = f"{basename}-{checkpoint_id}{ext}" os_path = self._get_os_path(path=parent) cp_dir = os.path.join(os_path, self.checkpoint_dir) with self.perm_to_403(): @@ -133,7 +129,7 @@ def checkpoint_model(self, checkpoint_id, os_path): def no_such_checkpoint(self, path, checkpoint_id): raise HTTPError( 404, - u'Checkpoint does not exist: %s@%s' % (path, checkpoint_id) + f'Checkpoint does not exist: {path}@{checkpoint_id}' ) @@ -146,7 +142,7 @@ def create_file_checkpoint(self, content, format, path): """Create a checkpoint from the current content of a file.""" path = path.strip('/') # only the one checkpoint ID: - checkpoint_id = u"checkpoint" + checkpoint_id = "checkpoint" os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) self.log.debug("creating checkpoint for %s", path) with self.perm_to_403(): @@ -159,7 +155,7 @@ def create_notebook_checkpoint(self, nb, path): """Create a checkpoint from the current content of a notebook.""" path = path.strip('/') # only the one checkpoint ID: - checkpoint_id = u"checkpoint" + checkpoint_id = "checkpoint" os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) self.log.debug("creating checkpoint for %s", path) with self.perm_to_403(): diff --git a/notebook/services/contents/fileio.py b/notebook/services/contents/fileio.py index f99504e32b..79482cf9ca 100644 --- a/notebook/services/contents/fileio.py +++ b/notebook/services/contents/fileio.py @@ -7,7 +7,6 @@ from contextlib import contextmanager import errno -import io import os import shutil @@ -102,9 +101,9 @@ def atomic_writing(path, text=True, encoding='utf-8', log=None, **kwargs): if text: # Make sure that text files have Unix linefeeds by default kwargs.setdefault('newline', '\n') - fileobj = io.open(path, 'w', encoding=encoding, **kwargs) + fileobj = open(path, 'w', encoding=encoding, **kwargs) else: - fileobj = io.open(path, 'wb', **kwargs) + fileobj = open(path, 'wb', **kwargs) try: yield fileobj @@ -154,9 +153,9 @@ def _simple_writing(path, text=True, encoding='utf-8', log=None, **kwargs): if text: # Make sure that text files have Unix linefeeds by default kwargs.setdefault('newline', '\n') - fileobj = io.open(path, 'w', encoding=encoding, **kwargs) + fileobj = open(path, 'w', encoding=encoding, **kwargs) else: - fileobj = io.open(path, 'wb', **kwargs) + fileobj = open(path, 'wb', **kwargs) try: yield fileobj @@ -197,7 +196,7 @@ class FileManagerMixin(Configurable): def open(self, os_path, *args, **kwargs): """wrapper around io.open that turns permission errors into 403""" with self.perm_to_403(os_path): - with io.open(os_path, *args, **kwargs) as f: + with open(os_path, *args, **kwargs) as f: yield f @contextmanager @@ -218,7 +217,7 @@ def perm_to_403(self, os_path=''): """context manager for turning permission errors into 403.""" try: yield - except (OSError, IOError) as e: + except OSError as e: if e.errno in {errno.EPERM, errno.EACCES}: # make 403 error message without root prefix # this may not work perfectly on unicode paths on Python 2, @@ -226,7 +225,7 @@ def perm_to_403(self, os_path=''): if not os_path: os_path = str_to_unicode(e.filename or 'unknown file') path = to_api_path(os_path, root=self.root_dir) - raise HTTPError(403, u'Permission denied: %s' % path) from e + raise HTTPError(403, f'Permission denied: {path}') from e else: raise @@ -257,7 +256,7 @@ def _get_os_path(self, path): root = os.path.abspath(self.root_dir) os_path = to_os_path(path, root) if not (os.path.abspath(os_path) + os.path.sep).startswith(root): - raise HTTPError(404, "%s is outside root contents directory" % path) + raise HTTPError(404, f"{path} is outside root contents directory") return os_path def _read_notebook(self, os_path, as_version=4): @@ -276,7 +275,7 @@ def _read_notebook(self, os_path, as_version=4): if not self.use_atomic_writing or not os.path.exists(tmp_path): raise HTTPError( 400, - u"Unreadable Notebook: %s %r" % (os_path, e_orig), + f"Unreadable Notebook: {os_path} {e_orig!r}", ) # Move the bad file aside, restore the intermediate, and try again. @@ -300,7 +299,7 @@ def _read_file(self, os_path, format): If not specified, try to decode as UTF-8, and fall back to base64 """ if not os.path.isfile(os_path): - raise HTTPError(400, "Cannot read non-file %s" % os_path) + raise HTTPError(400, f"Cannot read non-file {os_path}") with self.open(os_path, 'rb') as f: bcontent = f.read() @@ -314,7 +313,7 @@ def _read_file(self, os_path, format): if format == 'text': raise HTTPError( 400, - "%s is not UTF-8 encoded" % os_path, + f"{os_path} is not UTF-8 encoded", reason='bad format', ) from e return encodebytes(bcontent).decode('ascii'), 'base64' @@ -334,7 +333,7 @@ def _save_file(self, os_path, content, format): bcontent = decodebytes(b64_bytes) except Exception as e: raise HTTPError( - 400, u'Encoding error saving %s: %s' % (os_path, e) + 400, f'Encoding error saving {os_path}: {e}' ) from e with self.atomic_writing(os_path, text=False) as f: diff --git a/notebook/services/contents/filemanager.py b/notebook/services/contents/filemanager.py index 0c9386b2fc..08e590e64f 100644 --- a/notebook/services/contents/filemanager.py +++ b/notebook/services/contents/filemanager.py @@ -5,7 +5,6 @@ from datetime import datetime import errno -import io import os import shutil import stat @@ -60,7 +59,7 @@ def _post_save_script(model, os_path, contents_manager, **kwargs): script, resources = _script_exporter.from_filename(os_path) script_fname = base + resources.get('output_extension', '.txt') log.info("Saving script /%s", to_api_path(script_fname, contents_manager.root_dir)) - with io.open(script_fname, 'w', encoding='utf-8') as f: + with open(script_fname, 'w', encoding='utf-8') as f: f.write(script) @@ -132,8 +131,7 @@ def run_post_save_hook(self, model, os_path): self.post_save_hook(os_path=os_path, model=model, contents_manager=self) except Exception as e: self.log.error("Post-save hook failed o-n %s", os_path, exc_info=True) - raise web.HTTPError(500, u'Unexpected error while running post hook save: %s' - % e) from e + raise web.HTTPError(500, f'Unexpected error while running post hook save: {e}') from e @validate('root_dir') def _validate_root_dir(self, proposal): @@ -143,7 +141,7 @@ def _validate_root_dir(self, proposal): # If we receive a non-absolute path, make it absolute. value = os.path.abspath(value) if not os.path.isdir(value): - raise TraitError("%r is not a directory" % value) + raise TraitError(f"{value!r} is not a directory") return value @default('checkpoints_class') @@ -243,14 +241,20 @@ def _base_model(self, path): """Build the common base of a contents model""" os_path = self._get_os_path(path) info = os.lstat(os_path) - + + four_o_four = "file or directory does not exist: %r" % path + + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + self.log.info("Refusing to serve hidden file or directory %r, via 404 Error", os_path) + raise web.HTTPError(404, four_o_four) + try: - # size of file + # size of file size = info.st_size except (ValueError, OSError): self.log.warning('Unable to get size.') size = None - + try: last_modified = tz.utcfromtimestamp(info.st_mtime) except (ValueError, OSError): @@ -292,7 +296,7 @@ def _dir_model(self, path, content=True): """ os_path = self._get_os_path(path) - four_o_four = u'directory does not exist: %r' % path + four_o_four = f'directory does not exist: {path!r}' if not os.path.isdir(os_path): raise web.HTTPError(404, four_o_four) @@ -336,7 +340,7 @@ def _dir_model(self, path, content=True): if self.should_list(name): if self.allow_hidden or not is_file_hidden(os_path, stat_res=st): contents.append( - self.get(path='%s/%s' % (path, name), content=False) + self.get(path=f'{path}/{name}', content=False) ) except OSError as e: # ELOOP: recursive symlink @@ -365,6 +369,7 @@ def _file_model(self, path, content=True, format=None): model['type'] = 'file' os_path = self._get_os_path(path) + model['mimetype'] = mimetypes.guess_type(os_path)[0] if content: @@ -392,14 +397,14 @@ def _notebook_model(self, path, content=True): model = self._base_model(path) model['type'] = 'notebook' os_path = self._get_os_path(path) - + if content: nb = self._read_notebook(os_path, as_version=4) self.mark_trusted_cells(nb, path) model['content'] = nb model['format'] = 'json' self.validate_notebook_model(model) - + return model def get(self, path, content=True, type=None, format=None): @@ -425,34 +430,41 @@ def get(self, path, content=True, type=None, format=None): of the file or directory as well. """ path = path.strip('/') + os_path = self._get_os_path(path) + four_o_four = "file or directory does not exist: %r" % path if not self.exists(path): - raise web.HTTPError(404, u'No such file or directory: %s' % path) + raise web.HTTPError(404, four_o_four) - os_path = self._get_os_path(path) + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + self.log.info("Refusing to serve hidden file or directory %r, via 404 Error", os_path) + raise web.HTTPError(404, four_o_four) + + if os.path.isdir(os_path): if type not in (None, 'directory'): raise web.HTTPError(400, - u'%s is a directory, not a %s' % (path, type), reason='bad type') + f'{path} is a directory, not a {type}', reason='bad type') model = self._dir_model(path, content=content) elif type == 'notebook' or (type is None and path.endswith('.ipynb')): model = self._notebook_model(path, content=content) else: if type == 'directory': - raise web.HTTPError(400, - u'%s is not a directory' % path, reason='bad type') + raise web.HTTPError( + 400, + f'{path} is not a directory', reason='bad type') model = self._file_model(path, content=content, format=format) return model def _save_directory(self, os_path, model, path=''): """create a directory""" if is_hidden(os_path, self.root_dir) and not self.allow_hidden: - raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path) + raise web.HTTPError(400, f'Cannot create directory {os_path!r}') if not os.path.exists(os_path): with self.perm_to_403(): os.mkdir(os_path) elif not os.path.isdir(os_path): - raise web.HTTPError(400, u'Not a directory: %s' % (os_path)) + raise web.HTTPError(400, f'Not a directory: {os_path}') else: self.log.debug("Directory %r already exists", os_path) @@ -461,11 +473,15 @@ def save(self, model, path=''): path = path.strip('/') if 'type' not in model: - raise web.HTTPError(400, u'No file type provided') + raise web.HTTPError(400, 'No file type provided') if 'content' not in model and model['type'] != 'directory': - raise web.HTTPError(400, u'No file content provided') + raise web.HTTPError(400, 'No file content provided') os_path = self._get_os_path(path) + + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + raise web.HTTPError(400, f'Cannot create file or directory {os_path!r}') + self.log.debug("Saving %s", os_path) self.run_pre_save_hook(model=model, path=path) @@ -484,13 +500,12 @@ def save(self, model, path=''): elif model['type'] == 'directory': self._save_directory(os_path, model, path) else: - raise web.HTTPError(400, "Unhandled contents type: %s" % model['type']) + raise web.HTTPError(400, f"Unhandled contents type: {model['type']}") except web.HTTPError: raise except Exception as e: - self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True) - raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % - (path, e)) from e + self.log.error('Error while saving file: %s %s', path, e, exc_info=True) + raise web.HTTPError(500, f'Unexpected error while saving file: {path} {e}') from e validation_message = None if model['type'] == 'notebook': @@ -510,8 +525,14 @@ def delete_file(self, path): path = path.strip('/') os_path = self._get_os_path(path) rm = os.unlink - if not os.path.exists(os_path): - raise web.HTTPError(404, u'File or directory does not exist: %s' % os_path) + + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + raise web.HTTPError(400, f'Cannot delete file or directory {os_path!r}') + + four_o_four = "file or directory does not exist: %r" % path + + if not self.exists(path): + raise web.HTTPError(404, four_o_four) def is_non_empty_dir(os_path): if os.path.isdir(os_path): @@ -527,7 +548,7 @@ def is_non_empty_dir(os_path): if sys.platform == 'win32' and is_non_empty_dir(os_path): # send2trash can really delete files on Windows, so disallow # deleting non-empty files. See Github issue 3631. - raise web.HTTPError(400, u'Directory %s not empty' % os_path) + raise web.HTTPError(400, f'Directory {os_path} not empty') try: self.log.debug("Sending %s to trash", os_path) send2trash(os_path) @@ -538,7 +559,7 @@ def is_non_empty_dir(os_path): if os.path.isdir(os_path): # Don't permanently delete non-empty directories. if is_non_empty_dir(os_path): - raise web.HTTPError(400, u'Directory %s not empty' % os_path) + raise web.HTTPError(400, f'Directory {os_path} not empty') self.log.debug("Removing directory %s", os_path) with self.perm_to_403(): shutil.rmtree(os_path) @@ -561,9 +582,12 @@ def rename_file(self, old_path, new_path): new_os_path = self._get_os_path(new_path) old_os_path = self._get_os_path(old_path) + if (is_hidden(old_os_path, self.root_dir) or is_hidden(new_os_path, self.root_dir)) and not self.allow_hidden: + raise web.HTTPError(400, f'Cannot rename file or directory {old_path!r}') + # Should we proceed with the move? if os.path.exists(new_os_path) and not samefile(old_os_path, new_os_path): - raise web.HTTPError(409, u'File already exists: %s' % new_path) + raise web.HTTPError(409, f'File already exists: {new_path}') # Move the file try: @@ -572,8 +596,7 @@ def rename_file(self, old_path, new_path): except web.HTTPError: raise except Exception as e: - raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % - (old_path, e)) from e + raise web.HTTPError(500, f'Unknown error renaming file: {old_path} {e}') from e def info_string(self): return _("Serving notebooks from local directory: %s") % self.root_dir @@ -604,6 +627,6 @@ def _validate_path(path): for char in invalid_chars: if char in path: - raise web.HTTPError(400, "Path '{}' contains characters that are invalid for the filesystem. " - "Path names on this filesystem cannot contain any of the following " - "characters: {}".format(path, invalid_chars)) + raise web.HTTPError(400, f"Path '{path}' contains characters that are invalid for the filesystem. " + f"Path names on this filesystem cannot contain any of the following " + f"characters: {invalid_chars}") diff --git a/notebook/services/contents/handlers.py b/notebook/services/contents/handlers.py index b3216335bb..45932448e4 100644 --- a/notebook/services/contents/handlers.py +++ b/notebook/services/contents/handlers.py @@ -45,7 +45,7 @@ def validate_model(model, expect_content): if missing: raise web.HTTPError( 500, - u"Missing Model Keys: {missing}".format(missing=missing), + f"Missing Model Keys: {missing}", ) maybe_none_keys = ['content', 'format'] @@ -54,7 +54,7 @@ def validate_model(model, expect_content): if errors: raise web.HTTPError( 500, - u"Keys unexpectedly None: {keys}".format(keys=errors), + f"Keys unexpectedly None: {errors}", ) else: errors = { @@ -65,7 +65,7 @@ def validate_model(model, expect_content): if errors: raise web.HTTPError( 500, - u"Keys unexpectedly not None: {keys}".format(keys=errors), + f"Keys unexpectedly not None: {errors}", ) @@ -101,18 +101,20 @@ def get(self, path=''): of the files and directories it contains. """ path = path or '' + cm = self.contents_manager type = self.get_query_argument('type', default=None) if type not in {None, 'directory', 'file', 'notebook'}: - raise web.HTTPError(400, u'Type %r is invalid' % type) + raise web.HTTPError(400, f'Type {type!r} is invalid') format = self.get_query_argument('format', default=None) if format not in {None, 'text', 'base64'}: - raise web.HTTPError(400, u'Format %r is invalid' % format) + raise web.HTTPError(400, f'Format {format!r} is invalid') content = self.get_query_argument('content', default='1') if content not in {'0', '1'}: - raise web.HTTPError(400, u'Content %r is invalid' % content) + raise web.HTTPError(400, f'Content {content!r} is invalid') content = int(content) - + if cm.is_hidden(path) and not cm.allow_hidden: + raise web.HTTPError(404, f'file or directory {path!r} does not exist') model = yield maybe_future(self.contents_manager.get( path=path, type=type, format=format, content=content, )) @@ -125,8 +127,11 @@ def patch(self, path=''): """PATCH renames a file or directory without re-uploading content.""" cm = self.contents_manager model = self.get_json_body() + old_path = model.get('path') + if old_path and (cm.is_hidden(path) or cm.is_hidden(old_path)) and not cm.allow_hidden: + raise web.HTTPError(400, f'Cannot rename file or directory {path!r}') if model is None: - raise web.HTTPError(400, u'JSON body missing') + raise web.HTTPError(400, 'JSON body missing') model = yield maybe_future(cm.update(model, path)) validate_model(model, expect_content=False) self._finish_model(model) @@ -134,10 +139,7 @@ def patch(self, path=''): @gen.coroutine def _copy(self, copy_from, copy_to=None): """Copy a file, optionally specifying a target directory.""" - self.log.info(u"Copying {copy_from} to {copy_to}".format( - copy_from=copy_from, - copy_to=copy_to or '', - )) + self.log.info(f"Copying {copy_from} to {copy_to or ''}") model = yield maybe_future(self.contents_manager.copy(copy_from, copy_to)) self.set_status(201) validate_model(model, expect_content=False) @@ -146,7 +148,7 @@ def _copy(self, copy_from, copy_to=None): @gen.coroutine def _upload(self, model, path): """Handle upload of a new file to path""" - self.log.info(u"Uploading file to %s", path) + self.log.info("Uploading file to %s", path) model = yield maybe_future(self.contents_manager.new(model, path)) self.set_status(201) validate_model(model, expect_content=False) @@ -155,7 +157,7 @@ def _upload(self, model, path): @gen.coroutine def _new_untitled(self, path, type='', ext=''): """Create a new, empty untitled entity""" - self.log.info(u"Creating new %s in %s", type or 'file', path) + self.log.info("Creating new %s in %s", type or 'file', path) model = yield maybe_future(self.contents_manager.new_untitled(path=path, type=type, ext=ext)) self.set_status(201) validate_model(model, expect_content=False) @@ -166,7 +168,7 @@ def _save(self, model, path): """Save an existing file.""" chunk = model.get("chunk", None) if not chunk or chunk == -1: # Avoid tedious log information - self.log.info(u"Saving file at %s", path) + self.log.info("Saving file at %s", path) model = yield maybe_future(self.contents_manager.save(model, path)) validate_model(model, expect_content=False) self._finish_model(model) @@ -193,9 +195,12 @@ def post(self, path=''): dir_exists = yield maybe_future(cm.dir_exists(path)) if not dir_exists: - raise web.HTTPError(404, "No such directory: %s" % path) + raise web.HTTPError(404, f"No such directory: {path}") model = self.get_json_body() + copy_from = model.get('copy_from') + if copy_from and (cm.is_hidden(path) or cm.is_hidden(copy_from)) and not cm.allow_hidden: + raise web.HTTPError(400, f'Cannot copy file or directory {path!r}') if model is not None: copy_from = model.get('copy_from') @@ -222,9 +227,12 @@ def put(self, path=''): create a new empty notebook. """ model = self.get_json_body() + cm = self.contents_manager if model: if model.get('copy_from'): raise web.HTTPError(400, "Cannot copy with PUT, only POST") + if model.get('path') and (cm.is_hidden(path) or cm.is_hidden(model.get('path'))) and not cm.allow_hidden: + raise web.HTTPError(400, f'Cannot create file or directory {path!r}') exists = yield maybe_future(self.contents_manager.file_exists(path)) if exists: yield maybe_future(self._save(model, path)) @@ -238,6 +246,10 @@ def put(self, path=''): def delete(self, path=''): """delete a file in the given path""" cm = self.contents_manager + + if cm.is_hidden(path) and not cm.allow_hidden: + raise web.HTTPError(400, f'Cannot delete file or directory {path!r}') + self.log.warning('delete %s', path) yield maybe_future(cm.delete(path)) self.set_status(204) @@ -323,10 +335,10 @@ def post(self,path=''): _checkpoint_id_regex = r"(?P[\w-]+)" default_handlers = [ - (r"/api/contents%s/checkpoints" % path_regex, CheckpointsHandler), - (r"/api/contents%s/checkpoints/%s" % (path_regex, _checkpoint_id_regex), + (fr"/api/contents{path_regex}/checkpoints", CheckpointsHandler), + (fr"/api/contents{path_regex}/checkpoints/{_checkpoint_id_regex}", ModifyCheckpointsHandler), - (r"/api/contents%s/trust" % path_regex, TrustNotebooksHandler), - (r"/api/contents%s" % path_regex, ContentsHandler), + (fr"/api/contents{path_regex}/trust", TrustNotebooksHandler), + (fr"/api/contents{path_regex}", ContentsHandler), (r"/api/notebooks/?(.*)", NotebooksRedirectHandler), ] diff --git a/notebook/services/contents/largefilemanager.py b/notebook/services/contents/largefilemanager.py index 6779a0b5c2..38aeeef290 100644 --- a/notebook/services/contents/largefilemanager.py +++ b/notebook/services/contents/largefilemanager.py @@ -1,9 +1,7 @@ from notebook.services.contents.filemanager import FileContentsManager -from contextlib import contextmanager from tornado import web -import nbformat import base64 -import os, io +import os class LargeFileManager(FileContentsManager): """Handle large file upload.""" @@ -13,13 +11,13 @@ def save(self, model, path=''): chunk = model.get('chunk', None) if chunk is not None: path = path.strip('/') - + if 'type' not in model: - raise web.HTTPError(400, u'No file type provided') + raise web.HTTPError(400, 'No file type provided') if model['type'] != 'file': - raise web.HTTPError(400, u'File type "{}" is not supported for large file transfer'.format(model['type'])) + raise web.HTTPError(400, f'File type "{model["type"]}" is not supported for large file transfer') if 'content' not in model and model['type'] != 'directory': - raise web.HTTPError(400, u'No file content provided') + raise web.HTTPError(400, 'No file content provided') os_path = self._get_os_path(path) @@ -33,8 +31,8 @@ def save(self, model, path=''): except web.HTTPError: raise except Exception as e: - self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True) - raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e)) from e + self.log.error('Error while saving file: %s %s', path, e, exc_info=True) + raise web.HTTPError(500, f'Unexpected error while saving file: {path} {e}') from e model = self.get(path, content=False) @@ -60,11 +58,11 @@ def _save_large_file(self, os_path, content, format): bcontent = base64.b64decode(b64_bytes) except Exception as e: raise web.HTTPError( - 400, u'Encoding error saving %s: %s' % (os_path, e) + 400, f'Encoding error saving {os_path}: {e}' ) from e with self.perm_to_403(os_path): if os.path.islink(os_path): os_path = os.path.join(os.path.dirname(os_path), os.readlink(os_path)) - with io.open(os_path, 'ab') as f: + with open(os_path, 'ab') as f: f.write(bcontent) diff --git a/notebook/services/contents/manager.py b/notebook/services/contents/manager.py index b556abc1d3..dd8f848c7e 100644 --- a/notebook/services/contents/manager.py +++ b/notebook/services/contents/manager.py @@ -55,7 +55,7 @@ class ContentsManager(LoggingConfigurable): indicating the root path. """ - + root_dir = Unicode('/', config=True) allow_hidden = Bool(False, config=True, help="Allow access to hidden files") @@ -65,7 +65,7 @@ def _notary_default(self): return sign.NotebookNotary(parent=self) hide_globs = List(Unicode(), [ - u'__pycache__', '*.pyc', '*.pyo', + '__pycache__', '*.pyc', '*.pyo', '.DS_Store', '*.so', '*.dylib', '*~', ], config=True, help=""" Glob patterns to hide in file and directory listings. @@ -302,7 +302,7 @@ def info_string(self): def get_kernel_path(self, path, model=None): """Return the API path for the kernel - + KernelManagers can turn this value into a filesystem path, or ignore it altogether. @@ -334,17 +334,16 @@ def increment_filename(self, filename, path='', insert=''): basename, dot, ext = filename.rpartition('.') if ext != 'ipynb': basename, dot, ext = filename.partition('.') - + suffix = dot + ext for i in itertools.count(): if i: - insert_i = '{}{}'.format(insert, i) + insert_i = f'{insert}{i}' else: insert_i = '' - name = u'{basename}{insert}{suffix}'.format(basename=basename, - insert=insert_i, suffix=suffix) - if not self.exists(u'{}/{}'.format(path, name)): + name = f'{basename}{insert_i}{suffix}' + if not self.exists(f'{path}/{name}'): break return name @@ -353,33 +352,32 @@ def validate_notebook_model(self, model): try: validate_nb(model['content']) except ValidationError as e: - model['message'] = u'Notebook validation failed: {}:\n{}'.format( - e.message, json.dumps(e.instance, indent=1, default=lambda obj: ''), - ) + model['message'] = f'Notebook validation failed: {e.message}:\n' \ + f'{json.dumps(e.instance, indent=1, default=lambda obj: "")}' return model - + def new_untitled(self, path='', type='', ext=''): """Create a new untitled file or directory in path - + path must be a directory - + File extension can be specified. - + Use `new` to create files with a fully specified path (including filename). """ path = path.strip('/') if not self.dir_exists(path): - raise HTTPError(404, 'No such directory: %s' % path) - + raise HTTPError(404, f'No such directory: {path}') + model = {} if type: model['type'] = type - + if ext == '.ipynb': model.setdefault('type', 'notebook') else: model.setdefault('type', 'file') - + insert = '' if model['type'] == 'directory': untitled = self.untitled_directory @@ -390,26 +388,26 @@ def new_untitled(self, path='', type='', ext=''): elif model['type'] == 'file': untitled = self.untitled_file else: - raise HTTPError(400, "Unexpected model type: %r" % model['type']) - + raise HTTPError(400, f"Unexpected model type: {model['type']!r}") + name = self.increment_filename(untitled + ext, path, insert=insert) - path = u'{0}/{1}'.format(path, name) + path = f'{path}/{name}' return self.new(model, path) - + def new(self, model=None, path=''): """Create a new file or directory and return its model with no content. - + To create a new untitled entity in a directory, use `new_untitled`. """ path = path.strip('/') if model is None: model = {} - + if path.endswith('.ipynb'): model.setdefault('type', 'notebook') else: model.setdefault('type', 'file') - + # no content, not a directory, so fill out new-file model if 'content' not in model and model['type'] != 'directory': if model['type'] == 'notebook': @@ -419,7 +417,7 @@ def new(self, model=None, path=''): model['content'] = '' model['type'] = 'file' model['format'] = 'text' - + model = self.save(model, path) return model @@ -429,7 +427,7 @@ def copy(self, from_path, to_path=None): If to_path not specified, it will be the parent directory of from_path. If to_path is a directory, filename will increment `from_path-Copy#.ext`. Considering multi-part extensions, the Copy# part will be placed before the first dot for all the extensions except `ipynb`. - For easier manual searching in case of notebooks, the Copy# part will be placed before the last dot. + For easier manual searching in case of notebooks, the Copy# part will be placed before the last dot. from_path must be a full path to a file. """ @@ -442,20 +440,20 @@ def copy(self, from_path, to_path=None): else: from_dir = '' from_name = path - + model = self.get(path) model.pop('path', None) model.pop('name', None) if model['type'] == 'directory': raise HTTPError(400, "Can't copy directories") - + if to_path is None: to_path = from_dir if self.dir_exists(to_path): - name = copy_pat.sub(u'.', from_name) + name = copy_pat.sub('.', from_name) to_name = self.increment_filename(name, to_path, insert='-Copy') - to_path = u'{0}/{1}'.format(to_path, to_name) - + to_path = f'{to_path}/{to_name}' + model = self.save(model, to_path) return model diff --git a/notebook/services/contents/tests/test_contents_api.py b/notebook/services/contents/tests/test_contents_api.py index 6e4ad49dbc..e0a37aae94 100644 --- a/notebook/services/contents/tests/test_contents_api.py +++ b/notebook/services/contents/tests/test_contents_api.py @@ -2,7 +2,6 @@ from contextlib import contextmanager from functools import partial -import io import json import os import shutil @@ -28,10 +27,7 @@ from ipython_genutils import py3compat from ipython_genutils.tempdir import TemporaryDirectory -try: #PY3 - from base64 import encodebytes, decodebytes -except ImportError: #PY2 - from base64 import encodestring as encodebytes, decodestring as decodebytes +from base64 import encodebytes, decodebytes def uniq_stable(elems): @@ -50,7 +46,7 @@ def dirs_only(dir_model): return [x for x in dir_model['content'] if x['type']=='directory'] -class API(object): +class API: """Wrapper for contents API calls.""" def __init__(self, request): self.request = request @@ -128,16 +124,16 @@ class APITest(NotebookTestBase): """Test the kernels web service API""" dirs_nbs = [('', 'inroot'), ('Directory with spaces in', 'inspace'), - (u'unicodé', 'innonascii'), + ('unicodé', 'innonascii'), ('foo', 'a'), ('foo', 'b'), ('foo', 'name with spaces'), - ('foo', u'unicodé'), + ('foo', 'unicodé'), ('foo/bar', 'baz'), ('ordering', 'A'), ('ordering', 'b'), ('ordering', 'C'), - (u'å b', u'ç d'), + ('å b', 'ç d'), ] hidden_dirs = ['.hidden', '__pycache__'] @@ -151,36 +147,36 @@ def _blob_for_name(name): @staticmethod def _txt_for_name(name): - return u'%s text file' % name - + return f'{name} text file' + def to_os_path(self, api_path): return to_os_path(api_path, root=self.notebook_dir) - + def make_dir(self, api_path): """Create a directory at api_path""" os_path = self.to_os_path(api_path) try: os.makedirs(os_path) except OSError: - print("Directory already exists: %r" % os_path) + print(f"Directory already exists: {os_path!r}") def make_txt(self, api_path, txt): """Make a text file at a given api_path""" os_path = self.to_os_path(api_path) - with io.open(os_path, 'w', encoding='utf-8') as f: + with open(os_path, 'w', encoding='utf-8') as f: f.write(txt) - + def make_blob(self, api_path, blob): """Make a binary file at a given api_path""" os_path = self.to_os_path(api_path) - with io.open(os_path, 'wb') as f: + with open(os_path, 'wb') as f: f.write(blob) - + def make_nb(self, api_path, nb): """Make a notebook file at a given api_path""" os_path = self.to_os_path(api_path) - - with io.open(os_path, 'w', encoding='utf-8') as f: + + with open(os_path, 'w', encoding='utf-8') as f: write(nb, f, version=4) def delete_dir(self, api_path): @@ -192,10 +188,10 @@ def delete_file(self, api_path): """Delete a file at the given path if it exists.""" if self.isfile(api_path): os.unlink(self.to_os_path(api_path)) - + def isfile(self, api_path): return os.path.isfile(self.to_os_path(api_path)) - + def isdir(self, api_path): return os.path.isdir(self.to_os_path(api_path)) @@ -215,18 +211,18 @@ def setUp(self): for d, name in self.dirs_nbs: # create a notebook nb = new_notebook() - nbname = u'{}/{}.ipynb'.format(d, name) + nbname = f'{d}/{name}.ipynb' self.make_nb(nbname, nb) self.addCleanup(partial(self.delete_file, nbname)) # create a text file txt = self._txt_for_name(name) - txtname = u'{}/{}.txt'.format(d, name) + txtname = f'{d}/{name}.txt' self.make_txt(txtname, txt) self.addCleanup(partial(self.delete_file, txtname)) blob = self._blob_for_name(name) - blobname = u'{}/{}.blob'.format(d, name) + blobname = f'{d}/{name}.blob' self.make_blob(blobname, blob) self.addCleanup(partial(self.delete_file, blobname)) @@ -241,10 +237,10 @@ def test_list_notebooks(self): self.assertEqual(len(nbs), 1) self.assertEqual(nbs[0]['name'], 'inspace.ipynb') - nbs = notebooks_only(self.api.list(u'/unicodé/').json()) + nbs = notebooks_only(self.api.list('/unicodé/').json()) self.assertEqual(len(nbs), 1) self.assertEqual(nbs[0]['name'], 'innonascii.ipynb') - self.assertEqual(nbs[0]['path'], u'unicodé/innonascii.ipynb') + self.assertEqual(nbs[0]['path'], 'unicodé/innonascii.ipynb') nbs = notebooks_only(self.api.list('/foo/bar/').json()) self.assertEqual(len(nbs), 1) @@ -254,7 +250,7 @@ def test_list_notebooks(self): nbs = notebooks_only(self.api.list('foo').json()) self.assertEqual(len(nbs), 4) nbnames = { normalize('NFC', n['name']) for n in nbs } - expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb'] + expected = [ 'a.ipynb', 'b.ipynb', 'name with spaces.ipynb', 'unicodé.ipynb'] expected = { normalize('NFC', name) for name in expected } self.assertEqual(nbnames, expected) @@ -284,7 +280,7 @@ def test_get_nb_contents(self): for d, name in self.dirs_nbs: path = url_path_join(d, name + '.ipynb') nb = self.api.read(path).json() - self.assertEqual(nb['name'], u'%s.ipynb' % name) + self.assertEqual(nb['name'], f'{name}.ipynb') self.assertEqual(nb['path'], path) self.assertEqual(nb['type'], 'notebook') self.assertIn('content', nb) @@ -296,7 +292,7 @@ def test_get_nb_no_content(self): for d, name in self.dirs_nbs: path = url_path_join(d, name + '.ipynb') nb = self.api.read(path, content=False).json() - self.assertEqual(nb['name'], u'%s.ipynb' % name) + self.assertEqual(nb['name'], f'{name}.ipynb') self.assertEqual(nb['path'], path) self.assertEqual(nb['type'], 'notebook') self.assertIn('content', nb) @@ -311,7 +307,7 @@ def test_get_nb_invalid(self): 'metadata': {}, }], } - path = u'å b/Validate tést.ipynb' + path = 'å b/Validate tést.ipynb' self.make_txt(path, py3compat.cast_unicode(json.dumps(nb))) model = self.api.read(path).json() self.assertEqual(model['path'], path) @@ -325,11 +321,21 @@ def test_get_contents_no_such_file(self): with assert_http_error(404): self.api.read('foo/q.ipynb') + def test_get_404_hidden(self): + if sys.platform == 'win32': + self.skipTest("Disabled copying hidden files on Windows") + self.make_txt('.hidden/visible.txt', 'test string') + self.make_txt('.hidden.txt', 'test string') + with assert_http_error(404): + resp = self.api.read('.hidden/visible.txt') + with assert_http_error(404): + resp = self.api.read('.hidden.txt') + def test_get_text_file_contents(self): for d, name in self.dirs_nbs: path = url_path_join(d, name + '.txt') model = self.api.read(path).json() - self.assertEqual(model['name'], u'%s.txt' % name) + self.assertEqual(model['name'], f'{name}.txt') self.assertEqual(model['path'], path) self.assertIn('content', model) self.assertEqual(model['format'], 'text') @@ -348,7 +354,7 @@ def test_get_binary_file_contents(self): for d, name in self.dirs_nbs: path = url_path_join(d, name + '.blob') model = self.api.read(path).json() - self.assertEqual(model['name'], u'%s.blob' % name) + self.assertEqual(model['name'], f'{name}.blob') self.assertEqual(model['path'], path) self.assertIn('content', model) self.assertEqual(model['format'], 'base64') @@ -364,15 +370,15 @@ def test_get_binary_file_contents(self): def test_get_bad_type(self): with assert_http_error(400): - self.api.read(u'unicodé', type='file') # this is a directory + self.api.read('unicodé', type='file') # this is a directory with assert_http_error(400): - self.api.read(u'unicodé/innonascii.ipynb', type='directory') + self.api.read('unicodé/innonascii.ipynb', type='directory') def _check_created(self, resp, path, type='notebook'): self.assertEqual(resp.status_code, 201) location_header = py3compat.str_to_unicode(resp.headers['Location']) - self.assertEqual(location_header, url_path_join(self.url_prefix, u'api/contents', url_escape(path))) + self.assertEqual(location_header, url_path_join(self.url_prefix, 'api/contents', url_escape(path))) rjson = resp.json() self.assertEqual(rjson['name'], path.rsplit('/', 1)[-1]) self.assertEqual(rjson['path'], path) @@ -381,12 +387,12 @@ def _check_created(self, resp, path, type='notebook'): assert isright(path) def test_create_untitled(self): - resp = self.api.create_untitled(path=u'å b') - self._check_created(resp, u'å b/Untitled.ipynb') + resp = self.api.create_untitled(path='å b') + self._check_created(resp, 'å b/Untitled.ipynb') # Second time - resp = self.api.create_untitled(path=u'å b') - self._check_created(resp, u'å b/Untitled1.ipynb') + resp = self.api.create_untitled(path='å b') + self._check_created(resp, 'å b/Untitled1.ipynb') # And two directories down resp = self.api.create_untitled(path='foo/bar') @@ -405,39 +411,39 @@ def test_create_untitled_txt(self): def test_upload(self): nb = new_notebook() nbmodel = {'content': nb, 'type': 'notebook'} - path = u'å b/Upload tést.ipynb' + path = 'å b/Upload tést.ipynb' resp = self.api.upload(path, body=json.dumps(nbmodel)) self._check_created(resp, path) def test_mkdir_untitled(self): - resp = self.api.mkdir_untitled(path=u'å b') - self._check_created(resp, u'å b/Untitled Folder', type='directory') + resp = self.api.mkdir_untitled(path='å b') + self._check_created(resp, 'å b/Untitled Folder', type='directory') # Second time - resp = self.api.mkdir_untitled(path=u'å b') - self._check_created(resp, u'å b/Untitled Folder 1', type='directory') + resp = self.api.mkdir_untitled(path='å b') + self._check_created(resp, 'å b/Untitled Folder 1', type='directory') # And two directories down resp = self.api.mkdir_untitled(path='foo/bar') self._check_created(resp, 'foo/bar/Untitled Folder', type='directory') def test_mkdir(self): - path = u'å b/New ∂ir' + path = 'å b/New ∂ir' resp = self.api.mkdir(path) self._check_created(resp, path, type='directory') def test_mkdir_hidden_400(self): with assert_http_error(400): - resp = self.api.mkdir(u'å b/.hidden') + resp = self.api.mkdir('å b/.hidden') def test_upload_txt(self): - body = u'ünicode téxt' + body = 'ünicode téxt' model = { 'content' : body, 'format' : 'text', 'type' : 'file', } - path = u'å b/Upload tést.txt' + path = 'å b/Upload tést.txt' resp = self.api.upload(path, body=json.dumps(model)) # check roundtrip @@ -447,6 +453,51 @@ def test_upload_txt(self): self.assertEqual(model['format'], 'text') self.assertEqual(model['content'], body) + def test_upload_txt_hidden(self): + if sys.platform == 'win32': + self.skipTest("Disabled copying hidden files on Windows") + with assert_http_error(400): + body = 'ünicode téxt' + model = { + 'content' : body, + 'format' : 'text', + 'type' : 'file', + } + path = '.hidden/Upload tést.txt' + resp = self.api.upload(path, body=json.dumps(model)) + + with assert_http_error(400): + body = 'ünicode téxt' + model = { + 'content' : body, + 'format' : 'text', + 'type' : 'file', + 'path': '.hidden/test.txt' + } + path = 'Upload tést.txt' + resp = self.api.upload(path, body=json.dumps(model)) + + with assert_http_error(400): + body = 'ünicode téxt' + model = { + 'content' : body, + 'format' : 'text', + 'type' : 'file', + } + path = '.hidden.txt' + resp = self.api.upload(path, body=json.dumps(model)) + + with assert_http_error(400): + body = 'ünicode téxt' + model = { + 'content' : body, + 'format' : 'text', + 'type' : 'file', + 'path': '.hidden.txt' + } + path = 'Upload tést.txt' + resp = self.api.upload(path, body=json.dumps(model)) + def test_upload_b64(self): body = b'\xFFblob' b64body = encodebytes(body).decode('ascii') @@ -455,7 +506,7 @@ def test_upload_b64(self): 'format' : 'base64', 'type' : 'file', } - path = u'å b/Upload tést.blob' + path = 'å b/Upload tést.blob' resp = self.api.upload(path, body=json.dumps(model)) # check roundtrip @@ -473,7 +524,7 @@ def test_upload_v2(self): nb.worksheets.append(ws) ws.cells.append(v2.new_code_cell(input='print("hi")')) nbmodel = {'content': nb, 'type': 'notebook'} - path = u'å b/Upload tést.ipynb' + path = 'å b/Upload tést.ipynb' resp = self.api.upload(path, body=json.dumps(nbmodel)) self._check_created(resp, path) resp = self.api.read(path) @@ -481,38 +532,65 @@ def test_upload_v2(self): self.assertEqual(data['content']['nbformat'], 4) def test_copy(self): - resp = self.api.copy(u'å b/ç d.ipynb', u'å b') - self._check_created(resp, u'å b/ç d-Copy1.ipynb') - - resp = self.api.copy(u'å b/ç d.ipynb', u'å b') - self._check_created(resp, u'å b/ç d-Copy2.ipynb') - + resp = self.api.copy('å b/ç d.ipynb', 'å b') + self._check_created(resp, 'å b/ç d-Copy1.ipynb') + + resp = self.api.copy('å b/ç d.ipynb', 'å b') + self._check_created(resp, 'å b/ç d-Copy2.ipynb') + def test_copy_copy(self): - resp = self.api.copy(u'å b/ç d.ipynb', u'å b') - self._check_created(resp, u'å b/ç d-Copy1.ipynb') - - resp = self.api.copy(u'å b/ç d-Copy1.ipynb', u'å b') - self._check_created(resp, u'å b/ç d-Copy2.ipynb') - + resp = self.api.copy('å b/ç d.ipynb', 'å b') + self._check_created(resp, 'å b/ç d-Copy1.ipynb') + + resp = self.api.copy('å b/ç d-Copy1.ipynb', 'å b') + self._check_created(resp, 'å b/ç d-Copy2.ipynb') + def test_copy_path(self): - resp = self.api.copy(u'foo/a.ipynb', u'å b') - self._check_created(resp, u'å b/a.ipynb') - - resp = self.api.copy(u'foo/a.ipynb', u'å b') - self._check_created(resp, u'å b/a-Copy1.ipynb') + resp = self.api.copy('foo/a.ipynb', 'å b') + self._check_created(resp, 'å b/a.ipynb') + + resp = self.api.copy('foo/a.ipynb', 'å b') + self._check_created(resp, 'å b/a-Copy1.ipynb') + + def test_copy_400_hidden(self): + if sys.platform == 'win32': + self.skipTest("Disabled copying hidden files on Windows") + self.make_txt('new.txt', 'test string') + self.make_txt('.hidden/new.txt', 'test string') + self.make_txt('.hidden.txt', 'test string') + with assert_http_error(400): + resp = self.api.copy('.hidden/old.txt', 'new.txt') + with assert_http_error(400): + resp = self.api.copy('old.txt', '.hidden/new.txt') + with assert_http_error(400): + resp = self.api.copy('.hidden.txt', 'new.txt') + with assert_http_error(400): + resp = self.api.copy('old.txt', '.hidden.txt') def test_copy_put_400(self): with assert_http_error(400): - resp = self.api.copy_put(u'å b/ç d.ipynb', u'å b/cøpy.ipynb') + resp = self.api.copy_put('å b/ç d.ipynb', 'å b/cøpy.ipynb') + + def test_copy_put_400_hidden(self): + if sys.platform == 'win32': + self.skipTest("Disabled copying hidden files on Windows") + with assert_http_error(400): + resp = self.api.copy_put('.hidden/old.txt', 'new.txt') + with assert_http_error(400): + resp = self.api.copy_put('old.txt', '.hidden/new.txt') + with assert_http_error(400): + resp = self.api.copy_put('.hidden.txt', 'new.txt') + with assert_http_error(400): + resp = self.api.copy_put('old.txt', '.hidden.txt') def test_copy_dir_400(self): # can't copy directories with assert_http_error(400): - resp = self.api.copy(u'å b', u'foo') + resp = self.api.copy('å b', 'foo') def test_delete(self): for d, name in self.dirs_nbs: - print('%r, %r' % (d, name)) + print(f'{d!r}, {name!r}') resp = self.api.delete(url_path_join(d, name + '.ipynb')) self.assertEqual(resp.status_code, 204) @@ -537,15 +615,38 @@ def test_delete_non_empty_dir(self): self.skipTest("Disabled deleting non-empty dirs on Windows") # Test that non empty directory can be deleted try: - self.api.delete(u'å b') + self.api.delete('å b') except requests.HTTPError as e: if e.response.status_code == 400: - if not self.can_send2trash(u'å b'): + if not self.can_send2trash('å b'): self.skipTest("Dir can't be sent to trash") raise # Check if directory has actually been deleted with assert_http_error(404): - self.api.list(u'å b') + self.api.list('å b') + + def test_delete_hidden_dir(self): + if sys.platform == 'win32': + self.skipTest("Disabled deleting hidden dirs on Windows") + with assert_http_error(400): + # Test that non empty directory can be deleted + try: + self.api.delete('.hidden') + except requests.HTTPError as e: + assert e.response.status_code == 400 + raise e + + def test_delete_hidden_file(self): + #Test deleting file in a hidden directory + if sys.platform == 'win32': + self.skipTest("Disabled deleting hidden dirs on Windows") + with assert_http_error(400): + # Test that non empty directory can be deleted + self.api.delete('.hidden/test.txt') + + #Test deleting a hidden file + with assert_http_error(400): + self.api.delete('.hidden.txt') def test_rename(self): resp = self.api.rename('foo/a.ipynb', 'foo/z.ipynb') @@ -555,10 +656,25 @@ def test_rename(self): assert self.isfile('foo/z.ipynb') nbs = notebooks_only(self.api.list('foo').json()) - nbnames = set(n['name'] for n in nbs) + nbnames = {n['name'] for n in nbs} self.assertIn('z.ipynb', nbnames) self.assertNotIn('a.ipynb', nbnames) + def test_rename_400_hidden(self): + if sys.platform == 'win32': + self.skipTest("Disabled copying hidden files on Windows") + # self.make_txt('new.txt', 'test string') + # self.make_txt('.hidden/new.txt', 'test string') + # self.make_txt('.hidden.txt', 'test string') + with assert_http_error(400): + resp = self.api.rename('.hidden/old.txt', 'new.txt') + with assert_http_error(400): + resp = self.api.rename('old.txt', '.hidden/new.txt') + with assert_http_error(400): + resp = self.api.rename('.hidden.txt', 'new.txt') + with assert_http_error(400): + resp = self.api.rename('old.txt', '.hidden.txt') + def test_checkpoints_follow_file(self): # Read initial file state @@ -599,7 +715,7 @@ def test_save(self): resp = self.api.read('foo/a.ipynb') nbcontent = json.loads(resp.text)['content'] nb = from_dict(nbcontent) - nb.cells.append(new_markdown_cell(u'Created by test ³')) + nb.cells.append(new_markdown_cell('Created by test ³')) nbmodel = {'content': nb, 'type': 'notebook'} resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel)) @@ -607,7 +723,7 @@ def test_save(self): nbcontent = self.api.read('foo/a.ipynb').json()['content'] newnb = from_dict(nbcontent) self.assertEqual(newnb.cells[0].source, - u'Created by test ³') + 'Created by test ³') def test_checkpoints(self): resp = self.api.read('foo/a.ipynb') diff --git a/notebook/services/contents/tests/test_fileio.py b/notebook/services/contents/tests/test_fileio.py index adc06d97f1..281be6a3d6 100644 --- a/notebook/services/contents/tests/test_fileio.py +++ b/notebook/services/contents/tests/test_fileio.py @@ -3,7 +3,6 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -import io as stdlib_io import os.path import unittest import pytest @@ -17,13 +16,14 @@ umask = 0 def test_atomic_writing(): - class CustomExc(Exception): pass + class CustomExc(Exception): + pass with TemporaryDirectory() as td: f1 = os.path.join(td, 'penguin') - with stdlib_io.open(f1, 'w') as f: - f.write(u'Before') - + with open(f1, 'w') as f: + f.write('Before') + if os.name != 'nt': os.chmod(f1, 0o701) orig_mode = stat.S_IMODE(os.stat(f1).st_mode) @@ -40,18 +40,18 @@ class CustomExc(Exception): pass with pytest.raises(CustomExc): with atomic_writing(f1) as f: - f.write(u'Failing write') + f.write('Failing write') raise CustomExc # Because of the exception, the file should not have been modified - with stdlib_io.open(f1, 'r') as f: - assert f.read() == u'Before' + with open(f1) as f: + assert f.read() == 'Before' with atomic_writing(f1) as f: - f.write(u'Overwritten') + f.write('Overwritten') - with stdlib_io.open(f1, 'r') as f: - assert f.read() == u'Overwritten' + with open(f1) as f: + assert f.read() == 'Overwritten' if os.name != 'nt': mode = stat.S_IMODE(os.stat(f1).st_mode) @@ -60,10 +60,10 @@ class CustomExc(Exception): pass if have_symlink: # Check that writing over a file preserves a symlink with atomic_writing(f2) as f: - f.write(u'written from symlink') - - with stdlib_io.open(f1, 'r') as f: - assert f.read() == u'written from symlink' + f.write('written from symlink') + + with open(f1) as f: + assert f.read() == 'written from symlink' class TestWithSetUmask(unittest.TestCase): def setUp(self): @@ -71,7 +71,7 @@ def setUp(self): global umask umask = os.umask(0) os.umask(umask) - + def tearDown(self): # restore umask os.umask(umask) @@ -82,14 +82,14 @@ def test_atomic_writing_umask(self): os.umask(0o022) f1 = os.path.join(td, '1') with atomic_writing(f1) as f: - f.write(u'1') + f.write('1') mode = stat.S_IMODE(os.stat(f1).st_mode) assert mode == 0o644 - + os.umask(0o057) f2 = os.path.join(td, '2') with atomic_writing(f2) as f: - f.write(u'2') + f.write('2') mode = stat.S_IMODE(os.stat(f2).st_mode) assert mode == 0o620 @@ -97,36 +97,36 @@ def test_atomic_writing_umask(self): def test_atomic_writing_newlines(): with TemporaryDirectory() as td: path = os.path.join(td, 'testfile') - - lf = u'a\nb\nc\n' - plat = lf.replace(u'\n', os.linesep) - crlf = lf.replace(u'\n', u'\r\n') - + + lf = 'a\nb\nc\n' + plat = lf.replace('\n', os.linesep) + crlf = lf.replace('\n', '\r\n') + # test default - with stdlib_io.open(path, 'w') as f: + with open(path, 'w') as f: f.write(lf) - with stdlib_io.open(path, 'r', newline='') as f: + with open(path, newline='') as f: read = f.read() assert read == plat - + # test newline=LF - with stdlib_io.open(path, 'w', newline='\n') as f: + with open(path, 'w', newline='\n') as f: f.write(lf) - with stdlib_io.open(path, 'r', newline='') as f: + with open(path, newline='') as f: read = f.read() assert read == lf - + # test newline=CRLF with atomic_writing(path, newline='\r\n') as f: f.write(lf) - with stdlib_io.open(path, 'r', newline='') as f: + with open(path, newline='') as f: read = f.read() assert read == crlf - + # test newline=no convert - text = u'crlf\r\ncr\rlf\n' + text = 'crlf\r\ncr\rlf\n' with atomic_writing(path, newline='') as f: f.write(text) - with stdlib_io.open(path, 'r', newline='') as f: + with open(path, newline='') as f: read = f.read() assert read == text diff --git a/notebook/services/contents/tests/test_largefilemanager.py b/notebook/services/contents/tests/test_largefilemanager.py index 13d294b9b0..d52c697acb 100644 --- a/notebook/services/contents/tests/test_largefilemanager.py +++ b/notebook/services/contents/tests/test_largefilemanager.py @@ -13,7 +13,7 @@ def _make_dir(contents_manager, api_path): try: os.makedirs(os_path) except OSError: - print("Directory already exists: %r" % os_path) + print(f"Directory already exists: {os_path!r}") class TestLargeFileManager(TestCase): @@ -68,7 +68,7 @@ def test_save(self): try: model = {'name': 'test', 'path': 'test', 'chunk': 2, 'type': 'file', - 'content': u'test', 'format': 'json'} + 'content': 'test', 'format': 'json'} cm.save(model, model['path']) except web.HTTPError as e: self.assertEqual("HTTP 400: Bad Request (Must specify format of file contents as 'text' or 'base64')", @@ -76,7 +76,7 @@ def test_save(self): # Save model for different chunks model = {'name': 'test', 'path': 'test', 'type': 'file', - 'content': u'test==', 'format': 'text'} + 'content': 'test==', 'format': 'text'} name = model['name'] path = model['path'] cm.save(model, path) diff --git a/notebook/services/contents/tests/test_manager.py b/notebook/services/contents/tests/test_manager.py index dfe5d272f5..efb3d1ae95 100644 --- a/notebook/services/contents/tests/test_manager.py +++ b/notebook/services/contents/tests/test_manager.py @@ -26,14 +26,14 @@ def _make_dir(contents_manager, api_path): try: os.makedirs(os_path) except OSError: - print("Directory already exists: %r" % os_path) + print(f"Directory already exists: {os_path!r}") class TestFileContentsManager(TestCase): @contextmanager def assertRaisesHTTPError(self, status, msg=None): - msg = msg or "Should have raised HTTPError(%i)" % status + msg = msg or f"Should have raised HTTPError({status})" try: yield except HTTPError as e: @@ -87,7 +87,7 @@ def test_get_os_path(self): self.assertEqual(path, fs_path) def test_checkpoint_subdir(self): - subd = u'sub ∂ir' + subd = 'sub ∂ir' cp_name = 'test-cp.ipynb' with TemporaryDirectory() as td: root = td @@ -98,7 +98,7 @@ def test_checkpoint_subdir(self): 'cp', 'test.ipynb' ) cp_subdir = cpm.checkpoint_path( - 'cp', '/%s/test.ipynb' % subd + 'cp', f'/{subd}/test.ipynb' ) self.assertNotEqual(cp_dir, cp_subdir) self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name)) @@ -113,7 +113,7 @@ def test_bad_symlink(self): file_model = cm.new_untitled(path=path, ext='.txt') # create a broken symlink - self.symlink(cm, "target", '%s/%s' % (path, 'bad symlink')) + self.symlink(cm, "target", f'{path}/{"bad symlink"}') model = cm.get(path) contents = { @@ -149,7 +149,7 @@ def test_good_symlink(self): cm = FileContentsManager(root_dir=td) parent = 'test good symlink' name = 'good symlink' - path = '{0}/{1}'.format(parent, name) + path = f'{parent}/{name}' _make_dir(cm, parent) file_model = cm.new(path=parent + '/zfoo.txt') @@ -175,12 +175,159 @@ def test_403(self): os.chmod(os_path, 0o400) try: with cm.open(os_path, 'w') as f: - f.write(u"don't care") + f.write("don't care") except HTTPError as e: self.assertEqual(e.status_code, 403) else: self.fail("Should have raised HTTPError(403)") + @skipIf(sys.platform.startswith('win'), "Can't test hidden files on Windows") + def test_400(self): + #Test Delete behavior + #Test delete of file in hidden directory + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + + with self.assertRaises(HTTPError) as excinfo: + cm.delete_file(file_in_hidden_path) + self.assertEqual(excinfo.exception.status_code, 400) + + #Test delete hidden file in visible directory + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + + with self.assertRaises(HTTPError) as excinfo: + cm.delete_file(file_in_hidden_path) + self.assertEqual(excinfo.exception.status_code, 400) + + #Test Save behavior + #Test save of file in hidden directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.save(model,path=os_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + #Test save hidden file in visible directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.save(model,path=os_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + + #Test rename behavior + #Test rename with source file in hidden directory + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + old_path = file_in_hidden_path + new_path = "new.txt" + + with self.assertRaises(HTTPError) as excinfo: + cm.rename_file(old_path, new_path) + self.assertEqual(excinfo.exception.status_code, 400) + + #Test rename of dest file in hidden directory + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + new_path = file_in_hidden_path + old_path = "old.txt" + + with self.assertRaises(HTTPError) as excinfo: + cm.rename_file(old_path, new_path) + self.assertEqual(excinfo.exception.status_code, 400) + + #Test rename with hidden source file in visible directory + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + old_path = file_in_hidden_path + new_path = "new.txt" + + with self.assertRaises(HTTPError) as excinfo: + cm.rename_file(old_path, new_path) + self.assertEqual(excinfo.exception.status_code, 400) + + #Test rename with hidden dest file in visible directory + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + new_path = file_in_hidden_path + old_path = "old.txt" + + with self.assertRaises(HTTPError) as excinfo: + cm.rename_file(old_path, new_path) + self.assertEqual(excinfo.exception.status_code, 400) + + @skipIf(sys.platform.startswith('win'), "Can't test hidden files on Windows") + def test_404(self): + #Test visible file in hidden folder + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.get(os_path, 'w') + except HTTPError as e: + self.assertEqual(e.status_code, 404) + else: + self.fail("Should have raised HTTPError(404)") + #Test hidden file in visible folder + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.get(os_path, 'w') + except HTTPError as e: + self.assertEqual(e.status_code, 404) + else: + self.fail("Should have raised HTTPError(404)") + def test_escape_root(self): with TemporaryDirectory() as td: cm = FileContentsManager(root_dir=td) @@ -201,7 +348,7 @@ def test_escape_root(self): with self.assertRaisesHTTPError(404): cm.save(model={ 'type': 'file', - 'content': u'', + 'content': '', 'format': 'text', }, path='../foo') @@ -209,7 +356,7 @@ def test_escape_root(self): class TestContentsManager(TestCase): @contextmanager def assertRaisesHTTPError(self, status, msg=None): - msg = msg or "Should have raised HTTPError(%i)" % status + msg = msg or f"Should have raised HTTPError({status})" try: yield except HTTPError as e: @@ -309,7 +456,7 @@ def test_new_untitled(self): self.assertIn('type', model) self.assertEqual(model['type'], 'file') self.assertEqual(model['name'], 'untitled') - self.assertEqual(model['path'], '%s/untitled' % sub_dir) + self.assertEqual(model['path'], f'{sub_dir}/untitled') # Test with a compound extension model = cm.new_untitled(path=sub_dir, ext='.foo.bar') @@ -378,19 +525,19 @@ def test_get(self): self.assertIn('path', model2) self.assertIn('content', model2) self.assertEqual(model2['name'], 'Untitled.ipynb') - self.assertEqual(model2['path'], '{0}/{1}'.format(sub_dir.strip('/'), name)) + self.assertEqual(model2['path'], f'{sub_dir.strip("/")}/{name}') # Test with a regular file. file_model_path = cm.new_untitled(path=sub_dir, ext='.txt')['path'] file_model = cm.get(file_model_path) self.assertDictContainsSubset( { - 'content': u'', - 'format': u'text', - 'mimetype': u'text/plain', - 'name': u'untitled.txt', - 'path': u'foo/untitled.txt', - 'type': u'file', + 'content': '', + 'format': 'text', + 'mimetype': 'text/plain', + 'name': 'untitled.txt', + 'path': 'foo/untitled.txt', + 'type': 'file', 'writable': True, }, file_model, @@ -413,7 +560,7 @@ def test_get(self): # Directory contents should match the contents of each individual entry # when requested with content=False. model2_no_content = cm.get(sub_dir + name, content=False) - file_model_no_content = cm.get(u'foo/untitled.txt', content=False) + file_model_no_content = cm.get('foo/untitled.txt', content=False) sub_sub_dir_no_content = cm.get('foo/bar', content=False) self.assertEqual(sub_sub_dir_no_content['path'], 'foo/bar') self.assertEqual(sub_sub_dir_no_content['name'], 'bar') @@ -428,7 +575,7 @@ def test_get(self): elif entry['path'] == file_model_no_content['path']: self.assertEqual(entry, file_model_no_content) else: - self.fail("Unexpected directory entry: %s" % entry()) + self.fail(f"Unexpected directory entry: {entry()}") with self.assertRaises(HTTPError): cm.get('foo', type='file') @@ -582,9 +729,9 @@ def test_delete_root(self): def test_copy(self): cm = self.contents_manager - parent = u'å b' - name = u'nb √.ipynb' - path = u'{0}/{1}'.format(parent, name) + parent = 'å b' + name = 'nb √.ipynb' + path = f'{parent}/{name}' self.make_dir(parent) orig = cm.new(path=path) @@ -593,11 +740,11 @@ def test_copy(self): self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy1.ipynb')) # copy with specified name - copy2 = cm.copy(path, u'å b/copy 2.ipynb') - self.assertEqual(copy2['name'], u'copy 2.ipynb') - self.assertEqual(copy2['path'], u'å b/copy 2.ipynb') + copy2 = cm.copy(path, 'å b/copy 2.ipynb') + self.assertEqual(copy2['name'], 'copy 2.ipynb') + self.assertEqual(copy2['path'], 'å b/copy 2.ipynb') # copy with specified path - copy2 = cm.copy(path, u'/') + copy2 = cm.copy(path, '/') self.assertEqual(copy2['name'], name) self.assertEqual(copy2['path'], name) diff --git a/notebook/services/kernels/handlers.py b/notebook/services/kernels/handlers.py index 2b7f89b6f8..d2c5f8ef72 100644 --- a/notebook/services/kernels/handlers.py +++ b/notebook/services/kernels/handlers.py @@ -124,7 +124,7 @@ def rate_limit_window(self): return self.settings.get('rate_limit_window', 1.0) def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized')) + return f"{self.__class__.__name__}({getattr(self, 'kernel_id', 'uninitialized')})" def create_stream(self): km = self.kernel_manager @@ -138,7 +138,7 @@ def nudge(self): """Nudge the zmq connections with kernel_info_requests Returns a Future that will resolve when we have received - a shell reply and at least one iopub message, + a shell or control reply and at least one iopub message, ensuring that zmq subscriptions are established, sockets are fully connected, and kernel is responsive. @@ -157,10 +157,12 @@ def nudge(self): f = Future() f.set_result(None) return f - # Use a transient shell channel to prevent leaking # shell responses to the front-end. shell_channel = kernel.connect_shell() + # Use a transient control channel to prevent leaking + # control responses to the front-end. + control_channel = kernel.connect_control() # The IOPub used by the client, whose subscriptions we are verifying. iopub_channel = self.channels["iopub"] @@ -183,6 +185,8 @@ def cleanup(f=None): iopub_channel.stop_on_recv() if not shell_channel.closed(): shell_channel.close() + if not control_channel.closed(): + control_channel.close() # trigger cleanup when both message futures are resolved both_done.add_done_callback(cleanup) @@ -193,6 +197,12 @@ def on_shell_reply(msg): self.log.debug("Nudge: resolving shell future: %s", self.kernel_id) info_future.set_result(None) + def on_control_reply(msg): + self.log.debug("Nudge: control info reply received: %s", self.kernel_id) + if not info_future.done(): + self.log.debug("Nudge: resolving control future: %s", self.kernel_id) + info_future.set_result(None) + def on_iopub(msg): self.log.debug("Nudge: IOPub received: %s", self.kernel_id) if not iopub_future.done(): @@ -202,6 +212,7 @@ def on_iopub(msg): iopub_channel.on_recv(on_iopub) shell_channel.on_recv(on_shell_reply) + control_channel.on_recv(on_control_reply) loop = IOLoop.current() # Nudge the kernel with kernel info requests until we get an IOPub message @@ -227,6 +238,12 @@ def nudge(count): # check for closed zmq socket if shell_channel.closed(): + self.log.debug("Nudge: cancelling on closed zmq socket: %s", self.kernel_id) + finish() + return + + # check for closed zmq socket + if control_channel.closed(): self.log.debug( "Nudge: cancelling on closed zmq socket: %s", self.kernel_id ) @@ -235,8 +252,9 @@ def nudge(count): if not both_done.done(): log = self.log.warning if count % 10 == 0 else self.log.debug - log("Nudge: attempt %s on kernel %s" % (count, self.kernel_id)) + log(f"Nudge: attempt {count} on kernel {self.kernel_id}") self.session.send(shell_channel, "kernel_info_request") + self.session.send(control_channel, "kernel_info_request") nonlocal nudge_handle nudge_handle = loop.call_later(0.5, nudge, count) @@ -305,7 +323,7 @@ def _finish_kernel_info(self, info): protocol_version = info.get('protocol_version', client_protocol_version) if protocol_version != client_protocol_version: self.session.adapt_version = int(protocol_version.split('.')[0]) - self.log.info("Adapting from protocol version {protocol_version} (kernel {kernel_id}) to {client_protocol_version} (client).".format(protocol_version=protocol_version, kernel_id=self.kernel_id, client_protocol_version=client_protocol_version)) + self.log.info(f"Adapting from protocol version {protocol_version} (kernel {self.kernel_id}) to {client_protocol_version} (client).") if not self._kernel_info_future.done(): self._kernel_info_future.set_result(info) @@ -366,12 +384,13 @@ def _register_session(self): This is likely due to a client reconnecting from a lost network connection, where the socket on our side has not been cleaned up yet. """ - self.session_key = '%s:%s' % (self.kernel_id, self.session.session) + self.session_key = f'{self.kernel_id}:{self.session.session}' stale_handler = self._open_sessions.get(self.session_key) if stale_handler: self.log.warning("Replacing stale connection: %s", self.session_key) yield stale_handler.close() - self._open_sessions[self.session_key] = self + if self.kernel_id in self.kernel_manager: # only update open sessions if kernel is actively managed + self._open_sessions[self.session_key] = self def open(self, kernel_id): super().open() @@ -438,7 +457,7 @@ def on_message(self, msg): am = self.kernel_manager.allowed_message_types mt = msg['header']['msg_type'] if am and mt not in am: - self.log.warning('Received message of type "%s", which is not allowed. Ignoring.' % mt) + self.log.warning(f'Received message of type "{mt}", which is not allowed. Ignoring.') else: stream = self.channels[channel] self.session.send(stream, msg) @@ -485,7 +504,7 @@ def write_stderr(error_message): # Increment the bytes and message count self._iopub_window_msg_count += 1 if msg_type == 'stream': - byte_count = sum([len(x) for x in msg_list]) + byte_count = sum(len(x) for x in msg_list) else: byte_count = 0 self._iopub_window_byte_count += byte_count @@ -503,7 +522,7 @@ def write_stderr(error_message): if self.iopub_msg_rate_limit > 0 and msg_rate > self.iopub_msg_rate_limit: if not self._iopub_msgs_exceeded: self._iopub_msgs_exceeded = True - write_stderr(dedent("""\ + write_stderr(dedent(f"""\ IOPub message rate exceeded. The notebook server will temporarily stop sending output to the client in order to avoid crashing it. @@ -511,9 +530,9 @@ def write_stderr(error_message): `--NotebookApp.iopub_msg_rate_limit`. Current values: - NotebookApp.iopub_msg_rate_limit={} (msgs/sec) - NotebookApp.rate_limit_window={} (secs) - """.format(self.iopub_msg_rate_limit, self.rate_limit_window))) + NotebookApp.iopub_msg_rate_limit={self.iopub_msg_rate_limit} (msgs/sec) + NotebookApp.rate_limit_window={self.rate_limit_window} (secs) + """)) else: # resume once we've got some headroom below the limit if self._iopub_msgs_exceeded and msg_rate < (0.8 * self.iopub_msg_rate_limit): @@ -525,7 +544,7 @@ def write_stderr(error_message): if self.iopub_data_rate_limit > 0 and data_rate > self.iopub_data_rate_limit: if not self._iopub_data_exceeded: self._iopub_data_exceeded = True - write_stderr(dedent("""\ + write_stderr(dedent(f"""\ IOPub data rate exceeded. The notebook server will temporarily stop sending output to the client in order to avoid crashing it. @@ -533,9 +552,9 @@ def write_stderr(error_message): `--NotebookApp.iopub_data_rate_limit`. Current values: - NotebookApp.iopub_data_rate_limit={} (bytes/sec) - NotebookApp.rate_limit_window={} (secs) - """.format(self.iopub_data_rate_limit, self.rate_limit_window))) + NotebookApp.iopub_data_rate_limit={self.iopub_data_rate_limit} (bytes/sec) + NotebookApp.rate_limit_window={self.rate_limit_window} (secs) + """)) else: # resume once we've got some headroom below the limit if self._iopub_data_exceeded and data_rate < (0.8 * self.iopub_data_rate_limit): @@ -621,7 +640,7 @@ def on_restart_failed(self): default_handlers = [ (r"/api/kernels", MainKernelHandler), - (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler), - (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler), - (r"/api/kernels/%s/channels" % _kernel_id_regex, ZMQChannelsHandler), + (fr"/api/kernels/{_kernel_id_regex}", KernelHandler), + (fr"/api/kernels/{_kernel_id_regex}/{_kernel_action_regex}", KernelActionHandler), + (fr"/api/kernels/{_kernel_id_regex}/channels", ZMQChannelsHandler), ] diff --git a/notebook/services/kernels/kernelmanager.py b/notebook/services/kernels/kernelmanager.py index 7ed182dfc0..037c42be0e 100644 --- a/notebook/services/kernels/kernelmanager.py +++ b/notebook/services/kernels/kernelmanager.py @@ -33,7 +33,7 @@ try: from jupyter_client.multikernelmanager import AsyncMultiKernelManager except ImportError: - class AsyncMultiKernelManager(object): + class AsyncMultiKernelManager: """Empty class to satisfy unused reference by AsyncMappingKernelManager.""" def __init__(self, **kwargs): pass @@ -71,7 +71,7 @@ def _update_root_dir(self, proposal): # If we receive a non-absolute path, make it absolute. value = os.path.abspath(value) if not exists(value) or not os.path.isdir(value): - raise TraitError("kernel root dir %r is not a directory" % value) + raise TraitError(f"kernel root dir {value!r} is not a directory") return value cull_idle_timeout = Integer(0, config=True, @@ -176,8 +176,8 @@ async def start_kernel(self, kernel_id=None, path=None, **kwargs): kernel_id = await maybe_future(self.pinned_superclass.start_kernel(self, **kwargs)) self._kernel_connections[kernel_id] = 0 self.start_watching_activity(kernel_id) - self.log.info("Kernel started: %s, name: %s" % (kernel_id, self._kernels[kernel_id].kernel_name)) - self.log.debug("Kernel args: %r" % kwargs) + self.log.info(f"Kernel started: {kernel_id}, name: {self._kernels[kernel_id].kernel_name}") + self.log.debug(f"Kernel args: {kwargs!r}") # register callback for failed auto-restart self.add_restart_callback(kernel_id, lambda : self._handle_kernel_died(kernel_id), @@ -192,7 +192,7 @@ async def start_kernel(self, kernel_id=None, path=None, **kwargs): else: self._check_kernel_id(kernel_id) - self.log.info("Using existing kernel: %s" % kernel_id) + self.log.info(f"Using existing kernel: {kernel_id}") # Initialize culling if not already if not self._initialized_culler: @@ -391,7 +391,7 @@ def list_kernels(self): def _check_kernel_id(self, kernel_id): """Check a that a kernel_id exists and raise 404 if not.""" if kernel_id not in self: - raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id) + raise web.HTTPError(404, f'Kernel does not exist: {kernel_id}') # monitoring activity: @@ -457,8 +457,10 @@ async def cull_kernels(self): try: await self.cull_kernel_if_idle(kernel_id) except Exception as e: - self.log.exception("The following exception was encountered while checking the " - "idle duration of kernel {}: {}".format(kernel_id, e)) + self.log.exception( + f"The following exception was encountered while checking the idle duration of kernel " + f"{kernel_id}: {e}" + ) async def cull_kernel_if_idle(self, kernel_id): try: diff --git a/notebook/services/kernels/tests/test_kernels_api.py b/notebook/services/kernels/tests/test_kernels_api.py index 7895f36a81..38a9dfb00c 100644 --- a/notebook/services/kernels/tests/test_kernels_api.py +++ b/notebook/services/kernels/tests/test_kernels_api.py @@ -1,7 +1,6 @@ """Test the kernels service API.""" import json -import sys import time from requests import HTTPError @@ -24,7 +23,7 @@ async_testing_enabled = False -class KernelAPI(object): +class KernelAPI: """Wrapper for kernel REST API requests""" def __init__(self, request, base_url, headers): self.request = request @@ -204,11 +203,11 @@ class AsyncKernelAPITest(KernelAPITest): def setup_class(cls): if not async_testing_enabled: # Can be removed once jupyter_client >= 6.1 is required. raise SkipTest("AsyncKernelAPITest tests skipped due to down-level jupyter_client!") - super(AsyncKernelAPITest, cls).setup_class() + super().setup_class() @classmethod def get_argv(cls): - argv = super(AsyncKernelAPITest, cls).get_argv() + argv = super().get_argv() # before we extend the argv with the class, ensure that appropriate jupyter_client is available. # if not available, don't set kernel_manager_class, resulting in the repeat of sync-based tests. @@ -244,11 +243,11 @@ class KernelCullingTest(NotebookTestBase): @classmethod def get_argv(cls): - argv = super(KernelCullingTest, cls).get_argv() + argv = super().get_argv() # Enable culling with 5s timeout and 1s intervals - argv.extend(['--MappingKernelManager.cull_idle_timeout={}'.format(CULL_TIMEOUT), - '--MappingKernelManager.cull_interval={}'.format(CULL_INTERVAL), + argv.extend([f'--MappingKernelManager.cull_idle_timeout={CULL_TIMEOUT}', + f'--MappingKernelManager.cull_interval={CULL_INTERVAL}', '--MappingKernelManager.cull_connected=False']) return argv diff --git a/notebook/services/kernelspecs/handlers.py b/notebook/services/kernelspecs/handlers.py index a01d307fb2..bc68f367c8 100644 --- a/notebook/services/kernelspecs/handlers.py +++ b/notebook/services/kernelspecs/handlers.py @@ -87,7 +87,7 @@ def get(self, kernel_name): try: spec = yield maybe_future(ksm.get_kernel_spec(kernel_name)) except KeyError as e: - raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name) from e + raise web.HTTPError(404, f'Kernel spec {kernel_name} not found') from e if is_kernelspec_model(spec): model = spec else: @@ -102,5 +102,5 @@ def get(self, kernel_name): default_handlers = [ (r"/api/kernelspecs", MainKernelSpecHandler), - (r"/api/kernelspecs/%s" % kernel_name_regex, KernelSpecHandler), + (fr"/api/kernelspecs/{kernel_name_regex}", KernelSpecHandler), ] diff --git a/notebook/services/kernelspecs/tests/test_kernelspecs_api.py b/notebook/services/kernelspecs/tests/test_kernelspecs_api.py index 215bfc861b..846743933d 100644 --- a/notebook/services/kernelspecs/tests/test_kernelspecs_api.py +++ b/notebook/services/kernelspecs/tests/test_kernelspecs_api.py @@ -1,17 +1,14 @@ """Test the kernel specs webservice API.""" import errno -import io import json import os import shutil pjoin = os.path.join -import requests - from jupyter_client.kernelspec import NATIVE_KERNEL_NAME -from notebook.utils import url_path_join, url_escape +from notebook.utils import url_path_join from notebook.tests.launchnotebook import NotebookTestBase, assert_http_error # Copied from jupyter_client.tests.test_kernelspec so updating that doesn't @@ -20,10 +17,10 @@ 'display_name':'Test kernel', } -some_resource = u"The very model of a modern major general" +some_resource = "The very model of a modern major general" -class KernelSpecAPI(object): +class KernelSpecAPI: """Wrapper for notebook API calls.""" def __init__(self, request): self.request = request @@ -41,7 +38,7 @@ def list(self): def kernel_spec_info(self, name): return self._req('GET', url_path_join('api/kernelspecs', name)) - + def kernel_resource(self, name, path): return self._req('GET', url_path_join('kernelspecs', name, path)) @@ -60,11 +57,11 @@ def create_spec(self, name): except OSError as e: if e.errno != errno.EEXIST: raise - + with open(pjoin(sample_kernel_dir, 'kernel.json'), 'w') as f: json.dump(sample_kernel_json, f) - - with io.open(pjoin(sample_kernel_dir, 'resource.txt'), 'w', + + with open(pjoin(sample_kernel_dir, 'resource.txt'), 'w', encoding='utf-8') as f: f.write(some_resource) @@ -76,10 +73,10 @@ def test_list_kernelspecs_bad(self): except OSError as e: if e.errno != errno.EEXIST: raise - + with open(pjoin(bad_kernel_dir, 'kernel.json'), 'w') as f: f.write("garbage") - + model = self.ks_api.list().json() assert isinstance(model, dict) self.assertEqual(model['default'], NATIVE_KERNEL_NAME) @@ -87,9 +84,9 @@ def test_list_kernelspecs_bad(self): assert isinstance(specs, dict) # 2: the sample kernelspec created in setUp, and the native Python kernel self.assertGreaterEqual(len(specs), 2) - + shutil.rmtree(bad_kernel_dir) - + def test_list_kernelspecs(self): model = self.ks_api.list().json() assert isinstance(model, dict) @@ -123,14 +120,14 @@ def test_get_kernelspec_spaces(self): def test_get_nonexistant_kernelspec(self): with assert_http_error(404): self.ks_api.kernel_spec_info('nonexistant') - + def test_get_kernel_resource_file(self): res = self.ks_api.kernel_resource('sAmple', 'resource.txt') self.assertEqual(res.text, some_resource) - + def test_get_nonexistant_resource(self): with assert_http_error(404): self.ks_api.kernel_resource('nonexistant', 'resource.txt') - + with assert_http_error(404): self.ks_api.kernel_resource('sample', 'nonexistant.txt') diff --git a/notebook/services/nbconvert/handlers.py b/notebook/services/nbconvert/handlers.py index 7c65ee0fd9..563381c91b 100644 --- a/notebook/services/nbconvert/handlers.py +++ b/notebook/services/nbconvert/handlers.py @@ -13,7 +13,7 @@ def get(self): try: from nbconvert.exporters import base except ImportError as e: - raise web.HTTPError(500, "Could not import nbconvert: %s" % e) from e + raise web.HTTPError(500, f"Could not import nbconvert: {e}") from e res = {} exporters = base.get_export_names() for exporter_name in exporters: diff --git a/notebook/services/nbconvert/tests/test_nbconvert_api.py b/notebook/services/nbconvert/tests/test_nbconvert_api.py index d6ef9d2ca5..b7e9059dc0 100644 --- a/notebook/services/nbconvert/tests/test_nbconvert_api.py +++ b/notebook/services/nbconvert/tests/test_nbconvert_api.py @@ -1,9 +1,7 @@ -import requests - from notebook.utils import url_path_join from notebook.tests.launchnotebook import NotebookTestBase -class NbconvertAPI(object): +class NbconvertAPI: """Wrapper for nbconvert API calls.""" def __init__(self, request): self.request = request diff --git a/notebook/services/sessions/handlers.py b/notebook/services/sessions/handlers.py index fc4b3d1d38..7c0540fd6b 100644 --- a/notebook/services/sessions/handlers.py +++ b/notebook/services/sessions/handlers.py @@ -76,10 +76,12 @@ def post(self): kernel_id=kernel_id, name=name, type=mtype)) except NoSuchKernel: - msg = ("The '%s' kernel is not available. Please pick another " - "suitable kernel instead, or install that kernel." % kernel_name) - status_msg = '%s not found' % kernel_name - self.log.warning('Kernel not found: %s' % kernel_name) + msg = ( + f"The '{kernel_name}' kernel is not available. " + f"Please pick another suitable kernel instead, or install that kernel." + ) + status_msg = f'{kernel_name} not found' + self.log.warning(f'Kernel not found: {kernel_name}') self.set_status(501) self.finish(json.dumps(dict(message=msg, short_message=status_msg))) return @@ -133,7 +135,7 @@ def patch(self, session_id): if model['kernel'].get('id') is not None: kernel_id = model['kernel']['id'] if kernel_id not in km: - raise web.HTTPError(400, "No such kernel: %s" % kernel_id) + raise web.HTTPError(400, f"No such kernel: {kernel_id}") changes['kernel_id'] = kernel_id elif model['kernel'].get('name') is not None: kernel_name = model['kernel']['name'] @@ -174,7 +176,7 @@ def delete(self, session_id): _session_id_regex = r"(?P\w+-\w+-\w+-\w+-\w+)" default_handlers = [ - (r"/api/sessions/%s" % _session_id_regex, SessionHandler), + (fr"/api/sessions/{_session_id_regex}", SessionHandler), (r"/api/sessions", SessionRootHandler) ] diff --git a/notebook/services/sessions/sessionmanager.py b/notebook/services/sessions/sessionmanager.py index 5686332868..206090839a 100644 --- a/notebook/services/sessions/sessionmanager.py +++ b/notebook/services/sessions/sessionmanager.py @@ -171,9 +171,9 @@ def get_session(self, **kwargs): for column in kwargs.keys(): if column not in self._columns: raise TypeError("No such column: %r", column) - conditions.append("%s=?" % column) + conditions.append(f"{column}=?") - query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions)) + query = f"SELECT * FROM session WHERE {' AND '.join(conditions)}" self.cursor.execute(query, list(kwargs.values())) try: @@ -185,14 +185,14 @@ def get_session(self, **kwargs): if row is None: q = [] for key, value in kwargs.items(): - q.append("%s=%r" % (key, value)) + q.append(f"{key}={value!r}") - raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q))) + raise web.HTTPError(404, f'Session not found: {", ".join(q)}') try: model = yield maybe_future(self.row_to_model(row)) except KeyError as e: - raise web.HTTPError(404, u'Session not found: %s' % str(e)) + raise web.HTTPError(404, f'Session not found: {e}') raise gen.Return(model) @gen.coroutine @@ -220,9 +220,9 @@ def update_session(self, session_id, **kwargs): sets = [] for column in kwargs.keys(): if column not in self._columns: - raise TypeError("No such column: %r" % column) - sets.append("%s=?" % column) - query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) + raise TypeError(f"No such column: {column!r}") + sets.append(f"{column}=?") + query = f"UPDATE session SET {', '.join(sets)} WHERE session_id=?" self.cursor.execute(query, list(kwargs.values()) + [session_id]) def kernel_culled(self, kernel_id): diff --git a/notebook/services/sessions/tests/test_sessionmanager.py b/notebook/services/sessions/tests/test_sessionmanager.py index 9af38033c8..b58f8f51d5 100644 --- a/notebook/services/sessions/tests/test_sessionmanager.py +++ b/notebook/services/sessions/tests/test_sessionmanager.py @@ -11,7 +11,7 @@ from notebook.services.contents.manager import ContentsManager from notebook._tz import utcnow, isoformat -class DummyKernel(object): +class DummyKernel: def __init__(self, kernel_name='python'): self.kernel_name = kernel_name @@ -22,11 +22,11 @@ class DummyMKM(MappingKernelManager): """MappingKernelManager interface that doesn't start kernels, for testing""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.id_letters = iter(u'ABCDEFGHIJK') + self.id_letters = iter('ABCDEFGHIJK') def _new_id(self): return next(self.id_letters) - + def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): kernel_id = kernel_id or self._new_id() k = self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name) @@ -40,7 +40,7 @@ def shutdown_kernel(self, kernel_id, now=False): class TestSessionManager(TestCase): - + def setUp(self): self.sm = SessionManager( kernel_manager=DummyMKM(), @@ -59,7 +59,7 @@ def co_add(): sessions.append(session) raise gen.Return(sessions) return self.loop.run_sync(co_add) - + def create_session(self, **kwargs): return self.create_sessions(kwargs)[0] @@ -68,8 +68,8 @@ def test_get_session(self): session_id = self.create_session(path='/path/to/test.ipynb', kernel_name='bar')['id'] model = self.loop.run_sync(lambda: sm.get_session(session_id=session_id)) expected = {'id':session_id, - 'path': u'/path/to/test.ipynb', - 'notebook': {'path': u'/path/to/test.ipynb', 'name': None}, + 'path': '/path/to/test.ipynb', + 'notebook': {'path': '/path/to/test.ipynb', 'name': None}, 'type': 'notebook', 'name': None, 'kernel': { @@ -103,18 +103,18 @@ def test_get_session_dead_kernel(self): def test_list_sessions(self): sm = self.sm sessions = self.create_sessions( - dict(path='/path/to/1/test1.ipynb', kernel_name='python'), - dict(path='/path/to/2/test2.py', type='file', kernel_name='python'), - dict(path='/path/to/3', name='foo', type='console', kernel_name='python'), + {'path': '/path/to/1/test1.ipynb', 'kernel_name': 'python'}, + {'path': '/path/to/2/test2.py', 'type': 'file', 'kernel_name': 'python'}, + {'path': '/path/to/3', 'name': 'foo', 'type': 'console', 'kernel_name': 'python'}, ) - + sessions = self.loop.run_sync(lambda: sm.list_sessions()) expected = [ { 'id':sessions[0]['id'], - 'path': u'/path/to/1/test1.ipynb', + 'path': '/path/to/1/test1.ipynb', 'type': 'notebook', - 'notebook': {'path': u'/path/to/1/test1.ipynb', 'name': None}, + 'notebook': {'path': '/path/to/1/test1.ipynb', 'name': None}, 'name': None, 'kernel': { 'id': 'A', @@ -125,7 +125,7 @@ def test_list_sessions(self): } }, { 'id':sessions[1]['id'], - 'path': u'/path/to/2/test2.py', + 'path': '/path/to/2/test2.py', 'type': 'file', 'name': None, 'kernel': { @@ -137,7 +137,7 @@ def test_list_sessions(self): } }, { 'id':sessions[2]['id'], - 'path': u'/path/to/3', + 'path': '/path/to/3', 'type': 'console', 'name': 'foo', 'kernel': { @@ -163,10 +163,10 @@ def test_list_sessions_dead_kernel(self): expected = [ { 'id': sessions[1]['id'], - 'path': u'/path/to/2/test2.ipynb', + 'path': '/path/to/2/test2.ipynb', 'type': 'notebook', 'name': None, - 'notebook': {'path': u'/path/to/2/test2.ipynb', 'name': None}, + 'notebook': {'path': '/path/to/2/test2.ipynb', 'name': None}, 'kernel': { 'id': 'B', 'name':'python', @@ -185,10 +185,10 @@ def test_update_session(self): self.loop.run_sync(lambda: sm.update_session(session_id, path='/path/to/new_name.ipynb')) model = self.loop.run_sync(lambda: sm.get_session(session_id=session_id)) expected = {'id':session_id, - 'path': u'/path/to/new_name.ipynb', + 'path': '/path/to/new_name.ipynb', 'type': 'notebook', 'name': None, - 'notebook': {'path': u'/path/to/new_name.ipynb', 'name': None}, + 'notebook': {'path': '/path/to/new_name.ipynb', 'name': None}, 'kernel': { 'id': 'A', 'name':'julia', @@ -218,10 +218,10 @@ def test_delete_session(self): new_sessions = self.loop.run_sync(lambda: sm.list_sessions()) expected = [{ 'id': sessions[0]['id'], - 'path': u'/path/to/1/test1.ipynb', + 'path': '/path/to/1/test1.ipynb', 'type': 'notebook', 'name': None, - 'notebook': {'path': u'/path/to/1/test1.ipynb', 'name': None}, + 'notebook': {'path': '/path/to/1/test1.ipynb', 'name': None}, 'kernel': { 'id': 'A', 'name':'python', @@ -232,7 +232,7 @@ def test_delete_session(self): }, { 'id': sessions[2]['id'], 'type': 'console', - 'path': u'/path/to/3', + 'path': '/path/to/3', 'name': 'foo', 'kernel': { 'id': 'C', diff --git a/notebook/services/sessions/tests/test_sessions_api.py b/notebook/services/sessions/tests/test_sessions_api.py index cb4bc0bdea..67321e5e34 100644 --- a/notebook/services/sessions/tests/test_sessions_api.py +++ b/notebook/services/sessions/tests/test_sessions_api.py @@ -2,11 +2,9 @@ import errno from functools import partial -import io import os import json import shutil -import sys import time from unittest import SkipTest @@ -25,7 +23,7 @@ pjoin = os.path.join -class SessionAPI(object): +class SessionAPI: """Wrapper for notebook API calls.""" def __init__(self, request): self.request = request @@ -101,7 +99,7 @@ def setUp(self): raise self.addCleanup(partial(shutil.rmtree, subdir, ignore_errors=True)) - with io.open(pjoin(subdir, 'nb1.ipynb'), 'w', encoding='utf-8') as f: + with open(pjoin(subdir, 'nb1.ipynb'), 'w', encoding='utf-8') as f: nb = new_notebook() write(nb, f, version=4) @@ -130,7 +128,7 @@ def test_create(self): self.assertIn('id', newsession) self.assertEqual(newsession['path'], 'foo/nb1.ipynb') self.assertEqual(newsession['type'], 'notebook') - self.assertEqual(resp.headers['Location'], self.url_prefix + 'api/sessions/{0}'.format(newsession['id'])) + self.assertEqual(resp.headers['Location'], f'{self.url_prefix}api/sessions/{newsession["id"]}') sessions = self.sess_api.list().json() self.assertEqual(sessions, [newsession]) @@ -174,7 +172,7 @@ def test_create_with_kernel_id(self): self.assertIn('id', newsession) self.assertEqual(newsession['path'], 'foo/nb1.ipynb') self.assertEqual(newsession['kernel']['id'], kernel['id']) - self.assertEqual(resp.headers['Location'], self.url_prefix + 'api/sessions/{0}'.format(newsession['id'])) + self.assertEqual(resp.headers['Location'], f'{self.url_prefix}api/sessions/{newsession["id"]}') sessions = self.sess_api.list().json() self.assertEqual(sessions, [newsession]) @@ -273,11 +271,11 @@ class AsyncSessionAPITest(SessionAPITest): def setup_class(cls): if not async_testing_enabled: # Can be removed once jupyter_client >= 6.1 is required. raise SkipTest("AsyncSessionAPITest tests skipped due to down-level jupyter_client!") - super(AsyncSessionAPITest, cls).setup_class() + super().setup_class() @classmethod def get_argv(cls): - argv = super(AsyncSessionAPITest, cls).get_argv() + argv = super().get_argv() # Before we extend the argv with the class, ensure that appropriate jupyter_client is available. # if not available, don't set kernel_manager_class, resulting in the repeat of sync-based tests. diff --git a/notebook/static/acc_overwrite.less b/notebook/static/acc_overwrite.less deleted file mode 100644 index 4209ea633b..0000000000 --- a/notebook/static/acc_overwrite.less +++ /dev/null @@ -1,61 +0,0 @@ -//This is file created for overwriting some of bootstrap element color in order to satisfiy the color contrast greater than 4.5:1. - -.btn-danger{ - color: #ffffff; - background-color: #df0404; - border-color: #df0404; -} - -.btn-warning{ - color: #ffffff; - background-color:#b46102; - border-color: #b46102; -} - -@link-color: #296eaa; - -.close { - float: right; - font-size: (@font-size-base * 1.5); - font-weight: @close-font-weight; - line-height: 1; - color: @close-color; - text-shadow: @close-text-shadow; - .opacity(.6); - - &:hover, - &:focus { - color: @close-color; - text-decoration: none; - cursor: pointer; - .opacity(1.0); -} button& { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; - appearance: none; - } -} - -.navbar-nav { - > li > a { - color: @navbar-default-link-color; - // To make keyboard focus clearly visible on the Controls(File,Edit,View,Insert,Cell...etc)present on the menubar. - //&:hover, //To use browser's hover default value - &:focus { - /* -webkit-focus-ring-color = '#5B9DD9' */ - outline: -webkit-focus-ring-color auto 5px; - // color: @navbar-default-link-hover-color; - // background-color: @navbar-default-link-hover-bg; - } - } -} - -.menu_focus_highlight{ - a:focus { - outline: -webkit-focus-ring-color auto 5px; -} -} - diff --git a/notebook/static/auth/css/override.css b/notebook/static/auth/css/override.css deleted file mode 100644 index 0cb116ec2c..0000000000 --- a/notebook/static/auth/css/override.css +++ /dev/null @@ -1,8 +0,0 @@ -/*This file contains any manual css for this page that needs to override the global styles. -This is only required when different pages style the same element differently. This is just -a hack to deal with our current css styles and no new styling should be added in this file.*/ - -#ipython-main-app { - padding-top: 50px; - text-align: center; -} diff --git a/notebook/static/auth/js/loginmain.js b/notebook/static/auth/js/loginmain.js deleted file mode 100644 index 0c6b661f78..0000000000 --- a/notebook/static/auth/js/loginmain.js +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -define(['jquery', 'base/js/namespace', 'base/js/page'], function($, IPython, page) { - function login_main() { - var page_instance = new page.Page('div#header', 'div#site'); - page_instance.show(); - $('input#password_input').focus(); - IPython.page = page_instance; - } - return login_main; -}); diff --git a/notebook/static/auth/js/loginwidget.js b/notebook/static/auth/js/loginwidget.js deleted file mode 100644 index 8b68d3e84b..0000000000 --- a/notebook/static/auth/js/loginwidget.js +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -define([ - 'jquery', - 'base/js/utils', -], function($, utils){ - "use strict"; - - var LoginWidget = function (selector, options) { - options = options || {}; - this.base_url = options.base_url || utils.get_body_data("baseUrl"); - this.selector = selector; - if (this.selector !== undefined) { - this.element = $(selector); - this.bind_events(); - } - }; - - - LoginWidget.prototype.bind_events = function () { - var that = this; - this.element.find("button#logout").click(function () { - window.location = utils.url_path_join( - that.base_url, - "logout" - ); - }); - this.element.find("button#login").click(function () { - window.location = utils.url_path_join( - that.base_url, - "login" - ); - }); - }; - - return {'LoginWidget': LoginWidget}; -}); diff --git a/notebook/static/auth/js/logoutmain.js b/notebook/static/auth/js/logoutmain.js deleted file mode 100644 index 41e775053b..0000000000 --- a/notebook/static/auth/js/logoutmain.js +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -define(['base/js/namespace', 'base/js/page'], function(IPython, page) { - function logout_main() { - var page_instance = new page.Page('div#header', 'div#site'); - page_instance.show(); - - IPython.page = page_instance; - } - return logout_main; -}); diff --git a/notebook/static/auth/js/main.js b/notebook/static/auth/js/main.js deleted file mode 100644 index 68d7871c58..0000000000 --- a/notebook/static/auth/js/main.js +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -define(['./loginmain', './logoutmain', 'bidi/bidi'], function (login_main, logout_main, bidi) { - if(bidi.isMirroringEnabled()){ - $("body").attr("dir","rtl"); - } - return { - login_main: login_main, - logout_main: logout_main - }; -}); diff --git a/notebook/static/auth/less/login.less b/notebook/static/auth/less/login.less deleted file mode 100644 index 8a4ce3ffcb..0000000000 --- a/notebook/static/auth/less/login.less +++ /dev/null @@ -1,23 +0,0 @@ -// Custom styles for login.html -.center-nav { - display: inline-block; - // pull the lower margin back - margin-bottom: -4px; -} - -[dir="rtl"] .center-nav { - form.pull-left { - .pull-right(); - } - .navbar-text { - float: right; - } -} - -[dir="rtl"] .navbar-inner { - text-align: right; -} - -[dir="rtl"] div.text-left { - .text-right(); -} diff --git a/notebook/static/auth/less/logout.less b/notebook/static/auth/less/logout.less deleted file mode 100644 index 63cd701c9a..0000000000 --- a/notebook/static/auth/less/logout.less +++ /dev/null @@ -1,2 +0,0 @@ -// Custom styles for logout.html - diff --git a/notebook/static/auth/less/style.less b/notebook/static/auth/less/style.less deleted file mode 100644 index 4d1919cc03..0000000000 --- a/notebook/static/auth/less/style.less +++ /dev/null @@ -1,7 +0,0 @@ -/*! -* -* IPython auth -* -*/ -@import "login.less"; -@import "logout.less"; \ No newline at end of file diff --git a/notebook/static/base/images/favicon-busy-1.ico b/notebook/static/base/images/favicon-busy-1.ico deleted file mode 100644 index 5b46a82261..0000000000 Binary files a/notebook/static/base/images/favicon-busy-1.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon-busy-2.ico b/notebook/static/base/images/favicon-busy-2.ico deleted file mode 100644 index 4a8b841c2b..0000000000 Binary files a/notebook/static/base/images/favicon-busy-2.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon-busy-3.ico b/notebook/static/base/images/favicon-busy-3.ico deleted file mode 100644 index b5edce5733..0000000000 Binary files a/notebook/static/base/images/favicon-busy-3.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon-file.ico b/notebook/static/base/images/favicon-file.ico deleted file mode 100644 index 8167018cd0..0000000000 Binary files a/notebook/static/base/images/favicon-file.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon-notebook.ico b/notebook/static/base/images/favicon-notebook.ico deleted file mode 100644 index 4537e2d989..0000000000 Binary files a/notebook/static/base/images/favicon-notebook.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon-terminal.ico b/notebook/static/base/images/favicon-terminal.ico deleted file mode 100644 index ace499a338..0000000000 Binary files a/notebook/static/base/images/favicon-terminal.ico and /dev/null differ diff --git a/notebook/static/base/images/favicon.ico b/notebook/static/base/images/favicon.ico deleted file mode 100644 index 2d1bcff7ca..0000000000 Binary files a/notebook/static/base/images/favicon.ico and /dev/null differ diff --git a/notebook/static/base/images/logo.png b/notebook/static/base/images/logo.png deleted file mode 100644 index 3f3f57ff39..0000000000 Binary files a/notebook/static/base/images/logo.png and /dev/null differ diff --git a/notebook/static/base/js/dialog.js b/notebook/static/base/js/dialog.js deleted file mode 100644 index 77d81185f4..0000000000 --- a/notebook/static/base/js/dialog.js +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -define(['jquery', - 'codemirror/lib/codemirror', - 'bootstrap', - 'base/js/i18n'], - function($, CodeMirror, bs, i18n) { - "use strict"; - - /** - * A wrapper around bootstrap modal for easier use - * Pass it an option dictionary with the following properties: - * - * - body : or , main content of the dialog - * if pass a it will be wrapped in a p tag and - * html element escaped, unless you specify sanitize=false - * option. - * - title : Dialog title, default to empty string. - * - buttons : dict of btn_options who keys are button label. - * see btn_options below for description - * - open : callback to trigger on dialog open. - * - destroy: - * - notebook : notebook instance - * - keyboard_manager: keyboard manager instance. - * - * Unlike bootstrap modals, the backdrop options is set by default - * to 'static'. - * - * The rest of the options are passed as is to bootstrap modals. - * - * btn_options: dict with the following property: - * - * - click : callback to trigger on click - * - class : css classes to add to button. - * - * - * - **/ - var modal = function (options) { - - var modal = $("
") - .addClass("modal") - .addClass("fade") - .attr("role", "dialog"); - var dialog = $("
") - .addClass("modal-dialog") - .appendTo(modal); - var dialog_content = $("
") - .addClass("modal-content") - .appendTo(dialog); - if(typeof(options.body) === 'string' && options.sanitize !== false){ - options.body = $("

").text(options.body); - } - dialog_content.append( - $("

") - .addClass("modal-header") - .mousedown(function() { - $(".modal").draggable({handle: '.modal-header'}); - }) - .append($("