Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
803 changes: 798 additions & 5 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ readme = "README.md"
packages = [{ include = "nipanel", from = "src" }, { include = "ni", from = "src" }]

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

[tool.poetry.group.dev.dependencies]
types-grpcio = ">=1.0"
Expand Down
3 changes: 2 additions & 1 deletion src/nipanel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

from nipanel._panel import Panel
from nipanel._streamlit_panel import StreamlitPanel
from nipanel._streamlit_panel_value_accessor import StreamlitPanelValueAccessor

__all__ = ["Panel", "StreamlitPanel"]
__all__ = ["Panel", "StreamlitPanel", "StreamlitPanelValueAccessor"]

# Hide that it was defined in a helper file
Panel.__module__ = __name__
Expand Down
37 changes: 5 additions & 32 deletions src/nipanel/_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool

from nipanel._panel_client import PanelClient
from nipanel._panel_value_accessor import PanelValueAccessor


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

_panel_client: PanelClient
_panel_id: str
_panel_uri: str

__slots__ = ["_panel_client", "_panel_id", "_panel_uri", "__weakref__"]
__slots__ = ["_panel_uri"]

def __init__(
self,
Expand All @@ -30,21 +28,16 @@ def __init__(
grpc_channel: grpc.Channel | None = None,
) -> None:
"""Initialize the panel."""
self._panel_client = PanelClient(
super().__init__(
panel_id=panel_id,
provided_interface=provided_interface,
service_class=service_class,
discovery_client=discovery_client,
grpc_channel_pool=grpc_channel_pool,
grpc_channel=grpc_channel,
)
self._panel_id = panel_id
self._panel_uri = panel_uri

@property
def panel_id(self) -> str:
"""Read-only accessor for the panel ID."""
return self._panel_id

@property
def panel_uri(self) -> str:
"""Read-only accessor for the panel URI."""
Expand All @@ -53,23 +46,3 @@ def panel_uri(self) -> str:
def open_panel(self) -> None:
"""Open the panel."""
self._panel_client.open_panel(self._panel_id, self._panel_uri)

def get_value(self, value_id: str) -> object:
"""Get the value for a control on the panel.

Args:
value_id: The id of the value

Returns:
The value
"""
return self._panel_client.get_value(self._panel_id, value_id)

def set_value(self, value_id: str, value: object) -> None:
"""Set the value for a control on the panel.

Args:
value_id: The id of the value
value: The value
"""
self._panel_client.set_value(self._panel_id, value_id, value)
63 changes: 63 additions & 0 deletions src/nipanel/_panel_value_accessor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

from abc import ABC

import grpc
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool

from nipanel._panel_client import PanelClient


class PanelValueAccessor(ABC):
"""This class allows you to access values for a panel's controls."""

_panel_client: PanelClient
_panel_id: str

__slots__ = ["_panel_client", "_panel_id", "__weakref__"]

def __init__(
self,
*,
panel_id: str,
provided_interface: str,
service_class: str,
discovery_client: DiscoveryClient | None = None,
grpc_channel_pool: GrpcChannelPool | None = None,
grpc_channel: grpc.Channel | None = None,
) -> None:
"""Initialize the accessor."""
self._panel_client = PanelClient(
provided_interface=provided_interface,
service_class=service_class,
discovery_client=discovery_client,
grpc_channel_pool=grpc_channel_pool,
grpc_channel=grpc_channel,
)
self._panel_id = panel_id

@property
def panel_id(self) -> str:
"""Read-only accessor for the panel ID."""
return self._panel_id

def get_value(self, value_id: str) -> object:
"""Get the value for a control on the panel.
Args:
value_id: The id of the value
Returns:
The value
"""
return self._panel_client.get_value(self._panel_id, value_id)

def set_value(self, value_id: str, value: object) -> None:
"""Set the value for a control on the panel.
Args:
value_id: The id of the value
value: The value
"""
self._panel_client.set_value(self._panel_id, value_id, value)
1 change: 1 addition & 0 deletions src/nipanel/_streamlit_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
STREAMLIT_PYTHON_PANEL_SERVICE = "ni.pythonpanel.v1.PythonPanelService"
12 changes: 6 additions & 6 deletions src/nipanel/_streamlit_panel.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
from __future__ import annotations

from typing import final

import grpc
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool

from nipanel._panel import Panel
from nipanel._streamlit_constants import STREAMLIT_PYTHON_PANEL_SERVICE


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

PYTHON_PANEL_SERVICE = "ni.pythonpanel.v1.PythonPanelService"

__slots__ = ()

def __init__(
self,
panel_id: str,
Expand All @@ -36,8 +36,8 @@ def __init__(
super().__init__(
panel_id=panel_id,
panel_uri=streamlit_script_uri,
provided_interface=self.PYTHON_PANEL_SERVICE,
service_class=self.PYTHON_PANEL_SERVICE,
provided_interface=STREAMLIT_PYTHON_PANEL_SERVICE,
service_class=STREAMLIT_PYTHON_PANEL_SERVICE,
discovery_client=discovery_client,
grpc_channel_pool=grpc_channel_pool,
grpc_channel=grpc_channel,
Expand Down
41 changes: 41 additions & 0 deletions src/nipanel/_streamlit_panel_value_accessor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

from typing import final

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
from nipanel._streamlit_constants import STREAMLIT_PYTHON_PANEL_SERVICE


@final
class StreamlitPanelValueAccessor(PanelValueAccessor):
"""This class provides access to values for a Streamlit panel's controls."""

def __init__(
self,
panel_id: str,
*,
discovery_client: DiscoveryClient | None = None,
grpc_channel_pool: GrpcChannelPool | None = None,
grpc_channel: grpc.Channel | None = None,
) -> None:
"""Create an accessor for a Streamlit panel.

Args:
panel_id: A unique identifier for the panel.
grpc_channel: An optional gRPC channel to use for communication with the panel service.

Returns:
A new StreamlitPanelAccessor instance.
"""
super().__init__(
panel_id=panel_id,
provided_interface=STREAMLIT_PYTHON_PANEL_SERVICE,
service_class=STREAMLIT_PYTHON_PANEL_SERVICE,
discovery_client=discovery_client,
grpc_channel_pool=grpc_channel_pool,
grpc_channel=grpc_channel,
)
30 changes: 29 additions & 1 deletion tests/unit/test_streamlit_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest

import tests.types as test_types
from nipanel._streamlit_panel import StreamlitPanel
from nipanel import StreamlitPanel, StreamlitPanelValueAccessor
from tests.utils._fake_python_panel_service import FakePythonPanelService


Expand All @@ -25,6 +25,34 @@ def test___opened_panel___set_value___gets_same_value(
assert panel.get_value(value_id) == string_value


def test___opened_panel___panel_set_value___accessor_gets_same_value(
fake_panel_channel: grpc.Channel,
) -> None:
panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel)
panel.open_panel()
accessor = StreamlitPanelValueAccessor("my_panel", grpc_channel=fake_panel_channel)

value_id = "test_id"
string_value = "test_value"
panel.set_value(value_id, string_value)

assert accessor.get_value(value_id) == string_value


def test___opened_panel___accessor_set_value___panel_gets_same_value(
fake_panel_channel: grpc.Channel,
) -> None:
panel = StreamlitPanel("my_panel", "path/to/script", grpc_channel=fake_panel_channel)
panel.open_panel()
accessor = StreamlitPanelValueAccessor("my_panel", grpc_channel=fake_panel_channel)

value_id = "test_id"
string_value = "test_value"
accessor.set_value(value_id, string_value)

assert panel.get_value(value_id) == string_value


def test___first_open_panel_fails___open_panel___gets_value(
fake_python_panel_service: FakePythonPanelService,
fake_panel_channel: grpc.Channel,
Expand Down