Skip to content

Commit a466f4c

Browse files
committed
add type annotations and docstrings to devlib
Most of the files are covered, but some of the instruments and unused platforms are not augmented
1 parent 5425f4a commit a466f4c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+9231
-3990
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ devlib/bin/scripts/shutils
77
doc/_build/
88
build/
99
dist/
10+
.venv/
11+
.vscode/
12+
venv/
13+
.history/

devlib/_target_runner.py

+95-41
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 ARM Limited
1+
# Copyright 2024-2025 ARM Limited
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -17,15 +17,24 @@
1717
Target runner and related classes are implemented here.
1818
"""
1919

20-
import logging
2120
import os
2221
import time
22+
2323
from platform import machine
24+
from typing import Optional, cast, Protocol, TYPE_CHECKING, Union
25+
from typing_extensions import NotRequired, LiteralString, TypedDict
26+
if TYPE_CHECKING:
27+
from _typeshed import StrPath, BytesPath
28+
from devlib.platform import Platform
29+
else:
30+
StrPath = str
31+
BytesPath = bytes
2432

2533
from devlib.exception import (TargetStableError, HostError)
26-
from devlib.target import LinuxTarget
27-
from devlib.utils.misc import get_subprocess, which
34+
from devlib.target import LinuxTarget, Target
35+
from devlib.utils.misc import get_subprocess, which, get_logger
2836
from devlib.utils.ssh import SshConnection
37+
from devlib.utils.annotation_helpers import SubprocessCommand, SshUserConnectionSettings
2938

3039

3140
class TargetRunner:
@@ -36,16 +45,14 @@ class TargetRunner:
3645
(e.g., :class:`QEMUTargetRunner`).
3746
3847
:param target: Specifies type of target per :class:`Target` based classes.
39-
:type target: Target
4048
"""
4149

4250
def __init__(self,
43-
target):
51+
target: Target) -> None:
4452
self.target = target
53+
self.logger = get_logger(self.__class__.__name__)
4554

46-
self.logger = logging.getLogger(self.__class__.__name__)
47-
48-
def __enter__(self):
55+
def __enter__(self) -> 'TargetRunner':
4956
return self
5057

5158
def __exit__(self, *_):
@@ -58,29 +65,25 @@ class SubprocessTargetRunner(TargetRunner):
5865
5966
:param runner_cmd: The command to start runner process (e.g.,
6067
``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``).
61-
:type runner_cmd: list(str)
6268
6369
:param target: Specifies type of target per :class:`Target` based classes.
64-
:type target: Target
6570
6671
:param connect: Specifies if :class:`TargetRunner` should try to connect
6772
target after launching it, defaults to True.
68-
:type connect: bool or None
6973
7074
:param boot_timeout: Timeout for target's being ready for SSH access in
7175
seconds, defaults to 60.
72-
:type boot_timeout: int or None
7376
7477
:raises HostError: if it cannot execute runner command successfully.
7578
7679
:raises TargetStableError: if Target is inaccessible.
7780
"""
7881

7982
def __init__(self,
80-
runner_cmd,
81-
target,
82-
connect=True,
83-
boot_timeout=60):
83+
runner_cmd: SubprocessCommand,
84+
target: Target,
85+
connect: bool = True,
86+
boot_timeout: int = 60):
8487
super().__init__(target=target)
8588

8689
self.boot_timeout = boot_timeout
@@ -90,7 +93,7 @@ def __init__(self,
9093
try:
9194
self.runner_process = get_subprocess(runner_cmd)
9295
except Exception as ex:
93-
raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex
96+
raise HostError(f'Error while running "{runner_cmd!r}": {ex}') from ex
9497

9598
if connect:
9699
self.wait_boot_complete()
@@ -107,16 +110,16 @@ def __exit__(self, *_):
107110

108111
self.terminate()
109112

110-
def wait_boot_complete(self):
113+
def wait_boot_complete(self) -> None:
111114
"""
112-
Wait for target OS to finish boot up and become accessible over SSH in at most
113-
``SubprocessTargetRunner.boot_timeout`` seconds.
115+
Wait for the target OS to finish booting and become accessible within
116+
:attr:`boot_timeout` seconds.
114117
115-
:raises TargetStableError: In case of timeout.
118+
:raises TargetStableError: If the target is inaccessible after the timeout.
116119
"""
117120

118121
start_time = time.time()
119-
elapsed = 0
122+
elapsed: float = 0.0
120123
while self.boot_timeout >= elapsed:
121124
try:
122125
self.target.connect(timeout=self.boot_timeout - elapsed)
@@ -132,9 +135,9 @@ def wait_boot_complete(self):
132135
self.terminate()
133136
raise TargetStableError(f'Target is inaccessible for {self.boot_timeout} seconds!')
134137

135-
def terminate(self):
138+
def terminate(self) -> None:
136139
"""
137-
Terminate ``SubprocessTargetRunner.runner_process``.
140+
Terminate the subprocess associated with this runner.
138141
"""
139142

140143
self.logger.debug('Killing target runner...')
@@ -147,10 +150,9 @@ class NOPTargetRunner(TargetRunner):
147150
Class for implementing a target runner which does nothing except providing .target attribute.
148151
149152
:param target: Specifies type of target per :class:`Target` based classes.
150-
:type target: Target
151153
"""
152154

