Skip to content

Commit af61341

Browse files
committed
Make mypy happy and add CI job
1 parent 727133f commit af61341

13 files changed

+142
-75
lines changed

.gitlab-ci.yml

+16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ checks:pylint:
66
script:
77
- PYTHONPATH=test-packages:~/qubes-core-qrexec python3 -m pylint qubes
88
stage: checks
9+
910
checks:tests:
1011
after_script:
1112
- ci/codecov-wrapper -F unittests
@@ -37,6 +38,21 @@ checks:tests:
3738
stage: checks
3839
tags:
3940
- vm-kvm
41+
42+
mypy:
43+
stage: checks
44+
image: fedora:40
45+
tags:
46+
- docker
47+
before_script:
48+
- sudo dnf install -y python3-mypy python3-pip
49+
- sudo python3 -m pip install lxml-stubs types-docutils types-pywin32
50+
script:
51+
- mypy --install-types --non-interactive --ignore-missing-imports --exclude tests/ --junit-xml mypy.xml qubes
52+
artifacts:
53+
reports:
54+
junit: mypy.xml
55+
4056
include:
4157
- file: /r4.3/gitlab-base.yml
4258
project: QubesOS/qubes-continuous-integration

qubes/api/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class AbstractQubesAPI:
135135
'''
136136

137137
#: the preferred socket location (to be overridden in child's class)
138-
SOCKNAME = None
138+
SOCKNAME = ""
139139

140140
app: qubes.Qubes
141141
src: qubes.vm.qubesvm.QubesVM
@@ -144,7 +144,7 @@ def __init__(self,
144144
src: bytes,
145145
method_name: bytes,
146146
dest: bytes,
147-
arg: qubes.Qubes,
147+
arg: bytes,
148148
send_event: Any = None) -> None:
149149
#: :py:class:`qubes.Qubes` object
150150
self.app = app

qubes/config.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,26 @@
2828

2929
import os.path
3030

31+
from typing import TypedDict, Dict
32+
33+
class PoolConfig(TypedDict, total=False):
34+
dir_path: str
35+
name: str
36+
driver: str
37+
38+
class Defaults(TypedDict):
39+
libvirt_uri: str
40+
memory: int
41+
hvm_memory: int
42+
kernelopts: str
43+
kernelopts_pcidevs: str
44+
kernelopts_common: str
45+
private_img_size: int
46+
root_img_size: int
47+
pool_configs: Dict[str, PoolConfig]
48+
3149
qubes_base_dir = "/var/lib/qubes"
50+
3251
system_path = {
3352
'qrexec_daemon_path': '/usr/sbin/qrexec-daemon',
3453
'qrexec_client_path': '/usr/bin/qrexec-client',
@@ -48,7 +67,7 @@
4867
'dom0_services_dir': '/var/run/qubes-service',
4968
}
5069

51-
defaults = {
70+
defaults: Defaults = {
5271
'libvirt_uri': 'xen:///',
5372
'memory': 400,
5473
'hvm_memory': 400,
@@ -67,8 +86,9 @@
6786
'name': 'varlibqubes'
6887
},
6988
'linux-kernel': {
70-
'dir_path': os.path.join(qubes_base_dir,
71-
system_path['qubes_kernels_base_dir']),
89+
'dir_path': os.path.join(
90+
qubes_base_dir, system_path['qubes_kernels_base_dir']
91+
),
7292
'driver': 'linux-kernel',
7393
'name': 'linux-kernel'
7494
}

qubes/device_protocol.py

+34-19
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,15 @@
3535
import sys
3636
from enum import Enum
3737
from typing import Optional, Dict, Any, List, Union, Tuple, Callable
38+
from typing import TYPE_CHECKING
3839

3940
import qubes.utils
40-
4141
from qubes.exc import ProtocolError
4242

43-
QubesVM = "qubes.vm.BaseVM"
43+
if TYPE_CHECKING:
44+
from qubes.vm.qubesvm import QubesVM
45+
else:
46+
QubesVM = "qubes.vm.qubesvm.QubesVM"
4447

4548

4649
class UnexpectedDeviceProperty(qubes.exc.QubesException, ValueError):
@@ -82,8 +85,8 @@ def unpack_properties(
8285
"ascii", errors="strict"
8386
).strip()
8487

85-
properties = {}
86-
options = {}
88+
properties: Dict[str, str] = {}
89+
options: Dict[str, str] = {}
8790

8891
if not ut_decoded:
8992
return properties, options
@@ -221,7 +224,7 @@ def deserialize_str(value: str) -> str:
221224
def sanitize_str(
222225
untrusted_value: str,
223226
allowed_chars: set,
224-
replace_char: str = None,
227+
replace_char: Optional[str] = None,
225228
error_message: str = "",
226229
) -> str:
227230
"""
@@ -256,7 +259,10 @@ class Port:
256259
"""
257260

258261
def __init__(
259-
self, backend_domain: Optional[QubesVM], port_id: str, devclass: str
262+
self,
263+
backend_domain: Optional[QubesVM],
264+
port_id: str,
265+
devclass: Optional[str],
260266
):
261267
self.__backend_domain = backend_domain
262268
self.__port_id = port_id
@@ -370,6 +376,7 @@ def has_devclass(self):
370376

371377
class AnyPort(Port):
372378
"""Represents any port in virtual devices ("*")"""
379+
373380
def __init__(self, devclass: str):
374381
super().__init__(None, "*", devclass)
375382

@@ -395,14 +402,14 @@ def __init__(
395402
device_id: Optional[str] = None,
396403
):
397404
assert not isinstance(port, AnyPort) or device_id is not None
398-
self.port: Optional[Port] = port
405+
self.port: Optional[Port] = port # type: ignore
399406
self._device_id = device_id
400407

401408
def clone(self, **kwargs) -> "VirtualDevice":
402409
"""
403410
Clone object and substitute attributes with explicitly given.
404411
"""
405-
attr = {
412+
attr: Dict[str, Any] = {
406413
"port": self.port,
407414
"device_id": self.device_id,
408415
}
@@ -429,7 +436,7 @@ def port(self, value: Union[Port, str, None]):
429436
@property
430437
def device_id(self) -> str:
431438
# pylint: disable=missing-function-docstring
432-
if self.is_device_id_set:
439+
if self._device_id is not None and self.is_device_id_set:
433440
return self._device_id
434441
return "*"
435442

@@ -467,7 +474,7 @@ def description(self) -> str:
467474
"""
468475
Return human-readable description of the device identity.
469476
"""
470-
if self.device_id == "*":
477+
if not self.device_id or self.device_id == "*":
471478
return "any device"
472479
return self.device_id
473480

@@ -581,12 +588,11 @@ def _parse(
581588
backend = get_domain(backend_name)
582589
else:
583590
identity = representation
591+
584592
port_id, _, devid = identity.partition(":")
585-
if devid == "":
586-
devid = None
587593
return cls(
588594
Port(backend_domain=backend, port_id=port_id, devclass=devclass),
589-
device_id=devid,
595+
device_id=devid or None,
590596
)
591597

592598
def serialize(self) -> bytes:
@@ -849,7 +855,7 @@ def __init__(
849855
name: Optional[str] = None,
850856
serial: Optional[str] = None,
851857
interfaces: Optional[List[DeviceInterface]] = None,
852-
parent: Optional[Port] = None,
858+
parent: Optional["DeviceInfo"] = None,
853859
attachment: Optional[QubesVM] = None,
854860
device_id: Optional[str] = None,
855861
**kwargs,
@@ -1002,6 +1008,8 @@ def subdevices(self) -> List[VirtualDevice]:
10021008
If the device has subdevices (e.g., partitions of a USB stick),
10031009
the subdevices id should be here.
10041010
"""
1011+
if not self.backend_domain:
1012+
return []
10051013
return [
10061014
dev
10071015
for devclass in self.backend_domain.devices.keys()
@@ -1103,7 +1111,7 @@ def _deserialize(
11031111

11041112
if "attachment" not in properties or not properties["attachment"]:
11051113
properties["attachment"] = None
1106-
else:
1114+
elif expected_device.backend_domain:
11071115
app = expected_device.backend_domain.app
11081116
properties["attachment"] = app.domains.get_blind(
11091117
properties["attachment"]
@@ -1260,7 +1268,7 @@ def __lt__(self, other):
12601268
)
12611269

12621270
@property
1263-
def backend_domain(self) -> QubesVM:
1271+
def backend_domain(self) -> Optional[QubesVM]:
12641272
# pylint: disable=missing-function-docstring
12651273
return self.virtual_device.backend_domain
12661274

@@ -1287,14 +1295,16 @@ def device_id(self) -> str:
12871295
@property
12881296
def devices(self) -> List[DeviceInfo]:
12891297
"""Get DeviceInfo objects corresponding to this DeviceAssignment"""
1298+
result: List[DeviceInfo] = []
1299+
if not self.backend_domain:
1300+
return result
12901301
if self.port_id != "*":
12911302
dev = self.backend_domain.devices[self.devclass][self.port_id]
12921303
if (
12931304
isinstance(dev, UnknownDevice)
12941305
or self.device_id in (dev.device_id, "*")
12951306
):
12961307
return [dev]
1297-
result = []
12981308
if self.device_id == "0000:0000::?******":
12991309
return result
13001310
for dev in self.backend_domain.devices[self.devclass]:
@@ -1334,8 +1344,13 @@ def frontend_domain(self) -> Optional[QubesVM]:
13341344
def frontend_domain(self, frontend_domain: Optional[Union[str, QubesVM]]):
13351345
"""Which domain the device is attached/assigned to."""
13361346
if isinstance(frontend_domain, str):
1337-
frontend_domain = self.backend_domain.app.domains[frontend_domain]
1338-
self.__frontend_domain = frontend_domain
1347+
if not self.backend_domain:
1348+
raise ProtocolError("Cannot determine backend domain")
1349+
self.__frontend_domain: Optional[QubesVM] = (
1350+
self.backend_domain.app.domains[frontend_domain]
1351+
)
1352+
else:
1353+
self.__frontend_domain = frontend_domain
13391354

13401355
@property
13411356
def attached(self) -> bool:

qubes/dochelpers.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
try:
5252
log = logging.getLogger(__name__)
5353
except AttributeError:
54-
log = None
54+
log = None # type: ignore
5555

5656
class GithubTicket:
5757
# pylint: disable=too-few-public-methods
@@ -89,7 +89,8 @@ def ticket(name, rawtext, text, lineno, inliner, options=None, content=None):
8989
that called this function
9090
:param options: Directive options for customisation
9191
:param content: The directive content for customisation
92-
""" # pylint: disable=unused-argument,too-many-positional-arguments
92+
"""
93+
# pylint: disable=unused-argument,too-many-positional-arguments
9394

9495
if options is None:
9596
options = {}

qubes/ext/__init__.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
some systems. They may be OS- or architecture-dependent or custom-developed for
2525
particular customer.
2626
'''
27-
27+
import collections
2828
import importlib.metadata
2929
import qubes.events
3030

