From 8d3eabd60426e71882d21c79dcab39c099b8d77c Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sat, 9 Nov 2024 01:12:18 +0000 Subject: [PATCH 1/4] Added typing to _winconsole.py --- src/click/_winconsole.py | 53 ++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/click/_winconsole.py b/src/click/_winconsole.py index a91af0e03..25acadf50 100644 --- a/src/click/_winconsole.py +++ b/src/click/_winconsole.py @@ -13,6 +13,7 @@ import sys import time import typing as t +from ctypes import Array from ctypes import byref from ctypes import c_char from ctypes import c_char_p @@ -67,6 +68,12 @@ EOF = b"\x1a" MAX_BYTES_WRITTEN = 32767 +if t.TYPE_CHECKING: + try: + from collections.abc import Buffer # type: ignore + except ImportError: + from typing_extensions import Buffer + try: from ctypes import pythonapi except ImportError: @@ -93,32 +100,32 @@ class Py_buffer(Structure): PyObject_GetBuffer = pythonapi.PyObject_GetBuffer PyBuffer_Release = pythonapi.PyBuffer_Release - def get_buffer(obj, writable=False): + def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]: buf = Py_buffer() - flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE PyObject_GetBuffer(py_object(obj), byref(buf), flags) try: - buffer_type = c_char * buf.len + buffer_type: Array[c_char] = c_char * buf.len return buffer_type.from_address(buf.buf) finally: PyBuffer_Release(byref(buf)) class _WindowsConsoleRawIOBase(io.RawIOBase): - def __init__(self, handle): + def __init__(self, handle: int | None) -> None: self.handle = handle - def isatty(self): + def isatty(self) -> t.Literal[True]: super().isatty() return True class _WindowsConsoleReader(_WindowsConsoleRawIOBase): - def readable(self): + def readable(self) -> t.Literal[True]: return True - def readinto(self, b): + def readinto(self, b: Buffer) -> int: bytes_to_be_read = len(b) if not bytes_to_be_read: return 0 @@ -150,18 +157,18 @@ def readinto(self, b): class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): - def writable(self): + def writable(self) -> t.Literal[True]: return True @staticmethod - def _get_error_message(errno): + def _get_error_message(errno: int) -> str: if errno == ERROR_SUCCESS: return "ERROR_SUCCESS" elif errno == ERROR_NOT_ENOUGH_MEMORY: return "ERROR_NOT_ENOUGH_MEMORY" return f"Windows error {errno}" - def write(self, b): + def write(self, b: Buffer) -> int: bytes_to_be_written = len(b) buf = get_buffer(b) code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 @@ -209,7 +216,7 @@ def __getattr__(self, name: str) -> t.Any: def isatty(self) -> bool: return self.buffer.isatty() - def __repr__(self): + def __repr__(self) -> str: return f"" @@ -267,16 +274,20 @@ def _get_windows_console_stream( f: t.TextIO, encoding: str | None, errors: str | None ) -> t.TextIO | None: if ( - get_buffer is not None - and encoding in {"utf-16-le", None} - and errors in {"strict", None} - and _is_console(f) + get_buffer is None + or encoding not in {"utf-16-le", None} + or errors not in {"strict", None} + or not _is_console(f) ): - func = _stream_factories.get(f.fileno()) - if func is not None: - b = getattr(f, "buffer", None) + return None + + func = _stream_factories.get(f.fileno()) + if func is None: + return None + + b = getattr(f, "buffer", None) - if b is None: - return None + if b is None: + return None - return func(b) + return func(b) From 63db9afc76a2b1d1cc0600db76de8e6abc225bd9 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sat, 9 Nov 2024 01:25:11 +0000 Subject: [PATCH 2/4] Added _compat typing. --- src/click/_compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/click/_compat.py b/src/click/_compat.py index 9e14ace5f..6ef508c96 100644 --- a/src/click/_compat.py +++ b/src/click/_compat.py @@ -538,14 +538,14 @@ def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO: rv = t.cast(t.TextIO, ansi_wrapper.stream) _write = rv.write - def _safe_write(s): + def _safe_write(s: str) -> int: try: return _write(s) except BaseException: ansi_wrapper.reset_all() raise - rv.write = _safe_write + rv.write = _safe_write # type: ignore[method-assign] try: _ansi_stream_wrappers[stream] = rv From 4ed4690776e5ef498d7d6b31662d06303a4ba128 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sat, 9 Nov 2024 01:45:02 +0000 Subject: [PATCH 3/4] Test removng of type: ignore when win32 mypy "magic" check is used --- src/click/_compat.py | 4 ++-- src/click/_termui_impl.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/click/_compat.py b/src/click/_compat.py index 6ef508c96..b559da717 100644 --- a/src/click/_compat.py +++ b/src/click/_compat.py @@ -10,8 +10,8 @@ from types import TracebackType from weakref import WeakKeyDictionary -CYGWIN = sys.platform.startswith("cygwin") -WIN = sys.platform.startswith("win") +CYGWIN = sys.platform == "cygwin" +WIN = sys.platform == "win32" auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None _ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") diff --git a/src/click/_termui_impl.py b/src/click/_termui_impl.py index 24dec542e..05e6d1f3c 100644 --- a/src/click/_termui_impl.py +++ b/src/click/_termui_impl.py @@ -666,7 +666,7 @@ def _translate_ch_to_exc(ch: str) -> None: return None -if WIN: +if sys.platform == "win32": import msvcrt @contextlib.contextmanager @@ -706,9 +706,9 @@ def getchar(echo: bool) -> str: func: t.Callable[[], str] if echo: - func = msvcrt.getwche # type: ignore + func = msvcrt.getwche else: - func = msvcrt.getwch # type: ignore + func = msvcrt.getwch rv = func() From cfba7c3bed7243895d4a3bf71a4473d0e2b34d6d Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Sat, 9 Nov 2024 16:49:06 +0000 Subject: [PATCH 4/4] Finalisation + extend tox typing to include all platforms. --- src/click/_compat.py | 4 ++-- src/click/_termui_impl.py | 5 ++--- src/click/_winconsole.py | 2 ++ tox.ini | 12 +++++++++--- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/click/_compat.py b/src/click/_compat.py index b559da717..6ef508c96 100644 --- a/src/click/_compat.py +++ b/src/click/_compat.py @@ -10,8 +10,8 @@ from types import TracebackType from weakref import WeakKeyDictionary -CYGWIN = sys.platform == "cygwin" -WIN = sys.platform == "win32" +CYGWIN = sys.platform.startswith("cygwin") +WIN = sys.platform.startswith("win") auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None _ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") diff --git a/src/click/_termui_impl.py b/src/click/_termui_impl.py index 05e6d1f3c..b72b97e91 100644 --- a/src/click/_termui_impl.py +++ b/src/click/_termui_impl.py @@ -703,12 +703,11 @@ def getchar(echo: bool) -> str: # # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` # is doing the right thing in more situations than with `getch`. - func: t.Callable[[], str] if echo: - func = msvcrt.getwche + func = t.cast(t.Callable[[], str], msvcrt.getwche) else: - func = msvcrt.getwch + func = t.cast(t.Callable[[], str], msvcrt.getwch) rv = func() diff --git a/src/click/_winconsole.py b/src/click/_winconsole.py index 25acadf50..b01035b29 100644 --- a/src/click/_winconsole.py +++ b/src/click/_winconsole.py @@ -70,6 +70,8 @@ if t.TYPE_CHECKING: try: + # Using `typing_extensions.Buffer` instead of `collections.abc` + # on Windows for some reason does not have `Sized` implemented. from collections.abc import Buffer # type: ignore except ImportError: from typing_extensions import Buffer diff --git a/tox.ini b/tox.ini index 5d0d18693..744b162e6 100644 --- a/tox.ini +++ b/tox.ini @@ -23,9 +23,15 @@ commands = pre-commit run --all-files [testenv:typing] deps = -r requirements/typing.txt commands = - mypy - pyright tests/typing - pyright --verifytypes click --ignoreexternal + mypy --platform linux + mypy --platform darwin + mypy --platform win32 + pyright tests/typing --pythonplatform Linux + pyright tests/typing --pythonplatform Darwin + pyright tests/typing --pythonplatform Windows + pyright --verifytypes click --ignoreexternal --pythonplatform Linux + pyright --verifytypes click --ignoreexternal --pythonplatform Darwin + pyright --verifytypes click --ignoreexternal --pythonplatform Windows [testenv:docs] deps = -r requirements/docs.txt