##// END OF EJS Templates
catch IOError in addition to OSError...
Min RK -
Show More
@@ -1,166 +1,166 b''
1 1 """
2 2 Utilities for file-based Contents/Checkpoints managers.
3 3 """
4 4
5 5 # Copyright (c) IPython Development Team.
6 6 # Distributed under the terms of the Modified BSD License.
7 7
8 8 import base64
9 9 from contextlib import contextmanager
10 10 import errno
11 11 import io
12 12 import os
13 13 import shutil
14 14
15 15 from tornado.web import HTTPError
16 16
17 17 from IPython.html.utils import (
18 18 to_api_path,
19 19 to_os_path,
20 20 )
21 21 from IPython import nbformat
22 22 from IPython.utils.io import atomic_writing
23 23 from IPython.utils.py3compat import str_to_unicode
24 24
25 25
26 26 class FileManagerMixin(object):
27 27 """
28 28 Mixin for ContentsAPI classes that interact with the filesystem.
29 29
30 30 Provides facilities for reading, writing, and copying both notebooks and
31 31 generic files.
32 32
33 33 Shared by FileContentsManager and FileCheckpoints.
34 34
35 35 Note
36 36 ----
37 37 Classes using this mixin must provide the following attributes:
38 38
39 39 root_dir : unicode
40 40 A directory against against which API-style paths are to be resolved.
41 41
42 42 log : logging.Logger
43 43 """
44 44
45 45 @contextmanager
46 46 def open(self, os_path, *args, **kwargs):
47 47 """wrapper around io.open that turns permission errors into 403"""
48 48 with self.perm_to_403(os_path):
49 49 with io.open(os_path, *args, **kwargs) as f:
50 50 yield f
51 51
52 52 @contextmanager
53 53 def atomic_writing(self, os_path, *args, **kwargs):
54 54 """wrapper around atomic_writing that turns permission errors to 403"""
55 55 with self.perm_to_403(os_path):
56 56 with atomic_writing(os_path, *args, **kwargs) as f:
57 57 yield f
58 58
59 59 @contextmanager
60 60 def perm_to_403(self, os_path=''):
61 61 """context manager for turning permission errors into 403."""
62 62 try:
63 63 yield
64 except OSError as e:
64 except (OSError, IOError) as e:
65 65 if e.errno in {errno.EPERM, errno.EACCES}:
66 66 # make 403 error message without root prefix
67 67 # this may not work perfectly on unicode paths on Python 2,
68 68 # but nobody should be doing that anyway.
69 69 if not os_path:
70 70 os_path = str_to_unicode(e.filename or 'unknown file')
71 71 path = to_api_path(os_path, root=self.root_dir)
72 72 raise HTTPError(403, u'Permission denied: %s' % path)
73 73 else:
74 74 raise
75 75
76 76 def _copy(self, src, dest):
77 77 """copy src to dest
78 78
79 79 like shutil.copy2, but log errors in copystat
80 80 """
81 81 shutil.copyfile(src, dest)
82 82 try:
83 83 shutil.copystat(src, dest)
84 84 except OSError:
85 85 self.log.debug("copystat on %s failed", dest, exc_info=True)
86 86
87 87 def _get_os_path(self, path):
88 88 """Given an API path, return its file system path.
89 89
90 90 Parameters
91 91 ----------
92 92 path : string
93 93 The relative API path to the named file.
94 94
95 95 Returns
96 96 -------
97 97 path : string
98 98 Native, absolute OS path to for a file.
99 99 """
100 100 return to_os_path(path, self.root_dir)
101 101
102 102 def _read_notebook(self, os_path, as_version=4):
103 103 """Read a notebook from an os path."""
104 104 with self.open(os_path, 'r', encoding='utf-8') as f:
105 105 try:
106 106 return nbformat.read(f, as_version=as_version)
107 107 except Exception as e:
108 108 raise HTTPError(
109 109 400,
110 110 u"Unreadable Notebook: %s %r" % (os_path, e),
111 111 )
112 112
113 113 def _save_notebook(self, os_path, nb):
114 114 """Save a notebook to an os_path."""
115 115 with self.atomic_writing(os_path, encoding='utf-8') as f:
116 116 nbformat.write(nb, f, version=nbformat.NO_CONVERT)
117 117
118 118 def _read_file(self, os_path, format):
119 119 """Read a non-notebook file.
120 120
121 121 os_path: The path to be read.
122 122 format:
123 123 If 'text', the contents will be decoded as UTF-8.
124 124 If 'base64', the raw bytes contents will be encoded as base64.
125 125 If not specified, try to decode as UTF-8, and fall back to base64
126 126 """
127 127 if not os.path.isfile(os_path):
128 128 raise HTTPError(400, "Cannot read non-file %s" % os_path)
129 129
130 130 with self.open(os_path, 'rb') as f:
131 131 bcontent = f.read()
132 132
133 133 if format is None or format == 'text':
134 134 # Try to interpret as unicode if format is unknown or if unicode
135 135 # was explicitly requested.
136 136 try:
137 137 return bcontent.decode('utf8'), 'text'
138 138 except UnicodeError:
139 139 if format == 'text':
140 140 raise HTTPError(
141 141 400,
142 142 "%s is not UTF-8 encoded" % os_path,
143 143 reason='bad format',
144 144 )
145 145 return base64.encodestring(bcontent).decode('ascii'), 'base64'
146 146
147 147 def _save_file(self, os_path, content, format):
148 148 """Save content of a generic file."""
149 149 if format not in {'text', 'base64'}:
150 150 raise HTTPError(
151 151 400,
152 152 "Must specify format of file contents as 'text' or 'base64'",
153 153 )
154 154 try:
155 155 if format == 'text':
156 156 bcontent = content.encode('utf8')
157 157 else:
158 158 b64_bytes = content.encode('ascii')
159 159 bcontent = base64.decodestring(b64_bytes)
160 160 except Exception as e:
161 161 raise HTTPError(
162 162 400, u'Encoding error saving %s: %s' % (os_path, e)
163 163 )
164 164
165 165 with self.atomic_writing(os_path, text=False) as f:
166 166 f.write(bcontent)
General Comments 0
You need to be logged in to leave comments. Login now