Skip to content

Commit e911f7a

Browse files
author
anon
committed
type-check devices.py exc.py features.py
1 parent 71fab5b commit e911f7a

3 files changed

Lines changed: 54 additions & 27 deletions

File tree

qubesadmin/devices.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ class is implemented by an extension.
3131
Devices are identified by pair of (backend domain, `port_id`), where `port_id`
3232
is :py:class:`str`.
3333
"""
34+
from __future__ import annotations
3435
import itertools
35-
from typing import Iterable
36+
from typing import TYPE_CHECKING
37+
from collections.abc import Iterable, Iterator
3638

3739
import qubesadmin.exc
3840
from qubesadmin.device_protocol import (
@@ -43,6 +45,8 @@ class is implemented by an extension.
4345
VirtualDevice,
4446
AssignmentMode, DeviceInterface,
4547
)
48+
if TYPE_CHECKING:
49+
from qubesadmin.vm import QubesVM
4650

4751

4852
class DeviceCollection:
@@ -55,7 +59,7 @@ class DeviceCollection:
5559
5660
"""
5761

58-
def __init__(self, vm, class_):
62+
def __init__(self, vm: QubesVM, class_: str):
5963
self._vm = vm
6064
self._class = class_
6165
self._dev_cache = {}
@@ -268,7 +272,7 @@ def get_exposed_devices(self) -> Iterable[DeviceInfo]:
268272

269273
def update_assignment(
270274
self, device: VirtualDevice, required: AssignmentMode
271-
):
275+
) -> None:
272276
"""
273277
Update assignment of already attached device.
274278
@@ -288,15 +292,15 @@ def update_assignment(
288292

289293
__iter__ = get_exposed_devices
290294

291-
def clear_cache(self):
295+
def clear_cache(self) -> None:
292296
"""
293297
Clear cache of available devices.
294298
"""
295299
self._dev_cache.clear()
296300
self._assignment_cache = None
297301
self._attachment_cache = None
298302

299-
def __getitem__(self, item):
303+
def __getitem__(self, item: object) -> DeviceInfo:
300304
"""Get device object with given port_id.
301305
302306
:returns: py:class:`DeviceInfo`
@@ -316,6 +320,8 @@ def __getitem__(self, item):
316320
return dev
317321
# if still nothing, return UnknownDevice instance for the reason
318322
# explained in docstring, but don't cache it
323+
if not isinstance(item, str | None):
324+
raise NotImplementedError
319325
return UnknownDevice(Port(self._vm, item, devclass=self._class))
320326

321327

