python/cpython · error · UnsupportedOperation

File or stream is not writable.

Error message

File or stream is not writable.

What it means

Raised by IOBase._checkWritable() as io.UnsupportedOperation when write() (and writelines/truncate) is called on a stream whose writable() returns False — a stream opened read-only. Like 178, it enforces the open mode at call time for every IOBase-derived stream (files, sockets wrappers, BytesIO from binary payloads, etc.).

Source

Thrown at Lib/_pyio.py:462

    def _checkReadable(self, msg=None):
        """Internal: raise UnsupportedOperation if file is not readable
        """
        if not self.readable():
            raise UnsupportedOperation("File or stream is not readable."
                                       if msg is None else msg)

    def writable(self):
        """Return a bool indicating whether object was opened for writing.

        If False, write() and truncate() will raise OSError.
        """
        return False

    def _checkWritable(self, msg=None):
        """Internal: raise UnsupportedOperation if file is not writable
        """
        if not self.writable():
            raise UnsupportedOperation("File or stream is not writable."
                                       if msg is None else msg)

    @property
    def closed(self):
        """closed: bool.  True iff the file has been closed.

        For backwards compatibility, this is a property, not a predicate.
        """
        return self.__closed

    def _checkClosed(self, msg=None):
        """Internal: raise a ValueError if file is closed
        """
        if self.closed:
            raise ValueError("I/O operation on closed file."
                             if msg is None else msg)

    ### Context manager ###

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Open the target for writing: 'w', 'a' (append), 'x' (exclusive create), or 'r+' to extend in place.
  2. Guard in generic code: if stream.writable(): stream.write(...) else raise/handle.
  3. Separate concerns: pass a dedicated write handle to writers instead of reusing input handles.
  4. For in-memory mutation, use a fresh io.BytesIO()/io.StringIO() in its default read-write-capable state.

Example fix

// before
with open('note.txt', 'r') as f:
    f.write('update')      # UnsupportedOperation: not writable

// after
with open('note.txt', 'a') as f:
    f.write('update')      # append, or 'r+' + seek for in-place edit
Defensive patterns

Strategy: type-guard

Validate before calling

if stream.writable():
    stream.write(data)
else:
    raise ValueError(f'{stream!r} is not open for writing')

Type guard

def is_writable(stream) -> bool:
    probe = getattr(stream, 'writable', None)
    return callable(probe) and probe()

Try / catch

import io

try:
    sink.write(line)
except io.UnsupportedOperation:
    with open(path, 'a', encoding='utf-8') as real_sink:
        real_sink.write(line)

Prevention

When it happens

Trigger: open('in.txt', 'r').write('x'); writing to p.stdout of a Popen you read from (stdout is a read pipe); calling .write() on a GzipFile opened 'rb'; attempts to log into a handle received in read mode; io.BytesIO(read_only_bytes).write().

Common situations: Functions that accept 'a stream' and write to it, invoked with an accidentally read-mode handle; refactoring that changes an open('w') to open('r') for inspection and forgetting to revert; read-only resources (files without write permission are a different OSError, but read-mode opens are this one).

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/c5d3ca0398ec761d. Report an issue: GitHub.