├── .gitignore ├── Part1 ├── 01-tutorial-intro.md ├── 02-introtojs.md ├── 03-thethingsyoucantgoogle.md ├── 04-notebook-extensions.md ├── 05-keyboardshortcut.md ├── 06-storing-config.md ├── MyFirstExtension.ipynb ├── cannotknot.jpg ├── devtools.png └── extensions │ ├── hello-scipy-full.js │ ├── hello-scipy.js │ ├── markcell.js │ ├── quickaction │ ├── main.js │ └── styles.css │ ├── server_ext.py │ ├── server_ext_full.py │ └── solution.js ├── Part2 ├── Exercise 1.ipynb ├── Exercise 2.ipynb ├── Interact.ipynb ├── Widget Events.ipynb ├── Widget List.ipynb ├── Widget Low Level.ipynb ├── images │ ├── assoc.svg │ ├── busy.svg │ ├── display.svg │ ├── execute.svg │ ├── inputoutput.PNG │ ├── state.svg │ ├── state_sync.svg │ ├── transport.svg │ ├── widgetcomm.svg │ ├── widgetcomm2.svg │ └── widgets.PNG └── sln │ ├── 1.py │ ├── 2_1.js │ ├── 2_1.py │ ├── 2_2.js │ ├── 2_2.py │ ├── 2_2_1.js │ └── 2_2_2.js ├── Part3 ├── hub install.ipynb └── images │ ├── bower.png │ ├── hub.svg │ ├── nodejs.png │ ├── npm-logo.svg │ └── pieces.svg ├── readme.md └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | *.un~ 2 | *.swp 3 | __pycache__ 4 | .DS_Store 5 | .ipynb_checkpoints 6 | -------------------------------------------------------------------------------- /Part1/01-tutorial-intro.md: -------------------------------------------------------------------------------- 1 | # SciPy 2015 Jupyter advanced tutorial 2 | 3 | This is some of the material for the advanced Jupyter/IPython SciPy tutorial 4 | that is/was/will be given in July 2015. 5 | 6 | During this tutorial we will cover a few non-usual topics for Jupyter/IPython, 7 | and in particular concerning the notebook. 8 | 9 | The topics we will cover are the following: 10 | 11 | How can you extend the notebook's functionality, first by installing already available extensions, then how to write your own. It will take a little knowledge of javascript, but don't worry, we have a recap for you. 12 | 13 | We'll briefly go into the structure of the different configuration and extension folders, where you should store files and how to retrieve them. 14 | 15 | We'll have a look at some of the Jupyter Notebook APIs to do some easy customisation, and rebind keyboard shortcut. 16 | 17 | We will walk you through how to write an extension that provide custom keyboard shortcuts and actions that the user can trigger. 18 | 19 | Extensions can also extend the notebook server, and you can add custom `handlers` to allow some processing. You will probably be happy to do a bit of Python. 20 | 21 | Once you are Javascript experts, we can go into writing your own widgets, with all the view on the javascript side that talk to Python object in the kernel. 22 | 23 | We will finish with some demos on how you can install Jupyterhub to get a multi-user install. 24 | 25 | By the end of this tutorial you should be able to hack the Jupyter Notebook to cover your own needs. 26 | -------------------------------------------------------------------------------- /Part1/02-introtojs.md: -------------------------------------------------------------------------------- 1 | # The Things of the Web 2 | 3 | Hello reader, welcome to the beautiful world of Jupyter Notebook. 4 | One of the reason Jupyter Notebook is so awesome is that it leverages technologies from the web. This allow you to do a large number of things for free, like video, and audio in the Browser. 5 | 6 | If you want to hack and understand how the notebook works to fully appreciate its potential, you have to get familiar with a few web technologies. 7 | 8 | This chapter is here to give you the necessary basics on web technologies. We will also introduce the core tools you want to use when hacking on the web. 9 | 10 | 11 | ## The web browser 12 | 13 | One of the key tool you use all the time when working on the web your web browser. I'm sure you use your browser everyday, but your browser can be your best ally or worst enemy. The number of functionalities that your browser have and can do is pretty large. Describing all the things can take days, so we will focus on the basics. I encourage you to learn more about what your browser can you by yourself. 14 | 15 | 16 | ### Browser Choice 17 | 18 | As a browser of choice I will use Chrome. Chrome comes by default with a lot of baked in tools, so you will not need to install any extensions. The interface will be also consistent regardless of the Os you are using. 19 | 20 | If you are more a fan of Firefox, you may want to install the developer edition. It comes preconfigured with many defaults. The developer tools in the standard edition will get you a long way, however. 21 | 22 | The other major browser are Safari on MacOS, and Internet Explorer for Windows. Unfortunately, I am not familiar with either of them. 23 | As far as I know you can make most of what we will do in Safari. You might just have to enable developer mode in the advance preferences. 24 | 25 | ## Developer tools. 26 | 27 | The developer tools are a number of panels that allow you to interact with the web page in a way much different than usual browsing. You can sort of see that as a powerful debugger that allow you to modify and inspect the running page live. 28 | 29 | To open the developer tools, in **Chrome**, go to the `View > Developer > Developer Tools`. In Firefox, go to the menu, `Developer > Toggle Tools`. 30 | I suggest you learn the keyboard shortcut to open the Dev Tools. It's probably one of the shortcuts you will use the most in this tutorial. 31 | 32 | ![](devtools.png) 33 | 34 | 35 | Ok, so let's look at different areas of the developer tool you will be interested in. 36 | 37 | #### DOM Tree 38 | 39 | The far left tab, named `Elements` or `Inspector` is the tab that allow you to view the current structure of the 'Document Object Model', or DOM. 40 | We won't really get into the DOM much. As a first approximation, the DOM is the current state of the HTML. You want use this tab if you are trying to inspect or modify the structure of the DOM. 41 | 42 | One of the useful things is the small magnifying glass just to the left of the `Elements`. 43 | It allow you to click on a visual element in a page, and get it selected on the tree view. 44 | 45 | Let's try to mess a bit with the DOM. In the tree view on the upper left, try to double click and edit some element of the DOM. You will see that when you hover the tree, the relevant part of the view is highlighted. Try to figure out where the cell, the pager, and toolbar are in a notebook page. 46 | 47 | ### CSS inspector. 48 | 49 | On the right of the DOM Tree view is the CSS inspector. This lets you observe and modify the CSS rules that apply to the current selected element. It is these rules that describe the layout and appearance of the notebook page. 50 | 51 | ### Javascript console 52 | 53 | The last part of the Devtools that we will use today is the Javascript console. 54 | It's like IPython or any other REPL, but it runs Javascript in the context of the page. 55 | 56 | It's also different from usual REPLs in that the javascript on the page is continually executing, and does not pause when you're prompted for input. 57 | 58 | Let's try the basic of the console. 59 | 60 | ```javascript 61 | > console.log('Hello Jupyter Notebook') 62 | ``` 63 | 64 | You can also run a task in the background. 65 | 66 | ```javascript 67 | > setInterval(function(){console.log('hi there')}, 3000) 68 | ``` 69 | 70 | This will print every 3 seconds. When it annoys you enough, refresh your page, all your changes will be gone. 71 | 72 | Let's try something more visual: 73 | 74 | ```javascript 75 | > $('.cell:even').slideUp('slow').slideDown('slow') 76 | ``` 77 | 78 | We won't get into details, but you did something equivalent to numpy broadcasting. You applied a function to a collection of elements of the DOM. 79 | 80 | 81 | ## Browser lies 82 | 83 | There's an old programmer joke that there are only two hard problems with computers: naming things, cache invalidation, and off by one errors. Unfortunately, we have to deal with all of these in developing for the web. There are a few extension that allow you to flush cache and reload the page at the same time. But one of the easy options is to deactivate Browser caching when your developer tools are open. 84 | 85 | To do so with your dev tools open, click on the setting menu (gear icon on the top right) and disable caching when you are developing. 86 | 87 | This will most likely prevent you from madness, as it is easy to assume when you reload the page that you get the last version of the javascript you just saved. 88 | 89 | Keep in mind that opening the developer tools might slow down your browser. You might not see it, or feel it. Though it might have effect in case of timing bugs, and race condition (also know as Heisenbugs, after the quantum physicist). Opening the developer tools may make some bugs disappear. 90 | 91 | By quantum physicist, I of course mean people who are a superposition of Physicist and non-Physicist until you observe them. 92 | 93 | One last tricky-bit is that `$`, `_`, and a few other function/methods might not be and behave the same in console and in scripts. We'll see what these are in next chapter. 94 | 95 | 96 | ## Conclusion 97 | 98 | Well you get the basics for the tools. There is much more to say, but you should have enough knowledge to mess around. The goal here is to give you enough to get started. Next step is to introduce not the tool themselves but some common utilities, and non-written things that are common knowlege among web developers. 99 | -------------------------------------------------------------------------------- /Part1/03-thethingsyoucantgoogle.md: -------------------------------------------------------------------------------- 1 | # The things you can't Google 2 | 3 | There are some things that are inherently hard to Google. 4 | Let's takes for example `html5 website` (I want https://html5.org/). Google has difficulty understanding this. You might encounter a few such terms when doing web development. 5 | 6 | ![cannotknow](cannotknot.jpg) 7 | 8 | ## jQuery (aka `$`) 9 | 10 | In Javascript, `$` is a valid identifier. By convention, a widely used Javascript library known as [`jQuery`](jquery.org) injects itself into the global namespace as `$`. (But beware! `$` can also be a browser native interface that looks and behaves almost like jQuery, but only in the console.) 11 | 12 | jQuery is a library to do a lot of important but not really related things. Code written with it is much more concise, but often not very clear. It's not great, but it's almost indispensable, and a lot of Javascript uses it. 13 | 14 | You will often see jQuery used like `$('...')`. If the string part looks like an HTML tag (`$('
')`), it's making a new element. If it looks like a CSS selector, it's selecting existing tags. This can give you some short and readable syntax like `$('.cell:odd').remove()` instead of: 15 | 16 | ```javascript 17 | var elts = document.getElementsByClassName('cell'); 18 | var to_remove = []; 19 | for(var i in elts){ 20 | if (i%2 === 0){ 21 | to_remove.push(es[i]); 22 | } 23 | } 24 | 25 | ... 26 | ``` 27 | 28 | `$.something` are generally utility functions. 29 | 30 | ## Underscore (aka `_`), 31 | 32 | One beauty of javascript is its ability to use un-googleable names that have ambiguous meaning. This is one of the reason you find `.js` or `js` suffixes in javascript to reduce the ambiguity. Though it is not always the case. In particular you will find a few modules that have the good habit of being bound (or bind themselves) to `_`. Thus you might see things like `_.map`, `_.proxy`,`_.filter`, ... 33 | 34 | Most of the time the library bound to `_` is called "Underscore", but still rarely name "Underscore.js". It provides a few utility functions. 35 | 36 | ## This or that ? 37 | 38 | In Javascript you will often find the following construct: 39 | 40 | ```javascript 41 | var that = this; 42 | ``` 43 | 44 | This, or should I say that, is often confusing for the newcomer, especially if he or she comes from a Python background. `this` looks alluringly similar to `self`, but it doesn't always refer to what the experienced Pythonista might expect. 45 | 46 | In Javascript, there is no real difference between objects and functions. When the programmer creates what looks like a class and methods, that's not how Javascript sees it. The keyword `this` always refer to the current context the object is in, which by default is the **current function**. 47 | 48 | Let's take for example the following piece of code, that could be thought of as the `execute` method of a cell: 49 | 50 | ```javascript 51 | Cell.prototype.execute = function(){ 52 | this.kernel.execute(this.code) // this refer to a Cell object. 53 | } 54 | ``` 55 | 56 | To delay the execution, one is tempted to write: 57 | 58 | 59 | ```javascript 60 | Cell.prototype.execute = function(delay){ 61 | var do_ex = function(){ 62 | this.kernel.execute(this.code) // this refer to `do_ex` object. 63 | }; 64 | 65 | setTimeout(do_ex, delay); 66 | } 67 | ``` 68 | 69 | As the comment points out, `this` refers to the current function. The way around that is to use a closure around `that`: 70 | 71 | ```javascript 72 | Cell.prototype.execute = function(delay){ 73 | var that = this; 74 | var do_ex = function(){ 75 | that.kernel.execute(that.code) // *that* is still the Cell object 76 | }; 77 | 78 | setTimeout(do_ex, delay); 79 | } 80 | ``` 81 | 82 | Another way to achieve the same thing is to use `$.proxy`, that will set the context (value of `this` of a callback). It is about the same as using a closure, but `$.proxy` can be used on a function you did not construct, or for which you cannot create a closure around the context you like. 83 | 84 | ```javascript 85 | Cell.prototype.execute = function(delay){ 86 | var do_ex = $.proxy(function(){ 87 | this.kernel.execute(this.code) // now this will be the Cell object 88 | }, this); 89 | 90 | setTimeout(do_ex, delay); 91 | } 92 | ``` 93 | 94 | ## require 95 | 96 | Javascript in the browser does not have a nice import system like Python. This is partly because file loading needs to be asynchronous. 97 | 98 | One way around that is to use Asynchronous Module Definition (aka AMD), and we often do that with a library called `require` or `requirejs`. 99 | To use requirejs you need to know two functions: `define` and `require`. 100 | 101 | 102 | ```javascript 103 | define( 104 | ['base/js/namespace', 105 | 'moment' 106 | 'jquery'], function( 107 | namespace, 108 | mmt, 109 | $ 110 | ){ 111 | "use strict" 112 | // your module 113 | } 114 | ) 115 | ``` 116 | 117 | `require` is used in the same way unless you want to run the code that create the 118 | module as soon as possible. Rule of thumb: use `define` unless you cannot. 119 | 120 | In **some** cases, you can though use a simple syntax: 121 | 122 | ```javascript 123 | define(function(){ 124 | "use strict"; 125 | var namespace = require('base/js/namespace') 126 | var mmt = require('moment') 127 | var $ = require('jquery') 128 | 129 | //... your module 130 | }) 131 | ``` 132 | 133 | Which is easier to read, but only works if the module you refer to has already been imported. 134 | It also allows you to get handle to modules in the REPL. 135 | 136 | 137 | ## "use strict" 138 | 139 | Javascript uses the keyword `var` to declare variables. One gotcha is that if you forget `var`, you will implicitly refer to a variable in the global namespace (which in the browser is the `window` object). To prevent that, put the string `"use strict"` in quotes at the top of your module scope. This will make your browser less tolerant of forgetting `var` (and a few other mistakes), which will save you from headaches. 140 | 141 | ```javascript 142 | define([..., requirements], function(a,b,c){ 143 | "use strict"; 144 | }) 145 | ``` 146 | 147 | Top of module scope does not always mean top of file - using `"use strict"` at the top of a file might cause issues when working with legacy code. 148 | 149 | ## unlimited argument, default to undefined 150 | 151 | One thing to be aware of is that Javascript will never complain if you pass too many of too few arguments to a function. 152 | 153 | Thus you probably want to check arguments for undefined. This can be helpful though for default values. 154 | 155 | ```javascript 156 | > var say_hi = function(name){ console.log('Hi', name || 'unnamed person')} 157 | undefined 158 | > say_hi() 159 | Hi unnamed person 160 | > say_hi('Matthias') 161 | Hi Matthias 162 | 163 | ``` 164 | 165 | ## ==, === 166 | 167 | Javascript test for equality is done with triple equal, not double equal. 168 | Double equal will try to cast both members before doing a "smart" comparison, 169 | leading to some unexpected behaviour: 170 | 171 | ```javascript 172 | > '0' == 0 173 | true 174 | > 0 == '' 175 | true 176 | > '' == '0' 177 | false 178 | ``` 179 | 180 | 181 | 182 | ## IIFE 183 | 184 | You might find the following Here and there. These are Immediately Invoked Function Expression. You might find them: 185 | 186 | - At module/file level 187 | - In loops. 188 | 189 | ```javascript 190 | X = (function(A){ 191 | // do stuff 192 | // loosely 193 | // var X = something(A) 194 | // 195 | return X 196 | })(A) 197 | ``` 198 | 199 | (look like lisp right ?) 200 | 201 | These are basically a work around some scoping problem in JS. Imagine that in python: 202 | 203 | ```javascript 204 | >>> for i in range(5): 205 | >>> print(i) 206 | 5 207 | 5 208 | 5 209 | 5 210 | 5 211 | ``` 212 | 213 | You could fix that by: 214 | 215 | ```javascript 216 | myfun = lambda x:print(x) 217 | for i in range(5): 218 | myfun(i) 219 | ``` 220 | That can be rewritten as 221 | 222 | ```javascript 223 | for i in range(5): 224 | (lambda x:print(x))(i) 225 | ``` 226 | 227 | Same in js 228 | 229 | ```javascript 230 | //... 231 | X = do_stuff(A) 232 | ``` 233 | 234 | wrap in a function 235 | 236 | ```javascript 237 | function(A){ 238 | ... 239 | return do_stuff(A) 240 | } 241 | ``` 242 | Make it an expression 243 | 244 | ```javascript 245 | ( 246 | function(A){...} 247 | ) 248 | ``` 249 | 250 | Call with A as parameter, and assign to X 251 | 252 | ```javascript 253 | X = (function...)(A) 254 | ``` 255 | 256 | ## MDN 257 | 258 | Last tip, [Mozilla Developer Network](https://developer.mozilla.org/) is your 259 | friend (perhaps even more than Google) and often has really **good** docs 260 | with examples on how to use javascript/html/css. 261 | 262 | To know whether some features can 263 | be used across browsers, you can check 264 | [Can I use](http://caniuse.com/#search=translate). 265 | -------------------------------------------------------------------------------- /Part1/04-notebook-extensions.md: -------------------------------------------------------------------------------- 1 | # Notebook extensions 2 | 3 | Notebook extensions allow users to control the behavior of the Notebook and add functionality. 4 | 5 | Extensions capabilities can range from loading notebook files from [Google Drive](https://github.com/jupyter/jupyter-drive), or PostgreSQL server, presenting notebooks in the form of a slideshow, to adding a convenient button or keyboard shortcut for an action the user does often. 6 | 7 | The way we write Jupyter/IPython is to provide the minimal sensible default, with easy access to configuration for extension to modify behavior. 8 | 9 | Extensions can be composed of many pieces, but you will mostly find a Javascript part that lives on the frontend side (ie, the Browser), and a part that lives on the server side (written in Python). For now, we will focus on the Javascript side. 10 | 11 | There are a number of repos here and there on the internet, and we haven't taken the time to write a Jupyter Store (yet), to make extensions easily installable. Well, I suppose this could be done as an extension, and your research on the web will probably show that it can be done, but we will still focus on the old manual way of installing extension to learn how things works, because that's why you are here. 12 | 13 | Ok, so here are links to some active repos, with extensions: 14 | 15 | - https://github.com/ipython-contrib/IPython-notebook-extensions – check out the right branch for your version of IPython. If you are using IPython 3.x, you want extensions from the 3.x branch. 16 | - https://bitbucket.org/ipre/calico/ look in the `notebook/nbextension` folder. 17 | 18 | Ok, let's go. I've made a minimal extension for you in `extensions` next to this file. Link or copy it over into: 19 | 20 | 21 | ```shell 22 | $ ls ~/.ipython/nbextensions/ 23 | hello-scipy.js 24 | ``` 25 | 26 | > Tip: `~/.ipython` is the per-user config, there are system wide install location, 27 | and in 4.0 some of these folders will be `jupyter` instead of `ipython`. 28 | 29 | 30 | Now let's open a notebook and configure it to load the extension automatically. 31 | In a new notebook, or the one I provides with a reminder of the instructions, open the developer console 32 | and enter the following: 33 | 34 | 35 | ```js 36 | IPython.notebook.config.update({ 37 | "load_extensions": {"hello-scipy":true} 38 | }) 39 | ``` 40 | 41 | Now Reload your page, and look at the Javascript console - it should tell you what to do next! 42 | 43 | ## Explanation 44 | 45 | Do not be preoccupied with what `IPython.notebook.config.update` is: we will see that later. 46 | 47 | The `"load_extensions"` part takes a dict with the names of extensions, and whether they are loaded 48 | or not. It is one of the config value which is now stored on server side. 49 | 50 | There is a way to activate extensions from outside the notebook, but we won't see 51 | that for now. 52 | 53 | ### The extension 54 | 55 | ```js 56 | define([], function(){ 57 | 58 | function _on_load(){ 59 | console.info('Hello SciPy 2015') 60 | } 61 | 62 | return {load_ipython_extension: _on_load }; 63 | }) 64 | ``` 65 | 66 | The define call: `define(function(){` suggests that we have no dependencies, 67 | 68 | For readability, we define a function that will be called on notebook load at the right time. 69 | We keep python convention that `_xxx` is private. 70 | ```js 71 | function _on_load(){ 72 | console.info('Hello SciPy 2015') 73 | } 74 | ``` 75 | 76 | We only export a function called `load_ipython_extension` to the outside world: 77 | `return {load_ipython_extension: _on_load };`. Anything outside of this dict will 78 | be inaccessible for the rest of the code. You can see that as Python's `__all__`. 79 | 80 | Note that you will find legacy extensions on the internet that *do not define* 81 | `load_ipython_extension` and rely on IPython's events, and `custom.js`. 82 | While it mostly works for the time being, these extensions will break in the future 83 | and are subject to race conditions. 84 | 85 | While our Javascript API is still highly in motion, and not guaranteed stable, 86 | we'll try our best to make updating extensions that use `load_ipython_extension` easier 87 | that the ones using events and `custom.js`! 88 | 89 | 90 | ## New keyboard shortcut ! 91 | 92 | Ok, so now let's modify our extension in order to be able to actually modify the 93 | User interface. We will try-to create a shortcut that kill the kernel without confirmation, 94 | and clear all the cell, plus re-run all the things. 95 | 96 | First things, we want to get access to all the IPython instance, to do so we want 97 | to import the right module so that `IPython` variable can be used safely. 98 | 99 | Change the first line to the following 100 | 101 | 102 | ```js 103 | define(['base/js/namespace'], function(IPython){ 104 | ``` 105 | 106 | I remind you that this is basically equivalent to : 107 | 108 | ```python 109 | import base.js.namespace as IPython 110 | ``` 111 | 112 | 113 | Now in your `_on_load` you can access `IPython.`. If you fail 114 | to use the above way of declaring import, IPython might still be accessible on your 115 | machine with your current workload. Though it might break in some cases. 116 | Using `define([...])` insure in the dependency graph that the right file is loaded, 117 | and that the local name will be `IPython` (hint, in next release the global name might be `Jupyter`). 118 | 119 | Ok, now let's make a detour and [Keyboard Shortcut](./05-keyboardshortcut.md). 120 | 121 | A few things you might need : 122 | 123 | ```javascript 124 | var internal_name = IPython.keyboard_manager.actions.register(data, name , `scipy-2015`) 125 | IPython.keyboard_manager.command_shortcuts.remove_shortcut(string) 126 | IPython.keyboard_manager.command_shortcuts.add_shortcut(string, internal_name) 127 | ``` 128 | 129 | The notebook instance have a `clear_all_output` method, and a `kernel` attribute. 130 | The `kernel` instance has a `restart` method that have a `on_success` and `on_error` callbacks. 131 | 132 | 133 | ... 134 | 135 | have you figured it out ? 136 | 137 | My solution: 138 | 139 | ```js 140 | function (env) { 141 | var on_success = undefined; 142 | var on_error = undefined; 143 | 144 | env.notebook.clear_all_output(); 145 | env.notebook.kernel.restart(function(){ 146 | setTimeout(function(){ // wait 1 sec, 147 | // todo listen on Kernel ready event. 148 | console.log('executing all cells') 149 | env.notebook.execute_all_cells() 150 | }, 1000) 151 | }, 152 | on_error // Todo also 153 | ); 154 | } 155 | ``` 156 | 157 | ```js 158 | // register our new action 159 | var action_name = IPython.keyboard_manager.actions.register( 160 | clear_all_cell_restart, 161 | 'clear-all-cells-restart', 162 | 'scipy-2015') 163 | 164 | // unbind 00 165 | IPython.keyboard_manager.command_shortcuts.remove_shortcut('0,0') 166 | 167 | // bind 000 168 | IPython.keyboard_manager.command_shortcuts.add_shortcut('0,0,0', action_name) 169 | ``` 170 | 171 | 172 | ### Why use an action. 173 | 174 | How are things up until now ? You might feel like the code is a bit too verbose, 175 | and that some part are unnecessary right ? Now we will start to see why we use such 176 | verbose methods. 177 | 178 | You might have seen that some attributes of action seem to be unused. 179 | 180 | ``` 181 | help: 'Clear all cell and restart kernel without confirmations', 182 | icon : 'fa-recycle', 183 | help_index : '', 184 | ``` 185 | 186 | Now that your extension works go get a look in the help menu, keyboard shortcut submenu. 187 | If all is fine, you should see your new shortcut in there, with the help text. 188 | The help index is use to order/group the common shortcut together. The only last 189 | unused piece is the icon. 190 | 191 | With all theses attribute, you can easily bind an _action_, to either a keyboard shortcut, 192 | a button in a toolbar, or in a menu item (api is not there yet for that though). 193 | We often saw people wanting the same action in two places, and duplicating code, 194 | which is a bit painful. By defining action separately this make it easy to use these 195 | in many places keeping it DRY. This also allow you to distribute actions library 196 | without actually binding them and let user do their own key/icon bindings. 197 | 198 | Let see that in next section with toolbars. 199 | 200 | ### toolbars. 201 | 202 | Ok, that will be pretty simple you already did all the job :-) 203 | 204 | You just need to know that following exist, and take a list of action names: 205 | 206 | `IPython.toolbar.add_buttons_group` 207 | 208 | Now, go edit your custom extension ! You can also try to install `markcell.js` extension, 209 | `require()` it in your extension and try to use someof the methods defined in it. 210 | This show you how to spread your extension potentially across many files. 211 | 212 | Here is my solution: 213 | 214 | ``` 215 | IPython.toolbar.add_buttons_group(['scipy-2015.clear-all-cells-restart','ipython.restart-kernel']) 216 | ``` 217 | 218 | each call to this API will generate a new group of button with the default icons, 219 | and if you hover the button the help text will remind you the action. 220 | 221 | 222 | 223 | ### interact with user 224 | 225 | You can ask a value with the `base/js/dialog` module that have some convenience function. 226 | 227 | This module have a `modal` function that you can use like that: 228 | 229 | ``` 230 | dialog.modal({ 231 | body: text_or_dom_node , // jQuery is you friend 232 | title: string, 233 | buttons: { 234 | 'Ok':{ 235 | class: 'btn-primary', 236 | click: on_ok_callback 237 | }, 238 | 'Cancell':{ 239 | //... (or nothing to just dismiss ) 240 | } 241 | }, 242 | notebook:env.notebook, 243 | keyboard_manager: env.notebook.keyboard_manager, 244 | }) 245 | ``` 246 | 247 | 248 | ### server side handler. 249 | 250 | Ok, enough javascript (for now). Let's get back into a sane language. 251 | Notebook extension on the client-side have been there for quite a while, 252 | and we recently added the ability to have a server side extension. 253 | 254 | Server side extension are, as any IPython extension simply Python modules that 255 | define a specific method. In our case `load_jupyter_server_extension` 256 | (Yes we are ready for the future). 257 | 258 | Here is the minimal extension you can have: 259 | 260 | ```python 261 | def load_jupyter_server_extension(nbapp): 262 | pass 263 | ``` 264 | 265 | I've already provided that for you, in the `extensions` dir. 266 | You can try to run the following, and look at the console while starting the notebook, 267 | you will be able to see a new login message. 268 | 269 | ``` 270 | python3 -m IPython notebook --notebook-dir=~ --NotebookApp.server_extensions="['extensions.server_ext']" 271 | ``` 272 | 273 | 274 | Now let's add a handler capable of treating requests. 275 | As the Notebook is written in Tornado, we need to use tornado way to have 276 | handlers. We have to subclass `RequestHandler` and define a `initialize` method, 277 | as well as a methods which name match the request we will be doing (get, put patch).. 278 | 279 | 280 | 281 | ```python 282 | from IPython.html.utils import url_path_join as ujoin 283 | from tornado.web import RequestHandler 284 | 285 | 286 | class MyLogHandler(RequestHandler): 287 | def initialize(self, log=None): 288 | self.log = log 289 | 290 | def put(self): 291 | data = self.request.body.decode('utf-8') 292 | self.log.info(data) 293 | self.finish() 294 | 295 | 296 | def load_jupyter_server_extension(nbapp): 297 | nbapp.log.info('SciPy Ext loaded') 298 | 299 | webapp = nbapp.web_app 300 | base_url = webapp.settings['base_url'] 301 | webapp.add_handlers(".*$", [ 302 | (ujoin(base_url, r"/scipy/log"), MyLogHandler, 303 | {'log': nbapp.log}), 304 | ]) 305 | ``` 306 | 307 | Now if we hit the `/scipy/log` url with a PUT request, our `put` method will be 308 | called with the right data. From the Javascript side you can make a PUT request 309 | by adapting the following snippet of code: 310 | 311 | 312 | ```javascript 313 | settings = { 314 | url : '/scipy/log', 315 | processData : false, 316 | type : "PUT", 317 | dataType: "json", 318 | contentType: 'application/json', 319 | }; 320 | $.ajax(settings); 321 | ``` 322 | 323 | Try to use this to log a user input in a dialog. 324 | 325 | We won't go much further on how to write server-side handlers, this is just python/web programming, and is not really IPython specific. 326 | 327 | Let's dive a bit more into how to persist user configuration. 328 | -------------------------------------------------------------------------------- /Part1/05-keyboardshortcut.md: -------------------------------------------------------------------------------- 1 | # Keyboard Shortcuts 2 | 3 | Fair warning: All these APIs are unstable, and can change at any time. 4 | 5 | Through Javascript you can access the keyboard manager. 6 | 7 | The keyboard manager maps (some) shortcuts in command and edit mode to 'actions'. 8 | 9 | A shortcut is a string that represents a sequence of several key presses. Commas separate each step of the sequence, and dashes separate keys that have to be pressed together. 10 | 11 | For example `a,b,c,d` represent the succession of pressing the letters A, B, C and D without modifier. 12 | `Shift-a,b,c,d` will have only the `A` key pressed with shift modifier, and `Shift-a,Shift-b,Shift-c,Shift-d` represents holding shift and pressing `a,b,c,d` in order. 13 | 14 | We can bind some existing actions in command mode, using the javascript console (where `>` and `<` are in and out prompt): 15 | 16 | 17 | ```javascript 18 | > IPython.keyboard_manager.command_shortcuts.add_shortcut('Shift-k','ipython.move-selected-cell-up') 19 | < undefined 20 | 21 | > IPython.keyboard_manager.command_shortcuts.add_shortcut('Shift-j','ipython.move-selected-cell-down') 22 | < undefined 23 | ``` 24 | 25 | To see the list of available actions, you can issue the following in the developer console: 26 | 27 | ```javascript 28 | > $.map( 29 | IPython.keyboard_manager.command_shortcuts.actions._actions, 30 | function(k,v){return v} 31 | ) 32 | 33 | < ["ipython.run-select-next", 34 | "ipython.execute-in-place", 35 | "ipython.execute-and-insert-after", 36 | "ipython.go-to-command-mode", 37 | ... 38 | "ipython.move-cursor-down-or-next-cell", 39 | "ipython.scroll-down", "ipython.scroll-up", 40 | "ipython.save-notebook"] 41 | ``` 42 | 43 | > Tip: Use `Alt-Enter` for entering multiline text on Chrome. 44 | 45 | ## Actions 46 | 47 | In the previous section, you actually bound a keyboard shortcut to an action. 48 | 49 | You are most likely to want the same behavior as other users and/or to have buttons or menu do the same things as keyboard shortcut. 50 | 51 | Also, if like me you are not a huge fan of Javascript, you prefer to avoid rewriting anonymous function in your config file. 52 | 53 | An action is, in its simplest form, a name given to a sequence of API calls done in the notebook frontend. Some action are already pre-defined in Jupyter/IPython, and we prefix their name by `ipython`. Extensions can also register their own actions to be used. 54 | The API and naming of actions is still in flux, so what you read here is still approximate. 55 | 56 | As of this writing, an action is a combination of a `handler`, an `icon` and a `help_text`. The handler is a JavaScript function, that would be called in the right context when the action is triggered. 57 | The `help_text` and `icon` are extra meta-data that are used in various context. For example, if you add an action to a toolbar a button will be created. The icon will automatically be applied to the button, and the help text will appear on hover. Actions also have the capacity to call sub-actions. This make the combination of multiple repetitive tasks easy to customize. 58 | 59 | The quick way to bind a function to a shortcut is to use an anonymous action: 60 | 61 | ```javascript 62 | IPython.keyboard_manager.command_shortcuts.add_shortcut( 63 | 'Ctrl-C,Meta-C,Meta-b,u,t,t,e,r,f,l,y', 64 | { 65 | handler:function(){ 66 | window.open('https://xkcd.com/378/') 67 | } 68 | } 69 | ) 70 | ``` 71 | 72 | This is a bit hard to type quickly enough, but _Real Programmer_ 73 | should probably understand how to use this. 74 | 75 | ### Defining an action 76 | 77 | Le's see how to define an action. You can pick and icon from [the Font Awesome website](http://fortawesome.github.io/Font-Awesome/icons/). Try to avoid also action name to have space. 78 | 79 | We will use the following API to register an action: 80 | 81 | ```javascript 82 | IPython.keyboard_manager.actions.register 83 | ``` 84 | 85 | Let's define an action that clear call cells 86 | output and restart the kernel. 87 | 88 | ```javascript 89 | var clear_all_cells_restart = { 90 | help: 'Clear all cells and restart kernel without confirmations', 91 | icon : 'fa-recycle', 92 | help_index : '', 93 | handler : function (env) { 94 | var on_success = undefined; 95 | var on_error = undefined; 96 | env.notebook.clear_all_output(); 97 | env.notebook.kernel(on_success, on_error); 98 | } 99 | } 100 | ``` 101 | 102 | And now we can register it. The first argument of the `register` function is the action itself, 103 | the second one is the name for the action, and the third one is a namespace prefix (we will use scipy-2015). 104 | 105 | The register function return an id that refer to this action. 106 | 107 | ```javascript 108 | IPython.keyboard_manager.actions.register(clear_all_cell_restart, 'clear-all-cells-restart', 'scipy-2015') 109 | ``` 110 | -------------------------------------------------------------------------------- /Part1/06-storing-config.md: -------------------------------------------------------------------------------- 1 | # Storing configuration 2 | 3 | Your extension may need to store user data such as preferences. You could 4 | use `localStorage` for this, but if the user opens a different browser, or runs 5 | the notebook on a different port, previously stored information won't be 6 | available. So we provide an API to store config through the server. 7 | 8 | First, load `services/config` in your extension: 9 | 10 | ```javascript 11 | define(['services/config' 12 | ], 13 | function(configmod) { 14 | ... 15 | }); 16 | ``` 17 | 18 | There are two classes in this module: 19 | 20 | - `ConfigSection` talks to the server to load and store config. 21 | - `ConfigWithDefaults` is a nicer API for the code using configurable values. 22 | 23 | First, you need to set up a ConfigSection: 24 | 25 | ```javascript 26 | var config = new configmod.ConfigSection('myextension', 27 | {base_url: utils.get_body_data("baseUrl")}); 28 | config.load(); 29 | ``` 30 | 31 | In this example, `'myextension'` is the name of the config section. The server 32 | stores your config in a file with this name. One section should suffice for an 33 | extension. 34 | 35 | The `.load()` method is asynchronous: it will start loading the config, but 36 | it won't wait for loading to finish. 37 | 38 | ## Using configuration values 39 | 40 | Next, let's create a `ConfigWithDefaults` object: 41 | 42 | ```javascript 43 | var foo_config = new configmod.ConfigWithDefaults(config, { 44 | visible: true, 45 | colour: '#fe6500' 46 | }, 'foo') 47 | ``` 48 | 49 | There are three arguments here: 50 | 51 | 1. `config`: the `ConfigSection` we created before. 52 | 2. The default values, which we'll use if we haven't stored something else in 53 | the config. 54 | 3. A subsection name, if you need to subdivide the config section. For simple 55 | extensions, you can leave this out. 56 | 57 | The next step depends on what should happen if we try to get a value while the 58 | config is still loading. If it should wait for loading to finish: 59 | 60 | ```javascript 61 | foo_config.get('colour').then(function(colour) { 62 | // do things with colour 63 | }); 64 | ``` 65 | 66 | If it should use the default instead of waiting: 67 | 68 | ```javascript 69 | colour = foo_config.get_sync('colour'); 70 | ``` 71 | 72 | ## Setting configuration 73 | 74 | To set a single value: 75 | 76 | ```javascript 77 | foo_config.set('visible', false); 78 | ``` 79 | 80 | This is also asynchronous: it sends the new value off, but doesn't wait for a 81 | reply. 82 | 83 | ## Examples 84 | 85 | The notebook itself uses this confuguration system. Remember that to enable 86 | our extension, we did this: 87 | 88 | ```javascript 89 | IPython.notebook.config.update({ 90 | "load_extensions": {"hello-scipy":true} 91 | }) 92 | ``` 93 | 94 | `IPython.notebook.config` is a `ConfigSection` object. It stores a dictionary 95 | of extensions to enable. When you open a notebook, it loads that config, and 96 | loads the extensions specified. 97 | 98 | Storing these as a dictionary (unordered) makes it simpler to add and remove 99 | extensions from the set (`.update()` does a recursive dictionary update). 100 | 101 | [cite2c](https://github.com/takluyver/cite2c) also uses this config system to 102 | store user data. 103 | -------------------------------------------------------------------------------- /Part1/MyFirstExtension.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Reminder of what you should do :\n", 8 | "\n", 9 | "- open developper pannel\n", 10 | "- go to console\n", 11 | "- enter the following\n", 12 | "\n", 13 | "```\n", 14 | "IPython.notebook.config.update({\n", 15 | " \"load_extensions\": {\"hello-scipy\":true}\n", 16 | "})\n", 17 | "```" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": { 24 | "collapsed": false 25 | }, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "text/plain": [ 30 | "datetime.datetime(2015, 6, 26, 11, 46, 8, 450662)" 31 | ] 32 | }, 33 | "execution_count": 1, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "# you will probably need this line to be sure your extension works\n", 40 | "import datetime as d\n", 41 | "d.datetime.now()" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.5.0b2" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 0 66 | } 67 | -------------------------------------------------------------------------------- /Part1/cannotknot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/scipy-advanced-tutorial/1ef56e314527cc01f9f31714af429a292a216b89/Part1/cannotknot.jpg -------------------------------------------------------------------------------- /Part1/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/scipy-advanced-tutorial/1ef56e314527cc01f9f31714af429a292a216b89/Part1/devtools.png -------------------------------------------------------------------------------- /Part1/extensions/hello-scipy-full.js: -------------------------------------------------------------------------------- 1 | define(['base/js/namespace','base/js/dialog','jquery', 'nbextensions/markcell'],function(IPython, dialog, $, mc){ 2 | 3 | var do_restart = function(env){ 4 | env.notebook.clear_all_output(); 5 | env.notebook.kernel.restart(function(){ 6 | setTimeout(function(){ // wait 1 sec, 7 | // todo listen on Kernel ready event. 8 | env.notebook.execute_all_cells() 9 | }, 1000) 10 | }, on_error); 11 | } 12 | 13 | // we will define an action here that should happen when we ask to clear and restart the kernel. 14 | var clear_restart_reason = { 15 | help: 'Clear all cell and restart kernel asking and login reason', 16 | icon : 'fa-recycle', 17 | help_index : '', 18 | handler : function (env) { 19 | var on_success = undefined; 20 | var on_error = undefined; 21 | 22 | var p = $('

').text("Please enter why do yourestart kernel ?") 23 | var input = $('