From f9b87720296fa8274312c2bcbd56c86fc6e6295e Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Fri, 4 Oct 2024 18:06:13 -0700 Subject: [PATCH 1/2] _io / io redo This is an updated version of https://github.com/python/typeshed/pull/11187 In addition to shuffling defintions between files, these things got tweaked: - Removed several redundant definitions of __enter__. These seem to be an artifact of using TypeVar instead of Self originally - seek and truncate have inconsistent variable names. They're also positional only, so they were in the stubtest allowlist before, but I went ahead and made them explicit in the stubs - BytesIO.readlines shouldn't have been on the allowlist in the first place. It differs not only in variable name, but also default value. - A big block of functions in TextIOWrapper were commented that mypy needed them, but I don't think it does anymore, unless there's a problem with subclassing TextIOWrapper that doesn't show up in typeshed itself. No indication in the history about that. - In the implementation, the concrete classes inherit from the private implementation _*IOBase classes, not the classes in io which are actually metaclasses, but they are registered to those metaclasses at runtime. It wasn't technically required for any reason, but I split the difference on this by keeping the _*IOBase classes in their base classes directly. I think it's a bit of a reminder of the actual implementation, and means that a stubtest check for inheritance will show that typeshed is adding to the base classes, rather than replacing the base class, and I think that's a little cleaner. Partially related to #3968 --- pyproject.toml | 1 + stdlib/@tests/stubtest_allowlists/common.txt | 11 - stdlib/VERSIONS | 1 + stdlib/_io.pyi | 218 +++++++++++++++++ stdlib/io.pyi | 233 +++---------------- 5 files changed, 247 insertions(+), 217 deletions(-) create mode 100644 stdlib/_io.pyi diff --git a/pyproject.toml b/pyproject.toml index ae82dd6484be..9c7c11c87b91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,6 +133,7 @@ extra-standard-library = [ "_dummy_threading", "_heapq", "_imp", + "_io", "_json", "_locale", "_lsprof", diff --git a/stdlib/@tests/stubtest_allowlists/common.txt b/stdlib/@tests/stubtest_allowlists/common.txt index 877307b96869..b4e5114796fb 100644 --- a/stdlib/@tests/stubtest_allowlists/common.txt +++ b/stdlib/@tests/stubtest_allowlists/common.txt @@ -49,17 +49,6 @@ importlib.abc.Loader.exec_module # See Lib/importlib/_abc.py. Might be defined importlib.abc.MetaPathFinder.find_spec # Not defined on the actual class, but expected to exist. importlib.abc.PathEntryFinder.find_spec # Not defined on the actual class, but expected to exist. importlib.machinery.ExtensionFileLoader.get_filename # Wrapped with _check_name decorator which changes runtime signature -io.BufferedRandom.truncate -io.BufferedReader.seek -io.BufferedReader.truncate -io.BufferedWriter.seek -io.BufferedWriter.truncate -io.BytesIO.readlines -io.BytesIO.seek # Parameter name for a positional-only param differs from its name in the inherited method -io.FileIO.seek -io.StringIO.seek -io.StringIO.truncate -io.TextIOWrapper.truncate ipaddress.IPv4Interface.hostmask ipaddress.IPv6Interface.hostmask ipaddress._BaseNetwork.broadcast_address diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 2b2a66d57f2a..05f3d8816305 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -34,6 +34,7 @@ _dummy_thread: 3.0-3.8 _dummy_threading: 3.0-3.8 _heapq: 3.0- _imp: 3.0- +_io: 3.0- _interpchannels: 3.13- _interpqueues: 3.13- _interpreters: 3.13- diff --git a/stdlib/_io.pyi b/stdlib/_io.pyi new file mode 100644 index 000000000000..4c78b15d88da --- /dev/null +++ b/stdlib/_io.pyi @@ -0,0 +1,218 @@ +import builtins +import codecs +import sys +from _typeshed import FileDescriptorOrPath, MaybeNone, ReadableBuffer, WriteableBuffer +from collections.abc import Callable, Iterable, Iterator +from io import BufferedIOBase, RawIOBase, TextIOBase, UnsupportedOperation as UnsupportedOperation +from os import _Opener +from types import TracebackType +from typing import IO, Any, BinaryIO, Final, Generic, Literal, Protocol, TextIO, TypeVar, overload, type_check_only +from typing_extensions import Self + +@type_check_only +class _WrappedBuffer(Protocol): + # "name" is wrapped by TextIOWrapper. Its type is inconsistent between + # the various I/O types, see the comments on TextIOWrapper.name and + # TextIO.name. + @property + def name(self) -> Any: ... + @property + def closed(self) -> bool: ... + def read(self, size: int = ..., /) -> ReadableBuffer: ... + # Optional: def read1(self, size: int, /) -> ReadableBuffer: ... + def write(self, b: bytes, /) -> object: ... + def flush(self) -> object: ... + def close(self) -> object: ... + def seekable(self) -> bool: ... + def readable(self) -> bool: ... + def writable(self) -> bool: ... + def truncate(self, size: int, /) -> int: ... + def fileno(self) -> int: ... + def isatty(self) -> bool: ... + # Optional: Only needs to be present if seekable() returns True. + # def seek(self, offset: Literal[0], whence: Literal[2]) -> int: ... + # def tell(self) -> int: ... + +_T = TypeVar("_T") +_BufferT_co = TypeVar("_BufferT_co", bound=_WrappedBuffer, default=_WrappedBuffer, covariant=True) + +DEFAULT_BUFFER_SIZE: Final = 8192 + +open = builtins.open +BlockingIOError = builtins.BlockingIOError + +def open_code(path: str) -> IO[bytes]: ... + +if sys.version_info >= (3, 10): + @overload + def text_encoding(encoding: None, stacklevel: int = 2, /) -> Literal["locale", "utf-8"]: ... + @overload + def text_encoding(encoding: _T, stacklevel: int = 2, /) -> _T: ... + +class _IOBase: + def __iter__(self) -> Iterator[bytes]: ... + def __next__(self) -> bytes: ... + def __enter__(self) -> Self: ... + def __exit__( + self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None + ) -> None: ... + def close(self) -> None: ... + def fileno(self) -> int: ... + def flush(self) -> None: ... + def isatty(self) -> bool: ... + def readable(self) -> bool: ... + read: Callable[..., Any] + def readlines(self, hint: int = -1, /) -> list[bytes]: ... + def seek(self, offset: int, whence: int = ..., /) -> int: ... + def seekable(self) -> bool: ... + def tell(self) -> int: ... + def truncate(self, size: int | None = ..., /) -> int: ... + def writable(self) -> bool: ... + write: Callable[..., Any] + def writelines(self, lines: Iterable[ReadableBuffer], /) -> None: ... + def readline(self, size: int | None = -1, /) -> bytes: ... + def __del__(self) -> None: ... + @property + def closed(self) -> bool: ... + def _checkClosed(self) -> None: ... # undocumented + +class _RawIOBase(_IOBase): + def readall(self) -> bytes: ... + # The following methods can return None if the file is in non-blocking mode + # and no data is available. + def readinto(self, buffer: WriteableBuffer, /) -> int | MaybeNone: ... + def write(self, b: ReadableBuffer, /) -> int | MaybeNone: ... + def read(self, size: int = -1, /) -> bytes | MaybeNone: ... + +class FileIO(RawIOBase, _RawIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes + mode: str + # The type of "name" equals the argument passed in to the constructor, + # but that can make FileIO incompatible with other I/O types that assume + # "name" is a str. In the future, making FileIO generic might help. + name: Any + def __init__( + self, file: FileDescriptorOrPath, mode: str = ..., closefd: bool = ..., opener: _Opener | None = ... + ) -> None: ... + @property + def closefd(self) -> bool: ... + # this is here so stubtest is happy with the argument names + def seek(self, pos: int, whence: int = ..., /) -> int: ... + +class _BufferedIOBase(_IOBase): + def detach(self) -> RawIOBase: ... + def readinto(self, buffer: WriteableBuffer, /) -> int: ... + def write(self, buffer: ReadableBuffer, /) -> int: ... + def readinto1(self, buffer: WriteableBuffer, /) -> int: ... + def read(self, size: int | None = ..., /) -> bytes: ... + def read1(self, size: int = ..., /) -> bytes: ... + +class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes + def __init__(self, initial_bytes: ReadableBuffer = ...) -> None: ... + # BytesIO does not contain a "name" field. This workaround is necessary + # to allow BytesIO sub-classes to add this field, as it is defined + # as a read-only property on IO[]. + name: Any + def getvalue(self) -> bytes: ... + def getbuffer(self) -> memoryview: ... + def read1(self, size: int | None = -1, /) -> bytes: ... + def readlines(self, size: int | None = None, /) -> list[bytes]: ... + # this is here so stubtest is happy with the argument names + def seek(self, pos: int, whence: int = ..., /) -> int: ... + +class BufferedReader(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes + # technically has mode and name attributes, but they're just pass-throughs to + # self.raw.* + raw: RawIOBase + def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... + def peek(self, size: int = 0, /) -> bytes: ... + # these are here so stubtest is happy with the argument names + def seek(self, target: int, whence: int = ..., /) -> int: ... + def truncate(self, pos: int | None = ..., /) -> int: ... + +class BufferedWriter(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes + # technically has mode and name attributes, but they're just pass-throughs to + # self.raw.* + raw: RawIOBase + def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... + def write(self, buffer: ReadableBuffer, /) -> int: ... + # these are here so stubtest is happy with the argument names + def seek(self, target: int, whence: int = ..., /) -> int: ... + def truncate(self, pos: int | None = ..., /) -> int: ... + +class BufferedRWPair(BufferedIOBase, _BufferedIOBase): + def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = ...) -> None: ... + def peek(self, size: int = ..., /) -> bytes: ... + +# The C implementation is not subclass of BufferedReader and BufferedWriter at runtime, +# but the implementation in _pyio is +class BufferedRandom(BufferedReader, BufferedWriter, BufferedIOBase, _BufferedIOBase): ... # type: ignore[misc] # incompatible definitions of methods in the base classes + +class _TextIOBase(_IOBase): + encoding: str + errors: str | None + newlines: str | tuple[str, ...] | None + def __iter__(self) -> Iterator[str]: ... # type: ignore[override] + def __next__(self) -> str: ... # type: ignore[override] + def detach(self) -> BinaryIO: ... + def write(self, s: str, /) -> int: ... + def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override] + def readline(self, size: int = ..., /) -> str: ... # type: ignore[override] + def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override] + def read(self, size: int | None = ..., /) -> str: ... + +class TextIOWrapper(TextIOBase, _TextIOBase, TextIO, Generic[_BufferT_co]): # type: ignore[misc] # incompatible definitions of write in the base classes + def __init__( + self, + buffer: _BufferT_co, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + line_buffering: bool = False, + write_through: bool = False, + ) -> None: ... + # Equals the "buffer" argument passed in to the constructor. + @property + def buffer(self) -> _BufferT_co: ... # type: ignore[override] + @property + def line_buffering(self) -> bool: ... + @property + def name(self) -> str: ... + @property + def write_through(self) -> bool: ... + def reconfigure( + self, + *, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + line_buffering: bool | None = None, + write_through: bool | None = None, + ) -> None: ... + # Equals the "buffer" argument passed in to the constructor. + def detach(self) -> _BufferT_co: ... # type: ignore[override] + # TextIOWrapper's version of seek only supports a limited subset of + # operations. + def seek(self, cookie: int, whence: int = 0, /) -> int: ... + # this is here so stubtest is happy with the argument names + def truncate(self, pos: int | None = ..., /) -> int: ... + +# The C implementation is not a subclass of TextIOWrapper at runtime, +# but the implementation in _pyio is +class StringIO(TextIOWrapper, TextIOBase, _TextIOBase): # type: ignore[misc] # incompatible definitions of write in the base classes + def __init__(self, initial_value: str | None = ..., newline: str | None = ...) -> None: ... + # StringIO does not contain a "name" field. This workaround is necessary + # to allow StringIO sub-classes to add this field, as it is defined + # as a read-only property on IO[]. + name: Any + def getvalue(self) -> str: ... + # this is here so stubtest is happy with the argument names + def seek(self, pos: int, whence: int = ..., /) -> int: ... + +# The C implementation isnot a subclass of codecs.IncrementalDecoder at runtime +# but the implementation in _pyio is +class IncrementalNewlineDecoder(codecs.IncrementalDecoder): + def __init__(self, decoder: codecs.IncrementalDecoder | None, translate: bool, errors: str = ...) -> None: ... + def decode(self, input: ReadableBuffer | str, final: bool = False) -> str: ... + @property + def newlines(self) -> str | tuple[str, ...] | None: ... + def setstate(self, state: tuple[bytes, int], /) -> None: ... diff --git a/stdlib/io.pyi b/stdlib/io.pyi index dd61b4e88c1d..5c26cb245a2f 100644 --- a/stdlib/io.pyi +++ b/stdlib/io.pyi @@ -1,13 +1,26 @@ import abc -import builtins -import codecs import sys -from _typeshed import FileDescriptorOrPath, MaybeNone, ReadableBuffer, WriteableBuffer -from collections.abc import Callable, Iterable, Iterator -from os import _Opener -from types import TracebackType -from typing import IO, Any, BinaryIO, Final, Generic, Literal, Protocol, TextIO, TypeVar, overload, type_check_only -from typing_extensions import Self +from _io import ( + DEFAULT_BUFFER_SIZE as DEFAULT_BUFFER_SIZE, + BlockingIOError as BlockingIOError, + BufferedRandom as BufferedRandom, + BufferedReader as BufferedReader, + BufferedRWPair as BufferedRWPair, + BufferedWriter as BufferedWriter, + BytesIO as BytesIO, + FileIO as FileIO, + IncrementalNewlineDecoder as IncrementalNewlineDecoder, + StringIO as StringIO, + TextIOWrapper as TextIOWrapper, + _BufferedIOBase, + _IOBase, + _RawIOBase, + _TextIOBase, + _WrappedBuffer as _WrappedBuffer, # used elsewhere in typeshed + open as open, + open_code as open_code, +) +from typing import Final __all__ = [ "BlockingIOError", @@ -32,208 +45,16 @@ __all__ = [ ] if sys.version_info >= (3, 11): - __all__ += ["DEFAULT_BUFFER_SIZE", "IncrementalNewlineDecoder", "text_encoding"] - -_T = TypeVar("_T") + from _io import text_encoding as text_encoding -DEFAULT_BUFFER_SIZE: Final = 8192 + __all__ += ["DEFAULT_BUFFER_SIZE", "IncrementalNewlineDecoder", "text_encoding"] SEEK_SET: Final = 0 SEEK_CUR: Final = 1 SEEK_END: Final = 2 -open = builtins.open - -def open_code(path: str) -> IO[bytes]: ... - -BlockingIOError = builtins.BlockingIOError - class UnsupportedOperation(OSError, ValueError): ... - -class IOBase(metaclass=abc.ABCMeta): - def __iter__(self) -> Iterator[bytes]: ... - def __next__(self) -> bytes: ... - def __enter__(self) -> Self: ... - def __exit__( - self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None - ) -> None: ... - def close(self) -> None: ... - def fileno(self) -> int: ... - def flush(self) -> None: ... - def isatty(self) -> bool: ... - def readable(self) -> bool: ... - read: Callable[..., Any] - def readlines(self, hint: int = -1, /) -> list[bytes]: ... - def seek(self, offset: int, whence: int = ..., /) -> int: ... - def seekable(self) -> bool: ... - def tell(self) -> int: ... - def truncate(self, size: int | None = ..., /) -> int: ... - def writable(self) -> bool: ... - write: Callable[..., Any] - def writelines(self, lines: Iterable[ReadableBuffer], /) -> None: ... - def readline(self, size: int | None = -1, /) -> bytes: ... - def __del__(self) -> None: ... - @property - def closed(self) -> bool: ... - def _checkClosed(self) -> None: ... # undocumented - -class RawIOBase(IOBase): - def readall(self) -> bytes: ... - # The following methods can return None if the file is in non-blocking mode - # and no data is available. - def readinto(self, buffer: WriteableBuffer, /) -> int | MaybeNone: ... - def write(self, b: ReadableBuffer, /) -> int | MaybeNone: ... - def read(self, size: int = -1, /) -> bytes | MaybeNone: ... - -class BufferedIOBase(IOBase): - def detach(self) -> RawIOBase: ... - def readinto(self, buffer: WriteableBuffer, /) -> int: ... - def write(self, buffer: ReadableBuffer, /) -> int: ... - def readinto1(self, buffer: WriteableBuffer, /) -> int: ... - def read(self, size: int | None = ..., /) -> bytes: ... - def read1(self, size: int = ..., /) -> bytes: ... - -class FileIO(RawIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes - mode: str - # The type of "name" equals the argument passed in to the constructor, - # but that can make FileIO incompatible with other I/O types that assume - # "name" is a str. In the future, making FileIO generic might help. - name: Any - def __init__( - self, file: FileDescriptorOrPath, mode: str = ..., closefd: bool = ..., opener: _Opener | None = ... - ) -> None: ... - @property - def closefd(self) -> bool: ... - def __enter__(self) -> Self: ... - -class BytesIO(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes - def __init__(self, initial_bytes: ReadableBuffer = ...) -> None: ... - # BytesIO does not contain a "name" field. This workaround is necessary - # to allow BytesIO sub-classes to add this field, as it is defined - # as a read-only property on IO[]. - name: Any - def __enter__(self) -> Self: ... - def getvalue(self) -> bytes: ... - def getbuffer(self) -> memoryview: ... - def read1(self, size: int | None = -1, /) -> bytes: ... - -class BufferedReader(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes - raw: RawIOBase - def __enter__(self) -> Self: ... - def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... - def peek(self, size: int = 0, /) -> bytes: ... - -class BufferedWriter(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes - raw: RawIOBase - def __enter__(self) -> Self: ... - def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... - def write(self, buffer: ReadableBuffer, /) -> int: ... - -class BufferedRandom(BufferedReader, BufferedWriter): # type: ignore[misc] # incompatible definitions of methods in the base classes - def __enter__(self) -> Self: ... - def seek(self, target: int, whence: int = 0, /) -> int: ... # stubtest needs this - -class BufferedRWPair(BufferedIOBase): - def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = ...) -> None: ... - def peek(self, size: int = ..., /) -> bytes: ... - -class TextIOBase(IOBase): - encoding: str - errors: str | None - newlines: str | tuple[str, ...] | None - def __iter__(self) -> Iterator[str]: ... # type: ignore[override] - def __next__(self) -> str: ... # type: ignore[override] - def detach(self) -> BinaryIO: ... - def write(self, s: str, /) -> int: ... - def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override] - def readline(self, size: int = ..., /) -> str: ... # type: ignore[override] - def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override] - def read(self, size: int | None = ..., /) -> str: ... - -@type_check_only -class _WrappedBuffer(Protocol): - # "name" is wrapped by TextIOWrapper. Its type is inconsistent between - # the various I/O types, see the comments on TextIOWrapper.name and - # TextIO.name. - @property - def name(self) -> Any: ... - @property - def closed(self) -> bool: ... - def read(self, size: int = ..., /) -> ReadableBuffer: ... - # Optional: def read1(self, size: int, /) -> ReadableBuffer: ... - def write(self, b: bytes, /) -> object: ... - def flush(self) -> object: ... - def close(self) -> object: ... - def seekable(self) -> bool: ... - def readable(self) -> bool: ... - def writable(self) -> bool: ... - def truncate(self, size: int, /) -> int: ... - def fileno(self) -> int: ... - def isatty(self) -> bool: ... - # Optional: Only needs to be present if seekable() returns True. - # def seek(self, offset: Literal[0], whence: Literal[2]) -> int: ... - # def tell(self) -> int: ... - -_BufferT_co = TypeVar("_BufferT_co", bound=_WrappedBuffer, default=_WrappedBuffer, covariant=True) - -class TextIOWrapper(TextIOBase, TextIO, Generic[_BufferT_co]): # type: ignore[misc] # incompatible definitions of write in the base classes - def __init__( - self, - buffer: _BufferT_co, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - line_buffering: bool = False, - write_through: bool = False, - ) -> None: ... - # Equals the "buffer" argument passed in to the constructor. - @property - def buffer(self) -> _BufferT_co: ... # type: ignore[override] - @property - def closed(self) -> bool: ... - @property - def line_buffering(self) -> bool: ... - @property - def write_through(self) -> bool: ... - def reconfigure( - self, - *, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - line_buffering: bool | None = None, - write_through: bool | None = None, - ) -> None: ... - # These are inherited from TextIOBase, but must exist in the stub to satisfy mypy. - def __enter__(self) -> Self: ... - def __iter__(self) -> Iterator[str]: ... # type: ignore[override] - def __next__(self) -> str: ... # type: ignore[override] - def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override] - def readline(self, size: int = -1, /) -> str: ... # type: ignore[override] - def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override] - # Equals the "buffer" argument passed in to the constructor. - def detach(self) -> _BufferT_co: ... # type: ignore[override] - # TextIOWrapper's version of seek only supports a limited subset of - # operations. - def seek(self, cookie: int, whence: int = 0, /) -> int: ... - -class StringIO(TextIOWrapper): - def __init__(self, initial_value: str | None = ..., newline: str | None = ...) -> None: ... - # StringIO does not contain a "name" field. This workaround is necessary - # to allow StringIO sub-classes to add this field, as it is defined - # as a read-only property on IO[]. - name: Any - def getvalue(self) -> str: ... - -class IncrementalNewlineDecoder(codecs.IncrementalDecoder): - def __init__(self, decoder: codecs.IncrementalDecoder | None, translate: bool, errors: str = ...) -> None: ... - def decode(self, input: ReadableBuffer | str, final: bool = False) -> str: ... - @property - def newlines(self) -> str | tuple[str, ...] | None: ... - def setstate(self, state: tuple[bytes, int], /) -> None: ... - -if sys.version_info >= (3, 10): - @overload - def text_encoding(encoding: None, stacklevel: int = 2, /) -> Literal["locale", "utf-8"]: ... - @overload - def text_encoding(encoding: _T, stacklevel: int = 2, /) -> _T: ... +class IOBase(_IOBase, metaclass=abc.ABCMeta): ... +class RawIOBase(_RawIOBase, IOBase): ... +class BufferedIOBase(_BufferedIOBase, IOBase): ... +class TextIOBase(_TextIOBase, IOBase): ... From 2f0dbfded22b0ccd3ca014f0a8d7d4c60dec98ce Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Sat, 5 Oct 2024 13:41:53 -0700 Subject: [PATCH 2/2] fix VERSIONS sorting --- stdlib/VERSIONS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 05f3d8816305..28a27f5d9a0b 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -34,10 +34,10 @@ _dummy_thread: 3.0-3.8 _dummy_threading: 3.0-3.8 _heapq: 3.0- _imp: 3.0- -_io: 3.0- _interpchannels: 3.13- _interpqueues: 3.13- _interpreters: 3.13- +_io: 3.0- _json: 3.0- _locale: 3.0- _lsprof: 3.0-