Skip to content

Commit e05fbab

Browse files
authored
Introduce ReadableBuffer and WriteableBuffer Union aliases (#4232)
Since typing doesn't yet have a way to express buffer protocol objects (python/typing#593), various interfaces have ended up with a mish-mash of options: some list just bytes (or just bytearray, when writable), some include mmap, some include memoryview, I think none of them include array.array even though it's explicitly mentioned as bytes-like, etc. I ran into problems because RawIOBase.readinto didn't allow for memoryview. To allow for some uniformity until the fundamental issue is resolved, I've introduced _typeshed.ReadableBuffer and _typeshed.WriteableBuffer, and applied them in stdlib/3/io.pyi as an example. If these get rolled out in more places, it will mean that we have only one place where they have to get tweaked in future, or swapped out for a public protocol. This unfortunately does have the potential to break code that inherits from RawIOBase/BufferedIOBase and overrides these methods, because the base method is now more general and so the override now needs to accept these types as well (which is why I've also updated gzip and lzma). However, it should be a reasonably easy fix, and will make the downstream annotations more correct.
1 parent 1350e71 commit e05fbab

File tree

4 files changed

+18
-14
lines changed

4 files changed

+18
-14
lines changed

stdlib/2and3/_typeshed/__init__.pyi

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# If on Python versions < 3.10 and "from __future__ import annotations"
1313
# is not used, types from this module must be quoted.
1414

15+
import array
16+
import mmap
1517
import sys
1618
from typing import Protocol, Text, TypeVar, Union
1719
from typing_extensions import Literal
@@ -67,3 +69,6 @@ class SupportsReadline(Protocol[_T_co]):
6769
def readline(self, __length: int = ...) -> _T_co: ...
6870
class SupportsWrite(Protocol[_T_contra]):
6971
def write(self, __s: _T_contra) -> int: ...
72+
73+
ReadableBuffer = Union[bytes, bytearray, memoryview, array.array, mmap.mmap]
74+
WriteableBuffer = Union[bytearray, memoryview, array.array, mmap.mmap]

stdlib/3/gzip.pyi

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import sys
22
import zlib
33
from typing import IO, Optional, TextIO, Union, overload
44
import _compression
5-
from _typeshed import AnyPath
5+
from _typeshed import AnyPath, ReadableBuffer
66
from typing_extensions import Literal
77

88
_OpenBinaryMode = Literal["r", "rb", "a", "ab", "w", "wb", "x", "xb"]
@@ -63,7 +63,7 @@ class GzipFile(_compression.BaseStream):
6363
@property
6464
def mtime(self) -> Optional[int]: ...
6565
crc: int
66-
def write(self, data: bytes) -> int: ...
66+
def write(self, data: ReadableBuffer) -> int: ...
6767
def read(self, size: Optional[int] = ...) -> bytes: ...
6868
def read1(self, size: int = ...) -> bytes: ...
6969
def peek(self, n: int) -> bytes: ...

stdlib/3/io.pyi

+9-10
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import codecs
66
import sys
77
from mmap import mmap
88
from types import TracebackType
9-
10-
_bytearray_like = Union[bytearray, mmap]
9+
from _typeshed import ReadableBuffer, WriteableBuffer
1110

1211
DEFAULT_BUFFER_SIZE: int
1312

@@ -42,7 +41,7 @@ class IOBase:
4241
def tell(self) -> int: ...
4342
def truncate(self, __size: Optional[int] = ...) -> int: ...
4443
def writable(self) -> bool: ...
45-
def writelines(self, __lines: Iterable[Union[bytes, bytearray]]) -> None: ...
44+
def writelines(self, __lines: Iterable[ReadableBuffer]) -> None: ...
4645
def readline(self, __size: Optional[int] = ...) -> bytes: ...
4746
def __del__(self) -> None: ...
4847
@property
@@ -51,16 +50,16 @@ class IOBase:
5150

5251
class RawIOBase(IOBase):
5352
def readall(self) -> bytes: ...
54-
def readinto(self, __buffer: bytearray) -> Optional[int]: ...
55-
def write(self, __b: Union[bytes, bytearray]) -> Optional[int]: ...
53+
def readinto(self, __buffer: WriteableBuffer) -> Optional[int]: ...
54+
def write(self, __b: ReadableBuffer) -> Optional[int]: ...
5655
def read(self, __size: int = ...) -> Optional[bytes]: ...
5756

5857
class BufferedIOBase(IOBase):
5958
raw: RawIOBase # This is not part of the BufferedIOBase API and may not exist on some implementations.
6059
def detach(self) -> RawIOBase: ...
61-
def readinto(self, __buffer: _bytearray_like) -> int: ...
62-
def write(self, __buffer: Union[bytes, bytearray]) -> int: ...
63-
def readinto1(self, __buffer: _bytearray_like) -> int: ...
60+
def readinto(self, __buffer: WriteableBuffer) -> int: ...
61+
def write(self, __buffer: ReadableBuffer) -> int: ...
62+
def readinto1(self, __buffer: WriteableBuffer) -> int: ...
6463
def read(self, __size: Optional[int] = ...) -> bytes: ...
6564
def read1(self, __size: int = ...) -> bytes: ...
6665

@@ -75,7 +74,7 @@ class FileIO(RawIOBase, BinaryIO):
7574
closefd: bool = ...,
7675
opener: Optional[Callable[[Union[int, str], str], int]] = ...
7776
) -> None: ...
78-
def write(self, __b: bytes) -> int: ...
77+
def write(self, __b: ReadableBuffer) -> int: ...
7978
def read(self, __size: int = ...) -> bytes: ...
8079
def __enter__(self: _T) -> _T: ...
8180

@@ -105,7 +104,7 @@ class BufferedReader(BufferedIOBase, BinaryIO):
105104
class BufferedWriter(BufferedIOBase, BinaryIO):
106105
def __enter__(self: _T) -> _T: ...
107106
def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ...
108-
def write(self, __buffer: Union[bytes, bytearray]) -> int: ...
107+
def write(self, __buffer: ReadableBuffer) -> int: ...
109108

110109
class BufferedRandom(BufferedReader, BufferedWriter):
111110
def __enter__(self: _T) -> _T: ...

stdlib/3/lzma.pyi

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import io
22
from typing import IO, Any, Mapping, Optional, Sequence, TextIO, TypeVar, Union, overload
3-
from _typeshed import AnyPath
3+
from _typeshed import AnyPath, ReadableBuffer
44
from typing_extensions import Literal
55

66
_OpenBinaryWritingMode = Literal["w", "wb", "x", "xb", "a", "ab"]
@@ -88,7 +88,7 @@ class LZMAFile(io.BufferedIOBase, IO[bytes]):
8888
def read(self, size: Optional[int] = ...) -> bytes: ...
8989
def read1(self, size: int = ...) -> bytes: ...
9090
def readline(self, size: Optional[int] = ...) -> bytes: ...
91-
def write(self, data: bytes) -> int: ...
91+
def write(self, data: ReadableBuffer) -> int: ...
9292
def seek(self, offset: int, whence: int = ...) -> int: ...
9393
def tell(self) -> int: ...
9494

0 commit comments

Comments
 (0)