diff --git a/pyproject.toml b/pyproject.toml index e9da23ab6..c3fa2c7e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ tests-random = [ ] typing = [ "mypy", + "pyrefly>=1.2.0", "pyright", "pytest", ] @@ -196,6 +197,7 @@ dependency_groups = ["typing"] commands = [ ["mypy"], ["pyright", "--ignoreexternal", "--verifytypes", "click"], + ["pyrefly", "check"], ] [tool.tox.env.docs] @@ -229,3 +231,17 @@ dependency_groups = [] no_default_groups = true skip_install = true commands = [["uv", "lock", {replace = "posargs", default = ["-U"], extend = true}]] + +[tool.pyrefly] +project-includes = [ + "src", + "tests/typing", +] +# Windows-only module (guarded by `assert sys.platform == "win32"`); mypy and +# pyright both skip it as unreachable on other platforms, but pyrefly does +# not, so it is excluded here to avoid noise from missing Windows stubs. +project-excludes = [ + "src/click/_winconsole.py", +] +python-version = "3.10.0" +preset = "legacy" diff --git a/src/click/termui.py b/src/click/termui.py index 8fe008527..e187d6d6a 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -779,7 +779,7 @@ def unstyle(text: str) -> str: def secho( message: t.Any | None = None, - file: t.IO[t.AnyStr] | None = None, + file: t.IO[t.Any] | None = None, nl: bool = True, err: bool = False, color: bool | None = None, diff --git a/src/click/testing.py b/src/click/testing.py index cc123d4d1..3aa0c2f01 100644 --- a/src/click/testing.py +++ b/src/click/testing.py @@ -22,6 +22,11 @@ from .core import Command + if sys.version_info >= (3, 13): + from typing import TypeIs + else: + from typing_extensions import TypeIs + if sys.platform == "win32": CaptureMode: t.TypeAlias = t.Literal["sys"] # pyright: ignore[reportRedeclaration] else: @@ -208,12 +213,16 @@ def mode(self) -> str: return self._mode +def _has_read(x: object) -> TypeIs[t.IO[t.Any]]: + return hasattr(x, "read") + + def make_input_stream( input: str | bytes | t.IO[t.Any] | None, charset: str ) -> t.BinaryIO: # Is already an input stream. - if hasattr(input, "read"): - rv = _find_binary_reader(t.cast("t.IO[t.Any]", input)) + if _has_read(input): + rv = _find_binary_reader(input) if rv is not None: return rv diff --git a/src/click/types.py b/src/click/types.py index 0eb84621d..c418c8ea7 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -730,6 +730,7 @@ def __repr__(self) -> str: return "INT" +# pyrefly: ignore [invalid-inheritance] # https://github.com/facebook/pyrefly/issues/4842 class IntRange(_NumberRangeBase[int, int], IntParamType): """Restrict an :data:`click.INT` value to a range of accepted values. See :ref:`ranges`. @@ -762,6 +763,7 @@ def __repr__(self) -> str: return "FLOAT" +# pyrefly: ignore [invalid-inheritance] # https://github.com/facebook/pyrefly/issues/4842 class FloatRange(_NumberRangeBase[float, float], FloatParamType): """Restrict a :data:`click.FLOAT` value to a range of accepted values. See :ref:`ranges`. diff --git a/src/click/utils.py b/src/click/utils.py index b529eb04b..5d1c88679 100644 --- a/src/click/utils.py +++ b/src/click/utils.py @@ -203,7 +203,7 @@ def __exit__( ) -> None: self.close_intelligently() - def __iter__(self) -> cabc.Iterator[t.AnyStr]: + def __iter__(self) -> cabc.Iterator[t.Any]: self.open() return iter(self._f) # type: ignore @@ -245,7 +245,7 @@ def __exit__( def __repr__(self) -> str: return repr(self._file) - def __iter__(self) -> cabc.Iterator[t.AnyStr]: + def __iter__(self) -> cabc.Iterator[t.Any]: return iter(self._file) diff --git a/uv.lock b/uv.lock index bd9cefa99..15e94efc5 100644 --- a/uv.lock +++ b/uv.lock @@ -213,6 +213,7 @@ tests-random = [ ] typing = [ { name = "mypy" }, + { name = "pyrefly" }, { name = "pyright" }, { name = "pytest" }, ] @@ -246,6 +247,7 @@ tests-random = [ ] typing = [ { name = "mypy" }, + { name = "pyrefly", specifier = ">=1.2.0" }, { name = "pyright" }, { name = "pytest" }, ] @@ -865,6 +867,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09", size = 13218, upload-time = "2025-10-09T19:12:24.428Z" }, ] +[[package]] +name = "pyrefly" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/01/a86e9f24722b095c3f88e3616132b75a21b0df53804bdc6a45314dd4d93c/pyrefly-1.2.0.tar.gz", hash = "sha256:5485f960fc2481617068c918335c39ab1507ef90b6b5bd35bf57726e60e73185", size = 6243654, upload-time = "2026-08-01T02:56:27.592Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/9d/3c0ef1d4843987b22f996ed381ec9cf5a3b1273e29804db276252e4c95eb/pyrefly-1.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7f46d983ac49ddd2b043694960a01dc6a19a5cfd8eec609d6bd9c42866f91b4e", size = 14026305, upload-time = "2026-08-01T02:56:02.611Z" }, + { url = "https://files.pythonhosted.org/packages/0a/06/03bbb78fbea54cdc65b626619f3597d5611aca4fdef11e72a4e8360e7e63/pyrefly-1.2.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:756f669b5555090f5c1a4fef30db1785fabe657764f7e4e6dc88994dfb8ca82d", size = 13463880, upload-time = "2026-08-01T02:56:04.93Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/7d8bc00a38e93bbc9c3e7bd14d305f7948717e667c9bcddeab9dd42fd255/pyrefly-1.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3465812ce5ef4781fb592edbf2724547296f0a3124be115d73c7e8b2401862d", size = 13907329, upload-time = "2026-08-01T02:56:07.104Z" }, + { url = "https://files.pythonhosted.org/packages/be/94/9e08b4bf799d0b8f36b55a2783c7ba5f51730cf0632a85a67b5b5ed876cd/pyrefly-1.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5de7b2ad2bba5c8055181681a84b74143eac2234a48ba5d1b7ed7e7a722b02bd", size = 15039020, upload-time = "2026-08-01T02:56:09.208Z" }, + { url = "https://files.pythonhosted.org/packages/5b/bd/bca5fd0c80f4daf8ee6903a29df9f3de1feb05ff0946b8f35ec8c5096b13/pyrefly-1.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25822ea9505f589ea8a725e4268b475132fb89e038fbf092e446510443ac142a", size = 14986199, upload-time = "2026-08-01T02:56:11.924Z" }, + { url = "https://files.pythonhosted.org/packages/97/f7/f07087f3d185ad2eced0c56cef89ca5474dfb4ff25f146cd50a861c97553/pyrefly-1.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90efe75e17491ef5d636e10469e9278d7d0256b3b4c5e1f4750069bf3ae0f5d1", size = 14393715, upload-time = "2026-08-01T02:56:14.143Z" }, + { url = "https://files.pythonhosted.org/packages/d3/70/0d142c320e284b9e3ce35e9b1e58b8ce2ee1f578f2a7234bc30e5022b94f/pyrefly-1.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:368aaf7eee4f511ddc0f8e564cf14e01ab2f10b0db9105c6d5b153bf498d07bf", size = 13933008, upload-time = "2026-08-01T02:56:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e8/e84f11b6e1f63fd453ad3654213b9a0f6f4de8cef6b58038eef2d0d5955d/pyrefly-1.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52d5da7bc65fb7675fbaa80eda879d4f8787c494f04cac21603330d3abbdbbe", size = 14431827, upload-time = "2026-08-01T02:56:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/0f/06/810d31380f66c75e1c0779a408d3b16117b1b368b57894f6aa66bef21686/pyrefly-1.2.0-py3-none-win32.whl", hash = "sha256:8c90751de8506d938e8f802659c74cf35bd7a0036510ee6c634a38eebb280bfa", size = 13229447, upload-time = "2026-08-01T02:56:20.921Z" }, + { url = "https://files.pythonhosted.org/packages/ed/98/4dafa3c7a1caed2dc8cc708dde09ba27963c7736508f55b626fff3024113/pyrefly-1.2.0-py3-none-win_amd64.whl", hash = "sha256:8a8964c224ccc4882730130955815de21ff443c1ac3f0b90685b19bf63848170", size = 14087387, upload-time = "2026-08-01T02:56:23.188Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1c/df3cb0a2e5591660ded7a1836cd2f29dc48c91adb1c0a3a700a96f6d09e1/pyrefly-1.2.0-py3-none-win_arm64.whl", hash = "sha256:3a90bb8df39dfbac74b1f3b2e9d7c526b8f80568884c3944d955023a73ebf61e", size = 13430873, upload-time = "2026-08-01T02:56:25.425Z" }, +] + [[package]] name = "pyright" version = "1.1.408"