Skip to content

Commit d8a91cd

Browse files
committed
Merge remote-tracking branch 'origin/pr/458'
* origin/pr/458: misc. typing fixes type-hint storage.py type-hint spinner.py type-hint log.py type-hint label.py Pull request description: Major: type-hint `storage.py` Minor: type-hint `label.py spinner.py log.py` In `__lt__` we have ```python3 if self._vid and other._vid: return (self._pool, self._vid) < (other._pool, other._vid) ``` Technically, `_vid` is supposed to be non-None if `pool` is non-None, not the other way around. Shouldn't it be `if self.pool and other.pool:` ? In `revert`: ```python3 if not isinstance(revision, str): raise TypeError('revision must be a str') ``` but typing `def revert(self, revision: str) -> None:` raises no type-checker error, indicating that it's never called with a non-str argument. Is the raise really necessary ? ```python3 def __str__(self): return self.name ``` but `self.name` can be `None`, and `__str__` should never be `None`. I added an `assert is not None` for now. ```python3 def __lt__(self, other: object) -> bool: if isinstance(other, Pool): return self.name < other.name ``` What guarantees that `self.name` or `other.name` is defined here ?
2 parents 33442ab + 143aa64 commit d8a91cd

8 files changed

Lines changed: 118 additions & 79 deletions

File tree

qubesadmin/app.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,15 @@ def get_blind(self, item: str) -> QubesVM:
148148

149149
T = TypeVar("T")
150150

151-
def get(self, item: str | QubesVM, default: QubesVM | T=None)\
152-
-> QubesVM | T:
151+
@typing.overload
152+
def get(self, item: str | QubesVM) -> QubesVM:
153+
...
154+
@typing.overload
155+
def get(self, item: str | QubesVM, default: T) -> QubesVM | T:
156+
...
157+
# Overloaded to handle default None return type
158+
def get(self, item: str | QubesVM, default: object=None)\
159+
-> object:
153160
"""
154161
Get a VM object, or return *default* if it can't be found.
155162
"""

qubesadmin/backup/core2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def rule_from_xml_v1(node: _Element, action: str) -> Rule:
8787

8888
expire = node.get('expire')
8989

