diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index c06f367e6c..be28522243 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -12,6 +12,9 @@ permissions: jobs: check_release: runs-on: ubuntu-latest + strategy: + matrix: + group: [check_release, link_check] steps: - name: Checkout uses: actions/checkout@v2 @@ -37,10 +40,11 @@ jobs: ${{ runner.os }}-pip- ${{ runner.os }}-pip- - name: Cache checked links + if: ${{ matrix.group == 'link_check' }} uses: actions/cache@v2 with: path: ~/.cache/pytest-link-check - key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/.md') }}-md-links + key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/*.md', '**/*.rst') }}-md-links restore-keys: | ${{ runner.os }}-linkcheck- - name: Upgrade packaging dependencies @@ -50,8 +54,12 @@ jobs: run: | pip install -e . - name: Check Release + if: ${{ matrix.group == 'check_release' }} env: TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }} uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 with: token: ${{ secrets.GITHUB_TOKEN }} + - 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 4903ee9485..0f0faba1e4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: [ubuntu] - python-version: [ '3.6' ] + python-version: [ '3.7' ] steps: - name: Checkout uses: actions/checkout@v1 diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml new file mode 100644 index 0000000000..354a0468d7 --- /dev/null +++ b/.github/workflows/enforce-label.yml @@ -0,0 +1,11 @@ +name: Enforce PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, edited, synchronize] +jobs: + enforce-label: + runs-on: ubuntu-latest + steps: + - name: enforce-triage-label + uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 diff --git a/.github/workflows/python-nbconvert.yml b/.github/workflows/python-nbconvert.yml index 152bd9d4f4..d7dfd07c1d 100644 --- a/.github/workflows/python-nbconvert.yml +++ b/.github/workflows/python-nbconvert.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.6' , '3.7', '3.8', '3.9' ] + python-version: [ '3.7', '3.8', '3.9', '3.10' ] steps: - name: Checkout uses: actions/checkout@v1 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 92ee5d5214..282ea1fd85 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: [ '3.6' , '3.7', '3.8', '3.9' ] # Windows 3.9 fails due to the pywinpty dependency not working (Issue #5967) + python-version: [ '3.7', '3.8', '3.9', '3.10' ] steps: - name: Checkout uses: actions/checkout@v1 diff --git a/.github/workflows/selenium.yml b/.github/workflows/selenium.yml index b3f1cd2e32..b0c470eb87 100644 --- a/.github/workflows/selenium.yml +++ b/.github/workflows/selenium.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos] - python-version: [ '3.6', '3.7', '3.8', '3.9' ] + python-version: [ '3.7', '3.8', '3.9', '3.10' ] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1a045d97..e0fef4f52f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,156 @@ Use `pip install pip --upgrade` to upgrade pip. Check pip version with +## 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)) + +### Bugs fixed + +- Fix Chinese punctuation [#6268](https://github.com/jupyter/notebook/pull/6268) ([@LiHua-Official](https://github.com/LiHua-Official)) +- Add date field to kernel message header [#6265](https://github.com/jupyter/notebook/pull/6265) ([@kevin-bates](https://github.com/kevin-bates)) +- Fix deprecation warning [#6253](https://github.com/jupyter/notebook/pull/6253) ([@tornaria](https://github.com/tornaria)) + +### Maintenance and upkeep improvements + +- Enforce labels on PRs [#6235](https://github.com/jupyter/notebook/pull/6235) ([@blink1073](https://github.com/blink1073)) +- Fix: CI error for python 3.6 & macOS [#6215](https://github.com/jupyter/notebook/pull/6215) ([@penguinolog](https://github.com/penguinolog)) + +### Other merged PRs + +- handle KeyError when get session [#6245](https://github.com/jupyter/notebook/pull/6245) ([@ccw630](https://github.com/ccw630)) +- Updated doc for passwd [#6209](https://github.com/jupyter/notebook/pull/6209) ([@antoinecarme](https://github.com/antoinecarme)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2021-11-16&to=2022-01-12&type=c)) + +[@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)) + +### Bugs fixed + +- Fix `asyncio` error when opening notebooks [#6221](https://github.com/jupyter/notebook/pull/6221) ([@dleen](https://github.com/dleen)) +- Change to use a universal Chinese translation on certain words [#6218](https://github.com/jupyter/notebook/pull/6218) ([@jackexu](https://github.com/jackexu)) +- Fix Chinese translation typo [#6211](https://github.com/jupyter/notebook/pull/6211) ([@maliubiao](https://github.com/maliubiao) +- Fix `send2trash` tests failing on Windows [#6127](https://github.com/jupyter/notebook/pull/6127) ([@dolfinus](https://github.com/dolfinus)) + +### Maintenance and upkeep improvements + +- TST: don't look in user site for serverextensions [#6233](https://github.com/jupyter/notebook/pull/6233) ([@bnavigator](https://github.com/bnavigator)) +- Enable terminal tests as `pywinpty` is ported for python 3.9 [#6228](https://github.com/jupyter/notebook/pull/6228) ([@nsait-linaro](https://github.com/nsait-linaro)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2021-10-19&to=2021-11-16&type=c)) + +[@bnavigator](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Abnavigator+updated%3A2021-10-19..2021-11-16&type=Issues) | [@dleen](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Adleen+updated%3A2021-10-19..2021-11-16&type=Issues) | [@dolfinus](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Adolfinus+updated%3A2021-10-19..2021-11-16&type=Issues) | [@jackexu](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajackexu+updated%3A2021-10-19..2021-11-16&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2021-10-19..2021-11-16&type=Issues) | [@maliubiao](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Amaliubiao+updated%3A2021-10-19..2021-11-16&type=Issues) | [@nsait-linaro](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ansait-linaro+updated%3A2021-10-19..2021-11-16&type=Issues) | [@takluyver](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Atakluyver+updated%3A2021-10-19..2021-11-16&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AZsailer+updated%3A2021-10-19..2021-11-16&type=Issues) + +## 6.4.5 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.4...ccd9665571107e02a325a738b8baebd6532b2d3d)) + +### Bug fixes + +- Recover from failure to render mimetype [#6181](https://github.com/jupyter/notebook/pull/6181) ([@martinRenou](https://github.com/martinRenou)) + +### Maintenance and upkeep improvements + +- Fix crypto handling [#6197](https://github.com/jupyter/notebook/pull/6197) ([@blink1073](https://github.com/blink1073)) +- Fix `jupyter_client` warning [#6178](https://github.com/jupyter/notebook/pull/6178) ([@martinRenou](https://github.com/martinRenou)) + +### Documentation improvements + +- Fix nbsphinx settings [#6200](https://github.com/jupyter/notebook/pull/6200) ([@mgeier](https://github.com/mgeier)) +- Fully revert the pinning of `nbsphinx` to 0.8.6 [#6201](https://github.com/jupyter/notebook/pull/6201) ([@kevin-bates](https://github.com/kevin-bates)) +- Pin `nbsphinx` to 0.8.6, clean up orphaned resources [#6194](https://github.com/jupyter/notebook/pull/6194) ([@kevin-bates](https://github.com/kevin-bates)) +- Fix typo in docstring [#6188](https://github.com/jupyter/notebook/pull/6188) ([@jgarte](https://github.com/jgarte)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2021-09-03&to=2021-10-19&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2021-09-03..2021-10-19&type=Issues) | [@jgarte](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajgarte+updated%3A2021-09-03..2021-10-19&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2021-09-03..2021-10-19&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AmartinRenou+updated%3A2021-09-03..2021-10-19&type=Issues) | [@mgeier](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Amgeier+updated%3A2021-09-03..2021-10-19&type=Issues) + +## 6.4.4 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.3...c06c340574e1d2207940c5bd1190eb73d82ab945)) + +### Documentation improvements + +- Update Manual Release Instructions [#6152](https://github.com/jupyter/notebook/pull/6152) ([@blink1073](https://github.com/blink1073)) + +### Other merged PRs + +- Use default JupyterLab CSS sanitizer options for Markdown [#6160](https://github.com/jupyter/notebook/pull/6160) ([@krassowski](https://github.com/krassowski)) +- Fix syntax highlight [#6128](https://github.com/jupyter/notebook/pull/6128) ([@massongit](https://github.com/massongit)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2021-08-11&to=2021-09-03&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2021-08-11..2021-09-03&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2021-08-11..2021-09-03&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akrassowski+updated%3A2021-08-11..2021-09-03&type=Issues) | [@massongit](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Amassongit+updated%3A2021-08-11..2021-09-03&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aminrk+updated%3A2021-08-11..2021-09-03&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AZsailer+updated%3A2021-08-11..2021-09-03&type=Issues) + +## 6.4.3 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.2...c373bd89adaaddffbb71747ebbcfe8a749cae0a8)) + +### Bugs fixed + +- Add @babel/core dependency [#6133](https://github.com/jupyter/notebook/pull/6133) ([@afshin](https://github.com/afshin)) +- Switch webpack to production mode [#6131](https://github.com/jupyter/notebook/pull/6131) ([@afshin](https://github.com/afshin)) + +### Maintenance and upkeep improvements + +- Clean up link checking [#6130](https://github.com/jupyter/notebook/pull/6130) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2021-08-06&to=2021-08-10&type=c)) + +[@afshin](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aafshin+updated%3A2021-08-06..2021-08-10&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2021-08-06..2021-08-10&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AZsailer+updated%3A2021-08-06..2021-08-10&type=Issues) + +## 6.4.2 + +([Full Changelog](https://github.com/jupyter/notebook/compare/v6.4.0...999e8322bcd24e0ed62b180c19ec13db3f48165b)) + +### Bugs fixed + +- Add missing file to manifest [#6122](https://github.com/jupyter/notebook/pull/6122) ([@afshin](https://github.com/afshin)) +- Fix issue #3218 [#6108](https://github.com/jupyter/notebook/pull/6108) ([@Nazeeh21](https://github.com/Nazeeh21)) +- Fix version of jupyter-packaging in pyproject.toml [#6101](https://github.com/jupyter/notebook/pull/6101) ([@frenzymadness](https://github.com/frenzymadness)) +- "#element".tooltip is not a function on home page fixed. [#6070](https://github.com/jupyter/notebook/pull/6070) ([@ilayh123](https://github.com/ilayh123)) + +### Maintenance and upkeep improvements + +- Enhancements to the desktop entry [#6099](https://github.com/jupyter/notebook/pull/6099) ([@Amr-Ibra](https://github.com/Amr-Ibra)) +- Add missing spaces to help messages in config file [#6085](https://github.com/jupyter/notebook/pull/6085) ([@saiwing-yeung](https://github.com/saiwing-yeung)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/notebook/graphs/contributors?from=2021-05-17&to=2021-08-06&type=c)) + +[@afshin](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aafshin+updated%3A2021-05-17..2021-08-06&type=Issues) | [@Amr-Ibra](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AAmr-Ibra+updated%3A2021-05-17..2021-08-06&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afrenzymadness+updated%3A2021-05-17..2021-08-06&type=Issues) | [@ilayh123](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ailayh123+updated%3A2021-05-17..2021-08-06&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2021-05-17..2021-08-06&type=Issues) | [@Nazeeh21](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ANazeeh21+updated%3A2021-05-17..2021-08-06&type=Issues) | [@saiwing-yeung](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Asaiwing-yeung+updated%3A2021-05-17..2021-08-06&type=Issues) + ## 6.4.0 ([Full Changelog](https://github.com/jupyter/notebook/compare/6.3.0...80eb286f316838afc76a9a84b06f54e7dccb6c86)) @@ -49,8 +199,6 @@ Use `pip install pip --upgrade` to upgrade pip. Check pip version with [@afshin](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aafshin+updated%3A2021-03-22..2021-05-12&type=Issues) | [@befeleme](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Abefeleme+updated%3A2021-03-22..2021-05-12&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ablink1073+updated%3A2021-03-22..2021-05-12&type=Issues) | [@faucct](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afaucct+updated%3A2021-03-22..2021-05-12&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Afrenzymadness+updated%3A2021-03-22..2021-05-12&type=Issues) | [@gamestrRUS](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3AgamestrRUS+updated%3A2021-03-22..2021-05-12&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2021-03-22..2021-05-12&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Akevin-bates+updated%3A2021-03-22..2021-05-12&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Aminrk+updated%3A2021-03-22..2021-05-12&type=Issues) | [@misterhay](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Amisterhay+updated%3A2021-03-22..2021-05-12&type=Issues) | [@stef4k](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Astef4k+updated%3A2021-03-22..2021-05-12&type=Issues) | [@wggillen](https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Awggillen+updated%3A2021-03-22..2021-05-12&type=Issues) - - ## 6.3.0 ### Merged PRs diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 94c35c300b..6f947cb58c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -66,12 +66,7 @@ Verification While running the notebook, select one of your notebook files (the file will have the extension ``.ipynb``). In the top tab you will click on "Help" and then click on "About". In the pop window you will see information about the version of Jupyter that you are running. You will see "The version of the notebook server is:". -If you are working in development mode, you will see that your version of Jupyter notebook will include the word "dev". - -.. image:: ./docs/source/_static/images/jupyter-verification.png - :width: 40pt - -If it does not include the word "dev", you are currently not working in development mode and should follow the steps below to uninstall and reinstall Jupyter. +If you are working in development mode, you will see that your version of Jupyter notebook will include the word "dev". If it does not include the word "dev", you are currently not working in development mode and should follow the steps below to uninstall and reinstall Jupyter. Troubleshooting the Installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/MANIFEST.in b/MANIFEST.in index 48bf1e8af9..c28b820105 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,6 +9,7 @@ include pyproject.toml include setup.py include setupbase.py include Dockerfile +include *.js graft tools graft notebook/tests diff --git a/RELEASE.md b/RELEASE.md index 5395694ee2..81fdd0fdee 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -38,14 +38,15 @@ pip install -ve . ### Install release dependencies ```bash -conda install -c conda-forge nodejs babel twine build +conda install -c conda-forge nodejs babel npm install -g po2json +pip install jupyter_releaser # used for build dependencies (build, twine, tbump) ``` ### Update the version ```bash -vim notebook/_version.py +tbump --only-patch # set the new version python setup.py jsversion git commit -am "Release $(python setup.py --version)" git tag $(python setup.py --version) @@ -67,7 +68,7 @@ twine check dist/* && twine upload dist/* ### Change back to dev version ```bash -vim notebook/_version.py # Add the .dev suffix +tbump --only-patch # Add the .dev suffix python setup.py jsversion git commit -am "Back to dev version" ``` diff --git a/bower.json b/bower.json index 5f72f6ff04..393db02ad1 100644 --- a/bower.json +++ b/bower.json @@ -9,7 +9,6 @@ "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", - "google-caja": "5669", "jed": "~1.1.1", "jquery": "components/jquery#~3.5.0", "jquery-typeahead": "~2.10.6", diff --git a/docs/source/_static/images/jupyter-verification.png b/docs/source/_static/images/jupyter-verification.png deleted file mode 100644 index da816450e5..0000000000 Binary files a/docs/source/_static/images/jupyter-verification.png and /dev/null differ diff --git a/docs/source/conf.py b/docs/source/conf.py index f52289963c..1aa7613b42 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -85,7 +85,6 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = ['.rst', '.ipynb'] # The encoding of source files. #source_encoding = 'utf-8-sig' diff --git a/docs/source/examples/Notebook/header.md b/docs/source/examples/Notebook/header.md deleted file mode 100644 index 15d8df39d4..0000000000 --- a/docs/source/examples/Notebook/header.md +++ /dev/null @@ -1,4 +0,0 @@ -# Notebook Examples - -The pages in this section are all converted notebook files. You can also -[view these notebooks on nbviewer](http://nbviewer.jupyter.org/github/jupyter/notebook/tree/master/) diff --git a/docs/source/extending/savehooks.rst b/docs/source/extending/savehooks.rst index 623c2acfdd..68e228a42a 100644 --- a/docs/source/extending/savehooks.rst +++ b/docs/source/extending/savehooks.rst @@ -11,7 +11,9 @@ two hooks available: without content. This could be used to commit changes after every save, for instance. -They are both called with keyword arguments:: +They are both called with keyword arguments: + +.. code-block:: python pre_save_hook(model=model, path=path, contents_manager=cm) post_save_hook(model=model, os_path=os_path, contents_manager=cm) @@ -21,7 +23,9 @@ Examples These can both be added to :file:`jupyter_notebook_config.py`. -A pre-save hook for stripping output:: +A pre-save hook for stripping output: + +.. code-block:: python def scrub_output_pre_save(model, **kwargs): """scrub output before saving notebooks""" diff --git a/jupyter-notebook.desktop b/jupyter-notebook.desktop index 97a27743ec..095d5ac65b 100644 --- a/jupyter-notebook.desktop +++ b/jupyter-notebook.desktop @@ -1,10 +1,11 @@ [Desktop Entry] Name=Jupyter Notebook Comment=Run Jupyter Notebook -Exec=jupyter-notebook +Exec=jupyter-notebook %f Terminal=true Type=Application Icon=notebook StartupNotify=true MimeType=application/x-ipynb+json; -Categories=Development;Education +Categories=Development;Education; +Keywords=python; diff --git a/notebook/auth/login.py b/notebook/auth/login.py index 1ac434dc5e..16c4e7a356 100644 --- a/notebook/auth/login.py +++ b/notebook/auth/login.py @@ -112,7 +112,7 @@ def set_login_cookie(cls, handler, user_id=None): handler.set_secure_cookie(handler.cookie_name, user_id, **cookie_options) return user_id - auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE) + auth_header_pat = re.compile(r'token\s+(.+)', re.IGNORECASE) @classmethod def get_token(cls, handler): diff --git a/notebook/auth/security.py b/notebook/auth/security.py index 6664137137..cc9723901b 100644 --- a/notebook/auth/security.py +++ b/notebook/auth/security.py @@ -43,7 +43,7 @@ def passwd(passphrase=None, algorithm='argon2'): Examples -------- - >>> passwd('mypassword') + >>> passwd('mypassword', algorithm='sha1') 'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12' """ diff --git a/notebook/base/zmqhandlers.py b/notebook/base/zmqhandlers.py index 6f180f8fe7..c4d4554a89 100644 --- a/notebook/base/zmqhandlers.py +++ b/notebook/base/zmqhandlers.py @@ -14,7 +14,12 @@ from tornado.websocket import WebSocketHandler, WebSocketClosedError from jupyter_client.session import Session -from jupyter_client.jsonutil import date_default, extract_dates +try: + from jupyter_client.jsonutil import json_default, extract_dates +except ImportError: + from jupyter_client.jsonutil import ( + date_default as json_default, extract_dates + ) from ipython_genutils.py3compat import cast_unicode from notebook.utils import maybe_future @@ -40,7 +45,7 @@ def serialize_binary_message(msg): # don't modify msg or buffer list in-place msg = msg.copy() buffers = list(msg.pop('buffers')) - bmsg = json.dumps(msg, default=date_default).encode('utf8') + bmsg = json.dumps(msg, default=json_default).encode('utf8') buffers.insert(0, bmsg) nbufs = len(buffers) offsets = [4 * (nbufs + 1)] @@ -88,15 +93,15 @@ class WebSocketMixin(object): last_ping = 0 last_pong = 0 stream = None - + @property def ping_interval(self): """The interval for websocket keep-alive pings. - + Set ws_ping_interval = 0 to disable pings. """ return self.settings.get('ws_ping_interval', WS_PING_INTERVAL) - + @property def ping_timeout(self): """If no ping is received in this many milliseconds, @@ -109,7 +114,7 @@ def ping_timeout(self): def check_origin(self, origin=None): """Check Origin == Host or Access-Control-Allow-Origin. - + Tornado >= 4 calls this method automatically, raising 403 if it returns False. """ @@ -120,18 +125,18 @@ def check_origin(self, origin=None): host = self.request.headers.get("Host") if origin is None: origin = self.get_origin() - + # If no origin or host header is provided, assume from script if origin is None or host is None: return True - + origin = origin.lower() origin_host = urlparse(origin).netloc - + # OK if origin matches host if origin_host == host: return True - + # Check CORS headers if self.allow_origin: allow = self.allow_origin == origin @@ -193,7 +198,7 @@ def on_pong(self, data): class ZMQStreamHandler(WebSocketMixin, WebSocketHandler): - + if tornado.version_info < (4,1): """Backport send_error from tornado 4.1 to 4.0""" def send_error(self, *args, **kwargs): @@ -206,17 +211,17 @@ def send_error(self, *args, **kwargs): # we can close the connection more gracefully. self.stream.close() - + def _reserialize_reply(self, msg_or_list, channel=None): """Reserialize a reply message using JSON. msg_or_list can be an already-deserialized msg dict or the zmq buffer list. If it is the zmq list, it will be deserialized with self.session. - + This takes the msg list from the ZMQ socket and serializes the result for the websocket. This method should be used by self._on_zmq_reply to build messages that can be sent back to the browser. - + """ if isinstance(msg_or_list, dict): # already unpacked @@ -230,7 +235,7 @@ def _reserialize_reply(self, msg_or_list, channel=None): buf = serialize_binary_message(msg) return buf else: - smsg = json.dumps(msg, default=date_default) + smsg = json.dumps(msg, default=json_default) return cast_unicode(smsg) def _on_zmq_reply(self, stream, msg_list): diff --git a/notebook/conftest.py b/notebook/conftest.py deleted file mode 100644 index 2b1a913653..0000000000 --- a/notebook/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ - -import pytest - -import sys - -# TODO: Remove this hook once Issue #5967 is resolved. -def pytest_ignore_collect(path): - if str(path).endswith("test_terminals_api.py"): - if sys.platform.startswith('win') and sys.version_info >= (3, 9): - return True # do not collect diff --git a/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po b/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po index 07a1317368..0891ff5ad6 100644 --- a/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po +++ b/notebook/i18n/zh_CN/LC_MESSAGES/nbjs.po @@ -4,6 +4,7 @@ # FIRST AUTHOR , 2017. # #, fuzzy +# Universal translation: cell=单元格; kernal=内核; don't translate notebook whenever referring to Jupyter Notebook msgid "" msgstr "" "Project-Id-Version: Jupyter VERSION\n" @@ -35,11 +36,11 @@ msgstr "编辑元数据" #: notebook/static/base/js/dialog.js:202 msgid "Edit Notebook Metadata" -msgstr "编辑笔记本元数据" +msgstr "编辑Notebook元数据" #: notebook/static/base/js/dialog.js:204 msgid "Edit Cell Metadata" -msgstr "编辑块元数据" +msgstr "编辑单元格元数据" #: notebook/static/base/js/dialog.js:208 #: notebook/static/notebook/js/notebook.js:485 @@ -74,11 +75,11 @@ msgstr "警告: 不能保存无效的JSON。" #: notebook/static/base/js/dialog.js:247 msgid "There are no attachments for this cell." -msgstr "这个块没有附件。" +msgstr "这个单元格没有附件。" #: notebook/static/base/js/dialog.js:250 msgid "Current cell attachments" -msgstr "当前块附件" +msgstr "当前单元格附件" #: notebook/static/base/js/dialog.js:259 #: notebook/static/notebook/js/celltoolbarpresets/attachments.js:46 @@ -104,7 +105,7 @@ msgstr "编辑笔记本附件" #: notebook/static/base/js/dialog.js:350 msgid "Edit Cell Attachments" -msgstr "编辑块附件" +msgstr "编辑单元格附件" #: notebook/static/base/js/dialog.js:373 msgid "Select a file to insert." @@ -124,7 +125,7 @@ msgstr "该 notebook 服务的版本是:" #: notebook/static/notebook/js/about.js:22 msgid "The server is running on this version of Python:" -msgstr "该服务运行中使用的 Python 版本为:" +msgstr "该服务运行中使用的 Python 版本为:" #: notebook/static/notebook/js/about.js:25 msgid "Waiting for kernel to be available..." @@ -132,11 +133,11 @@ msgstr "等待内核可用..." #: notebook/static/notebook/js/about.js:27 msgid "Server Information:" -msgstr "服务信息:" +msgstr "服务信息:" #: notebook/static/notebook/js/about.js:29 msgid "Current Kernel Information:" -msgstr "当前内核信息:" +msgstr "当前内核信息:" #: notebook/static/notebook/js/about.js:32 msgid "Could not access sys_info variable for version information." @@ -188,7 +189,7 @@ 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)" @@ -196,11 +197,11 @@ msgstr "重启服务,然后重新运行整个笔记本(无确认对话框) #: notebook/static/notebook/js/actions.js:120 msgid "confirm restart kernel and run all cells" -msgstr "确认重启内核并且运行所有代码块" +msgstr "确认重启内核并且运行所有单元格" #: notebook/static/notebook/js/actions.js:121 msgid "restart the kernel, then re-run the whole notebook (with dialog)" -msgstr "重启内核, 然后重新运行整个代码(带确认对话框)" +msgstr "重启内核, 然后重新运行整个notebook(带确认对话框)" #: notebook/static/notebook/js/actions.js:127 msgid "restart kernel and clear output" @@ -225,36 +226,36 @@ msgstr "中断内核" #: notebook/static/notebook/js/actions.js:150 msgid "run cell and select next" -msgstr "运行代码块并且选择下一个代码块" +msgstr "运行单元格并且选择下一个单元格" #: notebook/static/notebook/js/actions.js:152 msgid "run cell, select below" -msgstr "运行代码块, 选择下面的代码块" +msgstr "运行单元格, 选择下面的单元格" #: notebook/static/notebook/js/actions.js:159 #: notebook/static/notebook/js/actions.js:160 msgid "run selected cells" -msgstr "运行选中的代码块" +msgstr "运行选中的单元格" #: notebook/static/notebook/js/actions.js:167 #: notebook/static/notebook/js/actions.js:168 msgid "run cell and insert below" -msgstr "运行代码块并且在下面插入代码块" +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 @@ -269,22 +270,22 @@ msgstr "插入图片" #: notebook/static/notebook/js/actions.js:213 #: notebook/static/notebook/js/actions.js:214 msgid "cut cell attachments" -msgstr "剪切代码块的附件" +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 msgid "paste cell attachments" -msgstr "粘贴代码块的附件" +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 @@ -293,45 +294,45 @@ msgstr "进入编辑模式" #: notebook/static/notebook/js/actions.js:253 msgid "select previous cell" -msgstr "选择上一个代码块" +msgstr "选择上一个单元格" #: notebook/static/notebook/js/actions.js:254 msgid "select cell above" -msgstr "选择上面的代码块" +msgstr "选择上面的单元格" #: notebook/static/notebook/js/actions.js:265 msgid "select next cell" -msgstr "选择下一个代码块" +msgstr "选择下一个单元格" #: notebook/static/notebook/js/actions.js:266 msgid "select cell below" -msgstr "选择下面的代码块" +msgstr "选择下面的单元格" #: notebook/static/notebook/js/actions.js:277 msgid "extend selection above" -msgstr "扩展上面的代码块" +msgstr "扩展上面的单元格" #: notebook/static/notebook/js/actions.js:278 msgid "extend selected cells above" -msgstr "扩展上面选择的代码块" +msgstr "扩展上面选择的单元格" #: notebook/static/notebook/js/actions.js:289 msgid "extend selection below" -msgstr "扩展下面的代码块" +msgstr "扩展下面的单元格" #: notebook/static/notebook/js/actions.js:290 msgid "extend selected cells below" -msgstr "扩展下面选择的代码块" +msgstr "扩展下面选择的单元格" #: notebook/static/notebook/js/actions.js:301 #: notebook/static/notebook/js/actions.js:302 msgid "cut selected cells" -msgstr "剪切选择的代码块" +msgstr "剪切选择的单元格" #: notebook/static/notebook/js/actions.js:312 #: notebook/static/notebook/js/actions.js:313 msgid "copy selected cells" -msgstr "复制选择的代码块" +msgstr "复制选择的单元格" #: notebook/static/notebook/js/actions.js:327 #: notebook/static/notebook/js/actions.js:328 @@ -346,73 +347,73 @@ msgstr "粘贴到下面" #: notebook/static/notebook/js/actions.js:344 #: notebook/static/notebook/js/actions.js:345 msgid "insert cell above" -msgstr "在上面插入代码块" +msgstr "在上面插入单元格" #: notebook/static/notebook/js/actions.js:354 #: notebook/static/notebook/js/actions.js:355 msgid "insert cell below" -msgstr "在下面插入代码块" +msgstr "在下面插入单元格" #: notebook/static/notebook/js/actions.js:365 #: notebook/static/notebook/js/actions.js:366 msgid "change cell to code" -msgstr "把代码块变成代码" +msgstr "把单元格变成代码快" #: notebook/static/notebook/js/actions.js:373 #: notebook/static/notebook/js/actions.js:374 msgid "change cell to markdown" -msgstr "把代码块变成 Markdown" +msgstr "把单元格变成 Markdown" #: notebook/static/notebook/js/actions.js:381 #: notebook/static/notebook/js/actions.js:382 msgid "change cell to raw" -msgstr "清除代码块格式" +msgstr "清除单元格格式" #: notebook/static/notebook/js/actions.js:389 #: notebook/static/notebook/js/actions.js:390 msgid "change cell to heading 1" -msgstr "把代码块变成标题 1" +msgstr "把单元格变成标题 1" #: notebook/static/notebook/js/actions.js:397 #: notebook/static/notebook/js/actions.js:398 msgid "change cell to heading 2" -msgstr "把代码块变成标题 2" +msgstr "把单元格变成标题 2" #: notebook/static/notebook/js/actions.js:405 #: notebook/static/notebook/js/actions.js:406 msgid "change cell to heading 3" -msgstr "把代码块变成标题 3" +msgstr "把单元格变成标题 3" #: notebook/static/notebook/js/actions.js:413 #: notebook/static/notebook/js/actions.js:414 msgid "change cell to heading 4" -msgstr "把代码块变成标题 4" +msgstr "把单元格变成标题 4" #: notebook/static/notebook/js/actions.js:421 #: notebook/static/notebook/js/actions.js:422 msgid "change cell to heading 5" -msgstr "把代码块变成标题 5" +msgstr "把单元格变成标题 5" #: notebook/static/notebook/js/actions.js:429 #: notebook/static/notebook/js/actions.js:430 msgid "change cell to heading 6" -msgstr "把代码块变成标题 6" +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" -msgstr "切换选定单元格的输出" +msgstr "显示/隐藏选定单元格的输出" #: notebook/static/notebook/js/actions.js:445 msgid "toggle cell scrolling" -msgstr "切换单元格滚动" +msgstr "切换单元格为滚动" #: notebook/static/notebook/js/actions.js:446 msgid "toggle output scrolling of selected cells" -msgstr "切换选中单元格的输出滚动" +msgstr "切换选中单元格的输出为滚动" #: notebook/static/notebook/js/actions.js:453 msgid "clear cell output" @@ -424,7 +425,7 @@ msgstr "清空已选择单元格的输出" #: notebook/static/notebook/js/actions.js:460 msgid "move cells down" -msgstr "下移" +msgstr "下移单元格" #: notebook/static/notebook/js/actions.js:461 msgid "move selected cells down" @@ -432,7 +433,7 @@ msgstr "下移选中单元格" #: notebook/static/notebook/js/actions.js:469 msgid "move cells up" -msgstr "上移" +msgstr "上移单元格" #: notebook/static/notebook/js/actions.js:470 msgid "move selected cells up" @@ -459,7 +460,7 @@ msgstr "删除选中单元格" #: notebook/static/notebook/js/actions.js:502 #: notebook/static/notebook/js/actions.js:503 msgid "undo cell deletion" -msgstr "撤销删除" +msgstr "撤销删除单元格" #: notebook/static/notebook/js/actions.js:512 msgid "merge cell with previous cell" diff --git a/notebook/i18n/zh_CN/LC_MESSAGES/nbui.po b/notebook/i18n/zh_CN/LC_MESSAGES/nbui.po index 2b7c506244..cfb5ed835d 100644 --- a/notebook/i18n/zh_CN/LC_MESSAGES/nbui.po +++ b/notebook/i18n/zh_CN/LC_MESSAGES/nbui.po @@ -4,6 +4,7 @@ # FIRST AUTHOR , 2017. # #, fuzzy +# Universal translation: cell=单元格; kernal=内核; don't translate notebook whenever referring to Jupyter Notebook msgid "" msgstr "" "Project-Id-Version: Jupyter VERSION\n" @@ -19,7 +20,7 @@ msgstr "" #: notebook/templates/404.html:3 msgid "You are requesting a page that does not exist!" -msgstr "请求的代码不存在!" +msgstr "您所请求的页面不存在!" #: notebook/templates/edit.html:37 msgid "current mode" @@ -81,11 +82,11 @@ msgstr "显示/隐藏 标题和logo" #: notebook/templates/edit.html:71 notebook/templates/notebook.html:163 msgid "Toggle Header" -msgstr "切换Header" +msgstr "显示/隐藏 标题栏" #: notebook/templates/edit.html:72 notebook/templates/notebook.html:171 msgid "Toggle Line Numbers" -msgstr "切换行号" +msgstr "显示/隐藏 行号" #: notebook/templates/edit.html:75 msgid "Language" @@ -93,15 +94,15 @@ msgstr "语言" #: notebook/templates/error.html:23 msgid "The error was:" -msgstr "错误:" +msgstr "错误:" #: notebook/templates/login.html:24 msgid "Password or token:" -msgstr "密码或者token:" +msgstr "密码或者token:" #: notebook/templates/login.html:26 msgid "Password:" -msgstr "密码:" +msgstr "密码:" #: notebook/templates/login.html:31 msgid "Log in" @@ -109,7 +110,7 @@ msgstr "登录" #: notebook/templates/login.html:39 msgid "No login available, you shouldn't be seeing this page." -msgstr "还没有登录, 请先登录." +msgstr "还没有登录,请先登录。" #: notebook/templates/logout.html:31 #, python-format @@ -127,15 +128,15 @@ msgstr "菜单" #: notebook/templates/notebook.html:65 notebook/templates/notebook.html:254 msgid "Kernel" -msgstr "服务" +msgstr "内核" #: notebook/templates/notebook.html:68 msgid "This notebook is read-only" -msgstr "这个代码是只读的" +msgstr "这个notebook文件是只读的" #: notebook/templates/notebook.html:81 msgid "New Notebook" -msgstr "新建代码" +msgstr "新建Notebook" #: notebook/templates/notebook.html:85 msgid "Opens a new window with the Dashboard view" @@ -147,7 +148,7 @@ msgstr "打开..." #: notebook/templates/notebook.html:90 msgid "Open a copy of this notebook's contents and start a new kernel" -msgstr "打开代码内容的副本并启动一个新的服务" +msgstr "复制并打开当前notebook的内容,并启动一个新的内核" #: notebook/templates/notebook.html:91 msgid "Make a Copy..." @@ -159,11 +160,11 @@ msgstr "重命名..." #: notebook/templates/notebook.html:93 msgid "Save and Checkpoint" -msgstr "保存" +msgstr "保存,且作为一个检查点" #: notebook/templates/notebook.html:96 msgid "Revert to Checkpoint" -msgstr "恢复" +msgstr "恢复到某一个检查点" #: notebook/templates/notebook.html:106 msgid "Print Preview" @@ -171,11 +172,11 @@ msgstr "打印预览" #: notebook/templates/notebook.html:107 msgid "Download as" -msgstr "下载" +msgstr "另存为" #: notebook/templates/notebook.html:109 msgid "Notebook (.ipynb)" -msgstr "代码(.ipynb)" +msgstr "Notebook (.ipynb)" #: notebook/templates/notebook.html:110 msgid "Script" @@ -199,7 +200,7 @@ msgstr "" #: notebook/templates/notebook.html:115 msgid "PDF via LaTeX (.pdf)" -msgstr "" +msgstr "通过LaTeX生成的PDF (.pdf)" #: notebook/templates/notebook.html:118 msgid "Deploy as" @@ -207,15 +208,15 @@ msgstr "部署在" #: notebook/templates/notebook.html:123 msgid "Trust the output of this notebook" -msgstr "信任代码的输出" +msgstr "信任当前notebook的输出" #: notebook/templates/notebook.html:124 msgid "Trust Notebook" -msgstr "信任代码" +msgstr "信任当前Notebook" #: notebook/templates/notebook.html:127 msgid "Shutdown this notebook's kernel, and close this window" -msgstr "关闭代码服务并关闭窗口" +msgstr "关闭当前notebook的内核,并关闭当前窗口" #: notebook/templates/notebook.html:128 msgid "Close and Halt" @@ -223,55 +224,55 @@ msgstr "关闭" #: notebook/templates/notebook.html:133 msgid "Cut Cells" -msgstr "剪切代码块" +msgstr "剪切单元格" #: notebook/templates/notebook.html:134 msgid "Copy Cells" -msgstr "复制代码块" +msgstr "复制单元格" #: notebook/templates/notebook.html:135 msgid "Paste Cells Above" -msgstr "粘贴到上面" +msgstr "粘贴单元格到上面" #: notebook/templates/notebook.html:136 msgid "Paste Cells Below" -msgstr "粘贴到下面" +msgstr "粘贴单元格到下面" #: notebook/templates/notebook.html:137 msgid "Paste Cells & Replace" -msgstr "粘贴代码块 & 替换" +msgstr "粘贴单元格 & 替换" #: notebook/templates/notebook.html:138 msgid "Delete Cells" -msgstr "删除代码块" +msgstr "删除单元格" #: notebook/templates/notebook.html:139 msgid "Undo Delete Cells" -msgstr "撤销删除" +msgstr "撤销删除单元格" #: notebook/templates/notebook.html:141 msgid "Split Cell" -msgstr "分割代码块" +msgstr "分割单元格" #: notebook/templates/notebook.html:142 msgid "Merge Cell Above" -msgstr "合并上面的代码块" +msgstr "合并上面的单元格" #: notebook/templates/notebook.html:143 msgid "Merge Cell Below" -msgstr "合并下面的代码块" +msgstr "合并下面的单元格" #: notebook/templates/notebook.html:145 msgid "Move Cell Up" -msgstr "上移代码块" +msgstr "上移单元格" #: notebook/templates/notebook.html:146 msgid "Move Cell Down" -msgstr "下移代码块" +msgstr "下移单元格" #: notebook/templates/notebook.html:148 msgid "Edit Notebook Metadata" -msgstr "编辑界面元数据" +msgstr "编辑Notebook的元数据" #: notebook/templates/notebook.html:150 msgid "Find and Replace" @@ -299,15 +300,15 @@ msgstr "显示/隐藏 操作图标" #: notebook/templates/notebook.html:167 msgid "Toggle Toolbar" -msgstr "" +msgstr "显示/隐藏 工具栏" #: notebook/templates/notebook.html:170 msgid "Show/Hide line numbers in cells" -msgstr "显示/隐藏行号" +msgstr "显示/隐藏 当前单元格的行号" #: notebook/templates/notebook.html:174 msgid "Cell Toolbar" -msgstr "单元格工具栏" +msgstr "单元格 工具栏" #: notebook/templates/notebook.html:179 msgid "Insert" @@ -315,19 +316,19 @@ msgstr "插入" #: notebook/templates/notebook.html:182 msgid "Insert an empty Code cell above the currently active cell" -msgstr "在当前活动单元上插入一个空的代码单元格" +msgstr "在当前代码块上面插入一个空的单元格" #: notebook/templates/notebook.html:183 msgid "Insert Cell Above" -msgstr "插入单元格上面" +msgstr "在上面插入一个单元格" #: notebook/templates/notebook.html:185 msgid "Insert an empty Code cell below the currently active cell" -msgstr "在当前活动单元下面插入一个空的代码单元格" +msgstr "在当前代码块下面插入一个空的单元格" #: notebook/templates/notebook.html:186 msgid "Insert Cell Below" -msgstr "插入单元格下面" +msgstr "在下面插入一个单元格" #: notebook/templates/notebook.html:189 msgid "Cell" @@ -335,7 +336,7 @@ msgstr "单元格" #: notebook/templates/notebook.html:191 msgid "Run this cell, and move cursor to the next one" -msgstr "运行这个单元格,并将光标移到下一个" +msgstr "运行此单元格,并将光标移到下一个" #: notebook/templates/notebook.html:192 msgid "Run Cells" @@ -343,19 +344,19 @@ msgstr "运行所有单元格" #: notebook/templates/notebook.html:193 msgid "Run this cell, select below" -msgstr "运行此单元,选择以下选项" +msgstr "运行此单元格,并自动选择下一个" #: notebook/templates/notebook.html:194 msgid "Run Cells and Select Below" -msgstr "运行单元格并自动选择下一个" +msgstr "运行所有单元格,并自动选择下一个" #: notebook/templates/notebook.html:195 msgid "Run this cell, insert below" -msgstr "运行单元格并选择以下" +msgstr "运行此单元格,并在下面插入一个新的单元格" #: notebook/templates/notebook.html:196 msgid "Run Cells and Insert Below" -msgstr "运行单元格并在下面插入" +msgstr "运行所有单元格,并在下面插入一个新的单元格" #: notebook/templates/notebook.html:197 msgid "Run all cells in the notebook" @@ -367,23 +368,23 @@ msgstr "运行所有" #: notebook/templates/notebook.html:199 msgid "Run all cells above (but not including) this cell" -msgstr "运行上面的所有单元(但不包括)这个单元格" +msgstr "运行上面所有的单元格(但不包括这个单元格)" #: notebook/templates/notebook.html:200 msgid "Run All Above" -msgstr "运行上面的代码块" +msgstr "运行上面所有的单元格" #: notebook/templates/notebook.html:201 msgid "Run this cell and all cells below it" -msgstr "运行当前及以下代码块" +msgstr "运行当前及以下所有的单元格" #: notebook/templates/notebook.html:202 msgid "Run All Below" -msgstr "运行下面的代码块" +msgstr "运行下面的所有的单元格" #: notebook/templates/notebook.html:205 msgid "All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells" -msgstr "代码里的所有单元格都有一个类型. 默认情况下, 新单元被创建为'Code'单元格" +msgstr "Notebook里的所有单元格都有一个类型。默认新单元格都会被创建为代码单元格" #: notebook/templates/notebook.html:206 msgid "Cell Type" @@ -391,7 +392,7 @@ msgstr "单元格类型" #: notebook/templates/notebook.html:209 msgid "Contents will be sent to the kernel for execution, and output will display in the footer of cell" -msgstr "内容将被发送到内核以执行, 输出将显示在单元格的页脚." +msgstr "内容将被发送到内核执行,输出将显示在单元格的页脚" #: notebook/templates/notebook.html:212 msgid "Contents will be rendered as HTML and serve as explanatory text" @@ -399,11 +400,11 @@ msgstr "内容将以HTML形式呈现, 并作为解释性文本" #: notebook/templates/notebook.html:213 notebook/templates/notebook.html:298 msgid "Markdown" -msgstr "标签" +msgstr "" #: notebook/templates/notebook.html:215 msgid "Contents will pass through nbconvert unmodified" -msgstr "内容将通过未经修改的nbconvert" +msgstr "内容将通过nbconvert且不会被修改" #: notebook/templates/notebook.html:216 msgid "Raw NBConvert" @@ -419,15 +420,15 @@ msgstr "隐藏/显示当前单元格输出" #: notebook/templates/notebook.html:224 notebook/templates/notebook.html:240 msgid "Toggle" -msgstr "切换" +msgstr "显示/隐藏" #: notebook/templates/notebook.html:227 msgid "Scroll the output of the current cell" -msgstr "滚动当前单元格的输出" +msgstr "滚动到当前单元格的输出" #: notebook/templates/notebook.html:228 notebook/templates/notebook.html:244 msgid "Toggle Scrolling" -msgstr "切换滚动" +msgstr "滚动显示" #: notebook/templates/notebook.html:231 msgid "Clear the output of the current cell" @@ -443,11 +444,11 @@ msgstr "所有输出" #: notebook/templates/notebook.html:239 msgid "Hide/Show the output of all cells" -msgstr "隐藏/显示 所有代码块的输出" +msgstr "隐藏/显示 所有单元格的输出" #: notebook/templates/notebook.html:243 msgid "Scroll the output of all cells" -msgstr "滚动所有单元格的输出" +msgstr "切换所有单元格的输出为滚动模式" #: notebook/templates/notebook.html:247 msgid "Clear the output of all cells" @@ -455,7 +456,7 @@ msgstr "清空所有代码块的输出" #: notebook/templates/notebook.html:257 msgid "Send Keyboard Interrupt (CTRL-C) to the Kernel" -msgstr "按下CTRL-C 中断服务" +msgstr "按下 CTRL-C 中断内核" #: notebook/templates/notebook.html:258 msgid "Interrupt" @@ -463,7 +464,7 @@ msgstr "中断" #: notebook/templates/notebook.html:261 msgid "Restart the Kernel" -msgstr "重启服务" +msgstr "重启内核" #: notebook/templates/notebook.html:262 msgid "Restart" @@ -479,7 +480,7 @@ msgstr "重启 & 清空输出" #: notebook/templates/notebook.html:269 msgid "Restart the Kernel and re-run the notebook" -msgstr "重启服务并且重新运行代码" +msgstr "重启内核并且重新运行整个notebook" #: notebook/templates/notebook.html:270 msgid "Restart & Run All" @@ -487,7 +488,7 @@ msgstr "重启 & 运行所有" #: notebook/templates/notebook.html:273 msgid "Reconnect to the Kernel" -msgstr "重新连接服务" +msgstr "重连内核" #: notebook/templates/notebook.html:274 msgid "Reconnect" @@ -495,7 +496,7 @@ msgstr "重连" #: notebook/templates/notebook.html:282 msgid "Change kernel" -msgstr "改变服务" +msgstr "更换内核" #: notebook/templates/notebook.html:287 msgid "Help" @@ -511,7 +512,7 @@ msgstr "用户界面之旅" #: notebook/templates/notebook.html:291 msgid "Opens a tooltip with all keyboard shortcuts" -msgstr "打开所有快捷键提示信息" +msgstr "打开包含所有快捷键的提示信息" #: notebook/templates/notebook.html:291 msgid "Keyboard Shortcuts" @@ -543,11 +544,11 @@ msgstr "关于" #: notebook/templates/page.html:114 msgid "Jupyter Notebook requires JavaScript." -msgstr "Jupyter Notebook需要的JavaScript." +msgstr "Jupyter Notebook 需要 JavaScript。" #: notebook/templates/page.html:115 msgid "Please enable it to proceed. " -msgstr "请允许它继续." +msgstr "请启用它以继续。" #: notebook/templates/page.html:122 msgid "dashboard" @@ -579,7 +580,7 @@ msgstr "选择操作对象." #: notebook/templates/tree.html:35 msgid "Duplicate selected" -msgstr "复制选择的对象" +msgstr "复制选中的对象" #: notebook/templates/tree.html:35 msgid "Duplicate" @@ -587,11 +588,11 @@ msgstr "复制" #: notebook/templates/tree.html:36 msgid "Rename selected" -msgstr "重命名" +msgstr "重命名选中的对象" #: notebook/templates/tree.html:37 msgid "Move selected" -msgstr "移动" +msgstr "移动选中的对象" #: notebook/templates/tree.html:37 msgid "Move" @@ -599,7 +600,7 @@ msgstr "移动" #: notebook/templates/tree.html:38 msgid "Download selected" -msgstr "下载" +msgstr "下载选中的对象" #: notebook/templates/tree.html:39 msgid "Shutdown selected notebook(s)" @@ -612,19 +613,19 @@ msgstr "关闭" #: notebook/templates/tree.html:40 msgid "View selected" -msgstr "查看" +msgstr "查看选中的对象" #: notebook/templates/tree.html:41 msgid "Edit selected" -msgstr "编辑" +msgstr "编辑选中的对象" #: notebook/templates/tree.html:42 msgid "Delete selected" -msgstr "删除" +msgstr "删除选中的对象" #: notebook/templates/tree.html:50 msgid "Click to browse for a file to upload." -msgstr "点击浏览文件上传" +msgstr "浏览文件并上传" #: notebook/templates/tree.html:51 msgid "Upload" @@ -652,7 +653,7 @@ msgstr "刷新笔记列表" #: notebook/templates/tree.html:90 msgid "Select All / None" -msgstr "全选 / 全部选" +msgstr "全选 / 全不选" #: notebook/templates/tree.html:93 msgid "Select..." @@ -668,15 +669,15 @@ msgstr "文件夹" #: notebook/templates/tree.html:99 msgid "Select All Notebooks" -msgstr "选择所有笔记" +msgstr "选择所有Notebooks" #: notebook/templates/tree.html:99 msgid "All Notebooks" -msgstr "所有笔记" +msgstr "所有Notebooks" #: notebook/templates/tree.html:100 msgid "Select Running Notebooks" -msgstr "选择运行中的笔记" +msgstr "选择运行中的Notebooks" #: notebook/templates/tree.html:100 msgid "Running" @@ -708,25 +709,25 @@ msgstr "刷新运行列表" #: notebook/templates/tree.html:150 msgid "There are no terminals running." -msgstr "没有终端运行" +msgstr "没有终端正在运行。" #: notebook/templates/tree.html:152 msgid "Terminals are unavailable." -msgstr "终端不可用" +msgstr "终端不可用。" #: notebook/templates/tree.html:162 msgid "Notebooks" -msgstr "笔记" +msgstr "" #: notebook/templates/tree.html:169 msgid "There are no notebooks running." -msgstr "没有笔记正在运行" +msgstr "没有notebooks正在运行。" #: notebook/templates/tree.html:178 msgid "Clusters tab is now provided by IPython parallel." -msgstr "集群标签现在由IPython并行提供." +msgstr "集群标签现在由IPython并行提供。" #: notebook/templates/tree.html:179 msgid "See 'IPython parallel' for installation details." -msgstr "安装细节查看 'IPython parallel'." +msgstr "安装细节查看 'IPython parallel'。" diff --git a/notebook/i18n/zh_CN/LC_MESSAGES/notebook.po b/notebook/i18n/zh_CN/LC_MESSAGES/notebook.po index 9009f957cb..aa03cd5831 100644 --- a/notebook/i18n/zh_CN/LC_MESSAGES/notebook.po +++ b/notebook/i18n/zh_CN/LC_MESSAGES/notebook.po @@ -4,6 +4,7 @@ # FIRST AUTHOR , 2017. # #, fuzzy +# Universal translation: cell=单元格; kernal=内核; don't translate notebook whenever referring to Jupyter Notebook msgid "" msgstr "" "Project-Id-Version: Jupyter VERSION\n" @@ -19,20 +20,20 @@ msgstr "" #: notebook/notebookapp.py:49 msgid "The Jupyter Notebook requires tornado >= 4.0" -msgstr "该程序要求 tornado 版本 >= 4.0" +msgstr "Jupyter Notebook要求拥有 tornado 版本号 >= 4.0" #: notebook/notebookapp.py:53 msgid "The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0" -msgstr "该程序要求 tornado 版本 >= 4.0, 可是现实却是 < 1.1.0" +msgstr "Jupyter Notebook要求拥有 tornado 版本号 >= 4.0, 但你的版本号却是 < 1.1.0" #: notebook/notebookapp.py:55 #, python-format msgid "The Jupyter Notebook requires tornado >= 4.0, but you have %s" -msgstr "该程序要求 tornado 版本 >= 4.0, 可是现实却是 %s" +msgstr "Jupyter Notebook要求拥有 tornado 版本号 >= 4.0, 但你的版本号却是 %s" #: notebook/notebookapp.py:205 msgid "The `ignore_minified_js` flag is deprecated and no longer works." -msgstr "" +msgstr "`ignore_minified_js` 这个标志已经被弃用且不再工作。" #: notebook/notebookapp.py:206 #, python-format @@ -41,46 +42,46 @@ msgstr "在使用notebook的JavaScript和LESS时,可以替换使用 `%s` " #: notebook/notebookapp.py:207 msgid "The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0" -msgstr "" +msgstr "`ignore_minified_js` 这个标志已经被弃用,且会在Notebook 6.0版本时被移除" #: notebook/notebookapp.py:385 msgid "List currently running notebook servers." -msgstr "列出当前运行的Notebook服务." +msgstr "列出当前运行的Notebook服务。" #: notebook/notebookapp.py:389 msgid "Produce machine-readable JSON list output." -msgstr "生成机器可读的JSON输出." +msgstr "生成机器可读的JSON输出。" #: notebook/notebookapp.py:391 msgid "Produce machine-readable JSON object on each line of output." -msgstr "当前运行的服务" +msgstr "在每一行输出上生成机器可读的JSON。" #: notebook/notebookapp.py:395 msgid "If True, the output will be a JSON list of objects, one per active notebook server, each with the details from the relevant server info file." -msgstr "如果是正确的,输出将是一个对象的JSON列表,一个活动的笔记本服务器,每一个都有相关的服务器信息文件的详细信息。" +msgstr "如果是正确的,输出将是一个对象的JSON列表,每个活动的笔记本服务器都有一个,每一个都有相关的服务器信息文件的详细信息。" #: notebook/notebookapp.py:399 msgid "If True, each line of output will be a JSON object with the details from the server info file. For a JSON list output, see the NbserverListApp.jsonlist configuration value" -msgstr "如果是正确的,每一行输出将是一个JSON对象,其中有来自服务器信息文件的详细信息。对于一个JSON列表输出,请参阅NbserverListApp。jsonlist配置值" +msgstr "如果是正确的,每一行输出将是一个JSON对象,且包含来自服务器信息文件的详细信息。对于一个JSON列表输出,请参阅NbserverListApp.jsonlist配置值" #: notebook/notebookapp.py:425 msgid "Don't open the notebook in a browser after startup." -msgstr "在启动服务以后不在浏览器中打开一个窗口." +msgstr "请不要在启动服务以后在浏览器中打开notebook。" #: notebook/notebookapp.py:429 msgid "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib." -msgstr "" +msgstr "尚未启用!请在notebook中使用 %pylab or %matplotlib 指令来启用 matplotlib 包。" #: notebook/notebookapp.py:445 msgid "Allow the notebook to be run from root user." -msgstr "允许notebook在root用户下运行." +msgstr "允许notebook在root用户下运行。" #: notebook/notebookapp.py:476 msgid "" "The Jupyter HTML Notebook.\n" " \n" " This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client." -msgstr "The Jupyter HTML Notebook.\n \n 这将启动一个基于tornado的HTML笔记本服务器,它提供一个html5/javascript笔记本客户端。" +msgstr "The Jupyter HTML Notebook.\n \n 这将启动一个基于tornado的HTML Notebook服务器,并提供一个HTML5/Javascript Notebook客户端。" #: notebook/notebookapp.py:515 msgid "Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation" @@ -88,11 +89,11 @@ msgstr "" #: notebook/notebookapp.py:546 msgid "Set the Access-Control-Allow-Credentials: true header" -msgstr "设置Access-Control-Allow-Credentials:true报头" +msgstr "请把Access-Control-Allow-Credentials设置为:true header" #: notebook/notebookapp.py:550 msgid "Whether to allow the user to run the notebook as root." -msgstr "是否允许notebook在root用户下运行." +msgstr "是否允许notebook在root用户下运行。" #: notebook/notebookapp.py:554 msgid "The default URL to redirect to from `/`" @@ -100,48 +101,48 @@ msgstr "从 `/` 重定向到的默认URL " #: notebook/notebookapp.py:558 msgid "The IP address the notebook server will listen on." -msgstr "notebook服务会监听的IP地址." +msgstr "notebook服务会监听的IP地址。" #: notebook/notebookapp.py:571 #, python-format msgid "" "Cannot bind to localhost, using 127.0.0.1 as default ip\n" "%s" -msgstr "不能绑定到localhost, 使用127.0.0.1作为默认的IP \n %s" +msgstr "不能绑定到localhost, 请使用127.0.0.1作为默认的IP \n %s" #: notebook/notebookapp.py:585 msgid "The port the notebook server will listen on." -msgstr "notebook服务会监听的IP端口." +msgstr "notebook服务会监听的IP端口。" #: notebook/notebookapp.py:589 msgid "The number of additional ports to try if the specified port is not available." -msgstr "如果指定的端口不可用,则要尝试其他端口的数量." +msgstr "如果指定的端口不可用,则要尝试其他端口的数量。" #: notebook/notebookapp.py:593 msgid "The full path to an SSL/TLS certificate file." -msgstr "SSL/TLS 认证文件所在全路径." +msgstr "SSL/TLS 认证文件所在的完整路径。" #: notebook/notebookapp.py:597 msgid "The full path to a private key file for usage with SSL/TLS." -msgstr "SSL/TLS 私钥文件所在全路径." +msgstr "SSL/TLS 私钥文件所在的完整路径。" #: notebook/notebookapp.py:601 msgid "The full path to a certificate authority certificate for SSL/TLS client authentication." -msgstr "用于ssl/tls客户端身份验证的证书颁发证书的完整路径." +msgstr "SSL/TLS 客户端身份验证的证书所在的完整路径。" #: notebook/notebookapp.py:605 msgid "The file where the cookie secret is stored." -msgstr "存放cookie密钥的文件被保存了." +msgstr "那个存放cookie密钥的文件。" #: notebook/notebookapp.py:634 #, python-format msgid "Writing notebook server cookie secret to %s" -msgstr "把notebook 服务cookie密码写入 %s" +msgstr "把notebook服务器的cookie密码写入 %s" #: notebook/notebookapp.py:641 #, python-format msgid "Could not set permissions on %s" -msgstr "不能在 %s 设置权限" +msgstr "无法在 %s 设置权限" #: notebook/notebookapp.py:646 msgid "" @@ -152,14 +153,14 @@ msgid "" "\n" " Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED.\n" " " -msgstr "" +msgstr "服务器第一次连接所需要的认证秘钥。\n \n 如果没有弃用密码,\n 默认将生成一个新的随机秘钥。\n \n 由于设置为一个空白字段会自动关闭认证,我们不推荐该行为。\n" #: notebook/notebookapp.py:656 msgid "" "One-time token used for opening a browser.\n" " Once used, this token cannot be used again.\n" " " -msgstr "" +msgstr "打开浏览器的一次性秘钥。\n 一旦使用,则无法再次使用。\n" #: notebook/notebookapp.py:732 msgid "" @@ -176,17 +177,17 @@ msgstr "" #: notebook/notebookapp.py:743 msgid "DEPRECATED, use tornado_settings" -msgstr "" +msgstr "已被弃用!请使用 tornado_settings" #: notebook/notebookapp.py:748 msgid "" "\n" " webapp_settings is deprecated, use tornado_settings.\n" -msgstr "" +msgstr "webapp_settings已被弃用,请使用tornado_settings。" #: notebook/notebookapp.py:752 msgid "Supply overrides for the tornado.web.Application that the Jupyter notebook uses." -msgstr "" +msgstr "Supply重写了Jupyter notebook正在使用的tornado.web.Application。" #: notebook/notebookapp.py:756 msgid "" @@ -217,34 +218,34 @@ msgstr "" #: notebook/notebookapp.py:778 msgid "Supply extra arguments that will be passed to Jinja environment." -msgstr "" +msgstr "请添加Jinja环境所需要的额外的变量。" #: notebook/notebookapp.py:782 msgid "Extra variables to supply to jinja templates when rendering." -msgstr "" +msgstr "需要额外的变量去渲染Jinja模板。" #: notebook/notebookapp.py:818 msgid "DEPRECATED use base_url" -msgstr "" +msgstr "已被弃用!请使用 base_url" #: notebook/notebookapp.py:822 msgid "base_project_url is deprecated, use base_url" -msgstr "" +msgstr "base_project_url已被弃用!请使用 base_url" #: notebook/notebookapp.py:838 msgid "Path to search for custom.js, css" -msgstr "" +msgstr "搜索custom.js, css所在的路径" #: notebook/notebookapp.py:850 msgid "" "Extra paths to search for serving jinja templates.\n" "\n" " Can be used to override templates from notebook.templates." -msgstr "" +msgstr "额外寻找Jinja模板的路径。\n \n 可以被用来覆盖 notebook.templates 里的模板" #: notebook/notebookapp.py:861 msgid "extra paths to look for Javascript notebook extensions" -msgstr "" +msgstr "额外寻找Javascript notebook扩展的路径" #: notebook/notebookapp.py:906 #, python-format @@ -293,40 +294,40 @@ msgid "" "\n" " DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.\n" " " -msgstr "" +msgstr "\n 尚未启用!请在Notebook里使用 %pylab or %matplotlib 指令去启用matplotlib。\n" #: notebook/notebookapp.py:994 msgid "Support for specifying --pylab on the command line has been removed." -msgstr "" +msgstr "不再支持在命令行里使用 --pylab 指令。" #: notebook/notebookapp.py:996 msgid "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself." -msgstr "" +msgstr "请在notebook里使用 %pylab{0}` or `%matplotlib{0}`。" #: notebook/notebookapp.py:1001 msgid "The directory to use for notebooks and kernels." -msgstr "用于笔记本和内核的目录。" +msgstr "用于notebooks和内核的目录。" #: notebook/notebookapp.py:1024 #, python-format msgid "No such notebook dir: '%r'" -msgstr "没有找到路径: '%r' " +msgstr "没有找到notebook的路径: '%r' " #: notebook/notebookapp.py:1037 msgid "DEPRECATED use the nbserver_extensions dict instead" -msgstr "不赞成使用nbserverextensions" +msgstr "已被弃用!请使用 nbserver_extensions dict 指令" #: notebook/notebookapp.py:1042 msgid "server_extensions is deprecated, use nbserver_extensions" -msgstr "服务器扩展被弃用,使用nbserverextensions" +msgstr "server_extensions指令已被弃用,请使用nbserver_extensions" #: notebook/notebookapp.py:1046 msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order." -msgstr "将Python模块作为笔记本服务器扩展加载。可以使用条目值来启用和禁用扩展的加载。这些扩展将以字母顺序加载。" +msgstr "可被作为notebook服务器扩展的Python模块们。可以使用条目值来启用和禁用扩展的加载。这些扩展将以字母顺序加载。" #: notebook/notebookapp.py:1055 msgid "Reraise exceptions encountered loading server extensions?" -msgstr "重新运行的异常会遇到加载服务器扩展吗?" +msgstr "加载服务器扩展时,遇到重新引发的异常?" #: notebook/notebookapp.py:1058 msgid "" @@ -346,7 +347,7 @@ msgstr "" msgid "" "(sec) Time window used to \n" " check the message and data rate limits." -msgstr "(sec)时间窗口被用来 \n 检查消息和数据速率限制." +msgstr "(sec)时间窗口被用来 \n 检查消息和限制数据速率。" #: notebook/notebookapp.py:1077 #, python-format @@ -355,7 +356,7 @@ msgstr "找不到文件或文件夹: %s" #: notebook/notebookapp.py:1147 msgid "Notebook servers are configured to only be run with a password." -msgstr "服务设置为只能使用密码运行." +msgstr "Notebook服务器被设置为只能使用密码运行。" #: notebook/notebookapp.py:1148 msgid "Hint: run the following command to set a password" @@ -363,21 +364,21 @@ msgstr "提示: 运行下面命令设置密码" #: notebook/notebookapp.py:1149 msgid "\t$ python -m notebook.auth password" -msgstr "" +msgstr "\t$ python -m notebook.auth password" #: notebook/notebookapp.py:1187 #, python-format msgid "The port %i is already in use, trying another port." -msgstr "端口 %i 已经被站用, 请尝试其他端口." +msgstr "端口 %i 已经被占用, 请尝试其他端口。" #: notebook/notebookapp.py:1190 #, python-format msgid "Permission to listen on port %i denied" -msgstr "监听端口 %i 失败" +msgstr "没有监听端口 %i 的权限" #: notebook/notebookapp.py:1199 msgid "ERROR: the notebook server could not be started because no available port could be found." -msgstr "错误: 服务启动失败因为没有找到可用的端口. " +msgstr "错误: Notebook服务器启动失败 - 因为没有找到可用的端口。" #: notebook/notebookapp.py:1205 msgid "[all ip addresses on your system]" @@ -403,15 +404,15 @@ msgstr "" #: notebook/notebookapp.py:1269 #, python-format msgid "Shutdown this notebook server (%s/[%s])? " -msgstr "关闭服务 (%s/[%s])" +msgstr "关闭这个notebook服务器 (%s/[%s])" #: notebook/notebookapp.py:1275 msgid "Shutdown confirmed" -msgstr "关闭确定" +msgstr "确认关闭" #: notebook/notebookapp.py:1279 msgid "No answer for 5s:" -msgstr "5s 未响应" +msgstr "5秒未响应" #: notebook/notebookapp.py:1280 msgid "resuming operation..." @@ -431,39 +432,39 @@ msgstr "加载插件 %s 失败" #, python-format msgid "Shutting down %d kernel" msgid_plural "Shutting down %d kernels" -msgstr[0] "关闭 %d 服务" -msgstr[1] "关闭 %d 服务" +msgstr[0] "关闭 %d 内核" +msgstr[1] "关闭 %d 内核" #: notebook/notebookapp.py:1383 #, python-format msgid "%d active kernel" msgid_plural "%d active kernels" -msgstr[0] "%d 活跃的服务" -msgstr[1] "%d 活跃的服务" +msgstr[0] "%d 活跃的内核" +msgstr[1] "%d 活跃的内核" #: notebook/notebookapp.py:1387 #, python-format msgid "" "The Jupyter Notebook is running at:\n" "%s" -msgstr "本程序运行在: %s" +msgstr "Jupyter Notebook运行在:\n %s" #: notebook/notebookapp.py:1434 msgid "Running as root is not recommended. Use --allow-root to bypass." -msgstr "不建议以root身份运行.使用--allow-root绕过过." +msgstr "不建议以root身份运行。请使用 --allow-root 绕过。" #: notebook/notebookapp.py:1440 msgid "Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)." -msgstr "使用control-c停止此服务器并关闭所有内核(两次跳过确认)." +msgstr "使用 Control-C 停止此服务器并关闭所有内核(连续操作两次便可跳过确认界面)。" #: notebook/notebookapp.py:1442 msgid "Welcome to Project Jupyter! Explore the various tools available and their corresponding documentation. If you are interested in contributing to the platform, please visit the communityresources section at http://jupyter.org/community.html." -msgstr "欢迎来到项目Jupyter! 探索可用的各种工具及其相应的文档. 如果你有兴趣对这个平台,请访问http://jupyter.org/community.html community resources部分." +msgstr "欢迎来到Jupyter项目! 探索可用的各种工具及其相应的文档。如果你有兴趣对这个平台,请访问http://jupyter.org/community.html 的community resources部分。" #: notebook/notebookapp.py:1453 #, python-format msgid "No web browser found: %s." -msgstr "没有找到web浏览器: %s." +msgstr "没有找到网页浏览器: %s。" #: notebook/notebookapp.py:1458 #, python-format @@ -477,7 +478,7 @@ msgstr "已经中断..." #: notebook/services/contents/filemanager.py:525 #, python-format msgid "Serving notebooks from local directory: %s" -msgstr "启动notebooks 在本地路径: %s" +msgstr "notebooks 运行所在的本地路径: %s" #: notebook/services/contents/manager.py:69 msgid "Untitled" diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 590e755b86..e07bfcf507 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -1076,7 +1076,7 @@ def _token_changed(self, change): allow_password_change = Bool(True, config=True, help="""Allow password to be changed at login for the notebook server. - While loggin in with a token, the notebook server UI will give the opportunity to + While logging in with a token, the notebook server UI will give the opportunity to the user to enter a new password at the same time that will replace the token login mechanism. @@ -2092,7 +2092,34 @@ def _init_asyncio_patch(self): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 + + With the introduction of the async kernel, the existing sync kernel + requires the use of nested loops in order to run code synchronously. + This is done in `jupyter_client` using the helper util `run_sync`: + + ref: https://github.com/jupyter/jupyter_client/blob/f453b51eeeff9e905c583b7da3905c0e35cfbdf0/jupyter_client/utils.py#L11 + + which creates a new event loop and relies on `nest_asyncio` patching + to allow nested loops. This requires that *all* potential tasks are + patched before executing. When only some tasks are patched it leads to + the following issue: + + ref: https://github.com/jupyter/notebook/issues/6164 + + So we must call `nest_asyncio.apply()` method as early as possible. It + is preferable to do this in the consuming application rather than the + `jupyter_client` as it is a global patch and would impact all consumers + rather than just the ones that rely on synchronous kernel behavior. """ + import nest_asyncio + + try: + nest_asyncio.apply() + except RuntimeError: + # nest_asyncio requires a running loop in order to patch. + # In tests the loop may not have been created yet. + pass + if sys.platform.startswith("win") and sys.version_info >= (3, 8): import asyncio try: diff --git a/notebook/services/contents/handlers.py b/notebook/services/contents/handlers.py index 9f782add50..b3216335bb 100644 --- a/notebook/services/contents/handlers.py +++ b/notebook/services/contents/handlers.py @@ -11,7 +11,12 @@ from tornado import gen, web from notebook.utils import maybe_future, url_path_join, url_escape -from jupyter_client.jsonutil import date_default +try: + from jupyter_client.jsonutil import json_default +except ImportError: + from jupyter_client.jsonutil import ( + date_default as json_default + ) from notebook.base.handlers import ( IPythonHandler, APIHandler, path_regex, @@ -85,7 +90,7 @@ def _finish_model(self, model, location=True): self.set_header('Location', location) self.set_header('Last-Modified', model['last_modified']) self.set_header('Content-Type', 'application/json') - self.finish(json.dumps(model, default=date_default)) + self.finish(json.dumps(model, default=json_default)) @web.authenticated @gen.coroutine @@ -107,7 +112,7 @@ def get(self, path=''): if content not in {'0', '1'}: raise web.HTTPError(400, u'Content %r is invalid' % content) content = int(content) - + model = yield maybe_future(self.contents_manager.get( path=path, type=type, format=format, content=content, )) @@ -125,7 +130,7 @@ def patch(self, path=''): model = yield maybe_future(cm.update(model, path)) validate_model(model, expect_content=False) self._finish_model(model) - + @gen.coroutine def _copy(self, copy_from, copy_to=None): """Copy a file, optionally specifying a target directory.""" @@ -146,7 +151,7 @@ def _upload(self, model, path): self.set_status(201) validate_model(model, expect_content=False) self._finish_model(model) - + @gen.coroutine def _new_untitled(self, path, type='', ext=''): """Create a new, empty untitled entity""" @@ -155,13 +160,13 @@ def _new_untitled(self, path, type='', ext=''): self.set_status(201) validate_model(model, expect_content=False) self._finish_model(model) - + @gen.coroutine def _save(self, model, path): """Save an existing file.""" - chunk = model.get("chunk", None) + 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(u"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) @@ -247,7 +252,7 @@ def get(self, path=''): """get lists checkpoints for a file""" cm = self.contents_manager checkpoints = yield maybe_future(cm.list_checkpoints(path)) - data = json.dumps(checkpoints, default=date_default) + data = json.dumps(checkpoints, default=json_default) self.finish(data) @web.authenticated @@ -256,7 +261,7 @@ def post(self, path=''): """post creates a new checkpoint""" cm = self.contents_manager checkpoint = yield maybe_future(cm.create_checkpoint(path)) - data = json.dumps(checkpoint, default=date_default) + data = json.dumps(checkpoint, default=json_default) location = url_path_join(self.base_url, 'api/contents', url_escape(path), 'checkpoints', url_escape(checkpoint['id'])) self.set_header('Location', location) diff --git a/notebook/services/kernels/handlers.py b/notebook/services/kernels/handlers.py index 0bb2fd7b50..a2d4ddbab9 100644 --- a/notebook/services/kernels/handlers.py +++ b/notebook/services/kernels/handlers.py @@ -15,7 +15,12 @@ from tornado.ioloop import IOLoop from jupyter_client import protocol_version as client_protocol_version -from jupyter_client.jsonutil import date_default +try: + from jupyter_client.jsonutil import json_default +except ImportError: + from jupyter_client.jsonutil import ( + date_default as json_default + ) from ipython_genutils.py3compat import cast_unicode from notebook.utils import maybe_future, url_path_join, url_escape @@ -29,7 +34,7 @@ class MainKernelHandler(APIHandler): def get(self): km = self.kernel_manager kernels = yield maybe_future(km.list_kernels()) - self.finish(json.dumps(kernels, default=date_default)) + self.finish(json.dumps(kernels, default=json_default)) @web.authenticated @gen.coroutine @@ -48,7 +53,7 @@ def post(self): location = url_path_join(self.base_url, 'api', 'kernels', url_escape(kernel_id)) self.set_header('Location', location) self.set_status(201) - self.finish(json.dumps(model, default=date_default)) + self.finish(json.dumps(model, default=json_default)) class KernelHandler(APIHandler): @@ -58,7 +63,7 @@ class KernelHandler(APIHandler): def get(self, kernel_id): km = self.kernel_manager model = yield maybe_future(km.kernel_model(kernel_id)) - self.finish(json.dumps(model, default=date_default)) + self.finish(json.dumps(model, default=json_default)) @web.authenticated @gen.coroutine @@ -87,7 +92,7 @@ def post(self, kernel_id, action): self.set_status(500) else: model = yield maybe_future(km.kernel_model(kernel_id)) - self.write(json.dumps(model, default=date_default)) + self.write(json.dumps(model, default=json_default)) self.finish() @@ -95,7 +100,7 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler): '''There is one ZMQChannelsHandler per running kernel and it oversees all the sessions. ''' - + # class-level registry of open sessions # allows checking for conflict on session-id, # which is used as a zmq identity and must be unique. @@ -128,12 +133,12 @@ def create_stream(self): meth = getattr(km, "connect_" + channel) self.channels[channel] = stream = meth(self.kernel_id, identity=identity) stream.channel = channel - + 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. @@ -152,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"] @@ -178,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) @@ -188,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(): @@ -197,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 @@ -222,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 ) @@ -232,6 +254,7 @@ def nudge(count): log = self.log.warning if count % 10 == 0 else self.log.debug log("Nudge: attempt %s on kernel %s" % (count, 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) @@ -268,7 +291,7 @@ def request_kernel_info(self): def _handle_kernel_info_reply(self, msg): """process the kernel_info_reply - + enabling msg spec adaptation, if necessary """ idents,msg = self.session.feed_identities(msg) @@ -285,15 +308,15 @@ def _handle_kernel_info_reply(self, msg): self.log.error("Kernel info request failed, assuming current %s", info) info = {} self._finish_kernel_info(info) - + # close the kernel_info channel, we don't need it anymore if self.kernel_info_channel: self.kernel_info_channel.close() self.kernel_info_channel = None - + def _finish_kernel_info(self, info): """Finish handling kernel_info reply - + Set up protocol adaptation, if needed, and signal that connection can continue. """ @@ -303,7 +326,7 @@ def _finish_kernel_info(self, info): 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)) if not self._kernel_info_future.done(): self._kernel_info_future.set_result(info) - + def initialize(self): super().initialize() self.zmq_stream = None @@ -336,7 +359,7 @@ def pre_get(self): kernel = self.kernel_manager.get_kernel(self.kernel_id) self.session.key = kernel.session.key future = self.request_kernel_info() - + def give_up(): """Don't wait forever for the kernel to reply""" if future.done(): @@ -347,16 +370,16 @@ def give_up(): loop.add_timeout(loop.time() + self.kernel_info_timeout, give_up) # actually wait for it yield future - + @gen.coroutine def get(self, kernel_id): self.kernel_id = cast_unicode(kernel_id, 'ascii') yield super().get(kernel_id=kernel_id) - + @gen.coroutine def _register_session(self): """Ensure we aren't creating a duplicate session. - + If a previous identical session is still open, close it to avoid collisions. 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. @@ -366,7 +389,8 @@ def _register_session(self): 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() @@ -379,7 +403,7 @@ def open(self, kernel_id): self.log.info("Restoring connection for %s", self.session_key) self.channels = buffer_info['channels'] connected = self.nudge() - + def replay(value): replay_buffer = buffer_info['buffer'] if replay_buffer: @@ -449,7 +473,7 @@ def write_stderr(error_message): parent=parent ) msg['channel'] = 'iopub' - self.write_message(json.dumps(msg, default=date_default)) + self.write_message(json.dumps(msg, default=json_default)) channel = getattr(stream, 'channel', None) msg_type = msg['header']['msg_type'] @@ -463,7 +487,7 @@ def write_stderr(error_message): self._iopub_data_exceeded = False if channel == 'iopub' and msg_type not in {'status', 'comm_open', 'execute_input'}: - + # Remove the counts queued for removal. now = IOLoop.current().time() while len(self._iopub_window_byte_queue) > 0: @@ -484,16 +508,16 @@ def write_stderr(error_message): else: byte_count = 0 self._iopub_window_byte_count += byte_count - - # Queue a removal of the byte and message count for a time in the + + # Queue a removal of the byte and message count for a time in the # future, when we are no longer interested in it. self._iopub_window_byte_queue.append((now + self.rate_limit_window, byte_count)) - + # Check the limits, set the limit flags, and reset the # message and data counts. msg_rate = float(self._iopub_window_msg_count) / self.rate_limit_window data_rate = float(self._iopub_window_byte_count) / self.rate_limit_window - + # Check the msg rate if self.iopub_msg_rate_limit > 0 and msg_rate > self.iopub_msg_rate_limit: if not self._iopub_msgs_exceeded: @@ -504,7 +528,7 @@ def write_stderr(error_message): to the client in order to avoid crashing it. To change this limit, set the config variable `--NotebookApp.iopub_msg_rate_limit`. - + Current values: NotebookApp.iopub_msg_rate_limit={} (msgs/sec) NotebookApp.rate_limit_window={} (secs) @@ -526,7 +550,7 @@ def write_stderr(error_message): to the client in order to avoid crashing it. To change this limit, set the config variable `--NotebookApp.iopub_data_rate_limit`. - + Current values: NotebookApp.iopub_data_rate_limit={} (bytes/sec) NotebookApp.rate_limit_window={} (secs) @@ -537,7 +561,7 @@ def write_stderr(error_message): self._iopub_data_exceeded = False if not self._iopub_msgs_exceeded: self.log.warning("iopub messages resumed") - + # If either of the limit flags are set, do not send the message. if self._iopub_msgs_exceeded or self._iopub_data_exceeded: # we didn't send it, remove the current message from the calculus @@ -595,7 +619,7 @@ def _send_status_message(self, status): {'execution_state': status} ) msg['channel'] = 'iopub' - self.write_message(json.dumps(msg, default=date_default)) + self.write_message(json.dumps(msg, default=json_default)) def on_kernel_restarted(self): logging.warn("kernel %s restarted", self.kernel_id) diff --git a/notebook/services/sessions/handlers.py b/notebook/services/sessions/handlers.py index 42815f4d4a..fc4b3d1d38 100644 --- a/notebook/services/sessions/handlers.py +++ b/notebook/services/sessions/handlers.py @@ -11,7 +11,12 @@ from tornado import gen, web from ...base.handlers import APIHandler -from jupyter_client.jsonutil import date_default +try: + from jupyter_client.jsonutil import json_default +except ImportError: + from jupyter_client.jsonutil import ( + date_default as json_default + ) from notebook.utils import maybe_future, url_path_join from jupyter_client.kernelspec import NoSuchKernel @@ -24,7 +29,7 @@ def get(self): # Return a list of running sessions sm = self.session_manager sessions = yield maybe_future(sm.list_sessions()) - self.finish(json.dumps(sessions, default=date_default)) + self.finish(json.dumps(sessions, default=json_default)) @web.authenticated @gen.coroutine @@ -82,7 +87,7 @@ def post(self): location = url_path_join(self.base_url, 'api', 'sessions', model['id']) self.set_header('Location', location) self.set_status(201) - self.finish(json.dumps(model, default=date_default)) + self.finish(json.dumps(model, default=json_default)) class SessionHandler(APIHandler): @@ -93,7 +98,7 @@ def get(self, session_id): # Returns the JSON model for a single session sm = self.session_manager model = yield maybe_future(sm.get_session(session_id=session_id)) - self.finish(json.dumps(model, default=date_default)) + self.finish(json.dumps(model, default=json_default)) @web.authenticated @gen.coroutine @@ -146,7 +151,7 @@ def patch(self, session_id): yield maybe_future( km.shutdown_kernel(before['kernel']['id']) ) - self.finish(json.dumps(model, default=date_default)) + self.finish(json.dumps(model, default=json_default)) @web.authenticated @gen.coroutine diff --git a/notebook/services/sessions/sessionmanager.py b/notebook/services/sessions/sessionmanager.py index 92b2a73454..5686332868 100644 --- a/notebook/services/sessions/sessionmanager.py +++ b/notebook/services/sessions/sessionmanager.py @@ -189,7 +189,10 @@ def get_session(self, **kwargs): raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q))) - model = yield maybe_future(self.row_to_model(row)) + 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 gen.Return(model) @gen.coroutine diff --git a/notebook/services/sessions/tests/test_sessionmanager.py b/notebook/services/sessions/tests/test_sessionmanager.py index 719bbaef70..9af38033c8 100644 --- a/notebook/services/sessions/tests/test_sessionmanager.py +++ b/notebook/services/sessions/tests/test_sessionmanager.py @@ -94,7 +94,7 @@ def test_get_session_dead_kernel(self): session = self.create_session(path='/path/to/1/test1.ipynb', kernel_name='python') # kill the kernel sm.kernel_manager.shutdown_kernel(session['kernel']['id']) - with self.assertRaises(KeyError): + with self.assertRaises(web.HTTPError): self.loop.run_sync(lambda: sm.get_session(session_id=session['id'])) # no sessions left listed = self.loop.run_sync(lambda: sm.list_sessions()) diff --git a/notebook/static/base/js/markdown.js b/notebook/static/base/js/markdown.js index 94b8850140..492ac37554 100644 --- a/notebook/static/base/js/markdown.js +++ b/notebook/static/base/js/markdown.js @@ -106,7 +106,7 @@ define([ html = mathjaxutils.replace_math(html, math); } if(options.sanitize) { - html = $(security.sanitize_html_and_parse(html)); + html = $(security.sanitize_html_and_parse(html, true)); } } callback(err, html); diff --git a/notebook/static/base/js/namespace.js b/notebook/static/base/js/namespace.js index 56b1b271cf..2705bedebf 100644 --- a/notebook/static/base/js/namespace.js +++ b/notebook/static/base/js/namespace.js @@ -73,7 +73,7 @@ define(function(){ // tree jglobal('SessionList','tree/js/sessionlist'); - Jupyter.version = "6.4.0"; + Jupyter.version = "6.4.8"; Jupyter._target = '_blank'; return Jupyter; diff --git a/notebook/static/base/js/security.js b/notebook/static/base/js/security.js index 195c0a5cb0..7d01321ce8 100644 --- a/notebook/static/base/js/security.js +++ b/notebook/static/base/js/security.js @@ -3,124 +3,24 @@ define([ 'jquery', - 'components/google-caja/html-css-sanitizer-minified', -], function($, sanitize) { + 'components/sanitizer/index', +], function($, sanitizer) { "use strict"; - + var noop = function (x) { return x; }; - - var caja; - if (window && window.html) { - caja = window.html; - caja.html4 = window.html4; - caja.sanitizeStylesheet = window.sanitizeStylesheet; - } - - var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) { - /** - * add trusting data-attributes to the default sanitizeAttribs from caja - * this function is mostly copied from the caja source - */ - var ATTRIBS = caja.html4.ATTRIBS; - for (var i = 0; i < attribs.length; i += 2) { - var attribName = attribs[i]; - if (attribName.substr(0,5) == 'data-') { - var attribKey = '*::' + attribName; - if (!ATTRIBS.hasOwnProperty(attribKey)) { - ATTRIBS[attribKey] = 0; - } - } - } - // Caja doesn't allow data uri for img::src, see - // https://github.com/google/caja/issues/1558 - // This is not a security issue for browser post ie6 though, so we - // disable the check - // https://www.owasp.org/index.php/Script_in_IMG_tags - ATTRIBS['img::src'] = 0; - return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger); - }; - - var sanitize_css = function (css, tagPolicy) { - /** - * sanitize CSS - * like sanitize_html, but for CSS - * called by sanitize_stylesheets - */ - return caja.sanitizeStylesheet( - window.location.pathname, - css, - { - containerClass: null, - idSuffix: '', - tagPolicy: tagPolicy, - virtualizeAttrName: noop - }, - noop - ); - }; - - var sanitize_stylesheets = function (html, tagPolicy) { - /** - * sanitize just the css in style tags in a block of html - * called by sanitize_html, if allow_css is true - */ - var h = $("
").append(html); - var style_tags = h.find("style"); - if (!style_tags.length) { - // no style tags to sanitize - return html; - } - style_tags.each(function(i, style) { - style.innerHTML = sanitize_css(style.innerHTML, tagPolicy); - }); - return h.html(); - }; - + var defaultSanitizer = sanitizer.defaultSanitizer; + var sanitize_html = function (html, allow_css) { /** * sanitize HTML * if allow_css is true (default: false), CSS is sanitized as well. * otherwise, CSS elements and attributes are simply removed. */ - var html4 = caja.html4; - - if (allow_css) { - // allow sanitization of style tags, - // not just scrubbing - html4.ELEMENTS.style &= ~html4.eflags.UNSAFE; - html4.ATTRIBS.style = html4.atype.STYLE; - } else { - // scrub all CSS - html4.ELEMENTS.style |= html4.eflags.UNSAFE; - html4.ATTRIBS.style = html4.atype.SCRIPT; - } - - var record_messages = function (msg, opts) { - console.log("HTML Sanitizer", msg, opts); - }; - - var policy = function (tagName, attribs) { - if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) { - return { - 'attribs': sanitizeAttribs(tagName, attribs, - noop, noop, record_messages) - }; - } else { - record_messages(tagName + " removed", { - change: "removed", - tagName: tagName - }); - } - }; - - var sanitized = caja.sanitizeWithPolicy(html, policy); - - if (allow_css) { - // sanitize style tags as stylesheets - sanitized = sanitize_stylesheets(sanitized, policy); - } - - return sanitized; + const options = {}; + if (!allow_css) { + options.allowedStyles = {}; + } + return defaultSanitizer.sanitize(html, options); }; var sanitize_html_and_parse = function (html, allow_css) { @@ -141,9 +41,8 @@ define([ $.htmlPrefilter = prev_htmlPrefilter; // Set it back again } }; - + var security = { - caja: caja, sanitize_html_and_parse: sanitize_html_and_parse, sanitize_html: sanitize_html }; diff --git a/notebook/static/notebook/js/outputarea.js b/notebook/static/notebook/js/outputarea.js index 0355f2b11f..2dd65a288e 100644 --- a/notebook/static/notebook/js/outputarea.js +++ b/notebook/static/notebook/js/outputarea.js @@ -48,7 +48,7 @@ define([ OutputArea.config_defaults = { stream_chunk_size: 8192, // chunk size for stream output }; - + /** * Class prototypes **/ @@ -79,17 +79,17 @@ define([ if (!this.prompt_area) { this.prompt_overlay.hide(); } - + this.wrapper.addClass('output_wrapper'); this.element.addClass('output'); - + this.collapse_button.addClass("btn btn-default output_collapsed"); this.collapse_button.attr('title', i18n.msg._('click to expand output')); this.collapse_button.text('. . .'); - + this.prompt_overlay.addClass('out_prompt_overlay prompt'); this.prompt_overlay.attr('title', i18n.msg._('click to expand output; double click to hide output')); - + this.expand(); }; @@ -255,7 +255,7 @@ define([ } this.append_output(json); }; - + // Declare mime type as constants var MIME_JAVASCRIPT = 'application/javascript'; var MIME_HTML = 'text/html'; @@ -267,8 +267,8 @@ define([ var MIME_GIF = 'image/gif'; var MIME_PDF = 'application/pdf'; var MIME_TEXT = 'text/plain'; - - + + OutputArea.output_types = [ MIME_JAVASCRIPT, MIME_HTML, @@ -304,10 +304,10 @@ define([ }); return bundle; }; - + OutputArea.prototype.append_output = function (json) { this.expand(); - + if (this.clear_queued) { this.clear_output(false); this._needs_height_reset = true; @@ -443,7 +443,7 @@ define([ .append($('
').text(err.toString()).addClass('js-error')) .append($('
').text(i18n.msg._('See your browser Javascript console for more details.')).addClass('js-error')); }; - + OutputArea.prototype._safe_append = function (toinsert, toreplace) { /** * safely append an item to the document @@ -657,7 +657,7 @@ define([ element: element, }); }; - + OutputArea.prototype.append_display_data = function (json, handle_inserted) { var toinsert = this.create_output_area(); this._record_display_id(json, toinsert); @@ -687,22 +687,28 @@ define([ var md = json.metadata || {}; var value = json.data[type]; var toinsert; - if (!this.trusted && !OutputArea.safe_outputs[type]) { - // not trusted, sanitize HTML - if (type===MIME_HTML || type==='text/svg') { - var parsed = $(security.sanitize_html_and_parse(value)); - toinsert = append.apply(this, [parsed, md, element, handle_inserted]); + + try { + if (!this.trusted && !OutputArea.safe_outputs[type]) { + // not trusted, sanitize HTML + if (type===MIME_HTML || type==='text/svg') { + var parsed = $(security.sanitize_html_and_parse(value)); + toinsert = append.apply(this, [parsed, md, element, handle_inserted]); + } else { + // don't display if we don't know how to sanitize it + console.log("Ignoring untrusted " + type + " output."); + continue; + } } else { - // don't display if we don't know how to sanitize it - console.log("Ignoring untrusted " + type + " output."); - continue; + toinsert = append.apply(this, [value, md, element, handle_inserted]); } - } else { - toinsert = append.apply(this, [value, md, element, handle_inserted]); + } catch (e) { + console.error('Failed to render mimetype "' + type + '" with: ', e); + continue; } // Since only the png and jpeg mime types call the inserted - // callback, if the mime type is something other we must call the + // callback, if the mime type is something other we must call the // inserted callback only when the element is actually inserted // into the DOM. Use a timeout of 0 to do this. if ([MIME_PNG, MIME_JPEG, MIME_GIF].indexOf(type) < 0 && handle_inserted !== undefined) { @@ -712,6 +718,7 @@ define([ return toinsert; } } + return null; }; @@ -783,7 +790,7 @@ define([ var type = MIME_SVG; var toinsert = this.create_output_subarea(md, "output_svg", type); - // Get the svg element from within the HTML. + // Get the svg element from within the HTML. // One svg is supposed, but could embed other nested svgs var svg = $($('
').html(svg_html).find('svg')[0]); var svg_area = $('
'); @@ -819,7 +826,7 @@ define([ } }); } - + var set_width_height = function (img, md, mime) { /** * set width and height of an img element from metadata @@ -832,7 +839,7 @@ define([ img.addClass('unconfined'); } }; - + OutputArea.prototype._append_img = function (src_type, md, element, handle_inserted, MIME, type_string) { var type = MIME; var toinsert = this.create_output_subarea(md, 'output_' + type_string, type); @@ -849,7 +856,7 @@ define([ element.append(toinsert); return toinsert; }; - + var append_png = function (png, md, element, handle_inserted) { return this._append_img(png, md, element, handle_inserted, MIME_PNG, 'png'); }; @@ -857,7 +864,7 @@ define([ var append_jpeg = function (jpeg, md, element, handle_inserted) { return this._append_img(jpeg, md, element, handle_inserted, MIME_JPEG, 'jpeg'); }; - + var append_gif = function (gif, md, element, handle_inserted) { return this._append_img(gif, md, element, handle_inserted, MIME_GIF, 'gif'); }; @@ -884,18 +891,18 @@ define([ element.append(toinsert); return toinsert; }; - + OutputArea.prototype.append_raw_input = function (msg) { var that = this; this.expand(); var content = msg.content; var area = this.create_output_area(); - + // disable any other raw_inputs, if they are left around $("div.output_subarea.raw_input_container").remove(); - + var input_type = content.password ? 'password' : 'text'; - + area.append( $("
") .addClass("box-flex1 output_subarea raw_input_container") @@ -920,7 +927,7 @@ define([ ) .attr("dir","auto") ); - + this.element.append(area); var raw_input = area.find('input.raw_input'); // Register events that enable/disable the keyboard manager while raw @@ -986,7 +993,7 @@ define([ this.element.height(height); this.clear_queued = false; } - + // Clear all // Remove load event handlers from img tags because we don't want // them to fire if the image is never added to the page. @@ -997,7 +1004,7 @@ define([ // Notify others of changes. this.element.trigger('changed', {output_area: this}); this.element.trigger('cleared', {output_area: this}); - + this.outputs = []; this._display_id_targets = {}; this.trusted = true; @@ -1104,11 +1111,11 @@ define([ OutputArea.append_map[MIME_LATEX] = append_latex; OutputArea.append_map[MIME_JAVASCRIPT] = append_javascript; OutputArea.append_map[MIME_PDF] = append_pdf; - + OutputArea.prototype.mime_types = function () { return OutputArea.display_order; }; - + OutputArea.prototype.register_mime_type = function (mimetype, append, options) { if (mimetype && typeof(append) === 'function') { OutputArea.output_types.push(mimetype); diff --git a/notebook/static/notebook/less/notificationarea.less b/notebook/static/notebook/less/notificationarea.less index 8f8b7566c9..8e9d6f7f5d 100644 --- a/notebook/static/notebook/less/notificationarea.less +++ b/notebook/static/notebook/less/notificationarea.less @@ -30,6 +30,8 @@ .kernel_indicator_name { padding-left: 5px; padding-right: 5px; + // accessibility improvement + color: #102930 } } diff --git a/notebook/static/services/kernels/kernel.js b/notebook/static/services/kernels/kernel.js index f5a1669f37..e176ade32a 100644 --- a/notebook/static/services/kernels/kernel.js +++ b/notebook/static/services/kernels/kernel.js @@ -79,6 +79,7 @@ define([ Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) { var msg = { header : { + date: new Date().toISOString(), msg_id : utils.uuid(), username : this.username, session : this.session_id, diff --git a/notebook/static/tree/less/tree.less b/notebook/static/tree/less/tree.less index b231076fd7..92200bfde1 100644 --- a/notebook/static/tree/less/tree.less +++ b/notebook/static/tree/less/tree.less @@ -186,7 +186,8 @@ ul.breadcrumb { } .running-indicator { padding-top: @dashboard_tb_pad; - color: darken(@brand-success,20%); + // accessibility improvement + color: darken(@brand-success,25%); } .kernel-name { padding-top: @dashboard_tb_pad; @@ -204,6 +205,9 @@ ul.breadcrumb { .kernel-name { margin-left : @dashboard_lr_pad; float : right; + // accessibility improvement + color: #489eb8; + font-weight: 700; } } @@ -229,7 +233,8 @@ ul.breadcrumb { display: inline-block; padding-left: @dashboard_lr_pad; margin-left: -2px; - + // accessibility improvement + color: darken(@link-color, 10%); > .breadcrumb { padding: 0px; diff --git a/notebook/tests/README.md b/notebook/tests/README.md index f910471abc..8caa599a68 100644 --- a/notebook/tests/README.md +++ b/notebook/tests/README.md @@ -1,7 +1,7 @@ # IPython Notebook JavaScript Tests This directory includes regression tests for the web notebook. These tests -depend on [CasperJS](http://casperjs.org/), which in turn requires a recent +depend on [CasperJS](https://github.com/casperjs/casperjs/), which in turn requires a recent version of [PhantomJS](http://phantomjs.org/). The JavaScript tests are organized into subdirectories that match those in diff --git a/notebook/tests/launchnotebook.py b/notebook/tests/launchnotebook.py index bb5f8b7781..426ce43ffd 100644 --- a/notebook/tests/launchnotebook.py +++ b/notebook/tests/launchnotebook.py @@ -167,10 +167,16 @@ def start_thread(): token=cls.token, **bind_args ) - if 'asyncio' in sys.modules: - app._init_asyncio_patch() - import asyncio - asyncio.set_event_loop(asyncio.new_event_loop()) + if "asyncio" in sys.modules: + app._init_asyncio_patch() + import asyncio + + asyncio.set_event_loop(asyncio.new_event_loop()) + # Patch the current loop in order to match production + # behavior + import nest_asyncio + + nest_asyncio.apply() # don't register signal handler during tests app.init_signal = lambda : None # clear log handlers and propagate to root for nose to capture it diff --git a/notebook/tests/test_serverextensions.py b/notebook/tests/test_serverextensions.py index 35da80d4c5..898647926b 100644 --- a/notebook/tests/test_serverextensions.py +++ b/notebook/tests/test_serverextensions.py @@ -1,5 +1,6 @@ import imp import os +import site import sys from unittest import TestCase from unittest.mock import patch @@ -84,6 +85,11 @@ def setUp(self): p = patch.object(mod, 'ENV_CONFIG_PATH', []) self.patches.append(p) + # avoid adding the user site to the config paths with jupyter-core >= 4.9 + # https://github.com/jupyter/jupyter_core/pull/242 + p = patch.object(site, + 'ENABLE_USER_SITE', False) + self.patches.append(p) for p in self.patches: p.start() self.addCleanup(p.stop) diff --git a/package.json b/package.json index 52be970aeb..319d1b969e 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,22 @@ "scripts": { "bower": "bower install", "build": "python setup.py js css", + "build:webpack": "webpack --mode production", "build:watch": "npm run watch", "watch": "onchange 'notebook/static/**/!(*.min).js' 'notebook/static/**/*.less' 'bower.json' -- npm run build" }, "devDependencies": { + "@babel/core": "^7.15.0", + "@babel/preset-env": "^7.15.0", + "@jupyterlab/apputils": "^3.1.3", + "babel-loader": "^8.2.2", + "babel-polyfill": "^6.26.0", "bower": "^1.8.8", "less": "~2", "onchange": "^6.0.0", "po2json": "^0.4.5", - "requirejs": "^2.3.6" + "requirejs": "^2.3.6", + "webpack": "^5.46.0", + "webpack-cli": "^4.7.2" } } diff --git a/pyproject.toml b/pyproject.toml index 0aafe2812a..9553029ced 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,20 @@ [build-system] -requires=["jupyter_packaging~=0.9,<2"] +requires=["jupyter_packaging~=0.9"] build-backend = "setuptools.build_meta" [tool.check-manifest] ignore = ["docs-translations/**", ".*", "*.yml", "*.less", "git-hooks/**", "MANIFEST.in", "RELEASE.md", "jupyter.svg", "notebook/static/**", "notebook/i18n/**/*.json", "docs/source/*.*"] ignore-bad-ideas = ["notebook/i18n/**/*.mo"] +[tool.jupyter-releaser] +skip = ["check-links"] + [tool.jupyter-releaser.hooks] after-bump-version = "python setup.py jsversion" before-build-python = ["pip install babel", "npm install -g po2json"] [tool.jupyter-releaser.options] -ignore-glob = ["docs/source/examples/Notebook/Working With Markdown Cells.ipynb", "docs-translations/**/README.md", "docs/source/contributing.rst", "docs/source/examples/Notebook/JavaScript Notebook Extensions.ipynb", "CHANGELOG.md"] +ignore-glob = ["docs/source/examples/Notebook/Working With Markdown Cells.ipynb", "docs-translations/**/README.md", "docs/source/contributing.rst", "docs/source/examples/Notebook/JavaScript Notebook Extensions.ipynb", "CHANGELOG.md", "notebook/static/components/**/*.*"] [tool.tbump.version] current = "6.5.0.dev0" diff --git a/setup.py b/setup.py index 2a75fd08ba..c54f9a1594 100755 --- a/setup.py +++ b/setup.py @@ -121,8 +121,9 @@ 'jupyter_client>=5.3.4', 'nbformat', 'nbconvert', + 'nest-asyncio>=1.5', 'ipykernel', # bless IPython kernel for now - 'Send2Trash>=1.5.0', + 'Send2Trash>=1.8.0', 'terminado>=0.8.3', 'prometheus_client' ], diff --git a/setupbase.py b/setupbase.py index fb3327da30..8849a85e3b 100644 --- a/setupbase.py +++ b/setupbase.py @@ -137,7 +137,6 @@ def find_package_data(): pjoin(components, "font-awesome", "css", "*.css"), pjoin(components, "es6-promise", "*.js"), pjoin(components, "font-awesome", "fonts", "*.*"), - pjoin(components, "google-caja", "html-css-sanitizer-minified.js"), pjoin(components, "jed", "jed.js"), pjoin(components, "jquery", "jquery.min.js"), pjoin(components, "jquery-typeahead", "dist", "jquery.typeahead.min.js"), @@ -151,6 +150,7 @@ def find_package_data(): pjoin(components, "requirejs", "require.js"), pjoin(components, "requirejs-plugins", "src", "json.js"), pjoin(components, "requirejs-text", "text.js"), + pjoin(components, "sanitizer", "index.js"), pjoin(components, "underscore", "underscore-min.js"), pjoin(components, "moment", "moment.js"), pjoin(components, "moment", "min", "*.js"), @@ -374,14 +374,21 @@ def finalize_options(self): bower_dir = pjoin(static, 'components') node_modules = pjoin(repo_root, 'node_modules') + sanitizer_dir = pjoin(bower_dir, 'sanitizer') def should_run(self): if self.force: return True if not os.path.exists(self.bower_dir): return True - - return mtime(self.bower_dir) < mtime(pjoin(repo_root, 'bower.json')) + if not os.path.exists(self.sanitizer_dir): + return True + + bower_stale = mtime(self.bower_dir) < mtime(pjoin(repo_root, 'bower.json')) + if bower_stale: + return True + + return mtime(self.sanitizer_dir) < mtime(pjoin(repo_root, 'webpack.config.js')) def should_run_npm(self): if not which('npm'): @@ -415,6 +422,8 @@ def run(self): print("You can install js dependencies with `npm install`", file=sys.stderr) raise # self.npm_components() + if not os.path.exists(self.sanitizer_dir): + run(['npm', 'run', 'build:webpack'], cwd=repo_root, env=env) os.utime(self.bower_dir, None) # update package data in case this created new files update_package_data(self.distribution) diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000..bdb252ffd3 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,34 @@ +const path = require('path'); +const crypto = require('crypto'); + +// Workaround for loaders using "md4" by default, which is not supported in FIPS-compliant OpenSSL +// See https://github.com/jupyterlab/jupyterlab/issues/11248 +const cryptoOrigCreateHash = crypto.createHash; +crypto.createHash = (algorithm) => + cryptoOrigCreateHash(algorithm == 'md4' ? 'sha256' : algorithm); + +module.exports = { + entry: ['babel-polyfill', '@jupyterlab/apputils/lib/sanitizer'], + output: { + filename: 'index.js', + path: path.resolve(__dirname, 'notebook/static/components/sanitizer'), + libraryTarget: "amd", + }, + devtool: false, + optimization: { + minimize: false + }, + module: { + rules: [ + { + test: /\.m?jsx?$/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + } + } + } + ] + } +}