Skip to content

Commit feab4eb

Browse files
committed
_io.open() clarify and document
Align logic of _io.open() to language specification and tighten error detection in supporting PyFileIO constructor. No change to test_io scores. (0/6/65)
1 parent 6faf453 commit feab4eb

2 files changed

Lines changed: 79 additions & 54 deletions

File tree

src/org/python/modules/_io/PyFileIO.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,18 @@ private void setDelegate(PyObject file, OpenMode mode, boolean closefd) {
168168
throw Py.OSError(Errno.EBADF);
169169
}
170170
}
171-
172-
} else {
173-
// The file was a type we don't know how to use
174-
throw Py.TypeError(String.format("invalid file: %s", file.__repr__().asString()));
175171
}
176172
}
177173

174+
// If we couldn't figure it out, ioDelegate will still be null
175+
if (ioDelegate == null) {
176+
// The file was a type we don't know how to use
177+
throw Py.TypeError(String.format("invalid file: %s", file.__repr__().asString()));
178+
}
179+
178180
// The name is either the textual name or a file descriptor (see Python docs)
179181
fastGetDict().__setitem__("name", file);
182+
180183
}
181184

182185
private static final String[] openArgs = {"file", "mode", "closefd"};
@@ -432,6 +435,11 @@ final PyObject FileIO_fileno() {
432435
return PyJavaType.wrapJavaObject(ioDelegate.fileno());
433436
}
434437

