diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 591df5387..34a7ee708 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,15 +51,39 @@ jobs: uses: actions/setup-python@v7 with: python-version: 3.x + cache: pip + cache-dependency-path: pyproject.toml - uses: actions/cache@v6 with: path: ~/.cache/pip key: pip-pyright - name: Install dependencies - run: pip install -e . pyright + run: pip install --group pyright -e . - name: Run pyright run: pyright --ignoreexternal --verifytypes anyio + zuban: + runs-on: ubuntu-latest + needs: changed-files + if: | + ${{ + (needs.changed-files.outputs.workflow-changed == 'true') + || (needs.changed-files.outputs.src-changed == 'true') + || (needs.changed-files.outputs.tests-changed == 'true') + }} + steps: + - uses: actions/checkout@v7 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: 3.x + cache: pip + cache-dependency-path: pyproject.toml + - name: Install dependencies + run: pip install --group zuban -e . + - name: Run zuban + run: zuban check + test: strategy: fail-fast: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa62a16a1..411840212 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,15 +35,6 @@ repos: args: [--fix, --show-fixes] - id: ruff-format - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.3.1 - hooks: - - id: mypy - pass_filenames: false - additional_dependencies: - - pytest - - trio >= 0.26 - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index 7a53c2f18..cb02a5ee8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,12 @@ doc = [ "sphinx-autodoc-typehints >= 1.2.0", "sphinx-tabs >= 3.3.1", ] +pyright = [ + "pyright >= 1.1.411", +] +zuban = [ + "zuban >= 0.9.1", +] [tool.setuptools_scm] version_scheme = "post-release" @@ -113,12 +119,13 @@ extend-safe-fixes = ["TC001"] "tests/test_tempfile.py" = ["ASYNC230"] "tests/*.py" = ["ASYNC240"] -[tool.mypy] +[tool.zuban] python_version = "3.14" strict = true +warn_unreachable = false disallow_any_generics = false warn_return_any = false -files = ["src", "tests"] +files = ["src"] [tool.pytest] addopts = [ @@ -186,8 +193,14 @@ depends = [] allowlist_externals = ["pre-commit"] package = "skip" +[tool.tox.env.zuban] +commands = [["zuban", "check"]] +dependency_groups = ["zuban"] +allowlist_externals = ["zuban"] +package = "skip" + [tool.tox.env.pyright] -deps = ["pyright"] +dependency_groups = ["pyright"] commands = [["pyright", "--ignoreexternal", "--verifytypes", "anyio"]] [tool.tox.env.docs] diff --git a/src/anyio/_backends/_asyncio.py b/src/anyio/_backends/_asyncio.py index 7eba257c9..447a749e7 100644 --- a/src/anyio/_backends/_asyncio.py +++ b/src/anyio/_backends/_asyncio.py @@ -1488,6 +1488,7 @@ def callback(f: object) -> None: del self._receive_future loop.remove_reader(self.__raw_socket) + f: asyncio.Future[None] f = self._receive_future = asyncio.Future() loop.add_reader(self.__raw_socket, f.set_result, None) f.add_done_callback(callback) @@ -1498,6 +1499,7 @@ def callback(f: object) -> None: del self._send_future loop.remove_writer(self.__raw_socket) + f: asyncio.Future[None] f = self._send_future = asyncio.Future() loop.add_writer(self.__raw_socket, f.set_result, None) f.add_done_callback(callback) @@ -2647,7 +2649,7 @@ def create_capacity_limiter(cls, total_tokens: float) -> BaseCapacityLimiter: return CapacityLimiter(total_tokens) @classmethod - async def run_sync_in_worker_thread( # type: ignore[return] + async def run_sync_in_worker_thread( # type: ignore[return-value] cls, func: Callable[[Unpack[PosArgsT]], T_Retval], args: tuple[Unpack[PosArgsT]], @@ -2843,11 +2845,8 @@ def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: async def connect_tcp( cls, host: str, port: int, local_address: IPSockAddrType | None = None ) -> abc.SocketStream: - transport, protocol = cast( - tuple[asyncio.Transport, StreamProtocol], - await get_running_loop().create_connection( - StreamProtocol, host, port, local_addr=local_address - ), + transport, protocol = await get_running_loop().create_connection( + StreamProtocol, host, port, local_addr=local_address ) transport.pause_reading() return SocketStream(transport, protocol) diff --git a/src/anyio/_core/_contextmanagers.py b/src/anyio/_core/_contextmanagers.py index 302f32b0c..83a83c108 100644 --- a/src/anyio/_core/_contextmanagers.py +++ b/src/anyio/_core/_contextmanagers.py @@ -69,7 +69,7 @@ def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co: value = cm.__enter__() self.__cm = cm - return value + return cast(_T_co, value) @final def __exit__( @@ -162,7 +162,7 @@ async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co: value = await cm.__aenter__() self.__cm = cm - return value + return cast(_T_co, value) @final async def __aexit__( diff --git a/src/anyio/_core/_typedattr.py b/src/anyio/_core/_typedattr.py index 59d3da9d8..e41244de0 100644 --- a/src/anyio/_core/_typedattr.py +++ b/src/anyio/_core/_typedattr.py @@ -75,7 +75,7 @@ def extra(self, attribute: Any, default: object = undefined) -> object: """ try: - getter = self.extra_attributes[attribute] + getter: Callable[[], object] = self.extra_attributes[attribute] except KeyError: if default is undefined: raise TypedAttributeLookupError("Attribute not found") from None diff --git a/src/anyio/itertools.py b/src/anyio/itertools.py index 2a10cfd3b..3eddda276 100644 --- a/src/anyio/itertools.py +++ b/src/anyio/itertools.py @@ -144,7 +144,7 @@ async def __anext__(self) -> T: async def _operator_add(x: T, y: T) -> T: - return operator.add(x, y) + return operator.add(x, y) # type: ignore[call-overload] async def accumulate( diff --git a/src/anyio/pytest_plugin.py b/src/anyio/pytest_plugin.py index d6238cd11..d2dc65c58 100644 --- a/src/anyio/pytest_plugin.py +++ b/src/anyio/pytest_plugin.py @@ -232,11 +232,11 @@ def pytest_collection_finish(session: pytest.Session) -> None: ) else: # pytest 7.x callspec = CallSpec( # type: ignore[call-arg] - funcargs={}, + funcargs={}, # type: ignore[call-arg] params={"anyio_backend": backend}, indices={"anyio_backend": param_index}, - arg2scope={"anyio_backend": Scope.Module}, - idlist=[backend], + arg2scope={"anyio_backend": Scope.Module}, # type: ignore[call-arg] + idlist=[backend], # type: ignore[call-arg] marks=[], ) diff --git a/src/anyio/to_process.py b/src/anyio/to_process.py index 584128ec3..41d17f50c 100644 --- a/src/anyio/to_process.py +++ b/src/anyio/to_process.py @@ -42,7 +42,7 @@ _default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter") -async def run_sync( # type: ignore[return] +async def run_sync( # type: ignore[return-value] func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT], cancellable: bool = False,