Skip to content

Commit 526aca9

Browse files
committed
Merge remote-tracking branch 'origin/main' into users/jfriedri/lofi-support-for-builtin-sequences
Signed-off-by: Joe Friedrichsen <[email protected]>
2 parents 823b224 + 75c1d63 commit 526aca9

File tree

10 files changed

+205
-47
lines changed

10 files changed

+205
-47
lines changed

protos/ni/pythonpanel/v1/python_panel_service.proto

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ option ruby_package = "NI::PythonPanel::V1";
1616

1717
// Service interface for interacting with python panels
1818
service PythonPanelService {
19-
// Enumerate the panels available in the system
19+
// Enumerate the panels available in the system, including information about the state of the panels and what values they have.
2020
// Status Codes for errors:
2121
rpc EnumeratePanels(EnumeratePanelsRequest) returns (EnumeratePanelsResponse);
2222

@@ -46,9 +46,20 @@ service PythonPanelService {
4646
message EnumeratePanelsRequest {
4747
}
4848

49+
message PanelInformation {
50+
// Unique ID of the panel
51+
string panel_id = 1;
52+
53+
// Is the panel currently open?
54+
bool is_open = 2;
55+
56+
// IDs of all of the values associated with the panel
57+
repeated string value_ids = 3;
58+
}
59+
4960
message EnumeratePanelsResponse {
5061
// The list of panels available in the system
51-
repeated string panel_ids = 1;
62+
repeated PanelInformation panels = 1;
5263
}
5364

5465
message OpenPanelRequest {

src/ni/pythonpanel/v1/python_panel_service_pb2.py

Lines changed: 23 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ni/pythonpanel/v1/python_panel_service_pb2.pyi

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,47 @@ class EnumeratePanelsRequest(google.protobuf.message.Message):
2323

2424
global___EnumeratePanelsRequest = EnumeratePanelsRequest
2525

26+
@typing.final
27+
class PanelInformation(google.protobuf.message.Message):
28+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
29+
30+
PANEL_ID_FIELD_NUMBER: builtins.int
31+
IS_OPEN_FIELD_NUMBER: builtins.int
32+
VALUE_IDS_FIELD_NUMBER: builtins.int
33+
panel_id: builtins.str
34+
"""Unique ID of the panel"""
35+
is_open: builtins.bool
36+
"""Is the panel currently open?"""
37+
@property
38+
def value_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]:
39+
"""IDs of all of the values associated with the panel"""
40+
41+
def __init__(
42+
self,
43+
*,
44+
panel_id: builtins.str = ...,
45+
is_open: builtins.bool = ...,
46+
value_ids: collections.abc.Iterable[builtins.str] | None = ...,
47+
) -> None: ...
48+
def ClearField(self, field_name: typing.Literal["is_open", b"is_open", "panel_id", b"panel_id", "value_ids", b"value_ids"]) -> None: ...
49+
50+
global___PanelInformation = PanelInformation
51+
2652
@typing.final
2753
class EnumeratePanelsResponse(google.protobuf.message.Message):
2854
DESCRIPTOR: google.protobuf.descriptor.Descriptor
2955

30-
PANEL_IDS_FIELD_NUMBER: builtins.int
56+
PANELS_FIELD_NUMBER: builtins.int
3157
@property
32-
def panel_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]:
58+
def panels(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PanelInformation]:
3359
"""The list of panels available in the system"""
3460

3561
def __init__(
3662
self,
3763
*,
38-
panel_ids: collections.abc.Iterable[builtins.str] | None = ...,
64+
panels: collections.abc.Iterable[global___PanelInformation] | None = ...,
3965
) -> None: ...
40-
def ClearField(self, field_name: typing.Literal["panel_ids", b"panel_ids"]) -> None: ...
66+
def ClearField(self, field_name: typing.Literal["panels", b"panels"]) -> None: ...
4167

4268
global___EnumeratePanelsResponse = EnumeratePanelsResponse
4369

src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class PythonPanelServiceServicer(object):
4747
"""
4848

4949
def EnumeratePanels(self, request, context):
50-
"""Enumerate the panels available in the system
50+
"""Enumerate the panels available in the system, including information about the state of the panels and what values they have.
5151
Status Codes for errors:
5252
"""
5353
context.set_code(grpc.StatusCode.UNIMPLEMENTED)

src/ni/pythonpanel/v1/python_panel_service_pb2_grpc.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class PythonPanelServiceStub:
2525
ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsRequest,
2626
ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse,
2727
]
28-
"""Enumerate the panels available in the system
28+
"""Enumerate the panels available in the system, including information about the state of the panels and what values they have.
2929
Status Codes for errors:
3030
"""
3131

@@ -74,7 +74,7 @@ class PythonPanelServiceAsyncStub:
7474
ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsRequest,
7575
ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse,
7676
]
77-
"""Enumerate the panels available in the system
77+
"""Enumerate the panels available in the system, including information about the state of the panels and what values they have.
7878
Status Codes for errors:
7979
"""
8080

