-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fileinput: Fix TypeVar usage #7934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
860cd4a
ed20193
c95e8c8
3784512
1bdf7cd
baeed87
ec3cb3f
f27e4a2
972f2f4
3852e80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,11 @@ import sys | |
from _typeshed import Self, StrOrBytesPath | ||
from collections.abc import Callable, Iterable, Iterator | ||
from types import TracebackType | ||
from typing import IO, Any, AnyStr, Generic | ||
from typing import IO, Any, AnyStr, Generic, Protocol, TypeVar, overload | ||
from typing_extensions import Literal, TypeAlias | ||
|
||
if sys.version_info >= (3, 9): | ||
from types import GenericAlias | ||
|
||
__all__ = [ | ||
"input", | ||
|
@@ -19,40 +23,133 @@ __all__ = [ | |
"hook_encoded", | ||
] | ||
|
||
if sys.version_info >= (3, 9): | ||
from types import GenericAlias | ||
if sys.version_info >= (3, 11): | ||
_TextMode: TypeAlias = Literal["r"] | ||
else: | ||
_TextMode: TypeAlias = Literal["r", "rU", "U"] | ||
|
||
_AnyStr_co = TypeVar("_AnyStr_co", str, bytes, covariant=True) | ||
|
||
class _HasReadlineAndFileno(Protocol[_AnyStr_co]): | ||
def readline(self) -> _AnyStr_co: ... | ||
def fileno(self) -> int: ... | ||
|
||
if sys.version_info >= (3, 10): | ||
# encoding and errors are added | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: _TextMode = ..., | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = ..., | ||
encoding: str | None = ..., | ||
errors: str | None = ..., | ||
) -> FileInput[str]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str = ..., | ||
openhook: Callable[[StrOrBytesPath, str], IO[AnyStr]] = ..., | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
encoding: None = ..., | ||
errors: None = ..., | ||
) -> FileInput[bytes]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
encoding: str | None = ..., | ||
errors: str | None = ..., | ||
) -> FileInput[AnyStr]: ... | ||
) -> FileInput[Any]: ... | ||
|
||
elif sys.version_info >= (3, 8): | ||
# bufsize is dropped and mode and openhook become keyword-only | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str = ..., | ||
openhook: Callable[[StrOrBytesPath, str], IO[AnyStr]] = ..., | ||
) -> FileInput[AnyStr]: ... | ||
mode: _TextMode = ..., | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = ..., | ||
) -> FileInput[str]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
) -> FileInput[bytes]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
) -> FileInput[Any]: ... | ||
|
||
else: | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
bufsize: int = ..., | ||
mode: _TextMode = ..., | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = ..., | ||
) -> FileInput[str]: ... | ||
# Because mode isn't keyword-only here yet, we need two overloads each for | ||
# the bytes case and the fallback case. | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
bufsize: int = ..., | ||
*, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
) -> FileInput[bytes]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, | ||
inplace: bool, | ||
backup: str, | ||
bufsize: int, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
) -> FileInput[bytes]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
bufsize: int = ..., | ||
mode: str = ..., | ||
openhook: Callable[[StrOrBytesPath, str], IO[AnyStr]] = ..., | ||
) -> FileInput[AnyStr]: ... | ||
*, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
) -> FileInput[Any]: ... | ||
@overload | ||
def input( | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, | ||
inplace: bool, | ||
backup: str, | ||
bufsize: int, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
) -> FileInput[Any]: ... | ||
|
||
def close() -> None: ... | ||
def nextfile() -> None: ... | ||
|
@@ -65,36 +162,131 @@ def isstdin() -> bool: ... | |
|
||
class FileInput(Iterator[AnyStr], Generic[AnyStr]): | ||
if sys.version_info >= (3, 10): | ||
# encoding and errors are added | ||
@overload | ||
def __init__( | ||
self, | ||
files: None | StrOrBytesPath | Iterable[StrOrBytesPath] = ..., | ||
self: FileInput[str], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str = ..., | ||
openhook: Callable[[StrOrBytesPath, str], IO[AnyStr]] = ..., | ||
mode: _TextMode = ..., | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = ..., | ||
encoding: str | None = ..., | ||
errors: str | None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self: FileInput[bytes], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
encoding: None = ..., | ||
errors: None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self: FileInput[Any], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
encoding: str | None = ..., | ||
errors: str | None = ..., | ||
) -> None: ... | ||
|
||
elif sys.version_info >= (3, 8): | ||
# bufsize is dropped and mode and openhook become keyword-only | ||
@overload | ||
def __init__( | ||
self: FileInput[str], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: _TextMode = ..., | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self: FileInput[bytes], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self, | ||
files: None | StrOrBytesPath | Iterable[StrOrBytesPath] = ..., | ||
self: FileInput[Any], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
*, | ||
mode: str = ..., | ||
openhook: Callable[[StrOrBytesPath, str], IO[AnyStr]] = ..., | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
) -> None: ... | ||
|
||
else: | ||
@overload | ||
def __init__( | ||
self, | ||
files: None | StrOrBytesPath | Iterable[StrOrBytesPath] = ..., | ||
self: FileInput[str], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
bufsize: int = ..., | ||
mode: str = ..., | ||
openhook: Callable[[StrOrBytesPath, str], IO[AnyStr]] = ..., | ||
mode: _TextMode = ..., | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[str]] | None = ..., | ||
) -> None: ... | ||
# Because mode isn't keyword-only here yet, we need two overloads each for | ||
# the bytes case and the fallback case. | ||
Comment on lines
+247
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is annoying, but it doesn't look like there's any other way of doing this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sebastian opened a conversation a while ago about allowing arguments without defaults to follow arguments with defaults, which would make this easier, but it didn't go anywhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was some pushback, because that's mostly useful for typing and considered bad API design otherwise, but honestly more from typing-critical developers, if I remember correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Plus ça change, plus c'est la même chose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative could be to add a special marker like @overload
def __init__(
self: FileInput[bytes],
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ...,
inplace: bool = ...,
backup: str = ...,
bufsize: int = ...,
mode: Literal["rb"] = NO_DEFAULT,
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ...,
) -> None: ... And the semantics would be that type checkers don't pick the overload if no value is given for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Oh, I like that quite a lot, actually. It would mean we wouldn't have to change Python's grammar at all. It does also feel like the kind of thing that would disproportionately benefit typeshed, though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was also a recent mypy issue where end-users were complaining about this, so it might be more broadly useful. If @srittau likes it too I can send it to typing-sig. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be possible to write a script that automatically applies There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was the thread. Looking back at it, the push back wasn't too bad. Maybe it would be worth trying again with a complete PEP? I'm not particularly fond of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I actually prefer
|
||
@overload | ||
def __init__( | ||
self: FileInput[bytes], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
bufsize: int = ..., | ||
*, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self: FileInput[bytes], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, | ||
inplace: bool, | ||
backup: str, | ||
bufsize: int, | ||
mode: Literal["rb"], | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[bytes]] | None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self: FileInput[Any], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None = ..., | ||
inplace: bool = ..., | ||
backup: str = ..., | ||
bufsize: int = ..., | ||
*, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
) -> None: ... | ||
@overload | ||
def __init__( | ||
self: FileInput[Any], | ||
files: StrOrBytesPath | Iterable[StrOrBytesPath] | None, | ||
inplace: bool, | ||
backup: str, | ||
bufsize: int, | ||
mode: str, | ||
openhook: Callable[[StrOrBytesPath, str], _HasReadlineAndFileno[Any]] | None = ..., | ||
) -> None: ... | ||
|
||
def __del__(self) -> None: ... | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We define the same
TypeVar
(with the same name) inbuiltins.pyi
andos/__init__.pyi
— is it worth adding it to_typeshed
, do you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's simple enough that I don't mind redefining it, but I don't feel strongly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can leave it for now