├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.md
├── MANIFEST.in
├── Makefile
├── README.md
├── dashboards_bundlers
├── __init__.py
├── _version.py
├── server_download.py
└── server_upload.py
├── etc
├── bundlers_intro.png
└── notebooks
│ ├── associations_demo
│ ├── associations_demo.ipynb
│ ├── data
│ │ └── cars.csv
│ └── images
│ │ └── MjeLFmy6Lx8di.gif
│ ├── hello_world.ipynb
│ ├── hello_world_report.ipynb
│ └── widget_binding.ipynb
├── requirements.txt
├── setup.py
└── test
├── resources
├── env.ipynb
├── no_imports.ipynb
├── some.csv
└── some.ipynb
├── test_server_download.py
└── test_server_upload.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
59 | # OSX
60 | .DS_Store
61 | .ipynb_checkpoints/
62 | etc/notebooks/local_dashboards/
63 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - 2.7
4 | - 3.3
5 | - 3.4
6 | - 3.5
7 | - 3.6
8 |
9 | before_install:
10 | - pip install pip -U
11 | - pip install setuptools -U
12 | - pip install -U notebook
13 |
14 | script:
15 | - pip install -e .
16 | - python -B -m unittest discover -s test
17 |
18 | notifications:
19 | email:
20 | on_success: change
21 | on_failure: always
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.9.1 (2017-04-05)
4 |
5 | * Remove leftover setuptools script entrypoint
6 |
7 | ## 0.9.0 (2017-04-04)
8 |
9 | * Remove deprecated bundlers
10 | * Update to work with bundler API in notebook>=5.0.0
11 | * Update to install against notebook 5.0.0
12 |
13 | ## 0.8.1 (2016-09-09)
14 |
15 | * Fix SystemRoot env var on Windows
16 | * Fix missing license in package
17 |
18 | ## 0.8.0 (2016-06-16)
19 |
20 | * Use link attribute from dashboards server to redirect after deployment if available
21 | * Fix declarative widget errors from appearing in local dashboard views
22 | * Add an option to disable SSL verification for dashboard server deployment
23 | * Update example notebooks for compatibility with `jupyter_declarativewidgets` 0.6.0
24 |
25 | ## 0.7.0 (2016-04-28)
26 |
27 | * Fix local deploy compatibility with `jupyter-incubator/declarativewidgets>=0.5.0`
28 |
29 | ## 0.6.0 (2016-04-27)
30 |
31 | * Fix dashboard server bundle compatibility with `jupyter-incubator/declarativewidgets>=0.5.0`
32 | * Deprecate bundlers that rely on Thebe
33 |
34 | ## 0.5.0 (2016-04-26)
35 |
36 | * Add support for report layout in Thebe-based deployments
37 | * Add bundler to download a zip for deployment on `jupyter-incubator/dashboards_server`
38 | * Improve documentation about bundling associated assets
39 | * Make compatible with Jupyter Notebook 4.0.x to 4.2.x
40 |
41 | ## 0.4.0 (2016-03-04)
42 |
43 | * Fix authorization header format for deployment to dashboard server
44 | * Support bundling of frontend assets when deploying to dashboard server
45 | * Make forward compatible with `declarative widgets>=0.5.0`
46 |
47 | ## 0.3.1 (2016-02-23)
48 |
49 | * Fix compatibility with `jupyter_dashboards>=0.4.2` (internal lodash path change)
50 |
51 | ## 0.3.0 (2016-02-13)
52 |
53 | * Add bundler to deploy to `jupyter-incubator/dashboards_server`
54 |
55 | ## 0.2.2 (2016-02-07)
56 |
57 | * Fix compatibility with latest nbconvert (`jupyter nbconvert`)
58 |
59 | ## 0.2.1 (2016-01-26)
60 |
61 | * Hide stderr and Thebe errors in dashboard UI
62 |
63 | ## 0.2.0 (2016-01-21)
64 |
65 | * Separate `pip install` from `jupyter dashboards [install | activate | deactivate]`
66 | * Fix local deploy when base URL is set on Jupyter Notebook server
67 |
68 | ## 0.1.1 (2015-12-30)
69 |
70 | * Fix missing static assets in release
71 |
72 | ## 0.1.0 (2015-12-30)
73 |
74 | * First release with local dashboard and PHP app options
75 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # Licensing terms
2 |
3 | This project is licensed under the terms of the Modified BSD License
4 | (also known as New or Revised or 3-Clause BSD), as follows:
5 |
6 | - Copyright (c) 2001-2015, IPython Development Team
7 | - Copyright (c) 2015-, Jupyter Development Team
8 |
9 | All rights reserved.
10 |
11 | Redistribution and use in source and binary forms, with or without
12 | modification, are permitted provided that the following conditions are met:
13 |
14 | Redistributions of source code must retain the above copyright notice, this
15 | list of conditions and the following disclaimer.
16 |
17 | Redistributions in binary form must reproduce the above copyright notice, this
18 | list of conditions and the following disclaimer in the documentation and/or
19 | other materials provided with the distribution.
20 |
21 | Neither the name of the Jupyter Development Team nor the names of its
22 | contributors may be used to endorse or promote products derived from this
23 | software without specific prior written permission.
24 |
25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
26 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
27 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 |
36 | ## About the Jupyter Development Team
37 |
38 | The Jupyter Development Team is the set of all contributors to the Jupyter project.
39 | This includes all of the Jupyter Subprojects, which are the different repositories
40 | under the [jupyter](https://github.com/jupyter/) GitHub organization.
41 |
42 | The core team that coordinates development on GitHub can be found here:
43 | https://github.com/jupyter/.
44 |
45 | ## Our copyright policy
46 |
47 | Jupyter uses a shared copyright model. Each contributor maintains copyright
48 | over their contributions to Jupyter. But, it is important to note that these
49 | contributions are typically only changes to the repositories. Thus, the Jupyter
50 | source code, in its entirety is not the copyright of any single person or
51 | institution. Instead, it is the collective copyright of the entire Jupyter
52 | Development Team. If individual contributors want to maintain a record of what
53 | changes/contributions they have specific copyright on, they should indicate
54 | their copyright in the commit message of the change, when they commit the
55 | change to one of the Jupyter repositories.
56 |
57 | With this in mind, the following banner should be used in any source code file
58 | to indicate the copyright and license terms:
59 |
60 | # Copyright (c) Jupyter Development Team.
61 | # Distributed under the terms of the Modified BSD License.
62 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include dashboards_bundlers *
2 | include LICENSE.md
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | .PHONY: activate build clean env help notebook nuke release sdist test
5 |
6 | SA:=source activate
7 | ENV:=dashboards-bundlers
8 |
9 | help:
10 | # http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
11 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
12 |
13 | activate: ## eval $(make activate)
14 | @echo "$(SA) $(ENV)"
15 |
16 | clean: ## Make a clean source tree
17 | @-rm -rf dist
18 | @-rm -rf *.egg-info
19 | @-rm -rf __pycache__ */__pycache__ */*/__pycache__
20 | @-find . -name '*.pyc' -exec rm -fv {} \;
21 |
22 | build: env
23 | env: ## Make a dev environment
24 | @conda create -y -n $(ENV) -c conda-forge python=3 \
25 | --file requirements.txt \
26 | # Need to force notebook prerelease until 5.0 is out
27 | @$(SA) $(ENV) && \
28 | pip install -U --pre notebook && \
29 | pip install -e . && \
30 | jupyter bundlerextension enable --sys-prefix --py dashboards_bundlers
31 |
32 | notebook: ## Make a notebook server
33 | $(SA) $(ENV) && jupyter notebook --notebook-dir=./etc/notebooks
34 |
35 | nuke: clean ## Make clean + remove conda env
36 | -conda env remove -n $(ENV) -y
37 |
38 | release: sdist ## Make a release on PyPI
39 | $(SA) $(ENV) && python setup.py sdist register upload
40 |
41 | sdist: ## Make a dist/*.tar.gz source distribution
42 | $(SA) $(ENV) && python setup.py sdist
43 |
44 | test: ## Make a test run
45 | $(SA) $(ENV) && python -B -m unittest discover -s test
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/py/jupyter_dashboards_bundlers) [](https://groups.google.com/forum/#!forum/jupyter)
2 |
3 | # [RETIRED] Jupyter Dashboards Bundlers
4 |
5 | **This project has been retired.** See the [proposal to move the project to jupyter-attic](https://github.com/jupyter/enhancement-proposals/blob/master/jupyter-dashboards-deployment-attic/jupyter-dashboards-deployment-attic.md), [announcement of the proposal on the mailing list](https://groups.google.com/forum/#!topic/jupyter/icEtCVLniRc), and [Steering Council vote on the proposal PR](https://github.com/jupyter/enhancement-proposals/pull/22) for more information.
6 |
7 | ----
8 |
9 | The dashbaords bundler extension is an add-on for Jupyter Notebook. It takes a
10 | notebook with layout information from the [dashboard layout
11 | extention](https://github.com/jupyter/dashboards) and lets you download it or
12 | directly publish it to the experimental [dashboard
13 | server](https://github.com/jupyter-incubator/dashboards_server).
14 |
15 | 
16 |
17 | ## What It Gives You
18 |
19 | * *File → Download as → Jupyter Dashboards Server bundle (.zip)* - menu item to download the current notebook and its associated assets for manual deployment on a [Jupyter Dashboards](https://github.com/jupyter-incubator/dashboards_server) server.
20 | * *File → Deploy as → Dashboard on Jupyter Dashboards Server* - menu item to deploy the current notebook and its associated assets as a dashboard on a target [Jupyter Dashboards](https://github.com/jupyter-incubator/dashboards_server) server.
21 |
22 | ## Prerequisites
23 |
24 | * Jupyter Notebook >=5.0 running on Python 3.x or Python 2.7.x
25 | * Edge, Chrome, Firefox, or Safari
26 |
27 | If you are running an older version of the notebook server, you should use a
28 | version of this package prior to the 0.9 release.
29 |
30 | ## Installing and Enabling
31 |
32 | The following steps install the extension package using `pip` and enable the
33 | extension in the active Python environment.
34 |
35 | ```bash
36 | pip install jupyter_dashboards_bundlers
37 | jupyter bundlerextension enable --sys-prefix --py dashboards_bundlers
38 | ```
39 |
40 | ## Disabling and Uninstalling
41 |
42 | The following steps deactivate the extension in the active Python environment
43 | and uninstall the package using `pip`.
44 |
45 | ```bash
46 | jupyter bundlerextension disable --sys-prefix --py dashboards_bundlers
47 | pip uninstall jupyter_dashboards_bundlers
48 | ```
49 |
50 | ## Use It
51 |
52 | Currently, there are two bundlers available in this package.
53 |
54 | ### Download as → Jupyter Dashboards Server bundle (.zip)
55 |
56 | The first option bundles your notebook and any associated frontend assets into
57 | a zip file which you can manually deploy on a [Jupyter Dashboards
58 | Server](https://github.com/jupyter-incubator/dashboards_server). To use it:
59 |
60 | 1. Write a notebook.
61 | 2. Define a dashboard layout using the `jupyter_dashboards` extension.
62 | 3. If the notebook requires any frontend assets (e.g., CSS files), [associate
63 | them with the
64 | notebook](etc/notebooks/associations_demo/associations_demo.ipynb).
65 | 4. Click *File → Download as → Jupyter Dashboards Server bundle
66 | (.zip)*.
67 | 5. Install
68 | [jupyter-incubator/dashboards_server](https://github.com/jupyter-incubator/dashboards_server)
69 | by following the project README.
70 | 5. Unzip the bundle in the `data/` directory of the Jupyter Dashboard Server
71 | and run it.
72 |
73 | This bundler is compatible with:
74 |
75 | * `jupyter_declarativewidgets>=0.5.0` when deploying dashboards with declarative widgets
76 | * `ipywidgets>=5.0.0,<6.0.0` when deploying dashboards with ipywidgets
77 |
78 | ### Deploy as → Dashboard on Jupyter Dashboards Server
79 |
80 | The second option directly sends your notebook and any associated frontend
81 | assets to a [Jupyter Dashboards
82 | Server](https://github.com/jupyter-incubator/dashboards_server). To use it:
83 |
84 | 0. Run an instance of the Jupyter Dashboards Server by following the
85 | instructions in the
86 | [jupyter-incubator/dashboards_server](https://github.com/jupyter-incubator/dashboards_server)
87 | project README.
88 | 1. Set the following environment variables before launching your Jupyter
89 | Notebook server with the bundler extensions installed.
90 | * `DASHBOARD_SERVER_URL` - protocol, hostname, and port of the dashboard
91 | server to which to send dashboard notebooks
92 | * `DASHBOARD_REDIRECT_URL` (optional) - protocol, hostname, and port to use
93 | when redirecting the user's browser after upload if different from
94 | `DASHBOARD_SERVER_URL` and if upload response has no link property from
95 | the dashboard server (v0.6.0+)
96 | * `DASHBOARD_SERVER_AUTH_TOKEN` (optional) - upload token required by the
97 | dashboard server
98 | * `DASHBOARD_SERVER_NO_SSL_VERIFY` (optional) - skip verification of the
99 | dashboard server SSL certificate (for use in dev / trusted environments
100 | only!)
101 | 2. Write a notebook.
102 | 3. Define a dashboard layout using the `jupyter_dashboards` extension.
103 | 4. If the notebook requires any frontend assets (e.g., CSS files), [associate
104 | them with the
105 | notebook](etc/notebooks/associations_demo/associations_demo.ipynb).
106 | 5. Click *File → Deploy as → Dashboard on Jupyter Dashboard Server*.
107 | 6. Enjoy your dashboard after the redirect.
108 |
109 | This bundler is compatible with:
110 |
111 | * `jupyter_declarativewidgets>=0.5.0` when deploying dashboards with declarative widgets
112 | * `ipywidgets>=5.0.0,<6.0.0` when deploying dashboards with ipywidgets
113 |
114 | ## Caveats
115 |
116 | It is important to realize that kernels launched by your deployed dashboard
117 | will not being running in the same directory or possibly even the same
118 | environment as your original notebook. You must refer to external, kernel-side
119 | resources in a portable manner (e.g., put it in an external data store). You
120 | must also ensure your kernel environment has all the same
121 | libraries installed as your notebook authoring environment.
122 |
123 | It is also your responsibility to associate any frontend, dashboard-side assets
124 | with your notebook before packaging it for deployment. To aid in this task, the
125 | two bundlers here take advantage of the notebook association feature supported
126 | by the notebook bundler API. See the [associations
127 | demo](etc/notebooks/associations_demo/associations_demo.ipynb) for the markup
128 | you can use to refer to external files that should be included in your
129 | dashboard deployment.
130 |
131 | If you are using [declarative
132 | widgets](https://github.com/jupyter-incubator/declarativewidgets) in your
133 | dashboard, you should be mindful of the following when you deploy your
134 | dashboard:
135 |
136 | * You must run the entire notebook successfully before deploying. This action
137 | ensures all external Polymer components are properly installed on the
138 | notebook server and can be bundled with your converted notebook.
139 |
--------------------------------------------------------------------------------
/dashboards_bundlers/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 |
5 | def _jupyter_bundlerextension_paths():
6 | '''API for notebook bundler installation on notebook 5.0+'''
7 | return [{
8 | 'name': 'dashboards_server_upload',
9 | 'label': 'Dashboard on Jupyter Dashboards Server',
10 | 'module_name': 'dashboards_bundlers.server_upload',
11 | 'group': 'deploy'
12 | },
13 | {
14 | 'name': 'dashboards_server_download',
15 | 'label': 'Jupyter Dashboards Server bundle (.zip)',
16 | 'module_name': 'dashboards_bundlers.server_download',
17 | 'group': 'download'
18 | }]
19 |
--------------------------------------------------------------------------------
/dashboards_bundlers/_version.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | version_info = (0, 10, 0, 'dev')
5 | __version__ = '.'.join(map(str, version_info))
6 |
--------------------------------------------------------------------------------
/dashboards_bundlers/server_download.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | import os
5 | import shutil
6 | import tempfile
7 | from .server_upload import make_upload_bundle
8 |
9 |
10 | def bundle(handler, model):
11 | '''
12 | Downloads a notebook, either by itself, or within a zip file with
13 | associated data and widget files, for manual deployment to a Jupyter
14 | Dashboard Server.
15 | '''
16 | # Noteook implementation passes ContentManager models. This bundler
17 | # only works with local files anyway.
18 | abs_nb_path = os.path.join(
19 | handler.settings['contents_manager'].root_dir,
20 | model['path']
21 | )
22 |
23 | # Get name of notebook from filename
24 | notebook_basename = os.path.basename(abs_nb_path)
25 | notebook_name = os.path.splitext(notebook_basename)[0]
26 |
27 | # Python 2/3 compatible try/finally to cleanup a temp working directory
28 | tmp_dir = tempfile.mkdtemp()
29 | try:
30 | output_dir = os.path.join(tmp_dir, notebook_name)
31 | # Reuse the same logic we would use to send a zip file or notebook
32 | # file to a dashboard server, but send it back to the web browser
33 | # not to another server
34 | bundle_path = make_upload_bundle(abs_nb_path, output_dir, handler.tools)
35 |
36 | if bundle_path == abs_nb_path:
37 | # Send the notebook alone: it has no associated resources
38 | handler.set_header('Content-Disposition',
39 | 'attachment; filename="%s"' % notebook_basename)
40 | handler.set_header('Content-Type', 'application/json')
41 | else:
42 | # Send a zip of the notebook and its associated resources
43 | handler.set_header('Content-Disposition',
44 | 'attachment; filename="%s"' % (notebook_name + '.zip'))
45 | handler.set_header('Content-Type', 'application/zip')
46 |
47 | with open(bundle_path, 'rb') as bundle_file:
48 | handler.write(bundle_file.read())
49 | handler.finish()
50 |
51 | finally:
52 | # We read and send synchronously, so we can clean up safely after finish
53 | shutil.rmtree(tmp_dir, True)
54 |
--------------------------------------------------------------------------------
/dashboards_bundlers/server_upload.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | import os
5 | import nbformat
6 | import requests
7 | import shutil
8 | import tempfile
9 | from jupyter_core.paths import jupyter_path
10 | from notebook.utils import url_path_join
11 | from os.path import join as pjoin
12 | from tornado import escape, web
13 | from tornado.log import access_log, app_log
14 |
15 | UPLOAD_ENDPOINT = '/_api/notebooks/'
16 | VIEW_ENDPOINT = '/dashboards/'
17 |
18 |
19 | def skip_ssl_verification():
20 | return os.getenv('DASHBOARD_SERVER_NO_SSL_VERIFY', '').lower() in ['yes', 'true']
21 |
22 |
23 | # Log a warning if SSL verification is off at the outset
24 | if skip_ssl_verification():
25 | app_log.warn('Dashboard server SSL verification disabled')
26 |
27 |
28 | def bundle(handler, model):
29 | '''
30 | Uploads a notebook to a Jupyter Dashboard Server, either by itself, or
31 | within a zip file with associated data and widget files
32 | '''
33 | # Noteook implementation passes ContentManager models. This
34 | # bundler only works with local files anyway.
35 | abs_nb_path = os.path.join(
36 | handler.settings['contents_manager'].root_dir,
37 | model['path']
38 | )
39 |
40 | # Get name of notebook from filename
41 | notebook_basename = os.path.basename(abs_nb_path)
42 | notebook_name = os.path.splitext(notebook_basename)[0]
43 |
44 | # Python 2/3 compatible try/finally to cleanup a temp working directory
45 | tmp_dir = tempfile.mkdtemp()
46 | try:
47 | output_dir = os.path.join(tmp_dir, notebook_name)
48 | bundled = make_upload_bundle(abs_nb_path, output_dir, handler.tools)
49 | send_file(bundled, notebook_name, handler)
50 | finally:
51 | shutil.rmtree(tmp_dir, True)
52 |
53 |
54 | def get_extension_path(*parts):
55 | '''
56 | Searches all known jupyter extension paths for the referenced directory.
57 | Returns the first hit or None if not found.
58 | '''
59 | ext_path = pjoin(*parts)
60 | for root_path in jupyter_path():
61 | full_path = pjoin(root_path, 'nbextensions', ext_path)
62 | if os.path.exists(full_path):
63 | return full_path
64 |
65 |
66 | def bundle_file_references(output_path, notebook_fn, tools):
67 | '''
68 | Looks for files references in the notebook in the manner supported by
69 | notebook.bundler.tools. Adds those files to the output path if found.
70 |
71 | :param output_path: The output path of the dashboard being assembled
72 | :param notebook_fn: The absolute path to the notebook file being packaged
73 | '''
74 | if tools is not None:
75 | referenced_files = tools.get_file_references(notebook_fn, 4)
76 | tools.copy_filelist(os.path.dirname(notebook_fn), output_path,
77 | referenced_files)
78 |
79 |
80 | def bundle_declarative_widgets(output_path, notebook_file, widget_folder='static'):
81 | '''
82 | Adds frontend bower components dependencies into the bundle for the dashboard
83 | application. Creates the following directories under output_path:
84 |
85 | static/urth_widgets: Stores the js for urth_widgets which will be loaded in
86 | the frontend of the dashboard
87 | static/urth_components: The directory for all of the bower components of the
88 | dashboard.
89 |
90 | NOTE: This function is too specific to urth widgets. In the
91 | future we should investigate ways to make this more generic.
92 |
93 | :param output_path: The output path of the dashboard being assembled
94 | :param notebook_file: The absolute path to the notebook file being packaged
95 | :param widget_folder: Subfolder name in which the widgets should be contained.
96 | '''
97 | # Check if any of the cells contain widgets, if not we do not to copy the
98 | # bower_components
99 | notebook = nbformat.read(notebook_file, 4)
100 | # Using find instead of a regex to help future-proof changes that might be
101 | # to how user's will use urth-core-import
102 | # (i.e. vs. )
103 | any_cells_with_widgets = any(cell.get('source').find('urth-core-') != -1
104 | for cell in notebook.cells)
105 | if not any_cells_with_widgets:
106 | return
107 |
108 | # Directory of declarative widgets extension
109 | widgets_dir = get_extension_path('declarativewidgets') or get_extension_path('urth_widgets')
110 | if widgets_dir is None:
111 | raise web.HTTPError(500, 'Missing jupyter_declarativewidgets extension')
112 |
113 | # Root of declarative widgets within a dashboard app
114 | output_widgets_dir = pjoin(output_path, widget_folder, 'urth_widgets/') if widget_folder is not None else pjoin(output_path, 'urth_widgets/')
115 | # JavaScript entry point for widgets in dashboard app
116 | output_js_dir = pjoin(output_widgets_dir, 'js')
117 | # Web referenceable path from which all urth widget components will be served
118 | output_components_dir = pjoin(output_path, widget_folder, 'urth_components/') if widget_folder is not None else pjoin(output_path, 'urth_components/')
119 |
120 | # Copy declarative widgets js and installed bower components into the app
121 | # under output directory
122 | widgets_js_dir = pjoin(widgets_dir, 'js')
123 | shutil.copytree(widgets_js_dir, output_js_dir)
124 |
125 | # Widgets bower components could be under 'urth_components' or
126 | # 'bower_components' depending on the version of widgets being used.
127 | widgets_components_dir = pjoin(widgets_dir, 'urth_components')
128 | if not os.path.isdir(widgets_components_dir):
129 | widgets_components_dir = pjoin(widgets_dir, 'bower_components')
130 |
131 | # Install the widget components into the output components directory
132 | shutil.copytree(widgets_components_dir, output_components_dir)
133 |
134 |
135 | def make_upload_bundle(abs_nb_path, staging_dir, tools):
136 | '''
137 | Assembles the notebook and resources it needs, returning the path to a
138 | zip file bundling the notebook and its requirements if there are any,
139 | the notebook's path otherwise.
140 | :param abs_nb_path: The path to the notebook
141 | :param staging_dir: Temporary work directory, created and removed by the
142 | caller
143 | '''
144 | # Clean up bundle dir if it exists
145 | shutil.rmtree(staging_dir, True)
146 | os.makedirs(staging_dir)
147 |
148 | # Include the notebook as index.ipynb to make the final URL cleaner
149 | # and for consistency
150 | shutil.copy2(abs_nb_path, os.path.join(staging_dir, 'index.ipynb'))
151 | # Include frontend files referenced via the jupyter_cms bundle mechanism
152 | bundle_file_references(staging_dir, abs_nb_path, tools)
153 | bundle_declarative_widgets(staging_dir, abs_nb_path, widget_folder=None)
154 |
155 | # if nothing else was required, indicate to upload the notebook itself
156 | if len(os.listdir(staging_dir)) == 1:
157 | return abs_nb_path
158 |
159 | zip_file = shutil.make_archive(staging_dir, format='zip',
160 | root_dir=staging_dir, base_dir='.')
161 | return zip_file
162 |
163 |
164 | def send_file(file_path, dashboard_name, handler):
165 | '''
166 | Posts a file to the Jupyter Dashboards Server to be served as a dashboard
167 | :param file_path: The path of the file to send
168 | :param dashboard_name: The dashboard name under which it should be made
169 | available
170 | '''
171 | # Make information about the request Host header available for use in
172 | # constructing the urls
173 | segs = handler.request.host.split(':')
174 | hostname = segs[0]
175 | if len(segs) > 1:
176 | port = segs[1]
177 | else:
178 | port = ''
179 | protocol = handler.request.protocol
180 |
181 | # Treat empty as undefined
182 | dashboard_server = os.getenv('DASHBOARD_SERVER_URL')
183 | if dashboard_server:
184 | dashboard_server = dashboard_server.format(protocol=protocol,
185 | hostname=hostname,
186 | port=port)
187 | upload_url = url_path_join(dashboard_server, UPLOAD_ENDPOINT,
188 | escape.url_escape(dashboard_name, False))
189 | with open(file_path, 'rb') as file_content:
190 | headers = {}
191 | token = os.getenv('DASHBOARD_SERVER_AUTH_TOKEN')
192 | if token:
193 | headers['Authorization'] = 'token {}'.format(token)
194 | result = requests.post(upload_url, files={'file': file_content},
195 | headers=headers, timeout=60, verify=not
196 | skip_ssl_verification())
197 | if result.status_code >= 400:
198 | raise web.HTTPError(result.status_code)
199 |
200 | # Redirect to link specified in response body
201 | res_body = result.json()
202 | if 'link' in res_body:
203 | redirect_link = res_body['link']
204 | else:
205 | # Compute redirect link using environment variables
206 | # First try redirect URL as it might be different from
207 | # internal upload URL
208 | redirect_server = os.getenv('DASHBOARD_REDIRECT_URL')
209 | if redirect_server:
210 | redirect_root = redirect_server.format(hostname=hostname,
211 | port=port,
212 | protocol=protocol)
213 | else:
214 | redirect_root = dashboard_server
215 |
216 | redirect_link = url_path_join(redirect_root, VIEW_ENDPOINT,
217 | escape.url_escape(dashboard_name,
218 | False))
219 | handler.redirect(redirect_link)
220 | else:
221 | access_log.debug('Can not deploy, DASHBOARD_SERVER_URL not set')
222 | raise web.HTTPError(500, log_message='No dashboard server configured')
223 |
--------------------------------------------------------------------------------
/etc/bundlers_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jupyter/dashboards_bundlers/886e98ca4c6a8bfc96cf56a8dd0143c20dae7111/etc/bundlers_intro.png
--------------------------------------------------------------------------------
/etc/notebooks/associations_demo/associations_demo.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "extensions": {
7 | "jupyter_dashboards": {
8 | "version": 1,
9 | "views": {
10 | "grid_default": {
11 | "col": 0,
12 | "height": 4,
13 | "hidden": false,
14 | "row": 4,
15 | "width": 4
16 | },
17 | "report_default": {}
18 | }
19 | }
20 | }
21 | },
22 | "source": [
23 | "# Local File Associations"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "metadata": {
29 | "extensions": {
30 | "jupyter_dashboards": {
31 | "version": 1,
32 | "views": {
33 | "grid_default": {
34 | "col": 0,
35 | "height": 4,
36 | "hidden": false,
37 | "row": 8,
38 | "width": 12
39 | },
40 | "report_default": {}
41 | }
42 | }
43 | }
44 | },
45 | "source": [
46 | "This notebook demonstrates two syntaxes for associating local resources (e.g., data files, images) with a notebook. With the `jupyter_dashboards_bundlers` extension installed, you can download the notebook and its associated files in a zip."
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {
52 | "extensions": {
53 | "jupyter_dashboards": {
54 | "version": 1,
55 | "views": {
56 | "grid_default": {
57 | "col": 4,
58 | "height": 4,
59 | "hidden": false,
60 | "row": 4,
61 | "width": 4
62 | },
63 | "report_default": {}
64 | }
65 | }
66 | }
67 | },
68 | "source": [
69 | "## Syntax 1: Hidden Comment"
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "metadata": {
75 | "extensions": {
76 | "jupyter_dashboards": {
77 | "version": 1,
78 | "views": {
79 | "grid_default": {
80 | "col": 0,
81 | "height": 4,
82 | "hidden": false,
83 | "row": 0,
84 | "width": 4
85 | },
86 | "report_default": {
87 | "hidden": false
88 | }
89 | }
90 | }
91 | }
92 | },
93 | "source": [
94 | "To demonstrate the basic feature, let's first load a local CSV file."
95 | ]
96 | },
97 | {
98 | "cell_type": "code",
99 | "execution_count": null,
100 | "metadata": {
101 | "extensions": {
102 | "jupyter_dashboards": {
103 | "version": 1,
104 | "views": {
105 | "grid_default": {
106 | "col": 5,
107 | "height": 4,
108 | "hidden": false,
109 | "row": 0,
110 | "width": 4
111 | },
112 | "report_default": {
113 | "hidden": false
114 | }
115 | }
116 | }
117 | }
118 | },
119 | "outputs": [],
120 | "source": [
121 | "import pandas as pd\n",
122 | "cars = pd.read_csv('data/cars.csv')\n",
123 | "cars"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {
129 | "extensions": {
130 | "jupyter_dashboards": {
131 | "version": 1,
132 | "views": {
133 | "grid_default": {
134 | "col": 0,
135 | "height": 4,
136 | "hidden": false,
137 | "row": 12,
138 | "width": 12
139 | },
140 | "report_default": {}
141 | }
142 | }
143 | }
144 | },
145 | "source": [
146 | "Now let's imagine we want to send this notebook and the CSV file together to another Jupyter Notebook user. We can use the first association syntax, a Markdown/HTML comment, to associate the CSV file with the notebook."
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "metadata": {
152 | "extensions": {
153 | "jupyter_dashboards": {
154 | "version": 1,
155 | "views": {
156 | "grid_default": {
157 | "col": 8,
158 | "height": 4,
159 | "hidden": false,
160 | "row": 4,
161 | "width": 4
162 | },
163 | "report_default": {}
164 | }
165 | }
166 | }
167 | },
168 | "source": [
169 | ""
173 | ]
174 | },
175 | {
176 | "cell_type": "markdown",
177 | "metadata": {
178 | "extensions": {
179 | "jupyter_dashboards": {
180 | "version": 1,
181 | "views": {
182 | "grid_default": {
183 | "col": 0,
184 | "height": 8,
185 | "hidden": false,
186 | "row": 16,
187 | "width": 12
188 | },
189 | "report_default": {}
190 | }
191 | }
192 | }
193 | },
194 | "source": [
195 | "Since this syntax is based on a comment, it is not visible in the rendered notebook document. You must switch a cell to edit mode to see the comment. If you're reading this tutorial in a Jupyter Notebook server, you can double-click the blank cell right above this one to see the markup. If you're not, here's the hidden markup reproduced as a code block for your convenience:\n",
196 | "\n",
197 | "```\n",
198 | "\n",
204 | "```"
205 | ]
206 | },
207 | {
208 | "cell_type": "markdown",
209 | "metadata": {
210 | "extensions": {
211 | "jupyter_dashboards": {
212 | "version": 1,
213 | "views": {
214 | "grid_default": {
215 | "col": 0,
216 | "height": 5,
217 | "hidden": false,
218 | "row": 24,
219 | "width": 12
220 | },
221 | "report_default": {}
222 | }
223 | }
224 | }
225 | },
226 | "source": [
227 | "As you can see, associated filespecs are stated line-by-line according to the same rules used by git (https://git-scm.com/docs/gitignore), with two differences:\n",
228 | "\n",
229 | "1. The filespecs represent files to *include*, not *exclude* as in `.gitignore`.\n",
230 | "2. The filespecs are restricted to paths relative to and rooted at the notebook. Absolute paths and ancestor references (e.g., `../`) are disallowed (and not useful) for portability reasons."
231 | ]
232 | },
233 | {
234 | "cell_type": "markdown",
235 | "metadata": {
236 | "extensions": {
237 | "jupyter_dashboards": {
238 | "version": 1,
239 | "views": {
240 | "grid_default": {
241 | "col": 0,
242 | "height": 4,
243 | "hidden": false,
244 | "row": 29,
245 | "width": 4
246 | },
247 | "report_default": {}
248 | }
249 | }
250 | }
251 | },
252 | "source": [
253 | "## Syntax 2: Fenced Code"
254 | ]
255 | },
256 | {
257 | "cell_type": "markdown",
258 | "metadata": {
259 | "extensions": {
260 | "jupyter_dashboards": {
261 | "version": 1,
262 | "views": {
263 | "grid_default": {
264 | "col": 0,
265 | "height": 23,
266 | "hidden": false,
267 | "row": 33,
268 | "width": 12
269 | },
270 | "report_default": {}
271 | }
272 | }
273 | }
274 | },
275 | "source": [
276 | "Now let's include a reference to a local image using the second supported syntax, one that makes the reference visible in the notebook at all times.\n",
277 | "\n",
278 | "Here's the image:\n",
279 | "\n",
280 | "\n",
281 | "\n",
282 | "And here's the reference expressed as a fenced code block in Markdown:\n",
283 | "\n",
284 | "```\n",
285 | "# Comments are still allowed. Of course, there can be more than one filespec here too.\n",
286 | "images/*.gif\n",
287 | "```\n",
288 | "\n",
289 | "Unlike the comment syntax, this syntax doesn't require a cell for itself."
290 | ]
291 | },
292 | {
293 | "cell_type": "markdown",
294 | "metadata": {
295 | "collapsed": true,
296 | "extensions": {
297 | "jupyter_dashboards": {
298 | "version": 1,
299 | "views": {
300 | "grid_default": {
301 | "col": 0,
302 | "height": 4,
303 | "hidden": false,
304 | "row": 56,
305 | "width": 12
306 | },
307 | "report_default": {}
308 | }
309 | }
310 | }
311 | },
312 | "source": [
313 | "## Bundle It!\n",
314 | "\n",
315 | "With the two file lists above in place, you can now download your notebook and the associated files all together. Click *File → Download As → Jupyter Dashboards Server bundle (.zip)*. The resulting zip retains the directory structure of your workspace here in Jupyter relative to the notebook file."
316 | ]
317 | }
318 | ],
319 | "metadata": {
320 | "extensions": {
321 | "jupyter_dashboards": {
322 | "activeView": "grid_default",
323 | "version": 1,
324 | "views": {
325 | "grid_default": {
326 | "cellMargin": 10,
327 | "defaultCellHeight": 20,
328 | "maxColumns": 12,
329 | "name": "grid",
330 | "type": "grid"
331 | },
332 | "report_default": {
333 | "name": "report",
334 | "type": "report"
335 | }
336 | }
337 | }
338 | },
339 | "kernelspec": {
340 | "display_name": "Python 3",
341 | "language": "python",
342 | "name": "python3"
343 | },
344 | "language_info": {
345 | "codemirror_mode": {
346 | "name": "ipython",
347 | "version": 3
348 | },
349 | "file_extension": ".py",
350 | "mimetype": "text/x-python",
351 | "name": "python",
352 | "nbconvert_exporter": "python",
353 | "pygments_lexer": "ipython3",
354 | "version": "3.6.0"
355 | }
356 | },
357 | "nbformat": 4,
358 | "nbformat_minor": 1
359 | }
360 |
--------------------------------------------------------------------------------
/etc/notebooks/associations_demo/data/cars.csv:
--------------------------------------------------------------------------------
1 | Year,Make,Model,Description,Price
2 | 1997,Ford,E350,"ac, abs, moon",3000.00
3 | 1999,Chevy,"Venture ""Extended Edition""","",4900.00
4 | 1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
5 | 1996,Jeep,Grand Cherokee,"MUST SELL!
6 | air, moon roof, loaded",4799.00
7 |
--------------------------------------------------------------------------------
/etc/notebooks/associations_demo/images/MjeLFmy6Lx8di.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jupyter/dashboards_bundlers/886e98ca4c6a8bfc96cf56a8dd0143c20dae7111/etc/notebooks/associations_demo/images/MjeLFmy6Lx8di.gif
--------------------------------------------------------------------------------
/etc/notebooks/hello_world.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "urth": {
7 | "dashboard": {
8 | "layout": {
9 | "col": 0,
10 | "height": 2,
11 | "row": 0,
12 | "width": 8
13 | }
14 | }
15 | }
16 | },
17 | "source": [
18 | "# Hello World"
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": null,
24 | "metadata": {
25 | "collapsed": false,
26 | "urth": {
27 | "dashboard": {
28 | "layout": {
29 | "col": 0,
30 | "height": 2,
31 | "row": 2,
32 | "width": 5
33 | }
34 | }
35 | }
36 | },
37 | "outputs": [],
38 | "source": [
39 | "print('Tick tock goes the kernel clock ...')"
40 | ]
41 | },
42 | {
43 | "cell_type": "code",
44 | "execution_count": null,
45 | "metadata": {
46 | "collapsed": true,
47 | "urth": {
48 | "dashboard": {}
49 | }
50 | },
51 | "outputs": [],
52 | "source": [
53 | "from IPython.display import HTML, display, clear_output\n",
54 | "import time"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "metadata": {
61 | "collapsed": true,
62 | "urth": {
63 | "dashboard": {}
64 | }
65 | },
66 | "outputs": [],
67 | "source": [
68 | "start = 0x1f550"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "metadata": {
75 | "collapsed": false,
76 | "urth": {
77 | "dashboard": {
78 | "layout": {
79 | "col": 5,
80 | "height": 2,
81 | "row": 2,
82 | "width": 4
83 | }
84 | }
85 | }
86 | },
87 | "outputs": [],
88 | "source": [
89 | "for i in range(11):\n",
90 | " clear_output()\n",
91 | " display(HTML('{};'.format(start + i)))\n",
92 | " time.sleep(0.5)"
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": null,
98 | "metadata": {
99 | "collapsed": true
100 | },
101 | "outputs": [],
102 | "source": []
103 | }
104 | ],
105 | "metadata": {
106 | "kernelspec": {
107 | "display_name": "Python 3",
108 | "language": "python",
109 | "name": "python3"
110 | },
111 | "language_info": {
112 | "codemirror_mode": {
113 | "name": "ipython",
114 | "version": 3
115 | },
116 | "file_extension": ".py",
117 | "mimetype": "text/x-python",
118 | "name": "python",
119 | "nbconvert_exporter": "python",
120 | "pygments_lexer": "ipython3",
121 | "version": "3.4.3"
122 | },
123 | "urth": {
124 | "dashboard": {
125 | "cellMargin": 10,
126 | "defaultCellHeight": 20,
127 | "layoutStrategy": "packed",
128 | "maxColumns": 12
129 | }
130 | }
131 | },
132 | "nbformat": 4,
133 | "nbformat_minor": 0
134 | }
135 |
--------------------------------------------------------------------------------
/etc/notebooks/hello_world_report.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "urth": {
7 | "dashboard": {}
8 | }
9 | },
10 | "source": [
11 | "# Hello World"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "metadata": {
18 | "collapsed": false,
19 | "urth": {
20 | "dashboard": {
21 | "layout": {}
22 | }
23 | }
24 | },
25 | "outputs": [],
26 | "source": [
27 | "print('Tick tock goes the kernel clock ...')"
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": null,
33 | "metadata": {
34 | "collapsed": true,
35 | "urth": {
36 | "dashboard": {
37 | "hidden": true
38 | }
39 | }
40 | },
41 | "outputs": [],
42 | "source": [
43 | "from IPython.display import HTML, display, clear_output\n",
44 | "import time"
45 | ]
46 | },
47 | {
48 | "cell_type": "code",
49 | "execution_count": null,
50 | "metadata": {
51 | "collapsed": true,
52 | "urth": {
53 | "dashboard": {
54 | "hidden": true
55 | }
56 | }
57 | },
58 | "outputs": [],
59 | "source": [
60 | "start = 0x1f550"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "metadata": {
67 | "collapsed": false,
68 | "urth": {
69 | "dashboard": {
70 | "layout": {}
71 | }
72 | }
73 | },
74 | "outputs": [],
75 | "source": [
76 | "for i in range(11):\n",
77 | " clear_output()\n",
78 | " display(HTML('{};'.format(start + i)))\n",
79 | " time.sleep(0.5)"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "metadata": {
86 | "collapsed": true,
87 | "urth": {
88 | "dashboard": {
89 | "hidden": true
90 | }
91 | }
92 | },
93 | "outputs": [],
94 | "source": []
95 | }
96 | ],
97 | "metadata": {
98 | "kernelspec": {
99 | "display_name": "Python 3",
100 | "language": "python",
101 | "name": "python3"
102 | },
103 | "language_info": {
104 | "codemirror_mode": {
105 | "name": "ipython",
106 | "version": 3
107 | },
108 | "file_extension": ".py",
109 | "mimetype": "text/x-python",
110 | "name": "python",
111 | "nbconvert_exporter": "python",
112 | "pygments_lexer": "ipython3",
113 | "version": "3.4.4"
114 | },
115 | "urth": {
116 | "dashboard": {
117 | "layout": "report"
118 | }
119 | }
120 | },
121 | "nbformat": 4,
122 | "nbformat_minor": 0
123 | }
124 |
--------------------------------------------------------------------------------
/etc/notebooks/widget_binding.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "urth": {
7 | "dashboard": {
8 | "layout": {
9 | "col": 0,
10 | "height": 4,
11 | "row": 0,
12 | "width": 4
13 | }
14 | }
15 | }
16 | },
17 | "source": [
18 | "Enter text in the box. The label should update."
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": null,
24 | "metadata": {
25 | "collapsed": false,
26 | "urth": {
27 | "dashboard": {
28 | "hidden": true,
29 | "layout": {}
30 | }
31 | }
32 | },
33 | "outputs": [],
34 | "source": [
35 | "try:\n",
36 | " # declwidgets 0.6.0+\n",
37 | " import declarativewidgets\n",
38 | " declarativewidgets.init()\n",
39 | "except ImportError:\n",
40 | " # declwidgets<0.6.0\n",
41 | " pass"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": null,
47 | "metadata": {
48 | "collapsed": false,
49 | "urth": {
50 | "dashboard": {
51 | "layout": {
52 | "col": 4,
53 | "height": 4,
54 | "row": 0,
55 | "width": 5
56 | }
57 | }
58 | }
59 | },
60 | "outputs": [],
61 | "source": [
62 | "%%html\n",
63 | "\n",
64 | " Hello {{user}}
\n",
65 | " Name: \n",
66 | ""
67 | ]
68 | }
69 | ],
70 | "metadata": {
71 | "kernelspec": {
72 | "display_name": "Python 3",
73 | "language": "python",
74 | "name": "python3"
75 | },
76 | "language_info": {
77 | "codemirror_mode": {
78 | "name": "ipython",
79 | "version": 3
80 | },
81 | "file_extension": ".py",
82 | "mimetype": "text/x-python",
83 | "name": "python",
84 | "nbconvert_exporter": "python",
85 | "pygments_lexer": "ipython3",
86 | "version": "3.5.1"
87 | },
88 | "urth": {
89 | "dashboard": {
90 | "cellMargin": 10,
91 | "defaultCellHeight": 20,
92 | "layout": "grid",
93 | "layoutStrategy": "packed",
94 | "maxColumns": 12
95 | }
96 | },
97 | "widgets": {
98 | "state": {},
99 | "version": "1.1.2"
100 | }
101 | },
102 | "nbformat": 4,
103 | "nbformat_minor": 0
104 | }
105 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | notebook>=5.0
2 | requests>=2.7
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | import os
5 | from setuptools import setup
6 |
7 | # Get location of this file at runtime
8 | HERE = os.path.abspath(os.path.dirname(__file__))
9 |
10 | # Eval the version tuple and string from the source
11 | VERSION_NS = {}
12 | with open(os.path.join(HERE, 'dashboards_bundlers/_version.py')) as f:
13 | exec(f.read(), {}, VERSION_NS)
14 |
15 | setup_args = dict(
16 | name='jupyter_dashboards_bundlers',
17 | author='Jupyter Development Team',
18 | author_email='jupyter@googlegroups.com',
19 | description='Plugins for jupyter_cms to deploy and download notebooks as dashboard apps',
20 | long_description='''
21 | This package adds a *Deploy as* and *Download as* menu items for bundling
22 | notebooks created using jupyter_dashboards as standalone web applications.
23 |
24 | See `the project README `_
25 | for more information.
26 | ''',
27 | url='https://github.com/jupyter-incubator/dashboards_bundlers',
28 | version=VERSION_NS['__version__'],
29 | license='BSD',
30 | platforms=['Jupyter Notebook 5.x'],
31 | packages=[
32 | 'dashboards_bundlers',
33 | ],
34 | include_package_data=True,
35 | install_requires=[
36 | 'requests>=2.7',
37 | 'notebook>=5.0'
38 | ],
39 | classifiers=[
40 | 'Intended Audience :: Developers',
41 | 'Intended Audience :: System Administrators',
42 | 'Intended Audience :: Science/Research',
43 | 'License :: OSI Approved :: BSD License',
44 | 'Programming Language :: Python',
45 | 'Programming Language :: Python :: 2.7',
46 | 'Programming Language :: Python :: 3.3',
47 | 'Programming Language :: Python :: 3.4',
48 | 'Programming Language :: Python :: 3.5',
49 | 'Programming Language :: Python :: 3.6'
50 | ]
51 | )
52 |
53 | if __name__ == '__main__':
54 | setup(**setup_args)
55 |
--------------------------------------------------------------------------------
/test/resources/env.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "urth": {
7 | "dashboard": {
8 | "layout": {
9 | "col": 0,
10 | "height": 4,
11 | "row": 0,
12 | "width": 4
13 | }
14 | }
15 | }
16 | },
17 | "source": [
18 | "# Test clear_display"
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": null,
24 | "metadata": {
25 | "collapsed": false,
26 | "urth": {
27 | "dashboard": {
28 | "hidden": true
29 | }
30 | }
31 | },
32 | "outputs": [],
33 | "source": [
34 | "%matplotlib inline"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": null,
40 | "metadata": {
41 | "collapsed": false,
42 | "urth": {
43 | "dashboard": {
44 | "hidden": true
45 | }
46 | }
47 | },
48 | "outputs": [],
49 | "source": [
50 | "import matplotlib.pyplot as plt\n",
51 | "from IPython.html.widgets import *\n",
52 | "import numpy as np"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "metadata": {
59 | "collapsed": false,
60 | "urth": {
61 | "dashboard": {
62 | "layout": {
63 | "col": 0,
64 | "height": 12,
65 | "row": 4,
66 | "width": 5
67 | }
68 | }
69 | }
70 | },
71 | "outputs": [],
72 | "source": [
73 | "@interact(points=['-', '10', '20', '30'])\n",
74 | "def render(points=None):\n",
75 | " if points == '-': return\n",
76 | " points = int(points)\n",
77 | " fig, ax = plt.subplots()\n",
78 | " x = np.random.randn(points)\n",
79 | " y = np.random.randn(points)\n",
80 | " ax.scatter(x, y)"
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": null,
86 | "metadata": {
87 | "collapsed": true,
88 | "urth": {
89 | "dashboard": {
90 | "hidden": true
91 | }
92 | }
93 | },
94 | "outputs": [],
95 | "source": ["urth-core-import"]
96 | }
97 | ],
98 | "metadata": {
99 | "kernelspec": {
100 | "display_name": "Python 2",
101 | "language": "python",
102 | "name": "python2"
103 | },
104 | "language_info": {
105 | "codemirror_mode": {
106 | "name": "ipython",
107 | "version": 3
108 | },
109 | "file_extension": ".py",
110 | "mimetype": "text/x-python",
111 | "name": "python",
112 | "nbconvert_exporter": "python",
113 | "pygments_lexer": "ipython2",
114 | "version": "2.7.10"
115 | },
116 | "urth": {
117 | "dashboard": {
118 | "cellMargin": 10,
119 | "defaultCellHeight": 20,
120 | "maxColumns": 12,
121 | "max_cols": 4,
122 | "max_rows": 17
123 | }
124 | }
125 | },
126 | "nbformat": 4,
127 | "nbformat_minor": 0
128 | }
129 |
--------------------------------------------------------------------------------
/test/resources/no_imports.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "urth": {
7 | "dashboard": {
8 | "layout": {
9 | "col": 0,
10 | "height": 4,
11 | "row": 0,
12 | "width": 4
13 | }
14 | }
15 | }
16 | },
17 | "source": [
18 | "# Test clear_display"
19 | ]
20 | },
21 | {
22 | "cell_type": "code",
23 | "execution_count": null,
24 | "metadata": {
25 | "collapsed": false,
26 | "urth": {
27 | "dashboard": {
28 | "hidden": true
29 | }
30 | }
31 | },
32 | "outputs": [],
33 | "source": [
34 | "%matplotlib inline"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": null,
40 | "metadata": {
41 | "collapsed": false,
42 | "urth": {
43 | "dashboard": {
44 | "hidden": true
45 | }
46 | }
47 | },
48 | "outputs": [],
49 | "source": [
50 | "import matplotlib.pyplot as plt\n",
51 | "from IPython.html.widgets import *\n",
52 | "import numpy as np"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "metadata": {
59 | "collapsed": false,
60 | "urth": {
61 | "dashboard": {
62 | "layout": {
63 | "col": 0,
64 | "height": 12,
65 | "row": 4,
66 | "width": 5
67 | }
68 | }
69 | }
70 | },
71 | "outputs": [],
72 | "source": [
73 | "@interact(points=['-', '10', '20', '30'])\n",
74 | "def render(points=None):\n",
75 | " if points == '-': return\n",
76 | " points = int(points)\n",
77 | " fig, ax = plt.subplots()\n",
78 | " x = np.random.randn(points)\n",
79 | " y = np.random.randn(points)\n",
80 | " ax.scatter(x, y)"
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": null,
86 | "metadata": {
87 | "collapsed": true,
88 | "urth": {
89 | "dashboard": {
90 | "hidden": true
91 | }
92 | }
93 | },
94 | "outputs": [],
95 | "source": []
96 | }
97 | ],
98 | "metadata": {
99 | "kernelspec": {
100 | "display_name": "Python 3",
101 | "language": "python",
102 | "name": "python3"
103 | },
104 | "language_info": {
105 | "codemirror_mode": {
106 | "name": "ipython",
107 | "version": 3
108 | },
109 | "file_extension": ".py",
110 | "mimetype": "text/x-python",
111 | "name": "python",
112 | "nbconvert_exporter": "python",
113 | "pygments_lexer": "ipython3",
114 | "version": "3.4.3"
115 | },
116 | "urth": {
117 | "dashboard": {
118 | "cellMargin": 10,
119 | "defaultCellHeight": 20,
120 | "maxColumns": 12,
121 | "max_cols": 4,
122 | "max_rows": 17
123 | }
124 | }
125 | },
126 | "nbformat": 4,
127 | "nbformat_minor": 0
128 | }
129 |
--------------------------------------------------------------------------------
/test/resources/some.csv:
--------------------------------------------------------------------------------
1 | hello,world
2 |
--------------------------------------------------------------------------------
/test/resources/some.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "collapsed": true
7 | },
8 | "source": [
9 | "```\n",
10 | "some.csv\n",
11 | "```"
12 | ]
13 | }
14 | ],
15 | "metadata": {
16 | "kernelspec": {
17 | "display_name": "Python 3",
18 | "language": "python",
19 | "name": "python3"
20 | },
21 | "language_info": {
22 | "codemirror_mode": {
23 | "name": "ipython",
24 | "version": 3
25 | },
26 | "file_extension": ".py",
27 | "mimetype": "text/x-python",
28 | "name": "python",
29 | "nbconvert_exporter": "python",
30 | "pygments_lexer": "ipython3",
31 | "version": "3.4.3"
32 | }
33 | },
34 | "nbformat": 4,
35 | "nbformat_minor": 0
36 | }
37 |
--------------------------------------------------------------------------------
/test/test_server_download.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | import shutil
5 | import tempfile
6 | import unittest
7 | from os.path import join as pjoin, isdir
8 |
9 | import dashboards_bundlers.server_download as converter
10 | import notebook.bundler.tools
11 |
12 |
13 | class MockContentsManager(object):
14 | def __init__(self):
15 | self.root_dir = '.'
16 |
17 |
18 | class MockHandler(object):
19 | def __init__(self, notebook_dir):
20 | self.settings = {
21 | 'base_url': '/',
22 | 'contents_manager': MockContentsManager()
23 | }
24 | self.headers = {}
25 | self.request = type('HTTPRequest', (object,), {
26 | 'protocol': 'http',
27 | 'host': 'fake-host:5555'
28 | })
29 | self.written = False
30 | self.finished = False
31 | self.tools = notebook.bundler.tools
32 |
33 | def set_header(self, name, value):
34 | self.headers[name] = value
35 |
36 | def write(self, *args):
37 | self.written = True
38 |
39 | def finish(self):
40 | self.finished = True
41 |
42 |
43 | class TestServerDownload(unittest.TestCase):
44 | def setUp(self):
45 | self.tmp = tempfile.mkdtemp()
46 |
47 | def tearDown(self):
48 | shutil.rmtree(self.tmp, ignore_errors=True)
49 |
50 | def test_bundle_ipynb(self):
51 | '''Should initialize an ipynb file download.'''
52 | handler = MockHandler(self.tmp)
53 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
54 |
55 | output_dir = pjoin(self.tmp, 'no_imports')
56 | self.assertFalse(isdir(output_dir),
57 | 'app directory should no longer exist')
58 | self.assertTrue(handler.written, 'data should be written')
59 | self.assertTrue(handler.finished, 'response should be finished')
60 | self.assertIn('application/json', handler.headers['Content-Type'],
61 | 'headers should set json content type')
62 | self.assertIn('no_imports.ipynb',
63 | handler.headers['Content-Disposition'],
64 | 'headers should name the ipynb file')
65 |
66 | def test_bundle_zip(self):
67 | '''Should bundle and initiate a zip file download.'''
68 | handler = MockHandler(self.tmp)
69 | converter.bundle(handler, {'path': 'test/resources/some.ipynb'})
70 |
71 | output_dir = pjoin(self.tmp, 'some')
72 | self.assertFalse(isdir(output_dir),
73 | 'app directory should no longer exist')
74 | self.assertTrue(handler.written, 'data should be written')
75 | self.assertTrue(handler.finished, 'response should be finished')
76 | self.assertIn('application/zip', handler.headers['Content-Type'],
77 | 'headers should set zip content type')
78 | self.assertIn('some.zip', handler.headers['Content-Disposition'],
79 | 'headers should name the zip file')
80 |
--------------------------------------------------------------------------------
/test/test_server_upload.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) Jupyter Development Team.
2 | # Distributed under the terms of the Modified BSD License.
3 |
4 | import copy
5 | import errno
6 | import os
7 | import shutil
8 | import tempfile
9 | import unittest
10 | import zipfile
11 | from os.path import exists, join as pjoin
12 |
13 | import dashboards_bundlers.server_upload as converter
14 | import notebook.bundler.tools
15 | from jupyter_core.paths import jupyter_data_dir
16 | from tornado import web
17 |
18 | dashboard_link = 'http://notebook-server:3000/dashboards/test'
19 |
20 |
21 | class MockResult(object):
22 | def __init__(self, status_code, include_link=True):
23 | self.status_code = status_code
24 | if (include_link):
25 | self.json = lambda: {'link': dashboard_link}
26 | else:
27 | self.json = lambda: {}
28 |
29 |
30 | class MockPost(object):
31 | def __init__(self, status_code, include_result_link=True):
32 | self.args = None
33 | self.kwargs = None
34 | self.status_code = status_code
35 | self.include_result_link = include_result_link
36 |
37 | def __call__(self, *args, **kwargs):
38 | if self.args or self.kwargs:
39 | raise RuntimeError('MockPost already invoked')
40 | self.args = args
41 | self.kwargs = kwargs
42 | return MockResult(self.status_code, self.include_result_link)
43 |
44 |
45 | class MockZipPost(object):
46 | '''
47 | Explicitly checks for a posted zip file
48 | '''
49 | def __init__(self, status_code):
50 | self.args = None
51 | self.kwargs = None
52 | self.status_code = status_code
53 |
54 | def __call__(self, *args, **kwargs):
55 | if self.args or self.kwargs:
56 | raise RuntimeError('MockZipPost already invoked')
57 | self.args = args
58 | self.kwargs = kwargs
59 | uploaded_zip = zipfile.ZipFile(kwargs['files']['file'], 'r')
60 | self.zipped_files = uploaded_zip.namelist()
61 | return MockResult(self.status_code)
62 |
63 |
64 | class MockRequest(object):
65 | def __init__(self, host, protocol):
66 | self.host = host
67 | self.protocol = protocol
68 |
69 |
70 | class MockContentsManager(object):
71 | def __init__(self):
72 | self.root_dir = '.'
73 |
74 |
75 | class MockHandler(object):
76 | def __init__(self, host='notebook-server:8888', protocol='http'):
77 | self.settings = {
78 | 'base_url': '/',
79 | 'contents_manager': MockContentsManager()
80 | }
81 | self.request = MockRequest(host, protocol)
82 | self.last_redirect = None
83 | self.tools = notebook.bundler.tools
84 |
85 | def redirect(self, location):
86 | self.last_redirect = location
87 |
88 |
89 | class TestServerUpload(unittest.TestCase):
90 | def setUp(self):
91 | self.origin_env = copy.deepcopy(os.environ)
92 | converter.requests.post = MockPost(200)
93 |
94 | def tearDown(self):
95 | os.environ = self.origin_env
96 |
97 | def test_no_server(self):
98 | '''Should error if no server URL is set.'''
99 | handler = MockHandler('fake-host:8000', 'http')
100 | self.assertRaises(web.HTTPError, converter.bundle, handler,
101 | {'path': 'test/resources/no_imports.ipynb'})
102 |
103 | def test_upload_notebook(self):
104 | '''Should POST the notebook and redirect to the dashboard server.'''
105 | os.environ['DASHBOARD_SERVER_URL'] = 'http://dashboard-server'
106 | handler = MockHandler()
107 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
108 |
109 | args = converter.requests.post.args
110 | kwargs = converter.requests.post.kwargs
111 | self.assertEqual(args[0],
112 | 'http://dashboard-server/_api/notebooks/no_imports')
113 | self.assertTrue(kwargs['files']['file'])
114 | self.assertEqual(kwargs['headers'], {})
115 | self.assertEqual(handler.last_redirect, dashboard_link)
116 |
117 | def test_upload_zip(self):
118 | '''
119 | Should POST the notebook in a zip with resources and redirect to
120 | the dashboard server.
121 | '''
122 | os.environ['DASHBOARD_SERVER_URL'] = 'http://dashboard-server'
123 | handler = MockHandler()
124 | converter.requests.post = MockZipPost(200)
125 | converter.bundle(handler, {'path': 'test/resources/some.ipynb'})
126 |
127 | args = converter.requests.post.args
128 | kwargs = converter.requests.post.kwargs
129 | self.assertEqual(args[0],
130 | 'http://dashboard-server/_api/notebooks/some')
131 | self.assertTrue(kwargs['files']['file'])
132 | self.assertEqual(kwargs['headers'], {})
133 | self.assertEqual(handler.last_redirect, dashboard_link)
134 | self.assertTrue('index.ipynb' in converter.requests.post.zipped_files)
135 | self.assertTrue('some.csv' in converter.requests.post.zipped_files)
136 |
137 | def test_upload_token(self):
138 | '''Should include an auth token in the request.'''
139 | os.environ['DASHBOARD_SERVER_URL'] = 'http://dashboard-server'
140 | os.environ['DASHBOARD_SERVER_AUTH_TOKEN'] = 'fake-token'
141 | handler = MockHandler()
142 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
143 |
144 | kwargs = converter.requests.post.kwargs
145 | self.assertEqual(kwargs['headers'],
146 | {'Authorization': 'token fake-token'})
147 |
148 | def test_url_interpolation(self):
149 | '''Should build the server URL from the request Host header.'''
150 | os.environ['DASHBOARD_SERVER_URL'] = '{protocol}://{hostname}:8889'
151 | handler = MockHandler('notebook-server:8888', 'https')
152 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
153 |
154 | args = converter.requests.post.args
155 | self.assertEqual(args[0],
156 | 'https://notebook-server:8889/_api/notebooks/no_imports')
157 | self.assertEqual(handler.last_redirect, dashboard_link)
158 |
159 | def test_redirect_fallback(self):
160 | '''Should redirect to the given URL'''
161 | converter.requests.post = MockPost(200, False)
162 | os.environ['DASHBOARD_SERVER_URL'] = '{protocol}://{hostname}:8889'
163 | os.environ['DASHBOARD_REDIRECT_URL'] = 'http://{hostname}:3000'
164 | handler = MockHandler('notebook-server:8888', 'https')
165 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
166 |
167 | args = converter.requests.post.args
168 | self.assertEqual(args[0],
169 | 'https://notebook-server:8889/_api/notebooks/no_imports')
170 | self.assertEqual(handler.last_redirect,
171 | 'http://notebook-server:3000/dashboards/no_imports')
172 |
173 | def test_ssl_verify(self):
174 | '''Should verify SSL certificate by default.'''
175 | handler = MockHandler()
176 | os.environ['DASHBOARD_SERVER_URL'] = '{protocol}://{hostname}:8889'
177 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
178 | kwargs = converter.requests.post.kwargs
179 | self.assertEqual(kwargs['verify'], True)
180 |
181 | def test_no_ssl_verify(self):
182 | '''Should skip SSL certificate verification.'''
183 | os.environ['DASHBOARD_SERVER_NO_SSL_VERIFY'] = 'yes'
184 | os.environ['DASHBOARD_SERVER_URL'] = '{protocol}://{hostname}:8889'
185 | handler = MockHandler()
186 | converter.bundle(handler, {'path': 'test/resources/no_imports.ipynb'})
187 | kwargs = converter.requests.post.kwargs
188 | self.assertEqual(kwargs['verify'], False)
189 |
190 |
191 | # Mock existence of declarative widgets
192 | DECL_WIDGETS_DIR = pjoin(jupyter_data_dir(), 'nbextensions/urth_widgets/')
193 | DECL_WIDGETS_JS_DIR = pjoin(DECL_WIDGETS_DIR, 'js')
194 | DECL_VIZ_DIR = pjoin(DECL_WIDGETS_DIR, 'components/urth-viz')
195 | DECL_CORE_DIR = pjoin(DECL_WIDGETS_DIR, 'components/urth-core')
196 | BOWER_COMPONENT_DIR = pjoin(jupyter_data_dir(),
197 | 'nbextensions/urth_widgets/urth_components/component-a')
198 |
199 |
200 | class TestBundleWidgets(unittest.TestCase):
201 | @classmethod
202 | def setUpClass(cls):
203 | for d in (DECL_WIDGETS_DIR, DECL_WIDGETS_JS_DIR, DECL_CORE_DIR,
204 | DECL_VIZ_DIR, BOWER_COMPONENT_DIR):
205 | try:
206 | os.makedirs(d)
207 | except OSError as ex:
208 | if ex.errno != errno.EEXIST:
209 | raise
210 |
211 | def setUp(self):
212 | self.tmp = tempfile.mkdtemp()
213 |
214 | def tearDown(self):
215 | shutil.rmtree(self.tmp, ignore_errors=True)
216 |
217 | def test_bundle_declarative_widgets(self):
218 | '''Should write declarative widgets to output.'''
219 | converter.bundle_declarative_widgets(self.tmp,
220 | 'test/resources/env.ipynb')
221 | self.assertTrue(exists(pjoin(self.tmp, 'static/urth_widgets')),
222 | 'urth_widgets should exist')
223 | self.assertTrue(exists(pjoin(self.tmp, 'static/urth_components')),
224 | 'urth_components should exist')
225 |
226 | def test_skip_declarative_widgets(self):
227 | '''Should not write declarative widgets to output.'''
228 | # Testing to make sure we do not add bower components unnecessarily
229 | converter.bundle_declarative_widgets(self.tmp,
230 | 'test/resources/no_imports.ipynb')
231 | self.assertFalse(exists(pjoin(self.tmp, 'static/urth_widgets')),
232 | 'urth_widgets should not exist')
233 | self.assertFalse(exists(pjoin(self.tmp, 'static/urth_components')),
234 | 'urth_components should not exist')
235 |
--------------------------------------------------------------------------------