implement a detach() method for BufferedIOBase and TextIOBase #5883
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index a8c878e..1a525dc 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -526,6 +526,12 @@
 class CommonBufferedTests:
     # Tests common to BufferedReader, BufferedWriter and BufferedRandom
 
+    def test_detach(self):
+        raw = self.MockRawIO()
+        buf = self.tp(raw)
+        self.assertIs(buf.detach(), raw)
+        self.assertRaises(ValueError, buf.detach)
+
     def test_fileno(self):
         rawio = self.MockRawIO()
         bufio = self.tp(rawio)
@@ -811,6 +817,14 @@
         bufio.flush()
         self.assertEquals(b"".join(rawio._write_stack), b"abcghi")
 
+    def test_detach_flush(self):
+        raw = self.MockRawIO()
+        buf = self.tp(raw)
+        buf.write(b"howdy!")
+        self.assertFalse(raw._write_stack)
+        buf.detach()
+        self.assertEqual(raw._write_stack, [b"howdy!"])
+
     def test_write(self):
         # Write to the buffered IO but don't overflow the buffer.
         writer = self.MockRawIO()
@@ -1052,6 +1066,10 @@
         pair = self.tp(self.MockRawIO(), self.MockRawIO())
         self.assertFalse(pair.closed)
 
+    def test_detach(self):
+        pair = self.tp(self.MockRawIO(), self.MockRawIO())
+        self.assertRaises(self.UnsupportedOperation, pair.detach)
+
     def test_constructor_max_buffer_size_deprecation(self):
         with support.check_warnings() as w:
             warnings.simplefilter("always", DeprecationWarning)
@@ -1480,6 +1498,19 @@
         self.assertRaises(TypeError, t.__init__, b, newline=42)
         self.assertRaises(ValueError, t.__init__, b, newline='xyzzy')
 
+    def test_detach(self):
+        r = self.BytesIO()
+        b = self.BufferedWriter(r)
+        t = self.TextIOWrapper(b)
+        self.assertIs(t.detach(), b)
+
+        t = self.TextIOWrapper(b, encoding="ascii")
+        t.write("howdy")
+        self.assertFalse(r.getvalue())
+        t.detach()
+        self.assertEqual(r.getvalue(), b"howdy")
+        self.assertRaises(ValueError, t.detach)
+
     def test_repr(self):
         raw = self.BytesIO("hello".encode("utf-8"))
         b = self.BufferedReader(raw)
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index ad04613..d94d9dd 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -57,6 +57,10 @@
 
 class MemoryTestMixin:
 
+    def test_detach(self):
+        buf = self.ioclass()
+        self.assertRaises(self.UnsupportedOperation, buf.detach)
+
     def write_ops(self, f, t):
         self.assertEqual(f.write(t("blah.")), 5)
         self.assertEqual(f.seek(0), 0)
@@ -336,6 +340,9 @@
 
 
 class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
+
+    UnsupportedOperation = pyio.UnsupportedOperation
+
     @staticmethod
     def buftype(s):
         return s.encode("ascii")
@@ -413,6 +420,7 @@
 class PyStringIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
     buftype = str
     ioclass = pyio.StringIO
+    UnsupportedOperation = pyio.UnsupportedOperation
     EOF = ""
 
     # TextIO-specific behaviour.
@@ -518,9 +526,11 @@
 
 class CBytesIOTest(PyBytesIOTest):
     ioclass = io.BytesIO
+    UnsupportedOperation = io.UnsupportedOperation
 
 class CStringIOTest(PyStringIOTest):
     ioclass = io.StringIO
+    UnsupportedOperation = io.UnsupportedOperation
 
     # XXX: For the Python version of io.StringIO, this is highly
     # dependent on the encoding used for the underlying buffer.