##// END OF EJS Templates
Add fsync() call in atomic_writing
Thomas Kluyver -
Show More
@@ -1,315 +1,319 b''
1 1 # encoding: utf-8
2 2 """
3 3 IO related utilities.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12 from __future__ import print_function
13 13 from __future__ import absolute_import
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18 import codecs
19 19 from contextlib import contextmanager
20 20 import io
21 21 import os
22 22 import sys
23 23 import tempfile
24 24 from .capture import CapturedIO, capture_output
25 25 from .py3compat import string_types, input, PY3
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Code
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class IOStream:
33 33
34 34 def __init__(self,stream, fallback=None):
35 35 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
36 36 if fallback is not None:
37 37 stream = fallback
38 38 else:
39 39 raise ValueError("fallback required, but not specified")
40 40 self.stream = stream
41 41 self._swrite = stream.write
42 42
43 43 # clone all methods not overridden:
44 44 def clone(meth):
45 45 return not hasattr(self, meth) and not meth.startswith('_')
46 46 for meth in filter(clone, dir(stream)):
47 47 setattr(self, meth, getattr(stream, meth))
48 48
49 49 def __repr__(self):
50 50 cls = self.__class__
51 51 tpl = '{mod}.{cls}({args})'
52 52 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
53 53
54 54 def write(self,data):
55 55 try:
56 56 self._swrite(data)
57 57 except:
58 58 try:
59 59 # print handles some unicode issues which may trip a plain
60 60 # write() call. Emulate write() by using an empty end
61 61 # argument.
62 62 print(data, end='', file=self.stream)
63 63 except:
64 64 # if we get here, something is seriously broken.
65 65 print('ERROR - failed to write data to stream:', self.stream,
66 66 file=sys.stderr)
67 67
68 68 def writelines(self, lines):
69 69 if isinstance(lines, string_types):
70 70 lines = [lines]
71 71 for line in lines:
72 72 self.write(line)
73 73
74 74 # This class used to have a writeln method, but regular files and streams
75 75 # in Python don't have this method. We need to keep this completely
76 76 # compatible so we removed it.
77 77
78 78 @property
79 79 def closed(self):
80 80 return self.stream.closed
81 81
82 82 def close(self):
83 83 pass
84 84
85 85 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
86 86 devnull = open(os.devnull, 'w')
87 87 stdin = IOStream(sys.stdin, fallback=devnull)
88 88 stdout = IOStream(sys.stdout, fallback=devnull)
89 89 stderr = IOStream(sys.stderr, fallback=devnull)
90 90
91 91 class IOTerm:
92 92 """ Term holds the file or file-like objects for handling I/O operations.
93 93
94 94 These are normally just sys.stdin, sys.stdout and sys.stderr but for
95 95 Windows they can can replaced to allow editing the strings before they are
96 96 displayed."""
97 97
98 98 # In the future, having IPython channel all its I/O operations through
99 99 # this class will make it easier to embed it into other environments which
100 100 # are not a normal terminal (such as a GUI-based shell)
101 101 def __init__(self, stdin=None, stdout=None, stderr=None):
102 102 mymodule = sys.modules[__name__]
103 103 self.stdin = IOStream(stdin, mymodule.stdin)
104 104 self.stdout = IOStream(stdout, mymodule.stdout)
105 105 self.stderr = IOStream(stderr, mymodule.stderr)
106 106
107 107
108 108 class Tee(object):
109 109 """A class to duplicate an output stream to stdout/err.
110 110
111 111 This works in a manner very similar to the Unix 'tee' command.
112 112
113 113 When the object is closed or deleted, it closes the original file given to
114 114 it for duplication.
115 115 """
116 116 # Inspired by:
117 117 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
118 118
119 119 def __init__(self, file_or_name, mode="w", channel='stdout'):
120 120 """Construct a new Tee object.
121 121
122 122 Parameters
123 123 ----------
124 124 file_or_name : filename or open filehandle (writable)
125 125 File that will be duplicated
126 126
127 127 mode : optional, valid mode for open().
128 128 If a filename was give, open with this mode.
129 129
130 130 channel : str, one of ['stdout', 'stderr']
131 131 """
132 132 if channel not in ['stdout', 'stderr']:
133 133 raise ValueError('Invalid channel spec %s' % channel)
134 134
135 135 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
136 136 self.file = file_or_name
137 137 else:
138 138 self.file = open(file_or_name, mode)
139 139 self.channel = channel
140 140 self.ostream = getattr(sys, channel)
141 141 setattr(sys, channel, self)
142 142 self._closed = False
143 143
144 144 def close(self):
145 145 """Close the file and restore the channel."""
146 146 self.flush()
147 147 setattr(sys, self.channel, self.ostream)
148 148 self.file.close()
149 149 self._closed = True
150 150
151 151 def write(self, data):
152 152 """Write data to both channels."""
153 153 self.file.write(data)
154 154 self.ostream.write(data)
155 155 self.ostream.flush()
156 156
157 157 def flush(self):
158 158 """Flush both channels."""
159 159 self.file.flush()
160 160 self.ostream.flush()
161 161
162 162 def __del__(self):
163 163 if not self._closed:
164 164 self.close()
165 165
166 166
167 167 def ask_yes_no(prompt, default=None, interrupt=None):
168 168 """Asks a question and returns a boolean (y/n) answer.
169 169
170 170 If default is given (one of 'y','n'), it is used if the user input is
171 171 empty. If interrupt is given (one of 'y','n'), it is used if the user
172 172 presses Ctrl-C. Otherwise the question is repeated until an answer is
173 173 given.
174 174
175 175 An EOF is treated as the default answer. If there is no default, an
176 176 exception is raised to prevent infinite loops.
177 177
178 178 Valid answers are: y/yes/n/no (match is not case sensitive)."""
179 179
180 180 answers = {'y':True,'n':False,'yes':True,'no':False}
181 181 ans = None
182 182 while ans not in answers.keys():
183 183 try:
184 184 ans = input(prompt+' ').lower()
185 185 if not ans: # response was an empty string
186 186 ans = default
187 187 except KeyboardInterrupt:
188 188 if interrupt:
189 189 ans = interrupt
190 190 except EOFError:
191 191 if default in answers.keys():
192 192 ans = default
193 193 print()
194 194 else:
195 195 raise
196 196
197 197 return answers[ans]
198 198
199 199
200 200 def temp_pyfile(src, ext='.py'):
201 201 """Make a temporary python file, return filename and filehandle.
202 202
203 203 Parameters
204 204 ----------
205 205 src : string or list of strings (no need for ending newlines if list)
206 206 Source code to be written to the file.
207 207
208 208 ext : optional, string
209 209 Extension for the generated file.
210 210
211 211 Returns
212 212 -------
213 213 (filename, open filehandle)
214 214 It is the caller's responsibility to close the open file and unlink it.
215 215 """
216 216 fname = tempfile.mkstemp(ext)[1]
217 217 f = open(fname,'w')
218 218 f.write(src)
219 219 f.flush()
220 220 return fname, f
221 221
222 222 @contextmanager
223 223 def atomic_writing(path, text=True, encoding='utf-8', **kwargs):
224 224 """Context manager to write to a file only if the entire write is successful.
225 225
226 226 This works by creating a temporary file in the same directory, and renaming
227 227 it over the old file if the context is exited without an error. If the
228 228 target file is a symlink or a hardlink, this will not be preserved: it will
229 229 be replaced by a new regular file.
230 230
231 231 On Windows, there is a small chink in the atomicity: the target file is
232 232 deleted before renaming the temporary file over it. This appears to be
233 233 unavoidable.
234 234
235 235 Parameters
236 236 ----------
237 237 path : str
238 238 The target file to write to.
239 239
240 240 text : bool, optional
241 241 Whether to open the file in text mode (i.e. to write unicode). Default is
242 242 True.
243 243
244 244 encoding : str, optional
245 245 The encoding to use for files opened in text mode. Default is UTF-8.
246 246
247 247 **kwargs
248 248 Passed to :func:`io.open`.
249 249 """
250 250 dirname, basename = os.path.split(path)
251 251 handle, tmp_path = tempfile.mkstemp(prefix=basename, dir=dirname, text=text)
252 252 if text:
253 253 fileobj = io.open(handle, 'w', encoding=encoding, **kwargs)
254 254 else:
255 255 fileobj = io.open(handle, 'wb', **kwargs)
256 256
257 257 try:
258 258 yield fileobj
259 259 except:
260 260 fileobj.close()
261 261 os.remove(tmp_path)
262 262 raise
263 else:
264 # Written successfully, now rename it
265 fileobj.close()
266 263
267 if os.name == 'nt' and os.path.exists(path):
268 # Rename over existing file doesn't work on Windows
269 os.remove(path)
264 # Flush to disk
265 fileobj.flush()
266 os.fsync(fileobj.fileno())
267
268 # Written successfully, now rename it
269 fileobj.close()
270
271 if os.name == 'nt' and os.path.exists(path):
272 # Rename over existing file doesn't work on Windows
273 os.remove(path)
270 274
271 os.rename(tmp_path, path)
275 os.rename(tmp_path, path)
272 276
273 277
274 278 def raw_print(*args, **kw):
275 279 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
276 280
277 281 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
278 282 file=sys.__stdout__)
279 283 sys.__stdout__.flush()
280 284
281 285
282 286 def raw_print_err(*args, **kw):
283 287 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
284 288
285 289 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
286 290 file=sys.__stderr__)
287 291 sys.__stderr__.flush()
288 292
289 293
290 294 # Short aliases for quick debugging, do NOT use these in production code.
291 295 rprint = raw_print
292 296 rprinte = raw_print_err
293 297
294 298 def unicode_std_stream(stream='stdout'):
295 299 u"""Get a wrapper to write unicode to stdout/stderr as UTF-8.
296 300
297 301 This ignores environment variables and default encodings, to reliably write
298 302 unicode to stdout or stderr.
299 303
300 304 ::
301 305
302 306 unicode_std_stream().write(u'ł@e¶ŧ←')
303 307 """
304 308 assert stream in ('stdout', 'stderr')
305 309 stream = getattr(sys, stream)
306 310 if PY3:
307 311 try:
308 312 stream_b = stream.buffer
309 313 except AttributeError:
310 314 # sys.stdout has been replaced - use it directly
311 315 return stream
312 316 else:
313 317 stream_b = stream
314 318
315 319 return codecs.getwriter('utf-8')(stream_b)
General Comments 0
You need to be logged in to leave comments. Login now