Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ ignore_missing_imports = true
skips = [
"B101", # assert_used
]

[tool.pytest.ini_options]
addopts = "--doctest-modules --strict-markers"
testpaths = ["src/nipanel", "tests"]
9 changes: 9 additions & 0 deletions src/nipanel/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
"""The NI Panel."""

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

__all__ = ["Panel", "StreamlitPanel"]

# Hide that it was defined in a helper file
Panel.__module__ = __name__
StreamlitPanel.__module__ = __name__
91 changes: 91 additions & 0 deletions src/nipanel/_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from __future__ import annotations

import sys
from abc import ABC, abstractmethod
from types import TracebackType
from typing import Optional, Type, TYPE_CHECKING

from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub

if TYPE_CHECKING:
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


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

_stub: PythonPanelServiceStub | None
_panel_id: str
_panel_uri: str

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

def __init__(self, panel_id: str, panel_uri: str) -> None:
"""Initialize the panel."""
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."""
return self._panel_uri

def __enter__(self) -> Self:
"""Enter the runtime context related to this object."""
self.connect()
return self

def __exit__(
self,
exctype: Optional[Type[BaseException]],
excinst: Optional[BaseException],
exctb: Optional[TracebackType],
) -> Optional[bool]:
"""Exit the runtime context related to this object."""
self.disconnect()
return None

def connect(self) -> None:
"""Connect to the panel and open it."""
# TODO: AB#3095680 - Use gRPC pool management, create the _stub, and call _stub.Connect
self._resolve_service_location()

def disconnect(self) -> None:
"""Disconnect from the panel (does not close the panel)."""
# TODO: AB#3095680 - Use gRPC pool management, call _stub.Disconnect
pass

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
"""
# TODO: AB#3095681 - get the Any from _stub.GetValue and convert it to the correct type
return "placeholder value"

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
"""
# TODO: AB#3095681 - Convert the value to an Any and pass it to _stub.SetValue
pass

@abstractmethod
def _resolve_service_location(self) -> str:
"""Resolve the service location for the panel."""
raise NotImplementedError
23 changes: 23 additions & 0 deletions src/nipanel/_streamlit_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from nipanel._panel import Panel


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

__slots__ = ()

def __init__(self, panel_id: str, streamlit_script_uri: str) -> None:
"""Create a panel using a Streamlit script for the user interface.

Args:
panel_id: A unique identifier for the panel.
streamlit_script_uri: The file path of the Streamlit script.

Returns:
A new StreamlitPanel instance.
"""
super().__init__(panel_id, streamlit_script_uri)

def _resolve_service_location(self) -> str:
# TODO: AB#3095680 - resolve to the Streamlit PythonPanelService
return ""
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the `nipanel` package."""
27 changes: 27 additions & 0 deletions tests/unit/test_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import nipanel


def test___streamlit_panel___has_panel_id_and_panel_uri() -> None:
panel = nipanel.StreamlitPanel("my_panel", "path/to/script")
assert panel.panel_id == "my_panel"
assert panel.panel_uri == "path/to/script"


def test___connected_panel___set_value___gets_same_value() -> None:
panel = nipanel.StreamlitPanel("my_panel", "path/to/script")
panel.connect()

panel.set_value("test_id", "test_value")

# TODO: AB#3095681 - change asserted value to test_value
assert panel.get_value("test_id") == "placeholder value"
panel.disconnect()


def test___with_panel___set_value___gets_same_value() -> None:
with nipanel.StreamlitPanel("my_panel", "path/to/script") as panel:

panel.set_value("test_id", "test_value")

# TODO: AB#3095681 - change asserted value to test_value
assert panel.get_value("test_id") == "placeholder value"
2 changes: 0 additions & 2 deletions tests/unit/test_placeholder.py

This file was deleted.