├── .gitignore ├── CONTRIBUTING.md ├── JupyterHub + Kubernetes.pdf ├── JupyterHub.pdf ├── LICENSE ├── README.md ├── docs ├── JupyterHub-Tutorial.ipynb ├── Makefile ├── authenticators.ipynb ├── conf.py ├── environment.yml ├── img │ └── spawn-form.png ├── index.rst ├── jhubcheatsheet.md ├── make.bat ├── rest-api.ipynb ├── spawners.ipynb └── timeline.md ├── env ├── environment.yml ├── jupytercon └── JupyterHub-jupytercon-part1.pdf ├── jupyterhub_config.py-docker ├── jupyterhub_config.py-localgithub ├── jupyterhub_config.py-ssl ├── readthedocs.yml └── supervisor ├── jupyterhub.conf └── launch.sh /.gitignore: -------------------------------------------------------------------------------- 1 | jupyterhub.sqlite 2 | jupyterhub_cookie_secret 3 | ./docs/_build* 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome! As a [Jupyter](https://jupyter.org) project, we follow the [Jupyter contributor guide](https://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html). 4 | -------------------------------------------------------------------------------- /JupyterHub + Kubernetes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterhub/jupyterhub-tutorial/2537fdbc5707114077e3128ce3a37052f1eafe02/JupyterHub + Kubernetes.pdf -------------------------------------------------------------------------------- /JupyterHub.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6eda9877dab89478a9d6a6ffc7bbcfc73eb8bc481220702d2cf35e09c9d1d400 3 | size 713780 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Project Jupyter Contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with JupyterHub tutorial 2 | 3 | **Note: The [Zero to JupyterHub documentation](https://zero-to-jupyterhub.readthedocs.io) 4 | has a step-by-step walkthrough of setting up JupyterHub.** 5 | 6 | Updated for JupyterCon 2017 7 | by Carol Willing, Min Ragan-Kelley, Ryan , Yuvi Panda 8 | 9 | Follow along with [the PDF](./JupyterHub.pdf) 10 | or watch [the video on YouTube](https://youtu.be/gSVvxOchT8Y). 11 | 12 | Clone this repo: 13 | 14 | git clone https://github.com/jupyterhub/jupyterhub-tutorial /srv/jupyterhub 15 | 16 | You will need: 17 | 18 | - conda 19 | - jupyterhub 20 | - root access to a server or VM and/or docker 21 | 22 | Quickest way to get jupyterhub is to run in this repo: 23 | 24 | conda env create -f environment.yml 25 | 26 | The latter half of the tutorial will use [Docker](https://docs.docker.com). 27 | -------------------------------------------------------------------------------- /docs/JupyterHub-Tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Getting Started with JupyterHub Tutorial\n", 8 | "\n", 9 | "## Resources\n", 10 | "- [JupyterHub Documentation](https://jupyterhub.readthedocs.io)\n", 11 | "- [the PDF of PyData London 2016 slidedeck](https://github.com/minrk/jupyterhub-pydata-2016/blob/master/JupyterHub.pdf)\n", 12 | "- [the video on YouTube of PyData London 2016 tutorial](https://www.youtube.com/watch?v=LkgSCjyv75s)\n", 13 | "- [Timeline of video](./timeline.md)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": {}, 19 | "source": [ 20 | "### Introduction\n", 21 | "0:00:00 Welcome and Intro\n", 22 | "\n", 23 | "0:01:00 GitHub repo that accompanies the talk\n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "### Review of Notebook and Notebook Server\n", 31 | "\n", 32 | "0:01:44 What is the Notebook?\n", 33 | "\n", 34 | "0:03:00 What is a Notebook Server?" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "### Overview of JupyterHub\n", 42 | "\n", 43 | "0:04:17 JupyterHub\n", 44 | "\n", 45 | "0:05:41 Login\n", 46 | "\n", 47 | "0:05:55 Spawner\n", 48 | "\n", 49 | "0:06:27 Proxy\n", 50 | "\n", 51 | "0:07:11 Redirect user\n", 52 | "\n", 53 | "0:07:17 Browser to ask hub for auth\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "### Installation\n", 61 | "\n", 62 | "\n", 63 | "0:07:56 Installation (as admin)\n", 64 | "\n", 65 | "0:10:44 Installation (this repo)\n", 66 | "\n", 67 | "0:11:18 Installation: Caveats\n", 68 | "\n", 69 | "0:12:18 conda-forge\n", 70 | "\n", 71 | "- community maintained conda packages\n", 72 | "- Add conda-forge to default conda sources\n", 73 | "\n", 74 | "0:13:38 Installation docker (covered later on)\n" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": {}, 80 | "source": [ 81 | "### JupyterHub Defaults\n", 82 | "\n", 83 | "0:14:18 JupyterHub Defaults\n", 84 | "\n", 85 | "- Default behavior\n", 86 | "- Auth: PAM\n", 87 | "- Spawning: Local users\n", 88 | "- Hub run as root (alternative: sudospawner is fraught with peril)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "### Security and SSL\n", 96 | "\n", 97 | "0:15:12 Type jupyterhub in terminal\n", 98 | "\n", 99 | "- message returned that the hub will not start since there is no SSL provisioned\n", 100 | "- If you want to run without SSL, do so at your own risk.\n", 101 | "\n", 102 | "0:16:26 SSL\n", 103 | "\n", 104 | "- Use a self signed cert\n", 105 | "- Let's Encrypt 0:17:30" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "### Configuration of Hub\n", 113 | "\n", 114 | "0:18:12 Configure jupyterhub\n", 115 | "\n", 116 | "- create file\n", 117 | "- edit config file 0:19:12\n", 118 | "\n", 119 | "0:20:33 Connect to hub publicly\n", 120 | "\n", 121 | "- login, spawn server, redirect\n", 122 | "- control panel 0:21:20\n", 123 | "\n" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": {}, 129 | "source": [ 130 | "### Kernels and Programming Languages\n", 131 | "\n", 132 | "0:21:52 Installing kernels for all users" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "### Authentication\n", 140 | "\n", 141 | "0:24:15 Using GitHub OAuth\n", 142 | "\n", 143 | "- We have simple PAM; tell server to use GitHub OAuth\n", 144 | "- Authorization callback URL\n", 145 | "- Client ID\n", 146 | "- Client Secret\n", 147 | "\n", 148 | "- ./env -> export the variables\n", 149 | "\n", 150 | "0:30:44 Tell Jupyter to use oauthenticator\n", 151 | "\n", 152 | "0:32:48 Sign in with GitHub\n", 153 | "\n", 154 | "0:34:20 Specifying users\n", 155 | "\n", 156 | "- PAM ok\n", 157 | "- GitHub probably not ok\n", 158 | "- user whitelist - put in a python set in config file\n", 159 | "- admin users - put in a python set in config file\n" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "### Custom Authenticators\n", 167 | "\n", 168 | "0:36:26 JupyterHub Custom Authenticators\n", 169 | "\n", 170 | "- PAM - form based fairly simple\n", 171 | "- Secure Authenticator\n", 172 | "- jupyterhub hashing salted functions" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "### Spawners - DockerSpawner\n", 180 | "\n", 181 | "0:42:14 Using DockerSpawner\n", 182 | "\n", 183 | "- netifaces - python convenience library\n", 184 | "- local GH to general GH users\n", 185 | "- GH - DockerSpawner and whitelist\n", 186 | "\n", 187 | "0:44:00 Initially missing piece Hub API if cookie is valid\n", 188 | "\n", 189 | "- docker0 ip address netifaces\n", 190 | "\n", 191 | "0:48:00 Lots you can do with DockerSpawner" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "### Custom Spawners\n", 199 | "\n", 200 | "0:51:19 Customizing JupyterHub Spawners\n", 201 | "\n", 202 | "- Start my server goes to a form\n", 203 | "\n" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "### Deployment\n", 211 | "\n", 212 | "1:07:00 JupyterHub with supervisor\n", 213 | "\n", 214 | "1:09:00 Reference deployments" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "### Wrap Up\n", 222 | "\n", 223 | "1:11:00 Q & A\n", 224 | "\n", 225 | "1:13:00 Simula deployment with persistence in Hub\n" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": null, 231 | "metadata": { 232 | "collapsed": true 233 | }, 234 | "outputs": [], 235 | "source": [] 236 | } 237 | ], 238 | "metadata": { 239 | "kernelspec": { 240 | "display_name": "Python 3", 241 | "language": "python", 242 | "name": "python3" 243 | }, 244 | "language_info": { 245 | "codemirror_mode": { 246 | "name": "ipython", 247 | "version": 3 248 | }, 249 | "file_extension": ".py", 250 | "mimetype": "text/x-python", 251 | "name": "python", 252 | "nbconvert_exporter": "python", 253 | "pygments_lexer": "ipython3", 254 | "version": "3.5.1" 255 | } 256 | }, 257 | "nbformat": 4, 258 | "nbformat_minor": 0 259 | } 260 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | .PHONY: clean 52 | clean: 53 | rm -rf $(BUILDDIR)/* 54 | 55 | .PHONY: html 56 | html: 57 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 58 | @echo 59 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 60 | 61 | .PHONY: dirhtml 62 | dirhtml: 63 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 64 | @echo 65 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 66 | 67 | .PHONY: singlehtml 68 | singlehtml: 69 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 70 | @echo 71 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 72 | 73 | .PHONY: pickle 74 | pickle: 75 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 76 | @echo 77 | @echo "Build finished; now you can process the pickle files." 78 | 79 | .PHONY: json 80 | json: 81 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 82 | @echo 83 | @echo "Build finished; now you can process the JSON files." 84 | 85 | .PHONY: htmlhelp 86 | htmlhelp: 87 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 88 | @echo 89 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 90 | ".hhp project file in $(BUILDDIR)/htmlhelp." 91 | 92 | .PHONY: qthelp 93 | qthelp: 94 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 95 | @echo 96 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 97 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 98 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GettingStartedwithJupyterHubTutorial.qhcp" 99 | @echo "To view the help file:" 100 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GettingStartedwithJupyterHubTutorial.qhc" 101 | 102 | .PHONY: applehelp 103 | applehelp: 104 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 105 | @echo 106 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 107 | @echo "N.B. You won't be able to view it unless you put it in" \ 108 | "~/Library/Documentation/Help or install it in your application" \ 109 | "bundle." 110 | 111 | .PHONY: devhelp 112 | devhelp: 113 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 114 | @echo 115 | @echo "Build finished." 116 | @echo "To view the help file:" 117 | @echo "# mkdir -p $$HOME/.local/share/devhelp/GettingStartedwithJupyterHubTutorial" 118 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GettingStartedwithJupyterHubTutorial" 119 | @echo "# devhelp" 120 | 121 | .PHONY: epub 122 | epub: 123 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 124 | @echo 125 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 126 | 127 | .PHONY: epub3 128 | epub3: 129 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 130 | @echo 131 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 132 | 133 | .PHONY: latex 134 | latex: 135 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 136 | @echo 137 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 138 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 139 | "(use \`make latexpdf' here to do that automatically)." 140 | 141 | .PHONY: latexpdf 142 | latexpdf: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through pdflatex..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: latexpdfja 149 | latexpdfja: 150 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 151 | @echo "Running LaTeX files through platex and dvipdfmx..." 152 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 153 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 154 | 155 | .PHONY: text 156 | text: 157 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 158 | @echo 159 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 160 | 161 | .PHONY: man 162 | man: 163 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 164 | @echo 165 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 166 | 167 | .PHONY: texinfo 168 | texinfo: 169 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 170 | @echo 171 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 172 | @echo "Run \`make' in that directory to run these through makeinfo" \ 173 | "(use \`make info' here to do that automatically)." 174 | 175 | .PHONY: info 176 | info: 177 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 178 | @echo "Running Texinfo files through makeinfo..." 179 | make -C $(BUILDDIR)/texinfo info 180 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 181 | 182 | .PHONY: gettext 183 | gettext: 184 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 185 | @echo 186 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 187 | 188 | .PHONY: changes 189 | changes: 190 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 191 | @echo 192 | @echo "The overview file is in $(BUILDDIR)/changes." 193 | 194 | .PHONY: linkcheck 195 | linkcheck: 196 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 197 | @echo 198 | @echo "Link check complete; look for any errors in the above output " \ 199 | "or in $(BUILDDIR)/linkcheck/output.txt." 200 | 201 | .PHONY: doctest 202 | doctest: 203 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 204 | @echo "Testing of doctests in the sources finished, look at the " \ 205 | "results in $(BUILDDIR)/doctest/output.txt." 206 | 207 | .PHONY: coverage 208 | coverage: 209 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 210 | @echo "Testing of coverage in the sources finished, look at the " \ 211 | "results in $(BUILDDIR)/coverage/python.txt." 212 | 213 | .PHONY: xml 214 | xml: 215 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 216 | @echo 217 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 218 | 219 | .PHONY: pseudoxml 220 | pseudoxml: 221 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 222 | @echo 223 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 224 | -------------------------------------------------------------------------------- /docs/authenticators.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Custom Authenticators\n", 8 | "\n", 9 | "Let's peek at the Authenticator classes:" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from jupyterhub.auth import Authenticator, PAMAuthenticator" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/plain": [ 29 | "\u001b[0;31mSignature:\u001b[0m Authenticator.authenticate(self, handler, data)\n", 30 | "\u001b[0;31mDocstring:\u001b[0m\n", 31 | "Authenticate a user with login form data\n", 32 | "\n", 33 | "This must be a tornado gen.coroutine.\n", 34 | "It must return the username on successful authentication,\n", 35 | "and return None on failed authentication.\n", 36 | "\n", 37 | "Checking the whitelist is handled separately by the caller.\n", 38 | "\n", 39 | "Args:\n", 40 | " handler (tornado.web.RequestHandler): the current request handler\n", 41 | " data (dict): The formdata of the login form.\n", 42 | " The default form has 'username' and 'password' fields.\n", 43 | "Returns:\n", 44 | " username (str or None): The username of the authenticated user,\n", 45 | " or None if Authentication failed\n", 46 | "\u001b[0;31mFile:\u001b[0m ~/dev/jpy/jupyterhub/jupyterhub/auth.py\n", 47 | "\u001b[0;31mType:\u001b[0m function\n" 48 | ] 49 | }, 50 | "metadata": {}, 51 | "output_type": "display_data" 52 | } 53 | ], 54 | "source": [ 55 | "Authenticator.authenticate?" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "PAM calls out to a library with the given username and password:" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "\u001b[0;31mSignature:\u001b[0m PAMAuthenticator.authenticate(self, handler, data)\n", 74 | "\u001b[0;31mSource:\u001b[0m \n", 75 | " @gen.coroutine\n", 76 | " def authenticate(self, handler, data):\n", 77 | " \"\"\"Authenticate with PAM, and return the username if login is successful.\n", 78 | "\n", 79 | " Return None otherwise.\n", 80 | " \"\"\"\n", 81 | " username = data['username']\n", 82 | " try:\n", 83 | " pamela.authenticate(username, data['password'], service=self.service)\n", 84 | " except pamela.PAMError as e:\n", 85 | " if handler is not None:\n", 86 | " self.log.warning(\"PAM Authentication failed (%s@%s): %s\", username, handler.request.remote_ip, e)\n", 87 | " else:\n", 88 | " self.log.warning(\"PAM Authentication failed: %s\", e)\n", 89 | " else:\n", 90 | " return username\n", 91 | "\u001b[0;31mFile:\u001b[0m ~/dev/jpy/jupyterhub/jupyterhub/auth.py\n", 92 | "\u001b[0;31mType:\u001b[0m function\n" 93 | ] 94 | }, 95 | "metadata": {}, 96 | "output_type": "display_data" 97 | } 98 | ], 99 | "source": [ 100 | "PAMAuthenticator.authenticate??" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "Here's a super advanced Authenticator that does very secure password verification:" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 4, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "class SuperSecureAuthenticator(Authenticator):\n", 117 | " def authenticate(self, handler, data):\n", 118 | " username = data['username']\n", 119 | " # check password:\n", 120 | " if data['username'] == data['password']:\n", 121 | " return username" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "metadata": {}, 127 | "source": [ 128 | "## Exercise:\n", 129 | "\n", 130 | "Write a custom username+password Authenticator where:\n", 131 | "\n", 132 | "1. passwords are loaded from a dict\n", 133 | "2. hashed+salted passwords are stored somewhere, but not cleartext passwords" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": 5, 139 | "metadata": {}, 140 | "outputs": [ 141 | { 142 | "data": { 143 | "text/plain": [ 144 | "'sha512:16384:98400e241da5a64d:6e2c468dea2e6ec6f185936f6ac1a96e6046c4cdf6c0156aecb03fcd2e9963c5058cbc7cf9d7792ed9edc5bc1703f6e14274b7ba5804781499e49bb077aacccb'" 145 | ] 146 | }, 147 | "execution_count": 5, 148 | "metadata": {}, 149 | "output_type": "execute_result" 150 | } 151 | ], 152 | "source": [ 153 | "# possibly useful:\n", 154 | "\n", 155 | "from jupyterhub.utils import hash_token, compare_token\n", 156 | "hash_token('mypassword')" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 6, 162 | "metadata": {}, 163 | "outputs": [ 164 | { 165 | "data": { 166 | "text/plain": [ 167 | "True" 168 | ] 169 | }, 170 | "execution_count": 6, 171 | "metadata": {}, 172 | "output_type": "execute_result" 173 | } 174 | ], 175 | "source": [ 176 | "compare_token(_, 'mypassword')" 177 | ] 178 | } 179 | ], 180 | "metadata": { 181 | "kernelspec": { 182 | "display_name": "Python 3", 183 | "language": "python", 184 | "name": "python3" 185 | }, 186 | "language_info": { 187 | "codemirror_mode": { 188 | "name": "ipython", 189 | "version": 3 190 | }, 191 | "file_extension": ".py", 192 | "mimetype": "text/x-python", 193 | "name": "python", 194 | "nbconvert_exporter": "python", 195 | "pygments_lexer": "ipython3", 196 | "version": "3.5.2" 197 | } 198 | }, 199 | "nbformat": 4, 200 | "nbformat_minor": 1 201 | } 202 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | import recommonmark.parser 7 | 8 | # If extensions (or modules to document with autodoc) are in another directory, 9 | # add these directories to sys.path here. If the directory is relative to the 10 | # documentation root, use os.path.abspath to make it absolute, like shown here. 11 | #sys.path.insert(0, os.path.abspath('.')) 12 | 13 | # -- General configuration ------------------------------------------------ 14 | 15 | # If your documentation needs a minimal Sphinx version, state it here. 16 | #needs_sphinx = '1.0' 17 | 18 | # Add any Sphinx extension module names here, as strings. They can be 19 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 20 | # ones. 21 | extensions = [ 22 | 'sphinx.ext.intersphinx', 23 | 'sphinx.ext.mathjax', 24 | 'nbsphinx', 25 | ] 26 | 27 | # Add any paths that contain templates here, relative to this directory. 28 | templates_path = ['_templates'] 29 | 30 | source_parsers = { 31 | '.md': 'recommonmark.parser.CommonMarkParser', 32 | } 33 | 34 | # The suffix(es) of source filenames. 35 | # You can specify multiple suffix as a list of string: 36 | # source_suffix = ['.rst', '.md'] 37 | source_suffix = ['.rst', '.md', '.ipynb'] 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = 'Getting Started with JupyterHub Tutorial' 47 | copyright = '2016, Project Jupyter' 48 | author = 'Project Jupyter' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = '1.0' 56 | # The full version, including alpha/beta/rc tags. 57 | release = '1.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | # 62 | # This is also used if you do content translation via gettext catalogs. 63 | # Usually you set "language" from the command line for these cases. 64 | language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | # This patterns also effect to html_static_path and html_extra_path 75 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all 78 | # documents. 79 | #default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | #add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | #add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | #show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | #modindex_common_prefix = [] 97 | 98 | # If true, keep warnings as "system message" paragraphs in the built documents. 99 | #keep_warnings = False 100 | 101 | # If true, `todo` and `todoList` produce output, else they produce nothing. 102 | todo_include_todos = False 103 | 104 | 105 | # -- Options for HTML output ---------------------------------------------- 106 | 107 | # The theme to use for HTML and HTML Help pages. See the documentation for 108 | # a list of builtin themes. 109 | html_theme = 'sphinx_rtd_theme' 110 | 111 | # Theme options are theme-specific and customize the look and feel of a theme 112 | # further. For a list of options available for each theme, see the 113 | # documentation. 114 | #html_theme_options = {} 115 | 116 | # Add any paths that contain custom themes here, relative to this directory. 117 | #html_theme_path = [] 118 | 119 | # The name for this set of Sphinx documents. 120 | # " v documentation" by default. 121 | #html_title = 'Getting Started with JupyterHub Tutorial v1.0' 122 | 123 | # A shorter title for the navigation bar. Default is the same as html_title. 124 | #html_short_title = None 125 | 126 | # The name of an image file (relative to this directory) to place at the top 127 | # of the sidebar. 128 | #html_logo = None 129 | 130 | # The name of an image file (relative to this directory) to use as a favicon of 131 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 132 | # pixels large. 133 | #html_favicon = None 134 | 135 | # Add any paths that contain custom static files (such as style sheets) here, 136 | # relative to this directory. They are copied after the builtin static files, 137 | # so a file named "default.css" will overwrite the builtin "default.css". 138 | html_static_path = ['_static'] 139 | 140 | # Add any extra paths that contain custom files (such as robots.txt or 141 | # .htaccess) here, relative to this directory. These files are copied 142 | # directly to the root of the documentation. 143 | #html_extra_path = [] 144 | 145 | # If not None, a 'Last updated on:' timestamp is inserted at every page 146 | # bottom, using the given strftime format. 147 | # The empty string is equivalent to '%b %d, %Y'. 148 | #html_last_updated_fmt = None 149 | 150 | # If true, SmartyPants will be used to convert quotes and dashes to 151 | # typographically correct entities. 152 | #html_use_smartypants = True 153 | 154 | # Custom sidebar templates, maps document names to template names. 155 | #html_sidebars = {} 156 | 157 | # Additional templates that should be rendered to pages, maps page names to 158 | # template names. 159 | #html_additional_pages = {} 160 | 161 | # If false, no module index is generated. 162 | #html_domain_indices = True 163 | 164 | # If false, no index is generated. 165 | #html_use_index = True 166 | 167 | # If true, the index is split into individual pages for each letter. 168 | #html_split_index = False 169 | 170 | # If true, links to the reST sources are added to the pages. 171 | #html_show_sourcelink = True 172 | 173 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 174 | #html_show_sphinx = True 175 | 176 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 177 | #html_show_copyright = True 178 | 179 | # If true, an OpenSearch description file will be output, and all pages will 180 | # contain a tag referring to it. The value of this option must be the 181 | # base URL from which the finished HTML is served. 182 | #html_use_opensearch = '' 183 | 184 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 185 | #html_file_suffix = None 186 | 187 | # Language to be used for generating the HTML full-text search index. 188 | # Sphinx supports the following languages: 189 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 190 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 191 | #html_search_language = 'en' 192 | 193 | # A dictionary with options for the search language support, empty by default. 194 | # 'ja' uses this config value. 195 | # 'zh' user can custom change `jieba` dictionary path. 196 | #html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | #html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = 'GettingStartedwithJupyterHubTutorialdoc' 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | 217 | # Latex figure (float) alignment 218 | #'figure_align': 'htbp', 219 | } 220 | 221 | # Grouping the document tree into LaTeX files. List of tuples 222 | # (source start file, target name, title, 223 | # author, documentclass [howto, manual, or own class]). 224 | latex_documents = [ 225 | (master_doc, 'GettingStartedwithJupyterHubTutorial.tex', 'Getting Started with JupyterHub Tutorial Documentation', 226 | 'Project Jupyter', 'manual'), 227 | ] 228 | 229 | # The name of an image file (relative to this directory) to place at the top of 230 | # the title page. 231 | #latex_logo = None 232 | 233 | # For "manual" documents, if this is true, then toplevel headings are parts, 234 | # not chapters. 235 | #latex_use_parts = False 236 | 237 | # If true, show page references after internal links. 238 | #latex_show_pagerefs = False 239 | 240 | # If true, show URL addresses after external links. 241 | #latex_show_urls = False 242 | 243 | # Documents to append as an appendix to all manuals. 244 | #latex_appendices = [] 245 | 246 | # If false, no module index is generated. 247 | #latex_domain_indices = True 248 | 249 | 250 | # -- Options for manual page output --------------------------------------- 251 | 252 | # One entry per manual page. List of tuples 253 | # (source start file, name, description, authors, manual section). 254 | man_pages = [ 255 | (master_doc, 'gettingstartedwithjupyterhubtutorial', 'Getting Started with JupyterHub Tutorial Documentation', 256 | [author], 1) 257 | ] 258 | 259 | # If true, show URL addresses after external links. 260 | #man_show_urls = False 261 | 262 | 263 | # -- Options for Texinfo output ------------------------------------------- 264 | 265 | # Grouping the document tree into Texinfo files. List of tuples 266 | # (source start file, target name, title, author, 267 | # dir menu entry, description, category) 268 | texinfo_documents = [ 269 | (master_doc, 'GettingStartedwithJupyterHubTutorial', 'Getting Started with JupyterHub Tutorial Documentation', 270 | author, 'GettingStartedwithJupyterHubTutorial', 'One line description of project.', 271 | 'Miscellaneous'), 272 | ] 273 | 274 | # Documents to append as an appendix to all manuals. 275 | #texinfo_appendices = [] 276 | 277 | # If false, no module index is generated. 278 | #texinfo_domain_indices = True 279 | 280 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 281 | #texinfo_show_urls = 'footnote' 282 | 283 | # If true, do not generate a @detailmenu in the "Top" node's menu. 284 | #texinfo_no_detailmenu = False 285 | 286 | 287 | # Example configuration for intersphinx: refer to the Python standard library. 288 | intersphinx_mapping = {'https://docs.python.org/': None} 289 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | name: tutorial_docs 2 | dependencies: 3 | - python=3.5 4 | - sphinx_rtd_theme 5 | - ipython 6 | - jupyter_client 7 | - pip: 8 | - sphinx 9 | - nbsphinx 10 | - recommonmark 11 | -------------------------------------------------------------------------------- /docs/img/spawn-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterhub/jupyterhub-tutorial/2537fdbc5707114077e3128ce3a37052f1eafe02/docs/img/spawn-form.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Getting Started with JupyterHub Tutorial 2 | ======================================== 3 | 4 | This tutorial is based on Min Ragan Kelley's `PyData London talk `_. 5 | 6 | JupyterHub References 7 | --------------------- 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | jhubcheatsheet.md 13 | timeline.md 14 | 15 | Tutorial notebooks 16 | ------------------ 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | JupyterHub-Tutorial.ipynb 22 | authenticators.ipynb 23 | spawners.ipynb 24 | rest-api.ipynb 25 | 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` 33 | 34 | -------------------------------------------------------------------------------- /docs/jhubcheatsheet.md: -------------------------------------------------------------------------------- 1 | # JupyterHub Cheatsheet 2 | 3 | ## JupyterHub on GitHub 4 | JupyterHub organization on GitHub [https://github.com/jupyterhub](https://github.com/jupyterhub) 5 | 6 | ### Project 7 | 8 | JupyterHub 9 | - [GitHub](https://github.com/jupyterhub/jupyterhub) 10 | - [Documentation online](http://jupyterhub.readthedocs.io/en/latest/) 11 | - [PDF download](https://media.readthedocs.org/pdf/jupyterhub/latest/jupyterhub.pdf) 12 | 13 | ### Tutorial 14 | 15 | JupyterHub Tutorial - "Tutorial materials for deploying JupyterHub" 16 | - [GitHub](https://github.com/jupyterhub/jupyterhub-tutorial) 17 | - [Documentation online](http://jupyterhub-tutorial.readthedocs.io/) 18 | 19 | ### Authenticators 20 | 21 | LDAPauthenticator - "Simple LDAP Authenticator Plugin for JupyterHub" [https://github.com/jupyterhub/ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) 22 | 23 | OAuthenticator - "GitHub OAuth + JupyterHub Authenticator = OAuthenticator" 24 | [https://github.com/jupyterhub/oauthenticator](https://github.com/jupyterhub/oauthenticator) 25 | 26 | ### Spawners 27 | 28 | Batchspawner - "Custom Spawner for Jupyterhub to start servers in batch scheduled systems" [https://github.com/jupyterhub/batchspawner](https://github.com/jupyterhub/batchspawner) 29 | 30 | Dockerspawner - "Enables JupyterHub to spawn user servers in docker containers." [https://github.com/jupyterhub/dockerspawner](https://github.com/jupyterhub/dockerspawner) 31 | 32 | Sudospawner - "enables JupyterHub to spawn single-user servers without being root, by spawning an intermediate process via sudo, which takes actions on behalf of the user" [https://github.com/jupyterhub/sudospawner](https://github.com/jupyterhub/sudospawner) 33 | 34 | ### Proxy 35 | 36 | [Configurable HTTP Proxy](https://github.com/jupyterhub/configurable-http-proxy) - "node-http-proxy plus a REST API" 37 | 38 | ### Deployment examples 39 | 40 | [JupyterHub deployment using Ansible](https://github.com/jupyterhub/jupyterhub-deploy-teaching) 41 | 42 | [JupyterHub deployment with Docker](https://github.com/jupyterhub/jupyterhub-deploy-docker) 43 | 44 | [JupyterHub using Rackspace Carina](https://github.com/jupyterhub/jupyterhub-carina) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | goto end 43 | ) 44 | 45 | if "%1" == "clean" ( 46 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 47 | del /q /s %BUILDDIR%\* 48 | goto end 49 | ) 50 | 51 | 52 | REM Check if sphinx-build is available and fallback to Python version if any 53 | %SPHINXBUILD% 1>NUL 2>NUL 54 | if errorlevel 9009 goto sphinx_python 55 | goto sphinx_ok 56 | 57 | :sphinx_python 58 | 59 | set SPHINXBUILD=python -m sphinx.__init__ 60 | %SPHINXBUILD% 2> nul 61 | if errorlevel 9009 ( 62 | echo. 63 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 64 | echo.installed, then set the SPHINXBUILD environment variable to point 65 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 66 | echo.may add the Sphinx directory to PATH. 67 | echo. 68 | echo.If you don't have Sphinx installed, grab it from 69 | echo.http://sphinx-doc.org/ 70 | exit /b 1 71 | ) 72 | 73 | :sphinx_ok 74 | 75 | 76 | if "%1" == "html" ( 77 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 78 | if errorlevel 1 exit /b 1 79 | echo. 80 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 81 | goto end 82 | ) 83 | 84 | if "%1" == "dirhtml" ( 85 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 86 | if errorlevel 1 exit /b 1 87 | echo. 88 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 89 | goto end 90 | ) 91 | 92 | if "%1" == "singlehtml" ( 93 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 97 | goto end 98 | ) 99 | 100 | if "%1" == "pickle" ( 101 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 102 | if errorlevel 1 exit /b 1 103 | echo. 104 | echo.Build finished; now you can process the pickle files. 105 | goto end 106 | ) 107 | 108 | if "%1" == "json" ( 109 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished; now you can process the JSON files. 113 | goto end 114 | ) 115 | 116 | if "%1" == "htmlhelp" ( 117 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished; now you can run HTML Help Workshop with the ^ 121 | .hhp project file in %BUILDDIR%/htmlhelp. 122 | goto end 123 | ) 124 | 125 | if "%1" == "qthelp" ( 126 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 127 | if errorlevel 1 exit /b 1 128 | echo. 129 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 130 | .qhcp project file in %BUILDDIR%/qthelp, like this: 131 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\GettingStartedwithJupyterHubTutorial.qhcp 132 | echo.To view the help file: 133 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\GettingStartedwithJupyterHubTutorial.ghc 134 | goto end 135 | ) 136 | 137 | if "%1" == "devhelp" ( 138 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 139 | if errorlevel 1 exit /b 1 140 | echo. 141 | echo.Build finished. 142 | goto end 143 | ) 144 | 145 | if "%1" == "epub" ( 146 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 147 | if errorlevel 1 exit /b 1 148 | echo. 149 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 150 | goto end 151 | ) 152 | 153 | if "%1" == "epub3" ( 154 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 155 | if errorlevel 1 exit /b 1 156 | echo. 157 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 158 | goto end 159 | ) 160 | 161 | if "%1" == "latex" ( 162 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 166 | goto end 167 | ) 168 | 169 | if "%1" == "latexpdf" ( 170 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 171 | cd %BUILDDIR%/latex 172 | make all-pdf 173 | cd %~dp0 174 | echo. 175 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 176 | goto end 177 | ) 178 | 179 | if "%1" == "latexpdfja" ( 180 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 181 | cd %BUILDDIR%/latex 182 | make all-pdf-ja 183 | cd %~dp0 184 | echo. 185 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 186 | goto end 187 | ) 188 | 189 | if "%1" == "text" ( 190 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 191 | if errorlevel 1 exit /b 1 192 | echo. 193 | echo.Build finished. The text files are in %BUILDDIR%/text. 194 | goto end 195 | ) 196 | 197 | if "%1" == "man" ( 198 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 199 | if errorlevel 1 exit /b 1 200 | echo. 201 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 202 | goto end 203 | ) 204 | 205 | if "%1" == "texinfo" ( 206 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 207 | if errorlevel 1 exit /b 1 208 | echo. 209 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 210 | goto end 211 | ) 212 | 213 | if "%1" == "gettext" ( 214 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 215 | if errorlevel 1 exit /b 1 216 | echo. 217 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 218 | goto end 219 | ) 220 | 221 | if "%1" == "changes" ( 222 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 223 | if errorlevel 1 exit /b 1 224 | echo. 225 | echo.The overview file is in %BUILDDIR%/changes. 226 | goto end 227 | ) 228 | 229 | if "%1" == "linkcheck" ( 230 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Link check complete; look for any errors in the above output ^ 234 | or in %BUILDDIR%/linkcheck/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "doctest" ( 239 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of doctests in the sources finished, look at the ^ 243 | results in %BUILDDIR%/doctest/output.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "coverage" ( 248 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Testing of coverage in the sources finished, look at the ^ 252 | results in %BUILDDIR%/coverage/python.txt. 253 | goto end 254 | ) 255 | 256 | if "%1" == "xml" ( 257 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 258 | if errorlevel 1 exit /b 1 259 | echo. 260 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 261 | goto end 262 | ) 263 | 264 | if "%1" == "pseudoxml" ( 265 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 266 | if errorlevel 1 exit /b 1 267 | echo. 268 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 269 | goto end 270 | ) 271 | 272 | :end 273 | -------------------------------------------------------------------------------- /docs/rest-api.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# JupyterHub's API\n", 8 | "\n", 9 | "JupyterHub has a [REST API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyterhub/jupyterhub/master/docs/rest-api.yml#!/default/get_users):" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "collapsed": true 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "import requests\n", 21 | "hub_api = 'http://127.0.0.1:8081/hub/api/'\n", 22 | "\n", 23 | "token = \"result of token=$(jupyterhub token yourname)\"" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import json\n", 33 | "from requests import HTTPError\n", 34 | "\n", 35 | "def api_request(path, method='get', data=None):\n", 36 | " if data:\n", 37 | " data = json.dumps(data)\n", 38 | " \n", 39 | " r = requests.request(method, hub_api + path,\n", 40 | " headers={'Authorization': 'token %s' % token},\n", 41 | " data=data,\n", 42 | " )\n", 43 | " try:\n", 44 | " r.raise_for_status()\n", 45 | " except Exception as e:\n", 46 | " try:\n", 47 | " info = r.json()\n", 48 | " except Exception:\n", 49 | " raise e\n", 50 | " if 'message' in info:\n", 51 | " # raise nice json error if there was one\n", 52 | " raise HTTPError(\"%s: %s\" % (r.status_code, info['message'])) from None\n", 53 | " else:\n", 54 | " # raise original\n", 55 | " raise e\n", 56 | " if r.text:\n", 57 | " return r.json()\n", 58 | " else:\n", 59 | " return None" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "We can list users and their status:" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 3, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "data": { 76 | "text/plain": [ 77 | "[{'admin': True,\n", 78 | " 'last_activity': '2016-05-06T11:53:51.627000',\n", 79 | " 'name': 'minrk',\n", 80 | " 'pending': None,\n", 81 | " 'server': '/user/minrk'},\n", 82 | " {'admin': False,\n", 83 | " 'last_activity': '2016-05-06T11:57:13.329254',\n", 84 | " 'name': 'takluyver',\n", 85 | " 'pending': None,\n", 86 | " 'server': None}]" 87 | ] 88 | }, 89 | "execution_count": 3, 90 | "metadata": {}, 91 | "output_type": "execute_result" 92 | } 93 | ], 94 | "source": [ 95 | "api_request('users')" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "We can also start user servers:" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 4, 108 | "metadata": {}, 109 | "outputs": [ 110 | { 111 | "data": { 112 | "text/plain": [ 113 | "[{'admin': True,\n", 114 | " 'last_activity': '2016-05-06T11:53:51.627000',\n", 115 | " 'name': 'minrk',\n", 116 | " 'pending': None,\n", 117 | " 'server': '/user/minrk'},\n", 118 | " {'admin': False,\n", 119 | " 'last_activity': '2016-05-06T11:57:29.285044',\n", 120 | " 'name': 'takluyver',\n", 121 | " 'pending': None,\n", 122 | " 'server': '/user/takluyver'}]" 123 | ] 124 | }, 125 | "execution_count": 4, 126 | "metadata": {}, 127 | "output_type": "execute_result" 128 | } 129 | ], 130 | "source": [ 131 | "api_request('users/takluyver/server', method='post')\n", 132 | "api_request('users')" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "And stop them:" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 5, 145 | "metadata": {}, 146 | "outputs": [ 147 | { 148 | "data": { 149 | "text/plain": [ 150 | "[{'admin': True,\n", 151 | " 'last_activity': '2016-05-06T11:53:51.627000',\n", 152 | " 'name': 'minrk',\n", 153 | " 'pending': None,\n", 154 | " 'server': '/user/minrk'},\n", 155 | " {'admin': False,\n", 156 | " 'last_activity': '2016-05-06T11:57:30.145986',\n", 157 | " 'name': 'takluyver',\n", 158 | " 'pending': None,\n", 159 | " 'server': None}]" 160 | ] 161 | }, 162 | "execution_count": 5, 163 | "metadata": {}, 164 | "output_type": "execute_result" 165 | } 166 | ], 167 | "source": [ 168 | "api_request('users/takluyver/server', method='delete')\n", 169 | "api_request('users')" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "We can also see the proxy routing table:" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 6, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/plain": [ 187 | "{'/': {'last_activity': '2016-05-06T11:56:29.543Z',\n", 188 | " 'target': 'http://127.0.0.1:8081'},\n", 189 | " '/user/minrk': {'last_activity': '2016-05-06T11:57:29.159Z',\n", 190 | " 'target': 'http://127.0.0.1:45683',\n", 191 | " 'user': 'minrk'}}" 192 | ] 193 | }, 194 | "execution_count": 6, 195 | "metadata": {}, 196 | "output_type": "execute_result" 197 | } 198 | ], 199 | "source": [ 200 | "api_request('proxy')" 201 | ] 202 | } 203 | ], 204 | "metadata": { 205 | "kernelspec": { 206 | "display_name": "Python 3", 207 | "language": "python", 208 | "name": "python3" 209 | }, 210 | "language_info": { 211 | "codemirror_mode": { 212 | "name": "ipython", 213 | "version": 3 214 | }, 215 | "file_extension": ".py", 216 | "mimetype": "text/x-python", 217 | "name": "python", 218 | "nbconvert_exporter": "python", 219 | "pygments_lexer": "ipython3", 220 | "version": "3.5.2" 221 | } 222 | }, 223 | "nbformat": 4, 224 | "nbformat_minor": 1 225 | } 226 | -------------------------------------------------------------------------------- /docs/spawners.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# JupyterHub Spawners\n", 8 | "\n", 9 | "Let's peek at the base classes:" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": { 16 | "collapsed": true 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "from jupyterhub.spawner import Spawner, LocalProcessSpawner" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": { 27 | "scrolled": false 28 | }, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "\u001b[1;31mInit signature: \u001b[0m\u001b[0mSpawner\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 35 | "\u001b[1;31mSource:\u001b[0m\n", 36 | "\u001b[1;32mclass\u001b[0m \u001b[0mSpawner\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mLoggingConfigurable\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 37 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Base class for spawning single-user notebook servers.\u001b[0m\n", 38 | "\u001b[1;34m \u001b[0m\n", 39 | "\u001b[1;34m Subclass this, and override the following methods:\u001b[0m\n", 40 | "\u001b[1;34m \u001b[0m\n", 41 | "\u001b[1;34m - load_state\u001b[0m\n", 42 | "\u001b[1;34m - get_state\u001b[0m\n", 43 | "\u001b[1;34m - start\u001b[0m\n", 44 | "\u001b[1;34m - stop\u001b[0m\n", 45 | "\u001b[1;34m - poll\u001b[0m\n", 46 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 47 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 48 | "\u001b[1;33m\u001b[0m \u001b[0mdb\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAny\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 49 | "\u001b[1;33m\u001b[0m \u001b[0muser\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAny\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 50 | "\u001b[1;33m\u001b[0m \u001b[0mhub\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAny\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 51 | "\u001b[1;33m\u001b[0m \u001b[0mauthenticator\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAny\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 52 | "\u001b[1;33m\u001b[0m \u001b[0mapi_token\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mUnicode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 53 | "\u001b[1;33m\u001b[0m \u001b[0mip\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mUnicode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'127.0.0.1'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 54 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"The IP address (or hostname) the single-user server should listen on\"\u001b[0m\u001b[1;33m\u001b[0m\n", 55 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 56 | "\u001b[1;33m\u001b[0m \u001b[0mstart_timeout\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mInteger\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m60\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 57 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Timeout (in seconds) before giving up on the spawner.\u001b[0m\n", 58 | "\u001b[1;34m \u001b[0m\n", 59 | "\u001b[1;34m This is the timeout for start to return, not the timeout for the server to respond.\u001b[0m\n", 60 | "\u001b[1;34m Callers of spawner.start will assume that startup has failed if it takes longer than this.\u001b[0m\n", 61 | "\u001b[1;34m start should return when the server process is started and its location is known.\u001b[0m\n", 62 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 63 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 64 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 65 | "\u001b[1;33m\u001b[0m \u001b[0mhttp_timeout\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mInteger\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m30\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 66 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Timeout (in seconds) before giving up on a spawned HTTP server\u001b[0m\n", 67 | "\u001b[1;34m\u001b[0m\n", 68 | "\u001b[1;34m Once a server has successfully been spawned, this is the amount of time\u001b[0m\n", 69 | "\u001b[1;34m we wait before assuming that the server is unable to accept\u001b[0m\n", 70 | "\u001b[1;34m connections.\u001b[0m\n", 71 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 72 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 73 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 74 | "\u001b[1;33m\u001b[0m \u001b[0mpoll_interval\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mInteger\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m30\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 75 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Interval (in seconds) on which to poll the spawner.\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 76 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 77 | "\u001b[1;33m\u001b[0m \u001b[0m_callbacks\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mList\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 78 | "\u001b[1;33m\u001b[0m \u001b[0m_poll_callback\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAny\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 79 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 80 | "\u001b[1;33m\u001b[0m \u001b[0mdebug\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mBool\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 81 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"Enable debug-logging of the single-user server\"\u001b[0m\u001b[1;33m\u001b[0m\n", 82 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 83 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 84 | "\u001b[1;33m\u001b[0m \u001b[0moptions_form\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mUnicode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"\u001b[0m\n", 85 | "\u001b[1;34m An HTML form for options a user can specify on launching their server.\u001b[0m\n", 86 | "\u001b[1;34m The surrounding `
` element and the submit button are already provided.\u001b[0m\n", 87 | "\u001b[1;34m \u001b[0m\n", 88 | "\u001b[1;34m For example:\u001b[0m\n", 89 | "\u001b[1;34m \u001b[0m\n", 90 | "\u001b[1;34m Set your key:\u001b[0m\n", 91 | "\u001b[1;34m \u001b[0m\n", 92 | "\u001b[1;34m
\u001b[0m\n", 93 | "\u001b[1;34m Choose a letter:\u001b[0m\n", 94 | "\u001b[1;34m \u001b[0m\n", 98 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 99 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 100 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0moptions_from_form\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mform_data\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 101 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Interpret HTTP form data\u001b[0m\n", 102 | "\u001b[1;34m \u001b[0m\n", 103 | "\u001b[1;34m Form data will always arrive as a dict of lists of strings.\u001b[0m\n", 104 | "\u001b[1;34m Override this function to understand single-values, numbers, etc.\u001b[0m\n", 105 | "\u001b[1;34m \u001b[0m\n", 106 | "\u001b[1;34m This should coerce form data into the structure expected by self.user_options,\u001b[0m\n", 107 | "\u001b[1;34m which must be a dict.\u001b[0m\n", 108 | "\u001b[1;34m \u001b[0m\n", 109 | "\u001b[1;34m Instances will receive this data on self.user_options, after passing through this function,\u001b[0m\n", 110 | "\u001b[1;34m prior to `Spawner.start`.\u001b[0m\n", 111 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 112 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mform_data\u001b[0m\u001b[1;33m\u001b[0m\n", 113 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 114 | "\u001b[1;33m\u001b[0m \u001b[0muser_options\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mDict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"This is where form-specified options ultimately end up.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 115 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 116 | "\u001b[1;33m\u001b[0m \u001b[0menv_keep\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mList\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m\u001b[0m\n", 117 | "\u001b[1;33m\u001b[0m \u001b[1;34m'PATH'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 118 | "\u001b[1;33m\u001b[0m \u001b[1;34m'PYTHONPATH'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 119 | "\u001b[1;33m\u001b[0m \u001b[1;34m'CONDA_ROOT'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 120 | "\u001b[1;33m\u001b[0m \u001b[1;34m'CONDA_DEFAULT_ENV'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 121 | "\u001b[1;33m\u001b[0m \u001b[1;34m'VIRTUAL_ENV'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 122 | "\u001b[1;33m\u001b[0m \u001b[1;34m'LANG'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 123 | "\u001b[1;33m\u001b[0m \u001b[1;34m'LC_ALL'\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 124 | "\u001b[1;33m\u001b[0m \u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 125 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"Whitelist of environment variables for the subprocess to inherit\"\u001b[0m\u001b[1;33m\u001b[0m\n", 126 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 127 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mDict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Deprecated: use Spawner.get_env or Spawner.environment\u001b[0m\n", 128 | "\u001b[1;34m \u001b[0m\n", 129 | "\u001b[1;34m - extend Spawner.get_env for adding required env in Spawner subclasses\u001b[0m\n", 130 | "\u001b[1;34m - Spawner.environment for config-specified env\u001b[0m\n", 131 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 132 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 133 | "\u001b[1;33m\u001b[0m \u001b[0menvironment\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mDict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\u001b[0m\n", 134 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Environment variables to load for the Spawner.\u001b[0m\n", 135 | "\u001b[1;34m\u001b[0m\n", 136 | "\u001b[1;34m Value could be a string or a callable. If it is a callable, it will\u001b[0m\n", 137 | "\u001b[1;34m be called with one parameter, which will be the instance of the spawner\u001b[0m\n", 138 | "\u001b[1;34m in use. It should quickly (without doing much blocking operations) return\u001b[0m\n", 139 | "\u001b[1;34m a string that will be used as the value for the environment variable.\u001b[0m\n", 140 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 141 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 142 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 143 | "\u001b[1;33m\u001b[0m \u001b[0mcmd\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mCommand\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'jupyterhub-singleuser'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 144 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"The command used for starting notebooks.\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 145 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 146 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mList\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mUnicode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 147 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Extra arguments to be passed to the single-user server\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 148 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 149 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 150 | "\u001b[1;33m\u001b[0m \u001b[0mnotebook_dir\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mUnicode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m''\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 151 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"The notebook directory for the single-user server\u001b[0m\n", 152 | "\u001b[1;34m \u001b[0m\n", 153 | "\u001b[1;34m `~` will be expanded to the user's home directory\u001b[0m\n", 154 | "\u001b[1;34m `%U` will be expanded to the user's username\u001b[0m\n", 155 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 156 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 157 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 158 | "\u001b[1;33m\u001b[0m \u001b[0mdefault_url\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mUnicode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m''\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 159 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"The default URL for the single-user server. \u001b[0m\n", 160 | "\u001b[1;34m\u001b[0m\n", 161 | "\u001b[1;34m Can be used in conjunction with --notebook-dir=/ to enable \u001b[0m\n", 162 | "\u001b[1;34m full filesystem traversal, while preserving user's homedir as\u001b[0m\n", 163 | "\u001b[1;34m landing page for notebook\u001b[0m\n", 164 | "\u001b[1;34m\u001b[0m\n", 165 | "\u001b[1;34m `%U` will be expanded to the user's username\u001b[0m\n", 166 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 167 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 168 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 169 | "\u001b[1;33m\u001b[0m \u001b[0mdisable_user_config\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mBool\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 170 | "\u001b[1;33m\u001b[0m \u001b[0mhelp\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"\"\"Disable per-user configuration of single-user servers.\u001b[0m\n", 171 | "\u001b[1;34m \u001b[0m\n", 172 | "\u001b[1;34m This prevents any config in users' $HOME directories\u001b[0m\n", 173 | "\u001b[1;34m from having an effect on their server.\u001b[0m\n", 174 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 175 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtag\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mconfig\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 176 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 177 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 178 | "\u001b[1;33m\u001b[0m \u001b[0msuper\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mSpawner\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 179 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 180 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mload_state\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 181 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 182 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mload_state\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mstate\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 183 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"load state from the database\u001b[0m\n", 184 | "\u001b[1;34m \u001b[0m\n", 185 | "\u001b[1;34m This is the extensible part of state\u001b[0m\n", 186 | "\u001b[1;34m \u001b[0m\n", 187 | "\u001b[1;34m Override in a subclass if there is state to load.\u001b[0m\n", 188 | "\u001b[1;34m Should call `super`.\u001b[0m\n", 189 | "\u001b[1;34m \u001b[0m\n", 190 | "\u001b[1;34m See Also\u001b[0m\n", 191 | "\u001b[1;34m --------\u001b[0m\n", 192 | "\u001b[1;34m \u001b[0m\n", 193 | "\u001b[1;34m get_state, clear_state\u001b[0m\n", 194 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 195 | "\u001b[1;33m\u001b[0m \u001b[1;32mpass\u001b[0m\u001b[1;33m\u001b[0m\n", 196 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 197 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_state\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 198 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"store the state necessary for load_state\u001b[0m\n", 199 | "\u001b[1;34m \u001b[0m\n", 200 | "\u001b[1;34m A black box of extra state for custom spawners.\u001b[0m\n", 201 | "\u001b[1;34m Subclasses should call `super`.\u001b[0m\n", 202 | "\u001b[1;34m \u001b[0m\n", 203 | "\u001b[1;34m Returns\u001b[0m\n", 204 | "\u001b[1;34m -------\u001b[0m\n", 205 | "\u001b[1;34m \u001b[0m\n", 206 | "\u001b[1;34m state: dict\u001b[0m\n", 207 | "\u001b[1;34m a JSONable dict of state\u001b[0m\n", 208 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 209 | "\u001b[1;33m\u001b[0m \u001b[0mstate\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m{\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m\u001b[0m\n", 210 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mstate\u001b[0m\u001b[1;33m\u001b[0m\n", 211 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 212 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mclear_state\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 213 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"clear any state that should be cleared when the process stops\u001b[0m\n", 214 | "\u001b[1;34m \u001b[0m\n", 215 | "\u001b[1;34m State that should be preserved across server instances should not be cleared.\u001b[0m\n", 216 | "\u001b[1;34m \u001b[0m\n", 217 | "\u001b[1;34m Subclasses should call super, to ensure that state is properly cleared.\u001b[0m\n", 218 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 219 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapi_token\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m''\u001b[0m\u001b[1;33m\u001b[0m\n", 220 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 221 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_env\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 222 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Return the environment dict to use for the Spawner.\u001b[0m\n", 223 | "\u001b[1;34m\u001b[0m\n", 224 | "\u001b[1;34m This applies things like `env_keep`, anything defined in `Spawner.environment`,\u001b[0m\n", 225 | "\u001b[1;34m and adds the API token to the env.\u001b[0m\n", 226 | "\u001b[1;34m\u001b[0m\n", 227 | "\u001b[1;34m Use this to access the env in Spawner.start to allow extension in subclasses.\u001b[0m\n", 228 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 229 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m{\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m\u001b[0m\n", 230 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menv\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 231 | "\u001b[1;33m\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwarn\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Spawner.env is deprecated, found %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menv\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mDeprecationWarning\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 232 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menv\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 233 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 234 | "\u001b[1;33m\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mkey\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menv_keep\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 235 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mkey\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mos\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menviron\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 236 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mos\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menviron\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\n", 237 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 238 | "\u001b[1;33m\u001b[0m \u001b[1;31m# config overrides. If the value is a callable, it will be called with\u001b[0m\u001b[1;33m\u001b[0m\n", 239 | "\u001b[1;33m\u001b[0m \u001b[1;31m# one parameter - the current spawner instance - and the return value\u001b[0m\u001b[1;33m\u001b[0m\n", 240 | "\u001b[1;33m\u001b[0m \u001b[1;31m# will be assigned to the environment variable. This will be called at\u001b[0m\u001b[1;33m\u001b[0m\n", 241 | "\u001b[1;33m\u001b[0m \u001b[1;31m# spawn time.\u001b[0m\u001b[1;33m\u001b[0m\n", 242 | "\u001b[1;33m\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mkey\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mvalue\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0menvironment\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mitems\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 243 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mcallable\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 244 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 245 | "\u001b[1;33m\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 246 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mvalue\u001b[0m\u001b[1;33m\u001b[0m\n", 247 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 248 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'JPY_API_TOKEN'\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapi_token\u001b[0m\u001b[1;33m\u001b[0m\n", 249 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m\u001b[0m\n", 250 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 251 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 252 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Return the arguments to be passed after self.cmd\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 253 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m\u001b[0m\n", 254 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--user=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 255 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--port=%i'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mport\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 256 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--cookie-name=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcookie_name\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 257 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--base-url=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbase_url\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 258 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--hub-host=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhub\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhost\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 259 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--hub-prefix=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhub\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbase_url\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 260 | "\u001b[1;33m\u001b[0m \u001b[1;34m'--hub-api-url=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhub\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapi_url\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 261 | "\u001b[1;33m\u001b[0m \u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\n", 262 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mip\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 263 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'--ip=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mip\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 264 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnotebook_dir\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 265 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnotebook_dir\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnotebook_dir\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mreplace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"%U\"\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 266 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'--notebook-dir=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnotebook_dir\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 267 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdefault_url\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 268 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdefault_url\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdefault_url\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mreplace\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"%U\"\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 269 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'--NotebookApp.default_url=%s'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdefault_url\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 270 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 271 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 272 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'--debug'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 273 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdisable_user_config\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 274 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'--disable-user-config'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 275 | "\u001b[1;33m\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 276 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m\u001b[0m\n", 277 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 278 | "\u001b[1;33m\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 279 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 280 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Start the single-user process\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 281 | "\u001b[1;33m\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Override in subclass. Must be a Tornado gen.coroutine.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 282 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 283 | "\u001b[1;33m\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 284 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mstop\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnow\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mFalse\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 285 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Stop the single-user process\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 286 | "\u001b[1;33m\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Override in subclass. Must be a Tornado gen.coroutine.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 287 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 288 | "\u001b[1;33m\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 289 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mpoll\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 290 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Check if the single-user process is running\u001b[0m\n", 291 | "\u001b[1;34m\u001b[0m\n", 292 | "\u001b[1;34m return None if it is, an exit status (0 if unknown) if it is not.\u001b[0m\n", 293 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 294 | "\u001b[1;33m\u001b[0m \u001b[1;32mraise\u001b[0m \u001b[0mNotImplementedError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Override in subclass. Must be a Tornado gen.coroutine.\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 295 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 296 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0madd_poll_callback\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcallback\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 297 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"add a callback to fire when the subprocess stops\u001b[0m\n", 298 | "\u001b[1;34m \u001b[0m\n", 299 | "\u001b[1;34m as noticed by periodic poll_and_notify()\u001b[0m\n", 300 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 301 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0margs\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 302 | "\u001b[1;33m\u001b[0m \u001b[0mcb\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcallback\u001b[0m\u001b[1;33m\u001b[0m\n", 303 | "\u001b[1;33m\u001b[0m \u001b[0mcallback\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mlambda\u001b[0m \u001b[1;33m:\u001b[0m \u001b[0mcb\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 304 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_callbacks\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcallback\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 305 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 306 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mstop_polling\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 307 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"stop the periodic poll\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 308 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_poll_callback\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 309 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_poll_callback\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstop\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 310 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_poll_callback\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\n", 311 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 312 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mstart_polling\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 313 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Start polling periodically\u001b[0m\n", 314 | "\u001b[1;34m \u001b[0m\n", 315 | "\u001b[1;34m callbacks registered via `add_poll_callback` will fire\u001b[0m\n", 316 | "\u001b[1;34m if/when the process stops.\u001b[0m\n", 317 | "\u001b[1;34m \u001b[0m\n", 318 | "\u001b[1;34m Explicit termination via the stop method will not trigger the callbacks.\u001b[0m\n", 319 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 320 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpoll_interval\u001b[0m \u001b[1;33m<=\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 321 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Not polling subprocess\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 322 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\n", 323 | "\u001b[1;33m\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 324 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Polling subprocess every %is\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpoll_interval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 325 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 326 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstop_polling\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 327 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 328 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_poll_callback\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mPeriodicCallback\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\u001b[0m\n", 329 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpoll_and_notify\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 330 | "\u001b[1;33m\u001b[0m \u001b[1;36m1e3\u001b[0m \u001b[1;33m*\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpoll_interval\u001b[0m\u001b[1;33m\u001b[0m\n", 331 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 332 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_poll_callback\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 333 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 334 | "\u001b[1;33m\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 335 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mpoll_and_notify\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 336 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Used as a callback to periodically poll the process,\u001b[0m\n", 337 | "\u001b[1;34m and notify any watchers\u001b[0m\n", 338 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 339 | "\u001b[1;33m\u001b[0m \u001b[0mstatus\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpoll\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 340 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mstatus\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 341 | "\u001b[1;33m\u001b[0m \u001b[1;31m# still running, nothing to do here\u001b[0m\u001b[1;33m\u001b[0m\n", 342 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\n", 343 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 344 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstop_polling\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 345 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 346 | "\u001b[1;33m\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mcallback\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_callbacks\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 347 | "\u001b[1;33m\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 348 | "\u001b[1;33m\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmaybe_future\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcallback\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 349 | "\u001b[1;33m\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 350 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexception\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Unhandled error in poll callback for %s\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 351 | "\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mstatus\u001b[0m\u001b[1;33m\u001b[0m\n", 352 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 353 | "\u001b[1;33m\u001b[0m \u001b[0mdeath_interval\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mFloat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 354 | "\u001b[1;33m\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 355 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mwait_for_death\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 356 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"wait for the process to die, up to timeout seconds\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 357 | "\u001b[1;33m\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtimeout\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeath_interval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 358 | "\u001b[1;33m\u001b[0m \u001b[0mstatus\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpoll\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 359 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mstatus\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 360 | "\u001b[1;33m\u001b[0m \u001b[1;32mbreak\u001b[0m\u001b[1;33m\u001b[0m\n", 361 | "\u001b[1;33m\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 362 | "\u001b[1;33m\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeath_interval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 363 | "\n", 364 | "\u001b[1;31mFile: \u001b[0m~/conda/envs/jupyterhub-tutorial/lib/python3.5/site-packages/jupyterhub/spawner.py\n", 365 | "\u001b[1;31mType: \u001b[0mMetaHasTraits\n" 366 | ] 367 | } 368 | ], 369 | "source": [ 370 | "Spawner??" 371 | ] 372 | }, 373 | { 374 | "cell_type": "markdown", 375 | "metadata": {}, 376 | "source": [ 377 | "Start is the key method in a Spawner. It's how we decide how to start the process that will become the single-user server:" 378 | ] 379 | }, 380 | { 381 | "cell_type": "code", 382 | "execution_count": 3, 383 | "metadata": {}, 384 | "outputs": [ 385 | { 386 | "name": "stdout", 387 | "output_type": "stream", 388 | "text": [ 389 | "\u001b[1;31mSignature: \u001b[0m\u001b[0mLocalProcessSpawner\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 390 | "\u001b[1;31mSource:\u001b[0m\n", 391 | " \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 392 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 393 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Start the process\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 394 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mip\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 395 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mip\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mip\u001b[0m\u001b[1;33m\u001b[0m\n", 396 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mport\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mrandom_port\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 397 | "\u001b[1;33m\u001b[0m \u001b[0mcmd\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\n", 398 | "\u001b[1;33m\u001b[0m \u001b[0menv\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_env\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 399 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 400 | "\u001b[1;33m\u001b[0m \u001b[0mcmd\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcmd\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 401 | "\u001b[1;33m\u001b[0m \u001b[0mcmd\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_args\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 402 | "\u001b[1;33m\u001b[0m \u001b[1;33m\u001b[0m\n", 403 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Spawning %s\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m' '\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpipes\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mquote\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0ms\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0ms\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mcmd\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 404 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mproc\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mPopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcmd\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0menv\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0menv\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 405 | "\u001b[1;33m\u001b[0m \u001b[0mpreexec_fn\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmake_preexec_fn\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 406 | "\u001b[1;33m\u001b[0m \u001b[0mstart_new_session\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;31m# don't forward signals\u001b[0m\u001b[1;33m\u001b[0m\n", 407 | "\u001b[1;33m\u001b[0m \u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 408 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpid\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mproc\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpid\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 409 | "\n", 410 | "\u001b[1;31mFile: \u001b[0m~/conda/envs/jupyterhub-tutorial/lib/python3.5/site-packages/jupyterhub/spawner.py\n", 411 | "\u001b[1;31mType: \u001b[0mfunction\n" 412 | ] 413 | } 414 | ], 415 | "source": [ 416 | "LocalProcessSpawner.start??" 417 | ] 418 | }, 419 | { 420 | "cell_type": "markdown", 421 | "metadata": {}, 422 | "source": [ 423 | "Here is an example of a spawner that allows specifying extra *arguments* to pass to a user's notebook server, via `.options_form`. It results in a form like this:\n", 424 | "\n", 425 | "![form](img/spawn-form.png)" 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 4, 431 | "metadata": {}, 432 | "outputs": [], 433 | "source": [ 434 | "from traitlets import default\n", 435 | "\n", 436 | "class DemoFormSpawner(LocalProcessSpawner):\n", 437 | " @default('options_form')\n", 438 | " def _options_form(self):\n", 439 | " default_env = \"YOURNAME=%s\\n\" % self.user.name\n", 440 | " return \"\"\"\n", 441 | " \n", 442 | " \n", 443 | " \"\"\".format(env=default_env)\n", 444 | " \n", 445 | " def options_from_form(self, formdata):\n", 446 | " \"\"\"Turn html formdata (always lists of strings) into the dict we want.\"\"\"\n", 447 | " options = {}\n", 448 | " arg_s = formdata.get('args', [''])[0].strip()\n", 449 | " if arg_s:\n", 450 | " options['argv'] = shlex.split(arg_s)\n", 451 | " return options\n", 452 | " \n", 453 | " def get_args(self):\n", 454 | " \"\"\"Return arguments to pass to the notebook server\"\"\"\n", 455 | " argv = super().get_args()\n", 456 | " if self.user_options.get('argv'):\n", 457 | " argv.extend(self.user_options['argv'])\n", 458 | " return argv\n", 459 | " \n", 460 | " def get_env(self):\n", 461 | " \"\"\"Return environment variable dict\"\"\"\n", 462 | " env = super().get_env()\n", 463 | " return env" 464 | ] 465 | }, 466 | { 467 | "cell_type": "markdown", 468 | "metadata": {}, 469 | "source": [ 470 | "## Exercise:\n", 471 | "\n", 472 | "Write a custom Spawner that allows users to specify *environment variables* to load into their server.\n", 473 | "\n", 474 | "\n", 475 | "-------" 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": 5, 481 | "metadata": { 482 | "collapsed": true 483 | }, 484 | "outputs": [], 485 | "source": [ 486 | "from dockerspawner import DockerSpawner" 487 | ] 488 | }, 489 | { 490 | "cell_type": "code", 491 | "execution_count": 6, 492 | "metadata": {}, 493 | "outputs": [ 494 | { 495 | "name": "stdout", 496 | "output_type": "stream", 497 | "text": [ 498 | "\u001b[1;31mSignature: \u001b[0m\u001b[0mDockerSpawner\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mimage\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mextra_create_kwargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mextra_start_kwargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mextra_host_config\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 499 | "\u001b[1;31mSource:\u001b[0m\n", 500 | " \u001b[1;33m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[1;33m\u001b[0m\n", 501 | "\u001b[1;33m\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mimage\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mextra_create_kwargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 502 | "\u001b[1;33m\u001b[0m \u001b[0mextra_start_kwargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mextra_host_config\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 503 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"\"\"Start the single-user server in a docker container. You can override\u001b[0m\n", 504 | "\u001b[1;34m the default parameters passed to `create_container` through the\u001b[0m\n", 505 | "\u001b[1;34m `extra_create_kwargs` dictionary and passed to `start` through the\u001b[0m\n", 506 | "\u001b[1;34m `extra_start_kwargs` dictionary. You can also override the\u001b[0m\n", 507 | "\u001b[1;34m 'host_config' parameter passed to `create_container` through the\u001b[0m\n", 508 | "\u001b[1;34m `extra_host_config` dictionary.\u001b[0m\n", 509 | "\u001b[1;34m\u001b[0m\n", 510 | "\u001b[1;34m Per-instance `extra_create_kwargs`, `extra_start_kwargs`, and\u001b[0m\n", 511 | "\u001b[1;34m `extra_host_config` take precedence over their global counterparts.\u001b[0m\n", 512 | "\u001b[1;34m\u001b[0m\n", 513 | "\u001b[1;34m \"\"\"\u001b[0m\u001b[1;33m\u001b[0m\n", 514 | "\u001b[1;33m\u001b[0m \u001b[0mcontainer\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_container\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 515 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mcontainer\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 516 | "\u001b[1;33m\u001b[0m \u001b[0mimage\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mimage\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_image\u001b[0m\u001b[1;33m\u001b[0m\n", 517 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 518 | "\u001b[1;33m\u001b[0m \u001b[1;31m# build the dictionary of keyword arguments for create_container\u001b[0m\u001b[1;33m\u001b[0m\n", 519 | "\u001b[1;33m\u001b[0m \u001b[0mcreate_kwargs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\u001b[0m\n", 520 | "\u001b[1;33m\u001b[0m \u001b[0mimage\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mimage\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 521 | "\u001b[1;33m\u001b[0m \u001b[0menvironment\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_env\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 522 | "\u001b[1;33m\u001b[0m \u001b[0mvolumes\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvolume_mount_points\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 523 | "\u001b[1;33m\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_name\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 524 | "\u001b[1;33m\u001b[0m \u001b[0mcreate_kwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextra_create_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 525 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mextra_create_kwargs\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 526 | "\u001b[1;33m\u001b[0m \u001b[0mcreate_kwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mextra_create_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 527 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 528 | "\u001b[1;33m\u001b[0m \u001b[1;31m# build the dictionary of keyword arguments for host_config\u001b[0m\u001b[1;33m\u001b[0m\n", 529 | "\u001b[1;33m\u001b[0m \u001b[0mhost_config\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mbinds\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvolume_binds\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlinks\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlinks\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 530 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 531 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muse_internal_ip\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 532 | "\u001b[1;33m\u001b[0m \u001b[0mhost_config\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'port_bindings'\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m{\u001b[0m\u001b[1;36m8888\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_ip\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m\u001b[0m\n", 533 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 534 | "\u001b[1;33m\u001b[0m \u001b[0mhost_config\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextra_host_config\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 535 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 536 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mextra_host_config\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 537 | "\u001b[1;33m\u001b[0m \u001b[0mhost_config\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mextra_host_config\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 538 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 539 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdebug\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Starting host with config: %s\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mhost_config\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 540 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 541 | "\u001b[1;33m\u001b[0m \u001b[0mhost_config\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcreate_host_config\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mhost_config\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 542 | "\u001b[1;33m\u001b[0m \u001b[0mcreate_kwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msetdefault\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'host_config'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m{\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mhost_config\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 543 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 544 | "\u001b[1;33m\u001b[0m \u001b[1;31m# create the container\u001b[0m\u001b[1;33m\u001b[0m\n", 545 | "\u001b[1;33m\u001b[0m \u001b[0mresp\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdocker\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'create_container'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mcreate_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 546 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_id\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mresp\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'Id'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\n", 547 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\u001b[0m\n", 548 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"Created container '%s' (id: %s) from image %s\"\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 549 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_name\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_id\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;36m7\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mimage\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 550 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 551 | "\u001b[1;33m\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 552 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\u001b[0m\n", 553 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"Found existing container '%s' (id: %s)\"\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 554 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_name\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_id\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;36m7\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 555 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 556 | "\u001b[1;33m\u001b[0m \u001b[1;31m# TODO: handle unpause\u001b[0m\u001b[1;33m\u001b[0m\n", 557 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\u001b[0m\n", 558 | "\u001b[1;33m\u001b[0m \u001b[1;34m\"Starting container '%s' (id: %s)\"\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\n", 559 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_name\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_id\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;36m7\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 560 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 561 | "\u001b[1;33m\u001b[0m \u001b[1;31m# build the dictionary of keyword arguments for start\u001b[0m\u001b[1;33m\u001b[0m\n", 562 | "\u001b[1;33m\u001b[0m \u001b[0mstart_kwargs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m{\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m\u001b[0m\n", 563 | "\u001b[1;33m\u001b[0m \u001b[0mstart_kwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextra_start_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 564 | "\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mextra_start_kwargs\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n", 565 | "\u001b[1;33m\u001b[0m \u001b[0mstart_kwargs\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mextra_start_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 566 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 567 | "\u001b[1;33m\u001b[0m \u001b[1;31m# start the container\u001b[0m\u001b[1;33m\u001b[0m\n", 568 | "\u001b[1;33m\u001b[0m \u001b[1;32myield\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdocker\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'start'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcontainer_id\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mstart_kwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 569 | "\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\n", 570 | "\u001b[1;33m\u001b[0m \u001b[0mip\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mport\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32myield\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_ip_and_port\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n", 571 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mip\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mip\u001b[0m\u001b[1;33m\u001b[0m\n", 572 | "\u001b[1;33m\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0muser\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mserver\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mport\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mport\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 573 | "\n", 574 | "\u001b[1;31mFile: \u001b[0m~/conda/envs/jupyterhub-tutorial/lib/python3.5/site-packages/dockerspawner/dockerspawner.py\n", 575 | "\u001b[1;31mType: \u001b[0mfunction\n" 576 | ] 577 | } 578 | ], 579 | "source": [ 580 | "DockerSpawner.start??" 581 | ] 582 | }, 583 | { 584 | "cell_type": "markdown", 585 | "metadata": {}, 586 | "source": [ 587 | "## Exercise:\n", 588 | "\n", 589 | "Subclass DockerSpawner so that users can specify via `options_form` what docker image to use.\n", 590 | "\n", 591 | "Candidates from the [Jupyter docker-stacks repo](https://github.com/jupyter/docker-stacks) include:\n", 592 | "\n", 593 | "- jupyter/minimal-singleuser\n", 594 | "- jupyter/scipy-singleuser\n", 595 | "- jupyter/r-singleuser\n", 596 | "- jupyter/datascience-singleuser\n", 597 | "- jupyter/pyspark-singleuser\n", 598 | "\n", 599 | "Or, build your own images with\n", 600 | "\n", 601 | " FROM jupyterhub/singleuser\n", 602 | " \n", 603 | "The easiest version will assume that the images are fetched already." 604 | ] 605 | }, 606 | { 607 | "cell_type": "markdown", 608 | "metadata": {}, 609 | "source": [ 610 | "## Extra credit:\n", 611 | "\n", 612 | "Subclass DockerSpawner so that users can specify via `options_form` a GitHub repository to clone and install, a la [binder](http://mybinder.org)." 613 | ] 614 | } 615 | ], 616 | "metadata": { 617 | "kernelspec": { 618 | "display_name": "Python 3", 619 | "language": "python", 620 | "name": "python3" 621 | }, 622 | "language_info": { 623 | "codemirror_mode": { 624 | "name": "ipython", 625 | "version": 3 626 | }, 627 | "file_extension": ".py", 628 | "mimetype": "text/x-python", 629 | "name": "python", 630 | "nbconvert_exporter": "python", 631 | "pygments_lexer": "ipython3", 632 | "version": "3.5.2" 633 | } 634 | }, 635 | "nbformat": 4, 636 | "nbformat_minor": 1 637 | } 638 | -------------------------------------------------------------------------------- /docs/timeline.md: -------------------------------------------------------------------------------- 1 | # Timeline of tutorial video 2 | 3 | [PyData London 2016 YouTube Video](https://youtu.be/gSVvxOchT8Y) 4 | 5 | ## Introduction to Jupyter notebooks and JupyterHub 6 | 7 | 0:00:00 Welcome and Intro 8 | 9 | 0:01:00 GitHub repo that accompanies the talk 10 | 11 | 0:01:44 What is the Notebook? 12 | 13 | 0:03:00 What is a Notebook Server? 14 | 15 | ## Overview of JupyterHub 16 | 17 | 0:04:17 JupyterHub 18 | 19 | 0:05:41 Login 20 | 21 | 0:05:55 Spawner 22 | 23 | 0:06:27 Proxy 24 | 25 | 0:07:11 Redirect user 26 | 27 | 0:07:17 Browser to ask hub for auth 28 | 29 | [Additional reading for overview] (http://jupyterhub.readthedocs.io/en/latest/getting-started.html#overview) 30 | 31 | ## Installation of JupyterHub 32 | 33 | 0:07:56 Installation (as admin) 34 | 35 | 0:10:44 Installation (this repo) 36 | 37 | 0:11:18 Installation: Caveats 38 | 39 | 0:12:18 conda-forge 40 | 41 | - community maintained conda packages 42 | - Add conda-forge to default conda sources 43 | 44 | 0:13:38 Installation docker (covered later on) 45 | 46 | [Installation instructions can be found under Prerequisites and Installation](https://github.com/jupyterhub/jupyterhub/blob/master/README.md) 47 | 48 | ## Configuring JupyterHub 49 | 50 | 0:14:18 JupyterHub Defaults 51 | 52 | - Default behavior 53 | - Auth: PAM 54 | - Spawning: Local users 55 | - Hub run as root (alternative: sudospawner is fraught with peril) 56 | 57 | 0:15:12 Type jupyterhub in terminal 58 | 59 | - message returned that the hub will not start since there is no SSL provisioned 60 | - If you want to run without SSL, do so at your own risk. 61 | 62 | 0:16:26 SSL 63 | 64 | - Use a self signed cert 65 | - Let's Encrypt 0:17:30 66 | 67 | 0:18:12 Configure jupyterhub 68 | 69 | - create file 70 | - edit config file 0:19:12 71 | 72 | 0:20:33 Connect to hub publicly 73 | 74 | - login, spawn server, redirect 75 | - control panel 0:21:20 76 | 77 | ## Authenticators 78 | 79 | 0:21:52 Installing kernels for all users 80 | 81 | 0:24:15 Using GitHub OAuth 82 | 83 | - We have simple PAM; tell server to use GitHub OAuth 84 | - Authorization callback URL 85 | - Client ID 86 | - Client Secret 87 | 88 | - ./env -> export the variables 89 | 90 | 0:30:44 Tell Jupyter to use oauthenticator 91 | 92 | 0:32:48 Sign in with GitHub 93 | 94 | 0:34:20 Specifying users 95 | 96 | - PAM ok 97 | - GitHub probably not ok 98 | - user whitelist - put in a python set in config file 99 | - admin users - put in a python set in config file 100 | 101 | 0:36:26 Jupyterhub Custom Authenticators 102 | 103 | - PAM - form based fairly simple 104 | - Secure Authenticator 105 | - jupyterhub hashing salted functions 106 | 107 | ## Spawning Processes 108 | 109 | 0:42:14 Using DockerSpawner 110 | 111 | - netifaces - python convenience library 112 | - local GH to general GH users 113 | - GH - DockerSpawner and whitelist 114 | 115 | 0:44:00 Initially missing piece Hub API if cookie is valid 116 | 117 | - docker0 ip address netifaces 118 | 119 | 0:48:00 Lots you can do with DockerSpawner 120 | 121 | 0:51:19 Customizing JupyterHub Spawners 122 | 123 | - Start my server goes to a form 124 | 125 | 1:07:00 JupyterHub with supervisor 126 | 127 | 1:09:00 Reference deployments 128 | 129 | 1:11:00 Q & A 130 | 131 | 1:13:00 Simula deployment with persistence in Hub 132 | -------------------------------------------------------------------------------- /env: -------------------------------------------------------------------------------- 1 | # Create these values: https://github.com/settings/applications/new 2 | export GITHUB_CLIENT_ID=from_github 3 | export GITHUB_CLIENT_SECRET=also_from_github 4 | export OAUTH_CALLBACK_URL=https://[YOURDOMAIN]/hub/oauth_callback 5 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterhub-tutorial 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python = 3.6 6 | - jupyterhub == 0.7.2 7 | - notebook >= 5.0 8 | - ipykernel >= 4.3 9 | - jupyterlab = 0.26 10 | - pip: 11 | - oauthenticator 12 | - dockerspawner 13 | - netifaces 14 | -------------------------------------------------------------------------------- /jupytercon/JupyterHub-jupytercon-part1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyterhub/jupyterhub-tutorial/2537fdbc5707114077e3128ce3a37052f1eafe02/jupytercon/JupyterHub-jupytercon-part1.pdf -------------------------------------------------------------------------------- /jupyterhub_config.py-docker: -------------------------------------------------------------------------------- 1 | """ 2 | jupyterhub_config.py-docker 3 | --------------------------- 4 | 5 | Configuration for JupyterHub tutorial - used for docker deployment 6 | """ 7 | 8 | # SSL and hub port 9 | c.JupyterHub.ssl_key = 'jupyterhub.key' 10 | c.JupyterHub.ssl_cert = 'jupyterhub.crt' 11 | # or letsencrypt: 12 | # c.JupyterHub.ssl_key = '/etc/letsencrypt/live/hub-tutorial.jupyter.org/privkey.pem' 13 | # c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/hub-tutorial.jupyter.org/fullchain.pem' 14 | # c.JupyterHub.port = 443 15 | 16 | # User whitelist - set of users allowed to use the Hub 17 | c.Authenticator.whitelist = {'minrk', 'willingc', 'yuvipanda'} 18 | 19 | # Administrators - set of users who can administer the Hub itself 20 | c.Authenticator.admin_users = {'minrk'} 21 | 22 | # Authenticator 23 | from oauthenticator.github import GitHubOAuthenticator 24 | c.JupyterHub.authenticator_class = GitHubOAuthenticator 25 | 26 | # Spawner 27 | from dockerspawner import DockerSpawner 28 | c.JupyterHub.spawner_class = DockerSpawner 29 | 30 | # Hub IP and port 31 | # 32 | # By default, the Hub's API listens on localhost. 33 | # Yet, a docker container can't see the Hub's localhost. 34 | # Set the Hub's IP address to the docker0 address and 35 | # tell the Hub to listen on its docker network. 36 | # netifaces is a convenience library to gather IP information. 37 | import netifaces 38 | import platform 39 | if platform.system() == 'Darwin': 40 | en0 = netifaces.ifaddresses('en0') 41 | docker_ipv4 = en0[netifaces.AF_INET][0] 42 | else: 43 | docker0 = netifaces.ifaddresses('docker0') 44 | docker_ipv4 = docker0[netifaces.AF_INET][0] 45 | c.JupyterHub.hub_ip = docker_ipv4['addr'] 46 | -------------------------------------------------------------------------------- /jupyterhub_config.py-localgithub: -------------------------------------------------------------------------------- 1 | """ 2 | jupyterhub_config.py-localgithub 3 | -------------------------------- 4 | 5 | Configuration for JupyterHub tutorial - used for local GitHub Authentication 6 | """ 7 | 8 | # SSL and hub port 9 | c.JupyterHub.ssl_key = 'jupyterhub.key' 10 | c.JupyterHub.ssl_cert = 'jupyterhub.crt' 11 | # or letsencrypt: 12 | # c.JupyterHub.ssl_key = '/etc/letsencrypt/live/hub-tutorial.jupyter.org/privkey.pem' 13 | # c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/hub-tutorial.jupyter.org/fullchain.pem' 14 | # c.JupyterHub.port = 443 15 | 16 | # User whitelist - set of users allowed to use the Hub 17 | c.Authenticator.whitelist = {'minrk', 'willingc', 'yuvipanda'} 18 | 19 | # Administrators - set of users who can administer the Hub itself 20 | c.Authenticator.admin_users = {'minrk'} 21 | 22 | # Authenticator 23 | from oauthenticator.github import LocalGitHubOAuthenticator 24 | c.JupyterHub.authenticator_class = LocalGitHubOAuthenticator 25 | # c.LocalGitHubOAuthenticator.create_system_users = True 26 | -------------------------------------------------------------------------------- /jupyterhub_config.py-ssl: -------------------------------------------------------------------------------- 1 | """ 2 | jupyterhub_config.py-ssl 3 | ------------------------ 4 | 5 | Configuration for JupyterHub tutorial - SSL Configuration 6 | """ 7 | 8 | c.JupyterHub.ssl_key = 'jupyterhub.key' 9 | c.JupyterHub.ssl_cert = 'jupyterhub.crt' 10 | # or letsencrypt: 11 | # c.JupyterHub.ssl_key = '/etc/letsencrypt/live/hub-tutorial.jupyter.org/privkey.pem' 12 | # c.JupyterHub.ssl_cert = '/etc/letsencrypt/live/hub-tutorial.jupyter.org/fullchain.pem' 13 | # public https port 14 | # c.JupyterHub.port = 443 15 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | conda: 2 | file: docs/environment.yml 3 | python: 4 | version: 3 5 | -------------------------------------------------------------------------------- /supervisor/jupyterhub.conf: -------------------------------------------------------------------------------- 1 | # put me in /etc/supervisor/conf.d/jupyterhub.conf 2 | [program:jupyterhub] 3 | command=bash launch.sh 4 | directory=/srv/jupyterhub 5 | autostart=true 6 | autorestart=true 7 | startretries=3 8 | exitcodes=0,2 9 | stopsignal=TERM 10 | redirect_stderr=true 11 | stdout_logfile=/var/log/jupyterhub.log 12 | stdout_logfile_maxbytes=1MB 13 | stdout_logfile_backups=10 14 | user=root 15 | -------------------------------------------------------------------------------- /supervisor/launch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | source ./env 4 | exec jupyterhub $@ 5 | --------------------------------------------------------------------------------