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