RustPython/RustPython · error · OSError

"writer" argument must be writable.

Error message

"writer" argument must be writable.

What it means

BufferedRWPair(reader, writer) checks the second endpoint after the first: if writer.writable() returns false it raises OSError('"writer" argument must be writable.'). The pair needs a genuinely writable raw stream to build its internal BufferedWriter, and the io.IOBase default for writable() is False, so custom classes must override it.

Source

Thrown at Lib/_pyio.py:1349

    reader and writer are RawIOBase objects that are readable and
    writeable respectively. If the buffer_size is omitted it defaults to
    DEFAULT_BUFFER_SIZE.
    """

    # XXX The usefulness of this (compared to having two separate IO
    # objects) is questionable.

    def __init__(self, reader, writer, buffer_size=DEFAULT_BUFFER_SIZE):
        """Constructor.

        The arguments are two RawIO instances.
        """
        if not reader.readable():
            raise OSError('"reader" argument must be readable.')

        if not writer.writable():
            raise OSError('"writer" argument must be writable.')

        self.reader = BufferedReader(reader, buffer_size)
        self.writer = BufferedWriter(writer, buffer_size)

    def read(self, size=-1):
        if size is None:
            size = -1
        return self.reader.read(size)

    def readinto(self, b):
        return self.reader.readinto(b)

    def write(self, b):
        return self.writer.write(b)

    def peek(self, size=0):
        return self.reader.peek(size)

View on GitHub (pinned to aaeab4f754)

Solutions

  1. Pass the write end as the second argument: BufferedRWPair(read_end, write_end)
  2. Override writable() -> True and implement write() on the custom writer class
  3. Validate both capabilities before constructing the pair
  4. Use BufferedRandom for a single bidirectional seekable stream instead

Example fix

# before
r, w = os.pipe()
pair = io.BufferedRWPair(io.FileIO(r, 'r'), io.FileIO(r, 'r'))  # OSError: "writer" argument must be writable.

# after
pair = io.BufferedRWPair(io.FileIO(r, 'r'), io.FileIO(w, 'w'))

# custom writer side
class MyWriter(io.RawIOBase):
    def writable(self):
        return True
    def write(self, b):
        return os.write(self.fd, b)
Defensive patterns

Strategy: validation

Validate before calling

if not writer.writable():
    raise ValueError(f'{writer!r} cannot serve as the writer side')
pair = io.BufferedRWPair(reader, writer)

Type guard

import io

def rw_pair_ready(reader, writer) -> bool:
    """True when both endpoints can fill their RWPair roles."""
    return reader.readable() and writer.writable()

Try / catch

try:
    pair = io.BufferedRWPair(reader, writer)
except OSError as exc:
    if 'must be writable' in str(exc):
        raise ValueError(f'writer side is not writable: {writer!r}') from exc
    raise

Prevention

When it happens

Trigger: io.BufferedRWPair(reader, read_end) with the pipe's read end passed as the writer; a custom writer class without a writable() override; passing a BufferedReader or a makefile('rb') object as the writer argument.

Common situations: Swapped ends when wiring subprocess pipes or socketpairs; custom transports that only implemented read-side methods; copy-paste constructor calls where both arguments were filled from the same source.

Related errors


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/04e88e7e24fcc6e9. Report an issue: GitHub.