Skip to content

Commit 575d8d0

Browse files
erlend-aaslandpython-sidebar
authored andcommitted
pythongh-101819: Adapt _io types to heap types, batch 1 (pythonGH-101949)
Adapt StringIO, TextIOWrapper, FileIO, Buffered*, and BytesIO types. Automerge-Triggered-By: GH:erlend-aasland
1 parent d4d4136 commit 575d8d0

File tree

10 files changed

+463
-478
lines changed

10 files changed

+463
-478
lines changed

Lib/test/test_io.py

+90-1
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,95 @@ def close(self):
10421042
support.gc_collect()
10431043
self.assertIsNone(wr(), wr)
10441044

1045+
@support.cpython_only
1046+
class TestIOCTypes(unittest.TestCase):
1047+
def setUp(self):
1048+
_io = import_helper.import_module("_io")
1049+
self.types = [
1050+
_io.BufferedRWPair,
1051+
_io.BufferedRandom,
1052+
_io.BufferedReader,
1053+
_io.BufferedWriter,
1054+
_io.BytesIO,
1055+
_io.FileIO,
1056+
_io.IncrementalNewlineDecoder,
1057+
_io.StringIO,
1058+
_io.TextIOWrapper,
1059+
_io._BufferedIOBase,
1060+
_io._BytesIOBuffer,
1061+
_io._IOBase,
1062+
_io._RawIOBase,
1063+
_io._TextIOBase,
1064+
]
1065+
if sys.platform == "win32":
1066+
self.types.append(_io._WindowsConsoleIO)
1067+
self._io = _io
1068+
1069+
def test_immutable_types(self):
1070+
for tp in self.types:
1071+
with self.subTest(tp=tp):
1072+
with self.assertRaisesRegex(TypeError, "immutable"):
1073+
tp.foo = "bar"
1074+
1075+
def test_class_hierarchy(self):
1076+
def check_subs(types, base):
1077+
for tp in types:
1078+
with self.subTest(tp=tp, base=base):
1079+
self.assertTrue(issubclass(tp, base))
1080+
1081+
def recursive_check(d):
1082+
for k, v in d.items():
1083+
if isinstance(v, dict):
1084+
recursive_check(v)
1085+
elif isinstance(v, set):
1086+
check_subs(v, k)
1087+
else:
1088+
self.fail("corrupt test dataset")
1089+
1090+
_io = self._io
1091+
hierarchy = {
1092+
_io._IOBase: {
1093+
_io._BufferedIOBase: {
1094+
_io.BufferedRWPair,
1095+
_io.BufferedRandom,
1096+
_io.BufferedReader,
1097+
_io.BufferedWriter,
1098+
_io.BytesIO,
1099+
},
1100+
_io._RawIOBase: {
1101+
_io.FileIO,
1102+
},
1103+
_io._TextIOBase: {
1104+
_io.StringIO,
1105+
_io.TextIOWrapper,
1106+
},
1107+
},
1108+
}
1109+
if sys.platform == "win32":
1110+
hierarchy[_io._IOBase][_io._RawIOBase].add(_io._WindowsConsoleIO)
1111+
1112+
recursive_check(hierarchy)
1113+
1114+
def test_subclassing(self):
1115+
_io = self._io
1116+
dataset = {k: True for k in self.types}
1117+
dataset[_io._BytesIOBuffer] = False
1118+
1119+
for tp, is_basetype in dataset.items():
1120+
with self.subTest(tp=tp, is_basetype=is_basetype):
1121+
name = f"{tp.__name__}_subclass"
1122+
bases = (tp,)
1123+
if is_basetype:
1124+
_ = type(name, bases, {})
1125+
else:
1126+
msg = "not an acceptable base type"
1127+
with self.assertRaisesRegex(TypeError, msg):
1128+
_ = type(name, bases, {})
1129+
1130+
def test_disallow_instantiation(self):
1131+
_io = self._io
1132+
support.check_disallow_instantiation(self, _io._BytesIOBuffer)
1133+
10451134
class PyIOTest(IOTest):
10461135
pass
10471136

