Skip to content

Commit

Permalink
Merge branch 'main' into x_into_main
Browse files Browse the repository at this point in the history
  • Loading branch information
schloerke authored Oct 19, 2023
2 parents 89504b8 + 4d1e6db commit 8cb3e1d
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 205 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added `shiny.experimental.ui.popover()`, `update_popover()`, and `toggle_popover()` for easy creation (and server-side updating) of [Bootstrap popovers](https://getbootstrap.com/docs/5.2/components/popovers/). Popovers are similar to tooltips, but are more persistent, and should primarily be used with button-like UI elements (e.g. `input_action_button()` or icons) (#680).
* Added CSS classes to UI input methods (#680) .
* `Session` objects can now accept an asynchronous (or synchronous) function for `.on_flush(fn=)`, `.on_flushed(fn=)`, and `.on_ended(fn=)` (#686).
* `App()` now allows `static_assets` to represent multiple paths. To do this, pass in a dictionary instead of a string (#763).

### API changes

Expand Down
28 changes: 17 additions & 11 deletions examples/model-score/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,17 +293,23 @@ def update_time_range():
"""

reactive.invalidate_later(15)
min_time, max_time = pd.to_datetime(
con.execute(
"select min(timestamp), max(timestamp) from accuracy_scores"
).fetchone(),
utc=True,
)
ui.update_slider(
"timerange",
min=min_time.replace(tzinfo=timezone.utc),
max=max_time.replace(tzinfo=timezone.utc),
)
try:
min_time, max_time = pd.to_datetime(
con.execute(
"select min(timestamp), max(timestamp) from accuracy_scores"
).fetchone(),
utc=True,
)
ui.update_slider(
"timerange",
min=min_time.replace(tzinfo=timezone.utc),
max=max_time.replace(tzinfo=timezone.utc),
)
except sqlite3.OperationalError:
# Sometimes this executes before the background thread has had a
# chance to even create the sample data table. In that case, just
# ignore the error and try again later.
pass


app = App(app_ui, server)
4 changes: 0 additions & 4 deletions examples/model-score/requirements.in

This file was deleted.

178 changes: 4 additions & 174 deletions examples/model-score/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,174 +1,4 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile
#
anyio==3.7.1
# via
# starlette
# watchfiles
appdirs==1.4.4
# via shiny
appnope==0.1.3
# via
# ipykernel
# ipython
asgiref==3.7.2
# via shiny
asttokens==2.2.1
# via stack-data
backcall==0.2.0
# via ipython
click==8.1.6
# via
# shiny
# uvicorn
comm==0.1.3
# via ipykernel
contextvars==2.4
# via shiny
debugpy==1.6.7
# via ipykernel
decorator==5.1.1
# via ipython
exceptiongroup==1.1.2
# via anyio
executing==1.2.0
# via stack-data
h11==0.14.0
# via uvicorn
htmltools==0.2.1
# via shiny
idna==3.4
# via anyio
immutables==0.19
# via contextvars
ipykernel==6.25.0
# via ipywidgets
ipython==8.14.0
# via
# ipykernel
# ipywidgets
ipywidgets==8.0.7
# via shinywidgets
jedi==0.18.2
# via ipython
jupyter-client==8.3.0
# via ipykernel
jupyter-core==5.3.1
# via
# ipykernel
# jupyter-client
# shinywidgets
jupyterlab-widgets==3.0.8
# via ipywidgets
linkify-it-py==2.0.2
# via shiny
markdown-it-py==3.0.0
# via
# mdit-py-plugins
# shiny
matplotlib-inline==0.1.6
# via
# ipykernel
# ipython
mdit-py-plugins==0.4.0
# via shiny
mdurl==0.1.2
# via markdown-it-py
nest-asyncio==1.5.7
# via ipykernel
numpy==1.25.1
# via pandas
packaging==23.1
# via
# htmltools
# ipykernel
# plotly
pandas==2.0.3
# via -r requirements.in
parso==0.8.3
# via jedi
pexpect==4.8.0
# via ipython
pickleshare==0.7.5
# via ipython
platformdirs==3.9.1
# via jupyter-core
plotly==5.15.0
# via -r requirements.in
prompt-toolkit==3.0.39
# via ipython
psutil==5.9.5
# via ipykernel
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pygments==2.15.1
# via ipython
python-dateutil==2.8.2
# via
# jupyter-client
# pandas
# shinywidgets
python-multipart==0.0.6
# via shiny
pytz==2023.3
# via pandas
pyzmq==25.1.0
# via
# ipykernel
# jupyter-client
shiny==0.4.0
# via
# -r requirements.in
# shinywidgets
shinywidgets==0.2.1
# via -r requirements.in
six==1.16.0
# via
# asttokens
# python-dateutil
sniffio==1.3.0
# via anyio
stack-data==0.6.2
# via ipython
starlette==0.31.0
# via shiny
tenacity==8.2.2
# via plotly
tornado==6.3.2
# via
# ipykernel
# jupyter-client
traitlets==5.9.0
# via
# comm
# ipykernel
# ipython
# ipywidgets
# jupyter-client
# jupyter-core
# matplotlib-inline
typing-extensions==4.7.1
# via
# asgiref
# htmltools
# shiny
# uvicorn
tzdata==2023.3
# via pandas
uc-micro-py==1.0.2
# via linkify-it-py
uvicorn==0.23.1
# via shiny
watchfiles==0.19.0
# via shiny
wcwidth==0.2.6
# via prompt-toolkit
websockets==11.0.3
# via shiny
widgetsnbextension==4.0.8
# via ipywidgets
pandas
plotly
shiny
shinywidgets
60 changes: 46 additions & 14 deletions shiny/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
from ._connection import Connection, StarletteConnection
from ._error import ErrorMiddleware
from ._shinyenv import is_pyodide
from ._utils import is_async_callable
from ._utils import guess_mime_type, is_async_callable
from .html_dependencies import jquery_deps, require_deps, shiny_deps
from .http_staticfiles import StaticFiles
from .http_staticfiles import FileResponse, StaticFiles
from .session import Inputs, Outputs, Session, session_context

# Default values for App options.
Expand All @@ -54,7 +54,10 @@ class App:
A function which is called once for each session, ensuring that each app is
independent.
static_assets
An absolute directory containing static files to be served by the app.
Static files to be served by the app. If this is a string or Path object, it
must be a directory, and it will be mounted at `/`. If this is a dictionary,
each key is a mount point and each value is a file or directory to be served at
that mount point.
debug
Whether to enable debug mode.
Expand Down Expand Up @@ -100,7 +103,7 @@ def __init__(
ui: Tag | TagList | Callable[[Request], Tag | TagList] | Path,
server: Optional[Callable[[Inputs, Outputs, Session], None]],
*,
static_assets: Optional["str" | "os.PathLike[str]"] = None,
static_assets: Optional["str" | "os.PathLike[str]" | dict[str, Path]] = None,
debug: bool = False,
) -> None:
if server is None:
Expand All @@ -119,15 +122,16 @@ def _server(inputs: Inputs, outputs: Outputs, session: Session):
self.sanitize_errors: bool = SANITIZE_ERRORS
self.sanitize_error_msg: str = SANITIZE_ERROR_MSG

if static_assets is not None:
if not os.path.isdir(static_assets):
raise ValueError(f"static_assets must be a directory: {static_assets}")
if static_assets is None:
static_assets = {}
if isinstance(static_assets, (str, os.PathLike)):
if not os.path.isabs(static_assets):
raise ValueError(
f"static_assets must be an absolute path: {static_assets}"
)
static_assets = {"/": Path(static_assets)}

self._static_assets: str | os.PathLike[str] | None = static_assets
self._static_assets: dict[str, Path] = static_assets

self._sessions: dict[str, Session] = {}

Expand All @@ -136,13 +140,9 @@ def _server(inputs: Inputs, outputs: Outputs, session: Session):
self._registered_dependencies: dict[str, HTMLDependency] = {}
self._dependency_handler = starlette.routing.Router()

if self._static_assets is not None:
for mount_point, static_asset_path in self._static_assets.items():
self._dependency_handler.routes.append(
starlette.routing.Mount(
"/",
StaticFiles(directory=self._static_assets),
name="shiny-app-static-assets-directory",
)
create_static_asset_route(mount_point, static_asset_path)
)

starlette_app = self.init_starlette_app()
Expand Down Expand Up @@ -414,3 +414,35 @@ def is_uifunc(x: Path | Tag | TagList | Callable[[Request], Tag | TagList]):

def html_dep_name(dep: HTMLDependency) -> str:
return dep.name + "-" + str(dep.version)


def create_static_asset_route(
mount_point: str, static_asset_path: Path
) -> starlette.routing.BaseRoute:
"""
Create a Starlette route for serving static assets.
Parameters
----------
mount_point
The mount point where the static assets will be served.
static_asset_path
The path on disk to the static assets.
"""
if static_asset_path.is_dir():
return starlette.routing.Mount(
mount_point,
StaticFiles(directory=static_asset_path),
name="shiny-app-static-assets-" + mount_point,
)
else:
mime_type = guess_mime_type(static_asset_path, strict=False)

def file_response_handler(req: Request) -> FileResponse:
return FileResponse(static_asset_path, media_type=mime_type)

return starlette.routing.Route(
mount_point,
file_response_handler,
name="shiny-app-static-assets-" + mount_point,
)
10 changes: 8 additions & 2 deletions shiny/quarto.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def convert_code_cells_to_app_py(json_file: str | Path, app_file: str | Path) ->

app_content = f"""# This file generated by Quarto; do not edit by hand.
from __future__ import annotations
from pathlib import Path
from shiny import App, Inputs, Outputs, Session, ui
Expand All @@ -75,12 +77,16 @@ def convert_code_cells_to_app_py(json_file: str | Path, app_file: str | Path) ->
def server(input: Inputs, output: Outputs, session: Session) -> None:
{ "".join(session_code_cell_texts) }
_static_assets = ##STATIC_ASSETS_PLACEHOLDER##
_static_assets = {{"/" + sa: Path(__file__).parent / sa for sa in _static_assets}}
app = App(
Path(__file__).parent / "{ data["html_file"] }",
server,
static_assets=Path(__file__).parent,
static_assets=_static_assets,
)
"""
"""

with open(app_file, "w") as f:
f.write(app_content)
Expand Down

0 comments on commit 8cb3e1d

Please sign in to comment.