Skip to content

Commit 691f35f

Browse files
committed
maint: add type hints
1 parent 4001e2c commit 691f35f

8 files changed

Lines changed: 100 additions & 53 deletions

File tree

‎iotlabsshcli/_deprecated.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424
from iotlabsshcli.parser.open_linux_parser import main as _main
2525

2626

27-
def open_a8_cli():
27+
def open_a8_cli() -> None:
2828
"""Entry point for the deprecated open-a8-cli command."""
2929
deprecate_cmd(_main, "open-a8-cli", "iotlab-ssh")

‎iotlabsshcli/open_linux.py‎

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

2323
import os.path
2424
from collections import OrderedDict
25+
from typing import Any
2526

2627
from iotlabsshcli.sshlib import OpenLinuxSsh
2728

2829

29-
def _nodes_grouped(nodes):
30+
def _nodes_grouped(nodes: list[str]) -> OrderedDict[str, list[str]]:
3031
"""Group nodes per site from a list of nodes.
3132
>>> _nodes_grouped([])
3233
OrderedDict()
@@ -58,7 +59,9 @@ def _nodes_grouped(nodes):
5859
_REMOTE_SHARED_DIR = "shared/.iotlabsshcli"
5960

6061

61-
def flash(config_ssh, nodes, firmware, verbose=False):
62+
def flash(
63+
config_ssh: dict[str, Any], nodes: list[str], firmware: str, verbose: bool = False
64+
) -> dict[str, Any]:
6265
"""Flash the firmware of co-microcontroller"""
6366
failed_hosts = []
6467
# configure ssh and remote firmware names.
@@ -79,7 +82,7 @@ def flash(config_ssh, nodes, firmware, verbose=False):
7982
return {"flash": result}
8083

8184

82-
def reset(config_ssh, nodes, verbose=False):
85+
def reset(config_ssh: dict[str, Any], nodes: list[str], verbose: bool = False) -> dict[str, Any]:
8386
"""Reset co-microcontroller"""
8487

8588
# Configure ssh
@@ -89,7 +92,9 @@ def reset(config_ssh, nodes, verbose=False):
8992
return {"reset": ssh.run(_RESET_CMD)}
9093

9194

92-
def wait_for_boot(config_ssh, nodes, max_wait=120, verbose=False):
95+
def wait_for_boot(
96+
config_ssh: dict[str, Any], nodes: list[str], max_wait: int = 120, verbose: bool = False
97+
) -> dict[str, Any]:
9398
"""Wait for the open Linux nodes boot"""
9499

95100
# Configure ssh.
@@ -99,7 +104,13 @@ def wait_for_boot(config_ssh, nodes, max_wait=120, verbose=False):
99104
return {"wait-for-boot": ssh.wait(max_wait)}
100105

101106

102-
def run_cmd(config_ssh, nodes, cmd, run_on_frontend=False, verbose=False):
107+
def run_cmd(
108+
config_ssh: dict[str, Any],
109+
nodes: list[str],
110+
cmd: str,
111+
run_on_frontend: bool = False,
112+
verbose: bool = False,
113+
) -> dict[str, Any]:
103114
"""Run a command on the Linux nodes or SSH frontend servers"""
104115

105116
# Configure ssh.
@@ -108,7 +119,9 @@ def run_cmd(config_ssh, nodes, cmd, run_on_frontend=False, verbose=False):
108119
return {"run-cmd": ssh.run(cmd, with_proxy=not run_on_frontend)}
109120

110121

111-
def copy_file(config_ssh, nodes, file_path, verbose=False):
122+
def copy_file(
123+
config_ssh: dict[str, Any], nodes: list[str], file_path: str, verbose: bool = False
124+
) -> dict[str, Any]:
112125
"""Copy a file to SSH frontend servers"""
113126

114127
# Configure ssh.
@@ -120,7 +133,9 @@ def copy_file(config_ssh, nodes, file_path, verbose=False):
120133
return {"copy-file": result}
121134

122135

123-
def _get_failed_result(groups, result, run_on_frontend):
136+
def _get_failed_result(
137+
groups: OrderedDict[str, list[str]], result: dict[str, list[str]], run_on_frontend: bool
138+
) -> list[str]:
124139
"""Returns failed nodes or SSH frontend servers list.
125140
126141
We delete failed hosts for the next commands in the groups
@@ -139,7 +154,13 @@ def _get_failed_result(groups, result, run_on_frontend):
139154
return failed
140155

141156

142-
def run_script(config_ssh, nodes, script, run_on_frontend=False, verbose=False):
157+
def run_script(
158+
config_ssh: dict[str, Any],
159+
nodes: list[str],
160+
script: str,
161+
run_on_frontend: bool = False,
162+
verbose: bool = False,
163+
) -> dict[str, Any]:
143164
"""Run a script in background on Linux nodes or SSH frontend servers"""
144165