@@ -4671,7 +4760,7 @@ def load_tests(loader, tests, pattern):
46714760
CIncrementalNewlineDecoderTest, PyIncrementalNewlineDecoderTest,
46724761
CTextIOWrapperTest, PyTextIOWrapperTest,
46734762
CMiscIOTest, PyMiscIOTest,
4674-
CSignalsTest, PySignalsTest,
4763+
CSignalsTest, PySignalsTest, TestIOCTypes,
46754764
)
46764765

46774766
# Put the namespaces of the IO module we are testing and some useful mock

Modules/_io/_iomodule.c

+62-37
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#define PY_SSIZE_T_CLEAN
1111
#include "Python.h"
1212
#include "_iomodule.h"
13-
#include "pycore_moduleobject.h" // _PyModule_GetState()
1413
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1514

1615
#ifdef HAVE_SYS_TYPES_H
@@ -315,8 +314,9 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode,
315314
}
316315

317316
/* Create the Raw file stream */
317+
_PyIO_State *state = get_io_state(module);
318318
{
319-
PyObject *RawIO_class = (PyObject *)&PyFileIO_Type;
319+
PyObject *RawIO_class = (PyObject *)state->PyFileIO_Type;
320320
#ifdef MS_WINDOWS
321321
const PyConfig *config = _Py_GetConfig();
322322
if (!config->legacy_windows_stdio && _PyIO_get_console_type(path_or_fd) != '\0') {
@@ -390,12 +390,15 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode,
390390
{
391391
PyObject *Buffered_class;
392392

393-
if (updating)
394-
Buffered_class = (PyObject *)&PyBufferedRandom_Type;
395-
else if (creating || writing || appending)
396-
Buffered_class = (PyObject *)&PyBufferedWriter_Type;
397-
else if (reading)
398-
Buffered_class = (PyObject *)&PyBufferedReader_Type;
393+
if (updating) {
394+
Buffered_class = (PyObject *)state->PyBufferedRandom_Type;
395+
}
396+
else if (creating || writing || appending) {
397+
Buffered_class = (PyObject *)state->PyBufferedWriter_Type;
398+
}
399+
else if (reading) {
400+
Buffered_class = (PyObject *)state->PyBufferedReader_Type;
401+
}
399402
else {
400403
PyErr_Format(PyExc_ValueError,
401404
"unknown mode: '%s'", mode);
@@ -417,7 +420,7 @@ _io_open_impl(PyObject *module, PyObject *file, const char *mode,
417420
}
418421

419422
/* wraps into a TextIOWrapper */
420-
wrapper = PyObject_CallFunction((PyObject *)&PyTextIOWrapper_Type,
423+
wrapper = PyObject_CallFunction((PyObject *)state->PyTextIOWrapper_Type,
421424
"OsssO",
422425
buffer,
423426
encoding, errors, newline,
@@ -558,14 +561,6 @@ PyNumber_AsOff_t(PyObject *item, PyObject *err)
558561
return result;
559562
}
560563

561-
static inline _PyIO_State*
562-
get_io_state(PyObject *module)
563-
{
564-
void *state = _PyModule_GetState(module);
565-
assert(state != NULL);
566-
return (_PyIO_State *)state;
567-
}
568-
569564
_PyIO_State *
570565
_PyIO_get_module_state(void)
571566
{
@@ -587,6 +582,15 @@ iomodule_traverse(PyObject *mod, visitproc visit, void *arg) {
587582
return 0;
588583
Py_VISIT(state->locale_module);
589584
Py_VISIT(state->unsupported_operation);
585+
586+
Py_VISIT(state->PyBufferedRWPair_Type);
587+
Py_VISIT(state->PyBufferedRandom_Type);
588+
Py_VISIT(state->PyBufferedReader_Type);
589+
Py_VISIT(state->PyBufferedWriter_Type);
590+
Py_VISIT(state->PyBytesIO_Type);
591+
Py_VISIT(state->PyFileIO_Type);
592+
Py_VISIT(state->PyStringIO_Type);
593+
Py_VISIT(state->PyTextIOWrapper_Type);
590594
return 0;
591595
}
592596

@@ -599,6 +603,15 @@ iomodule_clear(PyObject *mod) {
599603
if (state->locale_module != NULL)
600604
Py_CLEAR(state->locale_module);
601605
Py_CLEAR(state->unsupported_operation);
606+
607+
Py_CLEAR(state->PyBufferedRWPair_Type);
608+
Py_CLEAR(state->PyBufferedRandom_Type);
609+
Py_CLEAR(state->PyBufferedReader_Type);
610+
Py_CLEAR(state->PyBufferedWriter_Type);
611+
Py_CLEAR(state->PyBytesIO_Type);
612+
Py_CLEAR(state->PyFileIO_Type);
613+
Py_CLEAR(state->PyStringIO_Type);
614+
Py_CLEAR(state->PyTextIOWrapper_Type);
602615
return 0;
603616
}
604617

@@ -612,7 +625,9 @@ iomodule_free(PyObject *mod) {
612625
* Module definition
613626
*/
614627

628+
#define clinic_state() (get_io_state(module))
615629
#include "clinic/_iomodule.c.h"
630+
#undef clinic_state
616631

617632
static PyMethodDef module_methods[] = {
618633
_IO_OPEN_METHODDEF
@@ -644,23 +659,11 @@ static PyTypeObject* static_types[] = {
644659
&PyRawIOBase_Type,
645660
&PyTextIOBase_Type,
646661

647-
// PyBufferedIOBase_Type(PyIOBase_Type) subclasses
648-
&PyBytesIO_Type,
649-
&PyBufferedReader_Type,
650-
&PyBufferedWriter_Type,
651-
&PyBufferedRWPair_Type,
652-
&PyBufferedRandom_Type,
653-
654662
// PyRawIOBase_Type(PyIOBase_Type) subclasses
655-
&PyFileIO_Type,
656663
&_PyBytesIOBuffer_Type,
657664
#ifdef MS_WINDOWS
658665
&PyWindowsConsoleIO_Type,
659666
#endif
660-
661-
// PyTextIOBase_Type(PyIOBase_Type) subclasses
662-
&PyStringIO_Type,
663-
&PyTextIOWrapper_Type,
664667
};
665668

666669

@@ -673,6 +676,17 @@ _PyIO_Fini(void)
673676
}
674677
}
675678

679+
#define ADD_TYPE(module, type, spec, base) \
680+
do { \
681+
type = (PyTypeObject *)PyType_FromModuleAndSpec(module, spec, \
682+
(PyObject *)base); \
683+
if (type == NULL) { \
684+
goto fail; \
685+
} \
686+
if (PyModule_AddType(module, type) < 0) { \
687+
goto fail; \
688+
} \
689+
} while (0)
676690

677691
PyMODINIT_FUNC
678692
PyInit__io(void)
@@ -705,17 +719,9 @@ PyInit__io(void)
705719
}
706720

707721
// Set type base classes
708-
PyFileIO_Type.tp_base = &PyRawIOBase_Type;
709-
PyBytesIO_Type.tp_base = &PyBufferedIOBase_Type;
710-
PyStringIO_Type.tp_base = &PyTextIOBase_Type;
711722
#ifdef MS_WINDOWS
712723
PyWindowsConsoleIO_Type.tp_base = &PyRawIOBase_Type;
713724
#endif
714-
PyBufferedReader_Type.tp_base = &PyBufferedIOBase_Type;
715-
PyBufferedWriter_Type.tp_base = &PyBufferedIOBase_Type;
716-
PyBufferedRWPair_Type.tp_base = &PyBufferedIOBase_Type;
717-
PyBufferedRandom_Type.tp_base = &PyBufferedIOBase_Type;
718-
PyTextIOWrapper_Type.tp_base = &PyTextIOBase_Type;
719725

720726
// Add types
721727
for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) {
@@ -725,6 +731,25 @@ PyInit__io(void)
725731
}
726732
}
727733

734+
// PyBufferedIOBase_Type(PyIOBase_Type) subclasses
735+
ADD_TYPE(m, state->PyBytesIO_Type, &bytesio_spec, &PyBufferedIOBase_Type);
736+
ADD_TYPE(m, state->PyBufferedWriter_Type, &bufferedwriter_spec,
737+
&PyBufferedIOBase_Type);
738+
ADD_TYPE(m, state->PyBufferedReader_Type, &bufferedreader_spec,
739+
&PyBufferedIOBase_Type);
740+
ADD_TYPE(m, state->PyBufferedRWPair_Type, &bufferedrwpair_spec,
741+
&PyBufferedIOBase_Type);
742+
ADD_TYPE(m, state->PyBufferedRandom_Type, &bufferedrandom_spec,
743+
&PyBufferedIOBase_Type);
744+
745+
// PyRawIOBase_Type(PyIOBase_Type) subclasses
746+
ADD_TYPE(m, state->PyFileIO_Type, &fileio_spec, &PyRawIOBase_Type);
747+
748+
// PyTextIOBase_Type(PyIOBase_Type) subclasses
749+
ADD_TYPE(m, state->PyStringIO_Type, &stringio_spec, &PyTextIOBase_Type);
750+
ADD_TYPE(m, state->PyTextIOWrapper_Type, &textiowrapper_spec,
751+
&PyTextIOBase_Type);
752+
728753
state->initialized = 1;
729754

730755
return m;

Modules/_io/_iomodule.h

+39-8
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,28 @@
44

55
#include "exports.h"
66

7+
#include "pycore_moduleobject.h" // _PyModule_GetState()
8+
#include "structmember.h"
9+
710
/* ABCs */
811
extern PyTypeObject PyIOBase_Type;
912
extern PyTypeObject PyRawIOBase_Type;
1013
extern PyTypeObject PyBufferedIOBase_Type;
1114
extern PyTypeObject PyTextIOBase_Type;
1215

1316
/* Concrete classes */
14-
extern PyTypeObject PyFileIO_Type;
15-
extern PyTypeObject PyBytesIO_Type;
16-
extern PyTypeObject PyStringIO_Type;
17-
extern PyTypeObject PyBufferedReader_Type;
18-
extern PyTypeObject PyBufferedWriter_Type;
19-
extern PyTypeObject PyBufferedRWPair_Type;
20-
extern PyTypeObject PyBufferedRandom_Type;
21-
extern PyTypeObject PyTextIOWrapper_Type;
2217
extern PyTypeObject PyIncrementalNewlineDecoder_Type;
2318

19+
/* Type specs */
20+
extern PyType_Spec bufferedrandom_spec;
21+
extern PyType_Spec bufferedreader_spec;
22+
extern PyType_Spec bufferedrwpair_spec;
23+
extern PyType_Spec bufferedwriter_spec;
24+
extern PyType_Spec bytesio_spec;
25+
extern PyType_Spec fileio_spec;
26+
extern PyType_Spec stringio_spec;
27+
extern PyType_Spec textiowrapper_spec;
28+
2429
#ifdef MS_WINDOWS
2530
extern PyTypeObject PyWindowsConsoleIO_Type;
2631
#endif /* MS_WINDOWS */
@@ -140,11 +145,37 @@ typedef struct {
140145
PyObject *locale_module;
141146

142147
PyObject *unsupported_operation;
148+
149+
/* Types */
150+
PyTypeObject *PyBufferedRWPair_Type;
151+
PyTypeObject *PyBufferedRandom_Type;
152+
PyTypeObject *PyBufferedReader_Type;
153+
PyTypeObject *PyBufferedWriter_Type;
154+
PyTypeObject *PyBytesIO_Type;
155+
PyTypeObject *PyFileIO_Type;
156+
PyTypeObject *PyStringIO_Type;
157+
PyTypeObject *PyTextIOWrapper_Type;
143158
} _PyIO_State;
144159

145160
#define IO_MOD_STATE(mod) ((_PyIO_State *)PyModule_GetState(mod))
146161
#define IO_STATE() _PyIO_get_module_state()
147162

163+
static inline _PyIO_State *
164+
get_io_state(PyObject *module)
165+
{
166+
void *state = _PyModule_GetState(module);
167+
assert(state != NULL);
168+
return (_PyIO_State *)state;
169+
}
170+
171+
static inline _PyIO_State *
172+
find_io_state_by_def(PyTypeObject *type)
173+
{
174+
PyObject *mod = PyType_GetModuleByDef(type, &_PyIO_Module);
175+
assert(mod != NULL);
176+
return get_io_state(mod);
177+
}
178+
148179
extern _PyIO_State *_PyIO_get_module_state(void);
149180

150181
#ifdef MS_WINDOWS

0 commit comments

Comments
 (0)