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
- Open the target for writing: 'w', 'a' (append), 'x' (exclusive create), or 'r+' to extend in place.
- Guard in generic code: if stream.writable(): stream.write(...) else raise/handle.
- Separate concerns: pass a dedicated write handle to writers instead of reusing input handles.
- 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
- Give writers their own handle opened 'w'/'a'/'x'; never reuse input handles.
- In APIs accepting streams, check stream.writable() up front and raise a clear domain error.
- Re-check mode strings after switching opens between 'r' and 'w' during debugging.
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
- File or stream is not readable.
- %s.%s() not supported
- File or stream is not seekable.
- invalid mode: %r
- can't have text and binary mode at once
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/c5d3ca0398ec761d.
Report an issue: GitHub.