Compare commits

...

86 Commits
main ... 4.2.x

Author SHA1 Message Date
Min RK 16a058794d Release 4.2.3
9 years ago
Min RK 268a79cbc6 Release notes for 4.2.3
9 years ago
Min RK 5cc11ec85c Backport PR #1732: yet another error-catching custom.js loader
9 years ago
Min RK 5affc1d4ff Backport PR #1665: Typo hunting: notebookapp.py
9 years ago
Min RK 25678af6ac Backport PR #1727: fix outdated links to ipython-doc
9 years ago
Min RK e0fac939c4 Release 4.2.2
10 years ago
Min RK f3e37754b0 Changelog for 4.2.2
10 years ago
Min RK d7fd3e2803 use `$.text` to put latex on the page
10 years ago
Min RK eb6526dc39 Backport PR #1577: Directory listing should not show hidden or system files
10 years ago
Min RK 4ad7bb3eac Backport PR #1518: Allow requests to POST in OPTIONS requests
10 years ago
Min RK 799665e9ca Backport PR #1645: Improve the error message for the nbextension.
10 years ago
Matthias Bussonnier 03b463ffaa Backport PR #1646: Improved message for conflicting args
10 years ago
wenjun.swj c99f859954 Backport PR #1631: scape file names in attachment headers
10 years ago
Matthias Bussonnier 9136fbd769 Backport PR #1552: avoid clobbering ssl_options.ssl_version
10 years ago
Matthias Bussonnier 4997a0ee8b Backport PR #1526: reverse nbconfig load order
10 years ago
Matthias Bussonnier 70f9133f4d Backport PR #1418: log exceptions in nbconvert handlers
10 years ago
Pierre Gerold 56d54dd805 Backport PR #1378: Protect the three call to custom.js
10 years ago
Min RK e145fdce86 pin casperjs to 1.1.1
10 years ago
Min RK f8ed239874 stop coveralls on travis
10 years ago
Min RK 2ca689be59 Backport PR #1515: setup doesn't require genutils
10 years ago
Min RK 1a5d8be45f release 4.2.1
10 years ago
Min RK a6c312923f Backport PR #1511: changes for 4.2.1
10 years ago
Min RK 0ce9daac27 regen rst notebooks
10 years ago
Min RK e15938d1a6 Backport PR #1471: Fixes for reconnecting on a flaky network
10 years ago
Min RK 557586474c Backport PR #1412: Check if sys_info is null
10 years ago
Min RK d9ad88f32e Backport PR #1403: Don't pass destination when installing nbextension from Python package
10 years ago
Min RK e075309e7f Backport PR #1391: specify destination for nbextension install
10 years ago
Min RK 783fc14930 Backport PR #1361: Missing newline impacting RTD rendering
10 years ago
Min RK 3d39c92b57 Backport PR #1360: Override mimetype for .css files
10 years ago
Min RK a58b9d736a Merge pull request #1510 from SylvainCorlay/Fix_widgets_warning
10 years ago
Sylvain Corlay 7330bd491a Fix widget warning in case of ipywidgets 5.x
10 years ago
Min RK 885979d506 Merge pull request #1399 from SylvainCorlay/_enable_old_widgets
10 years ago
Sylvain Corlay ee44d1947f Enable widgets 4.1
10 years ago
Min RK cc7e592f48 release 4.2.0
10 years ago
Min RK a90b4bc54d Backport PR #1353: Escape file names in attachment headers
10 years ago
Min RK ea4a0c7ccc Backport PR #1338: avoid double-base-url when launching browser
10 years ago
Min RK f10ce5d4ed Backport PR #1237: Allow a session to connect to an existing kernel
10 years ago
Min RK c5eb054f4d Backport PR #1235: Allow kernel id to take precedence over name
10 years ago
Min RK ba5e7fe1b2 Backport PR #1206: Allow modifying kernel associated with a session via PATCH
10 years ago
Min RK 925fd7febe Backport PR #1333: remove what's new from index
10 years ago
Min RK 69535708ff release 4.2.0b1
10 years ago
Carol Willing f42030b997 Merge pull request #1332 from minrk/4.2-notes
10 years ago
Min RK 945dd808a5 4.2.0 release notes
10 years ago
Min RK 6172f337d2 Backport PR #1328: The path for widgets has changed
10 years ago
Min RK 8e9fa93492 Backport PR #1327: avoid writing nbextension config to user dir during tests
10 years ago
Jonathan Frederic 9d250230a8 Merge pull request #1326 from minrk/backport-widget-hardcode
10 years ago
Min RK d542745a68 Backport pull request #1279: Downgraded ipywidget hack
10 years ago
Min RK 46b6829839 Backport PR #1325: Fix handling of preflight requests
10 years ago
Min RK 02e8e4377e Backport PR #1310: allow using sqlite from pysqlite2
10 years ago
Min RK efe922cd99 Backport PR #1288: Correctly render markdown code blocks with unknown language
10 years ago
Min RK 42132cac86 Backport PR #1275: fix validation warnings when validating
10 years ago
Min RK 5a43bdb685 Backport PR #998: use x-access for directory listing test in is_hidden
10 years ago
Min RK 6d3731c9b8 Backport PR #1273: some tests, fixes for nbextension aliases
10 years ago
Min RK 8151f2f35c Backport PR #879: New nbextensions installation API
10 years ago
Min RK ebaa23a027 Backport PR #1143: Exempt javascript files from files check
10 years ago
Min RK 6a20fb3bb6 Backport PR #1079: remove the 'random' from the random-port message
10 years ago
Min RK c73dac23d7 Backport PR #1021: don't install prereleases on Travis
10 years ago
Min RK f6f763ec37 Backport PR #1011: Workaround Firefox bug showing beforeunload twice
10 years ago
Min RK 8d9ebbafe8 Backport PR #1002: Implement delayed evaluation for the cell executions happening before a kernel is set up
10 years ago
Min RK 3eaaeb864c Backport PR #1240: fix a few remaining IPython Notebook references
10 years ago
Min RK 4f7825fe92 Backport PR #1231: Add `cookie_options` to make cookie args configurable
10 years ago
Min RK 3d218d6ee5 Backport PR #1216: Add missing pager.append function
10 years ago
Min RK ae6096e2ae Backport PR #1215: Toggle Header in Edit page
10 years ago
Min RK 41ede3ec56 Backport PR #1187: add missing url-encoding of base_url in terminal/edit templates
10 years ago
Min RK b3c97cd6a6 Backport PR #1168: allow notebook-dir to be root
10 years ago
Min RK 25b0846998 Backport PR #1136: channel.closed is a method
10 years ago
Min RK 8120459e4e Backport PR #1127: Don't force save before download if notebook isn't writable
10 years ago
Min RK fd765a4e8d Backport PR #1120: Removed misplaced docs
10 years ago
Min RK 89044886e3 Backport PR #1119: Clarify keyboard shortcut help for "split cell"
10 years ago
Min RK cb92bc323f Backport PR #1113: Fix long lines escaping from the cell container
10 years ago
Min RK a247c6689d Backport PR #1076: Lowercase file extension before looking it up in CodeMirror
10 years ago
Min RK c206169d78 Backport PR #1071: Focus selected cell after move.
10 years ago
Min RK a79f634716 Backport PR #1061: plural cell actions
10 years ago
Min RK f778c9cd33 Backport PR #1055: Allow all MathJax output formats
10 years ago
Min RK d3b9da68b1 Backport PR #1049: Nested svg
10 years ago
Min RK e4f5d040d1 Backport PR #931: Add simple iopub message rate limiter
10 years ago
Min RK 5fa6c34f40 Backport PR #1036: disable MathJax renderer selection menu
10 years ago
Min RK 8a3b21816d Backport PR #1032: bump MathJax to 2.6
10 years ago
Min RK 67e60d954f Backport PR #1020: only import ssl if it's used
10 years ago
Min RK 8f147f1dfc Backport PR #1017: Add `require` to list of modules to try fixing kernel.js loading.
10 years ago
Min RK 205bfcf220 Backport PR #992: use _.isEqual to check for metadata changes
10 years ago
Min RK f365612924 Backport PR #987: save before copy if notebook is dirty
10 years ago
Min RK eb31a3c652 Backport PR #976: Run notebook tests instead of IPython tests in Dockerfile
10 years ago
Min RK fb98f7e71f Backport PR #925: Be more explicit about deprecation.
10 years ago
Min RK 1faa5ee631 Backport PR #589: Docker purge unneeded files
10 years ago
Volker Braun ac6baa7d22 Use require.toUrl for help_links
10 years ago

@ -18,10 +18,10 @@ env:
- GROUP=js/services
- GROUP=js/tree
before_install:
- 'if [[ $GROUP == js* ]]; then npm install -g casperjs; fi'
- 'if [[ $GROUP == js* ]]; then npm install -g casperjs@1.1.1; fi'
- git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
install:
- pip install -f travis-wheels/wheelhouse --pre file://$PWD#egg=notebook[test] coveralls
- pip install -f travis-wheels/wheelhouse file://$PWD#egg=notebook[test]
script:
- 'if [[ $GROUP == js* ]]; then python -m notebook.jstest ${GROUP:3}; fi'
- 'if [[ $GROUP == python ]]; then nosetests --with-coverage --cover-package=notebook notebook; fi'
@ -32,5 +32,3 @@ matrix:
- python: 3.4
env: GROUP=python
after_success:
- coveralls

