How to annotate the argument type for method `write`?

I create a class inherited from io.RawIOBase and then manually define its write method with typing hints.

My trys

First, I casually annotatememoryview | bytearray | bytes. Of course, that’s incorrect.

import io

class VoidStream(io.RawIOBase):
    # Argument 1 of "write" is incompatible with
    # supertype "_RawIOBase"; supertype defines
    # the argument type as "Buffer"
    def write(self, b: memoryview | bytes | bytearray) -> int:
        return len(b)

Then, I modify the annotation to collection.abc.Buffer, but:

from collections.abc import Buffer
import io

class VoidStream(io.RawIOBase):
    def write(self, b: Buffer) -> int:
        # Argument 1 to "len" has incompatible
        # type "Buffer"; expected "Sized"
        return len(b) 

I learned mypy actually use the typeshed repository. The _io.pyi file also shows:

if sys.version_info >= (3, 12):
    from collections.abc import Buffer as Buffer

ReadableBuffer: TypeAlias = Buffer

def write(self, b: ReadableBuffer, /)

Finally, I worked out two hacking ways for bypassing.

def write(self, b: Buffer):
    b = memoryview(b)
    return len(b)
def write(self, b: memoryview): # type: ignore

Question:

  1. (out of curiosity) Is there any use case where write accepts a not-len-able argument?
  2. How to construct the non-hacking code to simultaneously satisfy both write and len?

Thanks.

Your second annotation (write(self, b: Buffer) -> int) is correct. The problem is not the annotation; the problem is that using len(b) at all in an override for io.RawIOBase is unsafe since the Buffer ABC only requires __buffer__, not __len__. Conceptually, that’s because it represents things that have buffers, not necessarily raw memory buffers themselves (an actualy raw memory buffer is just a memoryview). I’m not sure that anything in the standard library actually implements Buffer without Sized, but user-defined code absolutely can.

len(memoryview(b)) is indeed a good solution. You can think of it as extracting the (memory) buffer from the (Python) Buffer. Keep in mind that it’s more likely to be the number of items in the view than the number of bytes; for the latter, use memoryview(b).nbytes.

(I happen to have written a Stack Overflow answer to a very similar question!)