Skip to content

Commit 4664986

Browse files
authored
Turn TextIOWrapper(buffer) into a protocol (#11420)
1 parent a3d356c commit 4664986

File tree

3 files changed

+44
-4
lines changed

3 files changed

+44
-4
lines changed

stdlib/io.pyi

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ from _typeshed import FileDescriptorOrPath, ReadableBuffer, WriteableBuffer
66
from collections.abc import Callable, Iterable, Iterator
77
from os import _Opener
88
from types import TracebackType
9-
from typing import IO, Any, BinaryIO, Literal, TextIO, TypeVar, overload
9+
from typing import IO, Any, BinaryIO, Literal, Protocol, TextIO, TypeVar, overload, type_check_only
1010
from typing_extensions import Self
1111

1212
__all__ = [
@@ -94,7 +94,10 @@ class BufferedIOBase(IOBase):
9494

9595
class FileIO(RawIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes
9696
mode: str
97-
name: FileDescriptorOrPath
97+
# The type of "name" equals the argument passed in to the constructor,
98+
# but that can make FileIO incompatible with other I/O types that assume
99+
# "name" is a str. In the future, making FileIO generic might help.
100+
name: Any
98101
def __init__(
99102
self, file: FileDescriptorOrPath, mode: str = ..., closefd: bool = ..., opener: _Opener | None = ...
100103
) -> None: ...
@@ -146,16 +149,43 @@ class TextIOBase(IOBase):
146149
def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override]
147150
def read(self, __size: int | None = ...) -> str: ...
148151

152+
@type_check_only
153+
class _WrappedBuffer(Protocol):
154+
# "name" is wrapped by TextIOWrapper. Its type is inconsistent between
155+
# the various I/O types, see the comments on TextIOWrapper.name and
156+
# TextIO.name.
157+
@property
158+
def name(self) -> Any: ...
159+
@property
160+
def closed(self) -> bool: ...
161+
def read(self, size: int = ..., /) -> ReadableBuffer: ...
162+
# Optional: def read1(self, size: int, /) -> ReadableBuffer: ...
163+
def write(self, b: bytes, /) -> object: ...
164+
def flush(self) -> object: ...
165+
def close(self) -> object: ...
166+
def seekable(self) -> bool: ...
167+
def readable(self) -> bool: ...
168+
def writable(self) -> bool: ...
169+
def truncate(self, size: int, /) -> int: ...
170+
def fileno(self) -> int: ...
171+
def isatty(self) -> int: ...
172+
# Optional: Only needs to be present if seekable() returns True.
173+
# def seek(self, offset: Literal[0], whence: Literal[2]) -> int: ...
174+
# def tell(self) -> int: ...
175+
176+
# TODO: Should be generic over the buffer type, but needs to wait for
177+
# TypeVar defaults.
149178
class TextIOWrapper(TextIOBase, TextIO): # type: ignore[misc] # incompatible definitions of write in the base classes
150179
def __init__(
151180
self,
152-
buffer: IO[bytes],
181+
buffer: _WrappedBuffer,
153182
encoding: str | None = ...,
154183
errors: str | None = ...,
155184
newline: str | None = ...,
156185
line_buffering: bool = ...,
157186
write_through: bool = ...,
158187
) -> None: ...
188+
# Equals the "buffer" argument passed in to the constructor.
159189
@property
160190
def buffer(self) -> BinaryIO: ...
161191
@property
@@ -180,7 +210,11 @@ class TextIOWrapper(TextIOBase, TextIO): # type: ignore[misc] # incompatible d
180210
def writelines(self, __lines: Iterable[str]) -> None: ... # type: ignore[override]
181211
def readline(self, __size: int = -1) -> str: ... # type: ignore[override]
182212
def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override]
183-
def seek(self, __cookie: int, __whence: int = 0) -> int: ... # stubtest needs this
213+
# Equals the "buffer" argument passed in to the constructor.
214+
def detach(self) -> BinaryIO: ...
215+
# TextIOWrapper's version of seek only supports a limited subset of
216+
# operations.
217+
def seek(self, __cookie: int, __whence: int = 0) -> int: ...
184218

185219
class StringIO(TextIOWrapper):
186220
def __init__(self, initial_value: str | None = ..., newline: str | None = ...) -> None: ...

test_cases/stdlib/check_io.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from gzip import GzipFile
2+
from io import FileIO, TextIOWrapper
3+
4+
TextIOWrapper(FileIO(""))
5+
TextIOWrapper(FileIO(13))
6+
TextIOWrapper(GzipFile(""))

0 commit comments

Comments
 (0)