ironpython3 icon indicating copy to clipboard operation
ironpython3 copied to clipboard

OSError raised on opening a file with mismatched mode

Open BCSharp opened this issue 1 year ago • 1 comments

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".

BCSharp avatar Dec 17 '24 21:12 BCSharp

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))

slozier avatar Dec 18 '24 03:12 slozier