From 18f6bdaacf664a46c4d47d5ae3abe3a15cbc3042 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Thu, 29 May 2025 12:58:04 -0500 Subject: [PATCH 1/6] expand enumerate_panels, add enter and exit, and add clear_value --- .../pythonpanel/v1/python_panel_service.proto | 31 ++++++++- .../v1/python_panel_service_pb2.py | 48 +++++++------ .../v1/python_panel_service_pb2.pyi | 64 +++++++++++++++-- .../v1/python_panel_service_pb2_grpc.py | 37 +++++++++- .../v1/python_panel_service_pb2_grpc.pyi | 32 ++++++++- src/nipanel/_panel.py | 33 +++++++++ src/nipanel/_panel_client.py | 68 +++++++++++++++++-- src/nipanel/_panel_value_accessor.py | 27 ++++++++ tests/unit/test_panel_client.py | 60 +++++++++++++--- tests/unit/test_streamlit_panel.py | 61 ++++++++++++++++- tests/utils/_fake_python_panel_servicer.py | 57 +++++++++++++--- 11 files changed, 463 insertions(+), 55 deletions(-) diff --git a/protos/ni/pythonpanel/v1/python_panel_service.proto b/protos/ni/pythonpanel/v1/python_panel_service.proto index d131296..91f311f 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); @@ -37,6 +37,10 @@ service PythonPanelService { // - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. rpc SetValue(SetValueRequest) returns (SetValueResponse); + // Clear a value for a control on the panel + // - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. + rpc ClearValue(ClearValueRequest) returns (ClearValueResponse); + // Close a panel // Status Codes for errors: // - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. @@ -46,9 +50,21 @@ 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 { @@ -89,6 +105,17 @@ message SetValueRequest { message SetValueResponse { } +message ClearValueRequest { + // Unique ID of the panel + string panel_id = 1; + + // Unique ID of the value + string value_id = 2; +} + +message ClearValueResponse { +} + message ClosePanelRequest { // Unique ID of the panel string panel_id = 1; diff --git a/src/ni/pythonpanel/v1/python_panel_service_pb2.py b/src/ni/pythonpanel/v1/python_panel_service_pb2.py index 79fce9c..6851124 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\"7\n\x11\x43learValueRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x10\n\x08value_id\x18\x02 \x01(\t\"\x14\n\x12\x43learValueResponse\"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\xb6\x04\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\nClearValue\x12$.ni.pythonpanel.v1.ClearValueRequest\x1a%.ni.pythonpanel.v1.ClearValueResponse\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,30 @@ 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 + _CLEARVALUEREQUEST._serialized_start=576 + _CLEARVALUEREQUEST._serialized_end=631 + _CLEARVALUERESPONSE._serialized_start=633 + _CLEARVALUERESPONSE._serialized_end=653 + _CLOSEPANELREQUEST._serialized_start=655 + _CLOSEPANELREQUEST._serialized_end=707 + _CLOSEPANELRESPONSE._serialized_start=709 + _CLOSEPANELRESPONSE._serialized_end=729 + _PYTHONPANELSERVICE._serialized_start=732 + _PYTHONPANELSERVICE._serialized_end=1298 # @@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..6b7b706 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 @@ -147,6 +173,36 @@ class SetValueResponse(google.protobuf.message.Message): global___SetValueResponse = SetValueResponse +@typing.final +class ClearValueRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PANEL_ID_FIELD_NUMBER: builtins.int + VALUE_ID_FIELD_NUMBER: builtins.int + panel_id: builtins.str + """Unique ID of the panel""" + value_id: builtins.str + """Unique ID of the value""" + def __init__( + self, + *, + panel_id: builtins.str = ..., + value_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["panel_id", b"panel_id", "value_id", b"value_id"]) -> None: ... + +global___ClearValueRequest = ClearValueRequest + +@typing.final +class ClearValueResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___ClearValueResponse = ClearValueResponse + @typing.final class ClosePanelRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor 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..d7335d4 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py @@ -35,6 +35,11 @@ def __init__(self, channel): request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueRequest.SerializeToString, response_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.FromString, ) + self.ClearValue = channel.unary_unary( + '/ni.pythonpanel.v1.PythonPanelService/ClearValue', + request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueRequest.SerializeToString, + response_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueResponse.FromString, + ) self.ClosePanel = channel.unary_unary( '/ni.pythonpanel.v1.PythonPanelService/ClosePanel', request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClosePanelRequest.SerializeToString, @@ -47,7 +52,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) @@ -83,6 +88,14 @@ def SetValue(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def ClearValue(self, request, context): + """Clear a value for a control on the panel + - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def ClosePanel(self, request, context): """Close a panel Status Codes for errors: @@ -115,6 +128,11 @@ def add_PythonPanelServiceServicer_to_server(servicer, server): request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueRequest.FromString, response_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.SerializeToString, ), + 'ClearValue': grpc.unary_unary_rpc_method_handler( + servicer.ClearValue, + request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueRequest.FromString, + response_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueResponse.SerializeToString, + ), 'ClosePanel': grpc.unary_unary_rpc_method_handler( servicer.ClosePanel, request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClosePanelRequest.FromString, @@ -199,6 +217,23 @@ def SetValue(request, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + @staticmethod + def ClearValue(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/ni.pythonpanel.v1.PythonPanelService/ClearValue', + ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueRequest.SerializeToString, + ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + @staticmethod def ClosePanel(request, target, 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..447c9fe 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: """ @@ -58,6 +58,14 @@ class PythonPanelServiceStub: - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. """ + ClearValue: grpc.UnaryUnaryMultiCallable[ + ni.pythonpanel.v1.python_panel_service_pb2.ClearValueRequest, + ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse, + ] + """Clear a value for a control on the panel + - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. + """ + ClosePanel: grpc.UnaryUnaryMultiCallable[ ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelRequest, ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelResponse, @@ -74,7 +82,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: """ @@ -107,6 +115,14 @@ class PythonPanelServiceAsyncStub: - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. """ + ClearValue: grpc.aio.UnaryUnaryMultiCallable[ + ni.pythonpanel.v1.python_panel_service_pb2.ClearValueRequest, + ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse, + ] + """Clear a value for a control on the panel + - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. + """ + ClosePanel: grpc.aio.UnaryUnaryMultiCallable[ ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelRequest, ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelResponse, @@ -125,7 +141,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: """ @@ -164,6 +180,16 @@ class PythonPanelServiceServicer(metaclass=abc.ABCMeta): - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. """ + @abc.abstractmethod + def ClearValue( + self, + request: ni.pythonpanel.v1.python_panel_service_pb2.ClearValueRequest, + context: _ServicerContext, + ) -> typing.Union[ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse, collections.abc.Awaitable[ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse]]: + """Clear a value for a control on the panel + - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. + """ + @abc.abstractmethod def ClosePanel( self, diff --git a/src/nipanel/_panel.py b/src/nipanel/_panel.py index 5620519..b40f276 100644 --- a/src/nipanel/_panel.py +++ b/src/nipanel/_panel.py @@ -2,12 +2,22 @@ from abc import ABC +import sys +from types import TracebackType +from typing import TYPE_CHECKING + import grpc from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool 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 +51,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) @@ -52,3 +77,11 @@ def close_panel(self, reset: bool) -> None: reset: Whether to reset all storage associated with the panel. """ self._panel_client.close_panel(self._panel_id, reset=reset) + + def is_open(self) -> bool: + """Check if the panel is open.""" + return self._panel_client.is_panel_open(self._panel_id) + + def is_in_memory(self) -> bool: + """Check if the panel is in memory.""" + return self._panel_client.is_panel_in_memory(self._panel_id) diff --git a/src/nipanel/_panel_client.py b/src/nipanel/_panel_client.py index 48d8fa6..c8d87b9 100644 --- a/src/nipanel/_panel_client.py +++ b/src/nipanel/_panel_client.py @@ -11,8 +11,10 @@ OpenPanelRequest, ClosePanelRequest, EnumeratePanelsRequest, + PanelInformation, GetValueRequest, SetValueRequest, + ClearValueRequest, ) from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient @@ -79,17 +81,59 @@ 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]: - """Enumerate all available panels. - - Returns: - A list of panel IDs. - """ + def _enumerate_panels(self) -> list[PanelInformation]: enumerate_panels_request = EnumeratePanelsRequest() response = self._invoke_with_retry( self._get_stub().EnumeratePanels, enumerate_panels_request ) - return list(response.panel_ids) + return list(response.panels) + + def get_panel_ids(self) -> list[str]: + """Get the IDs of all panels in memory. + + Returns: + A list of panel IDs. + """ + panels = self._enumerate_panels() + return [panel.panel_id for panel in panels] + + def is_panel_in_memory(self, panel_id: str) -> bool: + """Check if the panel is in memory. + + Args: + panel_id: The ID of the panel. + + Returns: + True if the panel is in memory, False otherwise. + """ + panels = self._enumerate_panels() + return any(panel.panel_id == panel_id for panel in panels) + + def is_panel_open(self, panel_id: str) -> bool: + """Check if the panel is open. + + Args: + panel_id: The ID of the panel. + + Returns: + True if the panel is open, False otherwise. + """ + panels = self._enumerate_panels() + panel = next((panel for panel in panels if panel.panel_id == panel_id), None) + return panel.is_open if panel else False + + def get_value_ids(self, panel_id: str) -> list[str]: + """Get the IDs of all the values that have been set for the panel. + + Args: + panel_id: The ID of the panel. + + Returns: + A list of value IDs for the panel. + """ + panels = self._enumerate_panels() + panel = next((panel for panel in panels if panel.panel_id == panel_id), None) + return list(panel.value_ids) if panel else [] def set_value(self, panel_id: str, value_id: str, value: object) -> None: """Set the value for the control with value_id. @@ -118,6 +162,16 @@ def get_value(self, panel_id: str, value_id: str) -> object: the_value = from_any(response.value) return the_value + def clear_value(self, panel_id: str, value_id: str) -> None: + """Clear the value for the control with value_id. + + Args: + panel_id: The ID of the panel. + value_id: The ID of the control. + """ + clear_value_request = ClearValueRequest(panel_id=panel_id, value_id=value_id) + self._invoke_with_retry(self._get_stub().ClearValue, clear_value_request) + def _get_stub(self) -> PythonPanelServiceStub: if self._stub is None: if self._grpc_channel is not None: diff --git a/src/nipanel/_panel_value_accessor.py b/src/nipanel/_panel_value_accessor.py index 6059d03..d78326a 100644 --- a/src/nipanel/_panel_value_accessor.py +++ b/src/nipanel/_panel_value_accessor.py @@ -58,3 +58,30 @@ def set_value(self, value_id: str, value: object) -> None: value: The value """ self._panel_client.set_value(self._panel_id, value_id, value) + + def clear_value(self, value_id: str) -> None: + """Clear the value for a control on the panel. + + Args: + value_id: The id of the value + """ + self._panel_client.clear_value(self._panel_id, value_id) + + def has_value(self, value_id: str) -> bool: + """Check if the panel has a value with the given ID. + + Args: + value_id: The id of the value + + Returns: + True if the panel has the value, False otherwise. + """ + return value_id in self.get_value_ids() + + def get_value_ids(self) -> list[str]: + """Get the value IDs that have been set for the panel. + + Returns: + A list of value IDs for the panel. + """ + return self._panel_client.get_value_ids(self._panel_id) diff --git a/tests/unit/test_panel_client.py b/tests/unit/test_panel_client.py index a6d00f1..3ec68d5 100644 --- a/tests/unit/test_panel_client.py +++ b/tests/unit/test_panel_client.py @@ -4,22 +4,26 @@ from nipanel._panel_client import PanelClient -def test___enumerate_is_empty(fake_panel_channel: grpc.Channel) -> None: +def test___panel_ids_is_empty(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) - assert client.enumerate_panels() == [] + assert client.get_panel_ids() == [] -def test___open_panels___enumerate_has_panels(fake_panel_channel: grpc.Channel) -> None: +def test___open_panels___both_panels_open_and_in_memory(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) client.open_panel("panel1", "uri1") client.open_panel("panel2", "uri2") - assert client.enumerate_panels() == ["panel1", "panel2"] + assert client.get_panel_ids() == ["panel1", "panel2"] + assert client.is_panel_in_memory("panel1") + assert client.is_panel_in_memory("panel2") + assert client.is_panel_open("panel1") + assert client.is_panel_open("panel2") -def test___open_panels___close_panel_1_with_reset___enumerate_has_panel_2( +def test___open_panels___close_panel_1_with_reset___panel_1_not_in_memory( fake_panel_channel: grpc.Channel, ) -> None: client = create_panel_client(fake_panel_channel) @@ -28,10 +32,13 @@ 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.get_panel_ids() == ["panel2"] + assert not client.is_panel_in_memory("panel1") + assert client.is_panel_in_memory("panel2") + assert client.is_panel_open("panel2") -def test___open_panels___close_panel_1_without_reset___enumerate_has_both_panels( +def test___open_panels___close_panel_1_without_reset___both_panels_in_memory( fake_panel_channel: grpc.Channel, ) -> None: client = create_panel_client(fake_panel_channel) @@ -40,7 +47,11 @@ 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.get_panel_ids() == ["panel1", "panel2"] + assert client.is_panel_in_memory("panel1") + assert client.is_panel_in_memory("panel2") + assert not client.is_panel_open("panel1") + assert client.is_panel_open("panel2") def test___get_unset_value_raises_exception(fake_panel_channel: grpc.Channel) -> None: @@ -50,6 +61,39 @@ def test___get_unset_value_raises_exception(fake_panel_channel: grpc.Channel) -> client.get_value("panel1", "unset_id") +def test___set_value___get_value_ids_has_value( + fake_panel_channel: grpc.Channel, +) -> None: + client = create_panel_client(fake_panel_channel) + + client.set_value("panel1", "val1", "value1") + + assert client.get_value_ids("panel1") == ["val1"] + + +def test___set_value___clear_value___get_value_ids_empty( + fake_panel_channel: grpc.Channel, +) -> None: + client = create_panel_client(fake_panel_channel) + client.set_value("panel1", "val1", "value1") + + client.clear_value("panel1", "val1") + + assert client.get_value_ids("panel1") == [] + + +def test___set_values___clear_value_2___get_value_ids_has_value_1( + fake_panel_channel: grpc.Channel, +) -> None: + client = create_panel_client(fake_panel_channel) + client.set_value("panel1", "val1", "value1") + client.set_value("panel1", "val2", "value2") + + client.clear_value("panel1", "val2") + + assert client.get_value_ids("panel1") == ["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..9cf3776 100644 --- a/tests/unit/test_streamlit_panel.py +++ b/tests/unit/test_streamlit_panel.py @@ -23,6 +23,19 @@ def test___different_panels___have_different_panel_ids_and_uris() -> None: assert panel1._panel_client != panel2._panel_client +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 panel.is_open() + assert not panel.is_in_memory() + + panel.open_panel() + + assert panel.is_open() + assert panel.is_in_memory() + + def test___opened_panel___set_value___gets_same_value( fake_panel_channel: grpc.Channel, ) -> None: @@ -72,9 +85,12 @@ def test___opened_panel_with_value___close_without_reset___gets_value( value_id = "test_id" string_value = "test_value" panel.set_value(value_id, string_value) + assert panel.is_open() panel.close_panel(reset=False) + assert panel.is_in_memory() + assert not panel.is_open() assert panel.get_value(value_id) == string_value @@ -86,9 +102,11 @@ def test___opened_panel_with_value___close_with_reset___get_throws( value_id = "test_id" string_value = "test_value" panel.set_value(value_id, string_value) + assert panel.is_open() panel.close_panel(reset=True) + assert not panel.is_in_memory() with pytest.raises(grpc.RpcError): panel.get_value(value_id) @@ -111,7 +129,7 @@ def test___first_open_panel_fails___open_panel___gets_value( assert panel.get_value(value_id) == string_value -def test___unopened_panel___set_value___sets_value( +def test___unopened_panel___set_value___has_value( fake_panel_channel: grpc.Channel, ) -> None: """Test that set_value() succeeds before the user opens the panel.""" @@ -119,9 +137,24 @@ 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.has_value(value_id) + +def test___set_value___clear_value___does_not_have_value( + fake_panel_channel: grpc.Channel, +) -> None: + """Test that set_value() succeeds before the user opens the panel.""" + panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) + value_id = "test_id" + string_value = "test_value" panel.set_value(value_id, string_value) + panel.clear_value(value_id) + + assert not panel.has_value(value_id) + def test___unopened_panel___get_unset_value___raises_exception( fake_panel_channel: grpc.Channel, @@ -192,3 +225,29 @@ def test___unsupported_type___set_value___raises( value_id = "test_id" with pytest.raises(TypeError): panel.set_value(value_id, value_payload) + + +def test___with_panel___opens_and_closes_panel( + fake_panel_channel: grpc.Channel, +) -> None: + """Test that using the panel in a with statement opens and closes it.""" + panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) + + with panel: + assert panel.is_open() + assert panel.is_in_memory() + + assert not panel.is_open() + assert panel.is_in_memory() + + +def test___with_panel___set_value___gets_same_value( + fake_panel_channel: grpc.Channel, +) -> None: + """Test that using the panel in a with statement allows setting and getting values.""" + 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 diff --git a/tests/utils/_fake_python_panel_servicer.py b/tests/utils/_fake_python_panel_servicer.py index 48d4edb..8c94b7e 100644 --- a/tests/utils/_fake_python_panel_servicer.py +++ b/tests/utils/_fake_python_panel_servicer.py @@ -8,10 +8,13 @@ ClosePanelResponse, EnumeratePanelsRequest, EnumeratePanelsResponse, + PanelInformation, GetValueRequest, GetValueResponse, SetValueRequest, SetValueResponse, + ClearValueRequest, + ClearValueResponse, ) from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceServicer @@ -21,8 +24,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 +34,71 @@ 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 ClearValue( # noqa: N802 + self, request: ClearValueRequest, context: Any + ) -> ClearValueResponse: + """Trivial implementation for testing.""" + if request.panel_id in self._panel_value_ids: + self._panel_value_ids[request.panel_id].pop(request.value_id, None) + return ClearValueResponse() + 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 From 6403ed604ff9bb68a4da7022149c2fbdd3132100 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Thu, 29 May 2025 13:16:11 -0500 Subject: [PATCH 2/6] fix mypy --- src/nipanel/_panel.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nipanel/_panel.py b/src/nipanel/_panel.py index b40f276..a7220c7 100644 --- a/src/nipanel/_panel.py +++ b/src/nipanel/_panel.py @@ -1,8 +1,7 @@ from __future__ import annotations -from abc import ABC - import sys +from abc import ABC from types import TracebackType from typing import TYPE_CHECKING From c9e320ef048af67cec6abf8ea5490c91b227947c Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Thu, 29 May 2025 15:08:16 -0500 Subject: [PATCH 3/6] remove is_open and is_in_memory from public api, and make panel client thinner. --- .../pythonpanel/v1/python_panel_service.proto | 3 +- src/nipanel/_panel.py | 8 --- src/nipanel/_panel_client.py | 58 +++---------------- src/nipanel/_panel_value_accessor.py | 2 +- tests/unit/test_panel_client.py | 41 ++++++------- tests/unit/test_streamlit_panel.py | 46 ++++++++------- 6 files changed, 53 insertions(+), 105 deletions(-) diff --git a/protos/ni/pythonpanel/v1/python_panel_service.proto b/protos/ni/pythonpanel/v1/python_panel_service.proto index 91f311f..93a5bc7 100644 --- a/protos/ni/pythonpanel/v1/python_panel_service.proto +++ b/protos/ni/pythonpanel/v1/python_panel_service.proto @@ -50,8 +50,7 @@ service PythonPanelService { message EnumeratePanelsRequest { } -message PanelInformation -{ +message PanelInformation { // Unique ID of the panel string panel_id = 1; diff --git a/src/nipanel/_panel.py b/src/nipanel/_panel.py index a7220c7..8947d91 100644 --- a/src/nipanel/_panel.py +++ b/src/nipanel/_panel.py @@ -76,11 +76,3 @@ def close_panel(self, reset: bool) -> None: reset: Whether to reset all storage associated with the panel. """ self._panel_client.close_panel(self._panel_id, reset=reset) - - def is_open(self) -> bool: - """Check if the panel is open.""" - return self._panel_client.is_panel_open(self._panel_id) - - def is_in_memory(self) -> bool: - """Check if the panel is in memory.""" - return self._panel_client.is_panel_in_memory(self._panel_id) diff --git a/src/nipanel/_panel_client.py b/src/nipanel/_panel_client.py index c8d87b9..ecbf768 100644 --- a/src/nipanel/_panel_client.py +++ b/src/nipanel/_panel_client.py @@ -11,7 +11,6 @@ OpenPanelRequest, ClosePanelRequest, EnumeratePanelsRequest, - PanelInformation, GetValueRequest, SetValueRequest, ClearValueRequest, @@ -81,59 +80,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[PanelInformation]: + def enumerate_panels(self) -> dict[str, tuple[bool, list[str]]]: + """Enumerate all open panels. + + Returns: + 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.panels) - - def get_panel_ids(self) -> list[str]: - """Get the IDs of all panels in memory. - - Returns: - A list of panel IDs. - """ - panels = self._enumerate_panels() - return [panel.panel_id for panel in panels] - - def is_panel_in_memory(self, panel_id: str) -> bool: - """Check if the panel is in memory. - - Args: - panel_id: The ID of the panel. - - Returns: - True if the panel is in memory, False otherwise. - """ - panels = self._enumerate_panels() - return any(panel.panel_id == panel_id for panel in panels) - - def is_panel_open(self, panel_id: str) -> bool: - """Check if the panel is open. - - Args: - panel_id: The ID of the panel. - - Returns: - True if the panel is open, False otherwise. - """ - panels = self._enumerate_panels() - panel = next((panel for panel in panels if panel.panel_id == panel_id), None) - return panel.is_open if panel else False - - def get_value_ids(self, panel_id: str) -> list[str]: - """Get the IDs of all the values that have been set for the panel. - - Args: - panel_id: The ID of the panel. - - Returns: - A list of value IDs for the panel. - """ - panels = self._enumerate_panels() - panel = next((panel for panel in panels if panel.panel_id == panel_id), None) - return list(panel.value_ids) if panel else [] + 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/src/nipanel/_panel_value_accessor.py b/src/nipanel/_panel_value_accessor.py index d78326a..e78b937 100644 --- a/src/nipanel/_panel_value_accessor.py +++ b/src/nipanel/_panel_value_accessor.py @@ -84,4 +84,4 @@ def get_value_ids(self) -> list[str]: Returns: A list of value IDs for the panel. """ - return self._panel_client.get_value_ids(self._panel_id) + return self._panel_client.enumerate_panels()[self._panel_id][1] diff --git a/tests/unit/test_panel_client.py b/tests/unit/test_panel_client.py index 3ec68d5..4157ac7 100644 --- a/tests/unit/test_panel_client.py +++ b/tests/unit/test_panel_client.py @@ -4,10 +4,10 @@ from nipanel._panel_client import PanelClient -def test___panel_ids_is_empty(fake_panel_channel: grpc.Channel) -> None: +def test___enumerate_panels_is_empty(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) - assert client.get_panel_ids() == [] + assert client.enumerate_panels() == {} def test___open_panels___both_panels_open_and_in_memory(fake_panel_channel: grpc.Channel) -> None: @@ -16,11 +16,10 @@ def test___open_panels___both_panels_open_and_in_memory(fake_panel_channel: grpc client.open_panel("panel1", "uri1") client.open_panel("panel2", "uri2") - assert client.get_panel_ids() == ["panel1", "panel2"] - assert client.is_panel_in_memory("panel1") - assert client.is_panel_in_memory("panel2") - assert client.is_panel_open("panel1") - assert client.is_panel_open("panel2") + assert client.enumerate_panels() == { + "panel1": (True, []), + "panel2": (True, []), + } def test___open_panels___close_panel_1_with_reset___panel_1_not_in_memory( @@ -32,10 +31,9 @@ def test___open_panels___close_panel_1_with_reset___panel_1_not_in_memory( client.close_panel("panel1", reset=True) - assert client.get_panel_ids() == ["panel2"] - assert not client.is_panel_in_memory("panel1") - assert client.is_panel_in_memory("panel2") - assert client.is_panel_open("panel2") + assert client.enumerate_panels() == { + "panel2": (True, []), + } def test___open_panels___close_panel_1_without_reset___both_panels_in_memory( @@ -47,11 +45,10 @@ def test___open_panels___close_panel_1_without_reset___both_panels_in_memory( client.close_panel("panel1", reset=False) - assert client.get_panel_ids() == ["panel1", "panel2"] - assert client.is_panel_in_memory("panel1") - assert client.is_panel_in_memory("panel2") - assert not client.is_panel_open("panel1") - assert client.is_panel_open("panel2") + assert client.enumerate_panels() == { + "panel1": (False, []), + "panel2": (True, []), + } def test___get_unset_value_raises_exception(fake_panel_channel: grpc.Channel) -> None: @@ -61,17 +58,17 @@ def test___get_unset_value_raises_exception(fake_panel_channel: grpc.Channel) -> client.get_value("panel1", "unset_id") -def test___set_value___get_value_ids_has_value( +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.get_value_ids("panel1") == ["val1"] + assert client.enumerate_panels() == {"panel1": (False, ["val1"])} -def test___set_value___clear_value___get_value_ids_empty( +def test___set_value___clear_value___enumerate_panels_shows_no_value( fake_panel_channel: grpc.Channel, ) -> None: client = create_panel_client(fake_panel_channel) @@ -79,10 +76,10 @@ def test___set_value___clear_value___get_value_ids_empty( client.clear_value("panel1", "val1") - assert client.get_value_ids("panel1") == [] + assert client.enumerate_panels() == {"panel1": (False, [])} -def test___set_values___clear_value_2___get_value_ids_has_value_1( +def test___set_values___clear_value_2___enumerate_panels_has_value_1( fake_panel_channel: grpc.Channel, ) -> None: client = create_panel_client(fake_panel_channel) @@ -91,7 +88,7 @@ def test___set_values___clear_value_2___get_value_ids_has_value_1( client.clear_value("panel1", "val2") - assert client.get_value_ids("panel1") == ["val1"] + assert client.enumerate_panels() == {"panel1": (False, ["val1"])} def test___set_value___gets_value(fake_panel_channel: grpc.Channel) -> None: diff --git a/tests/unit/test_streamlit_panel.py b/tests/unit/test_streamlit_panel.py index 9cf3776..5561dac 100644 --- a/tests/unit/test_streamlit_panel.py +++ b/tests/unit/test_streamlit_panel.py @@ -23,19 +23,6 @@ def test___different_panels___have_different_panel_ids_and_uris() -> None: assert panel1._panel_client != panel2._panel_client -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 panel.is_open() - assert not panel.is_in_memory() - - panel.open_panel() - - assert panel.is_open() - assert panel.is_in_memory() - - def test___opened_panel___set_value___gets_same_value( fake_panel_channel: grpc.Channel, ) -> None: @@ -85,12 +72,9 @@ def test___opened_panel_with_value___close_without_reset___gets_value( value_id = "test_id" string_value = "test_value" panel.set_value(value_id, string_value) - assert panel.is_open() panel.close_panel(reset=False) - assert panel.is_in_memory() - assert not panel.is_open() assert panel.get_value(value_id) == string_value @@ -102,11 +86,9 @@ def test___opened_panel_with_value___close_with_reset___get_throws( value_id = "test_id" string_value = "test_value" panel.set_value(value_id, string_value) - assert panel.is_open() panel.close_panel(reset=True) - assert not panel.is_in_memory() with pytest.raises(grpc.RpcError): panel.get_value(value_id) @@ -227,6 +209,18 @@ def test___unsupported_type___set_value___raises( 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: @@ -234,11 +228,11 @@ def test___with_panel___opens_and_closes_panel( panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) with panel: - assert panel.is_open() - assert panel.is_in_memory() + assert is_panel_in_memory(panel) + assert is_panel_open(panel) - assert not panel.is_open() - assert panel.is_in_memory() + assert is_panel_in_memory(panel) + assert not is_panel_open(panel) def test___with_panel___set_value___gets_same_value( @@ -251,3 +245,11 @@ def test___with_panel___set_value___gets_same_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] From bdd6ce9e1aafcee4756678afc5f3b7fa99b776c3 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Thu, 29 May 2025 15:43:10 -0500 Subject: [PATCH 4/6] remove clear_value, has_value, and get_value_ids --- .../pythonpanel/v1/python_panel_service.proto | 15 -------- .../v1/python_panel_service_pb2.py | 18 ++++------ .../v1/python_panel_service_pb2.pyi | 30 ---------------- .../v1/python_panel_service_pb2_grpc.py | 35 ------------------- .../v1/python_panel_service_pb2_grpc.pyi | 26 -------------- src/nipanel/_panel_client.py | 11 ------ src/nipanel/_panel_value_accessor.py | 27 -------------- tests/unit/test_panel_client.py | 23 ------------ tests/unit/test_streamlit_panel.py | 18 ++-------- tests/utils/_fake_python_panel_servicer.py | 10 ------ 10 files changed, 9 insertions(+), 204 deletions(-) diff --git a/protos/ni/pythonpanel/v1/python_panel_service.proto b/protos/ni/pythonpanel/v1/python_panel_service.proto index 93a5bc7..b2c4d03 100644 --- a/protos/ni/pythonpanel/v1/python_panel_service.proto +++ b/protos/ni/pythonpanel/v1/python_panel_service.proto @@ -37,10 +37,6 @@ service PythonPanelService { // - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. rpc SetValue(SetValueRequest) returns (SetValueResponse); - // Clear a value for a control on the panel - // - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. - rpc ClearValue(ClearValueRequest) returns (ClearValueResponse); - // Close a panel // Status Codes for errors: // - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. @@ -104,17 +100,6 @@ message SetValueRequest { message SetValueResponse { } -message ClearValueRequest { - // Unique ID of the panel - string panel_id = 1; - - // Unique ID of the value - string value_id = 2; -} - -message ClearValueResponse { -} - message ClosePanelRequest { // Unique ID of the panel string panel_id = 1; diff --git a/src/ni/pythonpanel/v1/python_panel_service_pb2.py b/src/ni/pythonpanel/v1/python_panel_service_pb2.py index 6851124..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\"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\"7\n\x11\x43learValueRequest\x12\x10\n\x08panel_id\x18\x01 \x01(\t\x12\x10\n\x08value_id\x18\x02 \x01(\t\"\x14\n\x12\x43learValueResponse\"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\xb6\x04\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\nClearValue\x12$.ni.pythonpanel.v1.ClearValueRequest\x1a%.ni.pythonpanel.v1.ClearValueResponse\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()) @@ -40,14 +40,10 @@ _SETVALUEREQUEST._serialized_end=554 _SETVALUERESPONSE._serialized_start=556 _SETVALUERESPONSE._serialized_end=574 - _CLEARVALUEREQUEST._serialized_start=576 - _CLEARVALUEREQUEST._serialized_end=631 - _CLEARVALUERESPONSE._serialized_start=633 - _CLEARVALUERESPONSE._serialized_end=653 - _CLOSEPANELREQUEST._serialized_start=655 - _CLOSEPANELREQUEST._serialized_end=707 - _CLOSEPANELRESPONSE._serialized_start=709 - _CLOSEPANELRESPONSE._serialized_end=729 - _PYTHONPANELSERVICE._serialized_start=732 - _PYTHONPANELSERVICE._serialized_end=1298 + _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 6b7b706..74a5036 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2.pyi +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2.pyi @@ -173,36 +173,6 @@ class SetValueResponse(google.protobuf.message.Message): global___SetValueResponse = SetValueResponse -@typing.final -class ClearValueRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PANEL_ID_FIELD_NUMBER: builtins.int - VALUE_ID_FIELD_NUMBER: builtins.int - panel_id: builtins.str - """Unique ID of the panel""" - value_id: builtins.str - """Unique ID of the value""" - def __init__( - self, - *, - panel_id: builtins.str = ..., - value_id: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing.Literal["panel_id", b"panel_id", "value_id", b"value_id"]) -> None: ... - -global___ClearValueRequest = ClearValueRequest - -@typing.final -class ClearValueResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___ClearValueResponse = ClearValueResponse - @typing.final class ClosePanelRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor 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 d7335d4..69689d4 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py @@ -35,11 +35,6 @@ def __init__(self, channel): request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueRequest.SerializeToString, response_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.FromString, ) - self.ClearValue = channel.unary_unary( - '/ni.pythonpanel.v1.PythonPanelService/ClearValue', - request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueRequest.SerializeToString, - response_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueResponse.FromString, - ) self.ClosePanel = channel.unary_unary( '/ni.pythonpanel.v1.PythonPanelService/ClosePanel', request_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClosePanelRequest.SerializeToString, @@ -88,14 +83,6 @@ def SetValue(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def ClearValue(self, request, context): - """Clear a value for a control on the panel - - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - def ClosePanel(self, request, context): """Close a panel Status Codes for errors: @@ -128,11 +115,6 @@ def add_PythonPanelServiceServicer_to_server(servicer, server): request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueRequest.FromString, response_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.SetValueResponse.SerializeToString, ), - 'ClearValue': grpc.unary_unary_rpc_method_handler( - servicer.ClearValue, - request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueRequest.FromString, - response_serializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueResponse.SerializeToString, - ), 'ClosePanel': grpc.unary_unary_rpc_method_handler( servicer.ClosePanel, request_deserializer=ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClosePanelRequest.FromString, @@ -217,23 +199,6 @@ def SetValue(request, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - @staticmethod - def ClearValue(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/ni.pythonpanel.v1.PythonPanelService/ClearValue', - ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueRequest.SerializeToString, - ni_dot_pythonpanel_dot_v1_dot_python__panel__service__pb2.ClearValueResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - @staticmethod def ClosePanel(request, target, 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 447c9fe..98fb91b 100644 --- a/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi +++ b/src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi @@ -58,14 +58,6 @@ class PythonPanelServiceStub: - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. """ - ClearValue: grpc.UnaryUnaryMultiCallable[ - ni.pythonpanel.v1.python_panel_service_pb2.ClearValueRequest, - ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse, - ] - """Clear a value for a control on the panel - - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. - """ - ClosePanel: grpc.UnaryUnaryMultiCallable[ ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelRequest, ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelResponse, @@ -115,14 +107,6 @@ class PythonPanelServiceAsyncStub: - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. """ - ClearValue: grpc.aio.UnaryUnaryMultiCallable[ - ni.pythonpanel.v1.python_panel_service_pb2.ClearValueRequest, - ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse, - ] - """Clear a value for a control on the panel - - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. - """ - ClosePanel: grpc.aio.UnaryUnaryMultiCallable[ ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelRequest, ni.pythonpanel.v1.python_panel_service_pb2.ClosePanelResponse, @@ -180,16 +164,6 @@ class PythonPanelServiceServicer(metaclass=abc.ABCMeta): - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. """ - @abc.abstractmethod - def ClearValue( - self, - request: ni.pythonpanel.v1.python_panel_service_pb2.ClearValueRequest, - context: _ServicerContext, - ) -> typing.Union[ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse, collections.abc.Awaitable[ni.pythonpanel.v1.python_panel_service_pb2.ClearValueResponse]]: - """Clear a value for a control on the panel - - INVALID_ARGUMENT: The specified identifier contains invalid characters. Only alphanumeric characters and underscores are allowed. - """ - @abc.abstractmethod def ClosePanel( self, diff --git a/src/nipanel/_panel_client.py b/src/nipanel/_panel_client.py index ecbf768..d8018e3 100644 --- a/src/nipanel/_panel_client.py +++ b/src/nipanel/_panel_client.py @@ -13,7 +13,6 @@ EnumeratePanelsRequest, GetValueRequest, SetValueRequest, - ClearValueRequest, ) from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient @@ -120,16 +119,6 @@ def get_value(self, panel_id: str, value_id: str) -> object: the_value = from_any(response.value) return the_value - def clear_value(self, panel_id: str, value_id: str) -> None: - """Clear the value for the control with value_id. - - Args: - panel_id: The ID of the panel. - value_id: The ID of the control. - """ - clear_value_request = ClearValueRequest(panel_id=panel_id, value_id=value_id) - self._invoke_with_retry(self._get_stub().ClearValue, clear_value_request) - def _get_stub(self) -> PythonPanelServiceStub: if self._stub is None: if self._grpc_channel is not None: diff --git a/src/nipanel/_panel_value_accessor.py b/src/nipanel/_panel_value_accessor.py index e78b937..6059d03 100644 --- a/src/nipanel/_panel_value_accessor.py +++ b/src/nipanel/_panel_value_accessor.py @@ -58,30 +58,3 @@ def set_value(self, value_id: str, value: object) -> None: value: The value """ self._panel_client.set_value(self._panel_id, value_id, value) - - def clear_value(self, value_id: str) -> None: - """Clear the value for a control on the panel. - - Args: - value_id: The id of the value - """ - self._panel_client.clear_value(self._panel_id, value_id) - - def has_value(self, value_id: str) -> bool: - """Check if the panel has a value with the given ID. - - Args: - value_id: The id of the value - - Returns: - True if the panel has the value, False otherwise. - """ - return value_id in self.get_value_ids() - - def get_value_ids(self) -> list[str]: - """Get the value IDs that have been set for the panel. - - Returns: - A list of value IDs for the panel. - """ - return self._panel_client.enumerate_panels()[self._panel_id][1] diff --git a/tests/unit/test_panel_client.py b/tests/unit/test_panel_client.py index 4157ac7..9ac65de 100644 --- a/tests/unit/test_panel_client.py +++ b/tests/unit/test_panel_client.py @@ -68,29 +68,6 @@ def test___set_value___enumerate_panels_shows_value( assert client.enumerate_panels() == {"panel1": (False, ["val1"])} -def test___set_value___clear_value___enumerate_panels_shows_no_value( - fake_panel_channel: grpc.Channel, -) -> None: - client = create_panel_client(fake_panel_channel) - client.set_value("panel1", "val1", "value1") - - client.clear_value("panel1", "val1") - - assert client.enumerate_panels() == {"panel1": (False, [])} - - -def test___set_values___clear_value_2___enumerate_panels_has_value_1( - fake_panel_channel: grpc.Channel, -) -> None: - client = create_panel_client(fake_panel_channel) - client.set_value("panel1", "val1", "value1") - client.set_value("panel1", "val2", "value2") - - client.clear_value("panel1", "val2") - - 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 5561dac..265c353 100644 --- a/tests/unit/test_streamlit_panel.py +++ b/tests/unit/test_streamlit_panel.py @@ -111,7 +111,7 @@ def test___first_open_panel_fails___open_panel___gets_value( assert panel.get_value(value_id) == string_value -def test___unopened_panel___set_value___has_value( +def test___unopened_panel___set_value___client_has_value( fake_panel_channel: grpc.Channel, ) -> None: """Test that set_value() succeeds before the user opens the panel.""" @@ -121,21 +121,7 @@ def test___unopened_panel___set_value___has_value( string_value = "test_value" panel.set_value(value_id, string_value) - assert panel.has_value(value_id) - - -def test___set_value___clear_value___does_not_have_value( - fake_panel_channel: grpc.Channel, -) -> None: - """Test that set_value() succeeds before the user opens the panel.""" - panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) - value_id = "test_id" - string_value = "test_value" - panel.set_value(value_id, string_value) - - panel.clear_value(value_id) - - assert not panel.has_value(value_id) + assert panel._panel_client.enumerate_panels() == {"my_panel": (False, [value_id])} def test___unopened_panel___get_unset_value___raises_exception( diff --git a/tests/utils/_fake_python_panel_servicer.py b/tests/utils/_fake_python_panel_servicer.py index 8c94b7e..2f20f04 100644 --- a/tests/utils/_fake_python_panel_servicer.py +++ b/tests/utils/_fake_python_panel_servicer.py @@ -13,8 +13,6 @@ GetValueResponse, SetValueRequest, SetValueResponse, - ClearValueRequest, - ClearValueResponse, ) from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceServicer @@ -69,14 +67,6 @@ def SetValue(self, request: SetValueRequest, context: Any) -> SetValueResponse: self._panel_value_ids[request.panel_id][request.value_id] = request.value return SetValueResponse() - def ClearValue( # noqa: N802 - self, request: ClearValueRequest, context: Any - ) -> ClearValueResponse: - """Trivial implementation for testing.""" - if request.panel_id in self._panel_value_ids: - self._panel_value_ids[request.panel_id].pop(request.value_id, None) - return ClearValueResponse() - 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 From 6177bc9eb0993370383d152ce341da87a0799a0b Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Thu, 29 May 2025 15:53:59 -0500 Subject: [PATCH 5/6] cleanup --- src/nipanel/_panel_client.py | 2 +- tests/unit/test_panel_client.py | 4 ++-- tests/unit/test_streamlit_panel.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/nipanel/_panel_client.py b/src/nipanel/_panel_client.py index d8018e3..8fbd9f7 100644 --- a/src/nipanel/_panel_client.py +++ b/src/nipanel/_panel_client.py @@ -80,7 +80,7 @@ def close_panel(self, panel_id: str, reset: bool) -> None: self._invoke_with_retry(self._get_stub().ClosePanel, close_panel_request) def enumerate_panels(self) -> dict[str, tuple[bool, list[str]]]: - """Enumerate all open panels. + """Enumerate all available panels. Returns: A dictionary mapping panel IDs to a tuple containing a boolean indicating if the panel diff --git a/tests/unit/test_panel_client.py b/tests/unit/test_panel_client.py index 9ac65de..dd26818 100644 --- a/tests/unit/test_panel_client.py +++ b/tests/unit/test_panel_client.py @@ -22,7 +22,7 @@ def test___open_panels___both_panels_open_and_in_memory(fake_panel_channel: grpc } -def test___open_panels___close_panel_1_with_reset___panel_1_not_in_memory( +def test___open_panels___close_panel_1_with_reset___enumerate_has_panel_2( fake_panel_channel: grpc.Channel, ) -> None: client = create_panel_client(fake_panel_channel) @@ -36,7 +36,7 @@ def test___open_panels___close_panel_1_with_reset___panel_1_not_in_memory( } -def test___open_panels___close_panel_1_without_reset___both_panels_in_memory( +def test___open_panels___close_panel_1_without_reset___enumerate_has_both_panels( 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 265c353..586989a 100644 --- a/tests/unit/test_streamlit_panel.py +++ b/tests/unit/test_streamlit_panel.py @@ -111,7 +111,7 @@ def test___first_open_panel_fails___open_panel___gets_value( assert panel.get_value(value_id) == string_value -def test___unopened_panel___set_value___client_has_value( +def test___unopened_panel___set_value___sets_value( fake_panel_channel: grpc.Channel, ) -> None: """Test that set_value() succeeds before the user opens the panel.""" @@ -210,7 +210,6 @@ def test___open_panel___panel_is_open_and_in_memory( def test___with_panel___opens_and_closes_panel( fake_panel_channel: grpc.Channel, ) -> None: - """Test that using the panel in a with statement opens and closes it.""" panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) with panel: @@ -224,7 +223,6 @@ def test___with_panel___opens_and_closes_panel( def test___with_panel___set_value___gets_same_value( fake_panel_channel: grpc.Channel, ) -> None: - """Test that using the panel in a with statement allows setting and getting values.""" with StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) as panel: value_id = "test_id" string_value = "test_value" From 0fd6f372483305965c95907a18087c86375790c7 Mon Sep 17 00:00:00 2001 From: Mike Prosser Date: Thu, 29 May 2025 15:56:04 -0500 Subject: [PATCH 6/6] cleanup --- tests/unit/test_panel_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_panel_client.py b/tests/unit/test_panel_client.py index dd26818..4b7d2d9 100644 --- a/tests/unit/test_panel_client.py +++ b/tests/unit/test_panel_client.py @@ -4,13 +4,13 @@ from nipanel._panel_client import PanelClient -def test___enumerate_panels_is_empty(fake_panel_channel: grpc.Channel) -> None: +def test___enumerate_is_empty(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) assert client.enumerate_panels() == {} -def test___open_panels___both_panels_open_and_in_memory(fake_panel_channel: grpc.Channel) -> None: +def test___open_panels___enumerate_has_panels(fake_panel_channel: grpc.Channel) -> None: client = create_panel_client(fake_panel_channel) client.open_panel("panel1", "uri1")