@ -59,14 +59,16 @@ RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
python3 get-pip.py && \
rm get-pip.py && \
pip2 --no-cache-dir install requests[security] && \
pip3 --no-cache-dir install requests[security]
pip3 --no-cache-dir install requests[security] && \
rm -rf /root/.cache
# Install some dependencies.
RUN pip2 --no-cache-dir install ipykernel && \
pip3 --no-cache-dir install ipykernel && \
\
python2 -m ipykernel.kernelspec && \
python3 -m ipykernel.kernelspec
python3 -m ipykernel.kernelspec && \
rm -rf /root/.cache
# Move notebook contents into place.
ADD . /usr/src/jupyter-notebook
@ -76,21 +78,22 @@ RUN BUILD_DEPS="nodejs-legacy npm" && \
apt-get update -qq && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq $BUILD_DEPS && \
\
pip3 install --no-cache-dir --pre -e /usr/src/jupyter-notebook && \
pip3 install --no-cache-dir /usr/src/jupyter-notebook && \
pip3 install ipywidgets && \
\
npm cache clean && \
apt-get clean && \
rm -rf /root/.npm && \
rm -rf /root/.cache && \
rm -rf /root/.config && \
rm -rf /root/.local && \
rm -rf /root/tmp && \
rm -rf /var/lib/apt/lists/* && \
apt-get purge -y --auto-remove \
-o APT::AutoRemove::RecommendsImportant=false -o APT::AutoRemove::SuggestsImportant=false $BUILD_DEPS
# Run tests.
RUN pip2 install --no-cache-dir mock nose requests testpath && \
pip3 install --no-cache-dir nose requests testpath && \
\
iptest2 && iptest3 && \
\
pip2 uninstall -y funcsigs mock nose pbr requests six testpath && \
pip3 uninstall -y nose requests testpath
RUN pip3 install --no-cache-dir notebook[test] && nosetests notebook
# Add a notebook profile.
RUN mkdir -p -m 700 /root/.jupyter/ && \

@ -12,7 +12,7 @@
"jquery": "components/jquery#~2.0",
"jquery-ui": "components/jqueryui#~1.10",
"marked": "~0.3",
"MathJax": "components/MathJax#~2.5",
"MathJax": "components/MathJax#~2.6",
"moment": "~2.8.4",
"requirejs": "~2.1",
"term.js": "chjj/term.js#~0.0.7",

@ -6,6 +6,91 @@ Jupyter notebook changelog
A summary of changes in the Jupyter notebook.
For more detailed information, see `GitHub <https://github.com/jupyter/notebook>`__.
.. tip::
Use ``pip install notebook --upgrade`` or ``conda upgrade notebook`` to
upgrade to the latest release.
.. _release-4.2.3:
4.2.3
-----
4.2.3 is a small bugfix release on 4.2.
Highlights:
- Fix regression in 4.2.2 that delayed loading custom.js
until after ``notebook_loaded`` and ``app_initialized`` events have fired.
- Fix some outdated docs and links.
.. seealso::
4.2.3 `on GitHub <https://github.com/jupyter/notebook/milestones/4.2.3>`__.
.. _release-4.2.2:
4.2.2
-----
4.2.2 is a small bugfix release on 4.2, with an important security fix.
All users are strongly encouraged to upgrade to 4.2.2.
Highlights:
- **Security fix**: CVE-2016-6524, where untrusted latex output
could be added to the page in a way that could execute javascript.
- Fix missing POST in OPTIONS responses.
- Fix for downloading non-ascii filenames.
- Avoid clobbering ssl_options, so that users can specify more detailed SSL configuration.
- Fix inverted load order in nbconfig, so user config has highest priority.
- Improved error messages here and there.
.. seealso::
4.2.2 `on GitHub <https://github.com/jupyter/notebook/milestones/4.2.2>`__.
.. _release-4.2.1:
4.2.1
-----
4.2.1 is a small bugfix release on 4.2. Highlights:
- Compatibility fixes for some versions of ipywidgets
- Fix for ignored CSS on Windows
- Fix specifying destination when installing nbextensions
.. seealso::
4.2.1 `on GitHub <https://github.com/jupyter/notebook/milestones/4.2.1>`__.
.. _release-4.2.0:
4.2.0
-----
Release 4.2 adds a new API for enabling and installing extensions.
Extensions can now be enabled at the system-level, rather than just per-user.
An API is defined for installing directly from a Python package, as well.
.. seealso::
:doc:`./examples/Notebook/rstversions/Distributing Jupyter Extensions as Python Packages`
Highlighted changes:
- Upgrade MathJax to 2.6 to fix vertical-bar appearing on some equations.
- Restore ability for notebook directory to be root (4.1 regression)
- Large outputs are now throttled, reducing the ability of output floods to
kill the browser.
- Fix the notebook ignoring cell executions while a kernel is starting by queueing the messages.
- Fix handling of url prefixes (e.g. JupyterHub) in terminal and edit pages.
- Support nested SVGs in output.
And various other fixes and improvements.
.. _release-4.1.0:
4.1.0

@ -0,0 +1,281 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Distributing Jupyter Extensions as Python Packages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview\n",
"### How can the notebook be extended?\n",
"The Jupyter Notebook client and server application are both deeply customizable. Their behavior can be extended by creating, respectively:\n",
"\n",
"- nbextension: a notebook extension\n",
" - a single JS file, or directory of JavaScript, Cascading StyleSheets, etc. that contain at\n",
" minimum a JavaScript module packaged as an\n",
" [AMD modules](https://en.wikipedia.org/wiki/Asynchronous_module_definition)\n",
" that exports a function `load_ipython_extension`\n",
"- server extension: an importable Python module\n",
" - that implements `load_jupyter_server_extension`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Why create a Python package for Jupyter extensions?\n",
"Since it is rare to have a server extension that does not have any frontend components (an nbextension), for convenience and consistency, all these client and server extensions with their assets can be packaged and versioned together as a Python package with a few simple commands. This makes installing the package of extensions easier and less error-prone for the user. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Installation of Jupyter Extensions\n",
"### Install a Python package containing Jupyter Extensions\n",
"There are several ways that you may get a Python package containing Jupyter Extensions. Commonly, you will use a package manager for your system:\n",
"```shell\n",
"pip install helpful_package\n",
"# or\n",
"conda install helpful_package\n",
"# or\n",
"apt-get install helpful_package\n",
"\n",
"# where 'helpful_package' is a Python package containing one or more Jupyter Extensions\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Enable a Server Extension\n",
"\n",
"The simplest case would be to enable a server extension which has no frontend components. \n",
"\n",
"A `pip` user that wants their configuration stored in their home directory would type the following command:\n",
"```shell\n",
"jupyter serverextension enable --py helpful_package\n",
"```\n",
"\n",
"Alternatively, a `virtualenv` or `conda` user can pass `--sys-prefix` which keeps their environment isolated and reproducible. For example:\n",
"```shell\n",
"# Make sure that your virtualenv or conda environment is activated\n",
"[source] activate my-environment\n",
"\n",
"jupyter serverextension enable --py helpful_package --sys-prefix\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Install the nbextension assets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If a package also has an nbextension with frontend assets that must be available (but not neccessarily enabled by default), install these assets with the following command:\n",
"```shell\n",
"jupyter nbextension install --py helpful_package # or --sys-prefix if using virtualenv or conda\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Enable nbextension assets\n",
"If a package has assets that should be loaded every time a Jupyter app (e.g. lab, notebook, dashboard, terminal) is loaded in the browser, the following command can be used to enable the nbextension:\n",
"```shell\n",
"jupyter nbextension enable --py helpful_package # or --sys-prefix if using virtualenv or conda\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Did it work? Check by listing Jupyter Extensions.\n",
"After running one or more extension installation steps, you can list what is presently known about nbextensions or server extension. The following commands will list which extensions are available, whether they are enabled, and other extension details:\n",
"\n",
"```shell\n",
"jupyter nbextension list\n",
"jupyter serverextension list\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Additional resources on creating and distributing packages \n",
"\n",
"> Of course, in addition to the files listed, there are number of other files one needs to build a proper package. Here are some good resources:\n",
"- [The Hitchhiker's Guide to Packaging](http://the-hitchhikers-guide-to-packaging.readthedocs.org/en/latest/quickstart.html)\n",
"- [Repository Structure and Python](http://www.kennethreitz.org/essays/repository-structure-and-python) by Kenneth Reitz\n",
"\n",
"> How you distribute them, too, is important:\n",
"- [Packaging and Distributing Projects](http://python-packaging-user-guide.readthedocs.org/en/latest/distributing/)\n",
"- [conda: Building packages](http://conda.pydata.org/docs/building/build.html)\n",
"\n",
"> Here are some tools to get you started:\n",
"- [generator-nbextension](https://github.com/Anaconda-Server/generator-nbextension)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example - Server extension"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Creating a Python package with a server extension\n",
"\n",
"Here is an example of a python module which contains a server extension directly on itself. It has this directory structure:\n",
"```\n",
"- setup.py\n",
"- MANIFEST.in\n",
"- my_module/\n",
" - __init__.py\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Defining the server extension\n",
"This example shows that the server extension and its `load_jupyter_server_extension` function are defined in the `__init__.py` file.\n",
"\n",
"#### `my_module/__init__.py`\n",
"\n",
"```python\n",
"def _jupyter_server_extension_paths():\n",
" return [{\n",
" \"module\": \"my_module\"\n",
" }]\n",
"\n",
"\n",
"def load_jupyter_server_extension(nbapp):\n",
" nbapp.log.info(\"my module enabled!\")\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Install and enable the server extension\n",
"Which a user can install with:\n",
"```\n",
"jupyter serverextension enable --py my_module [--sys-prefix]\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example - Server extension and nbextension"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Creating a Python package with a server extension and nbextension\n",
"Here is another server extension, with a front-end module. It assumes this directory structure:\n",
"\n",
"```\n",
"- setup.py\n",
"- MANIFEST.in\n",
"- my_fancy_module/\n",
" - __init__.py\n",
" - static/\n",
" index.js\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"### Defining the server extension and nbextension\n",
"This example again shows that the server extension and its `load_jupyter_server_extension` function are defined in the `__init__.py` file. This time, there is also a function `_jupyter_nbextension_path` for the nbextension.\n",
"\n",
"#### `my_fancy_module/__init__.py`\n",
"\n",
"```python\n",
"def _jupyter_server_extension_paths():\n",
" return [{\n",
" \"module\": \"my_fancy_module\"\n",
" }]\n",
"\n",
"# Jupyter Extension points\n",
"def _jupyter_nbextension_paths():\n",
" return [dict(\n",
" section=\"notebook\",\n",
" # the path is relative to the `my_fancy_module` directory\n",
" src=\"static\",\n",
" # directory in the `nbextension/` namespace\n",
" dest=\"my_fancy_module\",\n",
" # _also_ in the `nbextension/` namespace\n",
" require=\"my_fancy_module/index\")]\n",
"\n",
"def load_jupyter_server_extension(nbapp):\n",
" nbapp.log.info(\"my module enabled!\")\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Install and enable the server extension and nbextension\n",
"\n",
"The user can install and enable the extensions with the following set of commands:\n",
"```\n",
"jupyter nbextension install --py my_fancy_module [--sys-prefix|--user]\n",
"jupyter nbextension enable --py my_fancy_module [--sys-prefix|--system]\n",
"jupyter serverextension enable --py my_fancy_module [--sys-prefix|--system]\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.1"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

@ -7,66 +7,6 @@
"# Notebook Basics"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Running the Notebook Server"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The Jupyter notebook server is a custom web server that runs the notebook web application. Most of the time, users run the notebook server on their local computer using the command line interface."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Starting the notebook server using the command line"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can start the notebook server from the command line (Terminal on Mac/Linux, CMD prompt on Windows) by running the following command: \n",
"\n",
" jupyter notebook\n",
"\n",
"This will print some information about the notebook server in your terminal, including the URL of the web application (by default, `http://127.0.0.1:8888`). It will then open your default web browser to this URL.\n",
"\n",
"When the notebook opens, you will see the **notebook dashboard**, which will show a list of the notebooks, files, and subdirectories in the directory where the notebook server was started (as seen in the next section, below). Most of the time, you will want to start a notebook server in the highest directory in your filesystem where notebooks can be found. Often this will be your home directory."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Additional options"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default, the notebook server starts on port 8888. If port 8888 is unavailable, the notebook server searchs the next available port.\n",
"\n",
"You can also specify the port manually:\n",
"\n",
" jupyter notebook --port 9999\n",
"\n",
"Or start notebook server without opening a web browser.\n",
"\n",
" jupyter notebook --no-browser\n",
"\n",
"The notebook server has a number of other command line arguments that can be displayed with the `--help` flag: \n",
"\n",
" jupyter notebook --help"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -291,21 +231,21 @@
],
"metadata": {
"kernelspec": {
"display_name": "IPython (Python 3)",
"display_name": "Python 2",
"language": "python",
"name": "python3"
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.1"
"pygments_lexer": "ipython2",
"version": "2.7.11"
}
},
"nbformat": 4,

@ -0,0 +1,245 @@
`View the original notebook on nbviewer <http://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Distributing%20Jupyter%20Extensions%20as%20Python%20Packages.ipynb>`__
Distributing Jupyter Extensions as Python Packages
==================================================
Overview
--------
How can the notebook be extended?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Jupyter Notebook client and server application are both deeply
customizable. Their behavior can be extended by creating, respectively:
- nbextension: a notebook extension
- a single JS file, or directory of JavaScript, Cascading
StyleSheets, etc. that contain at minimum a JavaScript module
packaged as an `AMD
modules <https://en.wikipedia.org/wiki/Asynchronous_module_definition>`__
that exports a function ``load_ipython_extension``
- server extension: an importable Python module
- that implements ``load_jupyter_server_extension``
Why create a Python package for Jupyter extensions?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since it is rare to have a server extension that does not have any
frontend components (an nbextension), for convenience and consistency,
all these client and server extensions with their assets can be packaged
and versioned together as a Python package with a few simple commands.
This makes installing the package of extensions easier and less
error-prone for the user.
Installation of Jupyter Extensions
----------------------------------
Install a Python package containing Jupyter Extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are several ways that you may get a Python package containing
Jupyter Extensions. Commonly, you will use a package manager for your
system:
.. code:: shell
pip install helpful_package
# or
conda install helpful_package
# or
apt-get install helpful_package
# where 'helpful_package' is a Python package containing one or more Jupyter Extensions
Enable a Server Extension
~~~~~~~~~~~~~~~~~~~~~~~~~
The simplest case would be to enable a server extension which has no
frontend components.
A ``pip`` user that wants their configuration stored in their home
directory would type the following command:
.. code:: shell
jupyter serverextension enable --py helpful_package
Alternatively, a ``virtualenv`` or ``conda`` user can pass
``--sys-prefix`` which keeps their environment isolated and
reproducible. For example:
.. code:: shell
# Make sure that your virtualenv or conda environment is activated
[source] activate my-environment
jupyter serverextension enable --py helpful_package --sys-prefix
Install the nbextension assets
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a package also has an nbextension with frontend assets that must be
available (but not neccessarily enabled by default), install these
assets with the following command:
.. code:: shell
jupyter nbextension install --py helpful_package # or --sys-prefix if using virtualenv or conda
Enable nbextension assets
~~~~~~~~~~~~~~~~~~~~~~~~~
If a package has assets that should be loaded every time a Jupyter app
(e.g. lab, notebook, dashboard, terminal) is loaded in the browser, the
following command can be used to enable the nbextension:
.. code:: shell
jupyter nbextension enable --py helpful_package # or --sys-prefix if using virtualenv or conda
Did it work? Check by listing Jupyter Extensions.
-------------------------------------------------
After running one or more extension installation steps, you can list
what is presently known about nbextensions or server extension. The
following commands will list which extensions are available, whether
they are enabled, and other extension details:
.. code:: shell
jupyter nbextension list
jupyter serverextension list
Additional resources on creating and distributing packages
----------------------------------------------------------
Of course, in addition to the files listed, there are number of
other files one needs to build a proper package. Here are some good
resources: - `The Hitchhiker's Guide to
Packaging <http://the-hitchhikers-guide-to-packaging.readthedocs.org/en/latest/quickstart.html>`__
- `Repository Structure and
Python <http://www.kennethreitz.org/essays/repository-structure-and-python>`__
by Kenneth Reitz
How you distribute them, too, is important: - `Packaging and
Distributing
Projects <http://python-packaging-user-guide.readthedocs.org/en/latest/distributing/>`__
- `conda: Building
packages <http://conda.pydata.org/docs/building/build.html>`__
Here are some tools to get you started: -
`generator-nbextension <https://github.com/Anaconda-Server/generator-nbextension>`__
Example - Server extension
--------------------------
Creating a Python package with a server extension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is an example of a python module which contains a server extension
directly on itself. It has this directory structure:
::
- setup.py
- MANIFEST.in
- my_module/
- __init__.py
Defining the server extension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This example shows that the server extension and its
``load_jupyter_server_extension`` function are defined in the
``__init__.py`` file.
``my_module/__init__.py``
^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: python
def _jupyter_server_extension_paths():
return [{
"module": "my_module"
}]
def load_jupyter_server_extension(nbapp):
nbapp.log.info("my module enabled!")
Install and enable the server extension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Which a user can install with:
::
jupyter serverextension enable --py my_module [--sys-prefix]
Example - Server extension and nbextension
------------------------------------------
Creating a Python package with a server extension and nbextension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is another server extension, with a front-end module. It assumes
this directory structure:
::
- setup.py
- MANIFEST.in
- my_fancy_module/
- __init__.py
- static/
index.js
Defining the server extension and nbextension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This example again shows that the server extension and its
``load_jupyter_server_extension`` function are defined in the
``__init__.py`` file. This time, there is also a function
``_jupyter_nbextension_path`` for the nbextension.
``my_fancy_module/__init__.py``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: python
def _jupyter_server_extension_paths():
return [{
"module": "my_fancy_module"
}]
# Jupyter Extension points
def _jupyter_nbextension_paths():
return [dict(
section="notebook",
# the path is relative to the `my_fancy_module` directory
src="static",
# directory in the `nbextension/` namespace
dest="my_fancy_module",
# _also_ in the `nbextension/` namespace
require="my_fancy_module/index")]
def load_jupyter_server_extension(nbapp):
nbapp.log.info("my module enabled!")
Install and enable the server extension and nbextension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The user can install and enable the extensions with the following set of
commands:
::
jupyter nbextension install --py my_fancy_module [--sys-prefix|--user]
jupyter nbextension enable --py my_fancy_module [--sys-prefix|--system]
jupyter serverextension enable --py my_fancy_module [--sys-prefix|--system]
`View the original notebook on nbviewer <http://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Distributing%20Jupyter%20Extensions%20as%20Python%20Packages.ipynb>`__

@ -318,7 +318,7 @@ Exercise:
^^^^^^^^^
Try to wrap the all code in a file, put this file in
``{profile}/static/custom/<a-name>.js``, and add
``{jupyter_dir}/custom/<a-name>.js``, and add
::

@ -4,60 +4,6 @@
Notebook Basics
===============
Running the Notebook Server
---------------------------
The Jupyter notebook server is a custom web server that runs the
notebook web application. Most of the time, users run the notebook
server on their local computer using the command line interface.
Starting the notebook server using the command line
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can start the notebook server from the command line (Terminal on
Mac/Linux, CMD prompt on Windows) by running the following command:
::
jupyter notebook
This will print some information about the notebook server in your
terminal, including the URL of the web application (by default,
``http://127.0.0.1:8888``). It will then open your default web browser
to this URL.
When the notebook opens, you will see the **notebook dashboard**, which
will show a list of the notebooks, files, and subdirectories in the
directory where the notebook server was started (as seen in the next
section, below). Most of the time, you will want to start a notebook
server in the highest directory in your filesystem where notebooks can
be found. Often this will be your home directory.
Additional options
~~~~~~~~~~~~~~~~~~
By default, the notebook server starts on port 8888. If port 8888 is
unavailable, the notebook server searchs the next available port.
You can also specify the port manually:
::
jupyter notebook --port 9999
Or start notebook server without opening a web browser.
::
jupyter notebook --no-browser
The notebook server has a number of other command line arguments that
can be displayed with the ``--help`` flag:
::
jupyter notebook --help
The Notebook dashboard
----------------------

@ -38,7 +38,7 @@ extension:
});
.. note::
Although for historical reasons the function is called
``load_ipython_extension``, it does apply to the Jupyter notebook in
general, and will work regardless of the kernel in use.
@ -112,7 +112,7 @@ place:
});
.. note::
The standard keybindings might not work correctly on non-US keyboards.
Unfortunately, this is a limitation of browser implementations and the
status of keyboard event handling on the web in general. We appreciate your
@ -184,6 +184,7 @@ actions defined in an extension, it makes sense to use the extension name as
the prefix. For the action name, the following guidelines should be considered:
.. adapted from notebook/static/notebook/js/actions.js
* First pick a noun and a verb for the action. For example, if the action is
"restart kernel," the verb is "restart" and the noun is "kernel".
* Omit terms like "selected" and "active" by default, so "delete-cell", rather
@ -202,10 +203,12 @@ the prefix. For the action name, the following guidelines should be considered:
Installing and enabling extensions
----------------------------------
You can install your nbextension with the command:
You can install your nbextension with the command::
jupyter nbextension install path/to/my_extension/
jupyter nbextension install path/to/my_extension/ [--user|--sys-prefix]
The default installation is system-wide. You can use ``--user`` to do a per-user installation,
or ``--sys-prefix`` to install to Python's prefix (e.g. in a virtual or conda environment).
Where my_extension is the directory containing the Javascript files.
This will copy it to a Jupyter data directory (the exact location is platform
dependent - see :ref:`jupyter_path`).
@ -214,11 +217,15 @@ For development, you can use the ``--symlink`` flag to symlink your extension
rather than copying it, so there's no need to reinstall after changes.
To use your extension, you'll also need to **enable** it, which tells the
notebook interface to load it. You can do that with another command:
notebook interface to load it. You can do that with another command::
jupyter nbextension enable my_extension/main
jupyter nbextension enable my_extension/main [--sys-prefix]
The argument refers to the Javascript module containing your
``load_ipython_extension`` function, which is ``my_extension/main.js`` in this
example. There is a corresponding ``disable`` command to stop using an
extension without uninstalling it.
.. versionchanged:: 4.2
Added ``--sys-prefix`` argument

@ -12,3 +12,4 @@ override the notebook's defaults with your own custom behavior.
contents
savehooks
handlers
frontend_extensions

@ -2,22 +2,6 @@
The Jupyter notebook
====================
.. sidebar:: What's New in Jupyter Notebook
:subtitle: Release :ref:`release-4.1.0`
`Release Announcement <https://blog.jupyter.org/2016/01/08/notebook-4-1-release/>`_
- Cell toolbar selector moved to View menu
- Restart & Run All Cells added to Kernel menu
- Multiple-cell selection and actions including cut, copy, paste and execute
- Command palette added for executing Jupyter actions
- Find and replace added to Edit menu
To upgrade to the release:
``pip install notebook --upgrade``
or
``conda upgrade notebook``
.. toctree::
:maxdepth: 1
:caption: User Documentation
@ -49,6 +33,7 @@ The Jupyter notebook
:maxdepth: 1
:caption: Community documentation
examples/Notebook/rstversions/Distributing Jupyter Extensions as Python Packages
examples/Notebook/rstversions/Examples and Tutorials Index
.. toctree::

@ -9,5 +9,5 @@ store the current version info of the notebook.
# Next beta/alpha/rc release: The version number for beta is X.Y.ZbN **without dots**.
version_info = (4, 2, 0, '.dev')
version_info = (4, 2, 3)
__version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:])

@ -39,15 +39,15 @@ class LoginHandler(IPythonHandler):
def post(self):
typed_password = self.get_argument('password', default=u'')
cookie_options = self.settings.get('cookie_options', {})
cookie_options.setdefault('httponly', True)
if self.login_available(self.settings):
if passwd_check(self.hashed_password, typed_password):
# tornado <4.2 have a bug that consider secure==True as soon as
# tornado <4.2 has a bug that considers secure==True as soon as
# 'secure' kwarg is passed to set_secure_cookie
if self.settings.get('secure_cookie', self.request.protocol == 'https'):
kwargs = {'secure': True}
else:
kwargs = {}
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()), **kwargs)
cookie_options.setdefault('secure', True)
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()), **cookie_options)
else:
self.set_status(401)
self._render(message={'error': 'Invalid password'})

@ -22,7 +22,7 @@ except ImportError:
from jinja2 import TemplateNotFound
from tornado import web
from tornado import gen
from tornado import gen, escape
from tornado.log import app_log
from notebook._sysinfo import get_sys_info
@ -389,11 +389,10 @@ class APIHandler(IPythonHandler):
self.set_header('Content-Type', 'application/json')
return super(APIHandler, self).finish(*args, **kwargs)
@web.authenticated
def options(self, *args, **kwargs):
self.set_header('Access-Control-Allow-Headers', 'accept, content-type')
self.set_header('Access-Control-Allow-Methods',
'GET, PUT, PATCH, DELETE, OPTIONS')
'GET, PUT, POST, PATCH, DELETE, OPTIONS')
self.finish()
@ -411,7 +410,7 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
if os.path.splitext(path)[1] == '.ipynb':
name = path.rsplit('/', 1)[-1]
self.set_header('Content-Type', 'application/json')
self.set_header('Content-Disposition','attachment; filename="%s"' % name)
self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name))
return web.StaticFileHandler.get(self, path)

@ -218,16 +218,23 @@ class ZMQStreamHandler(WebSocketMixin, WebSocketHandler):
self.stream.close()
def _reserialize_reply(self, msg_list, channel=None):
def _reserialize_reply(self, msg_or_list, channel=None):
"""Reserialize a reply message using JSON.
This takes the msg list from the ZMQ socket, deserializes it using
self.session and then serializes the result using JSON. This method
should be used by self._on_zmq_reply to build messages that can
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.
"""
idents, msg_list = self.session.feed_identities(msg_list)
msg = self.session.deserialize(msg_list)
if isinstance(msg_or_list, dict):
# already unpacked
msg = msg_or_list
else:
idents, msg_list = self.session.feed_identities(msg_or_list)
msg = self.session.deserialize(msg_list)
if channel:
msg['channel'] = channel
if msg['buffers']:

@ -8,7 +8,7 @@ import mimetypes
import json
import base64
from tornado import web
from tornado import web, escape
from notebook.base.handlers import IPythonHandler
@ -31,7 +31,7 @@ class FilesHandler(IPythonHandler):
model = cm.get(path, type='file')
if self.get_argument("download", False):
self.set_header('Content-Disposition','attachment; filename="%s"' % name)
self.set_header('Content-Disposition','attachment; filename="%s"' % escape.url_escape(name))
# get mimetype from filename
if name.endswith('.ipynb'):

@ -7,7 +7,8 @@ import io
import os
import zipfile
from tornado import web
from tornado import web, escape
from tornado.log import app_log
from ..base.handlers import (
IPythonHandler, FilesRedirectHandler,
@ -38,7 +39,7 @@ def respond_zip(handler, name, output, resources):
# Headers
zip_filename = os.path.splitext(name)[0] + '.zip'
handler.set_header('Content-Disposition',
'attachment; filename="%s"' % zip_filename)
'attachment; filename="%s"' % escape.url_escape(zip_filename))
handler.set_header('Content-Type', 'application/zip')
# Prepare the zip file
@ -70,6 +71,7 @@ def get_exporter(format, **kwargs):
try:
return Exporter(**kwargs)
except Exception as e:
app_log.exception("Could not construct Exporter: %s", Exporter)
raise web.HTTPError(500, "Could not construct Exporter: %s" % e)
class NbconvertFileHandler(IPythonHandler):
@ -103,6 +105,7 @@ class NbconvertFileHandler(IPythonHandler):
}
)
except Exception as e:
self.log.exception("nbconvert failed: %s", e)
raise web.HTTPError(500, "nbconvert failed: %s" % e)
if respond_zip(self, name, output, resources):
@ -112,7 +115,7 @@ class NbconvertFileHandler(IPythonHandler):
if self.get_argument('download', 'false').lower() == 'true':
filename = os.path.splitext(name)[0] + resources['output_extension']
self.set_header('Content-Disposition',
'attachment; filename="%s"' % filename)
'attachment; filename="%s"' % escape.url_escape(filename))
# MIME type
if exporter.output_mimetype:

File diff suppressed because it is too large Load Diff

@ -13,18 +13,17 @@ import importlib
import io
import json
import logging
import mimetypes
import os
import random
import re
import select
import signal
import socket
import ssl
import sys
import threading
import webbrowser
from jinja2 import Environment, FileSystemLoader
# Install the pyzmq ioloop. This has to be done before anything else from
@ -77,7 +76,7 @@ from jupyter_client.session import Session
from nbformat.sign import NotebookNotary
from traitlets import (
Dict, Unicode, Integer, List, Bool, Bytes, Instance,
TraitError, Type,
TraitError, Type, Float
)
from ipython_genutils import py3compat
from jupyter_core.paths import jupyter_runtime_dir, jupyter_path
@ -116,15 +115,6 @@ def load_handlers(name):
return mod.default_handlers
class DeprecationHandler(IPythonHandler):
def get(self, url_path):
self.set_header("Content-Type", 'text/javascript')
self.finish("""
console.warn('`/static/widgets/js` is deprecated. Use `nbextensions/widgets/widgets/js` instead.');
define(['%s'], function(x) { return x; });
""" % url_path_join('nbextensions', 'widgets', 'widgets', url_path.rstrip('.js')))
self.log.warn('Deprecated widget Javascript path /static/widgets/js/*.js was used')
#-----------------------------------------------------------------------------
# The Tornado web application
#-----------------------------------------------------------------------------
@ -187,7 +177,12 @@ class NotebookWebApplication(web.Application):
},
version_hash=version_hash,
ignore_minified_js=ipython_app.ignore_minified_js,
# rate limits
iopub_msg_rate_limit=ipython_app.iopub_msg_rate_limit,
iopub_data_rate_limit=ipython_app.iopub_data_rate_limit,
rate_limit_window=ipython_app.rate_limit_window,
# authentication
cookie_secret=ipython_app.cookie_secret,
login_url=url_path_join(base_url,'/login'),
@ -222,7 +217,6 @@ class NotebookWebApplication(web.Application):
# Order matters. The first handler to match the URL will handle the request.
handlers = []
handlers.append((r'/deprecatedwidgets/(.*)', DeprecationHandler))
handlers.extend(load_handlers('tree.handlers'))
handlers.extend([(r"/login", settings['login_handler_class'])])
handlers.extend([(r"/logout", settings['logout_handler_class'])])
@ -241,16 +235,20 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('services.security.handlers'))
# BEGIN HARDCODED WIDGETS HACK
# TODO: Remove on notebook 5.0
try:
import ipywidgets
handlers.append(
(r"/nbextensions/widgets/(.*)", FileFindHandler, {
'path': ipywidgets.find_static_assets(),
'no_cache_paths': ['/'], # don't cache anything in nbextensions
}),
)
import widgetsnbextension
except:
app_log.warn('ipywidgets package not installed. Widgets are unavailable.')
try:
import ipywidgets as widgets
handlers.append(
(r"/nbextensions/widgets/(.*)", FileFindHandler, {
'path': widgets.find_static_assets(),
'no_cache_paths': ['/'], # don't cache anything in nbextensions
}),
)
except:
app_log.warning('Widgets are unavailable. Please install widgetsnbextension or ipywidgets 4.0')
# END HARDCODED WIDGETS HACK
handlers.append(
@ -473,7 +471,7 @@ class NotebookApp(JupyterApp):
)
client_ca = Unicode(u'', config=True,
help="""The full path to a certificate authority certifificate for SSL/TLS client authentication."""
help="""The full path to a certificate authority certificate for SSL/TLS client authentication."""
)
cookie_secret_file = Unicode(config=True,
@ -551,6 +549,10 @@ class NotebookApp(JupyterApp):
help="Supply overrides for the tornado.web.Application that the "
"Jupyter notebook uses.")
cookie_options = Dict(config=True,
help="Extra keyword arguments to pass to `set_secure_cookie`."
" See tornado's set_secure_cookie docs for details."
)
ssl_options = Dict(config=True,
help="""Supply SSL options for the tornado HTTPServer.
See the tornado docs for details.""")
@ -759,6 +761,11 @@ class NotebookApp(JupyterApp):
def _notebook_dir_validate(self, value, trait):
# Strip any trailing slashes
# *except* if it's root
_, path = os.path.splitdrive(value)
if path == os.sep:
return value
value = value.rstrip(os.sep)
if not os.path.isabs(value):
@ -774,9 +781,18 @@ class NotebookApp(JupyterApp):
self.config.FileContentsManager.root_dir = new
self.config.MappingKernelManager.root_dir = new
# TODO: Remove me in notebook 5.0
server_extensions = List(Unicode(), config=True,
help=("Python modules to load as notebook server extensions. "
"This is an experimental API, and may change in future releases.")
help=("DEPRECATED use the nbserver_extensions dict instead")
)
def _server_extensions_changed(self, name, old, new):
self.log.warning("server_extensions is deprecated, use nbserver_extensions")
self.server_extensions = new
nbserver_extensions = Dict({}, config=True,
help=("Dict of Python modules to load as notebook server extensions."
"Entry values can be used to enable and disable the loading of"
"the extensions.")
)
reraise_server_extension_failures = Bool(
@ -785,9 +801,20 @@ class NotebookApp(JupyterApp):
help="Reraise exceptions encountered loading server extensions?",
)
iopub_msg_rate_limit = Float(0, config=True, help="""(msg/sec)
Maximum rate at which messages can be sent on iopub before they are
limited.""")
iopub_data_rate_limit = Float(0, config=True, help="""(bytes/sec)
Maximum rate at which messages can be sent on iopub before they are
limited.""")
rate_limit_window = Float(1.0, config=True, help="""(sec) Time window used to
check the message and data rate limits.""")
def parse_command_line(self, argv=None):
super(NotebookApp, self).parse_command_line(argv)
if self.extra_args:
arg0 = self.extra_args[0]
f = os.path.abspath(arg0)
@ -852,6 +879,7 @@ class NotebookApp(JupyterApp):
if self.allow_origin_pat:
self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
self.tornado_settings['allow_credentials'] = self.allow_credentials
self.tornado_settings['cookie_options'] = self.cookie_options
# ensure default_url starts with base_url
if not self.default_url.startswith(self.base_url):
self.default_url = url_path_join(self.base_url, self.default_url)
@ -870,13 +898,17 @@ class NotebookApp(JupyterApp):
ssl_options['keyfile'] = self.keyfile
if self.client_ca:
ssl_options['ca_certs'] = self.client_ca
ssl_options['cert_reqs'] = ssl.CERT_REQUIRED
if not ssl_options:
# None indicates no SSL config
ssl_options = None
else:
# Disable SSLv3, since its use is discouraged.
ssl_options['ssl_version']=ssl.PROTOCOL_TLSv1
# SSL may be missing, so only import it if it's to be used
import ssl
# Disable SSLv3 by default, since its use is discouraged.
ssl_options.setdefault('ssl_version', ssl.PROTOCOL_TLSv1)
if ssl_options.get('ca_certs', False):
ssl_options.setdefault('cert_reqs', ssl.CERT_REQUIRED)
self.login_handler_class.validate_security(self, ssl_options=ssl_options)
self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
xheaders=self.trust_xheaders)
@ -887,7 +919,7 @@ class NotebookApp(JupyterApp):
self.http_server.listen(port, self.ip)
except socket.error as e:
if e.errno == errno.EADDRINUSE:
self.log.info('The port %i is already in use, trying another random port.' % port)
self.log.info('The port %i is already in use, trying another port.' % port)
continue
elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
self.log.warn("Permission to listen on port %i denied" % port)
@ -1000,18 +1032,34 @@ class NotebookApp(JupyterApp):
The extension API is experimental, and may change in future releases.
"""
# TODO: Remove me in notebook 5.0
for modulename in self.server_extensions:
try:
mod = importlib.import_module(modulename)
func = getattr(mod, 'load_jupyter_server_extension', None)
if func is not None:
func(self)
except Exception:
if self.reraise_server_extension_failures:
raise
self.log.warn("Error loading server extension %s", modulename,
exc_info=True)
# Don't override disable state of the extension if it already exist
# in the new traitlet
if not modulename in self.nbserver_extensions:
self.nbserver_extensions[modulename] = True
for modulename in self.nbserver_extensions:
if self.nbserver_extensions[modulename]:
try:
mod = importlib.import_module(modulename)
func = getattr(mod, 'load_jupyter_server_extension', None)
if func is not None:
func(self)
except Exception:
if self.reraise_server_extension_failures:
raise
self.log.warning("Error loading server extension %s", modulename,
exc_info=True)
def init_mime_overrides(self):
# On some Windows machines, an application has registered an incorrect
# mimetype for CSS in the registry. Tornado uses this when serving
# .css files, causing browsers to reject the stylesheet. We know the
# mimetype always needs to be text/css, so we override it here.
mimetypes.add_type('text/css', '.css')
@catch_config_error
def initialize(self, argv=None):
super(NotebookApp, self).initialize(argv)
@ -1024,6 +1072,7 @@ class NotebookApp(JupyterApp):
self.init_terminals()
self.init_signal()
self.init_server_extensions()
self.init_mime_overrides()
def cleanup_kernels(self):
"""Shutdown all kernels.
@ -1096,7 +1145,8 @@ class NotebookApp(JupyterApp):
relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep)))
else:
uri = self.default_url
# default_url contains base_url, but so does connection_url
uri = self.default_url[len(self.base_url):]
if browser:
b = lambda : browser.open(url_path_join(self.connection_url, uri),
new=2)

@ -0,0 +1,341 @@
# coding: utf-8
"""Utilities for installing server extensions for the notebook"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import print_function
import importlib
import sys
from jupyter_core.paths import jupyter_config_path
from ._version import __version__
from .nbextensions import (
JupyterApp, BaseNBExtensionApp, _get_config_dir,
GREEN_ENABLED, RED_DISABLED,
GREEN_OK, RED_X,
)
from traitlets import Bool
from traitlets.utils.importstring import import_item
from traitlets.config.manager import BaseJSONConfigManager
# ------------------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------------------
class ArgumentConflict(ValueError):
pass
def toggle_serverextension_python(import_name, enabled=None, parent=None,
user=True, sys_prefix=False, logger=None):
"""Toggle a server extension.
By default, toggles the extension in the system-wide Jupyter configuration
location (e.g. /usr/local/etc/jupyter).
Parameters
----------
import_name : str
Importable Python module (dotted-notation) exposing the magic-named
`load_jupyter_server_extension` function
enabled : bool [default: None]
Toggle state for the extension. Set to None to toggle, True to enable,
and False to disable the extension.
parent : Configurable [default: None]
user : bool [default: True]
Toggle in the user's configuration location (e.g. ~/.jupyter).
sys_prefix : bool [default: False]
Toggle in the current Python environment's configuration location
(e.g. ~/.envs/my-env/etc/jupyter). Will override `user`.
logger : Jupyter logger [optional]
Logger instance to use
"""
user = False if sys_prefix else user
config_dir = _get_config_dir(user=user, sys_prefix=sys_prefix)
cm = BaseJSONConfigManager(parent=parent, config_dir=config_dir)
cfg = cm.get("jupyter_notebook_config")
server_extensions = (
cfg.setdefault("NotebookApp", {})
.setdefault("nbserver_extensions", {})
)
old_enabled = server_extensions.get(import_name, None)
new_enabled = enabled if enabled is not None else not old_enabled
if logger:
if new_enabled:
logger.info(u"Enabling: %s" % (import_name))
else:
logger.info(u"Disabling: %s" % (import_name))
server_extensions[import_name] = new_enabled
if logger:
logger.info(u"- Writing config: {}".format(config_dir))
cm.update("jupyter_notebook_config", cfg)
if new_enabled:
validate_serverextension(import_name, logger)
def validate_serverextension(import_name, logger=None):
"""Assess the health of an installed server extension
Returns a list of validation warnings.
Parameters
----------
import_name : str
Importable Python module (dotted-notation) exposing the magic-named
`load_jupyter_server_extension` function
logger : Jupyter logger [optional]
Logger instance to use
"""
warnings = []
infos = []
func = None
if logger:
logger.info(" - Validating...")
try:
mod = importlib.import_module(import_name)
func = getattr(mod, 'load_jupyter_server_extension', None)
except Exception:
logger.warning("Error loading server extension %s", import_name)
import_msg = u" {} is {} importable?"
if func is not None:
infos.append(import_msg.format(GREEN_OK, import_name))
else:
warnings.append(import_msg.format(RED_X, import_name))
post_mortem = u" {} {} {}"
if logger:
if warnings:
[logger.info(info) for info in infos]
[logger.warn(warning) for warning in warnings]
else:
logger.info(post_mortem.format(import_name, "", GREEN_OK))
return warnings
# ----------------------------------------------------------------------
# Applications
# ----------------------------------------------------------------------
flags = {}
flags.update(JupyterApp.flags)
flags.pop("y", None)
flags.pop("generate-config", None)
flags.update({
"user" : ({
"ToggleServerExtensionApp" : {
"user" : True,
}}, "Perform the operation for the current user"
),
"system" : ({
"ToggleServerExtensionApp" : {
"user" : False,
"sys_prefix": False,
}}, "Perform the operation system-wide"
),
"sys-prefix" : ({
"ToggleServerExtensionApp" : {
"sys_prefix" : True,
}}, "Use sys.prefix as the prefix for installing server extensions"
),
"py" : ({
"ToggleServerExtensionApp" : {
"python" : True,
}}, "Install from a Python package"
),
})
flags['python'] = flags['py']
class ToggleServerExtensionApp(BaseNBExtensionApp):
"""A base class for enabling/disabling extensions"""
name = "jupyter serverextension enable/disable"
description = "Enable/disable a server extension using frontend configuration files."
aliases = {}
flags = flags
user = Bool(True, config=True, help="Whether to do a user install")
sys_prefix = Bool(False, config=True, help="Use the sys.prefix as the prefix")
python = Bool(False, config=True, help="Install from a Python package")
def toggle_server_extension(self, import_name):
"""Change the status of a named server extension.
Uses the value of `self._toggle_value`.
Parameters
---------
import_name : str
Importable Python module (dotted-notation) exposing the magic-named
`load_jupyter_server_extension` function
"""
toggle_serverextension_python(
import_name, self._toggle_value, parent=self, user=self.user,
sys_prefix=self.sys_prefix, logger=self.log)
def toggle_server_extension_python(self, package):
"""Change the status of some server extensions in a Python package.
Uses the value of `self._toggle_value`.
Parameters
---------
package : str
Importable Python module exposing the
magic-named `_jupyter_server_extension_paths` function
"""
m, server_exts = _get_server_extension_metadata(package)
for server_ext in server_exts:
module = server_ext['module']
self.toggle_server_extension(module)
def start(self):
"""Perform the App's actions as configured"""
if not self.extra_args:
sys.exit('Please specify a server extension/package to enable or disable')
for arg in self.extra_args:
if self.python:
self.toggle_server_extension_python(arg)
else:
self.toggle_server_extension(arg)
class EnableServerExtensionApp(ToggleServerExtensionApp):
"""An App that enables (and validates) Server Extensions"""
name = "jupyter serverextension enable"
description = """
Enable a serverextension in configuration.
Usage
jupyter serverextension enable [--system|--sys-prefix]
"""
_toggle_value = True
class DisableServerExtensionApp(ToggleServerExtensionApp):
"""An App that disables Server Extensions"""
name = "jupyter serverextension disable"
description = """
Disable a serverextension in configuration.
Usage
jupyter serverextension disable [--system|--sys-prefix]
"""
_toggle_value = False
class ListServerExtensionsApp(BaseNBExtensionApp):
"""An App that lists (and validates) Server Extensions"""
name = "jupyter serverextension list"
version = __version__
description = "List all server extensions known by the configuration system"
def list_server_extensions(self):
"""List all enabled and disabled server extensions, by config path
Enabled extensions are validated, potentially generating warnings.
"""
config_dirs = jupyter_config_path()
for config_dir in config_dirs:
cm = BaseJSONConfigManager(parent=self, config_dir=config_dir)
data = cm.get("jupyter_notebook_config")
server_extensions = (
data.setdefault("NotebookApp", {})
.setdefault("nbserver_extensions", {})
)
if server_extensions:
print(u'config dir: {}'.format(config_dir))
for import_name, enabled in server_extensions.items():
print(u' {} {}'.format(
import_name,
GREEN_ENABLED if enabled else RED_DISABLED))
validate_serverextension(import_name, self.log)
def start(self):
"""Perform the App's actions as configured"""
self.list_server_extensions()
_examples = """
jupyter serverextension list # list all configured server extensions
jupyter serverextension enable --py <packagename> # enable all server extensions in a Python package
jupyter serverextension disable --py <packagename> # disable all server extensions in a Python package
"""
class ServerExtensionApp(BaseNBExtensionApp):
"""Root level server extension app"""
name = "jupyter serverextension"
version = __version__
description = "Work with Jupyter server extensions"
examples = _examples
subcommands = dict(
enable=(EnableServerExtensionApp, "Enable an server extension"),
disable=(DisableServerExtensionApp, "Disable an server extension"),
list=(ListServerExtensionsApp, "List server extensions")
)
def start(self):
"""Perform the App's actions as configured"""
super(ServerExtensionApp, self).start()
# The above should have called a subcommand and raised NoStart; if we
# get here, it didn't, so we should self.log.info a message.
subcmds = ", ".join(sorted(self.subcommands))
sys.exit("Please supply at least one subcommand: %s" % subcmds)
main = ServerExtensionApp.launch_instance
# ------------------------------------------------------------------------------
# Private API
# ------------------------------------------------------------------------------
def _get_server_extension_metadata(module):
"""Load server extension metadata from a module.
Returns a tuple of (
the package as loaded
a list of server extension specs: [
{
"module": "mockextension"
}
]
)
Parameters
----------
module : str
Importable Python module exposing the
magic-named `_jupyter_server_extension_paths` function
"""
m = import_item(module)
if not hasattr(m, '_jupyter_server_extension_paths'):
raise KeyError(u'The Python module {} does not include any valid server extensions'.format(module))
return m, m._jupyter_server_extension_paths()
if __name__ == '__main__':
main()

@ -5,13 +5,47 @@
import os.path
from traitlets.config.manager import BaseJSONConfigManager
from jupyter_core.paths import jupyter_config_dir
from traitlets import Unicode
from traitlets.config.manager import BaseJSONConfigManager, recursive_update
from jupyter_core.paths import jupyter_config_dir, jupyter_config_path
from traitlets import Unicode, Instance, List
from traitlets.config import LoggingConfigurable
class ConfigManager(BaseJSONConfigManager):
class ConfigManager(LoggingConfigurable):
"""Config Manager used for storing notebook frontend config"""
config_dir = Unicode(config=True)
def _config_dir_default(self):
# Public API
def get(self, section_name):
"""Get the config from all config sections."""
config = {}
# step through back to front, to ensure front of the list is top priority
for p in self.read_config_path[::-1]:
cm = BaseJSONConfigManager(config_dir=p)
recursive_update(config, cm.get(section_name))
return config
def set(self, section_name, data):
"""Set the config only to the user's config."""
return self.write_config_manager.set(section_name, data)
def update(self, section_name, new_data):
"""Update the config only to the user's config."""
return self.write_config_manager.update(section_name, new_data)
# Private API
read_config_path = List(Unicode())
def _read_config_path_default(self):
return [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
write_config_dir = Unicode()
def _write_config_dir_default(self):
return os.path.join(jupyter_config_dir(), 'nbconfig')
write_config_manager = Instance(BaseJSONConfigManager)
def _write_config_manager_default(self):
return BaseJSONConfigManager(config_dir=self.write_config_dir)
def _write_config_dir_changed(self, name, old, new):
self.write_config_manager = BaseJSONConfigManager(config_dir=self.write_config_dir)

@ -7,6 +7,7 @@
import io
import os
import shutil
import warnings
import mimetypes
import nbformat
@ -32,9 +33,10 @@ _script_exporter = None
def _post_save_script(model, os_path, contents_manager, **kwargs):
"""convert notebooks to Python script after save with nbconvert
replaces `ipython notebook --script`
replaces `jupyter notebook --script`
"""
from nbconvert.exporters.script import ScriptExporter
warnings.warn("`_post_save_script` is deprecated and will be removed in Notebook 5.0", DeprecationWarning)
if model['type'] != 'notebook':
return
@ -62,17 +64,19 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
except AttributeError:
return getcwd()
save_script = Bool(False, config=True, help='DEPRECATED, use post_save_hook')
save_script = Bool(False, config=True, help='DEPRECATED, use post_save_hook. Will be removed in Notebook 5.0')
def _save_script_changed(self):
self.log.warn("""
`--script` is deprecated. You can trigger nbconvert via pre- or post-save hooks:
`--script` is deprecated and will be removed in notebook 5.0.
You can trigger nbconvert via pre- or post-save hooks:
ContentsManager.pre_save_hook
FileContentsManager.post_save_hook
A post-save hook has been registered that calls:
ipython nbconvert --to script [notebook]
jupyter nbconvert --to script [notebook]
which behaves similarly to `--script`.
""")

@ -16,7 +16,7 @@ from jupyter_client.jsonutil import date_default
from ipython_genutils.py3compat import cast_unicode
from notebook.utils import url_path_join, url_escape
from ...base.handlers import IPythonHandler, APIHandler, json_errors
from ...base.handlers import APIHandler, json_errors
from ...base.zmqhandlers import AuthenticatedZMQStreamHandler, deserialize_binary_message
from jupyter_client import protocol_version as client_protocol_version
@ -97,13 +97,30 @@ class KernelActionHandler(APIHandler):
class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
# class-level registry of open sessions
# allows checking for conflict on session-id,
# which is used as a zmq identity and must be unique.
_open_sessions = {}
@property
def kernel_info_timeout(self):
return self.settings.get('kernel_info_timeout', 10)
@property
def iopub_msg_rate_limit(self):
return self.settings.get('iopub_msg_rate_limit', None)
@property
def iopub_data_rate_limit(self):
return self.settings.get('iopub_data_rate_limit', None)
@property
def rate_limit_window(self):
return self.settings.get('rate_limit_window', 1.0)
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
def create_stream(self):
km = self.kernel_manager
identity = self.session.bsession
@ -182,11 +199,25 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
self.kernel_id = None
self.kernel_info_channel = None
self._kernel_info_future = Future()
self._close_future = Future()
self.session_key = ''
# Rate limiting code
self._iopub_window_msg_count = 0
self._iopub_window_byte_count = 0
self._iopub_msgs_exceeded = False
self._iopub_data_exceeded = False
# Queue of (time stamp, byte count)
# Allows you to specify that the byte count should be lowered
# by a delta amount at some point in the future.
self._iopub_window_byte_queue = []
@gen.coroutine
def pre_get(self):
# authenticate first
super(ZMQChannelsHandler, self).pre_get()
# check session collision:
yield self._register_session()
# then request kernel info, waiting up to a certain time before giving up.
# We don't want to wait forever, because browsers don't take it well when
# servers never respond to websocket connection requests.
@ -210,6 +241,21 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
self.kernel_id = cast_unicode(kernel_id, 'ascii')
yield super(ZMQChannelsHandler, self).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.
"""
self.session_key = '%s:%s' % (self.kernel_id, self.session.session)
stale_handler = self._open_sessions.get(self.session_key)
if stale_handler:
self.log.warning("Replacing stale connection: %s", self.session_key)
yield stale_handler.close()
self._open_sessions[self.session_key] = self
def open(self, kernel_id):
super(ZMQChannelsHandler, self).open()
try:
@ -244,8 +290,97 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
return
stream = self.channels[channel]
self.session.send(stream, msg)
def _on_zmq_reply(self, stream, msg_list):
idents, fed_msg_list = self.session.feed_identities(msg_list)
msg = self.session.deserialize(fed_msg_list)
parent = msg['parent_header']
def write_stderr(error_message):
self.log.warn(error_message)
msg = self.session.msg("stream",
content={"text": error_message, "name": "stderr"},
parent=parent
)
msg['channel'] = 'iopub'
self.write_message(json.dumps(msg, default=date_default))
channel = getattr(stream, 'channel', None)
msg_type = msg['header']['msg_type']
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:
queued = self._iopub_window_byte_queue[0]
if (now >= queued[0]):
self._iopub_window_byte_count -= queued[1]
self._iopub_window_msg_count -= 1
del self._iopub_window_byte_queue[0]
else:
# This part of the queue hasn't be reached yet, so we can
# abort the loop.
break
# Increment the bytes and message count
self._iopub_window_msg_count += 1
byte_count = sum([len(x) for x in msg_list])
self._iopub_window_byte_count += byte_count
# 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 is not None and msg_rate > self.iopub_msg_rate_limit and self.iopub_msg_rate_limit > 0:
if not self._iopub_msgs_exceeded:
self._iopub_msgs_exceeded = True
write_stderr("""iopub message rate exceeded. The
notebook server will temporarily stop sending iopub
messages to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_msg_rate_limit`.""")
return
else:
if self._iopub_msgs_exceeded:
self._iopub_msgs_exceeded = False
if not self._iopub_data_exceeded:
self.log.warn("iopub messages resumed")
# Check the data rate
if self.iopub_data_rate_limit is not None and data_rate > self.iopub_data_rate_limit and self.iopub_data_rate_limit > 0:
if not self._iopub_data_exceeded:
self._iopub_data_exceeded = True
write_stderr("""iopub data rate exceeded. The
notebook server will temporarily stop sending iopub
messages to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_data_rate_limit`.""")
return
else:
if self._iopub_data_exceeded:
self._iopub_data_exceeded = False
if not self._iopub_msgs_exceeded:
self.log.warn("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:
return
super(ZMQChannelsHandler, self)._on_zmq_reply(stream, msg)
def close(self):
super(ZMQChannelsHandler, self).close()
return self._close_future
def on_close(self):
self.log.debug("Websocket closed %s", self.session_key)
# unregister myself as an open session (only if it's really me)
if self._open_sessions.get(self.session_key) is self:
self._open_sessions.pop(self.session_key)
km = self.kernel_manager
if self.kernel_id in km:
km.remove_restart_callback(
@ -266,6 +401,7 @@ class ZMQChannelsHandler(AuthenticatedZMQStreamHandler):
socket.close()
self.channels = {}
self._close_future.set_result(None)
def _send_status_message(self, status):
msg = self.session.msg("status",

@ -115,7 +115,7 @@ class MappingKernelManager(MultiKernelManager):
def finish():
"""Common cleanup when restart finishes/fails for any reason."""
if not channel.closed:
if not channel.closed():
channel.close()
loop.remove_timeout(timeout)
kernel.remove_restart_callback(on_restart_failed, 'dead')

@ -42,10 +42,13 @@ class SessionRootHandler(APIHandler):
path = model['notebook']['path']
except KeyError:
raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
try:
kernel_name = model['kernel']['name']
except KeyError:
self.log.debug("No kernel name specified, using default kernel")
kernel = model.get('kernel', {})
kernel_name = kernel.get('name') or None
kernel_id = kernel.get('id') or None
if not kernel_id and not kernel_name:
self.log.debug("No kernel specified, using default kernel")
kernel_name = None
# Check to see if session exists
@ -55,7 +58,8 @@ class SessionRootHandler(APIHandler):
else:
try:
model = yield gen.maybe_future(
sm.create_session(path=path, kernel_name=kernel_name))
sm.create_session(path=path, kernel_name=kernel_name,
kernel_id=kernel_id))
except NoSuchKernel:
msg = ("The '%s' kernel is not available. Please pick another "
"suitable kernel instead, or install that kernel." % kernel_name)
@ -86,19 +90,47 @@ class SessionHandler(APIHandler):
@json_errors
@gen.coroutine
def patch(self, session_id):
# Currently, this handler is strictly for renaming notebooks
"""Patch updates sessions:
- notebook.path updates session to track renamed notebooks
- kernel.name starts a new kernel with a given kernelspec
"""
sm = self.session_manager
km = self.kernel_manager
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, "No JSON data provided")
# get the previous session model
before = yield gen.maybe_future(sm.get_session(session_id=session_id))
changes = {}
if 'notebook' in model:
notebook = model['notebook']
if 'path' in notebook:
if notebook.get('path') is not None:
changes['path'] = notebook['path']
if 'kernel' in model:
# Kernel id takes precedence over name.
if model['kernel'].get('id') is not None:
kernel_id = model['kernel']['id']
if kernel_id not in km:
raise web.HTTPError(400, "No such kernel: %s" % kernel_id)
changes['kernel_id'] = kernel_id
elif model['kernel'].get('name') is not None:
kernel_name = model['kernel']['name']
kernel_id = yield sm.start_kernel_for_session(
session_id, kernel_name=kernel_name, path=before['notebook']['path'])
changes['kernel_id'] = kernel_id
yield gen.maybe_future(sm.update_session(session_id, **changes))
model = yield gen.maybe_future(sm.get_session(session_id=session_id))
if model['kernel']['id'] != before['kernel']['id']:
# kernel_id changed because we got a new kernel
# shutdown the old one
yield gen.maybe_future(
km.shutdown_kernel(before['kernel']['id'])
)
self.finish(json.dumps(model, default=date_default))
@web.authenticated

@ -4,7 +4,12 @@
# Distributed under the terms of the Modified BSD License.
import uuid
import sqlite3
try:
import sqlite3
except ImportError:
# fallback on pysqlite2 if Python was build without sqlite
from pysqlite2 import dbapi2 as sqlite3
from tornado import gen, web
@ -62,22 +67,33 @@ class SessionManager(LoggingConfigurable):
def new_session_id(self):
"Create a uuid for a new session"
return unicode_type(uuid.uuid4())
@gen.coroutine
def create_session(self, path=None, kernel_name=None):
def create_session(self, path=None, kernel_name=None, kernel_id=None):
"""Creates a session and returns its model"""
session_id = self.new_session_id()
# allow nbm to specify kernels cwd
kernel_path = self.contents_manager.get_kernel_path(path=path)
kernel_id = yield gen.maybe_future(
self.kernel_manager.start_kernel(path=kernel_path, kernel_name=kernel_name)
)
if kernel_id is not None and kernel_id in self.kernel_manager:
pass
else:
kernel_id = yield self.start_kernel_for_session(session_id, path,
kernel_name)
result = yield gen.maybe_future(
self.save_session(session_id, path=path, kernel_id=kernel_id)
)
# py2-compat
raise gen.Return(result)
@gen.coroutine
def start_kernel_for_session(self, session_id, path, kernel_name):
"""Start a new kernel for a given session."""
# allow contents manager to specify kernels cwd
kernel_path = self.contents_manager.get_kernel_path(path=path)
kernel_id = yield gen.maybe_future(
self.kernel_manager.start_kernel(path=kernel_path, kernel_name=kernel_name)
)
# py2-compat
raise gen.Return(kernel_id)
def save_session(self, session_id, path=None, kernel_id=None):
"""Saves the items for the session with the given session_id
@ -211,9 +227,9 @@ class SessionManager(LoggingConfigurable):
pass
return result
@gen.coroutine
def delete_session(self, session_id):
"""Deletes the row in the session database with given session_id"""
# Check that session exists before deleting
session = self.get_session(session_id=session_id)
self.kernel_manager.shutdown_kernel(session['kernel']['id'])
yield gen.maybe_future(self.kernel_manager.shutdown_kernel(session['kernel']['id']))
self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))

@ -175,6 +175,8 @@ class TestSessionManager(TestCase):
# try to delete a session that doesn't exist ~ raise error
sm = self.sm
self.create_session(path='/path/to/test.ipynb', kernel_name='python')
self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
with self.assertRaises(TypeError):
self.loop.run_sync(lambda : sm.delete_session(bad_kwarg='23424')) # Bad keyword
with self.assertRaises(web.HTTPError):
self.loop.run_sync(lambda : sm.delete_session(session_id='23424')) # nonexistent

@ -39,15 +39,25 @@ class SessionAPI(object):
def get(self, id):
return self._req('GET', id)
def create(self, path, kernel_name='python'):
def create(self, path, kernel_name='python', kernel_id=None):
body = json.dumps({'notebook': {'path':path},
'kernel': {'name': kernel_name}})
'kernel': {'name': kernel_name,
'id': kernel_id}})
return self._req('POST', '', body)
def modify(self, id, path):
def modify_path(self, id, path):
body = json.dumps({'notebook': {'path':path}})
return self._req('PATCH', id, body)
def modify_kernel_name(self, id, kernel_name):
body = json.dumps({'kernel': {'name': kernel_name}})
return self._req('PATCH', id, body)
def modify_kernel_id(self, id, kernel_id):
# Also send a dummy name to show that id takes precedence.
body = json.dumps({'kernel': {'id': kernel_id, 'name': 'foo'}})
return self._req('PATCH', id, body)
def delete(self, id):
return self._req('DELETE', id)
@ -102,6 +112,28 @@ class SessionAPITest(NotebookTestBase):
got = self.sess_api.get(sid).json()
self.assertEqual(got, newsession)
def test_create_with_kernel_id(self):
# create a new kernel
r = requests.post(url_path_join(self.base_url(), 'api/kernels'))
r.raise_for_status()
kernel = r.json()
resp = self.sess_api.create('foo/nb1.ipynb', kernel_id=kernel['id'])
self.assertEqual(resp.status_code, 201)
newsession = resp.json()
self.assertIn('id', newsession)
self.assertEqual(newsession['notebook']['path'], 'foo/nb1.ipynb')
self.assertEqual(newsession['kernel']['id'], kernel['id'])
self.assertEqual(resp.headers['Location'], self.url_prefix + 'api/sessions/{0}'.format(newsession['id']))
sessions = self.sess_api.list().json()
self.assertEqual(sessions, [newsession])
# Retrieve it
sid = newsession['id']
got = self.sess_api.get(sid).json()
self.assertEqual(got, newsession)
def test_delete(self):
newsession = self.sess_api.create('foo/nb1.ipynb').json()
sid = newsession['id']
@ -115,10 +147,47 @@ class SessionAPITest(NotebookTestBase):
with assert_http_error(404):
self.sess_api.get(sid)
def test_modify(self):
def test_modify_path(self):
newsession = self.sess_api.create('foo/nb1.ipynb').json()
sid = newsession['id']
changed = self.sess_api.modify(sid, 'nb2.ipynb').json()
changed = self.sess_api.modify_path(sid, 'nb2.ipynb').json()
self.assertEqual(changed['id'], sid)
self.assertEqual(changed['notebook']['path'], 'nb2.ipynb')
def test_modify_kernel_name(self):
before = self.sess_api.create('foo/nb1.ipynb').json()
sid = before['id']
after = self.sess_api.modify_kernel_name(sid, before['kernel']['name']).json()
self.assertEqual(after['id'], sid)
self.assertEqual(after['notebook'], before['notebook'])
self.assertNotEqual(after['kernel']['id'], before['kernel']['id'])
# check kernel list, to be sure previous kernel was cleaned up
r = requests.get(url_path_join(self.base_url(), 'api/kernels'))
r.raise_for_status()
kernel_list = r.json()
self.assertEqual(kernel_list, [after['kernel']])
def test_modify_kernel_id(self):
before = self.sess_api.create('foo/nb1.ipynb').json()
sid = before['id']
# create a new kernel
r = requests.post(url_path_join(self.base_url(), 'api/kernels'))
r.raise_for_status()
kernel = r.json()
# Attach our session to the existing kernel
after = self.sess_api.modify_kernel_id(sid, kernel['id']).json()
self.assertEqual(after['id'], sid)
self.assertEqual(after['notebook'], before['notebook'])
self.assertNotEqual(after['kernel']['id'], before['kernel']['id'])
self.assertEqual(after['kernel']['id'], kernel['id'])
# check kernel list, to be sure previous kernel was cleaned up
r = requests.get(url_path_join(self.base_url(), 'api/kernels'))
r.raise_for_status()
kernel_list = r.json()
self.assertEqual(kernel_list, [kernel])

@ -73,7 +73,7 @@ define(function(){
// tree
jglobal('SessionList','tree/js/sessionlist');
Jupyter.version = "4.2.0.dev";
Jupyter.version = "4.2.3";
Jupyter._target = '_blank';
return Jupyter;
});

@ -13,6 +13,16 @@ define([
// keep track of which extensions have been loaded already
var extensions_loaded = [];
/**
* Whether or not an extension has been loaded
* @param {string} extension - name of the extension
* @return {boolean} true if loaded already
*/
var is_loaded = function(extension) {
var ext_path = "nbextensions/" + extension;
return extensions_loaded.indexOf(ext_path) >= 0;
};
/**
* Load a single extension.
* @param {string} extension - extension path.
@ -21,17 +31,17 @@ define([
var load_extension = function (extension) {
return new Promise(function(resolve, reject) {
var ext_path = "nbextensions/" + extension;
require([ext_path], function(module) {
try {
if (extensions_loaded.indexOf(ext_path) < 0) {
console.log("Loading extension: " + extension);
module.load_ipython_extension();
extensions_loaded.push(ext_path);
}
else{
console.log("Loaded extension already: " + extension);
requirejs([ext_path], function(module) {
if (!is_loaded(extension)) {
console.log("Loading extension: " + extension);
if (module.load_ipython_extension) {
Promise.resolve(module.load_ipython_extension()).then(function() {
resolve(module);
}).catch(reject);
}
} finally {
extensions_loaded.push(ext_path);
} else {
console.log("Loaded extension already: " + extension);
resolve(module);
}
}, function(err) {
@ -46,23 +56,38 @@ define([
* @return {Promise} that resolves to a list of loaded module handles.
*/
var load_extensions = function () {
console.log("load_extensions", arguments);
return Promise.all(Array.prototype.map.call(arguments, load_extension)).catch(function(err) {
console.error("Failed to load extension" + (err.requireModules.length>1?'s':'') + ":", err.requireModules, err);
});
};
/**
* Return a list of extensions that should be active
* The config for nbextensions comes in as a dict where keys are
* nbextensions paths and the values are a bool indicating if it
* should be active. This returns a list of nbextension paths
* where the value is true
*/
function filter_extensions(nbext_config) {
var active = [];
Object.keys(nbext_config).forEach(function (nbext) {
if (nbext_config[nbext]) {active.push(nbext);}
});
return active;
}
/**
* Wait for a config section to load, and then load the extensions specified
* in a 'load_extensions' key inside it.
*/
function load_extensions_from_config(section) {
section.loaded.then(function() {
return section.loaded.then(function() {
if (section.data.load_extensions) {
var nbextension_paths = Object.getOwnPropertyNames(
section.data.load_extensions);
load_extensions.apply(this, nbextension_paths);
var active = filter_extensions(section.data.load_extensions);
return load_extensions.apply(this, active);
}
});
}).catch(utils.reject('Could not load nbextensions from ' + section.section_name + ' config file'));
}
//============================================================================
@ -826,8 +851,10 @@ define([
};
var utils = {
is_loaded: is_loaded,
load_extension: load_extension,
load_extensions: load_extensions,
filter_extensions: filter_extensions,
load_extensions_from_config: load_extensions_from_config,
regex_split : regex_split,
uuid : uuid,

@ -116,7 +116,8 @@ function($,
if (ext_idx > 0) {
// CodeMirror.findModeByExtension wants extension without '.'
modeinfo = CodeMirror.findModeByExtension(model.name.slice(ext_idx + 1));
modeinfo = CodeMirror.findModeByExtension(
model.name.slice(ext_idx + 1).toLowerCase());
}
}
if (modeinfo) {

@ -13,7 +13,7 @@ require([
'edit/js/menubar',
'edit/js/savewidget',
'edit/js/notificationarea',
'custom/custom',
'custom',
], function(
$,
IPython,
@ -28,6 +28,7 @@ require([
notificationarea
){
"use strict";
page = new page.Page();
var base_url = utils.get_body_data('baseUrl');

@ -114,6 +114,11 @@ define([
});
// View
this.element.find('#toggle_header').click(function (){
$("#header-container").toggle();
});
this.element.find('#menu-line-numbers').click(function () {
var current = editor.codemirror.getOption('lineNumbers');
var value = Boolean(1-current);

@ -9,19 +9,27 @@ require([
'use strict';
$('#notebook_about').click(function () {
// use underscore template to auto html escape
var text = 'You are using Jupyter notebook.<br/><br/>';
text = text + 'The version of the notebook server is ';
text = text + _.template('<b><%- version %></b>')({ version: sys_info.notebook_version });
if (sys_info.commit_hash) {
text = text + _.template('-<%- hash %>')({ hash: sys_info.commit_hash });
if (sys_info) {
var text = 'You are using Jupyter notebook.<br/><br/>';
text = text + 'The version of the notebook server is ';
text = text + _.template('<b><%- version %></b>')({ version: sys_info.notebook_version });
if (sys_info.commit_hash) {
text = text + _.template('-<%- hash %>')({ hash: sys_info.commit_hash });
}
text = text + _.template(' and is running on:<br/><pre>Python <%- pyver %></pre>')({
pyver: sys_info.sys_version });
var kinfo = $('<div/>').attr('id', '#about-kinfo').text('Waiting for kernel to be available...');
var body = $('<div/>');
body.append($('<h4/>').text('Server Information:'));
body.append($('<p/>').html(text));
body.append($('<h4/>').text('Current Kernel Information:'));
body.append(kinfo);
} else {
var text = 'Could not access sys_info variable for version information.';
var body = $('<div/>');
body.append($('<h4/>').text('Cannot find sys_info!'));
body.append($('<p/>').html(text));
}
text = text + _.template(' and is running on:<br/><pre>Python <%- pyver %></pre>')({ pyver: sys_info.sys_version });
var kinfo = $('<div/>').attr('id', '#about-kinfo').text('Waiting for kernel to be available...');
var body = $('<div/>');
body.append($('<h4/>').text('Server Information:'));
body.append($('<p/>').html(text));
body.append($('<h4/>').text('Current Kernel Information:'));
body.append(kinfo);
dialog.modal({
title: 'About Jupyter Notebook',
body: body,

@ -212,6 +212,7 @@ define(function(require){
}
},
'cut-cell' : {
help: 'cut selected cells',
icon: 'fa-cut',
help_index : 'ee',
handler : function (env) {
@ -221,6 +222,7 @@ define(function(require){
}
},
'copy-cell' : {
help: 'copy selected cells',
icon: 'fa-copy',
help_index : 'ef',
handler : function (env) {
@ -228,14 +230,14 @@ define(function(require){
}
},
'paste-cell-above' : {
help: 'paste cell above',
help: 'paste cells above',
help_index : 'eg',
handler : function (env) {
env.notebook.paste_cell_above();
}
},
'paste-cell-below' : {
help: 'paste cell below',
help: 'paste cells below',
icon: 'fa-paste',
help_index : 'eh',
handler : function (env) {
@ -345,6 +347,7 @@ define(function(require){
}
},
'move-cell-down' : {
help: 'move selected cells down',
icon: 'fa-arrow-down',
help_index : 'eb',
handler : function (env) {
@ -352,6 +355,7 @@ define(function(require){
}
},
'move-cell-up' : {
help: 'move selected cells up',
icon: 'fa-arrow-up',
help_index : 'ea',
handler : function (env) {
@ -372,7 +376,7 @@ define(function(require){
}
},
'delete-cell': {
help: 'delete selected cell',
help: 'delete selected cells',
help_index : 'ej',
handler : function (env) {
env.notebook.delete_cell();
@ -545,9 +549,6 @@ define(function(require){
'duplicate-notebook':{
help: "Create an open a copy of current notebook",
handler : function (env, event) {
if (env.notebook.dirty) {
env.notebook.save_notebook({async : false});
}
env.notebook.copy_notebook();
}
},

@ -305,24 +305,21 @@ define([
* @method execute
*/
CodeCell.prototype.execute = function (stop_on_error) {
if (!this.kernel || !this.kernel.is_connected()) {
console.log("Can't execute, kernel is not connected.");
if (!this.kernel) {
console.log("Can't execute cell since kernel is not set.");
return;
}
this.output_area.clear_output(false, true);
if (stop_on_error === undefined) {
stop_on_error = true;
}
this.output_area.clear_output(false, true);
var old_msg_id = this.last_msg_id;
if (old_msg_id) {
this.kernel.clear_callbacks_for_msg(old_msg_id);
if (old_msg_id) {
delete CodeCell.msg_cells[old_msg_id];
}
delete CodeCell.msg_cells[old_msg_id];
this.last_msg_id = null;
}
if (this.get_text().trim().length === 0) {
// nothing to do

@ -6,7 +6,8 @@ define([
'base/js/namespace',
'base/js/dialog',
'base/js/utils',
], function($, IPython, dialog, utils) {
'require',
], function($, IPython, dialog, utils, require) {
"use strict";
var KernelSelector = function(selector, notebook) {
@ -147,6 +148,14 @@ define([
// load kernel js
if (ks.resources['kernel.js']) {
// Debug added for Notebook 4.2, please remove at some point in the
// future if the following does not append anymore when kernels
// have kernel.js
//
// > Uncaught (in promise) TypeError: require is not a function
//
console.info('Dynamically requiring kernel.js, `require` is ', require);
require([ks.resources['kernel.js']],
function (kernel_mod) {
if (kernel_mod && kernel_mod.onload) {

@ -79,7 +79,6 @@ define([
'up' : 'jupyter-notebook:move-cursor-up',
'down' : 'jupyter-notebook:move-cursor-down',
'ctrl-shift--' : 'jupyter-notebook:split-cell-at-cursor',
'ctrl-shift-subtract' : 'jupyter-notebook:split-cell-at-cursor',
};
};

@ -24,8 +24,7 @@ require([
'notebook/js/about',
'typeahead',
'notebook/js/searchandreplace',
// only loaded, not used, please keep sure this is loaded last
'custom/custom'
'custom',
], function(
IPython,
$,
@ -48,18 +47,10 @@ require([
CodeMirror,
about,
typeahead,
searchandreplace,
// please keep sure that even if not used, this is loaded last
custom
searchandreplace
) {
"use strict";
// BEGIN HARDCODED WIDGETS HACK
utils.load_extension('widgets/notebook/js/extension').catch(function () {
console.warn('ipywidgets package not installed. Widgets are not available.');
});
// END HARDCODED WIDGETS HACK
// compat with old IPython, remove for IPython > 3.0
window.CodeMirror = CodeMirror;
@ -85,7 +76,7 @@ require([
var save_widget = new savewidget.SaveWidget('span#save_widget', {
events: events,
keyboard_manager: keyboard_manager});
acts.extend_env({save_widget:save_widget})
acts.extend_env({save_widget:save_widget});
var contents = new contents.Contents({
base_url: common_options.base_url,
common_config: common_config
@ -177,8 +168,28 @@ require([
configurable: false
});
utils.load_extensions_from_config(config_section);
utils.load_extensions_from_config(common_config);
// Now actually load nbextensions from config
Promise.all([
utils.load_extensions_from_config(config_section),
utils.load_extensions_from_config(common_config),
])
.catch(function(error) {
console.error('Could not load nbextensions from user config files', error);
})
// BEGIN HARDCODED WIDGETS HACK
.then(function() {
if (!utils.is_loaded('jupyter-js-widgets/extension')) {
// Fallback to the ipywidgets extension
utils.load_extension('widgets/notebook/js/extension').catch(function () {
console.warn('Widgets are not available. Please install widgetsnbextension or ipywidgets 4.0');
});
}
})
.catch(function(error) {
console.error('Could not load ipywidgets', error);
});
// END HARDCODED WIDGETS HACK
notebook.load_notebook(common_options.notebook_path);
});

@ -28,7 +28,7 @@ define([
webFont: "STIX-Web",
styles: {'.MathJax_Display': {"margin": 0}},
linebreaks: { automatic: true }
}
},
});
MathJax.Hub.Configured();
} else if (window.mathjax_url !== "") {

@ -79,7 +79,7 @@ define([
) + "?download=" + download.toString();
var w = window.open('', IPython._target);
if (this.notebook.dirty) {
if (this.notebook.dirty && this.notebook.writable) {
this.notebook.save_notebook().then(function() {
w.location = url;
});
@ -112,9 +112,6 @@ define([
), IPython._target);
});
this.element.find('#copy_notebook').click(function () {
if (that.notebook.dirty) {
that.notebook.save_notebook({async : false});
}
that.notebook.copy_notebook();
return false;
});
@ -125,7 +122,7 @@ define([
var url = utils.url_path_join(
base_url, 'files', notebook_path
) + '?download=1';
if (that.notebook.dirty) {
if (that.notebook.dirty && that.notebook.writable) {
that.notebook.save_notebook().then(function() {
w.location = url;
});
@ -402,7 +399,7 @@ define([
.append($("<a>")
.attr('target', '_blank')
.attr('title', 'Opens in a new window')
.attr('href', link.url)
.attr('href', require.toUrl(link.url))
.append($("<i>")
.addClass("fa fa-external-link menu-icon pull-right")
)

@ -8,6 +8,7 @@ define(function (require) {
"use strict";
var IPython = require('base/js/namespace');
var $ = require('jquery');
var _ = require('underscore');
var utils = require('base/js/utils');
var dialog = require('base/js/dialog');
var cellmod = require('notebook/js/cell');
@ -66,6 +67,8 @@ define(function (require) {
this.last_modified = null;
// debug 484
this._last_modified = 'init';
// Firefox workaround
this._ff_beforeunload_fired = false;
// Create default scroll manager.
this.scroll_manager = new scrollmanager.ScrollManager(this);
@ -110,7 +113,8 @@ define(function (require) {
}
}, function (err) {
console.log("No CodeMirror mode: " + lang);
callback(err, code);
console.log("Require CodeMirror mode error: " + err);
callback(null, code);
});
}
});
@ -245,7 +249,7 @@ define(function (require) {
display_name: data.spec.display_name,
language: data.spec.language,
};
if (!existing_spec || JSON.stringify(existing_spec) != JSON.stringify(that.metadata.kernelspec)) {
if (!existing_spec || ! _.isEqual(existing_spec, that.metadata.kernelspec)) {
that.set_dirty(true);
}
// start session if the current session isn't already correct
@ -263,7 +267,7 @@ define(function (require) {
var existing_info = that.metadata.language_info;
var langinfo = kinfo.language_info;
that.metadata.language_info = langinfo;
if (!existing_info || JSON.stringify(existing_info) != JSON.stringify(langinfo)) {
if (!existing_info || ! _.isEqual(existing_info, langinfo)) {
that.set_dirty(true);
}
// Mode 'null' should be plain, unhighlighted text.
@ -313,6 +317,17 @@ define(function (require) {
if (kill_kernel) {
that.session.delete();
}
if ( utils.browser[0] === "Firefox") {
// Workaround ancient Firefox bug showing beforeunload twice: https://bugzilla.mozilla.org/show_bug.cgi?id=531199
if (that._ff_beforeunload_fired) {
return; // don't show twice on FF
}
that._ff_beforeunload_fired = true;
// unset flag immediately after dialog is dismissed
setTimeout(function () {
that._ff_beforeunload_fired = false;
}, 1);
}
// if we are autosaving, trigger an autosave on nav-away.
// still warn, because if we don't the autosave may fail.
if (that.dirty) {
@ -865,6 +880,7 @@ define(function (require) {
tomove.detach();
pivot.after(tomove);
this.get_cell(selected-1).focus_cell();
this.select(anchored - 1);
this.select(selected - 1, false);
};
@ -891,6 +907,7 @@ define(function (require) {
tomove.detach();
pivot.before(tomove);
this.get_cell(selected+1).focus_cell();
this.select(first);
this.select(anchored + 1);
this.select(selected + 1, false);
@ -2542,7 +2559,7 @@ define(function (require) {
" Selecting trust will immediately reload this notebook in a trusted state."
).append(
" For more information, see the "
).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
).append($("<a>").attr("href", "https://jupyter-notebook.readthedocs.io/en/latest/security.html")
.text("Jupyter security documentation")
).append(".")
);
@ -2578,23 +2595,32 @@ define(function (require) {
/**
* Make a copy of the current notebook.
* If the notebook has unsaved changes, it is saved first.
*/
Notebook.prototype.copy_notebook = function () {
var that = this;
var base_url = this.base_url;
var w = window.open('', IPython._target);
var parent = utils.url_path_split(this.notebook_path)[0];
this.contents.copy(this.notebook_path, parent).then(
function (data) {
w.location = utils.url_path_join(
base_url, 'notebooks', utils.encode_uri_components(data.path)
);
},
function(error) {
w.close();
that.events.trigger('notebook_copy_failed', error);
}
);
var p;
if (this.dirty) {
p = this.save_notebook();
} else {
p = Promise.resolve();
}
return p.then(function () {
return that.contents.copy(that.notebook_path, parent).then(
function (data) {
w.location = utils.url_path_join(
base_url, 'notebooks', utils.encode_uri_components(data.path)
);
},
function(error) {
w.close();
that.events.trigger('notebook_copy_failed', error);
}
);
});
};
/**

@ -139,8 +139,7 @@ define([
if (info.attempt === 1) {
var msg = "A connection to the notebook server could not be established." +
" The notebook will continue trying to reconnect, but" +
" until it does, you will NOT be able to run code. Check your" +
" The notebook will continue trying to reconnect. Check your" +
" network connection or notebook server configuration.";
dialog.kernel_modal({

@ -650,8 +650,9 @@ define([
var type = 'image/svg+xml';
var toinsert = this.create_output_subarea(md, "output_svg", type);
// Get the svg element from within the HTML.
var svg = $('<div />').html(svg_html).find('svg');
// Get the svg element from within the HTML.
// One svg is supposed, but could embed other nested svgs
var svg = $($('<div \>').html(svg_html).find('svg')[0]);
var svg_area = $('<div />');
var width = svg.attr('width');
var height = svg.attr('height');
@ -753,7 +754,7 @@ define([
*/
var type = 'text/latex';
var toinsert = this.create_output_subarea(md, "output_latex", type);
toinsert.append(latex);
toinsert.text(latex);
element.append(toinsert);
return toinsert;
};

@ -161,6 +161,14 @@ define([
this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
};
Pager.prototype.append = function (htm) {
/**
* The only user content injected with this HTML call is escaped by
* the fixConsole() method.
*/
this.pager_element.find(".container").append(htm);
};
Pager.prototype._resize = function() {
/**

@ -113,6 +113,7 @@ define([
'end':'End',
'space':'Space',
'backspace':'Backspace',
'-':'Minus'
};
var humanize_map;
@ -123,7 +124,7 @@ define([
humanize_map = default_humanize_map;
}
var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
var special_case = { pageup: "PageUp", pagedown: "Page Down" };
function humanize_key(key){
if (key.length === 1){

@ -97,18 +97,11 @@ div.cell {
}
div.inner_cell {
min-width: 0;
.vbox();
.box-flex1();
}
@-moz-document url-prefix() {
div.inner_cell {
// hack around FF bug causing cell to expand when lines are long
// instead of scrolling
overflow-x: hidden;
}
}
/* input_area and input_prompt must match in top border and margin for alignment */
div.input_area {
border: 1px solid @light_border_color;

@ -4,7 +4,7 @@
}
.shortcut_key {
display: inline-block;
width: 20ex;
width: 21ex;
text-align: right;
font-family: @font-family-monospace;
}

@ -63,6 +63,8 @@ define([
this._autorestart_attempt = 0;
this._reconnect_attempt = 0;
this.reconnect_limit = 7;
this._pending_messages = [];
};
/**
@ -337,7 +339,7 @@ define([
* @function reconnect
*/
if (this.is_connected()) {
return;
this.stop_channels();
}
this._reconnect_attempt = this._reconnect_attempt + 1;
this.events.trigger('kernel_reconnecting.Kernel', {
@ -409,6 +411,15 @@ define([
* @function _kernel_connected
*/
this.events.trigger('kernel_connected.Kernel', {kernel: this});
// Send pending messages. We shift the message off the queue
// after the message is sent so that if there is an exception,
// the message is still pending.
while (this._pending_messages.length > 0) {
this.ws.send(this._pending_messages[0]);
this._pending_messages.shift();
}
// get kernel info so we know what state the kernel is in
var that = this;
this.kernel_info(function (reply) {
@ -524,8 +535,13 @@ define([
this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
if (error) {
console.log('WebSocket connection failed: ', ws_url);
this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
console.log('WebSocket connection failed: ', ws_url, error);
this.events.trigger('kernel_connection_failed.Kernel', {
kernel: this,
ws_url: ws_url,
attempt: this._reconnect_attempt,
error: error,
});
}
this._schedule_reconnect();
};
@ -602,19 +618,34 @@ define([
return (this.ws === null);
};
Kernel.prototype._send = function(msg) {
/**
* Send a message (if the kernel is connected) or queue the message for future delivery
*
* Pending messages will automatically be sent when a kernel becomes connected.
*
* @function _send
* @param msg
*/
if (this.is_connected()) {
this.ws.send(msg);
} else {
this._pending_messages.push(msg);
}
}
Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
/**
* Send a message on the Kernel's shell channel
*
* If the kernel is not connected, the message will be buffered.
*
* @function send_shell_message
*/
if (!this.is_connected()) {
throw new Error("kernel is not connected");
}
var msg = this._get_msg(msg_type, content, metadata, buffers);
msg.channel = 'shell';
this.ws.send(serialize.serialize(msg));
this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
this._send(serialize.serialize(msg));
return msg.header.msg_id;
};
@ -627,7 +658,7 @@ define([
*
* When calling this method, pass a callback function that expects one argument.
* The callback will be passed the complete `kernel_info_reply` message documented
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info)
*/
var callbacks;
if (callback) {
@ -645,7 +676,7 @@ define([
*
* When calling this method, pass a callback function that expects one argument.
* The callback will be passed the complete `comm_info_reply` message documented
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#comm_info)
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#comm_info)
*/
var callbacks;
if (callback) {
@ -663,7 +694,7 @@ define([
*
* When calling this method, pass a callback function that expects one argument.
* The callback will be passed the complete `inspect_reply` message documented
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#object-information)
*
* @function inspect
* @param code {string}
@ -754,7 +785,7 @@ define([
* `complete_reply` message as its only argument when it arrives.
*
* `complete_reply` is documented
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#complete)
*
* @function complete
* @param code {string}
@ -777,16 +808,13 @@ define([
* @function send_input_reply
*/
Kernel.prototype.send_input_reply = function (input) {
if (!this.is_connected()) {
throw new Error("kernel is not connected");
}
var content = {
value : input
};
this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
var msg = this._get_msg("input_reply", content);
msg.channel = 'stdin';
this.ws.send(serialize.serialize(msg));
this._send(serialize.serialize(msg));
return msg.header.msg_id;
};

@ -8,7 +8,7 @@ require([
'base/js/page',
'services/config',
'terminal/js/terminado',
'custom/custom',
'custom',
], function(
$,
termjs,
@ -20,10 +20,12 @@ require([
"use strict";
page = new page.Page();
var config = new configmod.ConfigSection('terminal',
{base_url: utils.get_body_data('baseUrl')});
config.load();
var common_config = new configmod.ConfigSection('common',
{base_url: utils.get_body_data('baseUrl')});
common_config.load();
// Test size: 25x80
var termRowHeight = function(){ return 1.00 * $("#dummy-screen")[0].offsetHeight / 25;};
// 1.02 here arrived at by trial and error to make the spacing look right
@ -34,7 +36,7 @@ require([
var ws_url = location.protocol.replace('http', 'ws') + "//" + location.host
+ base_url + ws_path;
var header = $("#header")[0]
var header = $("#header")[0];
function calculate_size() {
var height = $(window).height() - header.offsetHeight;
var width = $('#terminado-container').width();
@ -51,6 +53,7 @@ require([
page.show_site();
utils.load_extensions_from_config(config);
utils.load_extensions_from_config(common_config);
window.onresize = function() {

@ -19,7 +19,7 @@ require([
// only loaded, not used:
'jquery-ui',
'bootstrap',
'custom/custom',
'custom',
], function(
$,
IPython,

@ -555,6 +555,7 @@ define([
var uri_prefix = NotebookList.uri_prefixes[model.type];
if (model.type === 'file' &&
model.mimetype && model.mimetype.substr(0,5) !== 'text/'
&& !model.mimetype.endsWith('javascript')
) {
// send text/unidentified files to editor, others go to raw viewer
uri_prefix = 'files';

@ -11,7 +11,7 @@
{% block bodyclasses %}edit_app {{super()}}{% endblock %}
{% block params %}
data-base-url="{{base_url}}"
data-base-url="{{base_url | urlencode}}"
data-file-path="{{file_path}}"
{{super()}}
{% endblock %}
@ -65,7 +65,9 @@ data-file-path="{{file_path}}"
</li>
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
<ul id="view-menu" class="dropdown-menu">
<li id="menu-line-numbers"><a href="#">Toggle Line Numbers</a></li>
<li id="toggle_header" title="Show/Hide the logo and notebook title (above menu bar)">
<a href="#">Toggle Header</a></li>
<li id="menu-line-numbers"><a href="#">Toggle Line Numbers</a></li>
</ul>
</li>
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Language</a>

@ -105,7 +105,7 @@ data-notebook-path="{{notebook_path | urlencode}}"
<li id="print_preview"><a href="#">Print Preview</a></li>
<li class="dropdown-submenu"><a href="#">Download as</a>
<ul class="dropdown-menu">
<li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
<li id="download_ipynb"><a href="#">Notebook (.ipynb)</a></li>
<li id="download_script"><a href="#">Script</a></li>
<li id="download_html"><a href="#">HTML (.html)</a></li>
<li id="download_markdown"><a href="#">Markdown (.md)</a></li>
@ -148,7 +148,7 @@ data-notebook-path="{{notebook_path | urlencode}}"
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
<ul id="view_menu" class="dropdown-menu">
<li id="toggle_header"
title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
title="Show/Hide the logo and notebook title (above menu bar)">
<a href="#">Toggle Header</a></li>
<li id="toggle_toolbar"
title="Show/Hide the action icons (below menu bar)">
@ -293,7 +293,7 @@ data-notebook-path="{{notebook_path | urlencode}}"
{% endif %}
{% endfor %}
<li class="divider"></li>
<li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
<li title="About Jupyter Notebook"><a id="notebook_about" href="#">About</a></li>
{% endblock %}
</ul>
</li>

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<title>{% block title %}IPython Notebook{% endblock %}</title>
<title>{% block title %}Jupyter Notebook{% endblock %}</title>
{% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">{% endblock %}
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
@ -27,7 +27,6 @@
'auth/js/main': 'auth/js/main.min',
custom : '{{ base_url }}custom',
nbextensions : '{{ base_url }}nbextensions',
widgets : '{{ base_url }}deprecatedwidgets',
kernelspecs : '{{ base_url }}kernelspecs',
underscore : 'components/underscore/underscore-min',
backbone : 'components/backbone/backbone-min',
@ -80,6 +79,33 @@
}
}
});
define("bootstrap", function () {
return window.$;
});
define("jquery", function () {
return window.$;
});
define("jqueryui", function () {
return window.$;
});
define("jquery-ui", function () {
return window.$;
});
// error-catching custom.js shim.
define("custom", function (require, exports, module) {
try {
var custom = require('custom/custom');
console.debug('loaded custom.js');
return custom;
} catch (e) {
console.error("error loading custom.js", e);
return {};
}
})
</script>
{% block meta %}
@ -91,7 +117,7 @@
<noscript>
<div id='noscript'>
IPython Notebook requires JavaScript.<br>
Jupyter Notebook requires JavaScript.<br>
Please enable it to proceed.
</div>
</noscript>

@ -6,7 +6,7 @@
{% block params %}
data-base-url="{{base_url}}"
data-base-url="{{base_url | urlencode}}"
data-ws-path="{{ws_path}}"
{% endblock %}

@ -18,6 +18,7 @@ except ImportError:
from mock import patch #py2
from tornado.ioloop import IOLoop
import zmq
import jupyter_core.paths
from ..notebookapp import NotebookApp
@ -130,6 +131,16 @@ class NotebookTestBase(TestCase):
cls.notebook_dir.cleanup()
cls.env_patch.stop()
cls.path_patch.stop()
# cleanup global zmq Context, to ensure we aren't leaving dangling sockets
def cleanup_zmq():
zmq.Context.instance().term()
t = Thread(target=cleanup_zmq)
t.daemon = True
t.start()
t.join(5) # give it a few seconds to clean up (this should be immediate)
# if term never returned, there's zmq stuff still open somewhere, so shout about it.
if t.is_alive():
raise RuntimeError("Failed to teardown zmq Context, open sockets likely left lying around.")
@classmethod
def base_url(cls):

@ -0,0 +1,56 @@
//
// Test buffering for execution requests.
//
casper.notebook_test(function () {
this.then(function() {
// make sure there are at least three cells for the tests below.
this.append_cell();
this.append_cell();
this.append_cell();
})
this.thenEvaluate(function () {
IPython.notebook.kernel.stop_channels();
var cell = IPython.notebook.get_cell(0);
cell.set_text('a=10; print(a)');
IPython.notebook.execute_cells([0]);
IPython.notebook.kernel.reconnect(1);
});
this.wait_for_output(0);
this.then(function () {
var result = this.get_output_cell(0);
this.test.assertEquals(result.text, '10\n', 'kernels buffer messages if connection is down');
});
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
var cellplus = IPython.notebook.get_cell(1);
var cellprint = IPython.notebook.get_cell(2);
cell.set_text('k=1');
cellplus.set_text('k+=1');
cellprint.set_text('k*=2')
IPython.notebook.kernel.stop_channels();
// Repeated execution of cell queued up in the kernel executes
// each execution request in order.
IPython.notebook.execute_cells([0]);
IPython.notebook.execute_cells([2]);
IPython.notebook.execute_cells([1]);
IPython.notebook.execute_cells([1]);
IPython.notebook.execute_cells([1]);
cellprint.set_text('print(k)')
IPython.notebook.execute_cells([2]);
IPython.notebook.kernel.reconnect(1);
});
this.wait_for_output(2);
this.then(function () {
var result = this.get_output_cell(2);
this.test.assertEquals(result.text, '5\n', 'kernels send buffered messages in order');
});
});

@ -41,7 +41,7 @@ casper.notebook_test(function () {
this.set_cell_text(0, 'abcd');
this.set_cell_editor_cursor(0, 0, 2);
this.test.assertEquals(this.get_cell_text(0), 'abcd', 'Verify that cell 0 has the new contents.');
this.trigger_keydown('ctrl-shift-subtract'); // Split
this.trigger_keydown('ctrl-shift--'); // Split
this.test.assertEquals(this.get_cell_text(0), 'ab', 'split; Verify that cell 0 has the first half.');
this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.');
this.validate_notebook_state('split', 'edit', 1);
@ -125,12 +125,12 @@ casper.notebook_test(function () {
this.select_cell(1);
this.trigger_keydown('enter');
this.set_cell_editor_cursor(1, 0, 2);
this.trigger_keydown('ctrl-shift-subtract'); // Split
this.trigger_keydown('ctrl-shift--'); // Split
this.test.assertEquals(this.get_cells_length(), N, 'Split cell 1: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Split cell 1: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Split cell 1: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Split cell 1: Cell 2 is unchanged');
this.validate_notebook_state('ctrl-shift-subtract', 'edit', 1);
this.validate_notebook_state('ctrl-shift--', 'edit', 1);
});
// Try to merge cell 1 down, should fail, as 1 is locked

@ -60,4 +60,46 @@ casper.notebook_test(function () {
var hashes = new Array(level + 1).join('#');
this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level);
}
// Test markdown code blocks
function md_render_test (codeblock, result, message) {
// make a cell and trigger render
casper.thenEvaluate(function (text) {
var cell = Jupyter.notebook.insert_cell_at_bottom('markdown');
cell.set_text(text);
// signal window._rendered when cell render completes
window._rendered = null;
cell.events.one("rendered.MarkdownCell", function (event, data) {
window._rendered = data.cell.get_rendered();
});
cell.render();
}, {text: codeblock});
// wait for render to complete
casper.waitFor(function () {
return casper.evaluate(function () {
return window._rendered;
});
});
// test after waiting
casper.then(function () {
// get rendered result
var output = casper.evaluate(function () {
var rendered = window._rendered;
delete window._rendered;
return rendered;
});
// perform test
this.test.assertEquals(output.trim(), result, message);
});
};
var codeblock = '```\nx = 1\n```'
var result = '<pre><code>x = 1\n</code></pre>'
md_render_test(codeblock, result, 'Markdown code block no language');
codeblock = '```aaaa\nx = 1\n```'
result = '<pre><code class="cm-s-ipython language-aaaa">x = 1\n</code></pre>'
md_render_test(codeblock, result, 'Markdown code block unknown language');
});

@ -95,4 +95,32 @@ casper.notebook_test(function () {
text: "3\n"
}]
);
this.test_coalesced_output("test nested svg", [
'from IPython.display import SVG',
'nested_svg="""',
'<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" >',
' <svg x="0">',
' <rect x="10" y="10" height="80" width="80" style="fill: #0000ff"/>',
' </svg>',
' <svg x="100">',
' <rect x="10" y="10" height="80" width="80" style="fill: #00cc00"/>',
' </svg>',
'</svg>"""',
'SVG(nested_svg)'
].join("\n"), [{
output_type: "execute_result",
data: {
"text/plain" : "<IPython.core.display.SVG object>",
"image/svg+xml": [
'<svg height="200" width="100" xmlns="http://www.w3.org/2000/svg">',
' <svg x="0">',
' <rect height="80" style="fill: #0000ff" width="80" x="10" y="10"/>',
' </svg>',
' <svg x="100">',
' <rect height="80" style="fill: #00cc00" width="80" x="10" y="10"/>',
' </svg>',
'</svg>'].join("\n")
},
}]
);
});

@ -6,12 +6,12 @@
import glob
import os
import re
import sys
import tarfile
import zipfile
from io import BytesIO, StringIO
from os.path import basename, join as pjoin
from traitlets.tests.utils import check_help_all_output
from unittest import TestCase
try:
@ -23,7 +23,14 @@ import ipython_genutils.testing.decorators as dec
from ipython_genutils import py3compat
from ipython_genutils.tempdir import TemporaryDirectory
from notebook import nbextensions
from notebook.nbextensions import install_nbextension, check_nbextension
from notebook.nbextensions import (install_nbextension, check_nbextension,
enable_nbextension, disable_nbextension,
install_nbextension_python, uninstall_nbextension_python,
enable_nbextension_python, disable_nbextension_python, _get_config_dir,
validate_nbextension, validate_nbextension_python
)
from traitlets.config.manager import BaseJSONConfigManager
def touch(file, mtime=None):
@ -38,6 +45,15 @@ def touch(file, mtime=None):
os.utime(file, (atime, mtime))
return os.stat(file).st_mtime
def test_help_output():
check_help_all_output('notebook.nbextensions')
check_help_all_output('notebook.nbextensions', ['enable'])
check_help_all_output('notebook.nbextensions', ['disable'])
check_help_all_output('notebook.nbextensions', ['install'])
check_help_all_output('notebook.nbextensions', ['uninstall'])
class TestInstallNBExtension(TestCase):
def tempdir(self):
@ -47,7 +63,7 @@ class TestInstallNBExtension(TestCase):
def setUp(self):
self.tempdirs = []
src = self.src = self.tempdir()
self.src = self.tempdir()
self.files = files = [
pjoin(u'ƒile'),
pjoin(u'∂ir', u'ƒile1'),
@ -60,19 +76,24 @@ class TestInstallNBExtension(TestCase):
os.makedirs(parent)
touch(fullpath)
self.data_dir = self.tempdir()
self.patch_data = patch.object(nbextensions,
'jupyter_data_dir', lambda : self.data_dir)
self.patch_data.start()
self.system_path = [self.tempdir()]
self.system_nbext = pjoin(self.system_path[0], 'nbextensions')
self.patch_system_data = patch.object(nbextensions,
self.test_dir = self.tempdir()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
self.system_path = [self.system_data_dir]
self.system_nbext = os.path.join(self.system_data_dir, 'nbextensions')
self.patch_env = patch.dict('os.environ', {
'JUPYTER_CONFIG_DIR': self.config_dir,
'JUPYTER_DATA_DIR': self.data_dir,
})
self.patch_env.start()
self.patch_system_path = patch.object(nbextensions,
'SYSTEM_JUPYTER_PATH', self.system_path)
self.patch_system_data.start()
self.patch_system_path.start()
def tearDown(self):
self.patch_data.stop()
self.patch_system_data.stop()
self.patch_env.stop()
self.patch_system_path.stop()
def assert_dir_exists(self, path):
if not os.path.exists(path):
@ -104,18 +125,20 @@ class TestInstallNBExtension(TestCase):
def test_create_data_dir(self):
"""install_nbextension when data_dir doesn't exist"""
with TemporaryDirectory() as td:
self.data_dir = pjoin(td, u'jupyter_data')
install_nbextension(self.src, user=True)
self.assert_dir_exists(self.data_dir)
for file in self.files:
self.assert_installed(
pjoin(basename(self.src), file),
user=True,
)
data_dir = os.path.join(td, self.data_dir)
with patch.dict('os.environ', {
'JUPYTER_DATA_DIR': data_dir,
}):
install_nbextension(self.src, user=True)
self.assert_dir_exists(data_dir)
for file in self.files:
self.assert_installed(
pjoin(basename(self.src), file),
user=True,
)
def test_create_nbextensions_user(self):
with TemporaryDirectory() as td:
self.data_dir = pjoin(td, u'jupyter_data')
install_nbextension(self.src, user=True)
self.assert_installed(
pjoin(basename(self.src), u'ƒile'),
@ -197,7 +220,7 @@ class TestInstallNBExtension(TestCase):
install_nbextension(src)
self.assert_installed(fname)
dest = pjoin(self.system_nbext, fname)
old_mtime = os.stat(dest).st_mtime
os.stat(dest).st_mtime
with open(src, 'w') as f:
f.write('overwrite')
touch(src, mtime + 10)
@ -225,7 +248,7 @@ class TestInstallNBExtension(TestCase):
stderr = StringIO()
with patch.object(sys, 'stdout', stdout), \
patch.object(sys, 'stderr', stderr):
install_nbextension(self.src, verbose=0)
install_nbextension(self.src)
self.assertEqual(stdout.getvalue(), '')
self.assertEqual(stderr.getvalue(), '')
@ -343,3 +366,131 @@ class TestInstallNBExtension(TestCase):
with self.assertRaises(ValueError):
install_nbextension(zsrc, destination='foo')
def test_nbextension_enable(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
src = pjoin(d, f)
touch(src)
install_nbextension(src, user=True)
enable_nbextension(section='notebook', require=u'ƒ')
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get(u'ƒ', False)
assert enabled
def test_nbextension_disable(self):
self.test_nbextension_enable()
disable_nbextension(section='notebook', require=u'ƒ')
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get(u'ƒ', False)
assert not enabled
def _mock_extension_spec_meta(self, section='notebook'):
return {
'section': section,
'src': 'mockextension',
'dest': '_mockdestination',
'require': '_mockdestination/index'
}
def _inject_mock_extension(self, section='notebook'):
outer_file = __file__
meta = self._mock_extension_spec_meta(section)
class mock():
__file__ = outer_file
@staticmethod
def _jupyter_nbextension_paths():
return [meta]
import sys
sys.modules['mockextension'] = mock
def test_nbextensionpy_files(self):
self._inject_mock_extension()
install_nbextension_python('mockextension')
assert check_nbextension('_mockdestination/index.js')
assert check_nbextension(['_mockdestination/index.js'])
def test_nbextensionpy_user_files(self):
self._inject_mock_extension()
install_nbextension_python('mockextension', user=True)
assert check_nbextension('_mockdestination/index.js', user=True)
assert check_nbextension(['_mockdestination/index.js'], user=True)
def test_nbextensionpy_uninstall_files(self):
self._inject_mock_extension()
install_nbextension_python('mockextension', user=True)
uninstall_nbextension_python('mockextension', user=True)
assert not check_nbextension('_mockdestination/index.js')
assert not check_nbextension(['_mockdestination/index.js'])
def test_nbextensionpy_enable(self):
self._inject_mock_extension('notebook')
install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get('_mockdestination/index', False)
assert enabled
def test_nbextensionpy_disable(self):
self._inject_mock_extension('notebook')
install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
disable_nbextension_python('mockextension', user=True)
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get('_mockdestination/index', False)
assert not enabled
def test_nbextensionpy_validate(self):
self._inject_mock_extension('notebook')
paths = install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
meta = self._mock_extension_spec_meta()
warnings = validate_nbextension_python(meta, paths[0])
self.assertEqual([], warnings, warnings)
def test_nbextensionpy_validate_bad(self):
# Break the metadata (correct file will still be copied)
self._inject_mock_extension('notebook')
paths = install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
meta = self._mock_extension_spec_meta()
meta.update(require="bad-require")
warnings = validate_nbextension_python(meta, paths[0])
self.assertNotEqual([], warnings, warnings)
def test_nbextension_validate(self):
# Break the metadata (correct file will still be copied)
self._inject_mock_extension('notebook')
install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
warnings = validate_nbextension("_mockdestination/index")
self.assertEqual([], warnings, warnings)
def test_nbextension_validate_bad(self):
warnings = validate_nbextension("this-doesn't-exist")
self.assertNotEqual([], warnings, warnings)

@ -68,6 +68,11 @@ def test_nb_dir_with_slash():
app = NotebookApp(notebook_dir=td)
nt.assert_false(app.notebook_dir.endswith("/"))
def test_nb_dir_root():
root = os.path.abspath(os.sep) # gets the right value on Windows, Posix
app = NotebookApp(notebook_dir=root)
nt.assert_equal(app.notebook_dir, root)
def test_generate_config():
with TemporaryDirectory() as td:
app = NotebookApp(config_dir=td)

@ -0,0 +1,89 @@
import os
from unittest import TestCase
try:
from unittest.mock import patch
except ImportError:
from mock import patch # py2
from ipython_genutils.tempdir import TemporaryDirectory
from ipython_genutils import py3compat
from traitlets.config.manager import BaseJSONConfigManager
from traitlets.tests.utils import check_help_all_output
from notebook.serverextensions import toggle_serverextension_python
from notebook import nbextensions
from notebook.nbextensions import _get_config_dir
def test_help_output():
check_help_all_output('notebook.serverextensions')
check_help_all_output('notebook.serverextensions', ['enable'])
check_help_all_output('notebook.serverextensions', ['disable'])
check_help_all_output('notebook.serverextensions', ['install'])
check_help_all_output('notebook.serverextensions', ['uninstall'])
class TestInstallServerExtension(TestCase):
def tempdir(self):
td = TemporaryDirectory()
self.tempdirs.append(td)
return py3compat.cast_unicode(td.name)
def setUp(self):
self.tempdirs = []
self.test_dir = self.tempdir()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
self.system_path = [self.system_data_dir]
self.patch_env = patch.dict('os.environ', {
'JUPYTER_CONFIG_DIR': self.config_dir,
'JUPYTER_DATA_DIR': self.data_dir,
})
self.patch_env.start()
self.patch_system_path = patch.object(nbextensions,
'SYSTEM_JUPYTER_PATH', self.system_path)
self.patch_system_path.start()
def tearDown(self):
self.patch_env.stop()
self.patch_system_path.stop()
def _inject_mock_extension(self):
outer_file = __file__
class mock():
__file__ = outer_file
@staticmethod
def _jupyter_server_extension_paths():
return [{
'module': '_mockdestination/index'
}]
import sys
sys.modules['mockextension'] = mock
def _get_config(self, user=True):
cm = BaseJSONConfigManager(config_dir=_get_config_dir(user))
data = cm.get("jupyter_notebook_config")
return data.get("NotebookApp", {}).get("nbserver_extensions", {})
def test_enable(self):
self._inject_mock_extension()
toggle_serverextension_python('mockextension', True)
config = self._get_config()
assert config['mockextension']
def test_disable(self):
self._inject_mock_extension()
toggle_serverextension_python('mockextension', True)
toggle_serverextension_python('mockextension', False)
config = self._get_config()
assert not config['mockextension']

@ -102,12 +102,18 @@ def is_hidden(abs_path, abs_root=''):
return True
# check that dirs can be listed
# may fail on Windows junctions or non-user-readable dirs
if os.path.isdir(abs_path):
try:
os.listdir(abs_path)
except OSError:
return True
if sys.platform == 'win32':
# can't trust os.access on Windows because it seems to always return True
try:
os.stat(abs_path)
except OSError:
# stat may fail on Windows junctions or non-user-readable dirs
return True
else:
# use x-access, not actual listing, in case of slow/large listings
if not os.access(abs_path, os.X_OK | os.R_OK):
return True
# check UF_HIDDEN on any location up to root
path = abs_path
@ -126,7 +132,7 @@ def is_hidden(abs_path, abs_root=''):
if sys.platform == 'win32':
try:
attrs = ctypes.windll.kernel32.GetFileAttributesW(py3compat.cast_unicode(path))
attrs = ctypes.windll.kernel32.GetFileAttributesW(py3compat.cast_unicode(abs_path))
except AttributeError:
pass
else:

@ -0,0 +1,6 @@
#!/usr/bin/env python
from notebook.serverextensions import main
if __name__ == '__main__':
main()

@ -164,8 +164,6 @@ if 'setuptools' in sys.modules:
# setup.py develop should check for submodules
from setuptools.command.develop import develop
setup_args['cmdclass']['develop'] = css_js_prerelease(develop)
if not PY3:
setup_args['setup_requires'] = ['ipython_genutils']
try:
from wheel.bdist_wheel import bdist_wheel
@ -182,6 +180,7 @@ if 'setuptools' in sys.modules:
'console_scripts': [
'jupyter-notebook = notebook.notebookapp:main',
'jupyter-nbextension = notebook.nbextensions:main',
'jupyter-serverextension = notebook.serverextensions:main',
]
}
setup_args.pop('scripts', None)

@ -158,21 +158,30 @@ def find_package_data():
mj('MathJax.js'),
mj('config', 'TeX-AMS_HTML-full.js'),
mj('config', 'Safe.js'),
mj('extensions', 'Safe.js'),
mj('jax', 'output', 'HTML-CSS', '*.js'),
])
for tree in [
trees = []
mj_out = mj('jax', 'output')
if os.path.exists(mj_out):
for output in os.listdir(mj_out):
path = pjoin(mj_out, output)
static_data.append(pjoin(path, '*.js'))
autoload = pjoin(path, 'autoload')
if os.path.isdir(autoload):
trees.append(autoload)
for tree in trees + [
mj('localization'), # limit to en?
mj('fonts', 'HTML-CSS', 'STIX-Web', 'woff'),
mj('extensions', 'TeX'),
mj('extensions'),
mj('jax', 'input', 'TeX'),
mj('jax', 'output', 'HTML-CSS', 'autoload'),
mj('jax', 'output', 'HTML-CSS', 'fonts', 'STIX-Web'),
mj('jax', 'output', 'SVG', 'fonts', 'STIX-Web'),
]:
for parent, dirs, files in os.walk(tree):
for f in files:
static_data.append(pjoin(parent, f))
os.chdir(os.path.join('tests',))
js_tests = glob('*.js') + glob('*/*.js')

@ -23,7 +23,8 @@ var rjs_config = {
codemirror: 'components/codemirror',
termjs: 'components/term.js/src/term',
typeahead: 'components/jquery-typeahead/dist/jquery.typeahead',
contents: 'empty:'
contents: 'empty:',
custom: 'empty:',
},
map: { // for backward compatibility
"*": {

Loading…
Cancel
Save