Skip to content

Conversation

@srittau
Copy link
Collaborator

@srittau srittau commented Mar 14, 2025

Cf. #10737

@srittau srittau marked this pull request as draft March 14, 2025 11:19
@github-actions

This comment has been minimized.

@srittau srittau added the topic: io I/O related issues label Mar 14, 2025
@srittau
Copy link
Collaborator Author

srittau commented Mar 14, 2025

Primer hits:

  • django-stubs (both hits): name field is annotated as str | None. I think having an explicit override here makes sense, as this is usually not an expected type by code accessing a name property.

This looks much better than I feared. I wish we had "kwonly" type vars, but I still think this is an improvement as it allows use to tighten the types to name fields of other I/O types in future PRs, which could lead to better type safety around this property with its rather unintuitive types.

@srittau srittau marked this pull request as ready for review March 14, 2025 12:14
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

Diff from mypy_primer, showing the effect of this PR on open source code:

django-stubs (https://github.com/typeddjango/django-stubs)
+ django-stubs/db/models/fields/files.pyi:22: error: Incompatible types in assignment (expression has type "str | None", base class "IO" defined the type as "str")  [assignment]
+ django-stubs/core/files/base.pyi:12: error: Incompatible types in assignment (expression has type "str | None", base class "IO" defined the type as "str")  [assignment]

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any] | None = ..., stdout: int | IO[Any] | None = ..., stderr: int | IO[Any] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: bool | None = ..., encoding: str, errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
+ src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any, str] | None = ..., stdout: int | IO[Any, str] | None = ..., stderr: int | IO[Any, str] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: bool | None = ..., encoding: str, errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
- src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any] | None = ..., stdout: int | IO[Any] | None = ..., stderr: int | IO[Any] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: bool | None = ..., encoding: str | None = ..., errors: str, user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
+ src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any, str] | None = ..., stdout: int | IO[Any, str] | None = ..., stderr: int | IO[Any, str] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: bool | None = ..., encoding: str | None = ..., errors: str, user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
- src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any] | None = ..., stdout: int | IO[Any] | None = ..., stderr: int | IO[Any] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., *, universal_newlines: Literal[True], startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., text: bool | None = ..., encoding: str | None = ..., errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
+ src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any, str] | None = ..., stdout: int | IO[Any, str] | None = ..., stderr: int | IO[Any, str] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., *, universal_newlines: Literal[True], startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., text: bool | None = ..., encoding: str | None = ..., errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
- src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any] | None = ..., stdout: int | IO[Any] | None = ..., stderr: int | IO[Any] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: Literal[True], encoding: str | None = ..., errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
+ src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any, str] | None = ..., stdout: int | IO[Any, str] | None = ..., stderr: int | IO[Any, str] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: Literal[True], encoding: str | None = ..., errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[str]
- src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any] | None = ..., stdout: int | IO[Any] | None = ..., stderr: int | IO[Any] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: Literal[False] | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: Literal[False] | None = ..., encoding: None = ..., errors: None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[bytes]
+ src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any, str] | None = ..., stdout: int | IO[Any, str] | None = ..., stderr: int | IO[Any, str] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: Literal[False] | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: Literal[False] | None = ..., encoding: None = ..., errors: None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[bytes]
- src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any] | None = ..., stdout: int | IO[Any] | None = ..., stderr: int | IO[Any] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: bool | None = ..., encoding: str | None = ..., errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[Any]
+ src/prefect/cli/shell.py:99: note:     def [AnyStr: (str, bytes)] __init__(self, args: str | bytes | PathLike[str] | PathLike[bytes] | Sequence[str | bytes | PathLike[str] | PathLike[bytes]], bufsize: int = ..., executable: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., stdin: int | IO[Any, str] | None = ..., stdout: int | IO[Any, str] | None = ..., stderr: int | IO[Any, str] | None = ..., preexec_fn: Callable[[], Any] | None = ..., close_fds: bool = ..., shell: bool = ..., cwd: str | bytes | PathLike[str] | PathLike[bytes] | None = ..., env: Mapping[bytes, str | bytes | PathLike[str] | PathLike[bytes]] | Mapping[str, str | bytes | PathLike[str] | PathLike[bytes]] | None = ..., universal_newlines: bool | None = ..., startupinfo: Any | None = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Collection[int] = ..., *, text: bool | None = ..., encoding: str | None = ..., errors: str | None = ..., user: str | int | None = ..., group: str | int | None = ..., extra_groups: Iterable[str | int] | None = ..., umask: int = ..., pipesize: int = ...) -> Popen[Any]
- src/prefect/cli/shell.py:106: error: Incompatible types in assignment (expression has type "Callable[[IO[str], list[str]], None]", variable has type "Callable[[IO[str], Callable[[str], None]], None]")  [assignment]
+ src/prefect/cli/shell.py:106: error: Incompatible types in assignment (expression has type "Callable[[IO[str, str], list[str]], None]", variable has type "Callable[[IO[str, str], Callable[[str], None]], None]")  [assignment]

