OSError raised on opening a file with mismatched mode
One way of opening a file in Python is by using os.open to open a file descriptor, and then passing that descriptor to io.open (or the open builtin). Both calls require a file mode to be specified, but in a different way: os.open uses OS flags, and io.open a mode string. Both modes, though different in form, should be equivalent, though need not to.
For instance, flag os.O_WRONLY should be matched with 'w' or wb, os.O_RDONLY with 'r' or 'rb', and os.O_RDWR with 'w+' or 'wb+'.
In CPython, when the mode used with io.open does not match the opening flags used with os.open, the opening of the file succeeds, and io.UnsupportedOperation is raised only when the user code attempts to perform read/write operation that was not enabled when the file descriptor was opened.
In IronPython, OSError is raised immediately when the file object is being created, with a cryptic message "raw" argument must be readable. or "raw" argument must be writable.
This difference leads to some situations when the code works in CPython, but not in IronPython. Example:
import os, io
temp_file = "temp_file"
fd = os.open(temp_file, os.O_CREAT | os.O_WRONLY)
f = open(fd, 'w+') # OSError in IPY
f.write("hello\n")
f.close()
fd = os.open(temp_file, os.O_RDONLY)
f = io.open(fd, 'w+') # OSError in IPY
print(f.read())
f.close()
I consider it a minor incompatibility (because, basically, the user code is not quite consistent), but if changing IronPython is too complicated and not worth the effort for the payoff, at least the error message could be changed to something more informative, and the behavior documented in "Differences with CPython".
The difference appears to be that the CPython FileIO object is lying about it's readability/writability. It looks like the io wrappers are supposed to be throwing when you give them an object that's not seekable/readable/writable. Though the proper error messages would be of the form:
io.UnsupportedOperation: File or stream is not seekable.
It can be tested with something like:
import io
class test:
def __init__(self, seekable, readable, writable):
self._seekable = seekable
self._readable = readable
self._writable = writable
def seekable(self): return self._seekable
def readable(self): return self._readable
def writable(self): return self._writable
io.BufferedRandom(test(False, False, False))