From 877d8e671fd37fe6f733dc103920c0bca0865df7 Mon Sep 17 00:00:00 2001 From: Alex Popov Date: Thu, 3 Sep 2026 00:44:35 +0300 Subject: [PATCH] chore: enable mypy disallow untyped decorators and update type hints in webdriver.py --- py/pyproject.toml | 2 +- py/selenium/webdriver/remote/webdriver.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/py/pyproject.toml b/py/pyproject.toml index 4b0080a95afa5..cc0be2306f685 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -115,7 +115,7 @@ disallow_incomplete_defs = false # type-checks the interior of functions without type annotations. check_untyped_defs = false # reports an error whenever a function with type annotations is decorated with a decorator without annotations. -disallow_untyped_decorators = false +disallow_untyped_decorators = true # changes the treatment of arguments with a default value of None by not implicitly making their type `typing.Optional`. no_implicit_optional = true # warns about casting an expression to it's inferred type. diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index a99369ffa1112..4040112f18f3f 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -32,10 +32,10 @@ import zipfile from abc import ABCMeta from base64 import b64decode, urlsafe_b64encode -from collections.abc import Generator +from collections.abc import Callable, Generator from contextlib import asynccontextmanager, contextmanager from importlib import import_module -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, cast from typing_extensions import Self @@ -173,6 +173,11 @@ def create_matches(options: list[BaseOptions]) -> dict: return capabilities +_P = ParamSpec("_P") +_R = TypeVar("_R") +_D = TypeVar("_D", bound="WebDriver") + + if TYPE_CHECKING: from selenium.webdriver.common.api_request_context import APIRequestContext from selenium.webdriver.common.fedcm.dialog import Dialog @@ -181,9 +186,9 @@ def create_matches(options: list[BaseOptions]) -> dict: from selenium.webdriver.common.virtual_authenticator import Credential, VirtualAuthenticatorOptions -def _required_chromium_based_browser(func): +def _required_chromium_based_browser(func: Callable[Concatenate[_D, _P], _R]) -> Callable[Concatenate[_D, _P], _R]: @functools.wraps(func) - def wrapper(self, *args, **kwargs): + def wrapper(self: _D, *args: _P.args, **kwargs: _P.kwargs) -> _R: assert self.caps["browserName"].lower() not in ["firefox", "safari"], ( "This only currently works in Chromium based browsers" ) @@ -192,10 +197,10 @@ def wrapper(self, *args, **kwargs): return wrapper -def _required_virtual_authenticator(func): +def _required_virtual_authenticator(func: Callable[Concatenate[_D, _P], _R]) -> Callable[Concatenate[_D, _P], _R]: @functools.wraps(func) @_required_chromium_based_browser - def wrapper(self, *args, **kwargs): + def wrapper(self: _D, *args: _P.args, **kwargs: _P.kwargs) -> _R: if not self.virtual_authenticator_id: raise ValueError("This function requires a virtual authenticator to be set.") return func(self, *args, **kwargs)