153-
def __init__(self, target):
155+
def __init__(self, target: Target) -> None:
154156
super().__init__(target=target)
155157

156158
def __enter__(self):
@@ -159,11 +161,62 @@ def __enter__(self):
159161
def __exit__(self, *_):
160162
pass
161163

162-
def terminate(self):
164+
def terminate(self) -> None:
163165
"""
164166
Nothing to terminate for NOP target runners.
165167
Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``).
166168
"""
169+
pass
170+
171+
172+
class QEMUTargetUserSettings(TypedDict, total=False):
173+
kernel_image: str
174+
arch: NotRequired[str]
175+
cpu_type: NotRequired[str]
176+
initrd_image: str
177+
mem_size: NotRequired[int]
178+
num_cores: NotRequired[int]
179+
num_threads: NotRequired[int]
180+
cmdline: NotRequired[str]
181+
enable_kvm: NotRequired[bool]
182+
183+
184+
class QEMUTargetRunnerSettings(TypedDict):
185+
kernel_image: str
186+
arch: str
187+
cpu_type: str
188+
initrd_image: str
189+
mem_size: int
190+
num_cores: int
191+
num_threads: int
192+
cmdline: str
193+
enable_kvm: bool
194+
195+
196+
# TODO - look into which params can be made NotRequired and Optional
197+
class SshConnectionSettings(TypedDict):
198+
username: str
199+
password: str
200+
keyfile: Optional[Union[LiteralString, StrPath, BytesPath]]
201+
host: str
202+
port: int
203+
timeout: float
204+
platform: 'Platform'
205+
sudo_cmd: str
206+
strict_host_check: bool
207+
use_scp: bool
208+
poll_transfers: bool
209+
start_transfer_poll_delay: int
210+
total_transfer_timeout: int
211+
transfer_poll_period: int
212+
213+
214+
class QEMUTargetRunnerTargetFactory(Protocol):
215+
"""
216+
Protocol for Lambda function for creating :class:`Target` based object.
217+
"""
218+
def __call__(self, *, connect: bool, conn_cls, connection_settings: SshConnectionSettings) -> Target:
219+
...
167220

168221

