Skip to content

Commit 427d66f

Browse files
committed
driver: add initial type hints for common driver code
Add `ty` as a development dependency and configure it to check one initial driver module. Annotate `labgrid.driver.common` while leaving existing attrs-based exception behavior unchanged. Signed-off-by: Rainer Poisel <rainer@embedded-focus.com>
1 parent 424a0b7 commit 427d66f

3 files changed

Lines changed: 72 additions & 13 deletions

File tree

labgrid/driver/common.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import logging
22
import subprocess
3+
from typing import cast
4+
35
import attr
46

7+
from labgrid.resource.common import Resource
8+
59
from ..binding import BindingError, BindingMixin
610
from .exception import ExecutionError
711

@@ -21,17 +25,17 @@ class Driver(BindingMixin):
2125
- deactivate
2226
"""
2327

24-
def __attrs_post_init__(self):
28+
def __attrs_post_init__(self) -> None:
2529
super().__attrs_post_init__()
2630
if self.target is None:
27-
raise BindingError("Drivers can only be created on a valid target")
31+
raise BindingError("Drivers can only be created on a valid target") # ty: ignore[too-many-positional-arguments]
2832

2933
logger_name = f"{self.__class__.__name__}({self.target.name})"
3034
if self.name:
3135
logger_name += f":{self.name}"
3236
self.logger = logging.getLogger(logger_name)
3337

34-
def get_priority(self, protocol):
38+
def get_priority(self, protocol) -> int:
3539
"""Retrieve the priority for a given protocol
3640
3741
Arguments:
@@ -41,17 +45,17 @@ def get_priority(self, protocol):
4145
Int: value of the priority if it is found, 0 otherwise.
4246
"""
4347
for cls in self.__class__.__mro__:
44-
prios = getattr(cls, 'priorities', {})
48+
prios = getattr(cls, "priorities", {})
4549
# we found a matching parent priorities attribute with the matching protocol
4650
if prios and protocol in prios:
47-
return prios.get(protocol)
51+
return cast(int, prios[protocol])
4852
# If we find the parent protocol, set the priority to 0
4953
if cls.__name__ == protocol.__name__:
5054
return 0
5155

5256
return 0
5357

54-
def get_export_name(self):
58+
def get_export_name(self) -> str:
5559
"""Get the name to be used for exported variables.
5660
5761
Falls back to the class name if the driver has no name.
@@ -60,29 +64,32 @@ def get_export_name(self):
6064
return self.name
6165
return self.__class__.__name__
6266

63-
def get_export_vars(self):
67+
def get_export_vars(self) -> dict[str, str]:
6468
"""Get a dictionary of variables to be exported."""
6569
return {}
6670

6771
@property
68-
def skip_deactivate_on_export(self):
72+
def skip_deactivate_on_export(self) -> bool:
6973
"""Drivers are deactivated on export by default.
7074
7175
If the driver can handle external accesses even while active, it can
7276
return True here.
7377
"""
7478
return False
7579

76-
def get_bound_resources(self):
80+
def get_bound_resources(self) -> set[Resource]:
7781
"""Return the bound resources for a driver
7882
7983
This recursively calls all suppliers and combines the sets of returned resources.
8084
"""
81-
res = set()
85+
res: set[Resource] = set()
8286
for supplier in self.suppliers:
8387
res |= supplier.get_bound_resources()
8488
return res
8589

86-
def check_file(filename, *, command_prefix=[]):
87-
if subprocess.call(command_prefix + ['test', '-r', filename]) != 0:
88-
raise ExecutionError(f"File {filename} is not readable")
90+
91+
def check_file(filename, *, command_prefix=None) -> None:
92+
if command_prefix is None:
93+
command_prefix: list[str] = []
94+
if subprocess.call(command_prefix + ["test", "-r", filename]) != 0:
95+
raise ExecutionError(f"File {filename} is not readable") # ty: ignore[too-many-positional-arguments]

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ dev = [
9191
"pytest-mock>=3.6.1",
9292
"pylint>=3.0.0",
9393
"ruff>=0.5.7",
94+
"ty>=0.0.64",
9495
"pystuck",
9596
"requests[socks]>=2.26.0",
9697

@@ -138,6 +139,11 @@ testpaths = [
138139
]
139140
addopts = "-p no:labgrid"
140141

142+
[tool.ty.src]
143+
include = [
144+
"labgrid/driver/common.py",
145+
]
146+
141147
[tool.pylint.imports]
142148
ignored-modules = ["gi"]
143149

@@ -224,6 +230,7 @@ include = [
224230
"examples/**/*.py",
225231
"labgrid/driver/httpvideodriver.py",
226232
"labgrid/driver/manualswitchdriver.py",
233+
"labgrid/driver/common.py",
227234
"labgrid/driver/power/gude8031.py",
228235
"labgrid/driver/power/pe6216.py",
229236
"labgrid/driver/power/poe_netgear_plus.py",

tests/test_driver_common.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from unittest.mock import Mock
2+
3+
import pytest
4+
5+
from labgrid import Target
6+
from labgrid.binding import BindingError
7+
from labgrid.driver.common import Driver, check_file
8+
from labgrid.driver.exception import ExecutionError
9+
10+
11+
def test_driver_requires_target() -> None:
12+
with pytest.raises(BindingError):
13+
Driver(None, None)
14+
15+
16+
def test_driver_get_priority_returns_zero_for_unknown_protocol(target: Target) -> None:
17+
class Protocol:
18+
pass
19+
20+
driver = Driver(target, None)
21+
22+
assert driver.get_priority(Protocol) == 0
23+
24+
25+
def test_driver_get_export_vars_returns_empty_dict(target: Target) -> None:
26+
driver = Driver(target, None)
27+
28+
assert driver.get_export_vars() == {}
29+
30+
31+
def test_check_file_uses_default_command_prefix(monkeypatch: pytest.MonkeyPatch) -> None:
32+
call = Mock(return_value=0)
33+
monkeypatch.setattr("labgrid.driver.common.subprocess.call", call)
34+
35+
check_file("/tmp/file")
36+
37+
call.assert_called_once_with(["test", "-r", "/tmp/file"])
38+
39+
40+
def test_check_file_raises_execution_error(monkeypatch: pytest.MonkeyPatch) -> None:
41+
call = Mock(return_value=1)
42+
monkeypatch.setattr("labgrid.driver.common.subprocess.call", call)
43+
44+
with pytest.raises(ExecutionError, match="File /tmp/file is not readable"):
45+
check_file("/tmp/file")

0 commit comments

Comments
 (0)