145166
# Configure ssh.

‎iotlabsshcli/parser/open_linux_parser.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import argparse
2424
import sys
25+
from typing import Any
2526

2627
from iotlabcli import auth, helpers, rest
2728
from iotlabcli.helpers import deprecate_warn_cmd
@@ -31,7 +32,7 @@
3132
import iotlabsshcli.open_linux
3233

3334

34-
def parse_options():
35+
def parse_options() -> argparse.ArgumentParser:
3536
"""Parse command line option."""
3637
parent_parser = argparse.ArgumentParser(add_help=False)
3738
common.add_auth_arguments(parent_parser, False)
@@ -54,7 +55,7 @@ def parse_options():
5455
class DeprecateHelpFormatter(argparse.HelpFormatter):
5556
"""Add drepecated help formatter"""
5657

57-
def add_usage(self, usage, actions, groups, prefix=None):
58+
def add_usage(self, usage, actions, groups, prefix=None) -> None: # type: ignore[override]
5859
# self._prog = iotlab-ssh flash-m3 | reset-m3
5960
old_cmd = self._prog.split()[-1]
6061
new_cmd = old_cmd.split("-")[0]
@@ -142,7 +143,7 @@ def add_usage(self, usage, actions, groups, prefix=None):
142143
return parser
143144

144145

145-
def open_linux_parse_and_run(opts):
146+
def open_linux_parse_and_run(opts: argparse.Namespace) -> dict[str, Any]:
146147
"""Parse namespace 'opts' object."""
147148
user, passwd = auth.get_user_credentials(opts.username, opts.password)
148149
api = rest.Api(user, passwd)
@@ -194,7 +195,7 @@ def open_linux_parse_and_run(opts):
194195
return res
195196

196197

197-
def main(args=None):
198+
def main(args: list[str] | None = None) -> None:
198199
"""Open Linux SSH cli parser."""
199200
args = args or sys.argv[1:] # required for easy testing.
200201
parser = parse_options()

‎iotlabsshcli/sshlib/open_linux_ssh.py‎

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
import asyncio
2424
import os
2525
import time
26+
from typing import Any
2627

2728
import asyncssh
2829

2930

30-
def _cleanup_result(result):
31+
def _cleanup_result(result: dict[str, list[str]]) -> dict[str, list[str]]:
3132
"""Remove empty list from result.
3233
3334
>>> _cleanup_result({ '0': [], '1': []})
@@ -49,7 +50,9 @@ def _cleanup_result(result):
4950
return result
5051

5152

52-
def _extend_result(result, new_result):
53+
def _extend_result(
54+
result: dict[str, list[str]], new_result: dict[str, list[str]]
55+
) -> dict[str, list[str]]:
5356
"""Extend result dictionnary values with new result
5457
dictionnary values
5558
@@ -100,7 +103,7 @@ def _extend_result(result, new_result):
100103
return result
101104

102105

