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