Skip to content

Commit 81b98d1

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 facd251 commit 81b98d1

Some content is hidden

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

51 files changed

+10203
-3820
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

+101-28
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.
@@ -20,12 +20,22 @@
2020
import logging
2121
import os
2222
import time
23+
2324
from platform import machine
25+
from typing import Optional, cast, Protocol, TYPE_CHECKING, TypedDict, Union
26+
from typing_extensions import NotRequired, LiteralString
27+
if TYPE_CHECKING:
28+
from _typeshed import StrPath, BytesPath
29+
from devlib.platform import Platform
30+
else:
31+
StrPath = str
32+
BytesPath = bytes
2433

2534
from devlib.exception import (TargetStableError, HostError)
26-
from devlib.target import LinuxTarget
35+
from devlib.target import LinuxTarget, Target
2736
from devlib.utils.misc import get_subprocess, which
2837
from devlib.utils.ssh import SshConnection
38+
from devlib.utils.annotation_helpers import SubprocessCommand, SshUserConnectionSettings
2939

3040

3141
class TargetRunner:
@@ -40,15 +50,22 @@ class TargetRunner:
4050
"""
4151

4252
def __init__(self,
43-
target):
53+
target: Target) -> None:
4454
self.target = target
45-
4655
self.logger = logging.getLogger(self.__class__.__name__)
4756

4857
def __enter__(self):
58+
"""
59+
Enter the context for this runner.
60+
:return: This runner instance.
61+
:rtype: TargetRunner
62+
"""
4963
return self
5064

5165
def __exit__(self, *_):
66+
"""
67+
Exit the context for this runner.
68+
"""
5269
pass
5370

5471

@@ -77,20 +94,20 @@ class SubprocessTargetRunner(TargetRunner):
7794
"""
7895

7996
def __init__(self,
80-
runner_cmd,
81-
target,
82-
connect=True,
83-
boot_timeout=60):
97+
runner_cmd: SubprocessCommand,
98+
target: Target,
99+
connect: bool = True,
100+
boot_timeout: int = 60):
84101
super().__init__(target=target)
85102

86-
self.boot_timeout = boot_timeout
103+
self.boot_timeout: int = boot_timeout
87104

88105
self.logger.info('runner_cmd: %s', runner_cmd)
89106

90107
try:
91108
self.runner_process = get_subprocess(runner_cmd)
92109
except Exception as ex:
93-
raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex
110+
raise HostError(f'Error while running "{runner_cmd!r}": {ex}') from ex
94111

95112
if connect:
96113
self.wait_boot_complete()
@@ -107,16 +124,16 @@ def __exit__(self, *_):
107124

108125
self.terminate()
109126

110-
def wait_boot_complete(self):
127+
def wait_boot_complete(self) -> None:
111128
"""
112-
Wait for target OS to finish boot up and become accessible over SSH in at most
113-
``SubprocessTargetRunner.boot_timeout`` seconds.
129+
Wait for the target OS to finish booting and become accessible within
130+
:attr:`boot_timeout` seconds.
114131
115-
:raises TargetStableError: In case of timeout.
132+
:raises TargetStableError: If the target is inaccessible after the timeout.
116133
"""
117134

118135
start_time = time.time()
119-
elapsed = 0
136+
elapsed: float = 0.0
120137
while self.boot_timeout >= elapsed:
121138
try:
122139
self.target.connect(timeout=self.boot_timeout - elapsed)
@@ -132,9 +149,9 @@ def wait_boot_complete(self):
132149
self.terminate()
133150
raise TargetStableError(f'Target is inaccessible for {self.boot_timeout} seconds!')
134151

135-
def terminate(self):
152+
def terminate(self) -> None:
136153
"""
137-
Terminate ``SubprocessTargetRunner.runner_process``.
154+
Terminate the subprocess associated with this runner.
138155
"""
139156

