Skip to content

Commit 06b01c8

Browse files
authored
Fix 'WriteToConn' object has no attribute 'flush' (#16801)
`WriteToConn` replaces stdout and stderr to capture output, but causes issues because it doesn't implement the `TextIO` API (as expected of `sys.stdout` and `sys.stderr`). By stubbing the rest of the `TextIO` API we prevent issues with other code which uses more of the API than we had previously accounted for. Fixes #16678
1 parent ed50208 commit 06b01c8

File tree

2 files changed

+64
-5
lines changed

2 files changed

+64
-5
lines changed

mypy/dmypy_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ def serve(self) -> None:
221221
while True:
222222
with server:
223223
data = receive(server)
224-
sys.stdout = WriteToConn(server, "stdout") # type: ignore[assignment]
225-
sys.stderr = WriteToConn(server, "stderr") # type: ignore[assignment]
224+
sys.stdout = WriteToConn(server, "stdout", sys.stdout.isatty())
225+
sys.stderr = WriteToConn(server, "stderr", sys.stderr.isatty())
226226
resp: dict[str, Any] = {}
227227
if "command" not in data:
228228
resp = {"error": "No command found in request"}

mypy/dmypy_util.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
from __future__ import annotations
77

8+
import io
89
import json
9-
from typing import Any, Final, Iterable
10+
from types import TracebackType
11+
from typing import Any, Final, Iterable, Iterator, TextIO
1012

1113
from mypy.ipc import IPCBase
1214

@@ -40,19 +42,76 @@ def send(connection: IPCBase, data: Any) -> None:
4042
connection.write(json.dumps(data))
4143

4244

43-
class WriteToConn:
45+
class WriteToConn(TextIO):
4446
"""Helper class to write to a connection instead of standard output."""
4547

46-
def __init__(self, server: IPCBase, output_key: str = "stdout") -> None:
48+
def __init__(self, server: IPCBase, output_key: str, isatty: bool) -> None:
4749
self.server = server
4850
self.output_key = output_key
51+
self._isatty = isatty
52+
53+
def __enter__(self) -> TextIO:
54+
return self
55+
56+
def __exit__(
57+
self,
58+
t: type[BaseException] | None,
59+
value: BaseException | None,
60+
traceback: TracebackType | None,
61+
) -> None:
62+
pass
63+
64+
def __iter__(self) -> Iterator[str]:
65+
raise io.UnsupportedOperation
66+
67+
def __next__(self) -> str:
68+
raise io.UnsupportedOperation
69+
70+
def close(self) -> None:
71+
pass
72+
73+
def fileno(self) -> int:
74+
raise OSError
75+
76+
def flush(self) -> None:
77+
pass
78+
79+
def isatty(self) -> bool:
80+
return self._isatty
81+
82+
def read(self, n: int = 0) -> str:
83+
raise io.UnsupportedOperation
84+
85+
def readable(self) -> bool:
86+
return False
87+
88+
def readline(self, limit: int = 0) -> str:
89+
raise io.UnsupportedOperation
90+
91+
def readlines(self, hint: int = 0) -> list[str]:
92+
raise io.UnsupportedOperation
93+
94+
def seek(self, offset: int, whence: int = 0) -> int:
95+
raise io.UnsupportedOperation
96+
97+
def seekable(self) -> bool:
98+
return False
99+
100+
def tell(self) -> int:
101+
raise io.UnsupportedOperation
102+
103+
def truncate(self, size: int | None = 0) -> int:
104+
raise io.UnsupportedOperation
49105

50106
def write(self, output: str) -> int:
51107
resp: dict[str, Any] = {}
52108
resp[self.output_key] = output
53109
send(self.server, resp)
54110
return len(output)
55111

112+
def writable(self) -> bool:
113+
return True
114+
56115
def writelines(self, lines: Iterable[str]) -> None:
57116
for s in lines:
58117
self.write(s)

0 commit comments

Comments
 (0)