103-
def _check_all_nodes_processed(result):
106+
def _check_all_nodes_processed(result: dict[str, list[str]]) -> bool:
104107
"""Verify all nodes are successful or failed.
105108
106109
>>> _check_all_nodes_processed({ 'saclay': [], 'grenoble': []})
@@ -126,12 +129,14 @@ def _check_all_nodes_processed(result):
126129
class OpenLinuxSsh:
127130
"""Implement SSH API using asyncssh."""
128131

129-
def __init__(self, config_ssh, groups, verbose=False):
132+
def __init__(
133+
self, config_ssh: dict[str, Any], groups: dict[str, list[str]], verbose: bool = False
134+
) -> None:
130135
self.config_ssh = config_ssh
131136
self.groups = groups
132137
self.verbose = verbose
133138

134-
def run(self, command, with_proxy=True, **kwargs):
139+
def run(self, command: str, with_proxy: bool = True, **kwargs: Any) -> dict[str, list[str]]:
135140
"""Run ssh command on nodes, optionally through a proxy."""
136141
result = {"0": [], "1": []}
137142
for site, hosts in self.groups.items():
@@ -143,7 +148,7 @@ def run(self, command, with_proxy=True, **kwargs):
143148
result = _extend_result(result, result_cmd)
144149
return _cleanup_result(result)
145150

146-
def scp(self, src, dst):
151+
def scp(self, src: str, dst: str) -> dict[str, list[str]]:
147152
"""Copy file to SSH frontend via SFTP."""
148153
result = {"0": [], "1": []}
149154
for site in self.groups:
@@ -154,7 +159,7 @@ def scp(self, src, dst):
154159
result["1"].append(site)
155160
return _cleanup_result(result)
156161

157-
def wait(self, max_wait):
162+
def wait(self, max_wait: int) -> dict[str, list[str]]:
158163
"""Wait for requested Linux nodes until they boot."""
159164
result = {"0": [], "1": []}
160165
start_time = time.time()
@@ -167,13 +172,20 @@ def wait(self, max_wait):
167172
result = _extend_result(result, result_cmd)
168173
return _cleanup_result(result)
169174

170-
def _connect_kwargs(self, timeout=10):
175+
def _connect_kwargs(self, timeout: int = 10) -> dict[str, Any]:
171176
kwargs = {"known_hosts": None, "connect_timeout": timeout}
172177
if SSH_KEY:
173178
kwargs["client_keys"] = [os.path.expanduser(SSH_KEY)]
174179
return kwargs
175180

176-
async def _run_command(self, command, hosts, proxy_host=None, timeout=10, **kwargs):
181+
async def _run_command(
182+
self,
183+
command: str,
184+
hosts: list[str],
185+
proxy_host: str | None = None,
186+
timeout: int = 10,
187+
**kwargs: Any,
188+
) -> dict[str, list[str]]:
177189
tasks = [
178190
self._run_on_host(host, command, proxy_host=proxy_host, timeout=timeout, **kwargs)
179191
for host in hosts
@@ -190,7 +202,14 @@ async def _run_command(self, command, hosts, proxy_host=None, timeout=10, **kwar
190202
result["0"].append(host)
191203
return result
192204

193-
async def _run_on_host(self, host, command, proxy_host=None, timeout=10, **kwargs):
205+
async def _run_on_host(
206+
self,
207+
host: str,
208+
command: str,
209+
proxy_host: str | None = None,
210+
timeout: int = 10,
211+
**kwargs: Any,
212+
) -> tuple[int, str]:
194213
ck = self._connect_kwargs(timeout)
195214
if proxy_host:
196215
async with asyncssh.connect(
@@ -204,7 +223,7 @@ async def _run_on_host(self, host, command, proxy_host=None, timeout=10, **kwarg
204223
result = await conn.run(command, **kwargs)
205224
return result.exit_status, result.stdout or ""
206225

207-
async def _copy_file(self, site, src, dst):
226+
async def _copy_file(self, site: str, src: str, dst: str) -> None:
208227
ck = self._connect_kwargs()
209228
async with asyncssh.connect(site, username=self.config_ssh["user"], **ck) as conn:
210229
async with conn.start_sftp_client() as sftp:

‎iotlabsshcli/tests/iotlabsshcli_mock.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import sys
2525
import unittest
26+
from typing import Any
2627
from unittest.mock import Mock, patch
2728

2829
from iotlabcli.helpers import json_dumps
@@ -34,14 +35,14 @@
3435
class RequestRet: # pylint:disable=too-few-public-methods
3536
"""Mock of Request return value"""
3637

37-
def __init__(self, status_code, content, headers=None):
38+
def __init__(self, status_code: int, content: str, headers: Any = None) -> None:
3839
self.status_code = status_code
3940
self.content = content.encode("utf-8")
4041
self.headers = headers
4142
self.text = self.content.decode("utf-8")
4243

4344

44-
def api_mock(ret=None):
45+
def api_mock(ret: dict[str, Any] | None = None) -> Mock:
4546
"""Return a mock of an api object
4647
returned value for api methods will be 'ret' parameter or API_RET
4748
"""
@@ -53,7 +54,7 @@ def api_mock(ret=None):
5354
return api_class.return_value
5455

5556

56-
def api_mock_stop():
57+
def api_mock_stop() -> None:
5758
"""Stop all patches started by api_mock.
5859
Actually it stops everything but not a problem"""
5960
patch.stopall()
@@ -62,7 +63,7 @@ def api_mock_stop():
6263
class MainMock(unittest.TestCase):
6364
"""Common mock needed for testing main function of parsers"""
6465

65-
def setUp(self):
66+
def setUp(self) -> None:
6667
self.api = api_mock()
6768

6869
patch("sys.stderr", sys.stdout).start()
@@ -75,11 +76,11 @@ def setUp(self):
7576
"iotlabcli.auth.get_user_credentials", Mock(return_value=("username", "password"))
7677
).start()
7778

78-
def get_exp(_, x, running_only=True):
79+
def get_exp(_: Any, x: int | None, running_only: bool = True) -> int:
7980
return x if x is not None else (123 if running_only else 234)
8081

8182
patch("iotlabcli.helpers.get_current_experiment", get_exp).start()
8283

83-
def tearDown(self):
84+
def tearDown(self) -> None:
8485
api_mock_stop()
8586
patch.stopall()

0 commit comments

Comments
 (0)