##// END OF EJS Templates
added "last_modified" to notebook_model
Zachary Sailer -
Show More
@@ -1,241 +1,242 b''
1 1 """A base class notebook manager.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 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 os
20 20 import uuid
21 21
22 22 from tornado import web
23 23
24 24 from IPython.config.configurable import LoggingConfigurable
25 25 from IPython.nbformat import current
26 26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Classes
30 30 #-----------------------------------------------------------------------------
31 31
32 32 class NotebookManager(LoggingConfigurable):
33 33
34 34 # Todo:
35 35 # The notebook_dir attribute is used to mean a couple of different things:
36 36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 37 # 2. The cwd of the kernel for a project.
38 38 # Right now we use this attribute in a number of different places and
39 39 # we are going to have to disentangle all of this.
40 40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 41 The directory to use for notebooks.
42 42 """)
43 43
44 44 def named_notebook_path(self, notebook_path):
45 45
46 46 l = len(notebook_path)
47 47 names = notebook_path.split('/')
48 48 if len(names) > 1:
49 49 name = names[len(names)-1]
50 50 if name[(len(name)-6):(len(name))] == ".ipynb":
51 51 name = name
52 52 path = notebook_path[0:l-len(name)-1]+'/'
53 53 else:
54 54 name = None
55 55 path = notebook_path+'/'
56 56 else:
57 57 name = names[0]
58 58 if name[(len(name)-6):(len(name))] == ".ipynb":
59 59 name = name
60 60 path = None
61 61 else:
62 62 name = None
63 63 path = notebook_path+'/'
64 64 return name, path
65 65
66 66 def _notebook_dir_changed(self, new):
67 67 """do a bit of validation of the notebook dir"""
68 68 if not os.path.isabs(new):
69 69 # If we receive a non-absolute path, make it absolute.
70 70 abs_new = os.path.abspath(new)
71 71 #self.notebook_dir = os.path.dirname(abs_new)
72 72 return
73 73 if os.path.exists(new) and not os.path.isdir(new):
74 74 raise TraitError("notebook dir %r is not a directory" % new)
75 75 if not os.path.exists(new):
76 76 self.log.info("Creating notebook dir %s", new)
77 77 try:
78 78 os.mkdir(new)
79 79 except:
80 80 raise TraitError("Couldn't create notebook dir %r" % new)
81 81
82 82 allowed_formats = List([u'json',u'py'])
83 83
84 84
85 85 def load_notebook_names(self, path):
86 86 """Load the notebook names into memory.
87 87
88 88 This should be called once immediately after the notebook manager
89 89 is created to load the existing notebooks into the mapping in
90 90 memory.
91 91 """
92 92 self.list_notebooks(path)
93 93
94 94 def list_notebooks(self):
95 95 """List all notebooks.
96 96
97 97 This returns a list of dicts, each of the form::
98 98
99 99 dict(notebook_id=notebook,name=name)
100 100
101 101 This list of dicts should be sorted by name::
102 102
103 103 data = sorted(data, key=lambda item: item['name'])
104 104 """
105 105 raise NotImplementedError('must be implemented in a subclass')
106 106
107 107
108 108 def notebook_exists(self, notebook_name):
109 109 """Does a notebook exist?"""
110 110 return notebook_name in self.mapping
111 111
112 112 def notebook_model(self, notebook_name, notebook_path=None):
113 113 """ Creates the standard notebook model """
114 114 last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
115 115 model = {"notebook_name": notebook_name,
116 116 "notebook_path": notebook_path,
117 "content": content}
117 "content": content,
118 "last_modified": last_modified.ctime()}
118 119 return model
119 120
120 121 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
121 122 """Get the representation of a notebook in format by notebook_name."""
122 123 format = unicode(format)
123 124 if format not in self.allowed_formats:
124 125 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
125 126 kwargs = {}
126 127 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
127 128 if format == 'json':
128 129 # don't split lines for sending over the wire, because it
129 130 # should match the Python in-memory format.
130 131 kwargs['split_lines'] = False
131 132 representation = current.writes(nb, format, **kwargs)
132 133 name = nb.metadata.get('name', 'notebook')
133 134 return last_mod, representation, name
134 135
135 136 def read_notebook_object(self, notebook_name, notebook_path):
136 137 """Get the object representation of a notebook by notebook_id."""
137 138 raise NotImplementedError('must be implemented in a subclass')
138 139
139 140 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
140 141 """Save a new notebook and return its notebook_id.
141 142
142 143 If a name is passed in, it overrides any values in the notebook data
143 144 and the value in the data is updated to use that value.
144 145 """
145 146 if format not in self.allowed_formats:
146 147 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
147 148
148 149 try:
149 150 nb = current.reads(data.decode('utf-8'), format)
150 151 except:
151 152 raise web.HTTPError(400, u'Invalid JSON data')
152 153
153 154 if name is None:
154 155 try:
155 156 name = nb.metadata.name
156 157 except AttributeError:
157 158 raise web.HTTPError(400, u'Missing notebook name')
158 159 nb.metadata.name = name
159 160
160 161 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
161 162 return notebook_name
162 163
163 164 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
164 165 """Save an existing notebook by notebook_name."""
165 166 if format not in self.allowed_formats:
166 167 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
167 168
168 169 try:
169 170 nb = current.reads(data.decode('utf-8'), format)
170 171 except:
171 172 raise web.HTTPError(400, u'Invalid JSON data')
172 173
173 174 if name is not None:
174 175 nb.metadata.name = name
175 176 self.write_notebook_object(nb, name, notebook_path, new_name)
176 177
177 178 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
178 179 """Write a notebook object and return its notebook_name.
179 180
180 181 If notebook_name is None, this method should create a new notebook_name.
181 182 If notebook_name is not None, this method should check to make sure it
182 183 exists and is valid.
183 184 """
184 185 raise NotImplementedError('must be implemented in a subclass')
185 186
186 187 def delete_notebook(self, notebook_name, notebook_path):
187 188 """Delete notebook by notebook_id."""
188 189 raise NotImplementedError('must be implemented in a subclass')
189 190
190 191 def increment_filename(self, name):
191 192 """Increment a filename to make it unique.
192 193
193 194 This exists for notebook stores that must have unique names. When a notebook
194 195 is created or copied this method constructs a unique filename, typically
195 196 by appending an integer to the name.
196 197 """
197 198 return name
198 199
199 200 def new_notebook(self, notebook_path=None):
200 201 """Create a new notebook and return its notebook_id."""
201 202 name = self.increment_filename('Untitled', notebook_path)
202 203 metadata = current.new_metadata(name=name)
203 204 nb = current.new_notebook(metadata=metadata)
204 205 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
205 206 return notebook_name
206 207
207 208 def copy_notebook(self, name, path):
208 209 """Copy an existing notebook and return its notebook_id."""
209 210 last_mod, nb = self.read_notebook_object(name, path)
210 211 name = nb.metadata.name + '-Copy'
211 212 name = self.increment_filename(name, path)
212 213 nb.metadata.name = name
213 214 notebook_name = self.write_notebook_object(nb, notebook_path = path)
214 215 return notebook_name
215 216
216 217 # Checkpoint-related
217 218
218 219 def create_checkpoint(self, notebook_name, notebook_path=None):
219 220 """Create a checkpoint of the current state of a notebook
220 221
221 222 Returns a checkpoint_id for the new checkpoint.
222 223 """
223 224 raise NotImplementedError("must be implemented in a subclass")
224 225
225 226 def list_checkpoints(self, notebook_name, notebook_path=None):
226 227 """Return a list of checkpoints for a given notebook"""
227 228 return []
228 229
229 230 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
230 231 """Restore a notebook from one of its checkpoints"""
231 232 raise NotImplementedError("must be implemented in a subclass")
232 233
233 234 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
234 235 """delete a checkpoint for a notebook"""
235 236 raise NotImplementedError("must be implemented in a subclass")
236 237
237 238 def log_info(self):
238 239 self.log.info(self.info_string())
239 240
240 241 def info_string(self):
241 242 return "Serving notebooks"
General Comments 0
You need to be logged in to leave comments. Login now