140157
self.logger.debug('Killing target runner...')
@@ -150,7 +167,7 @@ class NOPTargetRunner(TargetRunner):
150167
:type target: Target
151168
"""
152169

153-
def __init__(self, target):
170+
def __init__(self, target: Target) -> None:
154171
super().__init__(target=target)
155172

156173
def __enter__(self):
@@ -159,11 +176,63 @@ def __enter__(self):
159176
def __exit__(self, *_):
160177
pass
161178

162-
def terminate(self):
179+
def terminate(self) -> None:
163180
"""
164181
Nothing to terminate for NOP target runners.
165182
Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``).
166183
"""
184+
pass
185+
186+
187+
QEMUTargetUserSettings = TypedDict("QEMUTargetUserSettings", {
188+
'kernel_image': str,
189+
'arch': NotRequired[str],
190+
'cpu_type': NotRequired[str],
191+
'initrd_image': str,
192+
'mem_size': NotRequired[int],
193+
'num_cores': NotRequired[int],
194+
'num_threads': NotRequired[int],
195+
'cmdline': NotRequired[str],
196+
'enable_kvm': NotRequired[bool],
197+
})
198+
199+
QEMUTargetRunnerSettings = TypedDict("QEMUTargetRunnerSettings", {
200+
'kernel_image': str,
201+
'arch': str,
202+
'cpu_type': str,
203+
'initrd_image': str,
204+
'mem_size': int,
205+
'num_cores': int,
206+
'num_threads': int,
207+
'cmdline': str,
208+
'enable_kvm': bool,
209+
})
210+
211+
212+
SshConnectionSettings = TypedDict("SshConnectionSettings", {
213+
'username': str,
214+
'password': str,
215+
'keyfile': Optional[Union[LiteralString, StrPath, BytesPath]],
216+
'host': str,
217+
'port': int,
218+
'timeout': float,
219+
'platform': 'Platform',
220+
'sudo_cmd': str,
221+
'strict_host_check': bool,
222+
'use_scp': bool,
223+
'poll_transfers': bool,
224+
'start_transfer_poll_delay': int,
225+
'total_transfer_timeout': int,
226+
'transfer_poll_period': int,
227+
})
228+
229+
230+
class QEMUTargetRunnerTargetFactory(Protocol):
231+
"""
232+
Protocol for Lambda function for creating :class:`Target` based object.
233+
"""
234+
def __call__(self, *, connect: bool, conn_cls, connection_settings: SshConnectionSettings) -> Target:
235+
...
167236

168237

169238
class QEMUTargetRunner(SubprocessTargetRunner):
@@ -177,7 +246,7 @@ class QEMUTargetRunner(SubprocessTargetRunner):
177246
178247
* ``arch``: Architecture type. Defaults to ``aarch64``.
179248
180-
* ``cpu_types``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
249+
* ``cpu_type``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
181250
default. This parameter is valid for Arm architectures only.
182251
183252
* ``initrd_image``: This points to the location of initrd image (e.g.,
@@ -212,21 +281,25 @@ class QEMUTargetRunner(SubprocessTargetRunner):
212281
"""
213282

214283
def __init__(self,
215-
qemu_settings,
216-
connection_settings=None,
217-
make_target=LinuxTarget,
218-
**args):
284+
qemu_settings: QEMUTargetUserSettings,
285+
connection_settings: Optional[SshUserConnectionSettings] = None,
286+
make_target: QEMUTargetRunnerTargetFactory = cast(QEMUTargetRunnerTargetFactory, LinuxTarget),
287+
**args) -> None:
219288

220-
self.connection_settings = {
289+
default_connection_settings = {
221290
'host': '127.0.0.1',
222291
'port': 8022,
223292
'username': 'root',
224293
'password': 'root',
225294
'strict_host_check': False,
226295
}
227-
self.connection_settings = {**self.connection_settings, **(connection_settings or {})}
228296

229-
qemu_args = {
297+
self.connection_settings: SshConnectionSettings = cast(SshConnectionSettings, {
298+
**default_connection_settings,
299+
**(connection_settings or {})
300+
})
301+
302+
qemu_default_args = {
230303
'arch': 'aarch64',
231304
'cpu_type': 'cortex-a72',
232305
'mem_size': 512,
@@ -235,7 +308,7 @@ def __init__(self,
235308
'cmdline': 'console=ttyAMA0',
236309
'enable_kvm': True,
237310
}
238-
qemu_args = {**qemu_args, **qemu_settings}
311+
qemu_args: QEMUTargetRunnerSettings = cast(QEMUTargetRunnerSettings, {**qemu_default_args, **qemu_settings})
239312

240313
qemu_executable = f'qemu-system-{qemu_args["arch"]}'
241314
qemu_path = which(qemu_executable)

devlib/collector/__init__.py

+60-11
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,62 @@
1616
import logging
1717

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

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

30-
def start(self):
43+
def start(self) -> None:
44+
"""
45+
Starts collecting from the target.
46+
"""
3147
pass
3248

3349
def stop(self):
50+
"""
51+
Stops collecting from target. Must be called after
52+
:func:`start()`.
53+
"""
3454
pass
3555

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

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

4277
def __enter__(self):
@@ -47,11 +82,25 @@ def __enter__(self):
4782
def __exit__(self, exc_type, exc_value, traceback):
4883
self.stop()
4984

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

54-
def __init__(self, path, path_kind):
103+
def __init__(self, path: str, path_kind: str):
55104
self.path = path
56105

57106
path_kind = caseless_string(path_kind)

0 commit comments

Comments
 (0)