Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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__
82 changes: 82 additions & 0 deletions src/nipanel/_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

import sys
import uuid
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_uri: str
_panel_id: str

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

def __init__(self, panel_uri: str) -> None:
"""Initialize the panel."""
self._panel_uri = panel_uri
self._panel_id = str(uuid.uuid4())

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
22 changes: 22 additions & 0 deletions src/nipanel/_streamlit_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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, streamlit_script_uri: str) -> None:
"""Create a panel using a Streamlit script for the user interface.

Args:
streamlit_script_uri: The file path of the Streamlit script.

Returns:
A new StreamlitPanel instance.
"""
super().__init__(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."""
33 changes: 33 additions & 0 deletions tests/unit/test_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import nipanel


def test___streamlit_panel___has_panel_id_and_panel_uri() -> None:
panel = nipanel.StreamlitPanel("path/to/script")
assert panel._panel_id is not None
assert panel._panel_uri == "path/to/script"


def test___two_panels___have_different_panel_ids() -> None:
panel1 = nipanel.StreamlitPanel("path/to/script1")
panel2 = nipanel.StreamlitPanel("path/to/script2")
assert panel1._panel_id != panel2._panel_id


def test___connected_panel___set_value___gets_same_value() -> None:
panel = nipanel.StreamlitPanel("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("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.