Skip to content

Commit f75343f

Browse files
[py] PEP 484 type hints for common.exceptions and webdriver.support.color (#9482)
* add mypy configuration, tox env to run mypy checks Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com> * add type hints for selenium.common.exceptions Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com> * add type hints for selenium.webdriver.support.color Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com> * restrict exception args types to string or None Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com> * change type of stacktrace argument to a string sequence Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com> Co-authored-by: David Burns <david.burns@theautomatedtester.co.uk>
1 parent 6ca474d commit f75343f

File tree

4 files changed

+64
-20
lines changed

4 files changed

+64
-20
lines changed

py/mypy.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[mypy]
2+
files = selenium
3+
ignore_missing_imports = False
4+
warn_unused_configs = True
5+
disallow_subclassing_any = True
6+
disallow_any_generics = True
7+
disallow_untyped_calls = True
8+
disallow_untyped_defs = True
9+
disallow_incomplete_defs = True
10+
check_untyped_defs = True
11+
disallow_untyped_decorators = True
12+
no_implicit_optional = True
13+
warn_redundant_casts = True
14+
warn_unused_ignores = True
15+
warn_return_any = True
16+
warn_unreachable = True

py/selenium/common/exceptions.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@
1919
Exceptions that may happen in all the webdriver code.
2020
"""
2121

22+
from typing import Optional, Sequence
23+
2224

2325
class WebDriverException(Exception):
2426
"""
2527
Base webdriver exception.
2628
"""
2729

28-
def __init__(self, msg=None, screen=None, stacktrace=None):
30+
def __init__(self, msg: Optional[str] = None, screen: Optional[str] = None, stacktrace: Optional[Sequence[str]] = None) -> None:
2931
self.msg = msg
3032
self.screen = screen
3133
self.stacktrace = stacktrace
3234

33-
def __str__(self):
35+
def __str__(self) -> str:
3436
exception_msg = "Message: %s\n" % self.msg
3537
if self.screen:
3638
exception_msg += "Screenshot: available via screen\n"
@@ -126,11 +128,11 @@ class UnexpectedAlertPresentException(WebDriverException):
126128
Usually raised when an unexpected modal is blocking the webdriver from executing
127129
commands.
128130
"""
129-
def __init__(self, msg=None, screen=None, stacktrace=None, alert_text=None):
131+
def __init__(self, msg: Optional[str] = None, screen: Optional[str] = None, stacktrace: Optional[Sequence[str]] = None, alert_text: Optional[str] = None) -> None:
130132
super(UnexpectedAlertPresentException, self).__init__(msg, screen, stacktrace)
131133
self.alert_text = alert_text
132134

133-
def __str__(self):
135+
def __str__(self) -> str:
134136
return "Alert Text: %s\n%s" % (self.alert_text, super(UnexpectedAlertPresentException, self).__str__())
135137

136138

py/selenium/webdriver/support/color.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
import sys
19+
from typing import Any, Optional, Sequence, TYPE_CHECKING
20+
21+
if sys.version_info >= (3, 9):
22+
from re import Match
23+
else:
24+
from typing import Match
25+
26+
if TYPE_CHECKING:
27+
from typing import SupportsInt, SupportsFloat, Union
28+
from typing_extensions import SupportsIndex
29+
30+
ParseableFloat = Union[SupportsFloat, SupportsIndex, str, bytes, bytearray]
31+
ParseableInt = Union[SupportsInt, SupportsIndex, str, bytes]
32+
else:
33+
ParseableFloat = Any
34+
ParseableInt = Any
35+
36+
1837
RGB_PATTERN = r"^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$"
1938
RGB_PCT_PATTERN = r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$"
2039
RGBA_PATTERN = r"^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0\.\d+)\s*\)\s*$"
@@ -41,19 +60,21 @@ class Color(object):
4160
"""
4261

4362
@staticmethod
44-
def from_string(str_):
63+
def from_string(str_: str) -> "Color":
4564
import re
4665

4766
class Matcher(object):
48-
def __init__(self):
67+
match_obj: Optional[Match[str]]
68+
69+
def __init__(self) -> None:
4970
self.match_obj = None
5071

51-
def match(self, pattern, str_):
72+
def match(self, pattern: str, str_: str) -> Optional[Match[str]]:
5273
self.match_obj = re.match(pattern, str_)
5374
return self.match_obj
5475

5576
@property
56-
def groups(self):
77+
def groups(self) -> Sequence[str]:
5778
return () if not self.match_obj else self.match_obj.groups()
5879

5980
m = Matcher()
@@ -66,7 +87,7 @@ def groups(self):
6687
elif m.match(RGBA_PATTERN, str_):
6788
return Color(*m.groups)
6889
elif m.match(RGBA_PCT_PATTERN, str_):
69-
rgba = tuple([float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]])
90+
rgba = tuple([float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]]) # type: ignore
7091
return Color(*rgba)
7192
elif m.match(HEX_PATTERN, str_):
7293
rgb = tuple([int(each, 16) for each in m.groups])
@@ -82,7 +103,7 @@ def groups(self):
82103
raise ValueError("Could not convert %s into color" % str_)
83104

84105
@staticmethod
85-
def _from_hsl(h, s, light, a=1):
106+
def _from_hsl(h: ParseableFloat, s: ParseableFloat, light: ParseableFloat, a: ParseableFloat = 1) -> "Color":
86107
h = float(h) / 360
87108
s = float(s) / 100
88109
_l = float(light) / 100
@@ -95,7 +116,7 @@ def _from_hsl(h, s, light, a=1):
95116
luminocity2 = _l * (1 + s) if _l < 0.5 else _l + s - _l * s
96117
luminocity1 = 2 * _l - luminocity2
97118

98-
def hue_to_rgb(lum1, lum2, hue):
119+
def hue_to_rgb(lum1: float, lum2: float, hue: float) -> float:
99120
if hue < 0.0:
100121
hue += 1
101122
if hue > 1.0:
@@ -116,42 +137,42 @@ def hue_to_rgb(lum1, lum2, hue):
116137

117138
return Color(round(r * 255), round(g * 255), round(b * 255), a)
118139

119-
def __init__(self, red, green, blue, alpha=1):
140+
def __init__(self, red: ParseableInt, green: ParseableInt, blue: ParseableInt, alpha: ParseableFloat = 1) -> None:
120141
self.red = int(red)
121142
self.green = int(green)
122143
self.blue = int(blue)
123144
self.alpha = "1" if float(alpha) == 1 else str(float(alpha) or 0)
124145

125146
@property
126-
def rgb(self):
147+
def rgb(self) -> str:
127148
return "rgb(%d, %d, %d)" % (self.red, self.green, self.blue)
128149

129150
@property
130-
def rgba(self):
151+
def rgba(self) -> str:
131152
return "rgba(%d, %d, %d, %s)" % (self.red, self.green, self.blue, self.alpha)
132153

133154
@property
134-
def hex(self):
155+
def hex(self) -> str:
135156
return "#%02x%02x%02x" % (self.red, self.green, self.blue)
136157

137-
def __eq__(self, other):
158+
def __eq__(self, other: object) -> bool:
138159
if isinstance(other, Color):
139160
return self.rgba == other.rgba
140161
return NotImplemented
141162

142-
def __ne__(self, other):
163+
def __ne__(self, other: Any) -> bool:
143164
result = self.__eq__(other)
144165
if result is NotImplemented:
145166
return result
146167
return not result
147168

148-
def __hash__(self):
169+
def __hash__(self) -> int:
149170
return hash((self.red, self.green, self.blue, self.alpha))
150171

151-
def __repr__(self):
172+
def __repr__(self) -> str:
152173
return "Color(red=%d, green=%d, blue=%d, alpha=%s)" % (self.red, self.green, self.blue, self.alpha)
153174

154-
def __str__(self):
175+
def __str__(self) -> str:
155176
return "Color: %s" % self.rgba
156177

157178

py/tox.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ commands = sphinx-build -b html -d ../build/doctrees docs/source ../build/docs/a
1212
skip_install = true
1313
deps = flake8
1414
commands = flake8 {posargs}
15+
16+
[testenv:mypy]
17+
skip_install = true
18+
deps = mypy
19+
commands = mypy

0 commit comments

Comments
 (0)