169222
class QEMUTargetRunner(SubprocessTargetRunner):
@@ -177,7 +230,7 @@ class QEMUTargetRunner(SubprocessTargetRunner):
177230
178231
* ``arch``: Architecture type. Defaults to ``aarch64``.
179232
180-
* ``cpu_types``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
233+
* ``cpu_type``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
181234
default. This parameter is valid for Arm architectures only.
182235
183236
* ``initrd_image``: This points to the location of initrd image (e.g.,
@@ -197,36 +250,37 @@ class QEMUTargetRunner(SubprocessTargetRunner):
197250
* ``enable_kvm``: Specifies if KVM will be used as accelerator in QEMU or not.
198251
Enabled by default if host architecture matches with target's for improving
199252
QEMU performance.
200-
:type qemu_settings: Dict
201253
202254
:param connection_settings: the dictionary to store connection settings
203255
of ``Target.connection_settings``, defaults to None.
204-
:type connection_settings: Dict or None
205256
206257
:param make_target: Lambda function for creating :class:`Target` based object.
207-
:type make_target: func or None
208258
209259
:Variable positional arguments: Forwarded to :class:`TargetRunner`.
210260
211261
:raises FileNotFoundError: if QEMU executable, kernel or initrd image cannot be found.
212262
"""
213263

214264
def __init__(self,
215-
qemu_settings,
216-
connection_settings=None,
217-
make_target=LinuxTarget,
218-
**args):
265+
qemu_settings: QEMUTargetUserSettings,
266+
connection_settings: Optional[SshUserConnectionSettings] = None,
267+
make_target: QEMUTargetRunnerTargetFactory = cast(QEMUTargetRunnerTargetFactory, LinuxTarget),
268+
**kwargs) -> None:
219269

220-
self.connection_settings = {
270+
default_connection_settings = {
221271
'host': '127.0.0.1',
222272
'port': 8022,
223273
'username': 'root',
224274
'password': 'root',
225275
'strict_host_check': False,
226276
}
227-
self.connection_settings = {**self.connection_settings, **(connection_settings or {})}
228277

229-
qemu_args = {
278+
self.connection_settings: SshConnectionSettings = cast(SshConnectionSettings, {
279+
**default_connection_settings,
280+
**(connection_settings or {})
281+
})
282+
283+
qemu_default_args = {
230284
'arch': 'aarch64',
231285
'cpu_type': 'cortex-a72',
232286
'mem_size': 512,
@@ -235,7 +289,7 @@ def __init__(self,
235289
'cmdline': 'console=ttyAMA0',
236290
'enable_kvm': True,
237291
}
238-
qemu_args = {**qemu_args, **qemu_settings}
292+
qemu_args: QEMUTargetRunnerSettings = cast(QEMUTargetRunnerSettings, {**qemu_default_args, **qemu_settings})
239293

240294
qemu_executable = f'qemu-system-{qemu_args["arch"]}'
241295
qemu_path = which(qemu_executable)
@@ -281,4 +335,4 @@ def __init__(self,
281335

282336
super().__init__(runner_cmd=qemu_cmd,
283337
target=target,
284-
**args)
338+
**kwargs)

devlib/collector/__init__.py

+57-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2015 ARM Limited
1+
# Copyright 2015-2025 ARM Limited
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -16,27 +16,63 @@
1616
import logging
1717

1818
from devlib.utils.types import caseless_string
19+
from devlib.utils.misc import get_logger
20+
from typing import TYPE_CHECKING, Optional, List
21+
if TYPE_CHECKING:
22+
from devlib.target import Target
23+
1924

2025
class CollectorBase(object):
26+
"""
27+
The Collector API provide a consistent way of collecting arbitrary data from
28+
a target. Data is collected via an instance of a class derived from :class:`CollectorBase`.
2129
22-
def __init__(self, target):
30+
:param target: The devlib Target from which data will be collected.
31+
"""
32+
def __init__(self, target: 'Target'):
2333
self.target = target
24-
self.logger = logging.getLogger(self.__class__.__name__)
25-
self.output_path = None
26-
27-
def reset(self):
34+
self.logger: logging.Logger = get_logger(self.__class__.__name__)
35+
self.output_path: Optional[str] = None
36+
37+
def reset(self) -> None:
38+
"""
39+
This can be used to configure a collector for collection. This must be invoked
40+
before :meth:`start()` is called to begin collection.
41+
"""
2842
pass
2943

30-
def start(self):
44+
def start(self) -> None:
45+
"""
46+
Starts collecting from the target.
47+
"""
3148
pass
3249

3350
def stop(self):
51+
"""
52+
Stops collecting from target. Must be called after
53+
:func:`start()`.
54+
"""
3455
pass
3556

36-
def set_output(self, output_path):
57+
def set_output(self, output_path: str) -> None:
58+
"""
59+
Configure the output path for the particular collector. This will be either
60+
a directory or file path which will be used when storing the data. Please see
61+
the individual Collector documentation for more information.
62+
63+
:param output_path: The path (file or directory) to which data will be saved.
64+
"""
3765
self.output_path = output_path
3866

39-
def get_data(self):
67+
def get_data(self) -> 'CollectorOutput':
68+
"""
69+
The collected data will be return via the previously specified output_path.
70+
This method will return a :class:`CollectorOutput` object which is a subclassed
71+
list object containing individual ``CollectorOutputEntry`` objects with details
72+
about the individual output entry.
73+
74+
:raises RuntimeError: If ``output_path`` has not been set.
75+
"""
4076
return CollectorOutput()
4177

4278
def __enter__(self):
@@ -47,18 +83,26 @@ def __enter__(self):
4783
def __exit__(self, exc_type, exc_value, traceback):
4884
self.stop()
4985

86+
5087
class CollectorOutputEntry(object):
88+
"""
89+
This object is designed to allow for the output of a collector to be processed
90+
generically. The object will behave as a regular string containing the path to
91+
underlying output path and can be used directly in ``os.path`` operations.
5192
52-
path_kinds = ['file', 'directory']
93+
:param path: The file path of the collected output data.
94+
:param path_kind: The type of output. Must be one of ``file`` or ``directory``.
95+
"""
96+
path_kinds: List[str] = ['file', 'directory']
5397

54-
def __init__(self, path, path_kind):
55-
self.path = path
98+
def __init__(self, path: str, path_kind: str):
99+
self.path = path # path for the corresponding output item
56100

57101
path_kind = caseless_string(path_kind)
58102
if path_kind not in self.path_kinds:
59103
msg = '{} is not a valid path_kind [{}]'
60104
raise ValueError(msg.format(path_kind, ' '.join(self.path_kinds)))
61-
self.path_kind = path_kind
105+
self.path_kind = path_kind # file or directory
62106

63107
def __str__(self):
64108
return self.path

0 commit comments

Comments
 (0)