Skip to content

Commit 13e7f3c

Browse files
mikeprosserniMike Prosser
andauthored
Implement Task AB#3095678 - Define Measurement Script API (#2)
* first draft * add basic tests * cleanup * address feedback * refactor to use _StreamlitPanel subclass * refactor to make StreamlitPanel public instead of Panel * make Panel public also, for type hints * panel_id should be specified by the user. Also, lets provide readonly accessors for that and panel_uri. * put panel_id berfore panel_uri --------- Co-authored-by: Mike Prosser <Mike.Prosser@emerson.com>
1 parent 70e69d7 commit 13e7f3c

File tree

8 files changed

+162
-2
lines changed

8 files changed

+162
-2
lines changed

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"."
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.pytestEnabled": true
7+
}

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ ignore_missing_imports = true
5252
skips = [
5353
"B101", # assert_used
5454
]
55+
56+
[tool.pytest.ini_options]
57+
addopts = "--doctest-modules --strict-markers"
58+
testpaths = ["src/nipanel", "tests"]

src/nipanel/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
11
"""The NI Panel."""
2+
3+
from nipanel._panel import Panel
4+
from nipanel._streamlit_panel import StreamlitPanel
5+
6+
__all__ = ["Panel", "StreamlitPanel"]
7+
8+
# Hide that it was defined in a helper file
9+
Panel.__module__ = __name__
10+
StreamlitPanel.__module__ = __name__

src/nipanel/_panel.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from abc import ABC, abstractmethod
5+
from types import TracebackType
6+
from typing import Optional, Type, TYPE_CHECKING
7+
8+
from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub
9+
10+
if TYPE_CHECKING:
11+
if sys.version_info >= (3, 11):
12+
from typing import Self
13+
else:
14+
from typing_extensions import Self
15+
16+
17+
class Panel(ABC):
18+
"""This class allows you to connect to a panel and specify values for its controls."""
19+
20+
_stub: PythonPanelServiceStub | None
21+
_panel_id: str
22+
_panel_uri: str
23+
24+
__slots__ = ["_stub", "_panel_id", "_panel_uri", "__weakref__"]
25+
26+
def __init__(self, panel_id: str, panel_uri: str) -> None:
27+
"""Initialize the panel."""
28+
self._panel_id = panel_id
29+
self._panel_uri = panel_uri
30+
31+
@property
32+
def panel_id(self) -> str:
33+
"""Read-only accessor for the panel ID."""
34+
return self._panel_id
35+
36+
@property
37+
def panel_uri(self) -> str:
38+
"""Read-only accessor for the panel URI."""
39+
return self._panel_uri
40+
41+
def __enter__(self) -> Self:
42+
"""Enter the runtime context related to this object."""
43+
self.connect()
44+
return self
45+
46+
def __exit__(
47+
self,
48+
exctype: Optional[Type[BaseException]],
49+
excinst: Optional[BaseException],
50+
exctb: Optional[TracebackType],
51+
) -> Optional[bool]:
52+
"""Exit the runtime context related to this object."""
53+
self.disconnect()
54+
return None
55+
56+
def connect(self) -> None:
57+
"""Connect to the panel and open it."""
58+
# TODO: AB#3095680 - Use gRPC pool management, create the _stub, and call _stub.Connect
59+
self._resolve_service_location()
60+
61+
def disconnect(self) -> None:
62+
"""Disconnect from the panel (does not close the panel)."""
63+
# TODO: AB#3095680 - Use gRPC pool management, call _stub.Disconnect
64+
pass
65+
66+
def get_value(self, value_id: str) -> object:
67+
"""Get the value for a control on the panel.
68+
69+
Args:
70+
value_id: The id of the value
71+
72+
Returns:
73+
The value
74+
"""
75+
# TODO: AB#3095681 - get the Any from _stub.GetValue and convert it to the correct type
76+
return "placeholder value"
77+
78+
def set_value(self, value_id: str, value: object) -> None:
79+
"""Set the value for a control on the panel.
80+
81+
Args:
82+
value_id: The id of the value
83+
value: The value
84+
"""
85+
# TODO: AB#3095681 - Convert the value to an Any and pass it to _stub.SetValue
86+
pass
87+
88+
@abstractmethod
89+
def _resolve_service_location(self) -> str:
90+
"""Resolve the service location for the panel."""
91+
raise NotImplementedError

src/nipanel/_streamlit_panel.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from nipanel._panel import Panel
2+
3+
4+
class StreamlitPanel(Panel):
5+
"""This class allows you to connect to a Streamlit panel and specify values for its controls."""
6+
7+
__slots__ = ()
8+
9+
def __init__(self, panel_id: str, streamlit_script_uri: str) -> None:
10+
"""Create a panel using a Streamlit script for the user interface.
11+
12+
Args:
13+
panel_id: A unique identifier for the panel.
14+
streamlit_script_uri: The file path of the Streamlit script.
15+
16+
Returns:
17+
A new StreamlitPanel instance.
18+
"""
19+
super().__init__(panel_id, streamlit_script_uri)
20+
21+
def _resolve_service_location(self) -> str:
22+
# TODO: AB#3095680 - resolve to the Streamlit PythonPanelService
23+
return ""

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for the `nipanel` package."""

tests/unit/test_panel.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import nipanel
2+
3+
4+
def test___streamlit_panel___has_panel_id_and_panel_uri() -> None:
5+
panel = nipanel.StreamlitPanel("my_panel", "path/to/script")
6+
assert panel.panel_id == "my_panel"
7+
assert panel.panel_uri == "path/to/script"
8+
9+
10+
def test___connected_panel___set_value___gets_same_value() -> None:
11+
panel = nipanel.StreamlitPanel("my_panel", "path/to/script")
12+
panel.connect()
13+
14+
panel.set_value("test_id", "test_value")
15+
16+
# TODO: AB#3095681 - change asserted value to test_value
17+
assert panel.get_value("test_id") == "placeholder value"
18+
panel.disconnect()
19+
20+
21+
def test___with_panel___set_value___gets_same_value() -> None:
22+
with nipanel.StreamlitPanel("my_panel", "path/to/script") as panel:
23+
24+
panel.set_value("test_id", "test_value")
25+
26+
# TODO: AB#3095681 - change asserted value to test_value
27+
assert panel.get_value("test_id") == "placeholder value"

tests/unit/test_placeholder.py

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

0 commit comments

Comments
 (0)