Skip to content

Commit

Permalink
cli!: Move quarto extension methods under extension. Return core an…
Browse files Browse the repository at this point in the history
…d python assets only (#16)

Co-authored-by: Winston Chang <[email protected]>
  • Loading branch information
schloerke and wch authored Oct 5, 2023
1 parent 58085fd commit dc5f7d0
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 166 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [UNRELEASED]

* Adjusted cli api to have all quarto extension commands under `extension`. (#16)

## [0.0.18] - 2023-09-13

### Library updates
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@ To see which version of this Python package you have, and which version of the w
$ shinylive
Usage: shinylive [OPTIONS] COMMAND [ARGS]...
shinylive Python package version: 0.0.1
shinylive web assets version: 0.0.6
shinylive Python package version: 0.1.0
shinylive web assets version: 0.2.1
...
```

The web assets will be downloaded and cached the first time you run `shinylive export`. Or, you can run `shinylive assets download` to fetch them.

```
$ shinylive assets download
Downloading https://github.com/posit-dev/shinylive/releases/download/v0.0.6/shinylive-0.0.6.tar.gz...
Downloading https://github.com/posit-dev/shinylive/releases/download/v0.2.1/shinylive-0.2.1.tar.gz...
Unzipping to /Users/username/Library/Caches/shinylive/
```

Expand All @@ -81,21 +81,21 @@ $ shinylive assets info
/Users/username/Library/Caches/shinylive
Installed versions:
/Users/username/Library/Caches/shinylive/shinylive-0.2.1
/Users/username/Library/Caches/shinylive/shinylive-0.0.6
/Users/username/Library/Caches/shinylive/shinylive-0.0.5
```

You can remove old versions with `shinylive assets cleanup`. This will remove all versions except the one that the Python package wants to use:

```
$ shinylive assets cleanup
Keeping version 0.0.6
Removing /Users/username/Library/Caches/shinylive/shinylive-0.0.5
Keeping version 0.2.1
Removing /Users/username/Library/Caches/shinylive/shinylive-0.0.6
```

If you want to force it to remove a specific version, use the `shinylive assets remove --version xxx`:
If you want to force it to remove a specific version, use the `shinylive assets remove xxx`:

```
$ shinylive assets remove --version 0.0.6
Removing /Users/username/Library/Caches/shinylive/shinylive-0.0.6
$ shinylive assets remove 0.2.1
Removing /Users/username/Library/Caches/shinylive/shinylive-0.2.1
```
51 changes: 36 additions & 15 deletions shinylive/_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ def repodata_json_file(version: str = SHINYLIVE_ASSETS_VERSION) -> Path:
)


def codeblock_to_json_file() -> str:
p = Path(shinylive_assets_dir()) / "scripts" / "codeblock-to-json.js"
return str(p)


def copy_shinylive_local(
source_dir: str | Path,
destdir: Optional[str | Path] = None,
Expand Down Expand Up @@ -159,13 +164,15 @@ def cleanup_shinylive_assets(

shinylive_dir = Path(shinylive_dir)

version = _installed_shinylive_versions(shinylive_dir)
version = [re.sub("^shinylive-", "", os.path.basename(v)) for v in version]
if SHINYLIVE_ASSETS_VERSION in version:
version_paths = _installed_shinylive_version_paths(shinylive_dir)
version_names = [
re.sub("^shinylive-", "", os.path.basename(v)) for v in version_paths
]
if SHINYLIVE_ASSETS_VERSION in version_names:
print("Keeping version " + SHINYLIVE_ASSETS_VERSION)
version.remove(SHINYLIVE_ASSETS_VERSION)
version_names.remove(SHINYLIVE_ASSETS_VERSION)

remove_shinylive_assets(shinylive_dir, version)
remove_shinylive_assets(shinylive_dir, version_names)


def remove_shinylive_assets(
Expand Down Expand Up @@ -208,27 +215,41 @@ def remove_shinylive_assets(
print(f"{target_dir} does not exist.")


def _installed_shinylive_versions(shinylive_dir: Optional[Path] = None) -> list[str]:
def _installed_shinylive_version_paths(
shinylive_dir: Optional[Path] = None,
) -> list[Path]:
if shinylive_dir is None:
shinylive_dir = Path(shinylive_cache_dir())

shinylive_dir = Path(shinylive_dir)
subdirs = shinylive_dir.iterdir()
subdirs = [re.sub("^shinylive-", "", str(s)) for s in subdirs]
return subdirs

# print(", ".join([str(s.name) for s in subdirs]))
return [s for s in subdirs if not s.name.startswith(".")]
subdir_names = [str(s.name) for s in subdirs]
versions = [
re.sub("^shinylive-", "", subdir_name)
for subdir_name in subdir_names
if not subdir_name.startswith(".")
]
return versions


def print_shinylive_local_info(
destdir: Path | None = None,
) -> None:
if destdir is None:
destdir = Path(shinylive_cache_dir())

def print_shinylive_local_info() -> None:
print(
f""" Local cached shinylive asset dir:
{shinylive_cache_dir()}
{str(destdir)}
"""
)
if Path(shinylive_cache_dir()).exists():
if destdir.exists():
print(""" Installed versions:""")
installed_versions = _installed_shinylive_versions()
if len(installed_versions) > 0:
print(" " + "\n ".join(installed_versions))
installed_version_paths = _installed_shinylive_version_paths(destdir)
if len(installed_version_paths) > 0:
print(" " + "\n ".join([str(v) for v in installed_version_paths]))
else:
print(" (None)")
else:
Expand Down
91 changes: 67 additions & 24 deletions shinylive/_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys
from pathlib import Path
from textwrap import dedent
from typing import Callable, Iterable, Literal, Optional
from typing import Callable, Iterable, Literal, Optional, Tuple

# Even though TypedDict is available in Python 3.8, because it's used with NotRequired,
# they should both come from the same typing module.
Expand All @@ -33,6 +33,7 @@

# Packages that should always be included in a Shinylive deployment.
BASE_PYODIDE_PACKAGES = {"distutils", "micropip", "ssl"}
AssetType = Literal["base", "python", "r"]


# =============================================================================
Expand Down Expand Up @@ -120,26 +121,61 @@ def _pyodide_pkg_infos_to_quarto_html_dep_items(
def shinylive_base_deps_htmldep(
sw_dir: Optional[str] = None,
*,
all_files: bool = False,
asset_type: Tuple[AssetType] = ("base",),
) -> list[QuartoHtmlDependency]:
return [
_serviceworker_dep(sw_dir),
_shinylive_common_dep_htmldep(all_files=all_files),
_shinylive_common_dep_htmldep(asset_type=asset_type),
]


def shinylive_python_resources() -> list[HtmlDepItem]:
ret: list[HtmlDepItem] = []

# Add python support file
rel_paths = shinylive_common_files(asset_type=("python",))
assets_dir = shinylive_assets_dir()
ret.extend(
[
{"name": rel_path, "path": os.path.join(assets_dir, rel_path)}
for rel_path in rel_paths
]
)

# Add base python packages as resources
ret.extend(base_package_deps_htmldepitems())

return ret


# Not used in practice!
def shinylive_r_resources() -> list[HtmlDepItem]:
rel_paths = shinylive_common_files(asset_type=("r",))
assets_dir = shinylive_assets_dir()
return [
{"name": rel_path, "path": os.path.join(assets_dir, rel_path)}
for rel_path in rel_paths
]


# =============================================================================
# Common dependencies
# =============================================================================
def _shinylive_common_dep_htmldep(*, all_files: bool = False) -> QuartoHtmlDependency:
def _shinylive_common_dep_htmldep(
*,
asset_type: Tuple[AssetType, ...] = (
"base",
"python",
),
) -> QuartoHtmlDependency:
"""
Return an HTML dependency object consisting of files that are base dependencies; in
other words, the files that are always included in a Shinylive deployment.
"""
assets_dir = shinylive_assets_dir()

# First, get the list of base files.
base_files = shinylive_common_files(all_files=all_files)
base_files = shinylive_common_files(asset_type=asset_type)

# Next, categorize the base files into scripts, stylesheets, and resources.
scripts: list[str | HtmlDepItem] = []
Expand Down Expand Up @@ -176,9 +212,6 @@ def _shinylive_common_dep_htmldep(*, all_files: bool = False) -> QuartoHtmlDepen
}
)

# Add base python packages as resources
resources.extend(base_package_deps_htmldepitems())

# Sort scripts so that load-shinylive-sw.js is first, and run-python-blocks.js is
# last.
def scripts_sort_fun(x: str | HtmlDepItem) -> int:
Expand All @@ -205,25 +238,40 @@ def scripts_sort_fun(x: str | HtmlDepItem) -> int:
}


def shinylive_common_files(*, all_files: bool = False) -> list[str]:
def shinylive_common_files(
*,
asset_type: Tuple[AssetType, ...],
) -> list[str]:
"""
Return a list of files that are base dependencies; in other words, the files that are
always included in a Shinylive deployment.
Return a list of asset files for Python, and/or R, and/or language-agnostic (base) dependencies
"""
ensure_shinylive_assets()

has_base = "base" in asset_type
has_python = "python" in asset_type
has_r = "r" in asset_type

base_files: list[str] = []
for root, dirs, files in os.walk(shinylive_assets_dir()):
root = Path(root)
rel_root = root.relative_to(shinylive_assets_dir())
if rel_root == Path("."):
dirs.remove("scripts")
dirs.remove("export_template")
if not has_base:
# No files to add here
files = []
elif rel_root == Path("shinylive"):
files.remove("examples.json")
# Remove webr folder as it is only needed for R support
if not all_files:
if not has_r:
dirs.remove("webr")
if not has_python:
dirs.remove("pyodide")
dirs.remove("pyright")
if not has_base:
# No files to add here
files = []
elif rel_root == Path("shinylive/pyodide"):
dirs.remove("fonts")
files[:] = BASE_PYODIDE_FILES
Expand Down Expand Up @@ -258,10 +306,9 @@ def _serviceworker_dep(sw_dir: Optional[str] = None) -> QuartoHtmlDependency:
# =============================================================================
# Find which packages are used by a Shiny application
# =============================================================================
def package_deps_htmldepitems(
def shinylive_app_resources(
json_file: Optional[str | Path],
json_content: Optional[str],
verbose: bool = True,
) -> list[HtmlDepItem]:
"""
Find package dependencies from an app.json file, and return as a list of
Expand All @@ -275,10 +322,6 @@ def package_deps_htmldepitems(
):
raise RuntimeError("Must provide either `json_file` or `json_content`.")

def verbose_print(*args: object) -> None:
if verbose:
print(*args, file=sys.stderr)

file_contents: list[FileContentJson] = []

if json_file is not None:
Expand Down Expand Up @@ -317,20 +360,20 @@ def find_package_deps(

def base_package_deps_htmldepitems() -> list[HtmlDepItem]:
"""
Return list of packages that should be included in all Shinylive deployments. The
returned data structure is a list of PyodidePackageInfo objects.
Return list of python packages that should be included in all python Shinylive
deployments. The returned data structure is a list of HtmlDepItem objects
representing PyodidePackageInfo objects.
"""
dep_names = _find_recursive_deps(BASE_PYODIDE_PACKAGES)
pkg_infos = _dep_names_to_pyodide_pkg_infos(dep_names)
pkg_infos = base_package_deps()
deps = _pyodide_pkg_infos_to_quarto_html_dep_items(pkg_infos)

return deps


def base_package_deps() -> list[PyodidePackageInfo]:
"""
Return list of packages that should be included in all Shinylive deployments. The
returned data structure is a list of PyodidePackageInfo objects.
Return list of python packages that should be included in all python Shinylive
deployments. The returned data structure is a list of PyodidePackageInfo objects.
"""
dep_names = _find_recursive_deps(BASE_PYODIDE_PACKAGES)
pkg_infos = _dep_names_to_pyodide_pkg_infos(dep_names)
Expand Down
2 changes: 1 addition & 1 deletion shinylive/_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def verbose_print(*args: object) -> None:

base_files = _deps.shinylive_common_files(
# Do not include r-only support files
all_files=False,
asset_type=("base", "python"),
)
for file in base_files:
src_path = assets_dir / file
Expand Down
Loading

0 comments on commit dc5f7d0

Please sign in to comment.