##// END OF EJS Templates
Going back to using uuid.uuid4() for notebook ids....
Brian E. Granger -
Show More
@@ -1,219 +1,218
1 1 """A notebook manager that uses the local file system for storage.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import datetime
20 20 import os
21 21 import uuid
22 22 import glob
23 23
24 24 from tornado import web
25 25
26 26 from IPython.config.configurable import LoggingConfigurable
27 27 from IPython.nbformat import current
28 28 from IPython.utils.traitlets import Unicode, List, Dict
29 29
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Code
33 33 #-----------------------------------------------------------------------------
34 34
35 35
36 36 class NotebookManager(LoggingConfigurable):
37 37
38 38 notebook_dir = Unicode(os.getcwd(), config=True, help="""
39 39 The directory to use for notebooks.
40 40 """)
41 41 filename_ext = Unicode(u'.ipynb')
42 42 allowed_formats = List([u'json',u'py'])
43 43
44 44 # Map notebook_ids to notebook names
45 45 mapping = Dict()
46 46 # Map notebook names to notebook_ids
47 47 rev_mapping = Dict()
48 48
49 49 def list_notebooks(self):
50 50 """List all notebooks in the notebook dir.
51 51
52 52 This returns a list of dicts of the form::
53 53
54 54 dict(notebook_id=notebook,name=name)
55 55 """
56 56 names = glob.glob(os.path.join(self.notebook_dir,
57 57 '*' + self.filename_ext))
58 58 names = [os.path.splitext(os.path.basename(name))[0]
59 59 for name in names]
60 60
61 61 data = []
62 62 for name in names:
63 63 if name not in self.rev_mapping:
64 64 notebook_id = self.new_notebook_id(name)
65 65 else:
66 66 notebook_id = self.rev_mapping[name]
67 67 data.append(dict(notebook_id=notebook_id,name=name))
68 68 data = sorted(data, key=lambda item: item['name'])
69 69 return data
70 70
71 71 def new_notebook_id(self, name):
72 72 """Generate a new notebook_id for a name and store its mappings."""
73 notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
74 'file://'+self.get_path_by_name(name).encode('utf-8')))
73 notebook_id = unicode(uuid.uuid4())
75 74 self.mapping[notebook_id] = name
76 75 self.rev_mapping[name] = notebook_id
77 76 return notebook_id
78 77
79 78 def delete_notebook_id(self, notebook_id):
80 79 """Delete a notebook's id only. This doesn't delete the actual notebook."""
81 80 name = self.mapping[notebook_id]
82 81 del self.mapping[notebook_id]
83 82 del self.rev_mapping[name]
84 83
85 84 def notebook_exists(self, notebook_id):
86 85 """Does a notebook exist?"""
87 86 if notebook_id not in self.mapping:
88 87 return False
89 88 path = self.get_path_by_name(self.mapping[notebook_id])
90 89 return os.path.isfile(path)
91 90
92 91 def find_path(self, notebook_id):
93 92 """Return a full path to a notebook given its notebook_id."""
94 93 try:
95 94 name = self.mapping[notebook_id]
96 95 except KeyError:
97 96 raise web.HTTPError(404)
98 97 return self.get_path_by_name(name)
99 98
100 99 def get_path_by_name(self, name):
101 100 """Return a full path to a notebook given its name."""
102 101 filename = name + self.filename_ext
103 102 path = os.path.join(self.notebook_dir, filename)
104 103 return path
105 104
106 105 def get_notebook(self, notebook_id, format=u'json'):
107 106 """Get the representation of a notebook in format by notebook_id."""
108 107 format = unicode(format)
109 108 if format not in self.allowed_formats:
110 109 raise web.HTTPError(415)
111 110 last_modified, nb = self.get_notebook_object(notebook_id)
112 111 data = current.writes(nb, format)
113 112 name = nb.get('name','notebook')
114 113 return last_modified, name, data
115 114
116 115 def get_notebook_object(self, notebook_id):
117 116 """Get the NotebookNode representation of a notebook by notebook_id."""
118 117 path = self.find_path(notebook_id)
119 118 if not os.path.isfile(path):
120 119 raise web.HTTPError(404)
121 120 info = os.stat(path)
122 121 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
123 122 with open(path,'r') as f:
124 123 s = f.read()
125 124 try:
126 125 # v1 and v2 and json in the .ipynb files.
127 126 nb = current.reads(s, u'json')
128 127 except:
129 128 raise web.HTTPError(404)
130 129 if 'name' not in nb:
131 130 nb.name = os.path.split(path)[-1].split(u'.')[0]
132 131 return last_modified, nb
133 132
134 133 def save_new_notebook(self, data, name=None, format=u'json'):
135 134 """Save a new notebook and return its notebook_id.
136 135
137 136 If a name is passed in, it overrides any values in the notebook data
138 137 and the value in the data is updated to use that value.
139 138 """
140 139 if format not in self.allowed_formats:
141 140 raise web.HTTPError(415)
142 141
143 142 try:
144 143 nb = current.reads(data, format)
145 144 except:
146 145 raise web.HTTPError(400)
147 146
148 147 if name is None:
149 148 try:
150 149 name = nb.metadata.name
151 150 except AttributeError:
152 151 raise web.HTTPError(400)
153 152 nb.metadata.name = name
154 153
155 154 notebook_id = self.new_notebook_id(name)
156 155 self.save_notebook_object(notebook_id, nb)
157 156 return notebook_id
158 157
159 158 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
160 159 """Save an existing notebook by notebook_id."""
161 160 if format not in self.allowed_formats:
162 161 raise web.HTTPError(415)
163 162
164 163 try:
165 164 nb = current.reads(data, format)
166 165 except:
167 166 raise web.HTTPError(400)
168 167
169 168 if name is not None:
170 169 nb.metadata.name = name
171 170 self.save_notebook_object(notebook_id, nb)
172 171
173 172 def save_notebook_object(self, notebook_id, nb):
174 173 """Save an existing notebook object by notebook_id."""
175 174 if notebook_id not in self.mapping:
176 175 raise web.HTTPError(404)
177 176 old_name = self.mapping[notebook_id]
178 177 try:
179 178 new_name = nb.metadata.name
180 179 except AttributeError:
181 180 raise web.HTTPError(400)
182 181 path = self.get_path_by_name(new_name)
183 182 try:
184 183 with open(path,'w') as f:
185 184 current.write(nb, f, u'json')
186 185 except:
187 186 raise web.HTTPError(400)
188 187 if old_name != new_name:
189 188 old_path = self.get_path_by_name(old_name)
190 189 if os.path.isfile(old_path):
191 190 os.unlink(old_path)
192 191 self.mapping[notebook_id] = new_name
193 192 self.rev_mapping[new_name] = notebook_id
194 193
195 194 def delete_notebook(self, notebook_id):
196 195 """Delete notebook by notebook_id."""
197 196 path = self.find_path(notebook_id)
198 197 if not os.path.isfile(path):
199 198 raise web.HTTPError(404)
200 199 os.unlink(path)
201 200 self.delete_notebook_id(notebook_id)
202 201
203 202 def new_notebook(self):
204 203 """Create a new notebook and returns its notebook_id."""
205 204 i = 0
206 205 while True:
207 206 name = u'Untitled%i' % i
208 207 path = self.get_path_by_name(name)
209 208 if not os.path.isfile(path):
210 209 break
211 210 else:
212 211 i = i+1
213 212 notebook_id = self.new_notebook_id(name)
214 213 metadata = current.new_metadata(name=name)
215 214 nb = current.new_notebook(metadata=metadata)
216 215 with open(path,'w') as f:
217 216 current.write(nb, f, u'json')
218 217 return notebook_id
219 218
@@ -1,203 +1,202
1 1 .. _htmlnotebook:
2 2
3 3 =========================
4 4 An HTML Notebook IPython
5 5 =========================
6 6
7 7 The IPython Notebook consists of two related components:
8 8
9 * An XML/JSON based Notebook document format for recording and distributing
9 * An JSON based Notebook document format for recording and distributing
10 10 Python code and rich text.
11 11 * A web-based user interface for authoring and running notebook documents.
12 12
13 13 The Notebook can be used by starting the Notebook server with the
14 14 command::
15 15
16 16 $ ipython notebook
17 17
18 18 Note that by default, the notebook doesn't load pylab, it's just a normal
19 19 IPython session like any other. If you want pylab support, you must use::
20 20
21 21 $ ipython notebook --pylab
22 22
23 23 which will behave similar to the terminal and Qt console versions, using your
24 24 default matplotlib backend and providing floating interactive plot windows. If
25 25 you want inline figures, you must manually select the ``inline`` backend::
26 26
27 $ ipython notebook --pylab inline
27 $ ipython notebook --pylab=inline
28 28
29 29 This server uses the same ZeroMQ-based two process kernel architecture as
30 30 the QT Console as well Tornado for serving HTTP requests. Some of the main
31 31 features of the Notebook include:
32 32
33 33 * Display rich data (png/html/latex/svg) in the browser as a result of
34 34 computations.
35 35 * Compose text cells using HTML and Markdown.
36 * Import and export notebook documents in range of formats (.ipynb, .json, .py).
36 * Import and export notebook documents in range of formats (.ipynb, .py).
37 37 * In browser syntax highlighting, tab completion and autoindentation.
38 38 * Inline matplotlib plots that can be stored in Notebook documents and opened
39 39 later.
40 40
41 41 See :ref:`our installation documentation <install_index>` for directions on
42 42 how to install the notebook and its dependencies.
43 43
44 44 .. note::
45 45
46 46 You can start more than one notebook server at the same time, if you want to
47 47 work on notebooks in different directories. By default the first notebook
48 48 server starts in port 8888, later notebooks search for random ports near
49 49 that one. You can also manually specify the port with the ``--port``
50 option, if you want persistent URLs you can bookmark.
50 option.
51 51
52 52
53 53 Basic Usage
54 54 ===========
55 55
56 56 The landing page of the notebook server application, which we call the IPython
57 57 Notebook *dashboard*, shows the notebooks currently available in the directory
58 58 in which the application was started, and allows you to create new notebooks.
59 59
60 60 A notebook is a combination of two things:
61 61
62 1. an interactive session connected to an IPython kernel, controlled by a web
62 1. An interactive session connected to an IPython kernel, controlled by a web
63 63 application that can send input to the console and display many types of output
64 64 (text, graphics, mathematics and more). This is the same kernel used by the
65 65 :ref:`Qt console <qtconsole>`, but in this case the web console sends input in
66 66 persistent cells that you can edit in-place instead of the vertically scrolling
67 67 terminal style used by the Qt console.
68 68
69 2. a document that can save the inputs and outputs of the session as well as
69 2. A document that can save the inputs and outputs of the session as well as
70 70 additional text that accompanies the code but is not meant for execution. In
71 71 this way, notebook files serve as a complete computational record of a session
72 72 including explanatory text and mathematics, code and resulting figures. These
73 73 documents are internally JSON files and are saved with the ``.ipynb``
74 74 extension.
75 75
76 76 If you have ever used the Mathematica or Sage notebooks (the latter is also
77 77 web-based__) you should feel right at home. If you have not, you should be
78 78 able to learn how to use it in just a few minutes.
79 79
80 80 .. __: http://sagenb.org
81 81
82 82
83 83 Creating and editing notebooks
84 84 ------------------------------
85 85
86 86 You can create new notebooks from the dashboard with the ``New Notebook``
87 87 button or open existing ones by clicking on their name. Once in a notebook,
88 88 your browser tab will reflect the name of that notebook (prefixed with "IPy:").
89 The URL for that notebook is not meant to be human-readable, but it is
90 persistent across invocations of the notebook server *as long as you don't
91 rename the notebook*, so you can bookmark them for future use.
89 The URL for that notebook is not meant to be human-readable and is *not*
90 persistent across invocations of the notebook server.
92 91
93 You can also drag and dropp into the area listing files any python file: it
92 You can also drag and drop into the area listing files any python file: it
94 93 will be imported into a notebook with the same name (but ``.ipynb`` extension)
95 94 located in the directory where the notebook server was started. This notebook
96 95 will consist of a single cell with all the code in the file, which you can
97 96 later manually partition into individual cells for gradual execution, add text
98 97 and graphics, etc.
99 98
100 99 Workflow and limitations
101 100 ------------------------
102 101
103 102 The normal workflow in a notebook is quite similar to a normal IPython session,
104 103 with the difference that you can edit a cell in-place multiple times until you
105 104 obtain the desired results rather than having to rerun separate scripts with
106 105 the ``%run`` magic (though magics also work in the notebook). Typically
107 106 you'll work on a problem in pieces, organizing related pieces into cells and
108 107 moving forward as previous parts work correctly. This is much more convenient
109 108 for interactive exploration than breaking up a computation into scripts that
110 109 must be executed together, especially if parts of them take a long time to run
111 110 (you can use tricks with namespaces and ``%run -i``, but we think the notebook
112 111 is a more natural solution for that kind of problem).
113 112
114 113 The only significant limitation the notebook currently has, compared to the qt
115 114 console, is that it can not run any code that expects input from the kernel
116 115 (such as scripts that call :func:`raw_input`). Very importantly, this means
117 116 that the ``%debug`` magic does *not* work in the notebook! We intend to
118 117 correct this limitation, but in the meantime, there is a way to debug problems
119 118 in the notebook: you can attach a Qt console to your existing notebook kernel,
120 119 and run ``%debug`` from the Qt console. Simply look for the lines in the
121 120 terminal where you started the kernel that read something like::
122 121
123 122 [IPKernelApp] To connect another client to this kernel, use:
124 123 [IPKernelApp] --existing --shell=53328 --iopub=53817 --stdin=34736 --hb=45543
125 124
126 125 and then start a qt console pointing to that kernel::
127 126
128 127 ipython qtconsole --existing --shell=53328 --iopub=53817 --stdin=34736 --hb=45543
129 128
130 129
131 130 Text input
132 131 ----------
133 132
134 133 In addition to code cells and the output they procude (such as figures), you
135 134 can also type text not meant for execution. To type text, change the type of a
136 cell from ``Code`` to ``Markdown`` by using the button or the :kbd:`C-m m`
135 cell from ``Code`` to ``Markdown`` by using the button or the :kbd:`Ctrl-m m`
137 136 keybinding (see below). You can then type any text in Markdown_ syntax, as
138 137 well as mathematical expressions if you use ``$...$`` for inline math or
139 138 ``$$...$$`` for displayed math.
140 139
141 140 Exporting a notebook
142 141 --------------------
143 142
144 143 If you want to provide others with a static HTML or PDF view of your notebook,
145 144 use the ``Print`` button. This opens a static view of the document, which you
146 145 can print to PDF using your operating system's facilities, or save to a file
147 146 with your web browser's 'Save' option (note that typically, this will create
148 147 both an html file *and* a directory called `notebook_name_files` next to it
149 148 that contains all the necessary style information, so if you intend to share
150 149 this, you must send the directory along with the main html file).
151 150
152 151 The `Download` button lets you save a notebook file to the Download area
153 152 configured by your web browser (particularly useful if you are running the
154 153 notebook server on a remote host and need a file locally). The notebook is
155 154 saved by default with the ``.ipynb`` extension and the files contain JSON data
156 155 that is not meant for human editing or consumption. But you can always export
157 156 the input part of a notebook to a plain python script by choosing Python format
158 157 in the `Download` drop list. This removes all output and saves the text cells
159 158 in comment areas.
160 159
161 160 .. warning::
162 161
163 162 While in simple cases you can roundtrip a notebook to Python, edit the
164 163 python file and import it back without loss, this is in general *not
165 164 guaranteed to work at all*. As the notebook format evolves in complexity,
166 165 there will be attributes of the notebook that will not survive a roundtrip
167 166 through the Python form. You should think of the Python format as a way to
168 167 output a script version of a notebook and the import capabilities as a way
169 168 to load existing code to get a notebook started. But the Python version is
170 *not* an alternate Python format.
169 *not* an alternate notebook format.
171 170
172 171
173 172 Keyboard use
174 173 ------------
175 174
176 175 All actions in the notebook can be achieved with the mouse, but we have also
177 176 added keyboard shortcuts for the most common ones, so that productive use of
178 177 the notebook can be achieved with minimal mouse intervention. The main
179 178 key bindings you need to remember are:
180 179
181 180 * :kbd:`Shift-Enter`: execute the current cell (similar to the Qt console),
182 181 show output (if any) and create a new cell below. Note that in the notebook,
183 182 simply using :kbd:`Enter` *never* forces execution, it simply inserts a new
184 183 line in the current cell. Therefore, in the notebook you must always use
185 184 :kbd:`Shift-Enter` to get execution (or use the mouse and click on the ``Run
186 185 Selected`` button).
187 186
188 * :kbd:`Control-Enter`: execute the current cell in "terminal mode", where any
187 * :kbd:`Ctrl-Enter`: execute the current cell in "terminal mode", where any
189 188 output is shown but the cursor cursor stays in the current cell, whose input
190 189 area is flushed empty. This is convenient to do quick in-place experiments
191 190 or query things like filesystem content without creating additional cells you
192 191 may not want saved in your notebook.
193 192
194 * :kbd:`Control-m`: this is the prefix for all other keybindings, which consist
193 * :kbd:`Ctrl-m`: this is the prefix for all other keybindings, which consist
195 194 of an additional single letter. Type :kbd:`Ctrl-m h` (that is, the sole
196 195 letter :kbd:`h` after :kbd:`Ctrl-m`) and IPython will show you the remaining
197 196 available keybindings.
198 197
199 198
200 199 Notebook document format
201 200 ========================
202 201
203 202
General Comments 0
You need to be logged in to leave comments. Login now