Skip to content

Commit 6916ca9

Browse files
committed
Merge branch 'main' into users/vgleason/refresh_panel
2 parents 8e8c00c + 96498f7 commit 6916ca9

File tree

7 files changed

+211
-90
lines changed

7 files changed

+211
-90
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,6 @@ cython_debug/
172172

173173
# PyPI configuration file
174174
.pypirc
175+
176+
# VS Code settings
177+
.vscode/launch.json

CONTRIBUTING.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,50 @@ poetry run sphinx-build docs docs/_build --builder html --fail-on-warning
5454
start docs\_build\index.html
5555
```
5656

57+
# Debugging on the streamlit side
58+
59+
Debugging the measurement script can be done using standard Python debugging techniques. However, debugging the Streamlit script—or any code invoked by the Streamlit script—is more complex because it runs in a separate process launched by the PythonPanelServer. To debug the Streamlit script, you can use debugpy to attach the Visual Studio Code debugger as follows:
60+
61+
## Instrument Streamlit script to debug
62+
63+
To enable debugpy debugging, include this code in your streamlit script:
64+
65+
```python
66+
import debugpy # type: ignore
67+
68+
try:
69+
debugpy.listen(("localhost", 5678))
70+
debugpy.wait_for_client()
71+
except RuntimeError as e:
72+
if "debugpy.listen() has already been called on this process" not in str(e):
73+
raise
74+
```
75+
76+
The `debugpy.listen()` function opens a port that allows the debugger to attach to the running process. You can specify any available port, as long as it matches the port configured in the launch.json file shown below. Since calling listen() more than once will raise an exception, it is wrapped in a try block to prevent the script from crashing if it is rerun.
77+
78+
The `debugpy.wait_for_client()` function pauses script execution until the debugger is attached. This is helpful if you need to debug initialization code, but you can omit this line if it is not required.
79+
80+
The `import debugpy` statement includes a type suppression comment to satisfy mypy.
81+
82+
## Add debugpy configuration in launch.json
83+
84+
You will also need this configuration in your launch.json:
85+
86+
```json
87+
{
88+
"name": "Attach to Streamlit at localhost:5678",
89+
"type": "debugpy",
90+
"request": "attach",
91+
"connect": {
92+
"host": "localhost",
93+
"port": 5678
94+
},
95+
"justMyCode": false
96+
}
97+
```
98+
99+
After running your measurement script and allowing the PythonPanelServer to launch Streamlit with your Streamlit script, you can attach the debugger by clicking the **Attach to Streamlit at localhost:5678** button in the VS Code **Run and Debug** tab. Once attached, you can set breakpoints and use all standard debugging features in your Streamlit script, as well as in any nipanel code invoked by the Streamlit script.
100+
57101
# Developer Certificate of Origin (DCO)
58102

59103
Developer's Certificate of Origin 1.1

examples/sample/sample_panel.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
import nipanel
66

7-
panel = nipanel.StreamlitPanelValueAccessor(panel_id="sample_panel")
8-
panel.refresh()
7+
panel = nipanel.initialize_panel()
98

109
st.title("Sample Panel")
1110

poetry.lock

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

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ni-measurement-plugin-sdk = {version=">=2.3"}
1414
typing-extensions = ">=4.13.2"
1515
streamlit = ">=1.24"
1616
nitypes = {version=">=0.1.0dev1", allow-prereleases=true}
17+
debugpy = "^1.8.1"
1718

1819
[tool.poetry.group.dev.dependencies]
1920
types-grpcio = ">=1.0"

src/nipanel/__init__.py

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

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

7-
__all__ = ["Panel", "StreamlitPanel", "StreamlitPanelValueAccessor"]
8+
__all__ = ["Panel", "StreamlitPanel", "StreamlitPanelValueAccessor", "initialize_panel"]
89

910
# Hide that it was defined in a helper file
1011
Panel.__module__ = __name__
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import cast
2+
3+
import streamlit as st
4+
5+
from nipanel._streamlit_panel_value_accessor import StreamlitPanelValueAccessor
6+
7+
PANEL_ACCESSOR_KEY = "StreamlitPanelValueAccessor"
8+
9+
10+
def initialize_panel() -> StreamlitPanelValueAccessor:
11+
"""Initialize and return the Streamlit panel value accessor.
12+
13+
This function retrieves the Streamlit panel value accessor for the current Streamlit script.
14+
It is typically used within a Streamlit script to access and manipulate panel values.
15+
The accessor will be cached in the Streamlit session state to ensure that it is reused across
16+
reruns of the script.
17+
18+
Returns:
19+
A StreamlitPanelValueAccessor instance for the current panel.
20+
"""
21+
if PANEL_ACCESSOR_KEY not in st.session_state:
22+
st.session_state[PANEL_ACCESSOR_KEY] = _initialize_panel_from_base_path()
23+
24+
panel = cast(StreamlitPanelValueAccessor, st.session_state[PANEL_ACCESSOR_KEY])
25+
# TODO: declare the refresh component here
26+
return panel
27+
28+
29+
def _initialize_panel_from_base_path() -> StreamlitPanelValueAccessor:
30+
"""Validate and parse the Streamlit base URL path and return a StreamlitPanelValueAccessor."""
31+
base_url_path = st.get_option("server.baseUrlPath")
32+
if not base_url_path.startswith("/"):
33+
raise ValueError("Invalid or missing Streamlit server.baseUrlPath option.")
34+
panel_id = base_url_path.split("/")[-1]
35+
if not panel_id:
36+
raise ValueError(f"Panel ID is empty in baseUrlPath: '{base_url_path}'")
37+
return StreamlitPanelValueAccessor(panel_id)

0 commit comments

Comments
 (0)