Skip to content

Commit 0dd97b4

Browse files
committed
Ruff and mypy checks
1 parent 0b8a77b commit 0dd97b4

File tree

4 files changed

+54
-51
lines changed

4 files changed

+54
-51
lines changed

src/pqnstack/app/api/routes/qkd.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
from pqnstack.app.api.deps import ClientDep
1313
from pqnstack.app.api.deps import StateDep
14-
from pqnstack.app.core.config import settings, NodeState
14+
from pqnstack.app.core.config import NodeState
15+
from pqnstack.app.core.config import qkd_result_received_event
16+
from pqnstack.app.core.config import settings
1517
from pqnstack.constants import BasisBool
1618
from pqnstack.constants import QKDEncodingBasis
1719
from pqnstack.network.client import Client
@@ -195,10 +197,9 @@ def request_qkd_basis_list(leader_basis_list: list[str], state: StateDep) -> lis
195197

196198

197199
@router.post("/set_qkd_emoji")
198-
def set_qkd_emoji(emoji: str, state: StateDep):
200+
def set_qkd_emoji(emoji: str, state: StateDep) -> None:
199201
"""Set the emoji pick for QKD."""
200202
state.qkd_emoji_pick = emoji
201-
return {"message": "Emoji set successfully"}
202203

203204