@@ -53,6 +53,13 @@ def __new__(cls):
5353

5454
return cls._instance
5555

56+
def __init__(self):
57+
#: This is to be implemented in extension handling devices
58+
self.devices_cache = collections.defaultdict(dict)
59+
60+
#: This is to be implemented in extension handling devices
61+
def ensure_detach(self, vm, port):
62+
pass
5663

5764
def get_extensions():
5865
return set(ext.load()()

qubes/ext/admin.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def on_tag_add(self, vm, event, tag, **kwargs):
169169
@qubes.ext.handler(*(f'admin-permission:admin.vm.device.{ep.name}.Attach'
170170
for ep in importlib.metadata.entry_points(group='qubes.devices')))
171171
def on_device_attach(
172-
self, vm, event, dest, arg, device, mode, options, **kwargs
172+
self, vm, event, dest, arg, device, mode, **kwargs
173173
):
174174
# pylint: disable=unused-argument,too-many-positional-arguments
175175
# ignore auto-attachment
@@ -210,9 +210,8 @@ def _load_deny_list(deny: dict, path: str) -> None:
210210

211211
if line:
212212
name, *values = line.split()
213-
214213
values = ' '.join(values).replace(',', ' ').split()
215-
values = {v for v in values if len(v) > 0}
214+
values = [v for v in values if len(v) > 0]
216215

217216
deny[name] = deny.get(name, set()).union(set(values))
218217
except IOError:

0 commit comments

Comments
 (0)