python/cpython · error · OSError

"writer" argument must be writable.

Error message

"writer" argument must be writable.

What it means

Raised by io.BufferedRWPair.__init__ when the object passed as writer does not report itself as writable via its writable() method. It is the second of two constructor sanity checks (after the reader.readable() check) that the supplied raw IO objects really support the operations the pair will perform on them.

Source

Thrown at Lib/_pyio.py:1375

    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 bc6749cc3b)

Solutions

  1. Pass a genuinely writable object as the second argument (e.g. open(path,'wb') or a write-capable socket wrapper)
  2. Open the file with a write mode ('wb', 'ab', 'w+b') instead of 'rb'
  3. For custom raw IO classes, override writable() to return True when write() is implemented

Example fix

# before
pair = io.BufferedRWPair(sock_r, open('log.bin','rb'))  # writer not writable -> OSError

# after
pair = io.BufferedRWPair(sock_r, open('log.bin','wb'))
Defensive patterns

Strategy: type-guard

Validate before calling

def make_rwpair(reader, writer, buffer_size=8192):
    import io
    if not writer.writable():
        raise OSError('writer argument must be writable')
    return io.BufferedRWPair(reader, writer, buffer_size)

Type guard

def is_writable_raw(obj):
    return hasattr(obj, 'writable') and callable(obj.writable) and obj.writable()

Try / catch

try:
    pair = io.BufferedRWPair(r, w)
except OSError as e:
    if 'writable' in str(e):
        w = open(path, 'wb')
        pair = io.BufferedRWPair(r, w)
    else:
        raise

Prevention

When it happens

Trigger: io.BufferedRWPair(reader, writer) where writer.writable() returns False, e.g. a read-only FileIO opened with 'r', a socket wrapper that is read-only/shut down for writing, or a custom raw class whose writable() defaults to False.

Common situations: Passing two read streams (e.g. both ends opened read-only) to simulate a bidirectional channel; custom RawIOBase subclasses where only readable() was overridden; using a closed or half-shutdown socket object as the writer side.

Related errors


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