@@ -125,7 +125,7 @@ class PythonPanelServiceServicer(metaclass=abc.ABCMeta):
125125
request: ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsRequest,
126126
context: _ServicerContext,
127127
) -> typing.Union[ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse, collections.abc.Awaitable[ni.pythonpanel.v1.python_panel_service_pb2.EnumeratePanelsResponse]]:
128-
"""Enumerate the panels available in the system
128+
"""Enumerate the panels available in the system, including information about the state of the panels and what values they have.
129129
Status Codes for errors:
130130
"""
131131

src/nipanel/_panel.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
from __future__ import annotations
22

3+
import sys
34
from abc import ABC
5+
from types import TracebackType
6+
from typing import TYPE_CHECKING
47

58
import grpc
69
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
710
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
811

912
from nipanel._panel_value_accessor import PanelValueAccessor
1013

14+
if TYPE_CHECKING:
15+
if sys.version_info >= (3, 11):
16+
from typing import Self
17+
else:
18+
from typing_extensions import Self
19+
1120

1221
class Panel(PanelValueAccessor, ABC):
1322
"""This class allows you to open a panel and specify values for its controls."""
@@ -41,6 +50,21 @@ def panel_uri(self) -> str:
4150
"""Read-only accessor for the panel URI."""
4251
return self._panel_uri
4352

53+
def __enter__(self) -> Self:
54+
"""Enter the runtime context related to this object."""
55+
self.open_panel()
56+
return self
57+
58+
def __exit__(
59+
self,
60+
exctype: type[BaseException] | None,
61+
excinst: BaseException | None,
62+
exctb: TracebackType | None,
63+
) -> bool | None:
64+
"""Exit the runtime context related to this object."""
65+
self.close_panel(reset=False)
66+
return None
67+
4468
def open_panel(self) -> None:
4569
"""Open the panel."""
4670
self._panel_client.open_panel(self._panel_id, self._panel_uri)

src/nipanel/_panel_client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,18 @@ def close_panel(self, panel_id: str, reset: bool) -> None:
7979
close_panel_request = ClosePanelRequest(panel_id=panel_id, reset=reset)
8080
self._invoke_with_retry(self._get_stub().ClosePanel, close_panel_request)
8181

82-
def enumerate_panels(self) -> list[str]:
82+
def enumerate_panels(self) -> dict[str, tuple[bool, list[str]]]:
8383
"""Enumerate all available panels.
8484
8585
Returns:
86-
A list of panel IDs.
86+
A dictionary mapping panel IDs to a tuple containing a boolean indicating if the panel
87+
is open and a list of value IDs associated with the panel.
8788
"""
8889
enumerate_panels_request = EnumeratePanelsRequest()
8990
response = self._invoke_with_retry(
9091
self._get_stub().EnumeratePanels, enumerate_panels_request
9192
)
92-
return list(response.panel_ids)
93+
return {panel.panel_id: (panel.is_open, list(panel.value_ids)) for panel in response.panels}
9394

9495
def set_value(self, panel_id: str, value_id: str, value: object) -> None:
9596
"""Set the value for the control with value_id.

