Skip to content

Commit cc479d0

Browse files
committed
allow more operations to work on detached streams (closes #23093)
Patch by Martin Panter.
1 parent 4f29b37 commit cc479d0

File tree

5 files changed

+61
-42
lines changed

5 files changed

+61
-42
lines changed

Lib/_pyio.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ def __repr__(self):
793793
clsname = self.__class__.__name__
794794
try:
795795
name = self.name
796-
except AttributeError:
796+
except Exception:
797797
return "<_pyio.{0}>".format(clsname)
798798
else:
799799
return "<_pyio.{0} name={1!r}>".format(clsname, name)
@@ -1561,13 +1561,13 @@ def __repr__(self):
15611561
result = "<_pyio.TextIOWrapper"
15621562
try:
15631563
name = self.name
1564-
except AttributeError:
1564+
except Exception:
15651565
pass
15661566
else:
15671567
result += " name={0!r}".format(name)
15681568
try:
15691569
mode = self.mode
1570-
except AttributeError:
1570+
except Exception:
15711571
pass
15721572
else:
15731573
result += " mode={0!r}".format(mode)

Lib/test/test_io.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,8 @@ def test_detach(self):
701701
self.assertIs(buf.detach(), raw)
702702
self.assertRaises(ValueError, buf.detach)
703703

704+
repr(buf) # Should still work
705+
704706
def test_fileno(self):
705707
rawio = self.MockRawIO()
706708
bufio = self.tp(rawio)
@@ -2026,6 +2028,12 @@ def test_detach(self):
20262028
self.assertEqual(r.getvalue(), b"howdy")
20272029
self.assertRaises(ValueError, t.detach)
20282030

2031+
# Operations independent of the detached stream should still work
2032+
repr(t)
2033+
self.assertEqual(t.encoding, "ascii")
2034+
self.assertEqual(t.errors, "strict")
2035+
self.assertFalse(t.line_buffering)
2036+
20292037
def test_repr(self):
20302038
raw = self.BytesIO("hello".encode("utf-8"))
20312039
b = self.BufferedReader(raw)
@@ -2043,6 +2051,9 @@ def test_repr(self):
20432051
self.assertEqual(repr(t),
20442052
"<%s.TextIOWrapper name=b'dummy' mode='r' encoding='utf-8'>" % modname)
20452053

2054+
t.buffer.detach()
2055+
repr(t) # Should not raise an exception
2056+
20462057
def test_line_buffering(self):
20472058
r = self.BytesIO()
20482059
b = self.BufferedWriter(r, 1000)
@@ -2813,6 +2824,9 @@ def test_initialization(self):
28132824
self.assertRaises(ValueError, t.__init__, b, newline='xyzzy')
28142825
self.assertRaises(ValueError, t.read)
28152826

2827+
t = self.TextIOWrapper.__new__(self.TextIOWrapper)
2828+
self.assertRaises(Exception, repr, t)
2829+
28162830
def test_garbage_collection(self):
28172831
# C TextIOWrapper objects are collected, and collecting them flushes
28182832
# all data to disk.

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ Core and Builtins
4141
Library
4242
-------
4343

44+
- Issue #23093: In the io, module allow more operations to work on detached
45+
streams.
46+
4447
- Issue #19104: pprint now produces evaluable output for wrapped strings.
4548

4649
- Issue #23071: Added missing names to codecs.__all__. Patch by Martin Panter.

Modules/_io/bufferedio.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1359,7 +1359,7 @@ buffered_repr(buffered *self)
13591359

13601360
nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
13611361
if (nameobj == NULL) {
1362-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
1362+
if (PyErr_ExceptionMatches(PyExc_Exception))
13631363
PyErr_Clear();
13641364
else
13651365
return NULL;

Modules/_io/textio.c

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,25 +1219,27 @@ textiowrapper_closed_get(textio *self, void *context);
12191219

12201220
#define CHECK_INITIALIZED(self) \
12211221
if (self->ok <= 0) { \
1222-
if (self->detached) { \
1223-
PyErr_SetString(PyExc_ValueError, \
1224-
"underlying buffer has been detached"); \
1225-
} else { \
1226-
PyErr_SetString(PyExc_ValueError, \
1227-
"I/O operation on uninitialized object"); \
1228-
} \
1222+
PyErr_SetString(PyExc_ValueError, \
1223+
"I/O operation on uninitialized object"); \
1224+
return NULL; \
1225+
}
1226+
1227+
#define CHECK_ATTACHED(self) \
1228+
CHECK_INITIALIZED(self); \
1229+
if (self->detached) { \
1230+
PyErr_SetString(PyExc_ValueError, \
1231+
"underlying buffer has been detached"); \
12291232
return NULL; \
12301233
}
12311234

1232-
#define CHECK_INITIALIZED_INT(self) \
1235+
#define CHECK_ATTACHED_INT(self) \
12331236
if (self->ok <= 0) { \
1234-
if (self->detached) { \
1235-
PyErr_SetString(PyExc_ValueError, \
1236-
"underlying buffer has been detached"); \
1237-
} else { \
1238-
PyErr_SetString(PyExc_ValueError, \
1239-
"I/O operation on uninitialized object"); \
1240-
} \
1237+
PyErr_SetString(PyExc_ValueError, \
1238+
"I/O operation on uninitialized object"); \
1239+
return -1; \
1240+
} else if (self->detached) { \
1241+
PyErr_SetString(PyExc_ValueError, \
1242+
"underlying buffer has been detached"); \
12411243
return -1; \
12421244
}
12431245

@@ -1246,15 +1248,14 @@ static PyObject *
12461248
textiowrapper_detach(textio *self)
12471249
{
12481250
PyObject *buffer, *res;
1249-
CHECK_INITIALIZED(self);
1251+
CHECK_ATTACHED(self);
12501252
res = PyObject_CallMethodObjArgs((PyObject *)self, _PyIO_str_flush, NULL);
12511253
if (res == NULL)
12521254
return NULL;
12531255
Py_DECREF(res);
12541256
buffer = self->buffer;
12551257
self->buffer = NULL;
12561258
self->detached = 1;
1257-
self->ok = 0;
12581259
return buffer;
12591260
}
12601261

@@ -1299,7 +1300,7 @@ textiowrapper_write(textio *self, PyObject *args)
12991300
int haslf = 0;
13001301
int needflush = 0, text_needflush = 0;
13011302

1302-
CHECK_INITIALIZED(self);
1303+
CHECK_ATTACHED(self);
13031304

13041305
if (!PyArg_ParseTuple(args, "U:write", &text)) {
13051306
return NULL;
@@ -1556,7 +1557,7 @@ textiowrapper_read(textio *self, PyObject *args)
15561557
Py_ssize_t n = -1;
15571558
PyObject *result = NULL, *chunks = NULL;
15581559

1559-
CHECK_INITIALIZED(self);
1560+
CHECK_ATTACHED(self);
15601561

15611562
if (!PyArg_ParseTuple(args, "|O&:read", &_PyIO_ConvertSsize_t, &n))
15621563
return NULL;
@@ -1931,7 +1932,7 @@ textiowrapper_readline(textio *self, PyObject *args)
19311932
{
19321933
Py_ssize_t limit = -1;
19331934

1934-
CHECK_INITIALIZED(self);
1935+
CHECK_ATTACHED(self);
19351936
if (!PyArg_ParseTuple(args, "|n:readline", &limit)) {
19361937
return NULL;
19371938
}
@@ -2069,7 +2070,7 @@ textiowrapper_seek(textio *self, PyObject *args)
20692070
PyObject *res;
20702071
int cmp;
20712072

2072-
CHECK_INITIALIZED(self);
2073+
CHECK_ATTACHED(self);
20732074

20742075
if (!PyArg_ParseTuple(args, "O|i:seek", &cookieObj, &whence))
20752076
return NULL;
@@ -2249,7 +2250,7 @@ textiowrapper_tell(textio *self, PyObject *args)
22492250
Py_ssize_t dec_buffer_len;
22502251
int dec_flags;
22512252

2252-
CHECK_INITIALIZED(self);
2253+
CHECK_ATTACHED(self);
22532254
CHECK_CLOSED(self);
22542255

22552256
if (!self->seekable) {
@@ -2452,7 +2453,7 @@ textiowrapper_truncate(textio *self, PyObject *args)
24522453
PyObject *pos = Py_None;
24532454
PyObject *res;
24542455

2455-
CHECK_INITIALIZED(self)
2456+
CHECK_ATTACHED(self)
24562457
if (!PyArg_ParseTuple(args, "|O:truncate", &pos)) {
24572458
return NULL;
24582459
}
@@ -2475,9 +2476,10 @@ textiowrapper_repr(textio *self)
24752476
res = PyUnicode_FromString("<_io.TextIOWrapper");
24762477
if (res == NULL)
24772478
return NULL;
2479+
24782480
nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
24792481
if (nameobj == NULL) {
2480-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
2482+
if (PyErr_ExceptionMatches(PyExc_Exception))
24812483
PyErr_Clear();
24822484
else
24832485
goto error;
@@ -2493,7 +2495,7 @@ textiowrapper_repr(textio *self)
24932495
}
24942496
modeobj = _PyObject_GetAttrId((PyObject *) self, &PyId_mode);
24952497
if (modeobj == NULL) {
2496-
if (PyErr_ExceptionMatches(PyExc_AttributeError))
2498+
if (PyErr_ExceptionMatches(PyExc_Exception))
24972499
PyErr_Clear();
24982500
else
24992501
goto error;
@@ -2522,35 +2524,35 @@ textiowrapper_repr(textio *self)
25222524
static PyObject *
25232525
textiowrapper_fileno(textio *self, PyObject *args)
25242526
{
2525-
CHECK_INITIALIZED(self);
2527+
CHECK_ATTACHED(self);
25262528
return _PyObject_CallMethodId(self->buffer, &PyId_fileno, NULL);
25272529
}
25282530

25292531
static PyObject *
25302532
textiowrapper_seekable(textio *self, PyObject *args)
25312533
{
2532-
CHECK_INITIALIZED(self);
2534+
CHECK_ATTACHED(self);
25332535
return _PyObject_CallMethodId(self->buffer, &PyId_seekable, NULL);
25342536
}
25352537

25362538
static PyObject *
25372539
textiowrapper_readable(textio *self, PyObject *args)
25382540
{
2539-
CHECK_INITIALIZED(self);
2541+
CHECK_ATTACHED(self);
25402542
return _PyObject_CallMethodId(self->buffer, &PyId_readable, NULL);
25412543
}
25422544

25432545
static PyObject *
25442546
textiowrapper_writable(textio *self, PyObject *args)
25452547
{
2546-
CHECK_INITIALIZED(self);
2548+
CHECK_ATTACHED(self);
25472549
return _PyObject_CallMethodId(self->buffer, &PyId_writable, NULL);
25482550
}
25492551

25502552
static PyObject *
25512553
textiowrapper_isatty(textio *self, PyObject *args)
25522554
{
2553-
CHECK_INITIALIZED(self);
2555+
CHECK_ATTACHED(self);
25542556
return _PyObject_CallMethodId(self->buffer, &PyId_isatty, NULL);
25552557
}
25562558

@@ -2565,7 +2567,7 @@ textiowrapper_getstate(textio *self, PyObject *args)
25652567
static PyObject *
25662568
textiowrapper_flush(textio *self, PyObject *args)
25672569
{
2568-
CHECK_INITIALIZED(self);
2570+
CHECK_ATTACHED(self);
25692571
CHECK_CLOSED(self);
25702572
self->telling = self->seekable;
25712573
if (_textiowrapper_writeflush(self) < 0)
@@ -2578,7 +2580,7 @@ textiowrapper_close(textio *self, PyObject *args)
25782580
{
25792581
PyObject *res;
25802582
int r;
2581-
CHECK_INITIALIZED(self);
2583+
CHECK_ATTACHED(self);
25822584

25832585
res = textiowrapper_closed_get(self, NULL);
25842586
if (res == NULL)
@@ -2620,7 +2622,7 @@ textiowrapper_iternext(textio *self)
26202622
{
26212623
PyObject *line;
26222624

2623-
CHECK_INITIALIZED(self);
2625+
CHECK_ATTACHED(self);
26242626

26252627
self->telling = 0;
26262628
if (Py_TYPE(self) == &PyTextIOWrapper_Type) {
@@ -2656,22 +2658,22 @@ textiowrapper_iternext(textio *self)
26562658
static PyObject *
26572659
textiowrapper_name_get(textio *self, void *context)
26582660
{
2659-
CHECK_INITIALIZED(self);
2661+
CHECK_ATTACHED(self);
26602662
return _PyObject_GetAttrId(self->buffer, &PyId_name);
26612663
}
26622664

26632665
static PyObject *
26642666
textiowrapper_closed_get(textio *self, void *context)
26652667
{
2666-
CHECK_INITIALIZED(self);
2668+
CHECK_ATTACHED(self);
26672669
return PyObject_GetAttr(self->buffer, _PyIO_str_closed);
26682670
}
26692671

26702672
static PyObject *
26712673
textiowrapper_newlines_get(textio *self, void *context)
26722674
{
26732675
PyObject *res;
2674-
CHECK_INITIALIZED(self);
2676+
CHECK_ATTACHED(self);
26752677
if (self->decoder == NULL)
26762678
Py_RETURN_NONE;
26772679
res = PyObject_GetAttr(self->decoder, _PyIO_str_newlines);
@@ -2697,15 +2699,15 @@ textiowrapper_errors_get(textio *self, void *context)
26972699
static PyObject *
26982700
textiowrapper_chunk_size_get(textio *self, void *context)
26992701
{
2700-
CHECK_INITIALIZED(self);
2702+
CHECK_ATTACHED(self);
27012703
return PyLong_FromSsize_t(self->chunk_size);
27022704
}
27032705

27042706
static int
27052707
textiowrapper_chunk_size_set(textio *self, PyObject *arg, void *context)
27062708
{
27072709
Py_ssize_t n;
2708-
CHECK_INITIALIZED_INT(self);
2710+
CHECK_ATTACHED_INT(self);
27092711
n = PyNumber_AsSsize_t(arg, PyExc_ValueError);
27102712
if (n == -1 && PyErr_Occurred())
27112713
return -1;

0 commit comments

Comments
 (0)