Skip to content

Commit 2fca809

Browse files
committed
Add type annotations across the workflows package
Annotate Recipe, RecipeWrapper, CommonTransport, common_service, transport middleware (incl. OTEL), pika/stomp/offline transports, frontend, contrib, services, and util/zocalo modules. Also includes related minor refactors required by typing: elevate kwargs to explicit parameters where appropriate; make CommonService.transport raise if unset; route the cls.recipe override correctly in Recipe.__init__; forward log_extender in recipe.wrap_subscribe; type-annotate validate_recipe and the offline/middleware classes; tighten test signature expectations to match the new exact send() signature.
1 parent cab1b18 commit 2fca809

27 files changed

Lines changed: 1550 additions & 732 deletions

.pre-commit-config.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ repos:
1010
- id: check-merge-conflict
1111
- id: check-ast
1212
fail_fast: True
13+
language_version: python3.12
1314
- id: check-json
1415
- id: check-added-large-files
1516
args: ['--maxkb=200']
@@ -35,4 +36,5 @@ repos:
3536
hooks:
3637
- id: mypy
3738
files: 'src/.*\.py$'
38-
additional_dependencies: ['types-setuptools==57.0.2']
39+
additional_dependencies: ['types-setuptools>=57.0.2']
40+
language_version: python3.12

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,6 @@ required-imports = ["from __future__ import annotations"]
110110

111111
[tool.mypy]
112112
mypy_path = "src/"
113+
114+
[tool.pyright]
115+
exclude = ["tests", ".venv"]

src/workflows/contrib/start_service.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from __future__ import annotations
22

3+
import optparse
34
import sys
45
from collections.abc import Callable
56
from optparse import SUPPRESS_HELP, OptionParser
7+
from typing import Any
68

79
import workflows
810
import workflows.frontend
@@ -17,20 +19,22 @@ class ServiceStarter:
1719
used in a number of scenarios."""
1820

1921
@staticmethod
20-
def on_parser_preparation(parser):
22+
def on_parser_preparation(parser: OptionParser) -> OptionParser | None:
2123
"""Plugin hook to manipulate the OptionParser object before command line
2224
parsing. If a value is returned here it will replace the OptionParser
2325
object."""
2426

2527
@staticmethod
26-
def on_parsing(options, args):
28+
def on_parsing(
29+
options: optparse.Values, args: list[str]
30+
) -> tuple[optparse.Values, list[str]] | None:
2731
"""Plugin hook to manipulate the command line parsing results.
2832
A tuple of values can be returned, which will replace (options, args).
2933
"""
3034

3135
@staticmethod
3236
def on_transport_factory_preparation(
33-
transport_factory,
37+
transport_factory: Callable[[], CommonTransport],
3438
) -> Callable[[], CommonTransport] | None:
3539
"""Plugin hook to intercept/manipulate newly created Transport factories
3640
before first invocation."""
@@ -41,24 +45,26 @@ def on_transport_preparation(transport: CommonTransport) -> CommonTransport | No
4145
before connecting."""
4246

4347
@staticmethod
44-
def before_frontend_construction(kwargs):
48+
def before_frontend_construction(kwargs: dict[str, Any]) -> dict[str, Any] | None:
4549
"""Plugin hook to manipulate the Frontend object constructor arguments. If
4650
a value is returned here it will replace the keyword arguments
4751
dictionary passed to the constructor."""
4852

4953
@staticmethod
50-
def on_frontend_preparation(frontend):
54+
def on_frontend_preparation(
55+
frontend: workflows.frontend.Frontend,
56+
) -> workflows.frontend.Frontend | None:
5157
"""Plugin hook to manipulate the Frontend object before starting it. If a
5258
value is returned here it will replace the Frontend object."""
5359

