From 39e70a4e837a4ebfae009bc202ac274aa9005fb6 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Fri, 25 Jan 2013 15:30:58 +0200
Subject: Issue #9290: In IDLE the sys.std* streams now implement io.TextIOBase
 interface and support all mandatory methods and properties.

---
 Lib/idlelib/PyShell.py | 92 +++++++++++++++++++++++++++++++++++++-------------
 Lib/idlelib/run.py     | 57 ++++++-------------------------
 Misc/NEWS              |  3 ++
 3 files changed, 81 insertions(+), 71 deletions(-)

diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index fb47b99..865472e 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)
@@ -860,10 +858,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
@@ -1259,36 +1257,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
+
+    @property
+    def encoding(self):
+        return self._encoding
+
+    @property
+    def name(self):
+        return '<%s>' % self.tags
+
+    def isatty(self):
+        return True
+
+
+class PseudoOutputFile(PseudoFile):
+
+    def writable(self):
+        return True
 
     def write(self, s):
+        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)
 
-    def writelines(self, lines):
-        for line in lines:
-            self.write(line)
 
-    def flush(self):
-        pass
+class PseudoInputFile(PseudoFile):
 
-    def isatty(self):
-        return True
+    def __init__(self, shell, tags, encoding=None):
+        PseudoFile.__init__(self, shell, tags, encoding)
+        self._line_buffer = ''
 
-class PseudoInputFile(object):
-    def __init__(self, shell):
-        self.readline = shell.readline
-        self.isatty = shell.isatty
+    def readable(self):
+        return True
 
-    def write(self, s):
-        raise io.UnsupportedOperation("not writable")
-    writelines = write
+    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 b0a4794..7d0941e 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -15,6 +15,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__
 
@@ -262,62 +264,23 @@ 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)
+
         # 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 43864ae..ff40c2c 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -202,6 +202,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.
 
-- 
cgit v0.12

