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