5460
def run(
5561
self,
56-
cmdline_args=None,
57-
program_name="start_service",
58-
version=None,
62+
cmdline_args: list[str] | None = None,
63+
program_name: str = "start_service",
64+
version: str | None = None,
5965
add_metrics_option: bool = False,
60-
**kwargs,
61-
):
66+
**kwargs: Any,
67+
) -> None:
6268
"""Example command line interface to start services.
6369
6470
Args:
@@ -165,10 +171,12 @@ def on_transport_preparation_hook() -> CommonTransport:
165171
if options.service not in known_services:
166172
# First check whether the provided service name is a case-insensitive match.
167173
service_lower = options.service.lower()
168-
match = {s.lower(): s for s in known_services}.get(service_lower, None)
169-
match = (
170-
[match]
171-
if match
174+
exact_match = {s.lower(): s for s in known_services}.get(
175+
service_lower, None
176+
)
177+
match: list[str] = (
178+
[exact_match]
179+
if exact_match
172180
# Next, check whether the provided service name is a partial
173181
# case-sensitive match.
174182
else [s for s in known_services if s.startswith(options.service)]

src/workflows/contrib/status_monitor.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import curses
44
import threading
55
import time
6-
from typing import Any
6+
from collections.abc import Mapping
7+
from typing import Any, Callable
78

89
import workflows.transport
910
from workflows.services.common_service import CommonService
10-
11-
basestring = (str, bytes)
11+
from workflows.transport.common_transport import CommonTransport
1212

1313

1414
class Monitor: # pragma: no cover
@@ -19,29 +19,29 @@ class Monitor: # pragma: no cover
1919
shutdown = False
2020
"""Set to true to end the main loop and shut down the service monitor."""
2121

22-
cards: dict[Any, Any] = {}
22+
cards: list
2323
"""Register card shown for seen services"""
2424

2525
border_chars = ()
2626
"""Characters used for frame borders."""
2727
border_chars_text = ("|", "|", "=", "=", "/", "\\", "\\", "/")
2828
"""Example alternative set of frame border characters."""
2929

30-
def __init__(self, transport=None):
30+
def __init__(self, transport: Callable[[], CommonTransport] | str | None = None):
3131
"""Set up monitor and connect to the network transport layer"""
32-
if transport is None or isinstance(transport, basestring):
33-
self._transport = workflows.transport.lookup(transport)()
34-
else:
32+
if callable(transport):
3533
self._transport = transport()
34+
else:
35+
self._transport = workflows.transport.lookup(transport)()
3636
assert self._transport.connect(), "Could not connect to transport layer"
3737
self._lock = threading.RLock()
38-
self._node_status = {}
39-
self.message_box = None
38+
self._node_status: dict = {}
39+
self.message_box: curses.window | None = None
4040
self._transport.subscribe_broadcast(
4141
"transient.status", self.update_status, retroactive=True
4242
)
4343

44-
def update_status(self, header, message):
44+
def update_status(self, header: Mapping[str, Any], message: Any) -> None:
4545
"""Process incoming status message. Acquire lock for status dictionary before updating."""
4646
with self._lock:
4747
if self.message_box:
@@ -70,14 +70,21 @@ def update_status(self, header, message):
7070
self._node_status[message["host"]] = message
7171
self._node_status[message["host"]]["last_seen"] = receipt_time
7272

73-
def run(self):
73+
def run(self) -> None:
7474
"""A wrapper for the real _run() function to cleanly enable/disable the
7575
curses environment."""
7676
curses.wrapper(self._run)
7777

7878
def _boxwin(
79-
self, height, width, row, column, title=None, title_x=7, color_pair=None
80-
):
79+
self,
80+
height: int,
81+
width: int,
82+
row: int,
83+
column: int,
84+
title: str | None = None,
85+
title_x: int = 7,
86+
color_pair: int | None = None,
87+
) -> curses.window:
8188
with self._lock:
8289
box = curses.newwin(height, width, row, column)
8390
box.clear()
@@ -91,7 +98,7 @@ def _boxwin(
9198
box.noutrefresh()
9299
return curses.newwin(height - 2, width - 2, row + 1, column + 1)
93100

94-
def _redraw_screen(self, stdscr):
101+
def _redraw_screen(self, stdscr: curses.window) -> None:
95102
"""Redraw screen. This could be to initialize, or to redraw after resizing."""
96103
with self._lock:
97104
stdscr.clear()
@@ -105,7 +112,7 @@ def _redraw_screen(self, stdscr):
105112
self.message_box.scrollok(True)
106113
self.cards = []
107114

108-
def _get_card(self, number):
115+
def _get_card(self, number: int) -> curses.window:
109116
with self._lock:
110117
if number < len(self.cards):
111118
return self.cards[number]
@@ -123,7 +130,7 @@ def _get_card(self, number):
123130
return self.cards[number]
124131
raise RuntimeError("Card number too high")
125132

126-
def _erase_card(self, number):
133+
def _erase_card(self, number: int) -> None:
127134
"""Destroy cards with this or higher number."""
128135
with self._lock:
129136
if number < (len(self.cards) - 1):
@@ -141,7 +148,7 @@ def _erase_card(self, number):
141148
obliterate.noutrefresh()
142149
del self.cards[number]
143150

144-
def _run(self, stdscr):
151+
def _run(self, stdscr: curses.window) -> None:
145152
"""Start the actual service monitor"""
146153
with self._lock:
147154
curses.use_default_colors()

0 commit comments

Comments
 (0)