@@ -325,21 +331,22 @@ class DeviceManager(dict):
325331
:param vm: VM for which we manage devices
326332
"""
327333

328-
def __init__(self, vm):
334+
def __init__(self, vm: QubesVM):
329335
super().__init__()
330336
self._vm = vm
331337

332-
def __missing__(self, key):
338+
def __missing__(self, key: str) -> DeviceCollection:
333339
self[key] = DeviceCollection(self._vm, key)
334340
return self[key]
335341

336-
def __iter__(self):
342+
def __iter__(self) -> Iterator[str]:
337343
return iter(self._vm.app.list_deviceclass())
338344

339-
def keys(self):
345+
346+
def keys(self) -> list[str]: # type: ignore[override]
340347
return self._vm.app.list_deviceclass()
341348

342-
def deny(self, *interfaces: Iterable[DeviceInterface]):
349+
def deny(self, *interfaces: Iterable[DeviceInterface]) -> None:
343350
"""
344351
Deny a device with any of the given interfaces from attaching to the VM.
345352
"""
@@ -350,7 +357,7 @@ def deny(self, *interfaces: Iterable[DeviceInterface]):
350357
"".join(repr(ifc) for ifc in interfaces).encode('ascii'),
351358
)
352359

353-
def allow(self, *interfaces: Iterable[DeviceInterface]):
360+
def allow(self, *interfaces: Iterable[DeviceInterface]) -> None:
354361
"""
355362
Remove given interfaces from denied list.
356363
"""
@@ -361,7 +368,7 @@ def allow(self, *interfaces: Iterable[DeviceInterface]):
361368
"".join(repr(ifc) for ifc in interfaces).encode('ascii'),
362369
)
363370

364-
def clear_cache(self):
371+
def clear_cache(self) -> None:
365372
"""Clear cache of all available device classes"""
366373
for devclass in self.values():
367374
devclass.clear_cache()

qubesadmin/exc.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
class QubesException(Exception):
2727
"""Exception that can be shown to the user"""
2828

29-
def __init__(self, message_format, *args, **kwargs):
29+
def __init__(self, message_format: str, *args, **kwargs):
3030
# TODO: handle translations
3131
super().__init__(
3232
message_format % tuple(int(d) if d.isdigit() else d for d in args),
@@ -37,7 +37,7 @@ def __init__(self, message_format, *args, **kwargs):
3737
class QubesVMNotFoundError(QubesException, KeyError):
3838
"""Domain cannot be found in the system"""
3939

40-
def __str__(self):
40+
def __str__(self) -> str:
4141
# KeyError overrides __str__ method
4242
return QubesException.__str__(self)
4343

@@ -139,23 +139,23 @@ class QubesMemoryError(QubesVMError, MemoryError):
139139
class QubesFeatureNotFoundError(QubesException, KeyError):
140140
"""Feature not set for a given domain"""
141141

142-
def __str__(self):
142+
def __str__(self) -> str:
143143
# KeyError overrides __str__ method
144144
return QubesException.__str__(self)
145145

146146

147147
class QubesTagNotFoundError(QubesException, KeyError):
148148
"""Tag not set for a given domain"""
149149

150-
def __str__(self):
150+
def __str__(self) -> str:
151151
# KeyError overrides __str__ method
152152
return QubesException.__str__(self)
153153

154154

155155
class QubesLabelNotFoundError(QubesException, KeyError):
156156
"""Label does not exists"""
157157

158-
def __str__(self):
158+
def __str__(self) -> str:
159159
# KeyError overrides __str__ method
160160
return QubesException.__str__(self)
161161

@@ -213,7 +213,7 @@ class QubesDaemonCommunicationError(QubesException):
213213
class BackupRestoreError(QubesException):
214214
"""Restoring a backup failed"""
215215

216-
def __init__(self, msg, backup_log=None):
216+
def __init__(self, msg: str, backup_log: bytes | None=None):
217217
super().__init__(msg)
218218
self.backup_log = backup_log
219219

@@ -228,7 +228,7 @@ class QubesPropertyAccessError(QubesDaemonAccessError, AttributeError):
228228
"""Failed to read/write property value, cause is unknown (insufficient
229229
permissions, no such property, invalid value, other)"""
230230

231-
def __init__(self, prop):
231+
def __init__(self, prop: str):
232232
super().__init__("Failed to access '%s' property" % prop)
233233

234234

qubesadmin/features.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@
1919
# with this program; if not, see <http://www.gnu.org/licenses/>.
2020

2121
'''VM features interface'''
22+
from __future__ import annotations
2223

24+
import typing
25+
from typing import TypeVar
26+
from collections.abc import Iterator, Generator
27+
28+
if typing.TYPE_CHECKING:
29+
from qubesadmin.vm import QubesVM
30+
31+
T = TypeVar('T')
2332

2433
class Features:
2534
'''Manager of the features.
@@ -33,14 +42,14 @@ class Features:
3342
false in Python) will result in string `'0'`, which is considered true.
3443
'''
3544

36-
def __init__(self, vm):
45+
def __init__(self, vm: QubesVM):
3746
super().__init__()
3847
self.vm = vm
3948

40-
def __delitem__(self, key):
49+
def __delitem__(self, key: str) -> None:
4150
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Remove', key)
4251

43-
def __setitem__(self, key, value):
52+
def __setitem__(self, key: str, value: object) -> None:
4453
if isinstance(value, bool):
4554
# False value needs to be serialized as empty string
4655
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key,
@@ -49,25 +58,30 @@ def __setitem__(self, key, value):
4958
self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key,
5059
str(value).encode())
5160

52-
def __getitem__(self, item):
61+
def __getitem__(self, item: str) -> str:
5362
return self.vm.qubesd_call(
5463
self.vm.name, 'admin.vm.feature.Get', item).decode('utf-8')
5564

56-
def __iter__(self):
65+
def __iter__(self) -> Iterator[str]:
5766
qubesd_response = self.vm.qubesd_call(self.vm.name,
5867
'admin.vm.feature.List')
5968
return iter(qubesd_response.decode('utf-8').splitlines())
6069

6170
keys = __iter__
6271

63-
def items(self):
72+
def items(self) -> Generator[tuple[str, str]]:
6473
'''Return iterable of pairs (feature, value)'''
6574
for key in self:
6675
yield key, self[key]
6776

6877
NO_DEFAULT = object()
6978

70-
def get(self, item, default=None):
79+
@typing.overload
80+
def get(self, item: str) -> str | None: ...
81+
@typing.overload
82+
def get(self, item: str, default: T) -> str | T: ...
83+
# Overloaded to handle default None return type
84+
def get(self, item: str, default: object = None) -> object:
7185
'''Get a feature, return default value if missing.'''
7286
try:
7387
return self[item]
@@ -76,7 +90,13 @@ def get(self, item, default=None):
7690
raise
7791
return default
7892

79-
def check_with_template(self, feature, default=None):
93+
@typing.overload
94+
def check_with_template(self, item: str) -> str | None: ...
95+
@typing.overload
96+
def check_with_template(self, item: str, default: T) -> str | T: ...
97+
# Overloaded to handle default None return type
98+
def check_with_template(self, feature: str,
99+
default: object = None) -> object:
80100
''' Check if the vm's template has the specified feature. '''
81101
try:
82102
qubesd_response = self.vm.qubesd_call(

0 commit comments

Comments
 (0)