##// 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,225 +1,227 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from __future__ import print_function
9 from __future__ import print_function
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12
12
13 import atexit
13 import os
14 import os
14 import sys
15 import sys
15 import tempfile
16 import tempfile
16 from warnings import warn
17 from warnings import warn
17 from .capture import CapturedIO, capture_output
18 from .capture import CapturedIO, capture_output
18 from .py3compat import string_types, input, PY3
19 from .py3compat import string_types, input, PY3
19
20
20
21
21 class IOStream:
22 class IOStream:
22
23
23 def __init__(self,stream, fallback=None):
24 def __init__(self,stream, fallback=None):
24 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
25 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
25 if fallback is not None:
26 if fallback is not None:
26 stream = fallback
27 stream = fallback
27 else:
28 else:
28 raise ValueError("fallback required, but not specified")
29 raise ValueError("fallback required, but not specified")
29 self.stream = stream
30 self.stream = stream
30 self._swrite = stream.write
31 self._swrite = stream.write
31
32
32 # clone all methods not overridden:
33 # clone all methods not overridden:
33 def clone(meth):
34 def clone(meth):
34 return not hasattr(self, meth) and not meth.startswith('_')
35 return not hasattr(self, meth) and not meth.startswith('_')
35 for meth in filter(clone, dir(stream)):
36 for meth in filter(clone, dir(stream)):
36 setattr(self, meth, getattr(stream, meth))
37 setattr(self, meth, getattr(stream, meth))
37
38
38 def __repr__(self):
39 def __repr__(self):
39 cls = self.__class__
40 cls = self.__class__
40 tpl = '{mod}.{cls}({args})'
41 tpl = '{mod}.{cls}({args})'
41 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
42 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
42
43
43 def write(self,data):
44 def write(self,data):
44 try:
45 try:
45 self._swrite(data)
46 self._swrite(data)
46 except:
47 except:
47 try:
48 try:
48 # print handles some unicode issues which may trip a plain
49 # print handles some unicode issues which may trip a plain
49 # write() call. Emulate write() by using an empty end
50 # write() call. Emulate write() by using an empty end
50 # argument.
51 # argument.
51 print(data, end='', file=self.stream)
52 print(data, end='', file=self.stream)
52 except:
53 except:
53 # if we get here, something is seriously broken.
54 # if we get here, something is seriously broken.
54 print('ERROR - failed to write data to stream:', self.stream,
55 print('ERROR - failed to write data to stream:', self.stream,
55 file=sys.stderr)
56 file=sys.stderr)
56
57
57 def writelines(self, lines):
58 def writelines(self, lines):
58 if isinstance(lines, string_types):
59 if isinstance(lines, string_types):
59 lines = [lines]
60 lines = [lines]
60 for line in lines:
61 for line in lines:
61 self.write(line)
62 self.write(line)
62
63
63 # This class used to have a writeln method, but regular files and streams
64 # This class used to have a writeln method, but regular files and streams
64 # in Python don't have this method. We need to keep this completely
65 # in Python don't have this method. We need to keep this completely
65 # compatible so we removed it.
66 # compatible so we removed it.
66
67
67 @property
68 @property
68 def closed(self):
69 def closed(self):
69 return self.stream.closed
70 return self.stream.closed
70
71
71 def close(self):
72 def close(self):
72 pass
73 pass
73
74
74 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
75 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
75 devnull = open(os.devnull, 'w')
76 devnull = open(os.devnull, 'w')
77 atexit.register(devnull.close)
76 stdin = IOStream(sys.stdin, fallback=devnull)
78 stdin = IOStream(sys.stdin, fallback=devnull)
77 stdout = IOStream(sys.stdout, fallback=devnull)
79 stdout = IOStream(sys.stdout, fallback=devnull)
78 stderr = IOStream(sys.stderr, fallback=devnull)
80 stderr = IOStream(sys.stderr, fallback=devnull)
79
81
80 class Tee(object):
82 class Tee(object):
81 """A class to duplicate an output stream to stdout/err.
83 """A class to duplicate an output stream to stdout/err.
82
84
83 This works in a manner very similar to the Unix 'tee' command.
85 This works in a manner very similar to the Unix 'tee' command.
84
86
85 When the object is closed or deleted, it closes the original file given to
87 When the object is closed or deleted, it closes the original file given to
86 it for duplication.
88 it for duplication.
87 """
89 """
88 # Inspired by:
90 # Inspired by:
89 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
91 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
90
92
91 def __init__(self, file_or_name, mode="w", channel='stdout'):
93 def __init__(self, file_or_name, mode="w", channel='stdout'):
92 """Construct a new Tee object.
94 """Construct a new Tee object.
93
95
94 Parameters
96 Parameters
95 ----------
97 ----------
96 file_or_name : filename or open filehandle (writable)
98 file_or_name : filename or open filehandle (writable)
97 File that will be duplicated
99 File that will be duplicated
98
100
99 mode : optional, valid mode for open().
101 mode : optional, valid mode for open().
100 If a filename was give, open with this mode.
102 If a filename was give, open with this mode.
101
103
102 channel : str, one of ['stdout', 'stderr']
104 channel : str, one of ['stdout', 'stderr']
103 """
105 """
104 if channel not in ['stdout', 'stderr']:
106 if channel not in ['stdout', 'stderr']:
105 raise ValueError('Invalid channel spec %s' % channel)
107 raise ValueError('Invalid channel spec %s' % channel)
106
108
107 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
109 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
108 self.file = file_or_name
110 self.file = file_or_name
109 else:
111 else:
110 self.file = open(file_or_name, mode)
112 self.file = open(file_or_name, mode)
111 self.channel = channel
113 self.channel = channel
112 self.ostream = getattr(sys, channel)
114 self.ostream = getattr(sys, channel)
113 setattr(sys, channel, self)
115 setattr(sys, channel, self)
114 self._closed = False
116 self._closed = False
115
117
116 def close(self):
118 def close(self):
117 """Close the file and restore the channel."""
119 """Close the file and restore the channel."""
118 self.flush()
120 self.flush()
119 setattr(sys, self.channel, self.ostream)
121 setattr(sys, self.channel, self.ostream)
120 self.file.close()
122 self.file.close()
121 self._closed = True
123 self._closed = True
122
124
123 def write(self, data):
125 def write(self, data):
124 """Write data to both channels."""
126 """Write data to both channels."""
125 self.file.write(data)
127 self.file.write(data)
126 self.ostream.write(data)
128 self.ostream.write(data)
127 self.ostream.flush()
129 self.ostream.flush()
128
130
129 def flush(self):
131 def flush(self):
130 """Flush both channels."""
132 """Flush both channels."""
131 self.file.flush()
133 self.file.flush()
132 self.ostream.flush()
134 self.ostream.flush()
133
135
134 def __del__(self):
136 def __del__(self):
135 if not self._closed:
137 if not self._closed:
136 self.close()
138 self.close()
137
139
138
140
139 def ask_yes_no(prompt, default=None, interrupt=None):
141 def ask_yes_no(prompt, default=None, interrupt=None):
140 """Asks a question and returns a boolean (y/n) answer.
142 """Asks a question and returns a boolean (y/n) answer.
141
143
142 If default is given (one of 'y','n'), it is used if the user input is
144 If default is given (one of 'y','n'), it is used if the user input is
143 empty. If interrupt is given (one of 'y','n'), it is used if the user
145 empty. If interrupt is given (one of 'y','n'), it is used if the user
144 presses Ctrl-C. Otherwise the question is repeated until an answer is
146 presses Ctrl-C. Otherwise the question is repeated until an answer is
145 given.
147 given.
146
148
147 An EOF is treated as the default answer. If there is no default, an
149 An EOF is treated as the default answer. If there is no default, an
148 exception is raised to prevent infinite loops.
150 exception is raised to prevent infinite loops.
149
151
150 Valid answers are: y/yes/n/no (match is not case sensitive)."""
152 Valid answers are: y/yes/n/no (match is not case sensitive)."""
151
153
152 answers = {'y':True,'n':False,'yes':True,'no':False}
154 answers = {'y':True,'n':False,'yes':True,'no':False}
153 ans = None
155 ans = None
154 while ans not in answers.keys():
156 while ans not in answers.keys():
155 try:
157 try:
156 ans = input(prompt+' ').lower()
158 ans = input(prompt+' ').lower()
157 if not ans: # response was an empty string
159 if not ans: # response was an empty string
158 ans = default
160 ans = default
159 except KeyboardInterrupt:
161 except KeyboardInterrupt:
160 if interrupt:
162 if interrupt:
161 ans = interrupt
163 ans = interrupt
162 except EOFError:
164 except EOFError:
163 if default in answers.keys():
165 if default in answers.keys():
164 ans = default
166 ans = default
165 print()
167 print()
166 else:
168 else:
167 raise
169 raise
168
170
169 return answers[ans]
171 return answers[ans]
170
172
171
173
172 def temp_pyfile(src, ext='.py'):
174 def temp_pyfile(src, ext='.py'):
173 """Make a temporary python file, return filename and filehandle.
175 """Make a temporary python file, return filename and filehandle.
174
176
175 Parameters
177 Parameters
176 ----------
178 ----------
177 src : string or list of strings (no need for ending newlines if list)
179 src : string or list of strings (no need for ending newlines if list)
178 Source code to be written to the file.
180 Source code to be written to the file.
179
181
180 ext : optional, string
182 ext : optional, string
181 Extension for the generated file.
183 Extension for the generated file.
182
184
183 Returns
185 Returns
184 -------
186 -------
185 (filename, open filehandle)
187 (filename, open filehandle)
186 It is the caller's responsibility to close the open file and unlink it.
188 It is the caller's responsibility to close the open file and unlink it.
187 """
189 """
188 fname = tempfile.mkstemp(ext)[1]
190 fname = tempfile.mkstemp(ext)[1]
189 f = open(fname,'w')
191 f = open(fname,'w')
190 f.write(src)
192 f.write(src)
191 f.flush()
193 f.flush()
192 return fname, f
194 return fname, f
193
195
194 def atomic_writing(*args, **kwargs):
196 def atomic_writing(*args, **kwargs):
195 """DEPRECATED: moved to notebook.services.contents.fileio"""
197 """DEPRECATED: moved to notebook.services.contents.fileio"""
196 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio")
198 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio")
197 from notebook.services.contents.fileio import atomic_writing
199 from notebook.services.contents.fileio import atomic_writing
198 return atomic_writing(*args, **kwargs)
200 return atomic_writing(*args, **kwargs)
199
201
200 def raw_print(*args, **kw):
202 def raw_print(*args, **kw):
201 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
203 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
202
204
203 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
205 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
204 file=sys.__stdout__)
206 file=sys.__stdout__)
205 sys.__stdout__.flush()
207 sys.__stdout__.flush()
206
208
207
209
208 def raw_print_err(*args, **kw):
210 def raw_print_err(*args, **kw):
209 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
211 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
210
212
211 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
213 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
212 file=sys.__stderr__)
214 file=sys.__stderr__)
213 sys.__stderr__.flush()
215 sys.__stderr__.flush()
214
216
215
217
216 # Short aliases for quick debugging, do NOT use these in production code.
218 # Short aliases for quick debugging, do NOT use these in production code.
217 rprint = raw_print
219 rprint = raw_print
218 rprinte = raw_print_err
220 rprinte = raw_print_err
219
221
220
222
221 def unicode_std_stream(stream='stdout'):
223 def unicode_std_stream(stream='stdout'):
222 """DEPRECATED, moved to nbconvert.utils.io"""
224 """DEPRECATED, moved to nbconvert.utils.io"""
223 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io")
225 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io")
224 from nbconvert.utils.io import unicode_std_stream
226 from nbconvert.utils.io import unicode_std_stream
225 return unicode_std_stream(stream)
227 return unicode_std_stream(stream)
General Comments 0
You need to be logged in to leave comments. Login now