204205
@router.get("/question_order")
@@ -213,7 +214,6 @@ async def request_qkd_question_order(
213214
If this node is a follower, it requests the question order from the leader node.
214215
Returns the question order as a list of integers.
215216
"""
216-
217217
if state.leading and state.following:
218218
logger.error("Node cannot be both leader and follower")
219219
raise HTTPException(
@@ -245,8 +245,8 @@ async def request_qkd_question_order(
245245
detail="Failed to get question order from leader",
246246
)
247247
state.qkd_question_order = r.json()
248-
except Exception as e:
249-
logger.error("Error requesting question order from leader: %s", e)
248+
except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as e:
249+
logger.exception("Error requesting question order from leader: %s")
250250
raise HTTPException(
251251
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
252252
detail=f"Error requesting question order from leader: {e}",
@@ -257,12 +257,15 @@ async def request_qkd_question_order(
257257
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
258258
detail="Follower node has no leader address set",
259259
)
260+
260261
return state.qkd_question_order
261262

262263

263264
@router.get("/is_follower_ready")
264265
async def is_follower_ready(state: StateDep) -> bool:
265-
"""Check if the follower node is ready for QKD.
266+
"""
267+
Check if the follower node is ready for QKD.
268+
266269
Follower is ready when the state has the basis list with as many choices as the bitstring length.
267270
"""
268271
if not state.following:
@@ -277,42 +280,39 @@ async def is_follower_ready(state: StateDep) -> bool:
277280

278281
@router.post("/submit_qkd_result")
279282
async def submit_qkd_result(result: QKDResult, state: StateDep) -> None:
280-
"""
281-
QKD leader calls this endpoint of the follower to submit the QKD result as well as the emoji chosen.
282-
"""
283+
"""QKD leader calls this endpoint of the follower to submit the QKD result as well as the emoji chosen."""
283284
state.qkd_emoji_pick = result.emoji
284285
state.qkd_n_matching_bits = result.n_matching_bits
286+
qkd_result_received_event.set() # Signal that the result has been received
285287
logger.info("Received QKD result from follower: %s", result)
286288

287289

288-
async def _wait_for_follower_ready(state: NodeState, http_client: httpx.AsyncClient):
290+
async def _wait_for_follower_ready(state: NodeState, http_client: httpx.AsyncClient) -> None:
289291
"""Poll the follower until it's ready, checking every 0.5 seconds."""
290292
while True:
291293
try:
292294
r = await http_client.get(f"http://{state.followers_address}/qkd/is_follower_ready")
293295
if r.status_code == status.HTTP_200_OK:
294296
is_ready = r.json()
295297
if is_ready:
296-
logger.info("YAY FOLLOWER READY")
298+
logger.info("Follower has all basis choices. Ready to start QKD")
297299
break
298-
else:
299-
logger.info("Tried checking if follower is ready, but it wasn't ready")
300+
logger.info("Tried checking if follower is ready, but it wasn't ready")
300301
else:
301302
logger.info("Tried checking if follower is ready, but received non-200 status code")
302-
except Exception as e:
303+
except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as e:
303304
logger.info("Tried checking if follower is ready, but encountered error: %s", e)
304305

305306
await asyncio.sleep(0.5)
306307

307308

308309
async def _submit_qkd_result_to_follower(
309310
state: NodeState, http_client: httpx.AsyncClient, qkd_result: QKDResult
310-
):
311+
) -> None:
311312
"""Submit the QKD result to the follower node."""
312313
try:
313314
r = await http_client.post(
314-
f"http://{state.followers_address}/qkd/submit_qkd_result",
315-
json=qkd_result.model_dump()
315+
f"http://{state.followers_address}/qkd/submit_qkd_result", json=qkd_result.model_dump()
316316
)
317317
if r.status_code != status.HTTP_200_OK:
318318
logger.error("Failed to submit QKD result to follower: %s", r.text)
@@ -321,8 +321,8 @@ async def _submit_qkd_result_to_follower(
321321
detail="Failed to submit QKD result to follower",
322322
)
323323
logger.info("Successfully submitted QKD result to follower")
324-
except Exception as e:
325-
logger.error("Error submitting QKD result to follower: %s", e)
324+
except (httpx.HTTPError, httpx.RequestError, httpx.TimeoutException) as e:
325+
logger.exception("Error submitting QKD result to follower")
326326
raise HTTPException(
327327
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
328328
detail=f"Error submitting QKD result to follower: {e}",
@@ -343,42 +343,46 @@ async def _submit_qkd_basis_list_leader(
343343
n_matching_bits=len(ret),
344344
n_total_bits=settings.qkd_settings.bitstring_length,
345345
emoji=state.qkd_emoji_pick,
346-
role="leader"
346+
role="leader",
347347
)
348348

349349
# Submit result to follower
350350
await _submit_qkd_result_to_follower(state, http_client, qkd_result)
351351
return qkd_result
352352

353353

354-
async def _submit_qkd_basis_list_follower(
355-
state: NodeState, http_client: httpx.Client, basis_list: list[QKDEncodingBasis]
356-
) -> QKDResult:
354+
async def _submit_qkd_basis_list_follower(state: NodeState, basis_list: list[QKDEncodingBasis]) -> QKDResult:
357355
state.qkd_follower_basis_list = basis_list
358356

359-
# Wait until the leader submits the QKD result (state.qkd_n_matching_bits != -1)
360-
while state.qkd_n_matching_bits == -1:
361-
await asyncio.sleep(0.5)
357+
# don't wait for the event if the result is already set. This avoids deadlocks in case the result was set before this function is called.
358+
if state.qkd_n_matching_bits == -1:
359+
# Wait until the leader submits the QKD result
360+
await qkd_result_received_event.wait()
362361

363362
# Reassemble the QKDResult object from the state
364363
qkd_result = QKDResult(
365364
n_matching_bits=state.qkd_n_matching_bits,
366365
n_total_bits=settings.qkd_settings.bitstring_length,
367366
emoji=state.qkd_emoji_pick,
368-
role="follower"
367+
role="follower",
369368
)
370369

370+
# Clear the event for the next QKD run
371+
qkd_result_received_event.clear()
372+
371373
logger.info("Follower received QKD result: %s", state.qkd_n_matching_bits)
372374
return qkd_result
373375

374376

375377
@router.post("/submit_selection_and_start_qkd")
376378
async def submit_qkd_selection_and_start_qkd(
377379
state: StateDep, http_client: ClientDep, basis_list: list[str], timetagger_address: str = ""
378-
):
379-
"""GUI calls this function to submit the QKD basis selection and start the QKD protocol.
380-
This call is called by both leader and follower, depending on the node role, different actions are taken."""
380+
) -> QKDResult:
381+
"""
382+
GUI calls this function to submit the QKD basis selection and start the QKD protocol.
381383
384+
This call is called by both leader and follower, depending on the node role, different actions are taken.
385+
"""
382386
if state.leading and state.following:
383387
logger.error("Node cannot be both leader and follower")
384388
raise HTTPException(
@@ -401,7 +405,7 @@ async def submit_qkd_selection_and_start_qkd(
401405
elif basis_str.lower() == "b":
402406
qkd_basis_list.append(QKDEncodingBasis.DA)
403407
else:
404-
logger.error(f"Invalid basis string: {basis_str}. Expected 'a' or 'b'")
408+
logger.exception("Invalid basis string: %s. Expected 'a' or 'b'", basis_str)
405409
raise HTTPException(
406410
status_code=status.HTTP_400_BAD_REQUEST,
407411
detail=f"Invalid basis string: {basis_str}. Expected 'a' or 'b'",
@@ -414,9 +418,7 @@ async def submit_qkd_selection_and_start_qkd(
414418
status_code=status.HTTP_400_BAD_REQUEST,
415419
detail="Leader must provide timetagger address to start QKD",
416420
)
417-
ret = await _submit_qkd_basis_list_leader(state, http_client, qkd_basis_list, timetagger_address)
418-
return ret
421+
return await _submit_qkd_basis_list_leader(state, http_client, qkd_basis_list, timetagger_address)
419422

420-
if state.following:
421-
ret = await _submit_qkd_basis_list_follower(state, http_client, qkd_basis_list)
422-
return ret
423+
# If the node is not leading, it is assumed it is a follower due to previous check
424+
return await _submit_qkd_basis_list_follower(state, qkd_basis_list)

src/pqnstack/app/api/routes/serial.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,12 @@
77
from pydantic import BaseModel
88

99
from pqnstack.app.core.config import settings
10-
from pqnstack.pqn.drivers.rotaryencoder import SerialRotaryEncoder
10+
from pqnstack.pqn.drivers.rotaryencoder import SerialRotaryEncoder, MockRotaryEncoder
1111

1212
logger = logging.getLogger(__name__)
1313
router = APIRouter(prefix="/serial", tags=["measure"])
1414

1515

16-
class MockRotaryEncoder:
17-
"""Mock rotary encoder for terminal input when hardware is not available."""
18-
19-
def __init__(self) -> None:
20-
self.theta = 0.0
21-
22-
def read(self) -> float:
23-
return self.theta
24-
25-
2616
def update_theta_terminal(mock_encoder: MockRotaryEncoder) -> None:
2717
while True:
2818
try:

src/pqnstack/app/core/config.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from pqnstack.constants import BellState
1313
from pqnstack.constants import QKDEncodingBasis
14-
from pqnstack.pqn.drivers.rotaryencoder import SerialRotaryEncoder
14+
from pqnstack.pqn.drivers.rotaryencoder import SerialRotaryEncoder, MockRotaryEncoder
1515
from pqnstack.pqn.protocols.measurement import MeasurementConfig
1616

1717
logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ class Settings(BaseSettings):
4646
rotary_encoder_address: str = "/dev/ttyACM0"
4747
virtual_rotator: bool = False # If True, use terminal input instead of hardware rotary encoder
4848

49-
rotary_encoder: SerialRotaryEncoder | None = None
49+
rotary_encoder: SerialRotaryEncoder | MockRotaryEncoder | None = None
5050

5151
model_config = SettingsConfigDict(toml_file="./config.toml", env_file=".env", env_file_encoding="utf-8")
5252

@@ -101,7 +101,7 @@ class NodeState(BaseModel):
101101
# QKD state
102102
# FIXME: At the moment the reset_coordination_state resets this, probably want to refactor that function out.
103103
qkd_question_order: list[int] = [] # Order of questions for QKD
104-
qkd_emoji_pick: str = "" # Emoji chosen for QKD
104+
qkd_emoji_pick: str = "" # Emoji chosen for QKD
105105
qkd_leader_basis_list: list[QKDEncodingBasis] = [
106106
QKDEncodingBasis.DA,
107107
QKDEncodingBasis.DA,
@@ -127,6 +127,7 @@ class NodeState(BaseModel):
127127
state = NodeState()
128128
ask_user_for_follow_event = asyncio.Event()
129129
user_replied_event = asyncio.Event()
130+
qkd_result_received_event = asyncio.Event()
130131

131132

132133
def get_state() -> NodeState:

src/pqnstack/pqn/drivers/rotaryencoder.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,13 @@ def read(self) -> float:
2828
self._conn.write(b"ANGLE?\n")
2929
angle = self._conn.readline().decode().strip()
3030
return float(angle) + self.offset_degrees
31+
32+
33+
class MockRotaryEncoder:
34+
"""Mock rotary encoder for terminal input when hardware is not available."""
35+
36+
def __init__(self) -> None:
37+
self.theta = 0.0
38+
39+
def read(self) -> float:
40+
return self.theta

0 commit comments

Comments
 (0)