tests/unit/test_panel_client.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def test___enumerate_is_empty(fake_panel_channel: grpc.Channel) -> None:
88
client = create_panel_client(fake_panel_channel)
99

10-
assert client.enumerate_panels() == []
10+
assert client.enumerate_panels() == {}
1111

1212

1313
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)
1616
client.open_panel("panel1", "uri1")
1717
client.open_panel("panel2", "uri2")
1818

19-
assert client.enumerate_panels() == ["panel1", "panel2"]
19+
assert client.enumerate_panels() == {
20+
"panel1": (True, []),
21+
"panel2": (True, []),
22+
}
2023

2124

2225
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(
2831

2932
client.close_panel("panel1", reset=True)
3033

31-
assert client.enumerate_panels() == ["panel2"]
34+
assert client.enumerate_panels() == {
35+
"panel2": (True, []),
36+
}
3237

3338

3439
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
4045

4146
client.close_panel("panel1", reset=False)
4247

43-
assert client.enumerate_panels() == ["panel1", "panel2"]
48+
assert client.enumerate_panels() == {
49+
"panel1": (False, []),
50+
"panel2": (True, []),
51+
}
4452

4553

4654
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) ->
5058
client.get_value("panel1", "unset_id")
5159

5260

61+
def test___set_value___enumerate_panels_shows_value(
62+
fake_panel_channel: grpc.Channel,
63+
) -> None:
64+
client = create_panel_client(fake_panel_channel)
65+
66+
client.set_value("panel1", "val1", "value1")
67+
68+
assert client.enumerate_panels() == {"panel1": (False, ["val1"])}
69+
70+
5371
def test___set_value___gets_value(fake_panel_channel: grpc.Channel) -> None:
5472
client = create_panel_client(fake_panel_channel)
5573

tests/unit/test_streamlit_panel.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,10 @@ def test___unopened_panel___set_value___sets_value(
119119

120120
value_id = "test_id"
121121
string_value = "test_value"
122-
123122
panel.set_value(value_id, string_value)
124123

124+
assert panel._panel_client.enumerate_panels() == {"my_panel": (False, [value_id])}
125+
125126

126127
def test___unopened_panel___get_unset_value___raises_exception(
127128
fake_panel_channel: grpc.Channel,
@@ -245,3 +246,47 @@ def test___sequence_of_builtin_type___set_value___gets_same_value(
245246

246247
received_value = panel.get_value(value_id)
247248
assert list(received_value) == list(value_payload) # type: ignore [call-overload]
249+
250+
251+
def test___open_panel___panel_is_open_and_in_memory(
252+
fake_panel_channel: grpc.Channel,
253+
) -> None:
254+
panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel)
255+
assert not is_panel_in_memory(panel)
256+
257+
panel.open_panel()
258+
259+
assert is_panel_in_memory(panel)
260+
assert is_panel_open(panel)
261+
262+
263+
def test___with_panel___opens_and_closes_panel(
264+
fake_panel_channel: grpc.Channel,
265+
) -> None:
266+
panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel)
267+
268+
with panel:
269+
assert is_panel_in_memory(panel)
270+
assert is_panel_open(panel)
271+
272+
assert is_panel_in_memory(panel)
273+
assert not is_panel_open(panel)
274+
275+
276+
def test___with_panel___set_value___gets_same_value(
277+
fake_panel_channel: grpc.Channel,
278+
) -> None:
279+
with StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel) as panel:
280+
value_id = "test_id"
281+
string_value = "test_value"
282+
panel.set_value(value_id, string_value)
283+
284+
assert panel.get_value(value_id) == string_value
285+
286+
287+
def is_panel_in_memory(panel: StreamlitPanel) -> bool:
288+
return panel.panel_id in panel._panel_client.enumerate_panels().keys()
289+
290+
291+
def is_panel_open(panel: StreamlitPanel) -> bool:
292+
return panel._panel_client.enumerate_panels()[panel.panel_id][0]

0 commit comments

Comments
 (0)