Skip to content

Commit

Permalink
Merge pull request #326 from rstudio/migrate-shinylive
Browse files Browse the repository at this point in the history
  • Loading branch information
wch authored Sep 2, 2022
2 parents cacc017 + f2689a3 commit bded4e7
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 231 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Closed [#312](https://github.com/rstudio/py-shiny/issues/312): Matplotlib plots in a `@render.plot` can now use the global figure, instead of returning a `figure` object. ([#314](https://github.com/rstudio/py-shiny/pull/314))

* Disabled `shiny static` command, in favor of `shinylive export` from the shinylive package. ([#326](https://github.com/rstudio/py-shiny/pull/326))

### Bug fixes


Expand Down
2 changes: 1 addition & 1 deletion shiny/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""A package for building reactive web applications."""

__version__ = "0.2.5.9000"
__version__ = "0.2.5.9001"

from ._shinyenv import is_pyodide as _is_pyodide

Expand Down
135 changes: 32 additions & 103 deletions shiny/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,123 +350,52 @@ def create(appdir: str) -> None:


@main.command(
help="""Turn a Shiny app into a statically deployable bundle.
APPDIR is the directory containing the Shiny application.
DESTDIR is the destination directory where the output files will be written to. This
directory can be deployed as a static web site.
This will not deploy the contents of venv/ or any files that start with '.'
After writing the output files, you can serve them locally with the following command:
python3 -m http.server --directory DESTDIR 8008
"""
)
@click.argument("appdir", type=str)
@click.argument("destdir", type=str)
@click.option(
"--verbose",
is_flag=True,
default=False,
help="Print debugging information when copying files.",
show_default=True,
help="""The functionality from `shiny static` has been moved to the shinylive package.
Please install shinylive and use `shinylive export` instead of `shiny static`:
\b
shiny static-assets remove
pip install shinylive
shinylive export APPDIR DESTDIR
""",
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
),
)
@click.option(
"--subdir",
type=str,
default=None,
help="Subdir in which to put the app.",
show_default=True,
)
@click.option(
"--full-shinylive",
is_flag=True,
default=False,
help="Include the full Shinylive bundle, including all Pyodide packages. Without this flag, only the packages needed to run the application are included.",
show_default=True,
)
def static(
appdir: str,
destdir: str,
subdir: Union[str, None],
verbose: bool,
full_shinylive: bool,
) -> None:
_static.deploy_static(
appdir,
destdir,
subdir=subdir,
verbose=verbose,
full_shinylive=full_shinylive,
def static() -> None:
print(
"""The functionality from `shiny static` has been moved to the shinylive package.
Please install shinylive and use `shinylive export` instead of `shiny static`:
shiny static-assets remove
pip install shinylive
shinylive export APPDIR DESTDIR
"""
)
sys.exit(1)


@main.command(
help="""Manage local copy of assets for static app deployment.
no_args_is_help=True,
help="""Manage local copy of assets for static app deployment. (Deprecated)
\b
Commands:
download: Download assets from the remote server.
remove: Remove local copies of assets.
info: Print information about the local assets.
copy: Copy shinylive assets from a local directory. Must be used with --source.
"""
""",
)
@click.argument("command", type=str)
@click.option(
"--version",
type=str,
default=None,
help="Shinylive version to download or remove.",
show_default=True,
)
@click.option(
"--url",
type=str,
default=_static._SHINYLIVE_DOWNLOAD_URL,
help="URL to download from.",
show_default=True,
)
@click.option(
"--dir",
type=str,
default=None,
help="Directory to store shinylive assets (if not using the default)",
)
@click.option(
"--source",
type=str,
default=None,
help="Directory where shinylive assets will be copied from. Must be used with 'copy' command.",
)
def static_assets(
command: str, version: str, url: str, dir: str, source: Optional[str]
) -> None:
if dir is None:
dir = _static.get_default_shinylive_dir()

if command == "download":
if version is None:
version = _static._SHINYLIVE_DEFAULT_VERSION
print(f"Downloading shinylive-{version} from {url} to {dir}")
_static.download_shinylive(destdir=dir, version=version, url=url)
elif command == "remove":
if version is None:
print(f"Removing {dir}")
else:
print(f"Removing shinylive-{version} from {dir}")
_static.remove_shinylive_local(shinylive_dir=dir, version=version)
def static_assets(command: str) -> None:
dir = _static.get_default_shinylive_dir()

if command == "remove":
print(f"Removing {dir}")
_static.remove_shinylive_local(shinylive_dir=dir)
elif command == "info":
_static.print_shinylive_local_info()
elif command == "copy":
if source is None:
raise click.UsageError("Must specify --source")
if version is None:
version = _static._SHINYLIVE_DEFAULT_VERSION
print(f"Copying shinylive-{version} from {source} to {dir}/shinylive-{version}")
_static.copy_shinylive_local(source_dir=source, destdir=dir, version=version)
else:
raise click.UsageError(f"Unknown command: {command}")
129 changes: 2 additions & 127 deletions shiny/_static.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,9 @@
import importlib.util
import os
import re
import shutil
import sys
from pathlib import Path
from typing import List, Optional, Union

if sys.version_info >= (3, 8):
from typing import Literal, TypedDict
else:
from typing_extensions import Literal, TypedDict

_SHINYLIVE_DOWNLOAD_URL = "https://pyshiny.netlify.app/shinylive"
_SHINYLIVE_DEFAULT_VERSION = "0.0.2dev"

# This is the same as the FileContentJson type in TypeScript.
class FileContentJson(TypedDict):
name: str
content: str
type: Literal["text", "binary"]


def deploy_static(
appdir: Union[str, Path],
destdir: Union[str, Path],
*,
subdir: Union[str, Path, None] = None,
version: str = _SHINYLIVE_DEFAULT_VERSION,
verbose: bool = False,
full_shinylive: bool = False,
) -> None:
"""
Create a statically deployable distribution with a Shiny app.
"""

shinylive_bundle_dir = _ensure_shinylive_local(version=version)

# Dynamically import shinylive moodule.
spec = importlib.util.spec_from_file_location(
"shinylive", str(shinylive_bundle_dir / "scripts" / "shinylive.py")
)
if spec is None:
raise RuntimeError(
"Could not import scripts/shinylive.py from shinylive bundle."
)
shinylive = importlib.util.module_from_spec(spec)
sys.modules["shinylive"] = shinylive
spec.loader.exec_module(shinylive) # type: ignore

# Call out to shinylive module to do deployment.
shinylive.deploy(
appdir,
destdir,
subdir=subdir,
verbose=verbose,
full_shinylive=full_shinylive,
)


def remove_shinylive_local(
shinylive_dir: Union[str, Path, None] = None,
Expand Down Expand Up @@ -86,62 +33,6 @@ def remove_shinylive_local(

if target_dir.exists():
shutil.rmtree(target_dir)
else:
print(f"{target_dir} does not exist.")


def _ensure_shinylive_local(
destdir: Union[Path, None] = None,
version: str = _SHINYLIVE_DEFAULT_VERSION,
url: str = _SHINYLIVE_DOWNLOAD_URL,
) -> Path:
"""Ensure that there is a local copy of shinylive."""

if destdir is None:
destdir = Path(get_default_shinylive_dir())

if not destdir.exists():
print("Creating directory " + str(destdir))
destdir.mkdir(parents=True)

shinylive_bundle_dir = destdir / f"shinylive-{version}"
if not shinylive_bundle_dir.exists():
print(f"{shinylive_bundle_dir} does not exist.")
download_shinylive(url=url, version=version, destdir=destdir)

return shinylive_bundle_dir


def download_shinylive(
destdir: Union[str, Path, None] = None,
version: str = _SHINYLIVE_DEFAULT_VERSION,
url: str = _SHINYLIVE_DOWNLOAD_URL,
) -> None:
import tarfile
import urllib.request

if destdir is None:
destdir = get_default_shinylive_dir()

destdir = Path(destdir)
tmp_name = None

try:
bundle_url = f"{url}/shinylive-{version}.tar.gz"
print(f"Downloading {bundle_url}...")
tmp_name, _ = urllib.request.urlretrieve(bundle_url)

print(f"Unzipping to {destdir}")
with tarfile.open(tmp_name) as tar:
tar.extractall(destdir)
finally:
if tmp_name is not None:
# Can simplify this block after we drop Python 3.7 support.
if sys.version_info >= (3, 8):
Path(tmp_name).unlink(missing_ok=True)
else:
if os.path.exists(tmp_name):
os.remove(tmp_name)


def get_default_shinylive_dir() -> str:
Expand All @@ -150,29 +41,13 @@ def get_default_shinylive_dir() -> str:
return os.path.join(appdirs.user_cache_dir("shiny"), "shinylive")


def copy_shinylive_local(
source_dir: Union[str, Path],
destdir: Optional[Union[str, Path]] = None,
version: str = _SHINYLIVE_DEFAULT_VERSION,
):
if destdir is None:
destdir = Path(get_default_shinylive_dir())

destdir = Path(destdir)

target_dir = destdir / ("shinylive-" + version)

if target_dir.exists():
shutil.rmtree(target_dir)

shutil.copytree(source_dir, target_dir)


def _installed_shinylive_versions(shinylive_dir: Optional[Path] = None) -> List[str]:
if shinylive_dir is None:
shinylive_dir = Path(get_default_shinylive_dir())

shinylive_dir = Path(shinylive_dir)
if not shinylive_dir.exists():
return []
subdirs = shinylive_dir.iterdir()
subdirs = [re.sub("^shinylive-", "", str(s)) for s in subdirs]
return subdirs
Expand Down

0 comments on commit bded4e7

Please sign in to comment.