Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Htmlnotebook #705

Merged
merged 232 commits into from
This page is out of date. Refresh to see the latest.
Showing with 22,171 additions and 268 deletions.
  1. +1 −0  .gitignore
  2. +265 −22 IPython/core/display.py
  3. +157 −4 IPython/core/displaypub.py
  4. +25 −2 IPython/core/formatters.py
  5. +70 −2 IPython/core/magic.py
  6. +10 −0 IPython/core/profileapp.py
  7. +16 −0 IPython/extensions/sympyprinting.py
  8. +82 −0 IPython/external/mathjax.py
  9. 0  IPython/frontend/html/__init__.py
  10. 0  IPython/frontend/html/notebook/__init__.py
  11. +334 −0 IPython/frontend/html/notebook/handlers.py
  12. +325 −0 IPython/frontend/html/notebook/kernelmanager.py
  13. +277 −0 IPython/frontend/html/notebook/notebookapp.py
  14. +219 −0 IPython/frontend/html/notebook/notebookmanager.py
  15. +19 −0 IPython/frontend/html/notebook/static/codemirror-2.12/LICENSE
  16. +6 −0 IPython/frontend/html/notebook/static/codemirror-2.12/README.md
  17. +17 −0 IPython/frontend/html/notebook/static/codemirror-2.12/lib/Untitled0.ipynb
  18. +67 −0 IPython/frontend/html/notebook/static/codemirror-2.12/lib/codemirror.css
  19. +2,144 −0 IPython/frontend/html/notebook/static/codemirror-2.12/lib/codemirror.js
  20. +51 −0 IPython/frontend/html/notebook/static/codemirror-2.12/lib/overlay.js
  21. +27 −0 IPython/frontend/html/notebook/static/codemirror-2.12/lib/runmode.js
  22. +124 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/css/css.js
  23. +56 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/css/index.html
  24. +79 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/htmlmixed/htmlmixed.js
  25. +52 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/htmlmixed/index.html
  26. +78 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/javascript/index.html
  27. +348 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/javascript/javascript.js
  28. +21 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/python/LICENSE.txt
  29. +123 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/python/index.html
  30. +321 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/python/python.js
  31. +526 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/rst/index.html
  32. +75 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/rst/rst.css
  33. +333 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/rst/rst.js
  34. +42 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/xml/index.html
  35. +231 −0 IPython/frontend/html/notebook/static/codemirror-2.12/mode/xml/xml.js
  36. +18 −0 IPython/frontend/html/notebook/static/codemirror-2.12/theme/default.css
  37. +9 −0 IPython/frontend/html/notebook/static/codemirror-2.12/theme/elegant.css
  38. +41 −0 IPython/frontend/html/notebook/static/codemirror-2.12/theme/ipython.css
  39. +8 −0 IPython/frontend/html/notebook/static/codemirror-2.12/theme/neat.css
  40. +20 −0 IPython/frontend/html/notebook/static/codemirror-2.12/theme/night.css
  41. +56 −0 IPython/frontend/html/notebook/static/css/base.css
  42. +73 −0 IPython/frontend/html/notebook/static/css/boilerplate.css
  43. +103 −0 IPython/frontend/html/notebook/static/css/layout.css
  44. +82 −0 IPython/frontend/html/notebook/static/css/nbbrowser.css
  45. +321 −0 IPython/frontend/html/notebook/static/css/notebook.css
  46. +59 −0 IPython/frontend/html/notebook/static/css/renderedhtml.css
  47. BIN  IPython/frontend/html/notebook/static/favicon.ico
  48. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_flat_0_2d5972_40x100.png
  49. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_flat_0_4f4f4f_40x100.png
  50. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_flat_0_aaaaaa_40x100.png
  51. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_flat_100_ffffff_40x100.png
  52. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_glass_55_fbf9ee_1x400.png
  53. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_highlight-hard_80_85b2cb_1x100.png
  54. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_highlight-hard_80_c4c4c4_1x100.png
  55. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_highlight-hard_80_e3e3e3_1x100.png
  56. BIN  ...hon/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_highlight-soft_100_c4c4c4_1x100.png
  57. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_highlight-soft_75_85b2cb_1x100.png
  58. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_inset-hard_65_85b2cb_1x100.png
  59. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_inset-hard_65_c4c4c4_1x100.png
  60. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-bg_inset-soft_95_fef1ec_1x100.png
  61. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_2d5972_256x240.png
  62. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_2e83ff_256x240.png
  63. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_38667f_256x240.png
  64. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_3a6983_256x240.png
  65. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_616161_256x240.png
  66. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_898989_256x240.png
  67. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_cd0a0a_256x240.png
  68. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/aristo/images/ui-icons_ffffff_256x240.png
  69. +2,295 −0 IPython/frontend/html/notebook/static/jquery/css/themes/aristo/jquery-wijmo.css
  70. BIN  ...hon/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_diagonals-thick_50_00a6dd_40x40.png
  71. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_flat_0_aaaaaa_40x100.png
  72. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_flat_100_f1f1f1_40x100.png
  73. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_242122_1x100.png
  74. BIN  ...ontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_242122_1x100_bottom.png
  75. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_333333_1x100.png
  76. BIN  ...n/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_333333_1x100_50.png
  77. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_65358a_1x100.png
  78. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_8A56B2_1x100.png
  79. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_9eca38_1x100.png
  80. BIN  ...n/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_9eca38_1x100_50.png
  81. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_15_ca3838_1x100.png
  82. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_highlight-soft_35_00a6dd_1x100.png
  83. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_inset-soft_100_e1e1e1_1x100.png
  84. BIN  ...n/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_inset-soft_15_242122_1x100 - Copy.png
  85. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-bg_inset-soft_15_242122_1x100.png
  86. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_00a6dd_256x240.png
  87. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_304915_256x240.png
  88. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_d399ff_256x240.png
  89. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_eaffb9_256x240.png
  90. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_f4f4f9_256x240.png
  91. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_fafafa_256x240.png
  92. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/rocket/images/ui-icons_ff8f8f_256x240.png
  93. +2,312 −0 IPython/frontend/html/notebook/static/jquery/css/themes/rocket/jquery-wijmo.css
  94. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png
  95. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_flat_75_ffffff_40x100.png
  96. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
  97. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
  98. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_glass_75_dadada_1x400.png
  99. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
  100. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
  101. BIN  .../frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
  102. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-icons_222222_256x240.png
  103. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-icons_2e83ff_256x240.png
  104. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-icons_454545_256x240.png
  105. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-icons_888888_256x240.png
  106. BIN  IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/images/ui-icons_cd0a0a_256x240.png
  107. +568 −0 IPython/frontend/html/notebook/static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css
  108. +18 −0 IPython/frontend/html/notebook/static/jquery/js/jquery-1.6.2.min.js
  109. +789 −0 IPython/frontend/html/notebook/static/jquery/js/jquery-ui-1.8.14.custom.min.js
  110. +42 −0 IPython/frontend/html/notebook/static/jquery/js/jquery.autogrow.js
  111. +93 −0 IPython/frontend/html/notebook/static/js/cell.js
  112. +443 −0 IPython/frontend/html/notebook/static/js/codecell.js
  113. +156 −0 IPython/frontend/html/notebook/static/js/kernel.js
  114. +60 −0 IPython/frontend/html/notebook/static/js/kernelstatus.js
  115. +61 −0 IPython/frontend/html/notebook/static/js/layout.js
  116. +101 −0 IPython/frontend/html/notebook/static/js/leftpanel.js
  117. +30 −0 IPython/frontend/html/notebook/static/js/namespace.js
  118. +39 −0 IPython/frontend/html/notebook/static/js/nbbrowser_main.js
  119. +930 −0 IPython/frontend/html/notebook/static/js/notebook.js
  120. +58 −0 IPython/frontend/html/notebook/static/js/notebook_main.js
  121. +242 −0 IPython/frontend/html/notebook/static/js/notebooklist.js
  122. +101 −0 IPython/frontend/html/notebook/static/js/pager.js
  123. +246 −0 IPython/frontend/html/notebook/static/js/panelsection.js
  124. +54 −0 IPython/frontend/html/notebook/static/js/printwidget.js
  125. +147 −0 IPython/frontend/html/notebook/static/js/savewidget.js
  126. +270 −0 IPython/frontend/html/notebook/static/js/textcell.js
  127. +101 −0 IPython/frontend/html/notebook/static/js/utils.js
  128. +32 −0 IPython/frontend/html/notebook/static/pagedown/LICENSE.txt
  129. +1,318 −0 IPython/frontend/html/notebook/static/pagedown/Markdown.Converter.js
  130. +202 −0 IPython/frontend/html/notebook/static/prettify/COPYING
  131. +48 −0 IPython/frontend/html/notebook/static/prettify/prettify.css
  132. +28 −0 IPython/frontend/html/notebook/static/prettify/prettify.js
  133. +87 −0 IPython/frontend/html/notebook/templates/focus.html
  134. +65 −0 IPython/frontend/html/notebook/templates/nbbrowser.html
  135. +244 −0 IPython/frontend/html/notebook/templates/notebook.html
  136. 0  IPython/frontend/html/notebook/tests/__init__.py
  137. BIN  IPython/frontend/html/notebook/tests/test_hist.sqlite
  138. +26 −0 IPython/frontend/html/notebook/tests/test_kernelsession.py
  139. +96 −0 IPython/frontend/html/notebook/zmqhttp.py
  140. +3 −0  IPython/frontend/terminal/ipapp.py
  141. +35 −0 IPython/lib/display.py
  142. 0  IPython/nbformat/__init__.py
  143. +230 −0 IPython/nbformat/current.py
  144. 0  IPython/nbformat/tests/__init__.py
  145. +24 −0 IPython/nbformat/v1/__init__.py
  146. +16 −0 IPython/nbformat/v1/convert.py
  147. +72 −0 IPython/nbformat/v1/nbbase.py
  148. +54 −0 IPython/nbformat/v1/nbjson.py
  149. +47 −0 IPython/nbformat/v1/rwbase.py
  150. 0  IPython/nbformat/v1/tests/__init__.py
  151. +29 −0 IPython/nbformat/v1/tests/nbexamples.py
  152. +14 −0 IPython/nbformat/v1/tests/test_json.py
  153. +41 −0 IPython/nbformat/v1/tests/test_nbbase.py
  154. +78 −0 IPython/nbformat/v2/__init__.py
  155. +50 −0 IPython/nbformat/v2/convert.py
  156. +179 −0 IPython/nbformat/v2/nbbase.py
  157. +62 −0 IPython/nbformat/v2/nbjson.py
  158. +147 −0 IPython/nbformat/v2/nbpy.py
  159. +188 −0 IPython/nbformat/v2/nbxml.py
  160. +74 −0 IPython/nbformat/v2/rwbase.py
  161. 0  IPython/nbformat/v2/tests/__init__.py
  162. +103 −0 IPython/nbformat/v2/tests/nbexamples.py
  163. +21 −0 IPython/nbformat/v2/tests/test_json.py
  164. +113 −0 IPython/nbformat/v2/tests/test_nbbase.py
  165. +17 −0 IPython/nbformat/v2/tests/test_nbpy.py
  166. +1 −1  IPython/testing/iptest.py
  167. +9 −5 IPython/zmq/displayhook.py
  168. +0 −1  IPython/zmq/ipkernel.py
  169. +0 −1  IPython/zmq/pylab/backend_inline.py
  170. +2 −2 IPython/zmq/zmqshell.py
  171. +65 −0 docs/examples/newparallel/helloworld.ipynb
  172. +21 −6 docs/examples/newparallel/helloworld.py
  173. +0 −144 docs/examples/newparallel/mcdriver.py
  174. 0  docs/examples/newparallel/{multiengine1.ipy → multiengine1.py}
  175. +0 −1  docs/examples/newparallel/{mcpricer.py → options/mckernel.py}
  176. +243 −0 docs/examples/newparallel/options/mcpricer.ipynb
  177. +173 −0 docs/examples/newparallel/options/mcpricer.py
  178. 0  docs/examples/newparallel/{ → pi}/parallelpi.py
  179. 0  docs/examples/newparallel/{ → pi}/pidigits.py
  180. +0 −56 docs/examples/newparallel/rmt/rmt.ipy
  181. +228 −0 docs/examples/newparallel/rmt/rmt.ipynb
  182. +146 −0 docs/examples/newparallel/rmt/rmt.py
  183. +17 −19 docs/examples/newparallel/rmt/rmtkernel.py
  184. +89 −0 docs/examples/newparallel/task1.ipynb
  185. +40 −1 docs/examples/newparallel/task1.py
  186. +71 −0 docs/examples/newparallel/taskmap.ipynb
  187. +18 −1 docs/examples/newparallel/taskmap.py
  188. +282 −0 docs/examples/notebooks/basic_quantum.ipynb
  189. +281 −0 docs/examples/notebooks/decompose.ipynb
  190. +105 −0 docs/examples/notebooks/dense_coding.ipynb
  191. +30 −0 docs/examples/notebooks/formatting.ipynb
  192. +140 −0 docs/examples/notebooks/grovers.ipynb