438+
@Override
439+
public boolean isatty() {
440+
return FileIO_isatty();
441+
}
442+
435443
@ExposedMethod(doc = isatty_doc)
436444
final boolean FileIO_isatty() {
437445
if (__closed) {

src/org/python/modules/_io/_io.java

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -119,76 +119,93 @@ public void validate() {
119119

120120
mode.checkValid();
121121

122-
int line_buffering;
123-
124122
/*
125123
* Create the Raw file stream. Let the constructor deal with the variants and argument
126124
* checking.
127125
*/
128126
PyFileIO raw = new PyFileIO(file, mode, closefd);
129127

130-
// XXX Can this work: boolean isatty = raw.isatty() ? Or maybe:
131-
// PyObject res = PyObject_CallMethod(raw, "isatty", NULL);
132-
boolean isatty = false;
133-
134128
/*
135-
* Work out a felicitous buffer size
129+
* From the Python documentation for io.open() buffering = 0 to switch buffering off (only
130+
* allowed in binary mode), 1 to select line buffering (only usable in text mode), and an
131+
* integer > 1 to indicate the size of a fixed-size buffer.
132+
*
133+
* When no buffering argument is given, the default buffering policy works as follows:
134+
* Binary files are buffered in fixed-size chunks; "Interactive" text files (files for which
135+
* isatty() returns True) use line buffering. Other text files use the policy described
136+
* above for binary files.
137+
*
138+
* In Java, it seems a stream never is *known* to be interactive, but we ask anyway, and
139+
* maybe one day we shall know.
136140
*/
137-
if (buffering == 1 || (buffering < 0 && isatty)) {
138-
buffering = -1;
139-
line_buffering = 1;
140-
} else {
141-
line_buffering = 0;
142-
}
143-
144-
if (buffering < 0) {
145-
// Try to establish the default buffer size for this file using the OS.
146-
buffering = _DEFAULT_BUFFER_SIZE;
147-
// PyObject res = PyObject_CallMethod(raw, "fileno", NULL);
148-
// if (fstat(fileno, &st) >= 0 && st.st_blksize > 1)
149-
// buffering = st.st_blksize;
150-
}
141+
boolean line_buffering = false;
151142

152-
if (buffering < 0) {
153-
throw Py.ValueError("invalid buffering size");
154-
}
155-
156-
// If not buffering, return the raw file object
157143
if (buffering == 0) {
158144
if (!mode.binary) {
159145
throw Py.ValueError("can't have unbuffered text I/O");
160146
}
161147
return raw;
162-
}
163148

164-
// We are buffering, so wrap raw into a buffered file
165-
PyObject bufferType = null;
166-
PyObject io = imp.load("io");
149+
} else if (buffering == 1) {
150+
// The stream is to be read line-by-line.
151+
line_buffering = true;
152+
// Force default size for actual buffer
153+
buffering = -1;
167154

168-
if (mode.updating) {
169-
bufferType = io.__getattr__("BufferedRandom");
170-
} else if (mode.writing || mode.appending) {
171-
bufferType = io.__getattr__("BufferedWriter");
172-
} else { // = reading
173-
bufferType = io.__getattr__("BufferedReader");
155+
} else if (buffering < 0 && raw.isatty()) {
156+
// No buffering argument given but stream is inteeractive.
157+
line_buffering = true;
174158
}
175159

176-
PyInteger pyBuffering = new PyInteger(buffering);
177-
PyObject buffer = bufferType.__call__(raw, pyBuffering);
178-
179-
// If binary, return the buffered file
180-
if (mode.binary) {
181-
return buffer;
160+
if (buffering < 0) {
161+
/*
162+
* We are still being asked for the default buffer size. CPython establishes the default
163+
* buffer size using fstat(fd), but Java appears to give no clue. A useful study of
164+
* buffer sizes in NIO is http://www.evanjones.ca/software/java-bytebuffers.html . This
165+
* leads us to the fixed choice of _DEFAULT_BUFFER_SIZE (=8KB).
166+
*/
167+
buffering = _DEFAULT_BUFFER_SIZE;
182168
}
183169

184-
/* We are opening in text mode, so wrap buffer into a TextIOWrapper */
185-
PyObject textType = io.__getattr__("TextIOWrapper");
186-
PyObject[] textArgs =
187-
{buffer, ap.getPyObject(3, Py.None), ap.getPyObject(4, Py.None),
188-
ap.getPyObject(5, Py.None), Py.newInteger(line_buffering)};
189-
PyObject wrapper = textType.__call__(textArgs);
190-
wrapper.__setattr__("mode", new PyString(m));
191-
return wrapper;
170+
/*
171+
* We now know just what particular class of file we are opening, and therefore what stack
172+
* (buffering and text encoding) we should build.
173+
*/
174+
if (buffering == 0) {
175+
// Not buffering, return the raw file object
176+
return raw;
177+
178+
} else {
179+
// We are buffering, so wrap raw into a buffered file
180+
PyObject bufferType = null;
181+
PyObject io = imp.load("io");
182+
183+
if (mode.updating) {
184+
bufferType = io.__getattr__("BufferedRandom");
185+
} else if (mode.writing || mode.appending) {
186+
bufferType = io.__getattr__("BufferedWriter");
187+
} else { // = reading
188+
bufferType = io.__getattr__("BufferedReader");
189+
}
190+
191+
PyInteger pyBuffering = new PyInteger(buffering);
192+
PyObject buffer = bufferType.__call__(raw, pyBuffering);
193+
194+
if (mode.binary) {
195+
// If binary, return the just the buffered file
196+
return buffer;
197+
198+
} else {
199+
// We are opening in text mode, so wrap buffered file in a TextIOWrapper.
200+
PyObject textType = io.__getattr__("TextIOWrapper");
201+
PyObject[] textArgs =
202+
{buffer, ap.getPyObject(3, Py.None), ap.getPyObject(4, Py.None),
203+
ap.getPyObject(5, Py.None), Py.newBoolean(line_buffering)};
204+
PyObject wrapper = textType.__call__(textArgs);
205+
wrapper.__setattr__("mode", new PyString(m));
206+
return wrapper;
207+
}
208+
}
192209
}
193210

194211
private static final String[] openKwds = {"file", "mode", "buffering", "encoding", "errors",

0 commit comments

Comments
 (0)