Skip to content

Commit 13af249

Browse files
authored
[py] Fix type annotations, make docstrings consistent, centralize dev dependencies (#16821)
1 parent f3be951 commit 13af249

File tree

17 files changed

+158
-111
lines changed

17 files changed

+158
-111
lines changed

.github/workflows/ci-python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
run: |
5555
tox -c py/tox.ini || true
5656
env:
57-
TOXENV: mypy
57+
TOXENV: typecheck
5858

5959
unit-tests:
6060
name: Unit Tests

py/pyproject.toml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ changelog = "https://github.com/SeleniumHQ/selenium/blob/trunk/py/CHANGES"
4242
documentation = "https://www.selenium.dev/documentation/webdriver"
4343
issues = "https://github.com/SeleniumHQ/selenium/issues"
4444

45+
[dependency-groups]
46+
lint = [
47+
"ruff==0.14.10",
48+
]
49+
typecheck = [
50+
"mypy==1.19.1",
51+
"types-urllib3==1.26.25.14",
52+
"types-certifi==2021.10.8.3",
53+
"trio-typing==0.10.0",
54+
"trio-websocket>=0.12.2,<1.0",
55+
]
56+
validate = [
57+
"validate-pyproject==0.24.1",
58+
"packaging==25.0",
59+
]
60+
4561
[tool.setuptools]
4662
zip-safe = false
4763

@@ -126,11 +142,6 @@ warn_return_any = false
126142
# Shows a warning when encountering any code inferred to be unreachable after performing type analysis.
127143
warn_unreachable = false
128144

129-
# mypy module specific options
130-
[[tool.mypy.trio_websocket]]
131-
# suppress error messages about imports that cannot be resolved.
132-
ignore_missing_imports = true
133-
134145
[tool.ruff]
135146
extend-exclude = [
136147
"selenium/webdriver/common/devtools/",

py/requirements.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
attrs==25.4.0
22
backports.tarfile==1.2.0
3+
cachetools==6.2.4
4+
chardet==5.2.0
35
certifi==2025.11.12
46
cffi==2.0.0
57
charset-normalizer==3.4.4
68
colorama==0.4.6
79
cryptography==46.0.3
10+
distlib==0.4.0
811
docutils==0.21.2
912
exceptiongroup==1.3.1
13+
filelock==3.20.1
1014
filetype==1.2.0
1115
h11==0.16.0
1216
id==1.5.0
@@ -25,8 +29,10 @@ more-itertools==10.8.0
2529
nh3==0.3.2
2630
outcome==1.3.0.post0
2731
packaging==25.0
32+
platformdirs==4.5.1
2833
pluggy==1.6.0
2934
pycparser==2.23
35+
pyproject-api==1.10.0
3036
Pygments==2.19.2
3137
PySocks==1.7.1
3238
pytest==9.0.2
@@ -43,11 +49,13 @@ SecretStorage==3.5.0
4349
sniffio==1.3.1
4450
sortedcontainers==2.4.0
4551
tomli==2.3.0
52+
tox==4.32.0
4653
trio==0.32.0
4754
trio-websocket==0.12.2
4855
twine==6.2.0
4956
typing_extensions==4.15.0
5057
urllib3[socks]==2.6.2
58+
virtualenv==20.35.4
5159
websocket-client==1.9.0
5260
wsproto==1.3.2
5361
zipp==3.23.0

py/requirements_lock.txt

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ backports-tarfile==1.2.0 \
1717
# via
1818
# -r py/requirements.txt
1919
# jaraco-context
20+
cachetools==6.2.4 \
21+
--hash=sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51 \
22+
--hash=sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607
23+
# via
24+
# -r py/requirements.txt
25+
# tox
2026
certifi==2025.11.12 \
2127
--hash=sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b \
2228
--hash=sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316
@@ -111,6 +117,12 @@ cffi==2.0.0 \
111117
# via
112118
# -r py/requirements.txt
113119
# cryptography
120+
chardet==5.2.0 \
121+
--hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \
122+
--hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970
123+
# via
124+
# -r py/requirements.txt
125+
# tox
114126
charset-normalizer==3.4.4 \
115127
--hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \
116128
--hash=sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93 \
@@ -231,7 +243,9 @@ charset-normalizer==3.4.4 \
231243
colorama==0.4.6 \
232244
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
233245
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
234-
# via -r py/requirements.txt
246+
# via
247+
# -r py/requirements.txt
248+
# tox
235249
cryptography==46.0.3 \
236250
--hash=sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217 \
237251
--hash=sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d \
@@ -290,6 +304,12 @@ cryptography==46.0.3 \
290304
# via
291305
# -r py/requirements.txt
292306
# secretstorage
307+
distlib==0.4.0 \
308+
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
309+
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
310+
# via
311+
# -r py/requirements.txt
312+
# virtualenv
293313
docutils==0.21.2 \
294314
--hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \
295315
--hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2
@@ -304,6 +324,13 @@ exceptiongroup==1.3.1 \
304324
# pytest
305325
# trio
306326
# trio-websocket
327+
filelock==3.20.1 \
328+
--hash=sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a \
329+
--hash=sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c
330+
# via
331+
# -r py/requirements.txt
332+
# tox
333+
# virtualenv
307334
filetype==1.2.0 \
308335
--hash=sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb \
309336
--hash=sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25
@@ -436,14 +463,24 @@ packaging==25.0 \
436463
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
437464
# via
438465
# -r py/requirements.txt
466+
# pyproject-api
439467
# pytest
468+
# tox
440469
# twine
470+
platformdirs==4.5.1 \
471+
--hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
472+
--hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31
473+
# via
474+
# -r py/requirements.txt
475+
# tox
476+
# virtualenv
441477
pluggy==1.6.0 \
442478
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
443479
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
444480
# via
445481
# -r py/requirements.txt
446482
# pytest
483+
# tox
447484
pycparser==2.23 \
448485
--hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \
449486
--hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934
@@ -458,6 +495,12 @@ pygments==2.19.2 \
458495
# pytest
459496
# readme-renderer
460497
# rich
498+
pyproject-api==1.10.0 \
499+
--hash=sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330 \
500+
--hash=sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09
501+
# via
502+
# -r py/requirements.txt
503+
# tox
461504
pysocks==1.7.1 \
462505
--hash=sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299 \
463506
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
@@ -584,7 +627,13 @@ tomli==2.3.0 \
584627
--hash=sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876
585628
# via
586629
# -r py/requirements.txt
630+
# pyproject-api
587631
# pytest
632+
# tox
633+
tox==4.32.0 \
634+
--hash=sha256:1ad476b5f4d3679455b89a992849ffc3367560bbc7e9495ee8a3963542e7c8ff \
635+
--hash=sha256:451e81dc02ba8d1ed20efd52ee409641ae4b5d5830e008af10fe8823ef1bd551
636+
# via -r py/requirements.txt
588637
trio==0.32.0 \
589638
--hash=sha256:150f29ec923bcd51231e1d4c71c7006e65247d68759dd1c19af4ea815a25806b \
590639
--hash=sha256:4ab65984ef8370b79a76659ec87aa3a30c5c7c83ff250b4de88c29a8ab6123c5
@@ -607,13 +656,21 @@ typing-extensions==4.15.0 \
607656
# -r py/requirements.txt
608657
# cryptography
609658
# exceptiongroup
659+
# tox
660+
# virtualenv
610661
urllib3[socks]==2.6.2 \
611662
--hash=sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797 \
612663
--hash=sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd
613664
# via
614665
# -r py/requirements.txt
615666
# requests
616667
# twine
668+
virtualenv==20.35.4 \
669+
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
670+
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
671+
# via
672+
# -r py/requirements.txt
673+
# tox
617674
websocket-client==1.9.0 \
618675
--hash=sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98 \
619676
--hash=sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef

py/selenium/webdriver/chrome/service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(
4646
env: Mapping[str, str] | None = None,
4747
**kwargs,
4848
) -> None:
49-
self._service_args = service_args or []
49+
self._service_args = list(service_args or [])
5050

5151
super().__init__(
5252
executable_path=executable_path,

py/selenium/webdriver/chromium/service.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
# under the License.
1717

1818
from collections.abc import Mapping, Sequence
19-
from io import IOBase
2019
from typing import IO, Any
2120

2221
from selenium.webdriver.common import service
@@ -26,17 +25,12 @@ class ChromiumService(service.Service):
2625
"""Service class responsible for starting and stopping the ChromiumDriver WebDriver instance.
2726
2827
Args:
29-
executable_path: Install path of the executable.
30-
port: Port for the service to run on, defaults to 0 where the operating
31-
system will decide.
32-
service_args: (Optional) Sequence of args to be passed to the subprocess
33-
when launching the executable.
34-
log_output: (Optional) int representation of STDOUT/DEVNULL, any IO
35-
instance or String path to file.
36-
env: (Optional) Mapping of environment variables for the new process,
37-
defaults to `os.environ`.
38-
driver_path_env_key: (Optional) Environment variable to use to get the
39-
path to the driver executable.
28+
executable_path: (Optional) Install path of the executable.
29+
port: (Optional) Port for the service to run on, defaults to 0 where the operating system will decide.
30+
service_args: (Optional) Sequence of args to be passed to the subprocess when launching the executable.
31+
log_output: (Optional) int representation of STDOUT/DEVNULL, any IO instance or String path to file.
32+
env: (Optional) Mapping of environment variables for the new process, defaults to `os.environ`.
33+
driver_path_env_key: (Optional) Environment variable to use to get the path to the driver executable.
4034
"""
4135

4236
def __init__(
@@ -54,9 +48,7 @@ def __init__(
5448

5549
if isinstance(log_output, str):
5650
self._service_args.append(f"--log-path={log_output}")
57-
self.log_output: IOBase | None = None
58-
elif isinstance(log_output, IOBase):
59-
self.log_output = log_output
51+
self.log_output = None
6052
else:
6153
self.log_output = log_output
6254

py/selenium/webdriver/common/bidi/browsing_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,8 +984,8 @@ def reload(
984984
def set_viewport(
985985
self,
986986
context: str | None = None,
987-
viewport: dict | None | UNDEFINED = UNDEFINED,
988-
device_pixel_ratio: float | None | UNDEFINED = UNDEFINED,
987+
viewport: dict | None | Sentinel = UNDEFINED,
988+
device_pixel_ratio: float | None | Sentinel = UNDEFINED,
989989
user_contexts: list[str] | None = None,
990990
) -> None:
991991
"""Modifies specific viewport characteristics on the given top-level traversable.

py/selenium/webdriver/common/service.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from io import IOBase
2626
from subprocess import PIPE
2727
from time import sleep
28-
from typing import IO, Any, cast
28+
from typing import IO, Any
2929
from urllib import request
3030
from urllib.error import URLError
3131

@@ -42,8 +42,8 @@ class Service(ABC):
4242
communicate with a browser.
4343
4444
Args:
45-
executable: install path of the executable.
46-
port: Port for the service to run on, defaults to 0 where the operating system will decide.
45+
executable_path: (Optional) Install path of the executable.
46+
port: (Optional) Port for the service to run on, defaults to 0 where the operating system will decide.
4747
log_output: (Optional) int representation of STDOUT/DEVNULL, any IO instance or String path to file.
4848
env: (Optional) Mapping of environment variables for the new process, defaults to `os.environ`.
4949
driver_path_env_key: (Optional) Environment variable to use to get the path to the driver executable.
@@ -58,15 +58,15 @@ def __init__(
5858
driver_path_env_key: str | None = None,
5959
**kwargs,
6060
) -> None:
61-
self.log_output: int | IOBase | None
61+
self.log_output: int | IO[Any] | None
6262
if isinstance(log_output, str):
63-
self.log_output = cast(IOBase, open(log_output, "a+", encoding="utf-8"))
63+
self.log_output = open(log_output, "a+", encoding="utf-8")
6464
elif log_output == subprocess.STDOUT:
6565
self.log_output = None
6666
elif log_output is None or log_output == subprocess.DEVNULL:
6767
self.log_output = subprocess.DEVNULL
6868
else:
69-
self.log_output = cast(int | IOBase, log_output)
69+
self.log_output = log_output
7070

7171
self.port = port or utils.free_port()
7272
# Default value for every python subprocess: subprocess.Popen(..., creationflags=0)
@@ -220,8 +220,8 @@ def _start_process(self, path: str) -> None:
220220
cmd,
221221
env=self.env,
222222
close_fds=close_file_descriptors,
223-
stdout=cast(int | IO[Any] | None, self.log_output),
224-
stderr=cast(int | IO[Any] | None, self.log_output),
223+
stdout=self.log_output,
224+
stderr=self.log_output,
225225
stdin=PIPE,
226226
creationflags=self.creation_flags,
227227
startupinfo=start_info,

py/selenium/webdriver/firefox/service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222

2323

2424
class Service(service.Service):
25-
"""Service class responsible for starting and stopping geckodriver.
25+
"""Service class responsible for starting and stopping of `geckodriver`.
2626
2727
Args:
28-
executable_path: install path of the geckodriver executable, defaults to `geckodriver`.
29-
port: Port for the service to run on, defaults to 0 where the operating system will decide.
28+
executable_path: (Optional) Install path of the executable.
29+
port: (Optional) Port for the service to run on, defaults to 0 where the operating system will decide.
3030
service_args: (Optional) Sequence of args to be passed to the subprocess when launching the executable.
3131
log_output: (Optional) int representation of STDOUT/DEVNULL, any IO instance or String path to file.
3232
env: (Optional) Mapping of environment variables for the new process, defaults to `os.environ`.

py/selenium/webdriver/ie/service.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,19 @@
2222

2323

2424
class Service(service.Service):
25-
"""Object that manages the starting and stopping of the IEDriver."""
25+
"""Service class responsible for starting and stopping of `IEDriver`.
26+
27+
Args:
28+
executable_path: (Optional) Install path of the executable.
29+
port: (Optional) Port for the service to run on, defaults to 0 where the operating system will decide.
30+
host: (Optional) IP address the service port is bound
31+
service_args: (Optional) Sequence of args to be passed to the subprocess when launching the executable.
32+
log_level: (Optional) Level of logging of service, may be "FATAL", "ERROR", "WARN", "INFO", "DEBUG",
33+
"TRACE". Default is "FATAL".
34+
log_output: (Optional) int representation of STDOUT/DEVNULL, any IO instance or String path to file.
35+
driver_path_env_key: (Optional) Environment variable to use to get the path to the driver executable.
36+
**kwargs: Additional keyword arguments to pass to the parent Service class.
37+
"""
2638

2739
def __init__(
2840
self,
@@ -35,20 +47,6 @@ def __init__(
3547
driver_path_env_key: str | None = None,
3648
**kwargs,
3749
) -> None:
38-
"""Creates a new instance of the Service.
39-
40-
Args:
41-
executable_path: Path to the IEDriver
42-
port: Port the service is running on
43-
host: (Optional) IP address the service port is bound
44-
service_args: (Optional) Sequence of args to be passed to the subprocess when launching the executable.
45-
log_level: (Optional) Level of logging of service, may be "FATAL", "ERROR", "WARN", "INFO", "DEBUG",
46-
"TRACE". Default is "FATAL".
47-
log_output: (Optional) int representation of STDOUT/DEVNULL, any IO instance or String path to file.
48-
Default is "stdout".
49-
driver_path_env_key: (Optional) Environment variable to use to get the path to the driver executable.
50-
**kwargs: Additional keyword arguments to pass to the parent Service class.
51-
"""
5250
self._service_args = list(service_args or [])
5351
driver_path_env_key = driver_path_env_key or "SE_IEDRIVER"
5452

0 commit comments

Comments
 (0)