Sorry, we could not display the entire diff because it was too big.
View
1  .gitignore
@@ -4,6 +4,7 @@ _build
docs/man/*.gz
docs/source/api/generated
docs/gh-pages
+IPython/frontend/html/notebook/static/mathjax
*.py[co]
build
*.egg-info
View
287 IPython/core/display.py
@@ -17,6 +17,13 @@
# Imports
#-----------------------------------------------------------------------------
+from .displaypub import (
+ publish_pretty, publish_html,
+ publish_latex, publish_svg,
+ publish_png, publish_json,
+ publish_javascript, publish_jpeg
+)
+
#-----------------------------------------------------------------------------
# Main functions
#-----------------------------------------------------------------------------
@@ -53,78 +60,314 @@ def display(*objs, **kwargs):
publish('IPython.core.display.display', format_dict)
-def display_pretty(*objs):
+def display_pretty(*objs, **kwargs):
"""Display the pretty (default) representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
+ The Python objects to display, or if raw=True raw text data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
"""
- display(*objs, include=['text/plain'])
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_pretty(obj)
+ else:
+ display(*objs, include=['text/plain'])
-def display_html(*objs):
+def display_html(*objs, **kwargs):
"""Display the HTML representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
- """
- display(*objs, include=['text/plain','text/html'])
+ The Python objects to display, or if raw=True raw HTML data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
+ """
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_html(obj)
+ else:
+ display(*objs, include=['text/plain','text/html'])
-def display_svg(*objs):
+def display_svg(*objs, **kwargs):
"""Display the SVG representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
+ The Python objects to display, or if raw=True raw svg data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
"""
- display(*objs, include=['text/plain','image/svg+xml'])
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_svg(obj)
+ else:
+ display(*objs, include=['text/plain','image/svg+xml'])
-def display_png(*objs):
+def display_png(*objs, **kwargs):
"""Display the PNG representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
+ The Python objects to display, or if raw=True raw png data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
"""
- display(*objs, include=['text/plain','image/png'])
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_png(obj)
+ else:
+ display(*objs, include=['text/plain','image/png'])
+
+def display_jpeg(*objs, **kwargs):
+ """Display the JPEG representation of an object.
-def display_latex(*objs):
+ Parameters
+ ----------
+ objs : tuple of objects
+ The Python objects to display, or if raw=True raw JPEG data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
+ """
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_jpeg(obj)
+ else:
+ display(*objs, include=['text/plain','image/jpeg'])
+
+
+def display_latex(*objs, **kwargs):
"""Display the LaTeX representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
+ The Python objects to display, or if raw=True raw latex data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
"""
- display(*objs, include=['text/plain','text/latex'])
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_latex(obj)
+ else:
+ display(*objs, include=['text/plain','text/latex'])
-def display_json(*objs):
+def display_json(*objs, **kwargs):
"""Display the JSON representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
+ The Python objects to display, or if raw=True raw json data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
"""
- display(*objs, include=['text/plain','application/json'])
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_json(obj)
+ else:
+ display(*objs, include=['text/plain','application/json'])
-def display_javascript(*objs):
+def display_javascript(*objs, **kwargs):
"""Display the Javascript representation of an object.
Parameters
----------
objs : tuple of objects
- The Python objects to display.
+ The Python objects to display, or if raw=True raw javascript data to
+ display.
+ raw : bool
+ Are the data objects raw data or Python objects that need to be
+ formatted before display? [default: False]
"""
- display(*objs, include=['text/plain','application/javascript'])
+ raw = kwargs.pop('raw',False)
+ if raw:
+ for obj in objs:
+ publish_javascript(obj)
+ else:
+ display(*objs, include=['text/plain','application/javascript'])
+
+#-----------------------------------------------------------------------------
+# Smart classes
+#-----------------------------------------------------------------------------
+
+
+class DisplayObject(object):
+ """An object that wraps data to be displayed."""
+
+ _read_flags = 'r'
+
+ def __init__(self, data=None, url=None, filename=None):
+ """Create a display object given raw data.
+
+ When this object is returned by an expression or passed to the
+ display function, it will result in the data being displayed
+ in the frontend. The MIME type of the data should match the
+ subclasses used, so the Png subclass should be used for 'image/png'
+ data. If the data is a URL, the data will first be downloaded
+ and then displayed. If
+
+ Parameters
+ ----------
+ data : unicode, str or bytes
+ The raw data or a URL to download the data from.
+ url : unicode
+ A URL to download the data from.
+ filename : unicode
+ Path to a local file to load the data from.
+ """
+ if data is not None and data.startswith('http'):
+ self.url = data
+ self.filename = None
+ self.data = None
+ else:
+ self.data = data
+ self.url = url
+ self.filename = None if filename is None else unicode(filename)
+ self.reload()
+
+ def reload(self):
+ """Reload the raw data from file or URL."""
+ if self.filename is not None:
+ with open(self.filename, self._read_flags) as f:
+ self.data = f.read()
+ elif self.url is not None:
+ try:
+ import urllib2
+ response = urllib2.urlopen(self.url)
+ self.data = response.read()
+ except:
+ self.data = None
+
+class Pretty(DisplayObject):
+
+ def _repr_pretty_(self):
+ return self.data
+
+
+class HTML(DisplayObject):
+
+ def _repr_html_(self):
+ return self.data
+
+
+class Math(DisplayObject):
+
+ def _repr_latex_(self):
+ return self.data
+
+
+class SVG(DisplayObject):
+
+ def _repr_svg_(self):
+ return self.data
+
+
+class JSON(DisplayObject):
+
+ def _repr_json_(self):
+ return self.data
+
+
+class Javascript(DisplayObject):
+
+ def _repr_javascript_(self):
+ return self.data
+
+
+class Image(DisplayObject):
+
+ _read_flags = 'rb'
+
+ def __init__(self, data=None, url=None, filename=None, format=u'png', embed=False):
+ """Create a display an PNG/JPEG image given raw data.
+
+ When this object is returned by an expression or passed to the
+ display function, it will result in the image being displayed
+ in the frontend.
+
+ Parameters
+ ----------
+ data : unicode, str or bytes
+ The raw data or a URL to download the data from.
+ url : unicode
+ A URL to download the data from.
+ filename : unicode
+ Path to a local file to load the data from.
+ format : unicode
+ The format of the image data (png/jpeg/jpg). If a filename or URL is given
+ for format will be inferred from the filename extension.
+ embed : bool
+ Should the image data be embedded in the notebook using a data URI (True)
+ or be loaded using an <img> tag. Set this to True if you want the image
+ to be viewable later with no internet connection. If a filename is given
+ embed is always set to True.
+ """
+ if filename is not None:
+ ext = self._find_ext(filename)
+ elif url is not None:
+ ext = self._find_ext(url)
+ elif data.startswith('http'):
+ ext = self._find_ext(data)
+ else:
+ ext = None
+ if ext is not None:
+ if ext == u'jpg' or ext == u'jpeg':
+ format = u'jpeg'
+ if ext == u'png':
+ format = u'png'
+ self.format = unicode(format).lower()
+ self.embed = True if filename is not None else embed
+ super(Image, self).__init__(data=data, url=url, filename=filename)
+
+ def reload(self):
+ """Reload the raw data from file or URL."""
+ if self.embed:
+ super(Image,self).reload()
+
+ def _repr_html_(self):
+ if not self.embed:
+ return u'<img src="%s" />' % self.url
+
+ def _repr_png_(self):
+ if self.embed and self.format == u'png':
+ return self.data
+
+ def _repr_jpeg_(self):
+ if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
+ return self.data
+
+ def _find_ext(self, s):
+ return unicode(s.split('.')[-1].lower())
View
161 IPython/core/displaypub.py
@@ -56,7 +56,7 @@ def _validate_data(self, source, data, metadata=None):
Any metadata for the data.
"""
- if not isinstance(source, str):
+ if not isinstance(source, basestring):
raise TypeError('source must be a str, got: %r' % source)
if not isinstance(data, dict):
raise TypeError('data must be a dict, got: %r' % data)
@@ -76,8 +76,10 @@ def publish(self, source, data, metadata=None):
* text/html
* text/latex
* application/json
+ * application/javascript
* image/png
- * immage/svg+xml
+ * image/jpeg
+ * image/svg+xml
Parameters
----------
@@ -103,7 +105,7 @@ def publish(self, source, data, metadata=None):
print(data['text/plain'], file=io.stdout)
-def publish_display_data(self, source, data, metadata=None):
+def publish_display_data(source, data, metadata=None):
"""Publish data and metadata to all frontends.
See the ``display_data`` message in the messaging documentation for
@@ -115,8 +117,10 @@ def publish_display_data(self, source, data, metadata=None):
* text/html
* text/latex
* application/json
+ * application/javascript
* image/png
- * immage/svg+xml
+ * image/jpeg
+ * image/svg+xml
Parameters
----------
@@ -143,3 +147,152 @@ def publish_display_data(self, source, data, metadata=None):
metadata
)
+
+def publish_pretty(data, metadata=None):
+ """Publish raw text data to all frontends.
+
+ Parameters
+ ----------
+ data : unicode
+ The raw text data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_pretty',
+ {'text/plain':data},
+ metadata=metadata
+ )
+
+
+def publish_html(data, metadata=None):
+ """Publish raw HTML data to all frontends.
+
+ Parameters
+ ----------
+ data : unicode
+ The raw HTML data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_html',
+ {'text/html':data},
+ metadata=metadata
+ )
+
+
+def publish_latex(data, metadata=None):
+ """Publish raw LaTeX data to all frontends.
+
+ Parameters
+ ----------
+ data : unicode
+ The raw LaTeX data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_latex',
+ {'text/latex':data},
+ metadata=metadata
+ )
+
+def publish_png(data, metadata=None):
+ """Publish raw binary PNG data to all frontends.
+
+ Parameters
+ ----------
+ data : str/bytes
+ The raw binary PNG data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_png',
+ {'image/png':data},
+ metadata=metadata
+ )
+
+
+def publish_jpeg(data, metadata=None):
+ """Publish raw binary JPEG data to all frontends.
+
+ Parameters
+ ----------
+ data : str/bytes
+ The raw binary JPEG data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_jpeg',
+ {'image/jpeg':data},
+ metadata=metadata
+ )
+
+
+def publish_svg(data, metadata=None):
+ """Publish raw SVG data to all frontends.
+
+ Parameters
+ ----------
+ data : unicode
+ The raw SVG data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_svg',
+ {'image/svg+xml':data},
+ metadata=metadata
+ )
+
+def publish_json(data, metadata=None):
+ """Publish raw JSON data to all frontends.
+
+ Parameters
+ ----------
+ data : unicode
+ The raw JSON data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_json',
+ {'application/json':data},
+ metadata=metadata
+ )
+
+def publish_javascript(data, metadata=None):
+ """Publish raw Javascript data to all frontends.
+
+ Parameters
+ ----------
+ data : unicode
+ The raw Javascript data to publish.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ publish_display_data(
+ u'IPython.core.displaypub.publish_javascript',
+ {'application/javascript':data},
+ metadata=metadata
+ )
+
View
27 IPython/core/formatters.py
@@ -51,6 +51,7 @@ def _formatters_default(self):
HTMLFormatter,
SVGFormatter,
PNGFormatter,
+ JPEGFormatter,
LatexFormatter,
JSONFormatter,
JavascriptFormatter
@@ -72,8 +73,10 @@ def format(self, obj, include=None, exclude=None):
* text/html
* text/latex
* application/json
+ * application/javascript
* image/png
- * immage/svg+xml
+ * image/jpeg
+ * image/svg+xml
Parameters
----------
@@ -495,6 +498,22 @@ class PNGFormatter(BaseFormatter):
print_method = ObjectName('_repr_png_')
+class JPEGFormatter(BaseFormatter):
+ """A JPEG formatter.
+
+ To define the callables that compute the JPEG representation of your
+ objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
+ or :meth:`for_type_by_name` methods to register functions that handle
+ this.
+
+ The return value of this formatter should be raw JPEG data, *not*
+ base64 encoded.
+ """
+ format_type = Unicode('image/jpeg')
+
+ print_method = ObjectName('_repr_jpeg_')
+
+
class LatexFormatter(BaseFormatter):
"""A LaTeX formatter.
@@ -546,6 +565,7 @@ class JavascriptFormatter(BaseFormatter):
FormatterABC.register(HTMLFormatter)
FormatterABC.register(SVGFormatter)
FormatterABC.register(PNGFormatter)
+FormatterABC.register(JPEGFormatter)
FormatterABC.register(LatexFormatter)
FormatterABC.register(JSONFormatter)
FormatterABC.register(JavascriptFormatter)
@@ -562,8 +582,10 @@ def format_display_data(obj, include=None, exclude=None):
* text/html
* text/latex
* application/json
+ * application/javascript
* image/png
- * immage/svg+xml
+ * image/jpeg
+ * image/svg+xml
Parameters
----------
@@ -594,3 +616,4 @@ def format_display_data(obj, include=None, exclude=None):
include,
exclude
)
+
View
72 IPython/core/magic.py
@@ -48,7 +48,7 @@
from IPython.core.fakemodule import FakeModule
from IPython.core.profiledir import ProfileDir
from IPython.core.macro import Macro
-from IPython.core import page
+from IPython.core import magic_arguments, page
from IPython.core.prefilter import ESC_MAGIC
from IPython.lib.pylabtools import mpl_runner
from IPython.testing.skipdoctest import skip_doctest
@@ -2118,7 +2118,8 @@ def magic_loadpy(self, arg_s):
response = urllib2.urlopen(arg_s)
content = response.read()
else:
- content = open(arg_s).read()
+ with open(arg_s) as f:
+ content = f.read()
self.set_next_input(content)
def _find_edit_target(self, args, opts, last_call):
@@ -3495,4 +3496,71 @@ def magic_precision(self, s=''):
ptformatter.float_precision = s
return ptformatter.float_format
+
+ @magic_arguments.magic_arguments()
+ @magic_arguments.argument(
+ '-e', '--export', action='store_true', default=False,
+ help='Export IPython history as a notebook. The filename argument '
+ 'is used to specify the notebook name and format. For example '
+ 'a filename of notebook.ipynb will result in a notebook name '
+ 'of "notebook" and a format of "xml". Likewise using a ".json" '
+ 'or ".py" file extension will write the notebook in the json '
+ 'or py formats.'
+ )
+ @magic_arguments.argument(
+ '-f', '--format',
+ help='Convert an existing IPython notebook to a new format. This option '
+ 'specifies the new format and can have the values: xml, json, py. '
+ 'The target filename is choosen automatically based on the new '
+ 'format. The filename argument gives the name of the source file.'
+ )
+ @magic_arguments.argument(
+ 'filename', type=unicode,
+ help='Notebook name or filename'
+ )
+ def magic_notebook(self, s):
+ """Export and convert IPython notebooks.
+
+ This function can export the current IPython history to a notebook file
+ or can convert an existing notebook file into a different format. For
+ example, to export the history to "foo.ipynb" do "%notebook -e foo.ipynb".
+ To export the history to "foo.py" do "%notebook -e foo.py". To convert
+ "foo.ipynb" to "foo.json" do "%notebook -f json foo.ipynb". Possible
+ formats include (json/ipynb, py).
+ """
+ args = magic_arguments.parse_argstring(self.magic_notebook, s)
+
+ from IPython.nbformat import current
+ if args.export:
+ fname, name, format = current.parse_filename(args.filename)
+ cells = []
+ hist = list(self.history_manager.get_range())
+ for session, prompt_number, input in hist[:-1]:
+ cells.append(current.new_code_cell(prompt_number=prompt_number, input=input))
+ worksheet = current.new_worksheet(cells=cells)
+ nb = current.new_notebook(name=name,worksheets=[worksheet])
+ with open(fname, 'w') as f:
+ current.write(nb, f, format);
+ elif args.format is not None:
+ old_fname, old_name, old_format = current.parse_filename(args.filename)
+ new_format = args.format
+ if new_format == u'xml':
+ raise ValueError('Notebooks cannot be written as xml.')
+ elif new_format == u'ipynb' or new_format == u'json':
+ new_fname = old_name + u'.ipynb'
+ new_format = u'json'
+ elif new_format == u'py':
+ new_fname = old_name + u'.py'
+ else:
+ raise ValueError('Invalid notebook format: %s' % new_format)
+ with open(old_fname, 'r') as f:
+ s = f.read()
+ try:
+ nb = current.reads(s, old_format)
+ except:
+ nb = current.reads(s, u'xml')
+ with open(new_fname, 'w') as f:
+ current.write(nb, f, new_format)
+
+
# end Magic
View
10 IPython/core/profileapp.py
@@ -193,6 +193,16 @@ def init_config_files(self):
pass
else:
apps.append(IPythonQtConsoleApp)
+ try:
+ from IPython.frontend.html.notebook.notebookapp import IPythonNotebookApp
+ except ImportError:
+ pass
+ except Exception:
+ self.log.debug('Unexpected error when importing IPythonNotebookApp',
+ exc_info=True
+ )
+ else:
+ apps.append(IPythonNotebookApp)
if self.parallel:
from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
from IPython.parallel.apps.ipengineapp import IPEngineApp
View
16 IPython/extensions/sympyprinting.py
@@ -48,6 +48,14 @@ def print_png(o):
png = latex_to_png(s)
return png
+
+def print_latex(o):
+ """A function to generate the latex representation of sympy expressions."""
+ s = latex(o, mode='equation', itex=True)
+ s = s.replace('\\dag','\\dagger')
+ return s
@fperez Owner
fperez added a note

To avoid extra unnecessary assignments, the whole function can be written as:

return latex(o, mode='equation', itex=True).replace('\\dag','\\dagger')

I know one-liners can get unreadable, but in this case it doesn't seem that bad to me and it saves some time and memory (and I actually find the short form very readable). Not a big deal though, I'll leave the final decision up to you.

@ellisonbg Owner

I am going to leave this as it is extremely likely we will discover additional transforms that need to happen on the latex.

@fperez Owner
fperez added a note

no prob.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+
_loaded = False
def load_ipython_extension(ip):
@@ -71,5 +79,13 @@ def load_ipython_extension(ip):
png_formatter.for_type_by_name(
'sympy.core.basic', 'Basic', print_png
)
+
+ latex_formatter = ip.display_formatter.formatters['text/latex']
+ latex_formatter.for_type_by_name(
+ 'sympy.core.basic', 'Basic', print_latex
+ )
+ latex_formatter.for_type_by_name(
+ 'sympy.matrices.matrices', 'Matrix', print_latex
+ )
_loaded = True
View
82 IPython/external/mathjax.py
@@ -0,0 +1,82 @@
+"""Utility function for installing MathJax javascript library into
+the notebook's 'static' directory, for offline use.
+
+Authors:
+
+* Min RK
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import os
+import shutil
+import urllib2
+import tempfile
+import tarfile
+
+from IPython.frontend.html import notebook as nbmod
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+def install_mathjax(tag='v1.1', replace=False):
+ """Download and install MathJax for offline use.
+
+ This will install mathjax to the 'static' dir in the IPython notebook
+ package, so it will fail if the caller does not have write access
+ to that location.
+
+ MathJax is a ~15MB download, and ~150MB installed.
+
+ Parameters
+ ----------
+
+ replace : bool [False]
+ Whether to remove and replace an existing install.
+ tag : str ['v1.1']
+ Which tag to download. Default is 'v1.1', the current stable release,
+ but alternatives include 'v1.1a' and 'master'.
+ """
+ mathjax_url = "https://github.com/mathjax/MathJax/tarball/%s"%tag
+
+ nbdir = os.path.dirname(os.path.abspath(nbmod.__file__))
+ static = os.path.join(nbdir, 'static')
+ dest = os.path.join(static, 'mathjax')
+
+ # check for existence and permissions
+ if not os.access(static, os.W_OK):
+ raise IOError("Need have write access to %s"%static)
+ if os.path.exists(dest):
+ if replace:
+ if not os.access(dest, os.W_OK):
+ raise IOError("Need have write access to %s"%dest)
+ print "removing previous MathJax install"
+ shutil.rmtree(dest)
+ else:
+ print "offline MathJax apparently already installed"
+ return
+
+ # download mathjax
+ print "Downloading mathjax source..."
+ response = urllib2.urlopen(mathjax_url)
+ print "done"
+ # use 'r|gz' stream mode, because socket file-like objects can't seek:
+ tar = tarfile.open(fileobj=response.fp, mode='r|gz')
+ topdir = tar.firstmember.path
+ print "Extracting to %s"%dest
+ tar.extractall(static)
+ # it will be mathjax-MathJax-<sha>, rename to just mathjax
+ os.rename(os.path.join(static, topdir), dest)
+
+
+__all__ = ['install_mathjax']
View
0  IPython/frontend/html/__init__.py
No changes.
View
0  IPython/frontend/html/notebook/__init__.py
No changes.
View
334 IPython/frontend/html/notebook/handlers.py
@@ -0,0 +1,334 @@
+"""Tornado handlers for the notebook.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from tornado import web
+from tornado import websocket
+
+from zmq.eventloop import ioloop
+from zmq.utils import jsonapi
+
+from IPython.zmq.session import Session
+
+try:
+ from docutils.core import publish_string
+except ImportError:
+ publish_string = None
+
+
+
+#-----------------------------------------------------------------------------
+# Top-level handlers
+#-----------------------------------------------------------------------------
+
+
+class NBBrowserHandler(web.RequestHandler):
+ def get(self):
+ nbm = self.application.notebook_manager
+ project = nbm.notebook_dir
+ self.render('nbbrowser.html', project=project)
+
+
+class NewHandler(web.RequestHandler):
+ def get(self):
+ notebook_id = self.application.notebook_manager.new_notebook()
+ self.render('notebook.html', notebook_id=notebook_id)
+
+
+class NamedNotebookHandler(web.RequestHandler):
+ def get(self, notebook_id):
+ nbm = self.application.notebook_manager
+ if not nbm.notebook_exists(notebook_id):
+ raise web.HTTPError(404)
+ self.render('notebook.html', notebook_id=notebook_id)
+
+
+#-----------------------------------------------------------------------------
+# Kernel handlers
+#-----------------------------------------------------------------------------
+
+
+class MainKernelHandler(web.RequestHandler):
+
+ def get(self):
+ km = self.application.kernel_manager
+ self.finish(jsonapi.dumps(km.kernel_ids))
+
+ def post(self):
+ km = self.application.kernel_manager
+ notebook_id = self.get_argument('notebook', default=None)
+ kernel_id = km.start_kernel(notebook_id)
+ ws_url = self.application.ipython_app.get_ws_url()
+ data = {'ws_url':ws_url,'kernel_id':kernel_id}
+ self.set_header('Location', '/'+kernel_id)
+ self.finish(jsonapi.dumps(data))
+
+
+class KernelHandler(web.RequestHandler):
+
+ SUPPORTED_METHODS = ('DELETE')
+
+ def delete(self, kernel_id):
+ km = self.application.kernel_manager
+ km.kill_kernel(kernel_id)
+ self.set_status(204)
+ self.finish()
+
+
+class KernelActionHandler(web.RequestHandler):
+
+ def post(self, kernel_id, action):
+ km = self.application.kernel_manager
+ if action == 'interrupt':
+ km.interrupt_kernel(kernel_id)
+ self.set_status(204)
+ if action == 'restart':
+ new_kernel_id = km.restart_kernel(kernel_id)
+ ws_url = self.application.ipython_app.get_ws_url()
+ data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
+ self.set_header('Location', '/'+new_kernel_id)
+ self.write(jsonapi.dumps(data))
+ self.finish()
+
+
+class ZMQStreamHandler(websocket.WebSocketHandler):
+
+ def _reserialize_reply(self, msg_list):
+ """Reserialize a reply message using JSON.
+
+ This takes the msg list from the ZMQ socket, unserializes it using
+ self.session and then serializes the result using JSON. This method
+ should be used by self._on_zmq_reply to build messages that can
+ be sent back to the browser.
+ """
+ idents, msg_list = self.session.feed_identities(msg_list)
+ msg = self.session.unserialize(msg_list)
+ try:
+ msg['header'].pop('date')
+ except KeyError:
+ pass
+ try:
+ msg['parent_header'].pop('date')
+ except KeyError:
+ pass
+ msg.pop('buffers')
+ return jsonapi.dumps(msg)
+
+ def _on_zmq_reply(self, msg_list):
+ try:
+ msg = self._reserialize_reply(msg_list)
+ except:
+ self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
+ else:
+ self.write_message(msg)
+
+
+class IOPubHandler(ZMQStreamHandler):
+
+ def initialize(self, *args, **kwargs):
+ self._kernel_alive = True
+ self._beating = False
+ self.iopub_stream = None
+ self.hb_stream = None
+
+ def open(self, kernel_id):
+ km = self.application.kernel_manager
+ self.kernel_id = kernel_id
+ self.session = Session()
+ self.time_to_dead = km.time_to_dead
+ try:
+ self.iopub_stream = km.create_iopub_stream(kernel_id)
+ self.hb_stream = km.create_hb_stream(kernel_id)
+ except web.HTTPError:
+ # WebSockets don't response to traditional error codes so we
+ # close the connection.
+ if not self.stream.closed():
+ self.stream.close()
+ else:
+ self.iopub_stream.on_recv(self._on_zmq_reply)
+ self.start_hb(self.kernel_died)
+
+ def on_close(self):
+ # This method can be called twice, once by self.kernel_died and once
+ # from the WebSocket close event. If the WebSocket connection is
+ # closed before the ZMQ streams are setup, they could be None.
+ self.stop_hb()
+ if self.iopub_stream is not None and not self.iopub_stream.closed():
+ self.iopub_stream.on_recv(None)
+ self.iopub_stream.close()
+ if self.hb_stream is not None and not self.hb_stream.closed():
+ self.hb_stream.close()
+
+ def start_hb(self, callback):
+ """Start the heartbeating and call the callback if the kernel dies."""
+ if not self._beating:
+ self._kernel_alive = True
+
+ def ping_or_dead():
+ if self._kernel_alive:
+ self._kernel_alive = False
+ self.hb_stream.send(b'ping')
+ else:
+ try:
+ callback()
+ except:
+ pass
+ finally:
+ self._hb_periodic_callback.stop()
+
+ def beat_received(msg):
+ self._kernel_alive = True
+
+ self.hb_stream.on_recv(beat_received)
+ self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
+ self._hb_periodic_callback.start()
+ self._beating= True
+
+ def stop_hb(self):
+ """Stop the heartbeating and cancel all related callbacks."""
+ if self._beating:
+ self._hb_periodic_callback.stop()
+ if not self.hb_stream.closed():
+ self.hb_stream.on_recv(None)
+
+ def kernel_died(self):
+ self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
+ self.write_message(
+ {'header': {'msg_type': 'status'},
+ 'parent_header': {},
+ 'content': {'execution_state':'dead'}
+ }
+ )
+ self.on_close()
+
+
+class ShellHandler(ZMQStreamHandler):
+
+ def initialize(self, *args, **kwargs):
+ self.shell_stream = None
+
+ def open(self, kernel_id):
+ km = self.application.kernel_manager
+ self.max_msg_size = km.max_msg_size
+ self.kernel_id = kernel_id
+ try:
+ self.shell_stream = km.create_shell_stream(kernel_id)
+ except web.HTTPError:
+ # WebSockets don't response to traditional error codes so we
+ # close the connection.
+ if not self.stream.closed():
+ self.stream.close()
+ else:
+ self.session = Session()
+ self.shell_stream.on_recv(self._on_zmq_reply)
+
+ def on_message(self, msg):
+ if len(msg) < self.max_msg_size:
+ msg = jsonapi.loads(msg)
+ self.session.send(self.shell_stream, msg)
+
+ def on_close(self):
+ # Make sure the stream exists and is not already closed.
+ if self.shell_stream is not None and not self.shell_stream.closed():
+ self.shell_stream.close()
+
+
+#-----------------------------------------------------------------------------
+# Notebook web service handlers
+#-----------------------------------------------------------------------------
+
+class NotebookRootHandler(web.RequestHandler):
+
+ def get(self):
+ nbm = self.application.notebook_manager
+ files = nbm.list_notebooks()
+ self.finish(jsonapi.dumps(files))
+
+ def post(self):
+ nbm = self.application.notebook_manager
+ body = self.request.body.strip()
+ format = self.get_argument('format', default='json')
+ name = self.get_argument('name', default=None)
+ if body:
+ notebook_id = nbm.save_new_notebook(body, name=name, format=format)
+ else:
+ notebook_id = nbm.new_notebook()
+ self.set_header('Location', '/'+notebook_id)
+ self.finish(jsonapi.dumps(notebook_id))
+
+
+class NotebookHandler(web.RequestHandler):
+
+ SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
+
+ def get(self, notebook_id):
+ nbm = self.application.notebook_manager
+ format = self.get_argument('format', default='json')
+ last_mod, name, data = nbm.get_notebook(notebook_id, format)
+ if format == u'json':
+ self.set_header('Content-Type', 'application/json')
+ self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
+ elif format == u'py':
+ self.set_header('Content-Type', 'application/x-python')
+ self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
+ self.set_header('Last-Modified', last_mod)
+ self.finish(data)
+
+ def put(self, notebook_id):
+ nbm = self.application.notebook_manager
+ format = self.get_argument('format', default='json')
+ name = self.get_argument('name', default=None)
+ nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
+ self.set_status(204)
+ self.finish()
+
+ def delete(self, notebook_id):
+ nbm = self.application.notebook_manager
+ nbm.delete_notebook(notebook_id)
+ self.set_status(204)
+ self.finish()
+
+#-----------------------------------------------------------------------------
+# RST web service handlers
+#-----------------------------------------------------------------------------
+
+
+class RSTHandler(web.RequestHandler):
+
+ def post(self):
+ if publish_string is None:
+ raise web.HTTPError(503)
+ body = self.request.body.strip()
+ source = body
+ # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
+ defaults = {'file_insertion_enabled': 0,
+ 'raw_enabled': 0,
+ '_disable_config': 1,
+ 'stylesheet_path': 0
+ # 'template': template_path
+ }
+ try:
+ html = publish_string(source, writer_name='html',
+ settings_overrides=defaults
+ )
+ except:
+ raise web.HTTPError(400)
+ print html
+ self.set_header('Content-Type', 'text/html')
+ self.finish(html)
+
+
View
325 IPython/frontend/html/notebook/kernelmanager.py
@@ -0,0 +1,325 @@
+"""A kernel manager for multiple kernels.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import signal
+import sys
+import uuid
+
+import zmq
+from zmq.eventloop.zmqstream import ZMQStream
+
+from tornado import web
+
+from IPython.config.configurable import LoggingConfigurable
+from IPython.zmq.ipkernel import launch_kernel
+from IPython.utils.traitlets import Instance, Dict, List, Unicode, Float, Int
+
+#-----------------------------------------------------------------------------
+# Classes
+#-----------------------------------------------------------------------------
+
+class DuplicateKernelError(Exception):
+ pass
+
+
+class KernelManager(LoggingConfigurable):
+ """A class for managing multiple kernels."""
+
+ context = Instance('zmq.Context')
+ def _context_default(self):
+ return zmq.Context.instance()
+
+ _kernels = Dict()
+
+ @property
+ def kernel_ids(self):
+ """Return a list of the kernel ids of the active kernels."""
+ return self._kernels.keys()
+
+ def __len__(self):
+ """Return the number of running kernels."""
+ return len(self.kernel_ids)
+
+ def __contains__(self, kernel_id):
+ if kernel_id in self.kernel_ids:
+ return True
+ else:
+ return False
+
+ def start_kernel(self, **kwargs):
+ """Start a new kernel."""
+ kernel_id = unicode(uuid.uuid4())
+ (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
+ # Store the information for contacting the kernel. This assumes the kernel is
+ # running on localhost.
+ d = dict(
+ process = process,
+ stdin_port = stdin_port,
+ iopub_port = iopub_port,
+ shell_port = shell_port,
+ hb_port = hb_port,
+ ip = '127.0.0.1'
+ )
+ self._kernels[kernel_id] = d
+ return kernel_id
+
+ def kill_kernel(self, kernel_id):
+ """Kill a kernel by its kernel uuid.
+
+ Parameters
+ ==========
+ kernel_id : uuid
+ The id of the kernel to kill.
+ """
+ kernel_process = self.get_kernel_process(kernel_id)
+ if kernel_process is not None:
+ # Attempt to kill the kernel.
+ try:
+ kernel_process.kill()
+ except OSError, e:
+ # In Windows, we will get an Access Denied error if the process
+ # has already terminated. Ignore it.
+ if not (sys.platform == 'win32' and e.winerror == 5):
+ raise
+ del self._kernels[kernel_id]
+
+ def interrupt_kernel(self, kernel_id):
+ """Interrupt (SIGINT) the kernel by its uuid.
+
+ Parameters
+ ==========
+ kernel_id : uuid
+ The id of the kernel to interrupt.
+ """
+ kernel_process = self.get_kernel_process(kernel_id)
+ if kernel_process is not None:
+ if sys.platform == 'win32':
+ from parentpoller import ParentPollerWindows as Poller
+ Poller.send_interrupt(kernel_process.win32_interrupt_event)
+ else:
+ kernel_process.send_signal(signal.SIGINT)
+
+
+ def signal_kernel(self, kernel_id, signum):
+ """ Sends a signal to the kernel by its uuid.
+
+ Note that since only SIGTERM is supported on Windows, this function
+ is only useful on Unix systems.
+
+ Parameters
+ ==========
+ kernel_id : uuid
+ The id of the kernel to signal.
+ """
+ kernel_process = self.get_kernel_process(kernel_id)
+ if kernel_process is not None:
+ kernel_process.send_signal(signum)
+
+ def get_kernel_process(self, kernel_id):
+ """Get the process object for a kernel by its uuid.
+
+ Parameters
+ ==========
+ kernel_id : uuid
+ The id of the kernel.
+ """
+ d = self._kernels.get(kernel_id)
+ if d is not None:
+ return d['process']
+ else:
+ raise KeyError("Kernel with id not found: %s" % kernel_id)
+
+ def get_kernel_ports(self, kernel_id):
+ """Return a dictionary of ports for a kernel.
+
+ Parameters
+ ==========
+ kernel_id : uuid
+ The id of the kernel.
+
+ Returns
+ =======
+ port_dict : dict
+ A dict of key, value pairs where the keys are the names
+ (stdin_port,iopub_port,shell_port) and the values are the
+ integer port numbers for those channels.
+ """
+ d = self._kernels.get(kernel_id)
+ if d is not None:
+ dcopy = d.copy()
+ dcopy.pop('process')
+ dcopy.pop('ip')
+ return dcopy
+ else:
+ raise KeyError("Kernel with id not found: %s" % kernel_id)
+
+ def get_kernel_ip(self, kernel_id):
+ """Return ip address for a kernel.
+
+ Parameters
+ ==========
+ kernel_id : uuid
+ The id of the kernel.
+
+ Returns
+ =======
+ ip : str
+ The ip address of the kernel.
+ """
+ d = self._kernels.get(kernel_id)
+ if d is not None:
+ return d['ip']
+ else:
+ raise KeyError("Kernel with id not found: %s" % kernel_id)
+
+ def create_connected_stream(self, ip, port, socket_type):
+ sock = self.context.socket(socket_type)
+ addr = "tcp://%s:%i" % (ip, port)
+ self.log.info("Connecting to: %s" % addr)
+ sock.connect(addr)
+ return ZMQStream(sock)
+
+ def create_iopub_stream(self, kernel_id):
+ ip = self.get_kernel_ip(kernel_id)
+ ports = self.get_kernel_ports(kernel_id)
+ iopub_stream = self.create_connected_stream(ip, ports['iopub_port'], zmq.SUB)
+ iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
+ return iopub_stream
+
+ def create_shell_stream(self, kernel_id):
+ ip = self.get_kernel_ip(kernel_id)
+ ports = self.get_kernel_ports(kernel_id)
+ shell_stream = self.create_connected_stream(ip, ports['shell_port'], zmq.XREQ)
+ return shell_stream
+
+ def create_hb_stream(self, kernel_id):
+ ip = self.get_kernel_ip(kernel_id)
+ ports = self.get_kernel_ports(kernel_id)
+ hb_stream = self.create_connected_stream(ip, ports['hb_port'], zmq.REQ)
+ return hb_stream
+
+
+class MappingKernelManager(KernelManager):
+ """A KernelManager that handles notebok mapping and HTTP error handling"""
+
+ kernel_argv = List(Unicode)
+ kernel_manager = Instance(KernelManager)
+ time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
+ max_msg_size = Int(65536, config=True, help="""
+ The max raw message size accepted from the browser
+ over a WebSocket connection.
+ """)
+
+ _notebook_mapping = Dict()
+
+ #-------------------------------------------------------------------------
+ # Methods for managing kernels and sessions
+ #-------------------------------------------------------------------------
+
+ def kernel_for_notebook(self, notebook_id):
+ """Return the kernel_id for a notebook_id or None."""
+ return self._notebook_mapping.get(notebook_id)
+
+ def set_kernel_for_notebook(self, notebook_id, kernel_id):
+ """Associate a notebook with a kernel."""
+ if notebook_id is not None:
+ self._notebook_mapping[notebook_id] = kernel_id
+
+ def notebook_for_kernel(self, kernel_id):
+ """Return the notebook_id for a kernel_id or None."""
+ notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
+ if len(notebook_ids) == 1:
+ return notebook_ids[0]
+ else:
+ return None
+
+ def delete_mapping_for_kernel(self, kernel_id):
+ """Remove the kernel/notebook mapping for kernel_id."""
+ notebook_id = self.notebook_for_kernel(kernel_id)
+ if notebook_id is not None:
+ del self._notebook_mapping[notebook_id]
+
+ def start_kernel(self, notebook_id=None):
+ """Start a kernel for a notebok an return its kernel_id.
+
+ Parameters
+ ----------
+ notebook_id : uuid
+ The uuid of the notebook to associate the new kernel with. If this
+ is not None, this kernel will be persistent whenever the notebook
+ requests a kernel.
+ """
+ kernel_id = self.kernel_for_notebook(notebook_id)
+ if kernel_id is None:
+ kwargs = dict()
+ kwargs['extra_arguments'] = self.kernel_argv
+ kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
+ self.set_kernel_for_notebook(notebook_id, kernel_id)
+ self.log.info("Kernel started: %s" % kernel_id)
+ self.log.debug("Kernel args: %r" % kwargs)
+ else:
+ self.log.info("Using existing kernel: %s" % kernel_id)
+ return kernel_id
+
+ def kill_kernel(self, kernel_id):
+ """Kill a kernel and remove its notebook association."""
+ if kernel_id not in self:
+ raise web.HTTPError(404)
+ super(MappingKernelManager, self).kill_kernel(kernel_id)
+ self.delete_mapping_for_kernel(kernel_id)
+ self.log.info("Kernel killed: %s" % kernel_id)
+
+ def interrupt_kernel(self, kernel_id):
+ """Interrupt a kernel."""
+ if kernel_id not in self:
+ raise web.HTTPError(404)
+ super(MappingKernelManager, self).interrupt_kernel(kernel_id)
+ self.log.info("Kernel interrupted: %s" % kernel_id)
+
+ def restart_kernel(self, kernel_id):
+ """Restart a kernel while keeping clients connected."""
+ if kernel_id not in self:
+ raise web.HTTPError(404)
+
+ # Get the notebook_id to preserve the kernel/notebook association.
+ notebook_id = self.notebook_for_kernel(kernel_id)
+ # Create the new kernel first so we can move the clients over.
+ new_kernel_id = self.start_kernel()
+ # Now kill the old kernel.
+ self.kill_kernel(kernel_id)
+ # Now save the new kernel/notebook association. We have to save it
+ # after the old kernel is killed as that will delete the mapping.
+ self.set_kernel_for_notebook(notebook_id, new_kernel_id)
+ self.log.info("Kernel restarted: %s" % new_kernel_id)
+ return new_kernel_id
+
+ def create_iopub_stream(self, kernel_id):
+ if kernel_id not in self:
+ raise web.HTTPError(404)
+ return super(MappingKernelManager, self).create_iopub_stream(kernel_id)
+
+ def create_shell_stream(self, kernel_id):
+ if kernel_id not in self:
+ raise web.HTTPError(404)
+ return super(MappingKernelManager, self).create_shell_stream(kernel_id)
+
+ def create_hb_stream(self, kernel_id):
+ if kernel_id not in self:
+ raise web.HTTPError(404)
+ return super(MappingKernelManager, self).create_hb_stream(kernel_id)
+
View
277 IPython/frontend/html/notebook/notebookapp.py
@@ -0,0 +1,277 @@
+"""A tornado based IPython notebook server.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import errno
+import logging
+import os
+import signal
+import socket
+import sys
+
+import zmq
+
+# Install the pyzmq ioloop. This has to be done before anything else from
+# tornado is imported.
+from zmq.eventloop import ioloop
+import tornado.ioloop
+tornado.ioloop = ioloop
+
+from tornado import httpserver
+from tornado import web
+
+from .kernelmanager import MappingKernelManager
+from .handlers import (
+ NBBrowserHandler, NewHandler, NamedNotebookHandler,
+ MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
+ ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
+)
+from .notebookmanager import NotebookManager
+
+from IPython.core.application import BaseIPythonApplication
+from IPython.core.profiledir import ProfileDir
+from IPython.zmq.session import Session
+from IPython.zmq.zmqshell import ZMQInteractiveShell
+from IPython.zmq.ipkernel import (
+ flags as ipkernel_flags,
+ aliases as ipkernel_aliases,
+ IPKernelApp
+)
+from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
+
+#-----------------------------------------------------------------------------
+# Module globals
+#-----------------------------------------------------------------------------
+
+_kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
+_kernel_action_regex = r"(?P<action>restart|interrupt)"
+_notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
+
+LOCALHOST = '127.0.0.1'
+
+_examples = """
+ipython notebook # start the notebook
+ipython notebook --profile=sympy # use the sympy profile
+ipython notebook --pylab=inline # pylab in inline plotting mode
+ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
+ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
+"""
+
+#-----------------------------------------------------------------------------
+# The Tornado web application
+#-----------------------------------------------------------------------------
+
+class NotebookWebApplication(web.Application):
+
+ def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
+ handlers = [
+ (r"/", NBBrowserHandler),
+ (r"/new", NewHandler),
+ (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
+ (r"/kernels", MainKernelHandler),
+ (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
+ (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
+ (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
+ (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
+ (r"/notebooks", NotebookRootHandler),
+ (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
+ (r"/rstservice/render", RSTHandler)
+ ]
+ settings = dict(
+ template_path=os.path.join(os.path.dirname(__file__), "templates"),
+ static_path=os.path.join(os.path.dirname(__file__), "static"),
+ )
+ web.Application.__init__(self, handlers, **settings)
+
+ self.kernel_manager = kernel_manager
+ self.log = log
+ self.notebook_manager = notebook_manager
+ self.ipython_app = ipython_app
+
+
+#-----------------------------------------------------------------------------
+# Aliases and Flags
+#-----------------------------------------------------------------------------
+
+flags = dict(ipkernel_flags)
+
+# the flags that are specific to the frontend
+# these must be scrubbed before being passed to the kernel,
+# or it will raise an error on unrecognized flags
+notebook_flags = []
+
+aliases = dict(ipkernel_aliases)
+
+aliases.update({
+ 'ip': 'IPythonNotebookApp.ip',
+ 'port': 'IPythonNotebookApp.port',
+ 'keyfile': 'IPythonNotebookApp.keyfile',
+ 'certfile': 'IPythonNotebookApp.certfile',
+ 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
+ 'notebook-dir': 'NotebookManager.notebook_dir'
+})
+
+notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
+ u'notebook-dir']
+
+#-----------------------------------------------------------------------------
+# IPythonNotebookApp
+#-----------------------------------------------------------------------------
+
+class IPythonNotebookApp(BaseIPythonApplication):
+
+ name = 'ipython-notebook'
+ default_config_file_name='ipython_notebook_config.py'
+
+ description = """
+ The IPython HTML Notebook.
+
+ This launches a Tornado based HTML Notebook Server that serves up an
+ HTML5/Javascript Notebook client.
+ """
+ examples = _examples
+
+ classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
+ MappingKernelManager, NotebookManager]
+ flags = Dict(flags)
+ aliases = Dict(aliases)
+
+ kernel_argv = List(Unicode)
+
+ log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
+ default_value=logging.INFO,
+ config=True,
+ help="Set the log level by value or name.")
+
+ # Network related information.
+
+ ip = Unicode(LOCALHOST, config=True,
+ help="The IP address the notebook server will listen on."
+ )
+
+ def _ip_changed(self, name, old, new):
+ if new == u'*': self.ip = u''
+
+ port = Int(8888, config=True,
+ help="The port the notebook server will listen on."
+ )
+
+ ws_hostname = Unicode(LOCALHOST, config=True,
+ help="""The FQDN or IP for WebSocket connections. The default will work
+ fine when the server is listening on localhost, but this needs to
+ be set if the ip option is used. It will be used as the hostname part
+ of the WebSocket url: ws://hostname/path."""
+ )
+
+ certfile = Unicode(u'', config=True,
+ help="""The full path to an SSL/TLS certificate file."""
+ )
+
+ keyfile = Unicode(u'', config=True,
+ help="""The full path to a private key file for usage with SSL/TLS."""
+ )
+
+ def get_ws_url(self):
+ """Return the WebSocket URL for this server."""
+ if self.certfile:
+ prefix = u'wss://'
+ else:
+ prefix = u'ws://'
+ return prefix + self.ws_hostname + u':' + unicode(self.port)
+
+ def parse_command_line(self, argv=None):
+ super(IPythonNotebookApp, self).parse_command_line(argv)
+ if argv is None:
+ argv = sys.argv[1:]
+
+ self.kernel_argv = list(argv) # copy
+ # Kernel should inherit default config file from frontend
+ self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
+ # Scrub frontend-specific flags
+ for a in argv:
+ if a.startswith('-') and a.lstrip('-') in notebook_flags:
+ self.kernel_argv.remove(a)
+ for a in argv:
+ if a.startswith('-'):
+ alias = a.lstrip('-').split('=')[0]
+ if alias in notebook_aliases:
+ self.kernel_argv.remove(a)
+
+ def init_configurables(self):
+ # Don't let Qt or ZMQ swallow KeyboardInterupts.
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+ # Create a KernelManager and start a kernel.
+ self.kernel_manager = MappingKernelManager(
+ config=self.config, log=self.log, kernel_argv=self.kernel_argv
+ )
+ self.notebook_manager = NotebookManager(config=self.config, log=self.log)
+ self.notebook_manager.list_notebooks()
+
+ def init_logging(self):
+ super(IPythonNotebookApp, self).init_logging()
+ # This prevents double log messages because tornado use a root logger that
+ # self.log is a child of. The logging module dipatches log messages to a log
+ # and all of its ancenstors until propagate is set to False.
+ self.log.propagate = False
+
+ def initialize(self, argv=None):
+ super(IPythonNotebookApp, self).initialize(argv)
+ self.init_configurables()
+ self.web_app = NotebookWebApplication(
+ self, self.kernel_manager, self.notebook_manager, self.log
+ )
+ if self.certfile:
+ ssl_options = dict(certfile=self.certfile)
+ if self.keyfile:
+ ssl_options['keyfile'] = self.keyfile
+ else:
+ ssl_options = None
+ self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
+ if ssl_options is None and not self.ip:
+ self.log.critical('WARNING: the notebook server is listening on all IP addresses '
+ 'but not using any encryption or authentication. This is highly '
+ 'insecure and not recommended.')
+
+ # Try random ports centered around the default.
+ from random import randint
+ n = 50 # Max number of attempts, keep reasonably large.
+ for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]:
+ try:
+ self.http_server.listen(port, self.ip)
+ except socket.error, e:
+ if e.errno != errno.EADDRINUSE:
+ raise
+ self.log.info('The port %i is already in use, trying another random port.' % port)
+ else:
+ self.port = port
+ break
+
+ def start(self):
+ ip = self.ip if self.ip else '[all ip addresses on your system]'
+ self.log.info("The IPython Notebook is running at: http://%s:%i" % (ip, self.port))
+ ioloop.IOLoop.instance().start()
+
+#-----------------------------------------------------------------------------
+# Main entry point
+#-----------------------------------------------------------------------------
+
+def launch_new_instance():
+ app = IPythonNotebookApp()
+ app.initialize()
+ app.start()
+
View
219 IPython/frontend/html/notebook/notebookmanager.py
@@ -0,0 +1,219 @@
+"""A notebook manager that uses the local file system for storage.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import datetime
+import os
+import uuid
+import glob
+
+from tornado import web
+
+from IPython.config.configurable import LoggingConfigurable
+from IPython.nbformat import current
+from IPython.utils.traitlets import Unicode, List, Dict
+
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+
+class NotebookManager(LoggingConfigurable):
+
+ notebook_dir = Unicode(os.getcwd(), config=True, help="""
+ The directory to use for notebooks.
+ """)
+ filename_ext = Unicode(u'.ipynb')
+ allowed_formats = List([u'json',u'py'])
+
+ # Map notebook_ids to notebook names
+ mapping = Dict()
+ # Map notebook names to notebook_ids
+ rev_mapping = Dict()
+
+ def list_notebooks(self):
+ """List all notebooks in the notebook dir.
+
+ This returns a list of dicts of the form::
+
+ dict(notebook_id=notebook,name=name)
+ """
+ names = glob.glob(os.path.join(self.notebook_dir,
+ '*' + self.filename_ext))
+ names = [os.path.splitext(os.path.basename(name))[0]
+ for name in names]
+
+ data = []
+ for name in names:
+ if name not in self.rev_mapping:
+ notebook_id = self.new_notebook_id(name)
+ else:
+ notebook_id = self.rev_mapping[name]
+ data.append(dict(notebook_id=notebook_id,name=name))
+ data = sorted(data, key=lambda item: item['name'])
+ return data
+
+ def new_notebook_id(self, name):
+ """Generate a new notebook_id for a name and store its mappings."""
+ notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
+ 'file://'+self.get_path_by_name(name).encode('utf-8')))
+ self.mapping[notebook_id] = name
+ self.rev_mapping[name] = notebook_id
+ return notebook_id
+
+ def delete_notebook_id(self, notebook_id):
+ """Delete a notebook's id only. This doesn't delete the actual notebook."""
+ name = self.mapping[notebook_id]
+ del self.mapping[notebook_id]
+ del self.rev_mapping[name]
+
+ def notebook_exists(self, notebook_id):
+ """Does a notebook exist?"""
+ if notebook_id not in self.mapping:
+ return False
+ path = self.get_path_by_name(self.mapping[notebook_id])
+ return os.path.isfile(path)
+
+ def find_path(self, notebook_id):
+ """Return a full path to a notebook given its notebook_id."""
+ try:
+ name = self.mapping[notebook_id]
+ except KeyError:
+ raise web.HTTPError(404)
+ return self.get_path_by_name(name)
+
+ def get_path_by_name(self, name):
+ """Return a full path to a notebook given its name."""
+ filename = name + self.filename_ext
+ path = os.path.join(self.notebook_dir, filename)
+ return path
+
+ def get_notebook(self, notebook_id, format=u'json'):
+ """Get the representation of a notebook in format by notebook_id."""
+ format = unicode(format)
+ if format not in self.allowed_formats:
+ raise web.HTTPError(415)
+ last_modified, nb = self.get_notebook_object(notebook_id)
+ data = current.writes(nb, format)
+ name = nb.get('name','notebook')
+ return last_modified, name, data
+
+ def get_notebook_object(self, notebook_id):
+ """Get the NotebookNode representation of a notebook by notebook_id."""
+ path = self.find_path(notebook_id)
+ if not os.path.isfile(path):
+ raise web.HTTPError(404)
+ info = os.stat(path)
+ last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
+ with open(path,'r') as f:
+ s = f.read()
+ try:
+ # v1 and v2 and json in the .ipynb files.
+ nb = current.reads(s, u'json')
+ except:
+ raise web.HTTPError(404)
@minrk Owner
minrk added a note

We need a lot more feedback than a 404 here, especially since this isn't even the real page, so users will get exactly no feedback.

The frontend just sticks at 'loading...', and if they enter a name and save, the original corrupt (or xml) notebook is simply erased.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ if 'name' not in nb:
+ nb.name = os.path.split(path)[-1].split(u'.')[0]
+ return last_modified, nb
+
+ def save_new_notebook(self, data, name=None, format=u'json'):
+ """Save a new notebook and return its notebook_id.
+
+ If a name is passed in, it overrides any values in the notebook data
+ and the value in the data is updated to use that value.
+ """
+ if format not in self.allowed_formats:
+ raise web.HTTPError(415)
@fperez Owner
fperez added a note

Again, regarding debuggability: is it possible to include some information about the error? Something like raise web.HTTPError(415, "invalid format %s supplied" % format)?

@ellisonbg Owner

In this case 415 means "Unsupported Media Type", which is basically the information we would return. I have been quite careful to return error codes that are specific.

@fperez Owner
fperez added a note

ok, thanks for the info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ try:
+ nb = current.reads(data, format)
+ except:
+ raise web.HTTPError(400)
+
+ if name is None:
+ try:
+ name = nb.metadata.name
+ except AttributeError:
+ raise web.HTTPError(400)
+ nb.metadata.name = name
+
+ notebook_id = self.new_notebook_id(name)
+ self.save_notebook_object(notebook_id, nb)
+ return notebook_id
+
+ def save_notebook(self, notebook_id, data, name=None, format=u'json'):
+ """Save an existing notebook by notebook_id."""
+ if format not in self.allowed_formats:
+ raise web.HTTPError(415)
+
+ try:
+ nb = current.reads(data, format)
+ except:
+ raise web.HTTPError(400)
+
+ if name is not None:
+ nb.metadata.name = name
+ self.save_notebook_object(notebook_id, nb)
+
+ def save_notebook_object(self, notebook_id, nb):
+ """Save an existing notebook object by notebook_id."""
+ if notebook_id not in self.mapping:
+ raise web.HTTPError(404)
+ old_name = self.mapping[notebook_id]
+ try:
+ new_name = nb.metadata.name
+ except AttributeError: