Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d2e0c79

Browse files
committed
implement a detach() method for BufferedIOBase and TextIOBase #5883
1 parent 155374d commit d2e0c79

7 files changed

Lines changed: 214 additions & 15 deletions

File tree

Doc/library/io.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,17 @@ I/O Base Classes
361361
:class:`BufferedIOBase` provides or overrides these methods in addition to
362362
those from :class:`IOBase`:
363363

364+
.. method:: detach()
365+
366+
Separate the underlying raw stream from the buffer and return it.
367+
368+
After the raw stream has been detached, the buffer is in an unusable
369+
state.
370+
371+
Some buffers, like :class:`BytesIO`, do not have the concept of a single
372+
raw stream to return from this method. They raise
373+
:exc:`UnsupportedOperation`.
374+
364375
.. method:: read([n])
365376

366377
Read and return up to *n* bytes. If the argument is omitted, ``None``, or
@@ -547,7 +558,9 @@ Buffered Streams
547558

548559
*max_buffer_size* is unused and deprecated.
549560

550-
:class:`BufferedRWPair` implements all of :class:`BufferedIOBase`\'s methods.
561+
:class:`BufferedRWPair` implements all of :class:`BufferedIOBase`\'s methods
562+
except for :meth:`~BufferedIOBase.detach`, which raises
563+
:exc:`UnsupportedOperation`.
551564

552565

553566
.. class:: BufferedRandom(raw[, buffer_size[, max_buffer_size]])
@@ -588,6 +601,17 @@ Text I/O
588601
A string, a tuple of strings, or ``None``, indicating the newlines
589602
translated so far.
590603

604+
.. method:: detach()
605+
606+
Separate the underlying buffer from the :class:`TextIOBase` and return it.
607+
608+
After the underlying buffer has been detached, the :class:`TextIOBase` is
609+
in an unusable state.
610+
611+
Some :class:`TextIOBase` implementations, like :class:`StringIO`, may not
612+
have the concept of an underlying buffer and calling this method will
613+
raise :exc:`UnsupportedOperation`.
614+
591615
.. method:: read(n)
592616

593617
Read and return at most *n* characters from the stream as a single

Lib/_pyio.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,15 @@ def write(self, b: bytes) -> int:
642642
"""
643643
self._unsupported("write")
644644

645+
def detach(self) -> None:
646+
"""
647+
Separate the underlying raw stream from the buffer and return it.
648+
649+
After the raw stream has been detached, the buffer is in an unusable
650+
state.
651+
"""
652+
self._unsupported("detach")
653+
645654
io.BufferedIOBase.register(BufferedIOBase)
646655

647656

@@ -689,13 +698,21 @@ def flush(self):
689698
self.raw.flush()
690699

691700
def close(self):
692-
if not self.closed:
701+
if not self.closed and self.raw is not None:
693702
try:
694703
self.flush()
695704
except IOError:
696705
pass # If flush() fails, just give up
697706
self.raw.close()
698707

708+
def detach(self):
709+
if self.raw is None:
710+
raise ValueError("raw stream already detached")
711+
self.flush()
712+
raw = self.raw
713+
self.raw = None
714+
return raw
715+
699716
### Inquiries ###
700717

701718
def seekable(self):
@@ -1236,6 +1253,15 @@ def readline(self) -> str:
12361253
"""
12371254
self._unsupported("readline")
12381255

1256+
def detach(self) -> None:
1257+
"""
1258+
Separate the underlying buffer from the TextIOBase and return it.
1259+
1260+
After the underlying buffer has been detached, the TextIO is in an
1261+
unusable state.
1262+
"""
1263+
self._unsupported("detach")
1264+
12391265
@property
12401266
def encoding(self):
12411267
"""Subclasses should override."""
@@ -1448,11 +1474,12 @@ def flush(self):
14481474
self._telling = self._seekable
14491475

14501476
def close(self):
1451-
try:
1452-
self.flush()
1453-
except IOError:
1454-
pass # If flush() fails, just give up
1455-
self.buffer.close()
1477+
if self.buffer is not None:
1478+
try:
1479+
self.flush()
1480+
except IOError:
1481+
pass # If flush() fails, just give up
1482+
self.buffer.close()
14561483

14571484
@property
14581485
def closed(self):
@@ -1647,6 +1674,14 @@ def truncate(self, pos=None):
16471674
self.seek(pos)
16481675
return self.buffer.truncate()
16491676

1677+
def detach(self):
1678+
if self.buffer is None:
1679+
raise ValueError("buffer is already detached")
1680+
self.flush()
1681+
buffer = self.buffer
1682+
self.buffer = None
1683+
return buffer
1684+
16501685
def seek(self, cookie, whence=0):
16511686
if self.closed:
16521687
raise ValueError("tell on closed file")
@@ -1865,3 +1900,7 @@ def __repr__(self):
18651900
@property
18661901
def encoding(self):
18671902
return None
1903+
1904+
def detach(self):
1905+
# This doesn't make sense on StringIO.
1906+
self._unsupported("detach")

Lib/test/test_io.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,12 @@ class PyIOTest(IOTest):
526526
class CommonBufferedTests:
527527
# Tests common to BufferedReader, BufferedWriter and BufferedRandom
528528

529+
def test_detach(self):
530+
raw = self.MockRawIO()
531+
buf = self.tp(raw)
532+
self.assertIs(buf.detach(), raw)
533+
self.assertRaises(ValueError, buf.detach)
534+
529535
def test_fileno(self):
530536
rawio = self.MockRawIO()
531537
bufio = self.tp(rawio)
@@ -811,6 +817,14 @@ def test_constructor(self):
811817
bufio.flush()
812818
self.assertEquals(b"".join(rawio._write_stack), b"abcghi")
813819

820+
def test_detach_flush(self):
821+
raw = self.MockRawIO()
822+
buf = self.tp(raw)
823+
buf.write(b"howdy!")
824+
self.assertFalse(raw._write_stack)
825+
buf.detach()
826+
self.assertEqual(raw._write_stack, [b"howdy!"])
827+
814828
def test_write(self):
815829
# Write to the buffered IO but don't overflow the buffer.
816830
writer = self.MockRawIO()
@@ -1052,6 +1066,10 @@ def test_constructor(self):
10521066
pair = self.tp(self.MockRawIO(), self.MockRawIO())
10531067
self.assertFalse(pair.closed)
10541068

1069+
def test_detach(self):
1070+
pair = self.tp(self.MockRawIO(), self.MockRawIO())
1071+
self.assertRaises(self.UnsupportedOperation, pair.detach)
1072+
10551073
def test_constructor_max_buffer_size_deprecation(self):
10561074
with support.check_warnings() as w:
10571075
warnings.simplefilter("always", DeprecationWarning)
@@ -1480,6 +1498,19 @@ def test_constructor(self):
14801498
self.assertRaises(TypeError, t.__init__, b, newline=42)
14811499
self.assertRaises(ValueError, t.__init__, b, newline='xyzzy')
14821500

1501+
def test_detach(self):
1502+
r = self.BytesIO()
1503+
b = self.BufferedWriter(r)
1504+
t = self.TextIOWrapper(b)
1505+
self.assertIs(t.detach(), b)
1506+
1507+
t = self.TextIOWrapper(b, encoding="ascii")
1508+
t.write("howdy")
1509+
self.assertFalse(r.getvalue())
1510+
t.detach()
1511+
self.assertEqual(r.getvalue(), b"howdy")
1512+
self.assertRaises(ValueError, t.detach)
1513+
14831514
def test_repr(self):
14841515
raw = self.BytesIO("hello".encode("utf-8"))
14851516
b = self.BufferedReader(raw)

Lib/test/test_memoryio.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ def testTell(self):
5757

5858
class MemoryTestMixin:
5959

60+
def test_detach(self):
61+
buf = self.ioclass()
62+
self.assertRaises(self.UnsupportedOperation, buf.detach)
63+
6064
def write_ops(self, f, t):
6165
self.assertEqual(f.write(t("blah.")), 5)
6266
self.assertEqual(f.seek(0), 0)
@@ -336,6 +340,9 @@ def __init__(me, a, b):
336340

337341

