diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py index 6a9ed325dbd..9ccb31cb840 100644 --- a/Lib/idlelib/PyShell.py +++ b/Lib/idlelib/PyShell.py @@ -417,10 +417,8 @@ class ModifiedInterpreter(InteractiveInterpreter): except socket.timeout as err: self.display_no_subprocess_error() return None - # Can't regiter self.tkconsole.stdin, since run.py wants to - # call non-TextIO methods on it (such as getvar) - # XXX should be renamed to "console" - self.rpcclt.register("stdin", self.tkconsole) + self.rpcclt.register("console", self.tkconsole) + self.rpcclt.register("stdin", self.tkconsole.stdin) self.rpcclt.register("stdout", self.tkconsole.stdout) self.rpcclt.register("stderr", self.tkconsole.stderr) self.rpcclt.register("flist", self.tkconsole.flist) @@ -864,10 +862,10 @@ class PyShell(OutputWindow): self.save_stderr = sys.stderr self.save_stdin = sys.stdin from idlelib import IOBinding - self.stdin = PseudoInputFile(self) - self.stdout = PseudoFile(self, "stdout", IOBinding.encoding) - self.stderr = PseudoFile(self, "stderr", IOBinding.encoding) - self.console = PseudoFile(self, "console", IOBinding.encoding) + self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding) + self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding) + self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding) + self.console = PseudoOutputFile(self, "console", IOBinding.encoding) if not use_subprocess: sys.stdout = self.stdout sys.stderr = self.stderr @@ -1278,36 +1276,82 @@ class PyShell(OutputWindow): return 'disabled' return super().rmenu_check_paste() -class PseudoFile(object): +class PseudoFile(io.TextIOBase): def __init__(self, shell, tags, encoding=None): self.shell = shell self.tags = tags - self.encoding = encoding + self._encoding = encoding - def write(self, s): - if not isinstance(s, str): - raise TypeError('must be str, not ' + type(s).__name__) - return self.shell.write(s, self.tags) + @property + def encoding(self): + return self._encoding - def writelines(self, lines): - for line in lines: - self.write(line) - - def flush(self): - pass + @property + def name(self): + return '<%s>' % self.tags def isatty(self): return True -class PseudoInputFile(object): - def __init__(self, shell): - self.readline = shell.readline - self.isatty = shell.isatty + +class PseudoOutputFile(PseudoFile): + + def writable(self): + return True def write(self, s): - raise io.UnsupportedOperation("not writable") - writelines = write + if self.closed: + raise ValueError("write to closed file") + if not isinstance(s, str): + raise TypeError('must be str, not ' + type(s).__name__) + return self.shell.write(s, self.tags) + + +class PseudoInputFile(PseudoFile): + + def __init__(self, shell, tags, encoding=None): + PseudoFile.__init__(self, shell, tags, encoding) + self._line_buffer = '' + + def readable(self): + return True + + def read(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + result = self._line_buffer + self._line_buffer = '' + if size < 0: + while True: + line = self.shell.readline() + if not line: break + result += line + else: + while len(result) < size: + line = self.shell.readline() + if not line: break + result += line + self._line_buffer = result[size:] + result = result[:size] + return result + + def readline(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + line = self._line_buffer or self.shell.readline() + if size < 0: + size = len(line) + self._line_buffer = line[size:] + return line[:size] usage_msg = """\ diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 41e4bf99014..c66679cde92 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -16,6 +16,8 @@ from idlelib import RemoteDebugger from idlelib import RemoteObjectBrowser from idlelib import StackViewer from idlelib import rpc +from idlelib import PyShell +from idlelib import IOBinding import __main__ @@ -277,63 +279,24 @@ class MyRPCServer(rpc.RPCServer): quitting = True thread.interrupt_main() -class _RPCFile(io.TextIOBase): - """Wrapper class for the RPC proxy to typecheck arguments - that may not support pickling. The base class is there only - to support type tests; all implementations come from the remote - object.""" - - def __init__(self, rpc): - super.__setattr__(self, 'rpc', rpc) - - def __getattribute__(self, name): - # When accessing the 'rpc' attribute, or 'write', use ours - if name in ('rpc', 'write', 'writelines'): - return io.TextIOBase.__getattribute__(self, name) - # Else only look into the remote object only - return getattr(self.rpc, name) - - def __setattr__(self, name, value): - return setattr(self.rpc, name, value) - - @staticmethod - def _ensure_string(func): - def f(self, s): - if not isinstance(s, str): - raise TypeError('must be str, not ' + type(s).__name__) - return func(self, s) - return f - -class _RPCOutputFile(_RPCFile): - @_RPCFile._ensure_string - def write(self, s): - if not isinstance(s, str): - raise TypeError('must be str, not ' + type(s).__name__) - return self.rpc.write(s) - -class _RPCInputFile(_RPCFile): - @_RPCFile._ensure_string - def write(self, s): - raise io.UnsupportedOperation("not writable") - writelines = write - class MyHandler(rpc.RPCHandler): def handle(self): """Override base method""" executive = Executive(self) self.register("exec", executive) - self.console = self.get_remote_proxy("stdin") - sys.stdin = _RPCInputFile(self.console) - sys.stdout = _RPCOutputFile(self.get_remote_proxy("stdout")) - sys.stderr = _RPCOutputFile(self.get_remote_proxy("stderr")) + self.console = self.get_remote_proxy("console") + sys.stdin = PyShell.PseudoInputFile(self.console, "stdin", + IOBinding.encoding) + sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout", + IOBinding.encoding) + sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr", + IOBinding.encoding) + sys.displayhook = rpc.displayhook # page help() text to shell. import pydoc # import must be done here to capture i/o binding pydoc.pager = pydoc.plainpager - from idlelib import IOBinding - sys.stdin.encoding = sys.stdout.encoding = \ - sys.stderr.encoding = IOBinding.encoding self.interp = self.get_remote_proxy("interp") rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) diff --git a/Misc/NEWS b/Misc/NEWS index 3e4e86df8ff..be6d5c5f272 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -218,6 +218,9 @@ Core and Builtins Library ------- +- Issue #9290: In IDLE the sys.std* streams now implement io.TextIOBase + interface and support all mandatory methods and properties. + - Issue #13454: Fix a crash when deleting an iterator created by itertools.tee() if all other iterators were very advanced before.