90-
kwargs = {
90+
kwargs: dict[str, object] = {
9191
'action': action,
9292
}
9393
if dsthost:

qubesadmin/backup/restore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,7 @@ def _process_qubes_xml(self) -> Core2Qubes | Core3Qubes:
14381438
queue.put(QUEUE_FINISHED)
14391439

14401440
qubes_xml_path = os.path.join(self.tmpdir, 'qubes-restored.xml')
1441-
handlers = {
1441+
handlers: dict[str, Callable] = {
14421442
'qubes.xml': functools.partial(self._save_qubes_xml, qubes_xml_path)
14431443
}
14441444
extract_proc = self._start_inner_extraction_worker(queue, handlers)

qubesadmin/device_protocol.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,8 @@ def __lt__(self, other: object) -> bool:
513513
other.port, AnyPort
514514
):
515515
return False
516-
reprs = {self: [self.port], other: [other.port]}
516+
reprs: dict[VirtualDevice | DeviceAssignment, list[Port | str]] \
517+
= {self: [self.port], other: [other.port]}
517518
for obj, obj_repr in reprs.items():
518519
if obj.device_id != "*":
519520
obj_repr.append(obj.device_id)

qubesadmin/label.py

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

2121
'''VM Labels'''
22+
from __future__ import annotations
23+
from typing import TYPE_CHECKING
2224

2325
import qubesadmin.exc
2426

27+
if TYPE_CHECKING:
28+
from qubesadmin.app import QubesBase
29+
2530

2631
class Label:
2732
'''Label definition for virtual machines
@@ -32,14 +37,14 @@ class Label:
3237
:param str name: label's name like "red" or "green"
3338
'''
3439

35-
def __init__(self, app, name):
40+
def __init__(self, app: QubesBase, name: str):
3641
self.app = app
3742
self._name = name
38-
self._color = None
39-
self._index = None
43+
self._color: str | None = None
44+
self._index: int | None = None
4045

4146
@property
42-
def color(self):
47+
def color(self) -> str:
4348
'''color specification as in HTML (``#abcdef``)'''
4449
if self._color is None:
4550
try:
@@ -51,18 +56,18 @@ def color(self):
5156
return self._color
5257

5358
@property
54-
def name(self):
59+
def name(self) -> str:
5560
'''label's name like "red" or "green"'''
5661
return self._name
5762

5863
@property
59-
def icon(self):
64+
def icon(self) -> str:
6065
'''freedesktop icon name, suitable for use in
6166
:py:meth:`PyQt4.QtGui.QIcon.fromTheme`'''
6267
return 'appvm-' + self.name
6368

6469
@property
65-
def index(self):
70+
def index(self) -> int:
6671
'''label numeric identifier'''
6772
if self._index is None:
6873
try:
@@ -73,13 +78,13 @@ def index(self):
7378
self._index = int(qubesd_response.decode())
7479
return self._index
7580

76-
def __str__(self):
81+
def __str__(self) -> str:
7782
return self._name
7883

79-
def __eq__(self, other):
84+
def __eq__(self, other: object) -> bool:
8085
if isinstance(other, Label):
8186
return self.name == other.name
8287
return NotImplemented
8388

84-
def __hash__(self):
89+
def __hash__(self) -> int:
8590
return hash(self.name)

qubesadmin/log.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
formatter_debug = logging.Formatter(FORMAT_DEBUG)
3838

3939

40-
def enable():
40+
def enable() -> None:
4141
'''Enable global logging
4242
4343
Use :py:mod:`logging` module from standard library to log messages.
@@ -58,7 +58,7 @@ def enable():
5858
logging.root.setLevel(logging.INFO)
5959

6060

61-
def enable_debug():
61+
def enable_debug() -> None:
6262
'''Enable debug logging
6363
6464
Enable more messages and additional info to message format.

qubesadmin/spinner.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
import curses
4040
import io
4141
import itertools
42+
import typing
43+
from typing import IO
4244

43-
CHARSET = '-\\|/'
44-
ENTERPRISE_CHARSET = CHARSET * 4 + '-._.-^' * 2
45+
CHARSET: str = '-\\|/'
46+
ENTERPRISE_CHARSET: str = CHARSET * 4 + '-._.-^' * 2
4547

4648
class AbstractSpinner:
4749
'''The base class for all Spinners
@@ -54,57 +56,57 @@ class AbstractSpinner:
5456
2. zero or more calls to :py:meth:`update()`
5557
3. exactly one call to :py:meth:`hide()`
5658
'''
57-
def __init__(self, stream, charset=CHARSET):
59+
def __init__(self, stream: IO, charset: str=CHARSET):
5860
self.stream = stream
5961
self.charset = itertools.cycle(charset)
6062

61-
def show(self, prompt):
63+
def show(self, prompt: str) -> None:
6264
'''Show the spinner, with a prompt
6365
6466
:param str prompt: prompt, like "please wait"
6567
'''
6668
raise NotImplementedError()
6769

68-
def hide(self):
70+
def hide(self) -> None:
6971
'''Hide the spinner and the prompt'''
7072
raise NotImplementedError()
7173

72-
def update(self):
74+
def update(self) -> None:
7375
'''Show next spinner character'''
7476
raise NotImplementedError()
7577

7678

7779
class DummySpinner(AbstractSpinner):
7880
'''Dummy spinner, does not do anything'''
79-
def show(self, prompt):
81+
def show(self, prompt: str) -> None:
8082
pass
8183

82-
def hide(self):
84+
def hide(self) -> None:
8385
pass
8486

85-
def update(self):
87+
def update(self) -> None:
8688
pass
8789

8890

8991
class QubesSpinner(AbstractSpinner):
9092
'''Basic spinner
9193
9294
This spinner uses standard ASCII control characters'''
93-
def __init__(self, *args, **kwargs):
95+
def __init__(self, *args, **kwargs) -> None:
9496
super().__init__(*args, **kwargs)
9597
self.hidelen = 0
9698
self.cub1 = '\b'
9799

98-
def show(self, prompt):
100+
def show(self, prompt: str) -> None:
99101
self.hidelen = len(prompt) + 2
100102
self.stream.write('{} {}'.format(prompt, next(self.charset)))
101103
self.stream.flush()
102104

103-
def hide(self):
105+
def hide(self) -> None:
104106
self.stream.write('\r' + ' ' * self.hidelen + '\r')
105107
self.stream.flush()
106108

107-
def update(self):
109+
def update(self) -> None:
108110
self.stream.write(self.cub1 + next(self.charset))
109111
self.stream.flush()
110112

@@ -114,7 +116,7 @@ class QubesSpinnerEnterpriseEdition(QubesSpinner):
114116
115117
This is tty- and terminfo-aware spinner. Recommended.
116118
'''
117-
def __init__(self, stream, charset=None):
119+
def __init__(self, stream: IO, charset: str | None=None):
118120
# our Enterprise logic follows
119121
self.stream_isatty = stream.isatty()
120122
if charset is None:
@@ -126,18 +128,20 @@ def __init__(self, stream, charset=None):
126128
try:
127129
curses.setupterm()
128130
self.has_terminfo = True
129-
self.cub1 = curses.tigetstr('cub1').decode()
131+
self.cub1 = typing.cast(bytes, curses.tigetstr('cub1')).decode()
130132
except (curses.error, io.UnsupportedOperation):
131133
# we are in very non-Enterprise environment
132134
self.has_terminfo = False
133135
else:
134136
self.cub1 = ''
135137

136-
def hide(self):
138+
def hide(self) -> None:
137139
if self.stream_isatty:
138140
hideseq = '\r' + ' ' * self.hidelen + '\r'
139141
if self.has_terminfo:
140-
hideseq_l = (curses.tigetstr('cr'), curses.tigetstr('clr_eol'))
142+
hideseq_l = typing.cast(
143+
tuple[bytes, bytes],
144+
(curses.tigetstr('cr'), curses.tigetstr('clr_eol')))
141145
if all(seq is not None for seq in hideseq_l):
142146
hideseq = ''.join(seq.decode() for seq in hideseq_l)
143147
else:

0 commit comments

Comments
 (0)