Skip to content

Commit c1f163e

Browse files
authored
Merge pull request #277 from rstudio/bcwu-voila
voila mode
2 parents 94dbd28 + 60fe87f commit c1f163e

File tree

12 files changed

+1700
-16
lines changed

12 files changed

+1700
-16
lines changed

rsconnect/bundle.py

+458-15
Large diffs are not rendered by default.

rsconnect/main.py

+208
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@
4949
make_api_bundle,
5050
make_notebook_html_bundle,
5151
make_notebook_source_bundle,
52+
make_voila_bundle,
5253
read_manifest_app_mode,
5354
write_notebook_manifest_json,
5455
write_api_manifest_json,
5556
write_environment_file,
5657
write_quarto_manifest_json,
58+
write_voila_manifest_json,
5759
validate_entry_point,
5860
validate_extra_files,
5961
validate_file_is_notebook,
@@ -842,6 +844,108 @@ def deploy_notebook(
842844
ce.deploy_bundle().save_deployed_info().emit_task_log()
843845

844846

847+
# noinspection SpellCheckingInspection,DuplicatedCode
848+
@deploy.command(
849+
name="voila",
850+
short_help="Deploy Jupyter notebook in Voila mode to RStudio Connect [v2023.03.0+].",
851+
help=("Deploy a Jupyter notebook in Voila mode to RStudio Connect."),
852+
no_args_is_help=True,
853+
)
854+
@server_args
855+
@content_args
856+
@click.option(
857+
"--entrypoint",
858+
"-e",
859+
help=("The module and executable object which serves as the entry point."),
860+
)
861+
@click.option(
862+
"--multi-notebook",
863+
"-m",
864+
is_flag=True,
865+
help=("Deploy in multi-notebook mode."),
866+
)
867+
@click.option(
868+
"--exclude",
869+
"-x",
870+
multiple=True,
871+
help=(
872+
"Specify a glob pattern for ignoring files when building the bundle. Note that your shell may try "
873+
"to expand this which will not do what you expect. Generally, it's safest to quote the pattern. "
874+
"This option may be repeated."
875+
),
876+
)
877+
@click.option(
878+
"--python",
879+
"-p",
880+
type=click.Path(exists=True),
881+
help=(
882+
"Path to Python interpreter whose environment should be used. "
883+
"The Python environment must have the rsconnect package installed."
884+
),
885+
)
886+
@click.option(
887+
"--force-generate",
888+
"-g",
889+
is_flag=True,
890+
help='Force generating "requirements.txt", even if it already exists.',
891+
)
892+
@click.option(
893+
"--image",
894+
"-I",
895+
help="Target image to be used during content execution (only applicable if the RStudio Connect "
896+
"server is configured to use off-host execution)",
897+
)
898+
@click.argument("path", type=click.Path(exists=True, dir_okay=True, file_okay=True))
899+
@click.argument(
900+
"extra_files",
901+
nargs=-1,
902+
type=click.Path(exists=True, dir_okay=False, file_okay=True),
903+
)
904+
@cli_exception_handler
905+
def deploy_voila(
906+
path: str = None,
907+
entrypoint: str = None,
908+
python=None,
909+
force_generate=False,
910+
extra_files=None,
911+
exclude=None,
912+
image: str = "",
913+
title: str = None,
914+
env_vars: typing.Dict[str, str] = None,
915+
verbose: bool = False,
916+
new: bool = False,
917+
app_id: str = None,
918+
name: str = None,
919+
server: str = None,
920+
api_key: str = None,
921+
insecure: bool = False,
922+
cacert: typing.IO = None,
923+
connect_server: api.RSConnectServer = None,
924+
multi_notebook: bool = False,
925+
):
926+
kwargs = locals()
927+
set_verbosity(verbose)
928+
app_mode = AppModes.JUPYTER_VOILA
929+
kwargs["extra_files"] = extra_files = validate_extra_files(dirname(path), extra_files)
930+
environment = create_python_environment(
931+
path if isdir(path) else dirname(path),
932+
force_generate,
933+
python,
934+
)
935+
ce = RSConnectExecutor(**kwargs).validate_server().validate_app_mode(app_mode=app_mode)
936+
ce.make_bundle(
937+
make_voila_bundle,
938+
path,
939+
entrypoint,
940+
extra_files,
941+
exclude,
942+
force_generate,
943+
environment,
944+
image=image,
945+
multi_notebook=multi_notebook,
946+
).deploy_bundle().save_deployed_info().emit_task_log()
947+
948+
845949
# noinspection SpellCheckingInspection,DuplicatedCode
846950
@deploy.command(
847951
name="manifest",
@@ -1358,6 +1462,110 @@ def write_manifest_notebook(
13581462
write_environment_file(environment, base_dir)
13591463

13601464

1465+
@write_manifest.command(
1466+
name="voila",
1467+
short_help="Create a manifest.json file for a Voila notebook.",
1468+
help=(
1469+
"Create a manifest.json file for a Voila notebook for later deployment. "
1470+
'This will create an environment file ("requirements.txt") if one does '
1471+
"not exist. All files are created in the same directory as the notebook file."
1472+
),
1473+
)
1474+
@click.option("--overwrite", "-o", is_flag=True, help="Overwrite manifest.json, if it exists.")
1475+
@click.option(
1476+
"--python",
1477+
"-p",
1478+
type=click.Path(exists=True),
1479+
help="Path to Python interpreter whose environment should be used. "
1480+
+ "The Python environment must have the rsconnect package installed.",
1481+
)
1482+
@click.option(
1483+
"--force-generate",
1484+
"-g",
1485+
is_flag=True,
1486+
help='Force generating "requirements.txt", even if it already exists.',
1487+
)
1488+
@click.option("--verbose", "-v", "verbose", is_flag=True, help="Print detailed messages")
1489+
@click.option(
1490+
"--image",
1491+
"-I",
1492+
help="Target image to be used during content execution (only applicable if the RStudio Connect "
1493+
"server is configured to use off-host execution)",
1494+
)
1495+
@click.argument("path", type=click.Path(exists=True, dir_okay=True, file_okay=True))
1496+
@click.argument(
1497+
"extra_files",
1498+
nargs=-1,
1499+
type=click.Path(exists=True, dir_okay=False, file_okay=True),
1500+
)
1501+
@click.option("--entrypoint", "-e", help=("The module and executable object which serves as the entry point."))
1502+
@click.option(
1503+
"--exclude",
1504+
"-x",
1505+
multiple=True,
1506+
help=(
1507+
"Specify a glob pattern for ignoring files when building the bundle. Note that your shell may try "
1508+
"to expand this which will not do what you expect. Generally, it's safest to quote the pattern. "
1509+
"This option may be repeated."
1510+
),
1511+
)
1512+
@click.option(
1513+
"--multi-notebook",
1514+
"-m",
1515+
is_flag=True,
1516+
help=("Set the manifest for multi-notebook mode."),
1517+
)
1518+
def write_manifest_voila(
1519+
path: str,
1520+
entrypoint: str,
1521+
overwrite,
1522+
python,
1523+
force_generate,
1524+
verbose,
1525+
extra_files,
1526+
exclude,
1527+
image,
1528+
multi_notebook,
1529+
):
1530+
set_verbosity(verbose)
1531+
with cli_feedback("Checking arguments"):
1532+
base_dir = dirname(path)
1533+
extra_files = validate_extra_files(base_dir, extra_files)
1534+
manifest_path = join(base_dir, "manifest.json")
1535+
1536+
if exists(manifest_path) and not overwrite:
1537+
raise RSConnectException("manifest.json already exists. Use --overwrite to overwrite.")
1538+
1539+
with cli_feedback("Inspecting Python environment"):
1540+
python, environment = get_python_env_info(path, python, False, force_generate)
1541+
1542+
_warn_on_ignored_conda_env(environment)
1543+
1544+
environment_file_exists = exists(join(base_dir, environment.filename))
1545+
1546+
if environment_file_exists and not force_generate:
1547+
click.secho(
1548+
" Warning: %s already exists and will not be overwritten." % environment.filename,
1549+
fg="yellow",
1550+
)
1551+
else:
1552+
with cli_feedback("Creating %s" % environment.filename):
1553+
write_environment_file(environment, base_dir)
1554+
1555+
with cli_feedback("Creating manifest.json"):
1556+
write_voila_manifest_json(
1557+
path,
1558+
entrypoint,
1559+
environment,
1560+
AppModes.JUPYTER_VOILA,
1561+
extra_files,
1562+
exclude,
1563+
force_generate,
1564+
image,
1565+
multi_notebook,
1566+
)
1567+
1568+
13611569
@write_manifest.command(
13621570
name="quarto",
13631571
short_help="Create a manifest.json file for Quarto content.",

rsconnect/models.py

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class AppModes(object):
7878
SHINY_QUARTO = AppMode(13, "quarto-shiny", "Shiny Quarto Document")
7979
STATIC_QUARTO = AppMode(14, "quarto-static", "Quarto Document", ".qmd")
8080
PYTHON_SHINY = AppMode(15, "python-shiny", "Python Shiny Application")
81+
JUPYTER_VOILA = AppMode(16, "jupyter-voila", "Jupyter Voila Application")
8182

8283
_modes = [
8384
UNKNOWN,
@@ -96,6 +97,7 @@ class AppModes(object):
9697
SHINY_QUARTO,
9798
STATIC_QUARTO,
9899
PYTHON_SHINY,
100+
JUPYTER_VOILA,
99101
]
100102

101103
_cloud_to_connect_modes = {

0 commit comments

Comments
 (0)