##// END OF EJS Templates
Answer Issue #2366...
Ohad Ravid -
Show More
@@ -1,205 +1,210 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 disentagle 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 def _notebook_dir_changed(self, name, old, new):
44 44 """do a bit of validation of the notebook dir"""
45 if not os.path.isabs(new):
46 # If we receive a non-absolute path, make it absolute.
47 abs_new = os.path.abspath(new)
48 self.notebook_dir = abs_new
49 return
45 50 if os.path.exists(new) and not os.path.isdir(new):
46 51 raise TraitError("notebook dir %r is not a directory" % new)
47 52 if not os.path.exists(new):
48 53 self.log.info("Creating notebook dir %s", new)
49 54 try:
50 55 os.mkdir(new)
51 56 except:
52 57 raise TraitError("Couldn't create notebook dir %r" % new)
53 58
54 59 allowed_formats = List([u'json',u'py'])
55 60
56 61 # Map notebook_ids to notebook names
57 62 mapping = Dict()
58 63
59 64 def load_notebook_names(self):
60 65 """Load the notebook names into memory.
61 66
62 67 This should be called once immediately after the notebook manager
63 68 is created to load the existing notebooks into the mapping in
64 69 memory.
65 70 """
66 71 self.list_notebooks()
67 72
68 73 def list_notebooks(self):
69 74 """List all notebooks.
70 75
71 76 This returns a list of dicts, each of the form::
72 77
73 78 dict(notebook_id=notebook,name=name)
74 79
75 80 This list of dicts should be sorted by name::
76 81
77 82 data = sorted(data, key=lambda item: item['name'])
78 83 """
79 84 raise NotImplementedError('must be implemented in a subclass')
80 85
81 86
82 87 def new_notebook_id(self, name):
83 88 """Generate a new notebook_id for a name and store its mapping."""
84 89 # TODO: the following will give stable urls for notebooks, but unless
85 90 # the notebooks are immediately redirected to their new urls when their
86 91 # filemname changes, nasty inconsistencies result. So for now it's
87 92 # disabled and instead we use a random uuid4() call. But we leave the
88 93 # logic here so that we can later reactivate it, whhen the necessary
89 94 # url redirection code is written.
90 95 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
91 96 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
92 97
93 98 notebook_id = unicode(uuid.uuid4())
94 99 self.mapping[notebook_id] = name
95 100 return notebook_id
96 101
97 102 def delete_notebook_id(self, notebook_id):
98 103 """Delete a notebook's id in the mapping.
99 104
100 105 This doesn't delete the actual notebook, only its entry in the mapping.
101 106 """
102 107 del self.mapping[notebook_id]
103 108
104 109 def notebook_exists(self, notebook_id):
105 110 """Does a notebook exist?"""
106 111 return notebook_id in self.mapping
107 112
108 113 def get_notebook(self, notebook_id, format=u'json'):
109 114 """Get the representation of a notebook in format by notebook_id."""
110 115 format = unicode(format)
111 116 if format not in self.allowed_formats:
112 117 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
113 118 last_modified, nb = self.read_notebook_object(notebook_id)
114 119 kwargs = {}
115 120 if format == 'json':
116 121 # don't split lines for sending over the wire, because it
117 122 # should match the Python in-memory format.
118 123 kwargs['split_lines'] = False
119 124 data = current.writes(nb, format, **kwargs)
120 125 name = nb.metadata.get('name','notebook')
121 126 return last_modified, name, data
122 127
123 128 def read_notebook_object(self, notebook_id):
124 129 """Get the object representation of a notebook by notebook_id."""
125 130 raise NotImplementedError('must be implemented in a subclass')
126 131
127 132 def save_new_notebook(self, data, name=None, format=u'json'):
128 133 """Save a new notebook and return its notebook_id.
129 134
130 135 If a name is passed in, it overrides any values in the notebook data
131 136 and the value in the data is updated to use that value.
132 137 """
133 138 if format not in self.allowed_formats:
134 139 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
135 140
136 141 try:
137 142 nb = current.reads(data.decode('utf-8'), format)
138 143 except:
139 144 raise web.HTTPError(400, u'Invalid JSON data')
140 145
141 146 if name is None:
142 147 try:
143 148 name = nb.metadata.name
144 149 except AttributeError:
145 150 raise web.HTTPError(400, u'Missing notebook name')
146 151 nb.metadata.name = name
147 152
148 153 notebook_id = self.write_notebook_object(nb)
149 154 return notebook_id
150 155
151 156 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
152 157 """Save an existing notebook by notebook_id."""
153 158 if format not in self.allowed_formats:
154 159 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
155 160
156 161 try:
157 162 nb = current.reads(data.decode('utf-8'), format)
158 163 except:
159 164 raise web.HTTPError(400, u'Invalid JSON data')
160 165
161 166 if name is not None:
162 167 nb.metadata.name = name
163 168 self.write_notebook_object(nb, notebook_id)
164 169
165 170 def write_notebook_object(self, nb, notebook_id=None):
166 171 """Write a notebook object and return its notebook_id.
167 172
168 173 If notebook_id is None, this method should create a new notebook_id.
169 174 If notebook_id is not None, this method should check to make sure it
170 175 exists and is valid.
171 176 """
172 177 raise NotImplementedError('must be implemented in a subclass')
173 178
174 179 def delete_notebook(self, notebook_id):
175 180 """Delete notebook by notebook_id."""
176 181 raise NotImplementedError('must be implemented in a subclass')
177 182
178 183 def increment_filename(self, name):
179 184 """Increment a filename to make it unique.
180 185
181 186 This exists for notebook stores that must have unique names. When a notebook
182 187 is created or copied this method constructs a unique filename, typically
183 188 by appending an integer to the name.
184 189 """
185 190 return name
186 191
187 192 def new_notebook(self):
188 193 """Create a new notebook and return its notebook_id."""
189 194 name = self.increment_filename('Untitled')
190 195 metadata = current.new_metadata(name=name)
191 196 nb = current.new_notebook(metadata=metadata)
192 197 notebook_id = self.write_notebook_object(nb)
193 198 return notebook_id
194 199
195 200 def copy_notebook(self, notebook_id):
196 201 """Copy an existing notebook and return its notebook_id."""
197 202 last_mod, nb = self.read_notebook_object(notebook_id)
198 203 name = nb.metadata.name + '-Copy'
199 204 name = self.increment_filename(name)
200 205 nb.metadata.name = name
201 206 notebook_id = self.write_notebook_object(nb)
202 207 return notebook_id
203 208
204 209 def log_info(self):
205 210 self.log.info("Serving notebooks") No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now