pytest-robotframework (https://github.com/detachhead/pytest-robotframework)
- pytest_robotframework/_internal/pytest/plugin.py:452: error: "_RedirectStream[IO[str]]" has no attribute "_stream"  [attr-defined]
+ pytest_robotframework/_internal/pytest/plugin.py:452: error: "_RedirectStream[IO[str, str]]" has no attribute "_stream"  [attr-defined]

discord.py (https://github.com/Rapptz/discord.py)
- ...typeshed_to_test/stdlib/typing.pyi:1036: note: "update" of "TypedDict" defined here
+ ...typeshed_to_test/stdlib/typing.pyi:1041: note: "update" of "TypedDict" defined here

werkzeug (https://github.com/pallets/werkzeug)
- tests/test_wsgi.py:261: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:261: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:265: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:265: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:270: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:270: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:274: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:274: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:280: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:280: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:288: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:288: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:297: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:297: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wsgi.py:305: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes]]"  [arg-type]
+ tests/test_wsgi.py:305: error: Argument 1 to "_RangeWrapper" has incompatible type "Union[Iterable[str], Iterable[bytes]]"; expected "Union[Iterable[bytes], IO[bytes, str]]"  [arg-type]
- tests/test_wrappers.py:465: error: Incompatible types in assignment (expression has type "LowercasingStream", variable has type "IO[bytes]")  [assignment]
+ tests/test_wrappers.py:465: error: Incompatible types in assignment (expression has type "LowercasingStream", variable has type "IO[bytes, str]")  [assignment]
- tests/test_test.py:166: error: Item "IO[bytes]" of "Optional[IO[bytes]]" has no attribute "getvalue"  [union-attr]
+ tests/test_test.py:166: error: Item "IO[bytes, str]" of "Optional[IO[bytes, str]]" has no attribute "getvalue"  [union-attr]
- tests/test_test.py:166: error: Item "None" of "Optional[IO[bytes]]" has no attribute "getvalue"  [union-attr]
+ tests/test_test.py:166: error: Item "None" of "Optional[IO[bytes, str]]" has no attribute "getvalue"  [union-attr]
- tests/test_test.py:168: error: Item "IO[bytes]" of "Optional[IO[bytes]]" has no attribute "getvalue"  [union-attr]
+ tests/test_test.py:168: error: Item "IO[bytes, str]" of "Optional[IO[bytes, str]]" has no attribute "getvalue"  [union-attr]
- tests/test_test.py:168: error: Item "None" of "Optional[IO[bytes]]" has no attribute "getvalue"  [union-attr]
+ tests/test_test.py:168: error: Item "None" of "Optional[IO[bytes, str]]" has no attribute "getvalue"  [union-attr]
- tests/test_test.py:438: error: Argument 2 to "add_file" of "FileMultiDict" has incompatible type "SpecialInput"; expected "Union[str, PathLike[str], IO[bytes], FileStorage]"  [arg-type]
+ tests/test_test.py:438: error: Argument 2 to "add_file" of "FileMultiDict" has incompatible type "SpecialInput"; expected "Union[str, PathLike[str], IO[bytes, str], FileStorage]"  [arg-type]
- tests/test_formparser.py:196: error: Non-overlapping equality check (left operand type: "tuple[IO[bytes], MultiDict[str, str], MultiDict[str, FileStorage]]", right operand type: "tuple[str, MultiDict[Never, Never], MultiDict[Never, Never]]")  [comparison-overlap]
+ tests/test_formparser.py:196: error: Non-overlapping equality check (left operand type: "tuple[IO[bytes, str], MultiDict[str, str], MultiDict[str, FileStorage]]", right operand type: "tuple[str, MultiDict[Never, Never], MultiDict[Never, Never]]")  [comparison-overlap]
- tests/test_formparser.py:196: error: Argument 1 to "parse" of "FormDataParser" has incompatible type "str"; expected "IO[bytes]"  [arg-type]
+ tests/test_formparser.py:196: error: Argument 1 to "parse" of "FormDataParser" has incompatible type "str"; expected "IO[bytes, str]"  [arg-type]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: io I/O related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant