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