Skip to content

Commit ee64290

Browse files
mikeprosserniMike Prosser
andauthored
Add a panel accessor class for streamlit scripts to call (#31)
* streamlit panel accessor * cleanup * add streamlit dependency * try python ^3.11 * try python = "^3.9.13" * use Poetry 1.8.2 * python = "^3.9.8" (since streamlit is incompatible with 3.9.7) * lock file for 3.9.8 * cleanup - we aren't doing the component initialization in this PR * python = ">=3.9,<4.0,!=3.9.7" # Exclude 3.9.7 due to streamlit not supporting it * Johann's feedback * clean up __slots__ * rename to PanelValueAccessor * test___different_panels___have_different_panel_ids_and_uris * remove class-level type declarations --------- Co-authored-by: Mike Prosser <Mike.Prosser@emerson.com>
1 parent 529bcda commit ee64290

9 files changed

Lines changed: 955 additions & 48 deletions

poetry.lock

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

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ readme = "README.md"
77
packages = [{ include = "nipanel", from = "src" }, { include = "ni", from = "src" }]
88

99
[tool.poetry.dependencies]
10-
python = "^3.9"
10+
python = ">=3.9,<4.0,!=3.9.7" # Exclude 3.9.7 due to streamlit not supporting it
1111
grpcio = {version=">=1.49.0,<2.0"}
1212
protobuf = {version=">=4.21"}
1313
ni-measurement-plugin-sdk = {version=">=2.3"}
1414
typing-extensions = ">=4.13.2"
15+
streamlit = ">=1.24"
1516

1617
[tool.poetry.group.dev.dependencies]
1718
types-grpcio = ">=1.0"

src/nipanel/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from nipanel._panel import Panel
44
from nipanel._streamlit_panel import StreamlitPanel
5+
from nipanel._streamlit_panel_value_accessor import StreamlitPanelValueAccessor
56

6-
__all__ = ["Panel", "StreamlitPanel"]
7+
__all__ = ["Panel", "StreamlitPanel", "StreamlitPanelValueAccessor"]
78

89
# Hide that it was defined in a helper file
910
Panel.__module__ = __name__

src/nipanel/_panel.py

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@
66
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
77
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
88

9-
from nipanel._panel_client import PanelClient
9+
from nipanel._panel_value_accessor import PanelValueAccessor
1010

1111

12-
class Panel(ABC):
12+
class Panel(PanelValueAccessor, ABC):
1313
"""This class allows you to open a panel and specify values for its controls."""
1414

15-
_panel_client: PanelClient
16-
_panel_id: str
17-
_panel_uri: str
18-
19-
__slots__ = ["_panel_client", "_panel_id", "_panel_uri", "__weakref__"]
15+
__slots__ = ["_panel_uri"]
2016

2117
def __init__(
2218
self,
@@ -30,21 +26,16 @@ def __init__(
3026
grpc_channel: grpc.Channel | None = None,
3127
) -> None:
3228
"""Initialize the panel."""
33-
self._panel_client = PanelClient(
29+
super().__init__(
30+
panel_id=panel_id,
3431
provided_interface=provided_interface,
3532
service_class=service_class,
3633
discovery_client=discovery_client,
3734
grpc_channel_pool=grpc_channel_pool,
3835
grpc_channel=grpc_channel,
3936
)
40-
self._panel_id = panel_id
4137
self._panel_uri = panel_uri
4238

43-
@property
44-
def panel_id(self) -> str:
45-
"""Read-only accessor for the panel ID."""
46-
return self._panel_id
47-
4839
@property
4940
def panel_uri(self) -> str:
5041
"""Read-only accessor for the panel URI."""
@@ -53,23 +44,3 @@ def panel_uri(self) -> str:
5344
def open_panel(self) -> None:
5445
"""Open the panel."""
5546
self._panel_client.open_panel(self._panel_id, self._panel_uri)
56-
57-
def get_value(self, value_id: str) -> object:
58-
"""Get the value for a control on the panel.
59-
60-
Args:
61-
value_id: The id of the value
62-
63-
Returns:
64-
The value
65-
"""
66-
return self._panel_client.get_value(self._panel_id, value_id)
67-
68-
def set_value(self, value_id: str, value: object) -> None:
69-
"""Set the value for a control on the panel.
70-
71-
Args:
72-
value_id: The id of the value
73-
value: The value
74-
"""
75-
self._panel_client.set_value(self._panel_id, value_id, value)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC
4+
5+
import grpc
6+
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
7+
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
8+
9+
from nipanel._panel_client import PanelClient
10+
11+
12+
class PanelValueAccessor(ABC):
13+
"""This class allows you to access values for a panel's controls."""
14+
15+
__slots__ = ["_panel_client", "_panel_id", "__weakref__"]
16+
17+
def __init__(
18+
self,
19+
*,
20+
panel_id: str,
21+
provided_interface: str,
22+
service_class: str,
23+
discovery_client: DiscoveryClient | None = None,
24+
grpc_channel_pool: GrpcChannelPool | None = None,
25+
grpc_channel: grpc.Channel | None = None,
26+
) -> None:
27+
"""Initialize the accessor."""
28+
self._panel_client = PanelClient(
29+
provided_interface=provided_interface,
30+
service_class=service_class,
31+
discovery_client=discovery_client,
32+
grpc_channel_pool=grpc_channel_pool,
33+
grpc_channel=grpc_channel,
34+
)
35+
self._panel_id = panel_id
36+
37+
@property
38+
def panel_id(self) -> str:
39+
"""Read-only accessor for the panel ID."""
40+
return self._panel_id
41+
42+
def get_value(self, value_id: str) -> object:
43+
"""Get the value for a control on the panel.
44+
45+
Args:
46+
value_id: The id of the value
47+
48+
Returns:
49+
The value
50+
"""
51+
return self._panel_client.get_value(self._panel_id, value_id)
52+
53+
def set_value(self, value_id: str, value: object) -> None:
54+
"""Set the value for a control on the panel.
55+
56+
Args:
57+
value_id: The id of the value
58+
value: The value
59+
"""
60+
self._panel_client.set_value(self._panel_id, value_id, value)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
STREAMLIT_PYTHON_PANEL_SERVICE = "ni.pythonpanel.v1.PythonPanelService"

src/nipanel/_streamlit_panel.py

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

3+
from typing import final
4+
35
import grpc
46
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
57
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
68

79
from nipanel._panel import Panel
10+
from nipanel._streamlit_constants import STREAMLIT_PYTHON_PANEL_SERVICE
811

912

13+
@final
1014
class StreamlitPanel(Panel):
1115
"""This class allows you to open a Streamlit panel and specify values for its controls."""
1216

13-
PYTHON_PANEL_SERVICE = "ni.pythonpanel.v1.PythonPanelService"
14-
15-
__slots__ = ()
16-
1717
def __init__(
1818
self,
1919
panel_id: str,
@@ -36,8 +36,8 @@ def __init__(
3636
super().__init__(
3737
panel_id=panel_id,
3838
panel_uri=streamlit_script_uri,
39-
provided_interface=self.PYTHON_PANEL_SERVICE,
40-
service_class=self.PYTHON_PANEL_SERVICE,
39+
provided_interface=STREAMLIT_PYTHON_PANEL_SERVICE,
40+
service_class=STREAMLIT_PYTHON_PANEL_SERVICE,
4141
discovery_client=discovery_client,
4242
grpc_channel_pool=grpc_channel_pool,
4343
grpc_channel=grpc_channel,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from __future__ import annotations
2+
3+
from typing import final
4+
5+
import grpc
6+
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
7+
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
8+
9+
from nipanel._panel_value_accessor import PanelValueAccessor
10+
from nipanel._streamlit_constants import STREAMLIT_PYTHON_PANEL_SERVICE
11+
12+
13+
@final
14+
class StreamlitPanelValueAccessor(PanelValueAccessor):
15+
"""This class provides access to values for a Streamlit panel's controls."""
16+
17+
def __init__(
18+
self,
19+
panel_id: str,
20+
*,
21+
discovery_client: DiscoveryClient | None = None,
22+
grpc_channel_pool: GrpcChannelPool | None = None,
23+
grpc_channel: grpc.Channel | None = None,
24+
) -> None:
25+
"""Create an accessor for a Streamlit panel.
26+
27+
Args:
28+
panel_id: A unique identifier for the panel.
29+
grpc_channel: An optional gRPC channel to use for communication with the panel service.
30+
31+
Returns:
32+
A new StreamlitPanelAccessor instance.
33+
"""
34+
super().__init__(
35+
panel_id=panel_id,
36+
provided_interface=STREAMLIT_PYTHON_PANEL_SERVICE,
37+
service_class=STREAMLIT_PYTHON_PANEL_SERVICE,
38+
discovery_client=discovery_client,
39+
grpc_channel_pool=grpc_channel_pool,
40+
grpc_channel=grpc_channel,
41+
)

tests/unit/test_streamlit_panel.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pytest
33

44
import tests.types as test_types
5-
from nipanel._streamlit_panel import StreamlitPanel
5+
from nipanel import StreamlitPanel, StreamlitPanelValueAccessor
66
from tests.utils._fake_python_panel_service import FakePythonPanelService
77

88

@@ -12,6 +12,17 @@ def test___panel___has_panel_id_and_panel_uri() -> None:
1212
assert panel.panel_uri == "path/to/script"
1313

1414

15+
def test___different_panels___have_different_panel_ids_and_uris() -> None:
16+
panel1 = StreamlitPanel("panel1", "path/to/script1")
17+
panel2 = StreamlitPanel("panel2", "path/to/script2")
18+
19+
assert panel1.panel_id == "panel1"
20+
assert panel2.panel_id == "panel2"
21+
assert panel1._panel_uri == "path/to/script1"
22+
assert panel2._panel_uri == "path/to/script2"
23+
assert panel1._panel_client != panel2._panel_client
24+
25+
1526
def test___opened_panel___set_value___gets_same_value(
1627
fake_panel_channel: grpc.Channel,
1728
) -> None:
@@ -25,6 +36,34 @@ def test___opened_panel___set_value___gets_same_value(
2536
assert panel.get_value(value_id) == string_value
2637

2738

39+
def test___opened_panel___panel_set_value___accessor_gets_same_value(
40+
fake_panel_channel: grpc.Channel,
41+
) -> None:
42+
panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel)
43+
panel.open_panel()
44+
accessor = StreamlitPanelValueAccessor("my_panel", grpc_channel=fake_panel_channel)
45+
46+
value_id = "test_id"
47+
string_value = "test_value"
48+
panel.set_value(value_id, string_value)
49+
50+
assert accessor.get_value(value_id) == string_value
51+
52+
53+
def test___opened_panel___accessor_set_value___panel_gets_same_value(
54+
fake_panel_channel: grpc.Channel,
55+
) -> None:
56+
panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel)
57+
panel.open_panel()
58+
accessor = StreamlitPanelValueAccessor("my_panel", grpc_channel=fake_panel_channel)
59+
60+
value_id = "test_id"
61+
string_value = "test_value"
62+
accessor.set_value(value_id, string_value)
63+
64+
assert panel.get_value(value_id) == string_value
65+
66+
2867
def test___first_open_panel_fails___open_panel___gets_value(
2968
fake_python_panel_service: FakePythonPanelService,
3069
fake_panel_channel: grpc.Channel,

0 commit comments

Comments
 (0)