338342
class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
343+
344+
UnsupportedOperation = pyio.UnsupportedOperation
345+
339346
@staticmethod
340347
def buftype(s):
341348
return s.encode("ascii")
@@ -413,6 +420,7 @@ def test_bytes_array(self):
413420
class PyStringIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
414421
buftype = str
415422
ioclass = pyio.StringIO
423+
UnsupportedOperation = pyio.UnsupportedOperation
416424
EOF = ""
417425

418426
# TextIO-specific behaviour.
@@ -518,9 +526,11 @@ def test_issue5265(self):
518526

519527
class CBytesIOTest(PyBytesIOTest):
520528
ioclass = io.BytesIO
529+
UnsupportedOperation = io.UnsupportedOperation
521530

522531
class CStringIOTest(PyStringIOTest):
523532
ioclass = io.StringIO
533+
UnsupportedOperation = io.UnsupportedOperation
524534

525535
# XXX: For the Python version of io.StringIO, this is highly
526536
# dependent on the encoding used for the underlying buffer.

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ What's New in Python 3.1 beta 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #5883: In the io module, the BufferedIOBase and TextIOBase ABCs have
16+
received a new method, detach(). detach() disconnects the underlying stream
17+
from the buffer or text IO and returns it.
18+
1519
- Issue #5859: Remove switch from '%f' to '%g'-style formatting for
1620
floats with absolute value over 1e50. Also remove length
1721
restrictions for float formatting: '%.67f' % 12.34 and '%.120e' %

Modules/_io/bufferedio.c

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ BufferedIOBase_unsupported(const char *message)
7373
return NULL;
7474
}
7575

