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