Skip to content

Commit 103a6be

Browse files
Add support for deploying HoloViz Panel applications
This change adds support for deploying HoloViz Panel applications to Posit Connect. Panel is a popular Python framework for creating interactive dashboards and data apps. Changes include: - Added PYTHON_PANEL app mode (mode 18) to models.py - Added deploy and write-manifest commands for Panel apps in main.py - Added comprehensive tests for Panel manifest and bundle creation - Added test data files for Panel app testing This requires Posit Connect release 2025.10.0 or later. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent f2e79e6 commit 103a6be

File tree

5 files changed

+101
-0
lines changed

5 files changed

+101
-0
lines changed

rsconnect/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,6 +1871,7 @@ def deploy_app(
18711871
generate_deploy_python(app_mode=AppModes.BOKEH_APP, alias="bokeh", min_version="1.8.4")
18721872
generate_deploy_python(app_mode=AppModes.PYTHON_SHINY, alias="shiny", min_version="2022.07.0")
18731873
generate_deploy_python(app_mode=AppModes.PYTHON_GRADIO, alias="gradio", min_version="2024.12.0")
1874+
generate_deploy_python(app_mode=AppModes.PYTHON_PANEL, alias="panel", min_version="2025.10.0")
18741875

18751876

18761877
@deploy.command(
@@ -2408,6 +2409,7 @@ def manifest_writer(
24082409
generate_write_manifest_python(AppModes.PYTHON_SHINY, alias="shiny")
24092410
generate_write_manifest_python(AppModes.STREAMLIT_APP, alias="streamlit")
24102411
generate_write_manifest_python(AppModes.PYTHON_GRADIO, alias="gradio")
2412+
generate_write_manifest_python(AppModes.PYTHON_PANEL, alias="panel")
24112413

24122414

24132415
# noinspection SpellCheckingInspection

rsconnect/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class AppModes:
9898
PYTHON_SHINY = AppMode(15, "python-shiny", "Python Shiny Application")
9999
JUPYTER_VOILA = AppMode(16, "jupyter-voila", "Jupyter Voila Application")
100100
PYTHON_GRADIO = AppMode(17, "python-gradio", "Gradio Application")
101+
PYTHON_PANEL = AppMode(18, "python-panel", "HoloViz Panel Application")
101102

102103
_modes = [
103104
UNKNOWN,
@@ -118,6 +119,7 @@ class AppModes:
118119
PYTHON_SHINY,
119120
JUPYTER_VOILA,
120121
PYTHON_GRADIO,
122+
PYTHON_PANEL,
121123
]
122124

123125
Modes = Literal[
@@ -139,6 +141,7 @@ class AppModes:
139141
"python-shiny",
140142
"jupyter-voila",
141143
"python-gradio",
144+
"python-panel",
142145
]
143146

144147
_cloud_to_connect_modes = {

tests/test_bundle.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2868,6 +2868,76 @@ def test_make_api_bundle_gradio():
28682868
assert gradio_dir_ans["files"].keys() == bundle_json["files"].keys()
28692869

28702870

2871+
panel_dir = os.path.join(cur_dir, "./testdata/panel")
2872+
panel_file = os.path.join(cur_dir, "./testdata/panel/app.py")
2873+
2874+
2875+
def test_make_api_manifest_panel():
2876+
panel_dir_ans = {
2877+
"version": 1,
2878+
"locale": "en_US.UTF-8",
2879+
"metadata": {"appmode": "python-panel"},
2880+
"python": {
2881+
"version": "3.8.12",
2882+
"package_manager": {"name": "pip", "version": "23.0.1", "package_file": "requirements.txt"},
2883+
},
2884+
"files": {
2885+
"requirements.txt": {"checksum": "f90113cfbf5f67bfa6c5c6a5a8bc7eaa"},
2886+
"app.py": {"checksum": "e3b0c44298fc1c149afbf4c8996fb924"},
2887+
},
2888+
}
2889+
environment = Environment.create_python_environment(
2890+
panel_dir,
2891+
)
2892+
manifest, _ = make_api_manifest(
2893+
panel_dir,
2894+
None,
2895+
AppModes.PYTHON_PANEL,
2896+
environment,
2897+
None,
2898+
None,
2899+
)
2900+
2901+
assert panel_dir_ans["metadata"] == manifest["metadata"]
2902+
assert panel_dir_ans["files"].keys() == manifest["files"].keys()
2903+
2904+
2905+
def test_make_api_bundle_panel():
2906+
panel_dir_ans = {
2907+
"version": 1,
2908+
"locale": "en_US.UTF-8",
2909+
"metadata": {"appmode": "python-panel"},
2910+
"python": {
2911+
"version": "3.8.12",
2912+
"package_manager": {"name": "pip", "version": "23.0.1", "package_file": "requirements.txt"},
2913+
},
2914+
"files": {
2915+
"requirements.txt": {"checksum": "f90113cfbf5f67bfa6c5c6a5a8bc7eaa"},
2916+
"app.py": {"checksum": "e3b0c44298fc1c149afbf4c8996fb924"},
2917+
},
2918+
}
2919+
environment = Environment.create_python_environment(
2920+
panel_dir,
2921+
)
2922+
with make_api_bundle(
2923+
panel_dir,
2924+
None,
2925+
AppModes.PYTHON_PANEL,
2926+
environment,
2927+
None,
2928+
None,
2929+
) as bundle, tarfile.open(mode="r:gz", fileobj=bundle) as tar:
2930+
names = sorted(tar.getnames())
2931+
assert names == [
2932+
"app.py",
2933+
"manifest.json",
2934+
"requirements.txt",
2935+
]
2936+
bundle_json = json.loads(tar.extractfile("manifest.json").read().decode("utf-8"))
2937+
assert panel_dir_ans["metadata"] == bundle_json["metadata"]
2938+
assert panel_dir_ans["files"].keys() == bundle_json["files"].keys()
2939+
2940+
28712941
empty_manifest_file = os.path.join(cur_dir, "./testdata/Manifest_data/empty_manifest.json")
28722942
missing_file_manifest = os.path.join(cur_dir, "./testdata/Manifest_data/missing_file_manifest.json")
28732943

tests/testdata/panel/app.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import panel as pn
2+
3+
pn.extension()
4+
5+
def greet(name):
6+
return f"Hello, {name}!"
7+
8+
text_input = pn.widgets.TextInput(name="Enter your name", placeholder="Type here...")
9+
button = pn.widgets.Button(name="Greet", button_type="primary")
10+
11+
output = pn.pane.Markdown("Click the button to see a greeting!")
12+
13+
def update_output(event):
14+
output.object = greet(text_input.value)
15+
16+
button.on_click(update_output)
17+
18+
app = pn.Column(
19+
"# Panel Greeting App",
20+
text_input,
21+
button,
22+
output
23+
)
24+
25+
app.servable()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
panel

0 commit comments

Comments
 (0)