76+
PyDoc_STRVAR(BufferedIOBase_detach_doc,
77+
"Disconnect this buffer from its underlying raw stream and return it.\n"
78+
"\n"
79+
"After the raw stream has been detached, the buffer is in an unusable\n"
80+
"state.\n");
81+
82+
static PyObject *
83+
BufferedIOBase_detach(PyObject *self)
84+
{
85+
return BufferedIOBase_unsupported("detach");
86+
}
87+
7688
PyDoc_STRVAR(BufferedIOBase_read_doc,
7789
"Read and return up to n bytes.\n"
7890
"\n"
@@ -127,6 +139,7 @@ BufferedIOBase_write(PyObject *self, PyObject *args)
127139

128140

129141
static PyMethodDef BufferedIOBase_methods[] = {
142+
{"detach", (PyCFunction)BufferedIOBase_detach, METH_NOARGS, BufferedIOBase_detach_doc},
130143
{"read", BufferedIOBase_read, METH_VARARGS, BufferedIOBase_read_doc},
131144
{"read1", BufferedIOBase_read1, METH_VARARGS, BufferedIOBase_read1_doc},
132145
{"readinto", BufferedIOBase_readinto, METH_VARARGS, NULL},
@@ -181,6 +194,7 @@ typedef struct {
181194

182195
PyObject *raw;
183196
int ok; /* Initialized? */
197+
int detached;
184198
int readable;
185199
int writable;
186200

@@ -260,15 +274,25 @@ typedef struct {
260274

261275
#define CHECK_INITIALIZED(self) \
262276
if (self->ok <= 0) { \
263-
PyErr_SetString(PyExc_ValueError, \
264-
"I/O operation on uninitialized object"); \
277+
if (self->detached) { \
278+
PyErr_SetString(PyExc_ValueError, \
279+
"raw stream has been detached"); \
280+
} else { \
281+
PyErr_SetString(PyExc_ValueError, \
282+
"I/O operation on uninitialized object"); \
283+
} \
265284
return NULL; \
266285
}
267286

268287
#define CHECK_INITIALIZED_INT(self) \
269288
if (self->ok <= 0) { \
270-
PyErr_SetString(PyExc_ValueError, \
271-
"I/O operation on uninitialized object"); \
289+
if (self->detached) { \
290+
PyErr_SetString(PyExc_ValueError, \
291+
"raw stream has been detached"); \
292+
} else { \
293+
PyErr_SetString(PyExc_ValueError, \
294+
"I/O operation on uninitialized object"); \
295+
} \
272296
return -1; \
273297
}
274298

@@ -430,6 +454,24 @@ BufferedIOMixin_close(BufferedObject *self, PyObject *args)
430454
return res;
431455
}
432456

457+
/* detach */
458+
459+
static PyObject *
460+
BufferedIOMixin_detach(BufferedObject *self, PyObject *args)
461+
{
462+
PyObject *raw, *res;
463+
CHECK_INITIALIZED(self)
464+
res = PyObject_CallMethodObjArgs((PyObject *)self, _PyIO_str_flush, NULL);
465+
if (res == NULL)
466+
return NULL;
467+
Py_DECREF(res);
468+
raw = self->raw;
469+
self->raw = NULL;
470+
self->detached = 1;
471+
self->ok = 0;
472+
return raw;
473+
}
474+
433475
/* Inquiries */
434476

435477
static PyObject *
@@ -1101,6 +1143,7 @@ BufferedReader_init(BufferedObject *self, PyObject *args, PyObject *kwds)
11011143
PyObject *raw;
11021144

11031145
self->ok = 0;
1146+
self->detached = 0;
11041147

11051148
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|n:BufferedReader", kwlist,
11061149
&raw, &buffer_size)) {
@@ -1387,6 +1430,7 @@ _BufferedReader_peek_unlocked(BufferedObject *self, Py_ssize_t n)
13871430

13881431
static PyMethodDef BufferedReader_methods[] = {
13891432
/* BufferedIOMixin methods */
1433+
{"detach", (PyCFunction)BufferedIOMixin_detach, METH_NOARGS},
13901434
{"flush", (PyCFunction)BufferedIOMixin_flush, METH_NOARGS},
13911435
{"close", (PyCFunction)BufferedIOMixin_close, METH_NOARGS},
13921436
{"seekable", (PyCFunction)BufferedIOMixin_seekable, METH_NOARGS},
@@ -1499,6 +1543,7 @@ BufferedWriter_init(BufferedObject *self, PyObject *args, PyObject *kwds)
14991543
PyObject *raw;
15001544

15011545
self->ok = 0;
1546+
self->detached = 0;
15021547

15031548
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|nn:BufferedReader", kwlist,
15041549
&raw, &buffer_size, &max_buffer_size)) {
@@ -1745,6 +1790,7 @@ BufferedWriter_write(BufferedObject *self, PyObject *args)
17451790
static PyMethodDef BufferedWriter_methods[] = {
17461791
/* BufferedIOMixin methods */
17471792
{"close", (PyCFunction)BufferedIOMixin_close, METH_NOARGS},
1793+
{"detach", (PyCFunction)BufferedIOMixin_detach, METH_NOARGS},
17481794
{"seekable", (PyCFunction)BufferedIOMixin_seekable, METH_NOARGS},
17491795
{"readable", (PyCFunction)BufferedIOMixin_readable, METH_NOARGS},
17501796
{"writable", (PyCFunction)BufferedIOMixin_writable, METH_NOARGS},
@@ -2089,6 +2135,7 @@ BufferedRandom_init(BufferedObject *self, PyObject *args, PyObject *kwds)
20892135
PyObject *raw;
20902136

20912137
self->ok = 0;
2138+
self->detached = 0;
20922139

20932140
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|nn:BufferedReader", kwlist,
20942141
&raw, &buffer_size, &max_buffer_size)) {
@@ -2128,6 +2175,7 @@ BufferedRandom_init(BufferedObject *self, PyObject *args, PyObject *kwds)
21282175
static PyMethodDef BufferedRandom_methods[] = {
21292176
/* BufferedIOMixin methods */
21302177
{"close", (PyCFunction)BufferedIOMixin_close, METH_NOARGS},
2178+
{"detach", (PyCFunction)BufferedIOMixin_detach, METH_NOARGS},
21312179
{"seekable", (PyCFunction)BufferedIOMixin_seekable, METH_NOARGS},
21322180
{"readable", (PyCFunction)BufferedIOMixin_readable, METH_NOARGS},
21332181
{"writable", (PyCFunction)BufferedIOMixin_writable, METH_NOARGS},

0 commit comments

Comments
 (0)