diff --git a/rsconnect/main.py b/rsconnect/main.py index 357d2b9a..02ff5384 100644 --- a/rsconnect/main.py +++ b/rsconnect/main.py @@ -1741,6 +1741,7 @@ def deploy_app( generate_deploy_python(app_mode=AppModes.BOKEH_APP, alias="bokeh", min_version="1.8.4") generate_deploy_python(app_mode=AppModes.PYTHON_SHINY, alias="shiny", min_version="2022.07.0") generate_deploy_python(app_mode=AppModes.PYTHON_GRADIO, alias="gradio", min_version="2024.12.0") +generate_deploy_python(app_mode=AppModes.PYTHON_PANEL, alias="panel", min_version="2025.10.0") @deploy.command( @@ -2278,6 +2279,7 @@ def manifest_writer( generate_write_manifest_python(AppModes.PYTHON_SHINY, alias="shiny") generate_write_manifest_python(AppModes.STREAMLIT_APP, alias="streamlit") generate_write_manifest_python(AppModes.PYTHON_GRADIO, alias="gradio") +generate_write_manifest_python(AppModes.PYTHON_PANEL, alias="panel") # noinspection SpellCheckingInspection diff --git a/rsconnect/models.py b/rsconnect/models.py index bfaf5a2e..c6fd5c07 100644 --- a/rsconnect/models.py +++ b/rsconnect/models.py @@ -98,6 +98,7 @@ class AppModes: PYTHON_SHINY = AppMode(15, "python-shiny", "Python Shiny Application") JUPYTER_VOILA = AppMode(16, "jupyter-voila", "Jupyter Voila Application") PYTHON_GRADIO = AppMode(17, "python-gradio", "Gradio Application") + PYTHON_PANEL = AppMode(18, "python-panel", "HoloViz Panel Application") _modes = [ UNKNOWN, @@ -118,6 +119,7 @@ class AppModes: PYTHON_SHINY, JUPYTER_VOILA, PYTHON_GRADIO, + PYTHON_PANEL, ] Modes = Literal[ @@ -139,6 +141,7 @@ class AppModes: "python-shiny", "jupyter-voila", "python-gradio", + "python-panel", ] _cloud_to_connect_modes = { diff --git a/tests/test_bundle.py b/tests/test_bundle.py index e300954e..7cab72ed 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -2868,6 +2868,76 @@ def test_make_api_bundle_gradio(): assert gradio_dir_ans["files"].keys() == bundle_json["files"].keys() +panel_dir = os.path.join(cur_dir, "./testdata/panel") +panel_file = os.path.join(cur_dir, "./testdata/panel/app.py") + + +def test_make_api_manifest_panel(): + panel_dir_ans = { + "version": 1, + "locale": "en_US.UTF-8", + "metadata": {"appmode": "python-panel"}, + "python": { + "version": "3.8.12", + "package_manager": {"name": "pip", "version": "23.0.1", "package_file": "requirements.txt"}, + }, + "files": { + "requirements.txt": {"checksum": "f90113cfbf5f67bfa6c5c6a5a8bc7eaa"}, + "app.py": {"checksum": "e3b0c44298fc1c149afbf4c8996fb924"}, + }, + } + environment = Environment.create_python_environment( + panel_dir, + ) + manifest, _ = make_api_manifest( + panel_dir, + None, + AppModes.PYTHON_PANEL, + environment, + None, + None, + ) + + assert panel_dir_ans["metadata"] == manifest["metadata"] + assert panel_dir_ans["files"].keys() == manifest["files"].keys() + + +def test_make_api_bundle_panel(): + panel_dir_ans = { + "version": 1, + "locale": "en_US.UTF-8", + "metadata": {"appmode": "python-panel"}, + "python": { + "version": "3.8.12", + "package_manager": {"name": "pip", "version": "23.0.1", "package_file": "requirements.txt"}, + }, + "files": { + "requirements.txt": {"checksum": "f90113cfbf5f67bfa6c5c6a5a8bc7eaa"}, + "app.py": {"checksum": "e3b0c44298fc1c149afbf4c8996fb924"}, + }, + } + environment = Environment.create_python_environment( + panel_dir, + ) + with make_api_bundle( + panel_dir, + None, + AppModes.PYTHON_PANEL, + environment, + None, + None, + ) as bundle, tarfile.open(mode="r:gz", fileobj=bundle) as tar: + names = sorted(tar.getnames()) + assert names == [ + "app.py", + "manifest.json", + "requirements.txt", + ] + bundle_json = json.loads(tar.extractfile("manifest.json").read().decode("utf-8")) + assert panel_dir_ans["metadata"] == bundle_json["metadata"] + assert panel_dir_ans["files"].keys() == bundle_json["files"].keys() + + empty_manifest_file = os.path.join(cur_dir, "./testdata/Manifest_data/empty_manifest.json") missing_file_manifest = os.path.join(cur_dir, "./testdata/Manifest_data/missing_file_manifest.json") diff --git a/tests/testdata/panel/app.py b/tests/testdata/panel/app.py new file mode 100644 index 00000000..09c384a5 --- /dev/null +++ b/tests/testdata/panel/app.py @@ -0,0 +1,24 @@ +import panel as pn + +pn.extension() + + +def greet(name): + return f"Hello, {name}!" + + +text_input = pn.widgets.TextInput(name="Enter your name", placeholder="Type here...") +button = pn.widgets.Button(name="Greet", button_type="primary") + +output = pn.pane.Markdown("Click the button to see a greeting!") + + +def update_output(event): + output.object = greet(text_input.value) + + +button.on_click(update_output) + +app = pn.Column("# Panel Greeting App", text_input, button, output) + +app.servable() diff --git a/tests/testdata/panel/requirements.txt b/tests/testdata/panel/requirements.txt new file mode 100644 index 00000000..f9ec12dd --- /dev/null +++ b/tests/testdata/panel/requirements.txt @@ -0,0 +1 @@ +panel