##// END OF EJS Templates
standard model changes
Zachary Sailer -
Show More
@@ -1,97 +1,102 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view.
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 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler
23 from ..base.handlers import IPythonHandler
24 from ..utils import url_path_join
24 from ..utils import url_path_join
25 from urllib import quote
25 from urllib import quote
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Handlers
28 # Handlers
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class NewPathHandler(IPythonHandler):
32 class NewPathHandler(IPythonHandler):
33
33
34 @web.authenticated
34 @web.authenticated
35 def get(self, notebook_path):
35 def get(self, notebook_path):
36 notebook_name = self.notebook_manager.new_notebook(notebook_path)
36 notebook_name = self.notebook_manager.new_notebook(notebook_path)
37 self.redirect(url_path_join(self.base_project_url,"notebooks", notebook_path, notebook_name))
37 self.redirect(url_path_join(self.base_project_url,"notebooks", notebook_path, notebook_name))
38
38
39
39
40 class NewHandler(IPythonHandler):
40 class NewHandler(IPythonHandler):
41
41
42 @web.authenticated
42 @web.authenticated
43 def get(self):
43 def get(self):
44 notebook_name = self.notebook_manager.new_notebook()
44 notebook_name = self.notebook_manager.new_notebook()
45 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
45 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
46
46
47
47
48 class NamedNotebookHandler(IPythonHandler):
48 class NamedNotebookHandler(IPythonHandler):
49
49
50 @web.authenticated
50 @web.authenticated
51 def get(self, notebook_path):
51 def get(self, notebook_path):
52 nbm = self.notebook_manager
52 nbm = self.notebook_manager
53 name, path = nbm.named_notebook_path(notebook_path)
53 name, path = nbm.named_notebook_path(notebook_path)
54 if name != None:
54 if name != None:
55 name = quote(name)
55 name = quote(name)
56 if path == None:
56 if path == None:
57 project = self.project + '/' + name
57 project = self.project + '/' + name
58 else:
58 else:
59 project = self.project + '/' + path +'/'+ name
59 project = self.project + '/' + path +'/'+ name
60 if not nbm.notebook_exists(notebook_path):
60 if not nbm.notebook_exists(notebook_path):
61 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
61 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
62 self.write(self.render_template('notebook.html',
62 self.write(self.render_template('notebook.html',
63 project=project,
63 project=project,
64 notebook_path=path,
64 notebook_path=path,
65 notebook_name=name,
65 notebook_name=name,
66 kill_kernel=False,
66 kill_kernel=False,
67 mathjax_url=self.mathjax_url,
67 mathjax_url=self.mathjax_url,
68 )
68 )
69 )
69 )
70
71 @web.authenticated
72 def post(self, notebook_path):
73 nbm =self.notebook_manager
74 notebook_name = nbm.new_notebook()
70
75
71
76
72 class NotebookCopyHandler(IPythonHandler):
77 class NotebookCopyHandler(IPythonHandler):
73
78
74 @web.authenticated
79 @web.authenticated
75 def get(self, notebook_path=None):
80 def get(self, notebook_path=None):
76 nbm = self.notebook_manager
81 nbm = self.notebook_manager
77 name, path = nbm.named_notebook_path(notebook_path)
82 name, path = nbm.named_notebook_path(notebook_path)
78 notebook_name = self.notebook_manager.copy_notebook(name, path)
83 notebook_name = self.notebook_manager.copy_notebook(name, path)
79 if path==None:
84 if path==None:
80 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
85 self.redirect(url_path_join(self.base_project_url, "notebooks", notebook_name))
81 else:
86 else:
82 self.redirect(url_path_join(self.base_project_url, "notebooks", path, notebook_name))
87 self.redirect(url_path_join(self.base_project_url, "notebooks", path, notebook_name))
83
88
84
89
85 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
86 # URL to handler mappings
91 # URL to handler mappings
87 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
88
93
89
94
90 _notebook_path_regex = r"(?P<notebook_path>.+)"
95 _notebook_path_regex = r"(?P<notebook_path>.+)"
91
96
92 default_handlers = [
97 default_handlers = [
93 (r"/notebooks/%s/new" % _notebook_path_regex, NewPathHandler),
98 (r"/notebooks/%s/new" % _notebook_path_regex, NewPathHandler),
94 (r"/notebooks/new", NewHandler),
99 (r"/notebooks/new", NewHandler),
95 (r"/notebooks/%s/copy" % _notebook_path_regex, NotebookCopyHandler),
100 (r"/notebooks/%s/copy" % _notebook_path_regex, NotebookCopyHandler),
96 (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler)
101 (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler)
97 ]
102 ]
@@ -1,102 +1,104 b''
1 """A base class contents manager.
1 """A base class contents manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2013 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 datetime
19 import datetime
20 import io
20 import io
21 import os
21 import os
22 import glob
22 import glob
23 import shutil
23 import shutil
24 import ast
24 import ast
25 import base64
25 import base64
26
26
27 from tornado import web
27 from tornado import web
28
28
29 from IPython.config.configurable import LoggingConfigurable
29 from IPython.config.configurable import LoggingConfigurable
30 from IPython.nbformat import current
30 from IPython.nbformat import current
31 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
31 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
32 from IPython.utils import tz
32 from IPython.utils import tz
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Classes
35 # Classes
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class ContentManager(LoggingConfigurable):
38 class ContentManager(LoggingConfigurable):
39
39
40 content_dir = Unicode(os.getcwdu(), config=True, help="""
40 content_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for contents.
41 The directory to use for contents.
42 """)
42 """)
43
43
44 contents = List()
44 contents = List()
45
45
46 def get_content_names(self, content_path):
46 def get_content_names(self, content_path):
47 """List of dicts of files in content_path"""
47 names = glob.glob(os.path.join(self.content_dir, content_path,'*'))
48 names = glob.glob(os.path.join(self.content_dir, content_path,'*'))
48 content_names = list()
49 contents = list()
49 dir_names = list()
50 dirs = list()
51 notebooks = list()
50 for name in names:
52 for name in names:
51 if os.path.isdir(name) == True:
53 if os.path.isdir(name) == True:
52 dir_names.append(os.path.split(name)[1])
54 dirs.append(os.path.split(name)[1])
53 elif os.path.splitext(os.path.basename(name))[1] != '.ipynb':
55 elif os.path.splitext(name)[1] == '.ipynb':
54 content_names.append(os.path.split(name)[1])
56 notebooks.append(os.path.split(name)[1])
55 return dir_names, content_names
57 else:
56
58 contents.append(os.path.split(name)[1])
59 return dirs, notebooks, contents
60
57 def list_contents(self, content_path):
61 def list_contents(self, content_path):
58 """List all contents in the named path."""
62 """List all contents in the named path."""
59 dir_names, content_names = self.get_content_names(content_path)
63 dir_names, notebook_names, content_names = self.get_content_names(content_path)
60 content_mapping = []
64 content_mapping = []
61 for name in dir_names:
65 for name in dir_names:
62 model = self.directory_model(name, content_path)
66 model = self.content_model(name, content_path, type='dir')
63 content_mapping.append(model)
67 content_mapping.append(model)
64 for name in content_names:
68 for name in content_names:
65 model = self.content_model(name, content_path)
69 model = self.content_model(name, content_path, type='file')
70 content_mapping.append(model)
71 for name in notebook_names:
72 model = self.content_model(name, content_path, type='notebook')
66 content_mapping.append(model)
73 content_mapping.append(model)
67 return content_mapping
74 return content_mapping
68
75
69 def get_path_by_name(self, name, content_path):
76 def get_path_by_name(self, name, content_path):
70 """Return a full path to content"""
77 """Return a full path to content"""
71 path = os.path.join(self.content_dir, content_path, name)
78 path = os.path.join(self.content_dir, content_path, name)
72 return path
79 return path
73
80
74 def read_content(self, name, content_path):
81 def content_info(self, name, content_path):
82 """Read the content of a named file"""
75 file_type = os.path.splitext(os.path.basename(name))[1]
83 file_type = os.path.splitext(os.path.basename(name))[1]
76 #Collect contents of file
77 with open(name, 'rb') as file_content:
78 contents = file_content.read()
79 full_path = self.get_path_by_name(name, content_path)
84 full_path = self.get_path_by_name(name, content_path)
80 info = os.stat(full_path)
85 info = os.stat(full_path)
81 size = info.st_size
86 size = info.st_size
82 last_modified = tz.utcfromtimestamp(info.st_mtime)
87 last_modified = tz.utcfromtimestamp(info.st_mtime)
83 return last_modified, file_type, contents, size
88 return last_modified, file_type, size
84
85 def directory_model(self, name, content_path):
86 model = {"name": name,
87 "path": content_path,
88 "type": 'tree'}
89 return model
90
89
91 def content_model(self, name, content_path):
90 def content_model(self, name, content_path, type=None):
92 last_modified, file_type, contents, size = self.read_content(name, content_path)
91 """Create a dict standard model for any file (other than notebooks)"""
92 last_modified, file_type, size = self.content_info(name, content_path)
93 model = {"name": name,
93 model = {"name": name,
94 "path": content_path,
94 "path": content_path,
95 "type": file_type,
95 "type": type,
96 "MIME-type": "",
96 "last_modified": last_modified.ctime(),
97 "last_modified": last_modified.ctime(),
97 "size": size}
98 "size": size}
98 return model
99 return model
99
100
100 def delete_content(self, content_path):
101 def delete_content(self, content_path):
102 """Delete a file"""
101 os.unlink(os.path.join(self.content_dir, content_path))
103 os.unlink(os.path.join(self.content_dir, content_path))
102 No newline at end of file
104
@@ -1,68 +1,68 b''
1 """Tornado handlers for the contents web service.
1 """Tornado handlers for the contents web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2013 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 from tornado import web
19 from tornado import web
20
20
21 from zmq.utils import jsonapi
21 from zmq.utils import jsonapi
22
22
23 from IPython.utils.jsonutil import date_default
23 from IPython.utils.jsonutil import date_default
24
24
25 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
25 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Contents web service handlers
28 # Contents web service handlers
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class ContentRootHandler(IPythonHandler):
32 class ContentRootHandler(IPythonHandler):
33
33
34 @authenticate_unless_readonly
34 @authenticate_unless_readonly
35 def get(self):
35 def get(self):
36 cm = self.content_manager
36 cm = self.content_manager
37 contents = cm.list_contents("")
37 contents = cm.list_contents("")
38 self.finish(jsonapi.dumps(contents))
38 self.finish(jsonapi.dumps(contents))
39
39
40
40
41 class ContentHandler(IPythonHandler):
41 class ContentHandler(IPythonHandler):
42
42
43 @web.authenticated
43 @web.authenticated
44 def get(self, content_path):
44 def get(self, content_path):
45 cm = self.content_manager
45 cm = self.content_manager
46 contents = cm.list_contents(content_path)
46 contents = cm.list_contents(content_path)
47 self.finish(jsonapi.dumps(contents))
47 self.finish(jsonapi.dumps(contents))
48
48
49 @web.authenticated
49 @web.authenticated
50 def delete(self, content_path):
50 def delete(self, content_path):
51 cm = self.content_manager
51 cm = self.content_manager
52 cm.delete_content(content_path)
52 cm.delete_content(content_path)
53 self.set_status(204)
53 self.set_status(204)
54 self.finish()
54 self.finish()
55
55
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # URL to handler mappings
58 # URL to handler mappings
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 _content_path_regex = r"(?P<content_path>.+)"
61 _content_path_regex = r"(?P<content_path>.+)"
62
62
63 default_handlers = [
63 default_handlers = [
64 (r"api/contents/%s" % _content_path_regex, ContentHandler),
64 (r"api/contents/%s" % _content_path_regex, ContentHandler),
65 (r"api/contents", ContentRootHandler)
65 (r"api/contents", ContentRootHandler)
66 ]
66 ]
67
67
68
68
@@ -1,345 +1,345 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
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 datetime
19 import datetime
20 import io
20 import io
21 import os
21 import os
22 import glob
22 import glob
23 import shutil
23 import shutil
24 import ast
24 import ast
25
25
26 from unicodedata import normalize
26 from unicodedata import normalize
27
27
28 from tornado import web
28 from tornado import web
29
29
30 from .nbmanager import NotebookManager
30 from .nbmanager import NotebookManager
31 from IPython.nbformat import current
31 from IPython.nbformat import current
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
33 from IPython.utils import tz
33 from IPython.utils import tz
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes
36 # Classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class FileNotebookManager(NotebookManager):
39 class FileNotebookManager(NotebookManager):
40
40
41 save_script = Bool(False, config=True,
41 save_script = Bool(False, config=True,
42 help="""Automatically create a Python script when saving the notebook.
42 help="""Automatically create a Python script when saving the notebook.
43
43
44 For easier use of import, %run and %load across notebooks, a
44 For easier use of import, %run and %load across notebooks, a
45 <notebook-name>.py script will be created next to any
45 <notebook-name>.py script will be created next to any
46 <notebook-name>.ipynb on each save. This can also be set with the
46 <notebook-name>.ipynb on each save. This can also be set with the
47 short `--script` flag.
47 short `--script` flag.
48 """
48 """
49 )
49 )
50
50
51 checkpoint_dir = Unicode(config=True,
51 checkpoint_dir = Unicode(config=True,
52 help="""The location in which to keep notebook checkpoints
52 help="""The location in which to keep notebook checkpoints
53
53
54 By default, it is notebook-dir/.ipynb_checkpoints
54 By default, it is notebook-dir/.ipynb_checkpoints
55 """
55 """
56 )
56 )
57 def _checkpoint_dir_default(self):
57 def _checkpoint_dir_default(self):
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
59
59
60 def _checkpoint_dir_changed(self, name, old, new):
60 def _checkpoint_dir_changed(self, name, old, new):
61 """do a bit of validation of the checkpoint dir"""
61 """do a bit of validation of the checkpoint dir"""
62 if not os.path.isabs(new):
62 if not os.path.isabs(new):
63 # If we receive a non-absolute path, make it absolute.
63 # If we receive a non-absolute path, make it absolute.
64 abs_new = os.path.abspath(new)
64 abs_new = os.path.abspath(new)
65 self.checkpoint_dir = abs_new
65 self.checkpoint_dir = abs_new
66 return
66 return
67 if os.path.exists(new) and not os.path.isdir(new):
67 if os.path.exists(new) and not os.path.isdir(new):
68 raise TraitError("checkpoint dir %r is not a directory" % new)
68 raise TraitError("checkpoint dir %r is not a directory" % new)
69 if not os.path.exists(new):
69 if not os.path.exists(new):
70 self.log.info("Creating checkpoint dir %s", new)
70 self.log.info("Creating checkpoint dir %s", new)
71 try:
71 try:
72 os.mkdir(new)
72 os.mkdir(new)
73 except:
73 except:
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75
75
76 filename_ext = Unicode(u'.ipynb')
76 filename_ext = Unicode(u'.ipynb')
77
77
78
78
79 def get_notebook_names(self, path):
79 def get_notebook_names(self, path):
80 """List all notebook names in the notebook dir."""
80 """List all notebook names in the notebook dir."""
81 names = glob.glob(os.path.join(self.notebook_dir, path,
81 names = glob.glob(os.path.join(self.notebook_dir, path,
82 '*' + self.filename_ext))
82 '*' + self.filename_ext))
83 names = [os.path.basename(name)
83 names = [os.path.basename(name)
84 for name in names]
84 for name in names]
85 return names
85 return names
86
86
87 def list_notebooks(self, path):
87 def list_notebooks(self, path):
88 """List all notebooks in the notebook dir."""
88 """List all notebooks in the notebook dir."""
89 notebook_names = self.get_notebook_names(path)
89 notebook_names = self.get_notebook_names(path)
90 notebook_mapping = []
90 notebook_mapping = []
91 for name in notebook_names:
91 for name in notebook_names:
92 model = self.notebook_model(name, path)
92 model = self.notebook_model(name, path, content=False)
93 notebook_mapping.append(model)
93 notebook_mapping.append(model)
94 return notebook_mapping
94 return notebook_mapping
95
95
96 def change_notebook(self, data, notebook_name, notebook_path=None):
96 def change_notebook(self, data, notebook_name, notebook_path=None):
97 """Changes notebook"""
97 """Changes notebook"""
98 changes = data.keys()
98 changes = data.keys()
99 for change in changes:
99 for change in changes:
100 full_path = self.get_path(notebook_name, notebook_path)
100 full_path = self.get_path(notebook_name, notebook_path)
101 if change == "notebook_name":
101 if change == "notebook_name":
102 os.rename(full_path,
102 os.rename(full_path,
103 self.get_path(data['notebook_name'], notebook_path))
103 self.get_path(data['notebook_name'], notebook_path))
104 notebook_name = data['notebook_name']
104 notebook_name = data['notebook_name']
105 if change == "notebook_path":
105 if change == "notebook_path":
106 new_path = self.get_path(data['notebook_name'], data['notebook_path'])
106 new_path = self.get_path(data['notebook_name'], data['notebook_path'])
107 stutil.move(full_path, new_path)
107 stutil.move(full_path, new_path)
108 notebook_path = data['notebook_path']
108 notebook_path = data['notebook_path']
109 if change == "content":
109 if change == "content":
110 self.save_notebook(data, notebook_name, notebook_path)
110 self.save_notebook(data, notebook_name, notebook_path)
111 model = self.notebook_model(notebook_name, notebook_path)
111 model = self.notebook_model(notebook_name, notebook_path)
112 return model
112 return model
113
113
114 def notebook_exists(self, notebook_path):
114 def notebook_exists(self, notebook_path):
115 """Does a notebook exist?"""
115 """Does a notebook exist?"""
116 return os.path.isfile(notebook_path)
116 return os.path.isfile(notebook_path)
117
117
118 def get_path(self, notebook_name, notebook_path=None):
118 def get_path(self, notebook_name, notebook_path=None):
119 """Return a full path to a notebook given its notebook_name."""
119 """Return a full path to a notebook given its notebook_name."""
120 return self.get_path_by_name(notebook_name, notebook_path)
120 return self.get_path_by_name(notebook_name, notebook_path)
121
121
122 def get_path_by_name(self, name, notebook_path=None):
122 def get_path_by_name(self, name, notebook_path=None):
123 """Return a full path to a notebook given its name."""
123 """Return a full path to a notebook given its name."""
124 filename = name #+ self.filename_ext
124 filename = name #+ self.filename_ext
125 if notebook_path == None:
125 if notebook_path == None:
126 path = os.path.join(self.notebook_dir, filename)
126 path = os.path.join(self.notebook_dir, filename)
127 else:
127 else:
128 path = os.path.join(self.notebook_dir, notebook_path, filename)
128 path = os.path.join(self.notebook_dir, notebook_path, filename)
129 return path
129 return path
130
130
131 def read_notebook_object_from_path(self, path):
131 def read_notebook_object_from_path(self, path):
132 """read a notebook object from a path"""
132 """read a notebook object from a path"""
133 info = os.stat(path)
133 info = os.stat(path)
134 last_modified = tz.utcfromtimestamp(info.st_mtime)
134 last_modified = tz.utcfromtimestamp(info.st_mtime)
135 with open(path,'r') as f:
135 with open(path,'r') as f:
136 s = f.read()
136 s = f.read()
137 try:
137 try:
138 # v1 and v2 and json in the .ipynb files.
138 # v1 and v2 and json in the .ipynb files.
139 nb = current.reads(s, u'json')
139 nb = current.reads(s, u'json')
140 except ValueError as e:
140 except ValueError as e:
141 msg = u"Unreadable Notebook: %s" % e
141 msg = u"Unreadable Notebook: %s" % e
142 raise web.HTTPError(400, msg, reason=msg)
142 raise web.HTTPError(400, msg, reason=msg)
143 return last_modified, nb
143 return last_modified, nb
144
144
145 def read_notebook_object(self, notebook_name, notebook_path):
145 def read_notebook_object(self, notebook_name, notebook_path):
146 """Get the Notebook representation of a notebook by notebook_name."""
146 """Get the Notebook representation of a notebook by notebook_name."""
147 path = self.get_path(notebook_name, notebook_path)
147 path = self.get_path(notebook_name, notebook_path)
148 if not os.path.isfile(path):
148 if not os.path.isfile(path):
149 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
149 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
150 last_modified, nb = self.read_notebook_object_from_path(path)
150 last_modified, nb = self.read_notebook_object_from_path(path)
151 # Always use the filename as the notebook name.
151 # Always use the filename as the notebook name.
152 # Eventually we will get rid of the notebook name in the metadata
152 # Eventually we will get rid of the notebook name in the metadata
153 # but for now, that name is just an empty string. Until the notebooks
153 # but for now, that name is just an empty string. Until the notebooks
154 # web service knows about names in URLs we still pass the name
154 # web service knows about names in URLs we still pass the name
155 # back to the web app using the metadata though.
155 # back to the web app using the metadata though.
156 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
156 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
157 return last_modified, nb
157 return last_modified, nb
158
158
159 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name= None):
159 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name= None):
160 """Save an existing notebook object by notebook_name."""
160 """Save an existing notebook object by notebook_name."""
161 if new_name == None:
161 if new_name == None:
162 try:
162 try:
163 new_name = normalize('NFC', nb.metadata.name)
163 new_name = normalize('NFC', nb.metadata.name)
164 except AttributeError:
164 except AttributeError:
165 raise web.HTTPError(400, u'Missing notebook name')
165 raise web.HTTPError(400, u'Missing notebook name')
166
166
167 new_path = notebook_path
167 new_path = notebook_path
168 old_name = notebook_name
168 old_name = notebook_name
169 old_checkpoints = self.list_checkpoints(old_name)
169 old_checkpoints = self.list_checkpoints(old_name)
170
170
171 path = self.get_path_by_name(new_name, new_path)
171 path = self.get_path_by_name(new_name, new_path)
172
172
173 # Right before we save the notebook, we write an empty string as the
173 # Right before we save the notebook, we write an empty string as the
174 # notebook name in the metadata. This is to prepare for removing
174 # notebook name in the metadata. This is to prepare for removing
175 # this attribute entirely post 1.0. The web app still uses the metadata
175 # this attribute entirely post 1.0. The web app still uses the metadata
176 # name for now.
176 # name for now.
177 nb.metadata.name = u''
177 nb.metadata.name = u''
178
178
179 try:
179 try:
180 self.log.debug("Autosaving notebook %s", path)
180 self.log.debug("Autosaving notebook %s", path)
181 with open(path,'w') as f:
181 with open(path,'w') as f:
182 current.write(nb, f, u'json')
182 current.write(nb, f, u'json')
183 except Exception as e:
183 except Exception as e:
184 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
184 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
185
185
186 # save .py script as well
186 # save .py script as well
187 if self.save_script:
187 if self.save_script:
188 pypath = os.path.splitext(path)[0] + '.py'
188 pypath = os.path.splitext(path)[0] + '.py'
189 self.log.debug("Writing script %s", pypath)
189 self.log.debug("Writing script %s", pypath)
190 try:
190 try:
191 with io.open(pypath,'w', encoding='utf-8') as f:
191 with io.open(pypath,'w', encoding='utf-8') as f:
192 current.write(nb, f, u'py')
192 current.write(nb, f, u'py')
193 except Exception as e:
193 except Exception as e:
194 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
194 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
195
195
196 if old_name != None:
196 if old_name != None:
197 # remove old files if the name changed
197 # remove old files if the name changed
198 if old_name != new_name:
198 if old_name != new_name:
199 # remove renamed original, if it exists
199 # remove renamed original, if it exists
200 old_path = self.get_path_by_name(old_name, notebook_path)
200 old_path = self.get_path_by_name(old_name, notebook_path)
201 if os.path.isfile(old_path):
201 if os.path.isfile(old_path):
202 self.log.debug("unlinking notebook %s", old_path)
202 self.log.debug("unlinking notebook %s", old_path)
203 os.unlink(old_path)
203 os.unlink(old_path)
204
204
205 # cleanup old script, if it exists
205 # cleanup old script, if it exists
206 if self.save_script:
206 if self.save_script:
207 old_pypath = os.path.splitext(old_path)[0] + '.py'
207 old_pypath = os.path.splitext(old_path)[0] + '.py'
208 if os.path.isfile(old_pypath):
208 if os.path.isfile(old_pypath):
209 self.log.debug("unlinking script %s", old_pypath)
209 self.log.debug("unlinking script %s", old_pypath)
210 os.unlink(old_pypath)
210 os.unlink(old_pypath)
211
211
212 # rename checkpoints to follow file
212 # rename checkpoints to follow file
213 for cp in old_checkpoints:
213 for cp in old_checkpoints:
214 checkpoint_id = cp['checkpoint_id']
214 checkpoint_id = cp['checkpoint_id']
215 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
215 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
216 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
216 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
217 if os.path.isfile(old_cp_path):
217 if os.path.isfile(old_cp_path):
218 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
218 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
219 os.rename(old_cp_path, new_cp_path)
219 os.rename(old_cp_path, new_cp_path)
220
220
221 return new_name
221 return new_name
222
222
223 def delete_notebook(self, notebook_name, notebook_path):
223 def delete_notebook(self, notebook_name, notebook_path):
224 """Delete notebook by notebook_name."""
224 """Delete notebook by notebook_name."""
225 nb_path = self.get_path(notebook_name, notebook_path)
225 nb_path = self.get_path(notebook_name, notebook_path)
226 if not os.path.isfile(nb_path):
226 if not os.path.isfile(nb_path):
227 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
227 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
228
228
229 # clear checkpoints
229 # clear checkpoints
230 for checkpoint in self.list_checkpoints(notebook_name):
230 for checkpoint in self.list_checkpoints(notebook_name):
231 checkpoint_id = checkpoint['checkpoint_id']
231 checkpoint_id = checkpoint['checkpoint_id']
232 path = self.get_checkpoint_path(notebook_name, checkpoint_id)
232 path = self.get_checkpoint_path(notebook_name, checkpoint_id)
233 self.log.debug(path)
233 self.log.debug(path)
234 if os.path.isfile(path):
234 if os.path.isfile(path):
235 self.log.debug("unlinking checkpoint %s", path)
235 self.log.debug("unlinking checkpoint %s", path)
236 os.unlink(path)
236 os.unlink(path)
237
237
238 self.log.debug("unlinking notebook %s", nb_path)
238 self.log.debug("unlinking notebook %s", nb_path)
239 os.unlink(nb_path)
239 os.unlink(nb_path)
240
240
241 def increment_filename(self, basename, notebook_path=None):
241 def increment_filename(self, basename, notebook_path=None):
242 """Return a non-used filename of the form basename<int>.
242 """Return a non-used filename of the form basename<int>.
243
243
244 This searches through the filenames (basename0, basename1, ...)
244 This searches through the filenames (basename0, basename1, ...)
245 until is find one that is not already being used. It is used to
245 until is find one that is not already being used. It is used to
246 create Untitled and Copy names that are unique.
246 create Untitled and Copy names that are unique.
247 """
247 """
248 i = 0
248 i = 0
249 while True:
249 while True:
250 name = u'%s%i.ipynb' % (basename,i)
250 name = u'%s%i.ipynb' % (basename,i)
251 path = self.get_path_by_name(name, notebook_path)
251 path = self.get_path_by_name(name, notebook_path)
252 if not os.path.isfile(path):
252 if not os.path.isfile(path):
253 break
253 break
254 else:
254 else:
255 i = i+1
255 i = i+1
256 return name
256 return name
257
257
258 # Checkpoint-related utilities
258 # Checkpoint-related utilities
259
259
260 def get_checkpoint_path_by_name(self, name, checkpoint_id, notebook_path=None):
260 def get_checkpoint_path_by_name(self, name, checkpoint_id, notebook_path=None):
261 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
261 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
262 filename = u"{name}-{checkpoint_id}{ext}".format(
262 filename = u"{name}-{checkpoint_id}{ext}".format(
263 name=name,
263 name=name,
264 checkpoint_id=checkpoint_id,
264 checkpoint_id=checkpoint_id,
265 ext=self.filename_ext,
265 ext=self.filename_ext,
266 )
266 )
267 if notebook_path ==None:
267 if notebook_path ==None:
268 path = os.path.join(self.checkpoint_dir, filename)
268 path = os.path.join(self.checkpoint_dir, filename)
269 else:
269 else:
270 path = os.path.join(notebook_path, self.checkpoint_dir, filename)
270 path = os.path.join(notebook_path, self.checkpoint_dir, filename)
271 return path
271 return path
272
272
273 def get_checkpoint_path(self, notebook_name, checkpoint_id, notebook_path=None):
273 def get_checkpoint_path(self, notebook_name, checkpoint_id, notebook_path=None):
274 """find the path to a checkpoint"""
274 """find the path to a checkpoint"""
275 name = notebook_name
275 name = notebook_name
276 return self.get_checkpoint_path_by_name(name, checkpoint_id, notebook_path)
276 return self.get_checkpoint_path_by_name(name, checkpoint_id, notebook_path)
277
277
278 def get_checkpoint_info(self, notebook_name, checkpoint_id, notebook_path=None):
278 def get_checkpoint_info(self, notebook_name, checkpoint_id, notebook_path=None):
279 """construct the info dict for a given checkpoint"""
279 """construct the info dict for a given checkpoint"""
280 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
280 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
281 stats = os.stat(path)
281 stats = os.stat(path)
282 last_modified = tz.utcfromtimestamp(stats.st_mtime)
282 last_modified = tz.utcfromtimestamp(stats.st_mtime)
283 info = dict(
283 info = dict(
284 checkpoint_id = checkpoint_id,
284 checkpoint_id = checkpoint_id,
285 last_modified = last_modified,
285 last_modified = last_modified,
286 )
286 )
287
287
288 return info
288 return info
289
289
290 # public checkpoint API
290 # public checkpoint API
291
291
292 def create_checkpoint(self, notebook_name, notebook_path=None):
292 def create_checkpoint(self, notebook_name, notebook_path=None):
293 """Create a checkpoint from the current state of a notebook"""
293 """Create a checkpoint from the current state of a notebook"""
294 nb_path = self.get_path(notebook_name, notebook_path)
294 nb_path = self.get_path(notebook_name, notebook_path)
295 # only the one checkpoint ID:
295 # only the one checkpoint ID:
296 checkpoint_id = u"checkpoint"
296 checkpoint_id = u"checkpoint"
297 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
297 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
298 self.log.debug("creating checkpoint for notebook %s", notebook_name)
298 self.log.debug("creating checkpoint for notebook %s", notebook_name)
299 if not os.path.exists(self.checkpoint_dir):
299 if not os.path.exists(self.checkpoint_dir):
300 os.mkdir(self.checkpoint_dir)
300 os.mkdir(self.checkpoint_dir)
301 shutil.copy2(nb_path, cp_path)
301 shutil.copy2(nb_path, cp_path)
302
302
303 # return the checkpoint info
303 # return the checkpoint info
304 return self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)
304 return self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)
305
305
306 def list_checkpoints(self, notebook_name, notebook_path=None):
306 def list_checkpoints(self, notebook_name, notebook_path=None):
307 """list the checkpoints for a given notebook
307 """list the checkpoints for a given notebook
308
308
309 This notebook manager currently only supports one checkpoint per notebook.
309 This notebook manager currently only supports one checkpoint per notebook.
310 """
310 """
311 checkpoint_id = "checkpoint"
311 checkpoint_id = "checkpoint"
312 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
312 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
313 if not os.path.exists(path):
313 if not os.path.exists(path):
314 return []
314 return []
315 else:
315 else:
316 return [self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)]
316 return [self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)]
317
317
318
318
319 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
319 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
320 """restore a notebook to a checkpointed state"""
320 """restore a notebook to a checkpointed state"""
321 self.log.info("restoring Notebook %s from checkpoint %s", notebook_name, checkpoint_id)
321 self.log.info("restoring Notebook %s from checkpoint %s", notebook_name, checkpoint_id)
322 nb_path = self.get_path(notebook_name, notebook_path)
322 nb_path = self.get_path(notebook_name, notebook_path)
323 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
323 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
324 if not os.path.isfile(cp_path):
324 if not os.path.isfile(cp_path):
325 self.log.debug("checkpoint file does not exist: %s", cp_path)
325 self.log.debug("checkpoint file does not exist: %s", cp_path)
326 raise web.HTTPError(404,
326 raise web.HTTPError(404,
327 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
327 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
328 )
328 )
329 # ensure notebook is readable (never restore from an unreadable notebook)
329 # ensure notebook is readable (never restore from an unreadable notebook)
330 last_modified, nb = self.read_notebook_object_from_path(cp_path)
330 last_modified, nb = self.read_notebook_object_from_path(cp_path)
331 shutil.copy2(cp_path, nb_path)
331 shutil.copy2(cp_path, nb_path)
332 self.log.debug("copying %s -> %s", cp_path, nb_path)
332 self.log.debug("copying %s -> %s", cp_path, nb_path)
333
333
334 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
334 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
335 """delete a notebook's checkpoint"""
335 """delete a notebook's checkpoint"""
336 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
336 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
337 if not os.path.isfile(path):
337 if not os.path.isfile(path):
338 raise web.HTTPError(404,
338 raise web.HTTPError(404,
339 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
339 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
340 )
340 )
341 self.log.debug("unlinking %s", path)
341 self.log.debug("unlinking %s", path)
342 os.unlink(path)
342 os.unlink(path)
343
343
344 def info_string(self):
344 def info_string(self):
345 return "Serving notebooks from local directory: %s" % self.notebook_dir
345 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,247 +1,249 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 disentangle all of this.
39 # we are going to have to disentangle 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
43
44 def named_notebook_path(self, notebook_path):
44 def named_notebook_path(self, notebook_path):
45
45
46 l = len(notebook_path)
46 l = len(notebook_path)
47 names = notebook_path.split('/')
47 names = notebook_path.split('/')
48 if len(names) > 1:
48 if len(names) > 1:
49 name = names[len(names)-1]
49 name = names[len(names)-1]
50 if name[(len(name)-6):(len(name))] == ".ipynb":
50 if name[(len(name)-6):(len(name))] == ".ipynb":
51 name = name
51 name = name
52 path = notebook_path[0:l-len(name)-1]+'/'
52 path = notebook_path[0:l-len(name)-1]+'/'
53 else:
53 else:
54 name = None
54 name = None
55 path = notebook_path+'/'
55 path = notebook_path+'/'
56 else:
56 else:
57 name = names[0]
57 name = names[0]
58 if name[(len(name)-6):(len(name))] == ".ipynb":
58 if name[(len(name)-6):(len(name))] == ".ipynb":
59 name = name
59 name = name
60 path = None
60 path = None
61 else:
61 else:
62 name = None
62 name = None
63 path = notebook_path+'/'
63 path = notebook_path+'/'
64 return name, path
64 return name, path
65
65
66 def _notebook_dir_changed(self, new):
66 def _notebook_dir_changed(self, new):
67 """do a bit of validation of the notebook dir"""
67 """do a bit of validation of the notebook dir"""
68 if not os.path.isabs(new):
68 if not os.path.isabs(new):
69 # If we receive a non-absolute path, make it absolute.
69 # If we receive a non-absolute path, make it absolute.
70 abs_new = os.path.abspath(new)
70 abs_new = os.path.abspath(new)
71 #self.notebook_dir = os.path.dirname(abs_new)
71 #self.notebook_dir = os.path.dirname(abs_new)
72 return
72 return
73 if os.path.exists(new) and not os.path.isdir(new):
73 if os.path.exists(new) and not os.path.isdir(new):
74 raise TraitError("notebook dir %r is not a directory" % new)
74 raise TraitError("notebook dir %r is not a directory" % new)
75 if not os.path.exists(new):
75 if not os.path.exists(new):
76 self.log.info("Creating notebook dir %s", new)
76 self.log.info("Creating notebook dir %s", new)
77 try:
77 try:
78 os.mkdir(new)
78 os.mkdir(new)
79 except:
79 except:
80 raise TraitError("Couldn't create notebook dir %r" % new)
80 raise TraitError("Couldn't create notebook dir %r" % new)
81
81
82 allowed_formats = List([u'json',u'py'])
82 allowed_formats = List([u'json',u'py'])
83
83
84 def add_new_folder(self, path=None):
84 def add_new_folder(self, path=None):
85 new_path = os.path.join(self.notebook_dir, path)
85 new_path = os.path.join(self.notebook_dir, path)
86 if not os.path.exists(new_path):
86 if not os.path.exists(new_path):
87 os.makedirs(new_path)
87 os.makedirs(new_path)
88 else:
88 else:
89 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
89 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
90
90
91 def load_notebook_names(self, path):
91 def load_notebook_names(self, path):
92 """Load the notebook names into memory.
92 """Load the notebook names into memory.
93
93
94 This should be called once immediately after the notebook manager
94 This should be called once immediately after the notebook manager
95 is created to load the existing notebooks into the mapping in
95 is created to load the existing notebooks into the mapping in
96 memory.
96 memory.
97 """
97 """
98 self.list_notebooks(path)
98 self.list_notebooks(path)
99
99
100 def list_notebooks(self):
100 def list_notebooks(self):
101 """List all notebooks.
101 """List all notebooks.
102
102
103 This returns a list of dicts, each of the form::
103 This returns a list of dicts, each of the form::
104
104
105 dict(notebook_id=notebook,name=name)
105 dict(notebook_id=notebook,name=name)
106
106
107 This list of dicts should be sorted by name::
107 This list of dicts should be sorted by name::
108
108
109 data = sorted(data, key=lambda item: item['name'])
109 data = sorted(data, key=lambda item: item['name'])
110 """
110 """
111 raise NotImplementedError('must be implemented in a subclass')
111 raise NotImplementedError('must be implemented in a subclass')
112
112
113
113
114 def notebook_exists(self, notebook_path):
114 def notebook_exists(self, notebook_path):
115 """Does a notebook exist?"""
115 """Does a notebook exist?"""
116
116
117 def notebook_model(self, notebook_name, notebook_path=None):
117
118 def notebook_model(self, notebook_name, notebook_path=None, content=True):
118 """ Creates the standard notebook model """
119 """ Creates the standard notebook model """
119 last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
120 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
120 model = {"notebook_name": notebook_name,
121 model = {"notebook_name": notebook_name,
121 "notebook_path": notebook_path,
122 "notebook_path": notebook_path,
122 "content": content,
123 "last_modified": last_modified.ctime()}
123 "last_modified": last_modified.ctime()}
124 if content == True:
125 model['content'] = contents
124 return model
126 return model
125
127
126 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
128 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
127 """Get the representation of a notebook in format by notebook_name."""
129 """Get the representation of a notebook in format by notebook_name."""
128 format = unicode(format)
130 format = unicode(format)
129 if format not in self.allowed_formats:
131 if format not in self.allowed_formats:
130 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
132 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
131 kwargs = {}
133 kwargs = {}
132 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
134 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
133 if format == 'json':
135 if format == 'json':
134 # don't split lines for sending over the wire, because it
136 # don't split lines for sending over the wire, because it
135 # should match the Python in-memory format.
137 # should match the Python in-memory format.
136 kwargs['split_lines'] = False
138 kwargs['split_lines'] = False
137 representation = current.writes(nb, format, **kwargs)
139 representation = current.writes(nb, format, **kwargs)
138 name = nb.metadata.get('name', 'notebook')
140 name = nb.metadata.get('name', 'notebook')
139 return last_mod, representation, name
141 return last_mod, representation, name
140
142
141 def read_notebook_object(self, notebook_name, notebook_path):
143 def read_notebook_object(self, notebook_name, notebook_path):
142 """Get the object representation of a notebook by notebook_id."""
144 """Get the object representation of a notebook by notebook_id."""
143 raise NotImplementedError('must be implemented in a subclass')
145 raise NotImplementedError('must be implemented in a subclass')
144
146
145 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
147 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
146 """Save a new notebook and return its notebook_id.
148 """Save a new notebook and return its name.
147
149
148 If a name is passed in, it overrides any values in the notebook data
150 If a name is passed in, it overrides any values in the notebook data
149 and the value in the data is updated to use that value.
151 and the value in the data is updated to use that value.
150 """
152 """
151 if format not in self.allowed_formats:
153 if format not in self.allowed_formats:
152 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
154 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
153
155
154 try:
156 try:
155 nb = current.reads(data.decode('utf-8'), format)
157 nb = current.reads(data.decode('utf-8'), format)
156 except:
158 except:
157 raise web.HTTPError(400, u'Invalid JSON data')
159 raise web.HTTPError(400, u'Invalid JSON data')
158
160
159 if name is None:
161 if name is None:
160 try:
162 try:
161 name = nb.metadata.name
163 name = nb.metadata.name
162 except AttributeError:
164 except AttributeError:
163 raise web.HTTPError(400, u'Missing notebook name')
165 raise web.HTTPError(400, u'Missing notebook name')
164 nb.metadata.name = name
166 nb.metadata.name = name
165
167
166 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
168 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
167 return notebook_name
169 return notebook_name
168
170
169 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
171 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
170 """Save an existing notebook by notebook_name."""
172 """Save an existing notebook by notebook_name."""
171 if format not in self.allowed_formats:
173 if format not in self.allowed_formats:
172 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
174 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
173
175
174 try:
176 try:
175 nb = current.reads(data.decode('utf-8'), format)
177 nb = current.reads(data.decode('utf-8'), format)
176 except:
178 except:
177 raise web.HTTPError(400, u'Invalid JSON data')
179 raise web.HTTPError(400, u'Invalid JSON data')
178
180
179 if name is not None:
181 if name is not None:
180 nb.metadata.name = name
182 nb.metadata.name = name
181 self.write_notebook_object(nb, name, notebook_path, new_name)
183 self.write_notebook_object(nb, name, notebook_path, new_name)
182
184
183 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
185 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
184 """Write a notebook object and return its notebook_name.
186 """Write a notebook object and return its notebook_name.
185
187
186 If notebook_name is None, this method should create a new notebook_name.
188 If notebook_name is None, this method should create a new notebook_name.
187 If notebook_name is not None, this method should check to make sure it
189 If notebook_name is not None, this method should check to make sure it
188 exists and is valid.
190 exists and is valid.
189 """
191 """
190 raise NotImplementedError('must be implemented in a subclass')
192 raise NotImplementedError('must be implemented in a subclass')
191
193
192 def delete_notebook(self, notebook_name, notebook_path):
194 def delete_notebook(self, notebook_name, notebook_path):
193 """Delete notebook by notebook_id."""
195 """Delete notebook by notebook_id."""
194 raise NotImplementedError('must be implemented in a subclass')
196 raise NotImplementedError('must be implemented in a subclass')
195
197
196 def increment_filename(self, name):
198 def increment_filename(self, name):
197 """Increment a filename to make it unique.
199 """Increment a filename to make it unique.
198
200
199 This exists for notebook stores that must have unique names. When a notebook
201 This exists for notebook stores that must have unique names. When a notebook
200 is created or copied this method constructs a unique filename, typically
202 is created or copied this method constructs a unique filename, typically
201 by appending an integer to the name.
203 by appending an integer to the name.
202 """
204 """
203 return name
205 return name
204
206
205 def new_notebook(self, notebook_path=None):
207 def new_notebook(self, notebook_path=None):
206 """Create a new notebook and return its notebook_id."""
208 """Create a new notebook and return its notebook_id."""
207 name = self.increment_filename('Untitled', notebook_path)
209 name = self.increment_filename('Untitled', notebook_path)
208 metadata = current.new_metadata(name=name)
210 metadata = current.new_metadata(name=name)
209 nb = current.new_notebook(metadata=metadata)
211 nb = current.new_notebook(metadata=metadata)
210 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
212 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
211 return notebook_name
213 return notebook_name
212
214
213 def copy_notebook(self, name, path):
215 def copy_notebook(self, name, path):
214 """Copy an existing notebook and return its notebook_id."""
216 """Copy an existing notebook and return its notebook_id."""
215 last_mod, nb = self.read_notebook_object(name, path)
217 last_mod, nb = self.read_notebook_object(name, path)
216 name = nb.metadata.name + '-Copy'
218 name = nb.metadata.name + '-Copy'
217 name = self.increment_filename(name, path)
219 name = self.increment_filename(name, path)
218 nb.metadata.name = name
220 nb.metadata.name = name
219 notebook_name = self.write_notebook_object(nb, notebook_path = path)
221 notebook_name = self.write_notebook_object(nb, notebook_path = path)
220 return notebook_name
222 return notebook_name
221
223
222 # Checkpoint-related
224 # Checkpoint-related
223
225
224 def create_checkpoint(self, notebook_name, notebook_path=None):
226 def create_checkpoint(self, notebook_name, notebook_path=None):
225 """Create a checkpoint of the current state of a notebook
227 """Create a checkpoint of the current state of a notebook
226
228
227 Returns a checkpoint_id for the new checkpoint.
229 Returns a checkpoint_id for the new checkpoint.
228 """
230 """
229 raise NotImplementedError("must be implemented in a subclass")
231 raise NotImplementedError("must be implemented in a subclass")
230
232
231 def list_checkpoints(self, notebook_name, notebook_path=None):
233 def list_checkpoints(self, notebook_name, notebook_path=None):
232 """Return a list of checkpoints for a given notebook"""
234 """Return a list of checkpoints for a given notebook"""
233 return []
235 return []
234
236
235 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
237 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
236 """Restore a notebook from one of its checkpoints"""
238 """Restore a notebook from one of its checkpoints"""
237 raise NotImplementedError("must be implemented in a subclass")
239 raise NotImplementedError("must be implemented in a subclass")
238
240
239 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
241 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
240 """delete a checkpoint for a notebook"""
242 """delete a checkpoint for a notebook"""
241 raise NotImplementedError("must be implemented in a subclass")
243 raise NotImplementedError("must be implemented in a subclass")
242
244
243 def log_info(self):
245 def log_info(self):
244 self.log.info(self.info_string())
246 self.log.info(self.info_string())
245
247
246 def info_string(self):
248 def info_string(self):
247 return "Serving notebooks"
249 return "Serving notebooks"
@@ -1,104 +1,104 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the sessions web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2013 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 from tornado import web
19 from tornado import web
20
20
21 from zmq.utils import jsonapi
21 from zmq.utils import jsonapi
22
22
23 from IPython.utils.jsonutil import date_default
23 from IPython.utils.jsonutil import date_default
24 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
24 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Session web service handlers
27 # Session web service handlers
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31
31
32 class SessionRootHandler(IPythonHandler):
32 class SessionRootHandler(IPythonHandler):
33
33
34 @authenticate_unless_readonly
34 @authenticate_unless_readonly
35 def get(self):
35 def get(self):
36 sm = self.session_manager
36 sm = self.session_manager
37 nbm = self.notebook_manager
37 nbm = self.notebook_manager
38 km = self.kernel_manager
38 km = self.kernel_manager
39 sessions = sm.list_sessions()
39 sessions = sm.list_sessions()
40 self.finish(jsonapi.dumps(sessions))
40 self.finish(jsonapi.dumps(sessions))
41
41
42 @web.authenticated
42 @web.authenticated
43 def post(self):
43 def post(self):
44 sm = self.session_manager
44 sm = self.session_manager
45 nbm = self.notebook_manager
45 nbm = self.notebook_manager
46 km = self.kernel_manager
46 km = self.kernel_manager
47 notebook_path = self.get_argument('notebook_path', default=None)
47 notebook_path = self.get_argument('notebook_path', default=None)
48 notebook_name, path = nbm.named_notebook_path(notebook_path)
48 notebook_name, path = nbm.named_notebook_path(notebook_path)
49 session_id, model = sm.get_session(notebook_name, path)
49 session_id, model = sm.get_session(notebook_name, path)
50 if model == None:
50 if model == None:
51 kernel_id = km.start_kernel()
51 kernel_id = km.start_kernel()
52 kernel = km.kernel_model(kernel_id, self.ws_url)
52 kernel = km.kernel_model(kernel_id, self.ws_url)
53 model = sm.session_model(session_id, notebook_name, path, kernel)
53 model = sm.session_model(session_id, notebook_name, path, kernel)
54 self.finish(jsonapi.dumps(model))
54 self.finish(jsonapi.dumps(model))
55
55
56 class SessionHandler(IPythonHandler):
56 class SessionHandler(IPythonHandler):
57
57
58 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
58 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
59
59
60 @authenticate_unless_readonly
60 @authenticate_unless_readonly
61 def get(self, session_id):
61 def get(self, session_id):
62 sm = self.session_manager
62 sm = self.session_manager
63 model = sm.get_session_from_id(session_id)
63 model = sm.get_session_from_id(session_id)
64 self.finish(jsonapi.dumps(model))
64 self.finish(jsonapi.dumps(model))
65
65
66 @web.authenticated
66 @web.authenticated
67 def patch(self, session_id):
67 def patch(self, session_id):
68 sm = self.session_manager
68 sm = self.session_manager
69 nbm = self.notebook_manager
69 nbm = self.notebook_manager
70 km = self.kernel_manager
70 km = self.kernel_manager
71 notebook_path = self.request.body
71 notebook_path = self.request.body
72 notebook_name, path = nbm.named_notebook_path(notebook_path)
72 notebook_name, path = nbm.named_notebook_path(notebook_path)
73 kernel_id = sm.get_kernel_from_session(session_id)
73 kernel_id = sm.get_kernel_from_session(session_id)
74 kernel = km.kernel_model(kernel_id, self.ws_url)
74 kernel = km.kernel_model(kernel_id, self.ws_url)
75 sm.delete_mapping_for_session(session_id)
75 sm.delete_mapping_for_session(session_id)
76 model = sm.session_model(session_id, notebook_name, path, kernel)
76 model = sm.session_model(session_id, notebook_name, path, kernel)
77 self.finish(jsonapi.dumps(model))
77 self.finish(jsonapi.dumps(model))
78
78
79 @web.authenticated
79 @web.authenticated
80 def delete(self, session_id):
80 def delete(self, session_id):
81 sm = self.session_manager
81 sm = self.session_manager
82 nbm = self.notebook_manager
82 nbm = self.notebook_manager
83 km = self.kernel_manager
83 km = self.kernel_manager
84 kernel_id = sm.get_kernel_from_session(session_id)
84 kernel_id = sm.get_kernel_from_session(session_id)
85 km.shutdown_kernel(kernel_id)
85 km.shutdown_kernel(kernel_id)
86 sm.delete_mapping_for_session(session_id)
86 sm.delete_mapping_for_session(session_id)
87 self.set_status(204)
87 self.set_status(204)
88 self.finish()
88 self.finish()
89
89
90
90
91 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
92 # URL to handler mappings
92 # URL to handler mappings
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94
94
95 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
95 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
96
96
97 default_handlers = [
97 default_handlers = [
98 (r"api/sessions/%s" % _session_id_regex, SessionHandler),
98 (r"api/sessions/%s" % _session_id_regex, SessionHandler),
99 (r"api/sessions", SessionRootHandler)
99 (r"api/sessions", SessionRootHandler)
100 ]
100 ]
101
101
102
102
103
103
104
104
@@ -1,95 +1,97 b''
1 """A base class session manager.
1 """A base class session manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2013 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 SessionManager(LoggingConfigurable):
32 class SessionManager(LoggingConfigurable):
33
33
34 # Use session_ids to map notebook names to kernel_ids
34 # Use session_ids to map notebook names to kernel_ids
35 sessions = List()
35 sessions = List()
36
36
37 def get_session(self, nb_name, nb_path=None):
37 def get_session(self, nb_name, nb_path=None):
38 """Get an existing session or create a new one"""
38 """Get an existing session or create a new one"""
39 model = None
39 model = None
40 for session in self.sessions:
40 for session in self.sessions:
41 if session['notebook_name'] == nb_name and session['notebook_path'] == nb_path:
41 if session['notebook_name'] == nb_name and session['notebook_path'] == nb_path:
42 session_id = session['session_id']
42 session_id = session['session_id']
43 model = session
43 model = session
44 if model != None:
44 if model != None:
45 return session_id, model
45 return session_id, model
46 else:
46 else:
47 session_id = unicode(uuid.uuid4())
47 session_id = unicode(uuid.uuid4())
48 return session_id, model
48 return session_id, model
49
49
50 def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
50 def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
51 """ Create a session that links notebooks with kernels """
51 """ Create a session that links notebooks with kernels """
52 model = dict(session_id=session_id,
52 model = dict(session_id=session_id,
53 notebook_name=notebook_name,
53 notebook_name=notebook_name,
54 notebook_path=notebook_path,
54 notebook_path=notebook_path,
55 kernel=kernel)
55 kernel=kernel)
56 if notebook_path == None:
57 model['notebook_path']=""
56 self.sessions.append(model)
58 self.sessions.append(model)
57 return model
59 return model
58
60
59 def list_sessions(self):
61 def list_sessions(self):
60 """List all sessions and their information"""
62 """List all sessions and their information"""
61 return self.sessions
63 return self.sessions
62
64
63 def set_kernel_for_sessions(self, session_id, kernel_id):
65 def set_kernel_for_sessions(self, session_id, kernel_id):
64 """Maps the kernel_ids to the session_id in session_mapping"""
66 """Maps the kernel_ids to the session_id in session_mapping"""
65 for session in self.sessions:
67 for session in self.sessions:
66 if session['session_id'] == session_id:
68 if session['session_id'] == session_id:
67 session['kernel_id'] = kernel_id
69 session['kernel_id'] = kernel_id
68 return self.sessions
70 return self.sessions
69
71
70 def delete_mapping_for_session(self, session_id):
72 def delete_mapping_for_session(self, session_id):
71 """Delete the session from session_mapping with the given session_id"""
73 """Delete the session from session_mapping with the given session_id"""
72 i = 0
74 i = 0
73 for session in self.sessions:
75 for session in self.sessions:
74 if session['session_id'] == session_id:
76 if session['session_id'] == session_id:
75 del self.sessions[i]
77 del self.sessions[i]
76 i = i + 1
78 i = i + 1
77 return self.sessions
79 return self.sessions
78
80
79 def get_session_from_id(self, session_id):
81 def get_session_from_id(self, session_id):
80 for session in self.sessions:
82 for session in self.sessions:
81 if session['session_id'] == session_id:
83 if session['session_id'] == session_id:
82 return session
84 return session
83
85
84 def get_notebook_from_session(self, session_id):
86 def get_notebook_from_session(self, session_id):
85 """Returns the notebook_path for the given session_id"""
87 """Returns the notebook_path for the given session_id"""
86 for session in self.sessions:
88 for session in self.sessions:
87 if session['session_id'] == session_id:
89 if session['session_id'] == session_id:
88 return session['notebook_name']
90 return session['notebook_name']
89
91
90 def get_kernel_from_session(self, session_id):
92 def get_kernel_from_session(self, session_id):
91 """Returns the kernel_id for the given session_id"""
93 """Returns the kernel_id for the given session_id"""
92 for session in self.sessions:
94 for session in self.sessions:
93 if session['session_id'] == session_id:
95 if session['session_id'] == session_id:
94 return session['kernel']['kernel_id']
96 return session['kernel']['kernel_id']
95
97
General Comments 0
You need to be logged in to leave comments. Login now