Textualize/rich · error · UnsupportedOperation
write
Error message
write
What it means
rich's Progress.wrap_file() returns a _Reader: a read-only progress-tracking wrapper around the underlying file handle. Calling .write() on it raises io.UnsupportedOperation('write') because the wrapper only implements the read side of the IO interface (read/tell/seek) to update task progress; write is explicitly stubbed out.
Source
Thrown at rich/progress.py:279
lines = self.handle.readlines(hint)
self.progress.advance(self.task, advance=sum(map(len, lines)))
return lines
def close(self) -> None:
if self.close_handle:
self.handle.close()
self._closed = True
def seek(self, offset: int, whence: int = 0) -> int:
pos = self.handle.seek(offset, whence)
self.progress.update(self.task, completed=pos)
return pos
def tell(self) -> int:
return self.handle.tell()
def write(self, s: Any) -> int:
raise UnsupportedOperation("write")
def writelines(self, lines: Iterable[Any]) -> None:
raise UnsupportedOperation("writelines")
class _ReadContext(ContextManager[_I], Generic[_I]):
"""A utility class to handle a context for both a reader and a progress."""
def __init__(self, progress: "Progress", reader: _I) -> None:
self.progress = progress
self.reader: _I = reader
def __enter__(self) -> _I:
self.progress.start()
return self.reader.__enter__()
def __exit__(
self,View on GitHub (pinned to 9d8f9a372c)
Solutions
- Write to the original raw handle, not the wrapper: keep a reference to the file you passed into wrap_file and write there.
- If you need write progress, track it manually: call progress.update(task_id, completed=f.tell()) after writes instead of relying on wrap_file.
- Check mode/usage: wrap_file is designed for downloads/reads (e.g. httpx response.iter_bytes into the reader); restructure so only reads go through the wrapper.
Example fix
# before
with progress.wrap_file(f, total=size) as wrapped:
wrapped.write(b'data') # UnsupportedOperation
# after
with progress.wrap_file(f, total=size) as wrapped:
data = wrapped.read() # read-only wrapper
# write to the raw handle if needed
f.write(b'data')
progress.update(task, completed=f.tell()) Defensive patterns
Strategy: type-guard
Validate before calling
from rich.progress import Progress # _Reader is read-only: verify before writing is_readable = hasattr(obj, 'read') and not getattr(obj, 'closed', False)
Type guard
def is_readonly_progress_reader(obj: object) -> bool:
"""True for rich _Reader wrappers that reject write."""
return hasattr(obj, 'read') and callable(getattr(obj, 'write', None)) and getattr(type(obj), '__name__', '') == '_Reader' Prevention
- Keep a reference to the raw handle when calling Progress.wrap_file; never write through the wrapper.
- Only use wrap_file/Progress.open for read (download) progress; track write progress manually with progress.update().
- Read the docstring: _Reader implements the io read side only.
When it happens
Trigger: Calling progress.write(data) on the object returned by Progress.wrap_file(file) or Progress.open(file) (which delegates to wrap_file). E.g. with progress.wrap_file(f) as p: p.write(b'x'). Also triggered by any library that type-checks against IO and then writes, e.g. shutil.copyfileobj(dst=p) or requests upload plumbing that writes to the passed file object.
Common situations: Developer opens a file with Progress.open(..., mode='rb') for an upload and then accidentally writes to the wrapper instead of the raw handle; passing the wrapper as a destination to a copy function; treating wrap_file's result as a general-purpose file object.
Related errors
- writelines
- invalid mode {mode!r}
- unable to get the total number of bytes, please specify 'tot
- can't have unbuffered text I/O
- slices with step!=1 are not supported
AI-assisted analysis of Textualize/rich@9d8f9a372c (2026-08-15).
Data as JSON: /api/errors/d00f4acb13754087.
Report an issue: GitHub.