diff --git a/protos/ni/pythonpanel/v1/python_panel_service.proto b/protos/ni/pythonpanel/v1/python_panel_service.proto index d131296..b2c4d03 100644 --- a/protos/ni/pythonpanel/v1/python_panel_service.proto +++ b/protos/ni/pythonpanel/v1/python_panel_service.proto @@ -16,7 +16,7 @@ option ruby_package = "NI::PythonPanel::V1"; // Service interface for interacting with python panels service PythonPanelService { - // Enumerate the panels available in the system + // Enumerate the panels available in the system, including information about the state of the panels and what values they have. // Status Codes for errors: rpc EnumeratePanels(EnumeratePanelsRequest) returns (EnumeratePanelsResponse); @@ -46,9 +46,20 @@ service PythonPanelService { message EnumeratePanelsRequest { } +message PanelInformation { + // Unique ID of the panel + string panel_id = 1; + + // Is the panel currently open? + bool is_open = 2; + + // IDs of all of the values associated with the panel + repeated string value_ids = 3; +} + message EnumeratePanelsResponse { // The list of panels available in the system - repeated string panel_ids = 1; + repeated PanelInformation panels = 1; } message OpenPanelRequest { diff --git a/src/ni/pythonpanel/v1/python_panel_service_pb2.py b/src/ni/pythonpanel/v1/python_panel_service_pb2.py index 79fce9c..dc05d26 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2.py +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2.py @@ -14,7 +14,7 @@ from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,ni/pythonpanel/v1/python_panel_service.proto\x12\x11ni.pythonpanel.v1\x1a\x19google/protobuf/any.proto\"\x18\n\x16\x45numeratePanelsRequest\",\n\x17\x45numeratePanelsResponse\x12\x11\n\tpanel_ids\x18\x01 \x03(\t\"7\n\x10OpenPanelRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x11\n\tpanel_uri\x18\x02 \x01(\t\"\x13\n\x11OpenPanelResponse\"5\n\x0fGetValueRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x10\n\x08value_id\x18\x02 \x01(\t\"7\n\x10GetValueResponse\x12#\n\x05value\x18\x01 \x01(\x0b\x32\x14.google.protobuf.Any\"Z\n\x0fSetValueRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x10\n\x08value_id\x18\x02 \x01(\t\x12#\n\x05value\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any\"\x12\n\x10SetValueResponse\"4\n\x11\x43losePanelRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\r\n\x05reset\x18\x02 \x01(\x08\"\x14\n\x12\x43losePanelResponse2\xdb\x03\n\x12PythonPanelService\x12h\n\x0f\x45numeratePanels\x12).ni.pythonpanel.v1.EnumeratePanelsRequest\x1a*.ni.pythonpanel.v1.EnumeratePanelsResponse\x12V\n\tOpenPanel\x12#.ni.pythonpanel.v1.OpenPanelRequest\x1a$.ni.pythonpanel.v1.OpenPanelResponse\x12S\n\x08GetValue\x12\".ni.pythonpanel.v1.GetValueRequest\x1a#.ni.pythonpanel.v1.GetValueResponse\x12S\n\x08SetValue\x12\".ni.pythonpanel.v1.SetValueRequest\x1a#.ni.pythonpanel.v1.SetValueResponse\x12Y\n\nClosePanel\x12$.ni.pythonpanel.v1.ClosePanelRequest\x1a%.ni.pythonpanel.v1.ClosePanelResponseB\x9a\x01\n\x15\x63om.ni.pythonpanel.v1B\x17PythonPanelServiceProtoP\x01Z\rpythonpanelv1\xf8\x01\x01\xa2\x02\x04NIPP\xaa\x02\"NationalInstruments.PythonPanel.V1\xca\x02\x11NI\\PythonPanel\\V1\xea\x02\x13NI::PythonPanel::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,ni/pythonpanel/v1/python_panel_service.proto\x12\x11ni.pythonpanel.v1\x1a\x19google/protobuf/any.proto\"\x18\n\x16\x45numeratePanelsRequest\"H\n\x10PanelInformation\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x0f\n\x07is_open\x18\x02 \x01(\x08\x12\x11\n\tvalue_ids\x18\x03 \x03(\t\"N\n\x17\x45numeratePanelsResponse\x12\x33\n\x06panels\x18\x01 \x03(\x0b\x32#.ni.pythonpanel.v1.PanelInformation\"7\n\x10OpenPanelRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x11\n\tpanel_uri\x18\x02 \x01(\t\"\x13\n\x11OpenPanelResponse\"5\n\x0fGetValueRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x10\n\x08value_id\x18\x02 \x01(\t\"7\n\x10GetValueResponse\x12#\n\x05value\x18\x01 \x01(\x0b\x32\x14.google.protobuf.Any\"Z\n\x0fSetValueRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x10\n\x08value_id\x18\x02 \x01(\t\x12#\n\x05value\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any\"\x12\n\x10SetValueResponse\"4\n\x11\x43losePanelRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\r\n\x05reset\x18\x02 \x01(\x08\"\x14\n\x12\x43losePanelResponse2\xdb\x03\n\x12PythonPanelService\x12h\n\x0f\x45numeratePanels\x12).ni.pythonpanel.v1.EnumeratePanelsRequest\x1a*.ni.pythonpanel.v1.EnumeratePanelsResponse\x12V\n\tOpenPanel\x12#.ni.pythonpanel.v1.OpenPanelRequest\x1a$.ni.pythonpanel.v1.OpenPanelResponse\x12S\n\x08GetValue\x12\".ni.pythonpanel.v1.GetValueRequest\x1a#.ni.pythonpanel.v1.GetValueResponse\x12S\n\x08SetValue\x12\".ni.pythonpanel.v1.SetValueRequest\x1a#.ni.pythonpanel.v1.SetValueResponse\x12Y\n\nClosePanel\x12$.ni.pythonpanel.v1.ClosePanelRequest\x1a%.ni.pythonpanel.v1.ClosePanelResponseB\x9a\x01\n\x15\x63om.ni.pythonpanel.v1B\x17PythonPanelServiceProtoP\x01Z\rpythonpanelv1\xf8\x01\x01\xa2\x02\x04NIPP\xaa\x02\"NationalInstruments.PythonPanel.V1\xca\x02\x11NI\\PythonPanel\\V1\xea\x02\x13NI::PythonPanel::V1b\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ni.pythonpanel.v1.python_panel_service_pb2', globals()) @@ -24,24 +24,26 @@ DESCRIPTOR._serialized_options = b'\n\025com.ni.pythonpanel.v1B\027PythonPanelServiceProtoP\001Z\rpythonpanelv1\370\001\001\242\002\004NIPP\252\002\"NationalInstruments.PythonPanel.V1\312\002\021NI\\PythonPanel\\V1\352\002\023NI::PythonPanel::V1' _ENUMERATEPANELSREQUEST._serialized_start=94 _ENUMERATEPANELSREQUEST._serialized_end=118 - _ENUMERATEPANELSRESPONSE._serialized_start=120 - _ENUMERATEPANELSRESPONSE._serialized_end=164 - _OPENPANELREQUEST._serialized_start=166 - _OPENPANELREQUEST._serialized_end=221 - _OPENPANELRESPONSE._serialized_start=223 - _OPENPANELRESPONSE._serialized_end=242 - _GETVALUEREQUEST._serialized_start=244 - _GETVALUEREQUEST._serialized_end=297 - _GETVALUERESPONSE._serialized_start=299 - _GETVALUERESPONSE._serialized_end=354 - _SETVALUEREQUEST._serialized_start=356 - _SETVALUEREQUEST._serialized_end=446 - _SETVALUERESPONSE._serialized_start=448 - _SETVALUERESPONSE._serialized_end=466 - _CLOSEPANELREQUEST._serialized_start=468 - _CLOSEPANELREQUEST._serialized_end=520 - _CLOSEPANELRESPONSE._serialized_start=522 - _CLOSEPANELRESPONSE._serialized_end=542 - _PYTHONPANELSERVICE._serialized_start=545 - _PYTHONPANELSERVICE._serialized_end=1020 + _PANELINFORMATION._serialized_start=120 + _PANELINFORMATION._serialized_end=192 + _ENUMERATEPANELSRESPONSE._serialized_start=194 + _ENUMERATEPANELSRESPONSE._serialized_end=272 + _OPENPANELREQUEST._serialized_start=274 + _OPENPANELREQUEST._serialized_end=329 + _OPENPANELRESPONSE._serialized_start=331 + _OPENPANELRESPONSE._serialized_end=350 + _GETVALUEREQUEST._serialized_start=352 + _GETVALUEREQUEST._serialized_end=405 + _GETVALUERESPONSE._serialized_start=407 + _GETVALUERESPONSE._serialized_end=462 + _SETVALUEREQUEST._serialized_start=464 + _SETVALUEREQUEST._serialized_end=554 + _SETVALUERESPONSE._serialized_start=556 + _SETVALUERESPONSE._serialized_end=574 + _CLOSEPANELREQUEST._serialized_start=576 + _CLOSEPANELREQUEST._serialized_end=628 + _CLOSEPANELRESPONSE._serialized_start=630 + _CLOSEPANELRESPONSE._serialized_end=650 + _PYTHONPANELSERVICE._serialized_start=653 + _PYTHONPANELSERVICE._serialized_end=1128 # @@protoc_insertion_point(module_scope) diff --git a/src/ni/pythonpanel/v1/python_panel_service_pb2.pyi b/src/ni/pythonpanel/v1/python_panel_service_pb2.pyi index 161d039..74a5036 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2.pyi +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2.pyi @@ -23,21 +23,47 @@ class EnumeratePanelsRequest(google.protobuf.message.Message): global___EnumeratePanelsRequest = EnumeratePanelsRequest +@typing.final +class PanelInformation(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PANEL_ID_FIELD_NUMBER: builtins.int + IS_OPEN_FIELD_NUMBER: builtins.int + VALUE_IDS_FIELD_NUMBER: builtins.int + panel_id: builtins.str + """Unique ID of the panel""" + is_open: builtins.bool + """Is the panel currently open?""" + @property + def value_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """IDs of all of the values associated with the panel""" + + def __init__( + self, + *, + panel_id: builtins.str = ..., + is_open: builtins.bool = ..., + value_ids: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["is_open", b"is_open", "panel_id", b"panel_id", "value_ids", b"value_ids"]) -> None: ... + +global___PanelInformation = PanelInformation + @typing.final class EnumeratePanelsResponse(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor - PANEL_IDS_FIELD_NUMBER: builtins.int + PANELS_FIELD_NUMBER: builtins.int @property - def panel_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + def panels(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PanelInformation]: """The list of panels available in the system""" def __init__( self, *, - panel_ids: collections.abc.Iterable[builtins.str] | None = ..., + panels: collections.abc.Iterable[global___PanelInformation] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["panel_ids", b"panel_ids"]) -> None: ... + def ClearField(self, field_name: typing.Literal["panels", b"panels"]) -> None: ... global___EnumeratePanelsResponse = EnumeratePanelsResponse diff --git a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py index f3bfc2e..69689d4 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py @@ -47,7 +47,7 @@ class PythonPanelServiceServicer(object): """ def EnumeratePanels(self, request, context): - """Enumerate the panels available in the system + """Enumerate the panels available in the system, including information about the state of the panels and what values they have. Status Codes for errors: """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) diff --git a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi index d6a16b5..98fb91b 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi @@ -25,7 +25,7 @@ class PythonPanelServiceStub: ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsRequest, ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse, ] - """Enumerate the panels available in the system + """Enumerate the panels available in the system, including information about the state of the panels and what values they have. Status Codes for errors: """ @@ -74,7 +74,7 @@ class PythonPanelServiceAsyncStub: ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsRequest, ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse, ] - """Enumerate the panels available in the system + """Enumerate the panels available in the system, including information about the state of the panels and what values they have. Status Codes for errors: """ @@ -125,7 +125,7 @@ class PythonPanelServiceServicer(metaclass=abc.ABCMeta): request: ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsRequest, context: _ServicerContext, ) -> typing.Union[ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse, collections.abc.Awaitable[ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse]]: - """Enumerate the panels available in the system + """Enumerate the panels available in the system, including information about the state of the panels and what values they have. Status Codes for errors: """ diff --git a/src/nipanel/_panel.py b/src/nipanel/_panel.py index 5620519..8947d91 100644 --- a/src/nipanel/_panel.py +++ b/src/nipanel/_panel.py @@ -1,6 +1,9 @@ from __future__ import annotations +import sys from abc import ABC +from types import TracebackType +from typing import TYPE_CHECKING import grpc from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient @@ -8,6 +11,12 @@ from nipanel._panel_value_accessor import PanelValueAccessor +if TYPE_CHECKING: + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + class Panel(PanelValueAccessor, ABC): """This class allows you to open a panel and specify values for its controls.""" @@ -41,6 +50,21 @@ def panel_uri(self) -> str: """Read-only accessor for the panel URI.""" return self._panel_uri + def __enter__(self) -> Self: + """Enter the runtime context related to this object.""" + self.open_panel() + return self + + def __exit__( + self, + exctype: type[BaseException] | None, + excinst: BaseException | None, + exctb: TracebackType | None, + ) -> bool | None: + """Exit the runtime context related to this object.""" + self.close_panel(reset=False) + return None + def open_panel(self) -> None: """Open the panel.""" self._panel_client.open_panel(self._panel_id, self._panel_uri) diff --git a/src/nipanel/_panel_client.py b/src/nipanel/_panel_client.py index 48d8fa6..8fbd9f7 100644 --- a/src/nipanel/_panel_client.py +++ b/src/nipanel/_panel_client.py @@ -79,17 +79,18 @@ def close_panel(self, panel_id: str, reset: bool) -> None: close_panel_request = ClosePanelRequest(panel_id=panel_id, reset=reset) self._invoke_with_retry(self._get_stub().ClosePanel, close_panel_request) - def enumerate_panels(self) -> list[str]: + def enumerate_panels(self) -> dict[str, tuple[bool, list[str]]]: """Enumerate all available panels. Returns: - A list of panel IDs. + A dictionary mapping panel IDs to a tuple containing a boolean indicating if the panel + is open and a list of value IDs associated with the panel. """ enumerate_panels_request = EnumeratePanelsRequest() response = self._invoke_with_retry( self._get_stub().EnumeratePanels, enumerate_panels_request ) - return list(response.panel_ids) + return {panel.panel_id: (panel.is_open, list(panel.value_ids)) for panel in response.panels} def set_value(self, panel_id: str, value_id: str, value: object) -> None: """Set the value for the control with value_id. diff --git a/tests/unit/test_panel_client.py b/tests/unit/test_panel_client.py index a6d00f1..4b7d2d9 100644 --- a/tests/unit/test_panel_client.py +++ b/tests/unit/test_panel_client.py @@ -7,7 +7,7 @@ def test___enumerate_is_empty(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) - assert client.enumerate_panels() == [] + assert client.enumerate_panels() == {} def test___open_panels___enumerate_has_panels(fake_panel_channel: grpc.Channel) -> None: @@ -16,7 +16,10 @@ def test___open_panels___enumerate_has_panels(fake_panel_channel: grpc.Channel) client.open_panel("panel1", "uri1") client.open_panel("panel2", "uri2") - assert client.enumerate_panels() == ["panel1", "panel2"] + assert client.enumerate_panels() == { + "panel1": (True, []), + "panel2": (True, []), + } def test___open_panels___close_panel_1_with_reset___enumerate_has_panel_2( @@ -28,7 +31,9 @@ def test___open_panels___close_panel_1_with_reset___enumerate_has_panel_2( client.close_panel("panel1", reset=True) - assert client.enumerate_panels() == ["panel2"] + assert client.enumerate_panels() == { + "panel2": (True, []), + } def test___open_panels___close_panel_1_without_reset___enumerate_has_both_panels( @@ -40,7 +45,10 @@ def test___open_panels___close_panel_1_without_reset___enumerate_has_both_panels client.close_panel("panel1", reset=False) - assert client.enumerate_panels() == ["panel1", "panel2"] + assert client.enumerate_panels() == { + "panel1": (False, []), + "panel2": (True, []), + } def test___get_unset_value_raises_exception(fake_panel_channel: grpc.Channel) -> None: @@ -50,6 +58,16 @@ def test___get_unset_value_raises_exception(fake_panel_channel: grpc.Channel) -> client.get_value("panel1", "unset_id") +def test___set_value___enumerate_panels_shows_value( + fake_panel_channel: grpc.Channel, +) -> None: + client = create_panel_client(fake_panel_channel) + + client.set_value("panel1", "val1", "value1") + + assert client.enumerate_panels() == {"panel1": (False, ["val1"])} + + def test___set_value___gets_value(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) diff --git a/tests/unit/test_streamlit_panel.py b/tests/unit/test_streamlit_panel.py index f7813b8..586989a 100644 --- a/tests/unit/test_streamlit_panel.py +++ b/tests/unit/test_streamlit_panel.py @@ -119,9 +119,10 @@ def test___unopened_panel___set_value___sets_value( value_id = "test_id" string_value = "test_value" - panel.set_value(value_id, string_value) + assert panel._panel_client.enumerate_panels() == {"my_panel": (False, [value_id])} + def test___unopened_panel___get_unset_value___raises_exception( fake_panel_channel: grpc.Channel, @@ -192,3 +193,47 @@ def test___unsupported_type___set_value___raises( value_id = "test_id" with pytest.raises(TypeError): panel.set_value(value_id, value_payload) + + +def test___open_panel___panel_is_open_and_in_memory( + fake_panel_channel: grpc.Channel, +) -> None: + panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) + assert not is_panel_in_memory(panel) + + panel.open_panel() + + assert is_panel_in_memory(panel) + assert is_panel_open(panel) + + +def test___with_panel___opens_and_closes_panel( + fake_panel_channel: grpc.Channel, +) -> None: + panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) + + with panel: + assert is_panel_in_memory(panel) + assert is_panel_open(panel) + + assert is_panel_in_memory(panel) + assert not is_panel_open(panel) + + +def test___with_panel___set_value___gets_same_value( + fake_panel_channel: grpc.Channel, +) -> None: + with StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) as panel: + value_id = "test_id" + string_value = "test_value" + panel.set_value(value_id, string_value) + + assert panel.get_value(value_id) == string_value + + +def is_panel_in_memory(panel: StreamlitPanel) -> bool: + return panel.panel_id in panel._panel_client.enumerate_panels().keys() + + +def is_panel_open(panel: StreamlitPanel) -> bool: + return panel._panel_client.enumerate_panels()[panel.panel_id][0] diff --git a/tests/utils/_fake_python_panel_servicer.py b/tests/utils/_fake_python_panel_servicer.py index 48d4edb..2f20f04 100644 --- a/tests/utils/_fake_python_panel_servicer.py +++ b/tests/utils/_fake_python_panel_servicer.py @@ -8,6 +8,7 @@ ClosePanelResponse, EnumeratePanelsRequest, EnumeratePanelsResponse, + PanelInformation, GetValueRequest, GetValueResponse, SetValueRequest, @@ -21,8 +22,9 @@ class FakePythonPanelServicer(PythonPanelServiceServicer): def __init__(self) -> None: """Initialize the fake PythonPanelServicer.""" - self._values: dict[str, Any] = {} self._panel_ids: list[str] = [] + self._panel_is_open: dict[str, bool] = {} + self._panel_value_ids: dict[str, dict[str, Any]] = {} self._fail_next_open_panel = False def OpenPanel(self, request: OpenPanelRequest, context: Any) -> OpenPanelResponse: # noqa: N802 @@ -30,34 +32,63 @@ def OpenPanel(self, request: OpenPanelRequest, context: Any) -> OpenPanelRespons if self._fail_next_open_panel: self._fail_next_open_panel = False context.abort(grpc.StatusCode.UNAVAILABLE, "Simulated failure") - self._panel_ids.append(request.panel_id) + self._open_panel(request.panel_id) return OpenPanelResponse() def ClosePanel( # noqa: N802 self, request: ClosePanelRequest, context: Any ) -> ClosePanelResponse: """Trivial implementation for testing.""" - if request.reset: - self._panel_ids.remove(request.panel_id) - self._values.clear() + self._close_panel(request.reset, request.panel_id) return ClosePanelResponse() def EnumeratePanels( # noqa: N802 self, request: EnumeratePanelsRequest, context: Any ) -> EnumeratePanelsResponse: """Trivial implementation for testing.""" - return EnumeratePanelsResponse(panel_ids=self._panel_ids) + response = EnumeratePanelsResponse() + for panel_id in self._panel_ids: + panel = PanelInformation( + panel_id=panel_id, + is_open=self._panel_is_open[panel_id], + value_ids=self._panel_value_ids[panel_id], + ) + response.panels.append(panel) + return response def GetValue(self, request: GetValueRequest, context: Any) -> GetValueResponse: # noqa: N802 """Trivial implementation for testing.""" - value = self._values[request.value_id] + value = self._panel_value_ids[request.panel_id][request.value_id] return GetValueResponse(value=value) def SetValue(self, request: SetValueRequest, context: Any) -> SetValueResponse: # noqa: N802 """Trivial implementation for testing.""" - self._values[request.value_id] = request.value + self._init_panel(request.panel_id) + self._panel_value_ids[request.panel_id][request.value_id] = request.value return SetValueResponse() def fail_next_open_panel(self) -> None: """Set whether the OpenPanel method should fail the next time it is called.""" self._fail_next_open_panel = True + + def _init_panel(self, panel_id: str) -> None: + if panel_id not in self._panel_ids: + self._panel_ids.append(panel_id) + self._panel_is_open[panel_id] = False + self._panel_value_ids[panel_id] = {} + + def _open_panel(self, panel_id: str) -> None: + if panel_id not in self._panel_ids: + self._panel_ids.append(panel_id) + self._panel_is_open[panel_id] = True + self._panel_value_ids[panel_id] = {} + else: + self._panel_is_open[panel_id] = True + + def _close_panel(self, reset: bool, panel_id: str) -> None: + if reset: + self._panel_ids.remove(panel_id) + self._panel_is_open.pop(panel_id) + self._panel_value_ids.pop(panel_id) + else: + self._panel_is_open[panel_id] = False