Skip to content
Closed
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
53 changes: 53 additions & 0 deletions rsconnect/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,59 @@ def validate_quarto_engines(inspect: QuartoInspectResult):
return engines


def is_quarto_shiny(inspect: QuartoInspectResult) -> bool:
"""
Determines if the Quarto document uses Shiny by checking the quarto inspect output.

Quarto documents with `server: shiny` in their YAML front matter will have
this reflected in the inspect output in multiple locations:
- formats.<format>.metadata.server.type == "shiny"
- fileInformation.<path>.metadata.server == "shiny"

:param inspect: The parsed JSON from a 'quarto inspect' against the project.
:return: True if the document uses Shiny, False otherwise.
"""
# Cast to Any for accessing fields not defined in the TypedDict.
# The quarto inspect output contains many more fields than we've typed.
inspect_any = cast(typing.Any, inspect)

# Check formats.<format>.metadata.server.type
formats = inspect_any.get("formats", {})
if isinstance(formats, dict):
for format_data in formats.values():
if isinstance(format_data, dict):
metadata = format_data.get("metadata", {})
if isinstance(metadata, dict):
server = metadata.get("server", {})
if isinstance(server, dict) and server.get("type") == "shiny":
return True

# Check fileInformation.<path>.metadata.server
file_info = inspect_any.get("fileInformation", {})
if isinstance(file_info, dict):
for file_data in file_info.values():
if isinstance(file_data, dict):
metadata = file_data.get("metadata", {})
if isinstance(metadata, dict):
server = metadata.get("server")
if server == "shiny":
return True

return False


def infer_quarto_app_mode(inspect: QuartoInspectResult) -> AppMode:
"""
Infers the appropriate app mode for a Quarto document based on the inspect output.

:param inspect: The parsed JSON from a 'quarto inspect' against the project.
:return: AppModes.SHINY_QUARTO if the document uses Shiny, AppModes.STATIC_QUARTO otherwise.
"""
if is_quarto_shiny(inspect):
return AppModes.SHINY_QUARTO
return AppModes.STATIC_QUARTO


# ===============================================================================
# START: The following deprecated functions are here only for the vetiver-python
# package.
Expand Down
9 changes: 6 additions & 3 deletions rsconnect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
cli_feedback,
create_quarto_deployment_bundle,
describe_manifest,
infer_quarto_app_mode,
quarto_inspect,
set_verbosity,
test_api_key,
Expand Down Expand Up @@ -1598,6 +1599,7 @@ def deploy_quarto(
logger.debug("Quarto: %s" % quarto)
inspect = quarto_inspect(quarto, file_or_directory)
engines = validate_quarto_engines(inspect)
app_mode = infer_quarto_app_mode(inspect)

environment = None
if "jupyter" in engines:
Expand Down Expand Up @@ -1635,13 +1637,13 @@ def deploy_quarto(

(
ce.validate_server()
.validate_app_mode(app_mode=AppModes.STATIC_QUARTO)
.validate_app_mode(app_mode=app_mode)
.make_bundle(
create_quarto_deployment_bundle,
file_or_directory,
extra_files,
exclude,
AppModes.STATIC_QUARTO,
app_mode,
inspect,
environment,
image=image,
Expand Down Expand Up @@ -2542,11 +2544,12 @@ def write_manifest_quarto(
with cli_feedback("Creating %s" % environment.filename):
write_environment_file(environment, base_dir)

app_mode = infer_quarto_app_mode(inspect)
with cli_feedback("Creating manifest.json"):
write_quarto_manifest_json(
file_or_directory,
inspect,
AppModes.STATIC_QUARTO,
app_mode,
environment,
extra_files,
exclude,
Expand Down
111 changes: 110 additions & 1 deletion tests/test_actions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,118 @@
import os
from unittest import TestCase

from rsconnect.actions import _verify_server
from rsconnect.actions import _verify_server, infer_quarto_app_mode, is_quarto_shiny
from rsconnect.api import RSConnectServer
from rsconnect.exception import RSConnectException
from rsconnect.models import AppModes


class TestQuartoShinyDetection(TestCase):
def test_is_quarto_shiny_via_formats_metadata(self):
"""Test detection via formats.<format>.metadata.server.type"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["jupyter"],
"formats": {
"html": {
"metadata": {
"server": {"type": "shiny"},
},
},
},
}
self.assertTrue(is_quarto_shiny(inspect))

def test_is_quarto_shiny_via_file_information(self):
"""Test detection via fileInformation.<path>.metadata.server"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["jupyter"],
"fileInformation": {
"/path/to/app.qmd": {
"metadata": {
"server": "shiny",
},
},
},
}
self.assertTrue(is_quarto_shiny(inspect))

def test_is_quarto_shiny_static_document(self):
"""Test that static documents are not detected as Shiny"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["jupyter"],
"formats": {
"html": {
"metadata": {
"title": "My Document",
},
},
},
}
self.assertFalse(is_quarto_shiny(inspect))

def test_is_quarto_shiny_empty_inspect(self):
"""Test with minimal inspect output"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["markdown"],
}
self.assertFalse(is_quarto_shiny(inspect))

def test_is_quarto_shiny_wrong_server_type(self):
"""Test that documents with wrong server type are not detected as Shiny"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["jupyter"],
"formats": {
"html": {
"metadata": {
"server": {"type": "other"},
},
},
},
}
self.assertFalse(is_quarto_shiny(inspect))

def test_is_quarto_shiny_wrong_server_value(self):
"""Test that documents with wrong server value in fileInformation are not detected as Shiny"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["jupyter"],
"fileInformation": {
"/path/to/app.qmd": {
"metadata": {
"server": "other",
},
},
},
}
self.assertFalse(is_quarto_shiny(inspect))

def test_infer_quarto_app_mode_shiny(self):
"""Test that Shiny documents get SHINY_QUARTO mode"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["jupyter"],
"formats": {
"html": {
"metadata": {
"server": {"type": "shiny"},
},
},
},
}
self.assertEqual(infer_quarto_app_mode(inspect), AppModes.SHINY_QUARTO)

def test_infer_quarto_app_mode_static(self):
"""Test that static documents get STATIC_QUARTO mode"""
inspect = {
"quarto": {"version": "1.3.0"},
"engines": ["markdown"],
}
self.assertEqual(infer_quarto_app_mode(inspect), AppModes.STATIC_QUARTO)


class TestActions(TestCase):
Expand Down
Loading