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

  1. Write to the original raw handle, not the wrapper: keep a reference to the file you passed into wrap_file and write there.
  2. If you need write progress, track it manually: call progress.update(task_id, completed=f.tell()) after writes instead of relying on wrap_file.
  3. 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

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


AI-assisted analysis of Textualize/rich@9d8f9a372c (2026-08-15). Data as JSON: /api/errors/d00f4acb13754087. Report an issue: GitHub.