Skip to content

Commit 45f89cd

Browse files
authored
Improve typing on Windows (pallets#2803)
1 parent 4ffa1ef commit 45f89cd

File tree

4 files changed

+48
-30
lines changed

4 files changed

+48
-30
lines changed

src/click/_compat.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -538,14 +538,14 @@ def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO:
538538
rv = t.cast(t.TextIO, ansi_wrapper.stream)
539539
_write = rv.write
540540

541-
def _safe_write(s):
541+
def _safe_write(s: str) -> int:
542542
try:
543543
return _write(s)
544544
except BaseException:
545545
ansi_wrapper.reset_all()
546546
raise
547547

548-
rv.write = _safe_write
548+
rv.write = _safe_write # type: ignore[method-assign]
549549

550550
try:
551551
_ansi_stream_wrappers[stream] = rv

src/click/_termui_impl.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ def _translate_ch_to_exc(ch: str) -> None:
674674
return None
675675

676676

677-
if WIN:
677+
if sys.platform == "win32":
678678
import msvcrt
679679

680680
@contextlib.contextmanager
@@ -711,12 +711,11 @@ def getchar(echo: bool) -> str:
711711
#
712712
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
713713
# is doing the right thing in more situations than with `getch`.
714-
func: t.Callable[[], str]
715714

716715
if echo:
717-
func = msvcrt.getwche # type: ignore
716+
func = t.cast(t.Callable[[], str], msvcrt.getwche)
718717
else:
719-
func = msvcrt.getwch # type: ignore
718+
func = t.cast(t.Callable[[], str], msvcrt.getwch)
720719

721720
rv = func()
722721

src/click/_winconsole.py

+34-21
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys
1414
import time
1515
import typing as t
16+
from ctypes import Array
1617
from ctypes import byref
1718
from ctypes import c_char
1819
from ctypes import c_char_p
@@ -67,6 +68,14 @@
6768
EOF = b"\x1a"
6869
MAX_BYTES_WRITTEN = 32767
6970

71+
if t.TYPE_CHECKING:
72+
try:
73+
# Using `typing_extensions.Buffer` instead of `collections.abc`
74+
# on Windows for some reason does not have `Sized` implemented.
75+
from collections.abc import Buffer # type: ignore
76+
except ImportError:
77+
from typing_extensions import Buffer
78+
7079
try:
7180
from ctypes import pythonapi
7281
except ImportError:
@@ -93,32 +102,32 @@ class Py_buffer(Structure):
93102
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
94103
PyBuffer_Release = pythonapi.PyBuffer_Release
95104

96-
def get_buffer(obj, writable=False):
105+
def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]:
97106
buf = Py_buffer()
98-
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
107+
flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
99108
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
100109

101110
try:
102-
buffer_type = c_char * buf.len
111+
buffer_type: Array[c_char] = c_char * buf.len
103112
return buffer_type.from_address(buf.buf)
104113
finally:
105114
PyBuffer_Release(byref(buf))
106115

107116

108117
class _WindowsConsoleRawIOBase(io.RawIOBase):
109-
def __init__(self, handle):
118+
def __init__(self, handle: int | None) -> None:
110119
self.handle = handle
111120

112-
def isatty(self):
121+
def isatty(self) -> t.Literal[True]:
113122
super().isatty()
114123
return True
115124

116125

117126
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
118-
def readable(self):
127+
def readable(self) -> t.Literal[True]:
119128
return True
120129

121-
def readinto(self, b):
130+
def readinto(self, b: Buffer) -> int:
122131
bytes_to_be_read = len(b)
123132
if not bytes_to_be_read:
124133
return 0
@@ -150,18 +159,18 @@ def readinto(self, b):
150159

151160

152161
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
153-
def writable(self):
162+
def writable(self) -> t.Literal[True]:
154163
return True
155164

156165
@staticmethod
157-
def _get_error_message(errno):
166+
def _get_error_message(errno: int) -> str:
158167
if errno == ERROR_SUCCESS:
159168
return "ERROR_SUCCESS"
160169
elif errno == ERROR_NOT_ENOUGH_MEMORY:
161170
return "ERROR_NOT_ENOUGH_MEMORY"
162171
return f"Windows error {errno}"
163172

164-
def write(self, b):
173+
def write(self, b: Buffer) -> int:
165174
bytes_to_be_written = len(b)
166175
buf = get_buffer(b)
167176
code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
@@ -209,7 +218,7 @@ def __getattr__(self, name: str) -> t.Any:
209218
def isatty(self) -> bool:
210219
return self.buffer.isatty()
211220

212-
def __repr__(self):
221+
def __repr__(self) -> str:
213222
return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
214223

215224

@@ -267,16 +276,20 @@ def _get_windows_console_stream(
267276
f: t.TextIO, encoding: str | None, errors: str | None
268277
) -> t.TextIO | None:
269278
if (
270-
get_buffer is not None
271-
and encoding in {"utf-16-le", None}
272-
and errors in {"strict", None}
273-
and _is_console(f)
279+
get_buffer is None
280+
or encoding not in {"utf-16-le", None}
281+
or errors not in {"strict", None}
282+
or not _is_console(f)
274283
):
275-
func = _stream_factories.get(f.fileno())
276-
if func is not None:
277-
b = getattr(f, "buffer", None)
284+
return None
285+
286+
func = _stream_factories.get(f.fileno())
287+
if func is None:
288+
return None
289+
290+
b = getattr(f, "buffer", None)
278291

279-
if b is None:
280-
return None
292+
if b is None:
293+
return None
281294

282-
return func(b)
295+
return func(b)

tox.ini

+9-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ commands = pre-commit run --all-files
2323
[testenv:typing]
2424
deps = -r requirements/typing.txt
2525
commands =
26-
mypy
27-
pyright tests/typing
28-
pyright --verifytypes click --ignoreexternal
26+
mypy --platform linux
27+
mypy --platform darwin
28+
mypy --platform win32
29+
pyright tests/typing --pythonplatform Linux
30+
pyright tests/typing --pythonplatform Darwin
31+
pyright tests/typing --pythonplatform Windows
32+
pyright --verifytypes click --ignoreexternal --pythonplatform Linux
33+
pyright --verifytypes click --ignoreexternal --pythonplatform Darwin
34+
pyright --verifytypes click --ignoreexternal --pythonplatform Windows
2935

3036
[testenv:docs]
3137
deps = -r requirements/docs.txt

0 commit comments

Comments
 (0)