Skip to content

Commit b40708e

Browse files
authored
Start pattern to disallow untyped functions, MyPY updates (#91)
* Start on enforcing some type hints Add required type hints for control module * Make control module mock dispatcher conform to the main protocol
1 parent 3913859 commit b40708e

File tree

5 files changed

+42
-30
lines changed

5 files changed

+42
-30
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
- run: pip install mypy
6363
- run: pip install -e .[pg_notify]
6464
- run: python3 -m pip install types-PyYAML
65-
- run: mypy --ignore-missing-imports dispatcher
65+
- run: mypy dispatcher
6666

6767
flake8:
6868
name: Run flake8

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ linters:
1919
black dispatcher/
2020
isort dispatcher/
2121
flake8 dispatcher/
22-
mypy --ignore-missing-imports dispatcher
22+
mypy dispatcher

dispatcher/control.py

+36-27
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import logging
44
import time
55
import uuid
6-
from typing import Optional
6+
from typing import Optional, Union
77

88
from .factories import get_broker
99
from .producers import BrokeredProducer
10+
from .protocols import Producer
1011
from .service.asyncio_tasks import ensure_fatal
1112

1213
logger = logging.getLogger('awx.main.dispatch.control')
@@ -23,49 +24,57 @@ class ControlCallbacks:
2324
it exists to interact with producers, using variables relevant to the particular
2425
control message being sent"""
2526

26-
def __init__(self, queuename, send_data, expected_replies) -> None:
27+
def __init__(self, queuename: Optional[str], send_data: dict, expected_replies: int) -> None:
2728
self.queuename = queuename
2829
self.send_data = send_data
2930
self.expected_replies = expected_replies
3031

3132
# received_replies only tracks the reply message, not the channel name
3233
# because they come via a temporary reply_to channel and that is not user-facing
33-
self.received_replies: list[str] = []
34+
self.received_replies: list[Union[dict, str]] = []
3435
self.events = ControlEvents()
3536
self.shutting_down = False
3637

37-
async def process_message(self, payload, producer=None, channel=None) -> tuple[Optional[str], Optional[str]]:
38+
async def process_message(
39+
self, payload: Union[dict, str], producer: Optional[Producer] = None, channel: Optional[str] = None
40+
) -> tuple[Optional[str], Optional[str]]:
3841
self.received_replies.append(payload)
3942
if self.expected_replies and (len(self.received_replies) >= self.expected_replies):
4043
self.events.exit_event.set()
4144
return (None, None)
4245

43-
async def connected_callback(self, producer) -> None:
46+
async def connected_callback(self, producer: Producer) -> None:
4447
payload = json.dumps(self.send_data)
45-
await producer.notify(channel=self.queuename, message=payload)
48+
# Ignore the type hint here because we know it is a brokered producer
49+
await producer.notify(channel=self.queuename, message=payload) # type: ignore[attr-defined]
4650
logger.info('Sent control message, expecting replies soon')
4751

52+
async def main(self) -> None:
53+
"Unused"
54+
pass
55+
4856

4957
class Control(object):
5058
def __init__(self, broker_name: str, broker_config: dict, queue: Optional[str] = None) -> None:
5159
self.queuename = queue
5260
self.broker_name = broker_name
5361
self.broker_config = broker_config
5462

55-
def running(self, *args, **kwargs):
56-
return self.control_with_reply('running', *args, **kwargs)
57-
58-
def cancel(self, task_ids, with_reply=True):
59-
if with_reply:
60-
return self.control_with_reply('cancel', extra_data={'task_ids': task_ids})
61-
else:
62-
self.control({'control': 'cancel', 'task_ids': task_ids, 'reply_to': None}, extra_data={'task_ids': task_ids})
63-
6463
@classmethod
65-
def generate_reply_queue_name(cls):
64+
def generate_reply_queue_name(cls) -> str:
6665
return f"reply_to_{str(uuid.uuid4()).replace('-', '_')}"
6766

68-
async def acontrol_with_reply_internal(self, producer, send_data, expected_replies, timeout):
67+
@staticmethod
68+
def parse_replies(received_replies: list[Union[str, dict]]) -> list[dict]:
69+
ret = []
70+
for payload in received_replies:
71+
if isinstance(payload, dict):
72+
ret.append(payload)
73+
else:
74+
ret.append(json.loads(payload))
75+
return ret
76+
77+
async def acontrol_with_reply_internal(self, producer: Producer, send_data: dict, expected_replies: int, timeout: float) -> list[dict]:
6978
control_callbacks = ControlCallbacks(self.queuename, send_data, expected_replies)
7079

7180
await producer.start_producing(control_callbacks)
@@ -83,22 +92,22 @@ async def acontrol_with_reply_internal(self, producer, send_data, expected_repli
8392
control_callbacks.shutting_down = True
8493
await producer.shutdown()
8594

86-
return [json.loads(payload) for payload in control_callbacks.received_replies]
95+
return self.parse_replies(control_callbacks.received_replies)
8796

88-
def make_producer(self, reply_queue):
97+
def make_producer(self, reply_queue: str) -> Producer:
8998
broker = get_broker(self.broker_name, self.broker_config, channels=[reply_queue])
9099
return BrokeredProducer(broker, close_on_exit=True)
91100

92-
async def acontrol_with_reply(self, command, expected_replies=1, timeout=1, data=None):
101+
async def acontrol_with_reply(self, command: str, expected_replies: int = 1, timeout: int = 1, data: Optional[dict] = None) -> list[dict]:
93102
reply_queue = Control.generate_reply_queue_name()
94-
send_data = {'control': command, 'reply_to': reply_queue}
103+
send_data: dict[str, Union[dict, str]] = {'control': command, 'reply_to': reply_queue}
95104
if data:
96105
send_data['control_data'] = data
97106

98107
return await self.acontrol_with_reply_internal(self.make_producer(reply_queue), send_data, expected_replies, timeout)
99108

100-
async def acontrol(self, command, data=None):
101-
send_data = {'control': command}
109+
async def acontrol(self, command: str, data: Optional[dict] = None) -> None:
110+
send_data: dict[str, Union[dict, str]] = {'control': command}
102111
if data:
103112
send_data['control_data'] = data
104113

@@ -110,13 +119,13 @@ def control_with_reply(self, command: str, expected_replies: int = 1, timeout: f
110119
logger.info('control-and-reply {} to {}'.format(command, self.queuename))
111120
start = time.time()
112121
reply_queue = Control.generate_reply_queue_name()
113-
send_data = {'control': command, 'reply_to': reply_queue}
122+
send_data: dict[str, Union[dict, str]] = {'control': command, 'reply_to': reply_queue}
114123
if data:
115124
send_data['control_data'] = data
116125

117126
broker = get_broker(self.broker_name, self.broker_config, channels=[reply_queue])
118127

119-
def connected_callback():
128+
def connected_callback() -> None:
120129
payload = json.dumps(send_data)
121130
if self.queuename:
122131
broker.publish_message(channel=self.queuename, message=payload)
@@ -131,9 +140,9 @@ def connected_callback():
131140
logger.info(f'control-and-reply message returned in {time.time() - start} seconds')
132141
return replies
133142

134-
def control(self, command, data=None):
143+
def control(self, command: str, data: Optional[dict] = None) -> None:
135144
"Send message in fire-and-forget mode, as synchronous code. Only for no-reply control."
136-
send_data = {'control': command}
145+
send_data: dict[str, Union[dict, str]] = {'control': command}
137146
if data:
138147
send_data['control_data'] = data
139148

dispatcher/protocols.py

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ async def dispatch_task(self, message: dict) -> None:
6565

6666

6767
class DispatcherMain(Protocol):
68-
fd_lock: asyncio.Lock
6968

7069
async def main(self) -> None:
7170
"""This is the method that runs the service, bring your own event loop"""

mypy.ini

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[mypy]
2+
3+
[mypy-dispatcher.control]
4+
disallow_untyped_defs = True

0 commit comments

Comments
 (0)