Skip to content

Commit d971106

Browse files
Mike ProsserMike Prosser
authored andcommitted
add a retry if connect fails, and remove PanelNotFoundError
1 parent c7f5f78 commit d971106

File tree

4 files changed

+50
-27
lines changed

4 files changed

+50
-27
lines changed

src/nipanel/_panel.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
from __future__ import annotations
22

33
import sys
4+
import time
45
from abc import ABC, abstractmethod
56
from types import TracebackType
67
from typing import TYPE_CHECKING, Optional, Type
78

8-
from grpc import RpcError, StatusCode
9+
from grpc import RpcError
910
from ni.pythonpanel.v1.python_panel_service_pb2 import ConnectRequest, DisconnectRequest
1011
from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub
1112
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
1213

13-
from nipanel._panel_not_found_error import PanelNotFoundError
14-
1514
if TYPE_CHECKING:
1615
if sys.version_info >= (3, 11):
1716
from typing import Self
@@ -22,6 +21,8 @@
2221
class Panel(ABC):
2322
"""This class allows you to connect to a panel and specify values for its controls."""
2423

24+
RETRY_WAIT_TIME = 1 # time in seconds to wait before retrying connection
25+
2526
_channel_pool: GrpcChannelPool
2627
_stub: PythonPanelServiceStub | None
2728
_panel_id: str
@@ -32,6 +33,7 @@ class Panel(ABC):
3233
def __init__(self, panel_id: str, panel_uri: str) -> None:
3334
"""Initialize the panel."""
3435
self._channel_pool = GrpcChannelPool()
36+
self._stub = None
3537
self._panel_id = panel_id
3638
self._panel_uri = panel_uri
3739

@@ -65,31 +67,22 @@ def connect(self) -> None:
6567
address = self._resolve_service_address()
6668
channel = self._channel_pool.get_channel(address)
6769
self._stub = PythonPanelServiceStub(channel)
68-
connect_request = ConnectRequest(panel_id=self._panel_id, panel_uri=self._panel_uri)
6970

71+
connect_request = ConnectRequest(panel_id=self._panel_id, panel_uri=self._panel_uri)
7072
try:
7173
self._stub.Connect(connect_request)
72-
except RpcError as e:
73-
if e.code() == StatusCode.NOT_FOUND:
74-
raise PanelNotFoundError(self._panel_id, self._panel_uri) from e
75-
else:
76-
raise
74+
except RpcError:
75+
# retry the connection if it fails, but only once
76+
time.sleep(self.RETRY_WAIT_TIME)
77+
self._stub.Connect(connect_request)
7778

7879
def disconnect(self) -> None:
7980
"""Disconnect from the panel (does not close the panel)."""
80-
disconnect_request = DisconnectRequest(panel_id=self._panel_id)
81-
8281
if self._stub is None:
8382
raise RuntimeError("connect() must be called before disconnect()")
8483

85-
try:
86-
self._stub.Disconnect(disconnect_request)
87-
except RpcError as e:
88-
if e.code() == StatusCode.NOT_FOUND:
89-
raise PanelNotFoundError(self._panel_id, self._panel_uri) from e
90-
else:
91-
raise
92-
84+
disconnect_request = DisconnectRequest(panel_id=self._panel_id)
85+
self._stub.Disconnect(disconnect_request)
9386
self._stub = None
9487
self._channel_pool.close()
9588

src/nipanel/_panel_not_found_error.py

Lines changed: 0 additions & 8 deletions
This file was deleted.

tests/unit/test_panel.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import grpc
2+
import pytest
23

4+
from tests.utils._fake_python_panel_servicer import FakePythonPanelServicer
35
from tests.utils._port_panel import PortPanel
46

57

@@ -32,3 +34,30 @@ def test___with_panel___set_value___gets_same_value(
3234

3335
# TODO: AB#3095681 - change asserted value to test_value
3436
assert panel.get_value("test_id") == "placeholder value"
37+
38+
39+
def test___new_panel___disconnect___raises_runtime_error(
40+
fake_python_panel_service: tuple[grpc.Server, int],
41+
) -> None:
42+
_, port = fake_python_panel_service
43+
panel = PortPanel(port, "my_panel", "path/to/script")
44+
45+
with pytest.raises(RuntimeError):
46+
panel.disconnect()
47+
48+
49+
def test___first_connect_fails___connect___gets_value(
50+
fake_python_panel_service: tuple[grpc.Server, int],
51+
) -> None:
52+
"""Test that panel.connect() will automatically retry once."""
53+
# Simulate a failure on the first connect attempt
54+
FakePythonPanelServicer.fail_next_connect()
55+
_, port = fake_python_panel_service
56+
panel = PortPanel(port, "my_panel", "path/to/script")
57+
58+
panel.connect()
59+
60+
panel.set_value("test_id", "test_value")
61+
# TODO: AB#3095681 - change asserted value to test_value
62+
assert panel.get_value("test_id") == "placeholder value"
63+
panel.disconnect()

tests/utils/_fake_python_panel_servicer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ class FakePythonPanelServicer(PythonPanelServiceServicer):
1818
"""Fake implementation of the PythonPanelServicer for testing."""
1919

2020
_values = {"test_value": any_pb2.Any()}
21+
_fail_next_connect = False
2122

2223
def Connect(self, request: ConnectRequest, context: Any) -> ConnectResponse: # noqa: N802
2324
"""Just a trivial implementation for testing."""
25+
if self._fail_next_connect:
26+
self._fail_next_connect = False
27+
raise ValueError("Simulate a failure to Connect.")
2428
return ConnectResponse()
2529

2630
def Disconnect( # noqa: N802
@@ -38,3 +42,8 @@ def SetValue(self, request: SetValueRequest, context: Any) -> SetValueResponse:
3842
"""Just a trivial implementation for testing."""
3943
self._values[request.value_id] = request.value
4044
return SetValueResponse()
45+
46+
@classmethod
47+
def fail_next_connect(cls) -> None:
48+
"""Set whether the Connect method should fail the next time it is called."""
49+
cls._fail_next_connect = True

0 commit comments

Comments
 (0)