##// END OF EJS Templates
Merge pull request #8194 from toobaz/get_ipython_in_import...
Thomas Kluyver -
r21010:f1b56134 merge
parent child Browse files
Show More
@@ -1,521 +1,523
1 1 {
2 2 "cells": [
3 3 {
4 4 "cell_type": "markdown",
5 5 "metadata": {},
6 6 "source": [
7 7 "# Importing IPython Notebooks as Modules"
8 8 ]
9 9 },
10 10 {
11 11 "cell_type": "markdown",
12 12 "metadata": {},
13 13 "source": [
14 14 "It is a common problem that people want to import code from IPython Notebooks.\n",
15 15 "This is made difficult by the fact that Notebooks are not plain Python files,\n",
16 16 "and thus cannot be imported by the regular Python machinery.\n",
17 17 "\n",
18 18 "Fortunately, Python provides some fairly sophisticated [hooks](http://www.python.org/dev/peps/pep-0302/) into the import machinery,\n",
19 19 "so we can actually make IPython notebooks importable without much difficulty,\n",
20 20 "and only using public APIs."
21 21 ]
22 22 },
23 23 {
24 24 "cell_type": "code",
25 25 "execution_count": null,
26 26 "metadata": {
27 27 "collapsed": false
28 28 },
29 29 "outputs": [],
30 30 "source": [
31 31 "import io, os, sys, types"
32 32 ]
33 33 },
34 34 {
35 35 "cell_type": "code",
36 36 "execution_count": null,
37 37 "metadata": {
38 38 "collapsed": false
39 39 },
40 40 "outputs": [],
41 41 "source": [
42 "from IPython import get_ipython\n",
42 43 "from IPython.nbformat import current\n",
43 44 "from IPython.core.interactiveshell import InteractiveShell"
44 45 ]
45 46 },
46 47 {
47 48 "cell_type": "markdown",
48 49 "metadata": {},
49 50 "source": [
50 51 "Import hooks typically take the form of two objects:\n",
51 52 "\n",
52 53 "1. a Module **Loader**, which takes a module name (e.g. `'IPython.display'`), and returns a Module\n",
53 54 "2. a Module **Finder**, which figures out whether a module might exist, and tells Python what **Loader** to use"
54 55 ]
55 56 },
56 57 {
57 58 "cell_type": "code",
58 59 "execution_count": null,
59 60 "metadata": {
60 61 "collapsed": false
61 62 },
62 63 "outputs": [],
63 64 "source": [
64 65 "def find_notebook(fullname, path=None):\n",
65 66 " \"\"\"find a notebook, given its fully qualified name and an optional path\n",
66 67 " \n",
67 68 " This turns \"foo.bar\" into \"foo/bar.ipynb\"\n",
68 69 " and tries turning \"Foo_Bar\" into \"Foo Bar\" if Foo_Bar\n",
69 70 " does not exist.\n",
70 71 " \"\"\"\n",
71 72 " name = fullname.rsplit('.', 1)[-1]\n",
72 73 " if not path:\n",
73 74 " path = ['']\n",
74 75 " for d in path:\n",
75 76 " nb_path = os.path.join(d, name + \".ipynb\")\n",
76 77 " if os.path.isfile(nb_path):\n",
77 78 " return nb_path\n",
78 79 " # let import Notebook_Name find \"Notebook Name.ipynb\"\n",
79 80 " nb_path = nb_path.replace(\"_\", \" \")\n",
80 81 " if os.path.isfile(nb_path):\n",
81 82 " return nb_path\n",
82 83 " "
83 84 ]
84 85 },
85 86 {
86 87 "cell_type": "markdown",
87 88 "metadata": {},
88 89 "source": [
89 90 "## Notebook Loader"
90 91 ]
91 92 },
92 93 {
93 94 "cell_type": "markdown",
94 95 "metadata": {},
95 96 "source": [
96 97 "Here we have our Notebook Loader.\n",
97 98 "It's actually quite simple - once we figure out the filename of the module,\n",
98 99 "all it does is:\n",
99 100 "\n",
100 101 "1. load the notebook document into memory\n",
101 102 "2. create an empty Module\n",
102 103 "3. execute every cell in the Module namespace\n",
103 104 "\n",
104 105 "Since IPython cells can have extended syntax,\n",
105 106 "the IPython transform is applied to turn each of these cells into their pure-Python counterparts before executing them.\n",
106 107 "If all of your notebook cells are pure-Python,\n",
107 108 "this step is unnecessary."
108 109 ]
109 110 },
110 111 {
111 112 "cell_type": "code",
112 113 "execution_count": null,
113 114 "metadata": {
114 115 "collapsed": false
115 116 },
116 117 "outputs": [],
117 118 "source": [
118 119 "class NotebookLoader(object):\n",
119 120 " \"\"\"Module Loader for IPython Notebooks\"\"\"\n",
120 121 " def __init__(self, path=None):\n",
121 122 " self.shell = InteractiveShell.instance()\n",
122 123 " self.path = path\n",
123 124 " \n",
124 125 " def load_module(self, fullname):\n",
125 126 " \"\"\"import a notebook as a module\"\"\"\n",
126 127 " path = find_notebook(fullname, self.path)\n",
127 128 " \n",
128 129 " print (\"importing IPython notebook from %s\" % path)\n",
129 130 " \n",
130 131 " # load the notebook object\n",
131 132 " with io.open(path, 'r', encoding='utf-8') as f:\n",
132 133 " nb = current.read(f, 'json')\n",
133 134 " \n",
134 135 " \n",
135 136 " # create the module and add it to sys.modules\n",
136 137 " # if name in sys.modules:\n",
137 138 " # return sys.modules[name]\n",
138 139 " mod = types.ModuleType(fullname)\n",
139 140 " mod.__file__ = path\n",
140 141 " mod.__loader__ = self\n",
142 " mod.__dict__['get_ipython'] = get_ipython\n",
141 143 " sys.modules[fullname] = mod\n",
142 144 " \n",
143 145 " # extra work to ensure that magics that would affect the user_ns\n",
144 146 " # actually affect the notebook module's ns\n",
145 147 " save_user_ns = self.shell.user_ns\n",
146 148 " self.shell.user_ns = mod.__dict__\n",
147 149 " \n",
148 150 " try:\n",
149 151 " for cell in nb.worksheets[0].cells:\n",
150 152 " if cell.cell_type == 'code' and cell.language == 'python':\n",
151 153 " # transform the input to executable Python\n",
152 154 " code = self.shell.input_transformer_manager.transform_cell(cell.input)\n",
153 155 " # run the code in themodule\n",
154 156 " exec(code, mod.__dict__)\n",
155 157 " finally:\n",
156 158 " self.shell.user_ns = save_user_ns\n",
157 159 " return mod\n"
158 160 ]
159 161 },
160 162 {
161 163 "cell_type": "markdown",
162 164 "metadata": {},
163 165 "source": [
164 166 "## The Module Finder"
165 167 ]
166 168 },
167 169 {
168 170 "cell_type": "markdown",
169 171 "metadata": {},
170 172 "source": [
171 173 "The finder is a simple object that tells you whether a name can be imported,\n",
172 174 "and returns the appropriate loader.\n",
173 175 "All this one does is check, when you do:\n",
174 176 "\n",
175 177 "```python\n",
176 178 "import mynotebook\n",
177 179 "```\n",
178 180 "\n",
179 181 "it checks whether `mynotebook.ipynb` exists.\n",
180 182 "If a notebook is found, then it returns a NotebookLoader.\n",
181 183 "\n",
182 184 "Any extra logic is just for resolving paths within packages."
183 185 ]
184 186 },
185 187 {
186 188 "cell_type": "code",
187 189 "execution_count": null,
188 190 "metadata": {
189 191 "collapsed": false
190 192 },
191 193 "outputs": [],
192 194 "source": [
193 195 "class NotebookFinder(object):\n",
194 196 " \"\"\"Module finder that locates IPython Notebooks\"\"\"\n",
195 197 " def __init__(self):\n",
196 198 " self.loaders = {}\n",
197 199 " \n",
198 200 " def find_module(self, fullname, path=None):\n",
199 201 " nb_path = find_notebook(fullname, path)\n",
200 202 " if not nb_path:\n",
201 203 " return\n",
202 204 " \n",
203 205 " key = path\n",
204 206 " if path:\n",
205 207 " # lists aren't hashable\n",
206 208 " key = os.path.sep.join(path)\n",
207 209 " \n",
208 210 " if key not in self.loaders:\n",
209 211 " self.loaders[key] = NotebookLoader(path)\n",
210 212 " return self.loaders[key]\n"
211 213 ]
212 214 },
213 215 {
214 216 "cell_type": "markdown",
215 217 "metadata": {},
216 218 "source": [
217 219 "## Register the hook"
218 220 ]
219 221 },
220 222 {
221 223 "cell_type": "markdown",
222 224 "metadata": {},
223 225 "source": [
224 226 "Now we register the `NotebookFinder` with `sys.meta_path`"
225 227 ]
226 228 },
227 229 {
228 230 "cell_type": "code",
229 231 "execution_count": null,
230 232 "metadata": {
231 233 "collapsed": false
232 234 },
233 235 "outputs": [],
234 236 "source": [
235 237 "sys.meta_path.append(NotebookFinder())"
236 238 ]
237 239 },
238 240 {
239 241 "cell_type": "markdown",
240 242 "metadata": {},
241 243 "source": [
242 244 "After this point, my notebooks should be importable.\n",
243 245 "\n",
244 246 "Let's look at what we have in the CWD:"
245 247 ]
246 248 },
247 249 {
248 250 "cell_type": "code",
249 251 "execution_count": null,
250 252 "metadata": {
251 253 "collapsed": false
252 254 },
253 255 "outputs": [],
254 256 "source": [
255 257 "ls nbpackage"
256 258 ]
257 259 },
258 260 {
259 261 "cell_type": "markdown",
260 262 "metadata": {},
261 263 "source": [
262 264 "So I should be able to `import nbimp.mynotebook`.\n"
263 265 ]
264 266 },
265 267 {
266 268 "cell_type": "markdown",
267 269 "metadata": {},
268 270 "source": [
269 271 "### Aside: displaying notebooks"
270 272 ]
271 273 },
272 274 {
273 275 "cell_type": "markdown",
274 276 "metadata": {},
275 277 "source": [
276 278 "Here is some simple code to display the contents of a notebook\n",
277 279 "with syntax highlighting, etc."
278 280 ]
279 281 },
280 282 {
281 283 "cell_type": "code",
282 284 "execution_count": null,
283 285 "metadata": {
284 286 "collapsed": false
285 287 },
286 288 "outputs": [],
287 289 "source": [
288 290 "from pygments import highlight\n",
289 291 "from pygments.lexers import PythonLexer\n",
290 292 "from pygments.formatters import HtmlFormatter\n",
291 293 "\n",
292 294 "from IPython.display import display, HTML\n",
293 295 "\n",
294 296 "formatter = HtmlFormatter()\n",
295 297 "lexer = PythonLexer()\n",
296 298 "\n",
297 299 "# publish the CSS for pygments highlighting\n",
298 300 "display(HTML(\"\"\"\n",
299 301 "<style type='text/css'>\n",
300 302 "%s\n",
301 303 "</style>\n",
302 304 "\"\"\" % formatter.get_style_defs()\n",
303 305 "))"
304 306 ]
305 307 },
306 308 {
307 309 "cell_type": "code",
308 310 "execution_count": null,
309 311 "metadata": {
310 312 "collapsed": false
311 313 },
312 314 "outputs": [],
313 315 "source": [
314 316 "def show_notebook(fname):\n",
315 317 " \"\"\"display a short summary of the cells of a notebook\"\"\"\n",
316 318 " with io.open(fname, 'r', encoding='utf-8') as f:\n",
317 319 " nb = current.read(f, 'json')\n",
318 320 " html = []\n",
319 321 " for cell in nb.worksheets[0].cells:\n",
320 322 " html.append(\"<h4>%s cell</h4>\" % cell.cell_type)\n",
321 323 " if cell.cell_type == 'code':\n",
322 324 " html.append(highlight(cell.input, lexer, formatter))\n",
323 325 " else:\n",
324 326 " html.append(\"<pre>%s</pre>\" % cell.source)\n",
325 327 " display(HTML('\\n'.join(html)))\n",
326 328 "\n",
327 329 "show_notebook(os.path.join(\"nbpackage\", \"mynotebook.ipynb\"))"
328 330 ]
329 331 },
330 332 {
331 333 "cell_type": "markdown",
332 334 "metadata": {},
333 335 "source": [
334 336 "So my notebook has a heading cell and some code cells,\n",
335 337 "one of which contains some IPython syntax.\n",
336 338 "\n",
337 339 "Let's see what happens when we import it"
338 340 ]
339 341 },
340 342 {
341 343 "cell_type": "code",
342 344 "execution_count": null,
343 345 "metadata": {
344 346 "collapsed": false
345 347 },
346 348 "outputs": [],
347 349 "source": [
348 350 "from nbpackage import mynotebook"
349 351 ]
350 352 },
351 353 {
352 354 "cell_type": "markdown",
353 355 "metadata": {},
354 356 "source": [
355 357 "Hooray, it imported! Does it work?"
356 358 ]
357 359 },
358 360 {
359 361 "cell_type": "code",
360 362 "execution_count": null,
361 363 "metadata": {
362 364 "collapsed": false
363 365 },
364 366 "outputs": [],
365 367 "source": [
366 368 "mynotebook.foo()"
367 369 ]
368 370 },
369 371 {
370 372 "cell_type": "markdown",
371 373 "metadata": {},
372 374 "source": [
373 375 "Hooray again!\n",
374 376 "\n",
375 377 "Even the function that contains IPython syntax works:"
376 378 ]
377 379 },
378 380 {
379 381 "cell_type": "code",
380 382 "execution_count": null,
381 383 "metadata": {
382 384 "collapsed": false
383 385 },
384 386 "outputs": [],
385 387 "source": [
386 388 "mynotebook.has_ip_syntax()"
387 389 ]
388 390 },
389 391 {
390 392 "cell_type": "markdown",
391 393 "metadata": {},
392 394 "source": [
393 395 "## Notebooks in packages"
394 396 ]
395 397 },
396 398 {
397 399 "cell_type": "markdown",
398 400 "metadata": {},
399 401 "source": [
400 402 "We also have a notebook inside the `nb` package,\n",
401 403 "so let's make sure that works as well."
402 404 ]
403 405 },
404 406 {
405 407 "cell_type": "code",
406 408 "execution_count": null,
407 409 "metadata": {
408 410 "collapsed": false
409 411 },
410 412 "outputs": [],
411 413 "source": [
412 414 "ls nbpackage/nbs"
413 415 ]
414 416 },
415 417 {
416 418 "cell_type": "markdown",
417 419 "metadata": {},
418 420 "source": [
419 421 "Note that the `__init__.py` is necessary for `nb` to be considered a package,\n",
420 422 "just like usual."
421 423 ]
422 424 },
423 425 {
424 426 "cell_type": "code",
425 427 "execution_count": null,
426 428 "metadata": {
427 429 "collapsed": false
428 430 },
429 431 "outputs": [],
430 432 "source": [
431 433 "show_notebook(os.path.join(\"nbpackage\", \"nbs\", \"other.ipynb\"))"
432 434 ]
433 435 },
434 436 {
435 437 "cell_type": "code",
436 438 "execution_count": null,
437 439 "metadata": {
438 440 "collapsed": false
439 441 },
440 442 "outputs": [],
441 443 "source": [
442 444 "from nbpackage.nbs import other\n",
443 445 "other.bar(5)"
444 446 ]
445 447 },
446 448 {
447 449 "cell_type": "markdown",
448 450 "metadata": {},
449 451 "source": [
450 452 "So now we have importable notebooks, from both the local directory and inside packages.\n",
451 453 "\n",
452 454 "I can even put a notebook inside IPython, to further demonstrate that this is working properly:"
453 455 ]
454 456 },
455 457 {
456 458 "cell_type": "code",
457 459 "execution_count": null,
458 460 "metadata": {
459 461 "collapsed": false
460 462 },
461 463 "outputs": [],
462 464 "source": [
463 465 "import shutil\n",
464 466 "from IPython.utils.path import get_ipython_package_dir\n",
465 467 "\n",
466 468 "utils = os.path.join(get_ipython_package_dir(), 'utils')\n",
467 469 "shutil.copy(os.path.join(\"nbpackage\", \"mynotebook.ipynb\"),\n",
468 470 " os.path.join(utils, \"inside_ipython.ipynb\")\n",
469 471 ")"
470 472 ]
471 473 },
472 474 {
473 475 "cell_type": "markdown",
474 476 "metadata": {},
475 477 "source": [
476 478 "and import the notebook from `IPython.utils`"
477 479 ]
478 480 },
479 481 {
480 482 "cell_type": "code",
481 483 "execution_count": null,
482 484 "metadata": {
483 485 "collapsed": false
484 486 },
485 487 "outputs": [],
486 488 "source": [
487 489 "from IPython.utils import inside_ipython\n",
488 490 "inside_ipython.whatsmyname()"
489 491 ]
490 492 },
491 493 {
492 494 "cell_type": "markdown",
493 495 "metadata": {},
494 496 "source": [
495 497 "This approach can even import functions and classes that are defined in a notebook using the `%%cython` magic."
496 498 ]
497 499 }
498 500 ],
499 501 "metadata": {
500 502 "gist_id": "6011986",
501 503 "kernelspec": {
502 504 "display_name": "Python 3",
503 505 "language": "python",
504 506 "name": "python3"
505 507 },
506 508 "language_info": {
507 509 "codemirror_mode": {
508 510 "name": "ipython",
509 511 "version": 3
510 512 },
511 513 "file_extension": ".py",
512 514 "mimetype": "text/x-python",
513 515 "name": "python",
514 516 "nbconvert_exporter": "python",
515 517 "pygments_lexer": "ipython3",
516 518 "version": "3.4.3"
517 519 }
518 520 },
519 521 "nbformat": 4,
520 522 "nbformat_minor": 0
521 523 }
General Comments 0
You need to be logged in to leave comments. Login now