diff --git a/docs/Makefile b/docs/Makefile index c81892065..47a6af731 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -53,10 +53,14 @@ quartodoc_impl: $(PYBIN) ## Build qmd files for API docs $(eval export IN_QUARTODOC=true) . $(PYBIN)/activate \ && quartodoc interlinks \ - && quartodoc build --config _quartodoc-core.yml --verbose \ + && echo "::group::quartodoc build core docs" \ + && SHINY_MODE="core" quartodoc build --config _quartodoc-core.yml --verbose \ && mv objects.json _objects_core.json \ - && quartodoc build --config _quartodoc-express.yml --verbose \ - && mv objects.json _objects_express.json + && echo "::endgroup::" \ + && echo "::group::quartodoc build express docs" \ + && SHINY_MODE="express" quartodoc build --config _quartodoc-express.yml --verbose \ + && mv objects.json _objects_express.json \ + && echo "::endgroup::" quartodoc_post: $(PYBIN) ## Post-process qmd files for API docs . $(PYBIN)/activate \ diff --git a/docs/_renderer_core.py b/docs/_renderer_core.py index a46d51292..332a4ab93 100644 --- a/docs/_renderer_core.py +++ b/docs/_renderer_core.py @@ -2,6 +2,7 @@ import base64 import html +import os import re from importlib.resources import files from pathlib import Path @@ -270,6 +271,10 @@ def read_file(file: str | Path, root_dir: str | Path | None = None) -> FileConte def check_if_missing_expected_example(el, converted): + if os.environ.get("SHINY_MODE") == "express": + # These errors are thrown much earlier by @add_example() + return + if re.search(r"(^|\n)#{2,6} Examples\n", converted): # Manually added examples are fine return diff --git a/shiny/_docstring.py b/shiny/_docstring.py index a45a4edce..8ecc13c08 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -2,7 +2,8 @@ import os import sys -from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar +from functools import wraps +from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, TypeVar def find_api_examples_dir(start_dir: str) -> Optional[str]: @@ -29,6 +30,25 @@ def no_example(func: F) -> F: return func +def no_example_express(decorator: Callable[..., F]) -> F | Callable[..., F]: + """ + Prevent ``@add_example()`` from throwing an error about missing Express examples. + """ + + @wraps(decorator) + def wrapper_decorator(*args: Any, **kwargs: Any) -> F: + try: + # Apply the potentially problematic decorator + return decorator(*args, **kwargs) + except ExpressExampleNotFoundException: + # If an error occurs, return the original function + if args and callable(args[0]): + return args[0] + raise + + return wrapper_decorator + + # This class is used to mark docstrings when @add_example() is used, so that an error # will be thrown if @doc_format() is used afterward. This is to avoid an error when # the example contains curly braces -- the @doc_format() decorator will try to evaluate @@ -50,7 +70,7 @@ def write_example(self, app_files: list[str]) -> str: def add_example( - app_file: str = "app.py", + app_file: Optional[str] = None, ex_dir: Optional[str] = None, ) -> Callable[[F], F]: """ @@ -60,6 +80,14 @@ def add_example( ``__name__`` matches the name of directory under a ``api-examples/`` directory in the current or any parent directory. + * Examples for the ``shiny`` package are in ``shiny/api-examples/``. + * Examples for the ``shiny.express`` subpackage are in ``shiny/express/api-examples/``. + + Functions that can be used in Express or Core and whose canonical implementation is + in the ``shiny`` package should have examples in ``shiny/api-examples``. In this + case, the express variant should include an ``-express`` suffix and the core + variation can be named with a ``-core`` suffix or ``app.py``. + Parameters ---------- app_file: @@ -88,25 +116,40 @@ def _(func: F) -> F: ex_dir_found = find_api_examples_dir(func_dir) if ex_dir_found is None: - raise ValueError( + raise FileNotFoundError( f"No example directory found for {fn_name} in {func_dir} or its parent directories." ) example_dir = os.path.join(ex_dir_found, fn_name) else: - example_dir = os.path.join(func_dir, ex_dir) + example_dir = os.path.abspath(os.path.join(func_dir, ex_dir)) - example_file = os.path.join(example_dir, app_file) - if not os.path.exists(example_file): - raise ValueError( - f"No example for {fn_name} found in '{os.path.abspath(example_dir)}'." + if not os.path.exists(example_dir): + raise FileNotFoundError( + f"Example directory '{example_dir}' does not exist for {fn_name}." + ) + + app_file_name = app_file or "app.py" + try: + example_file = app_choose_core_or_express( + os.path.join(example_dir, app_file_name) ) + except ExampleNotFoundException as e: + func_dir = get_decorated_source_directory(func).split("py-shiny/")[1] + if "__code__" in dir(func): + print( + f"::warning file={func_dir},line={func.__code__.co_firstlineno}::{e}" + ) + else: + print(f"::warning file={func_dir}::{e}") + + return func other_files: list[str] = [] for f in os.listdir(example_dir): abs_f = os.path.join(example_dir, f) is_support_file = ( os.path.isfile(abs_f) - and f != app_file + and f != app_file_name and f != "app.py" and not f.startswith("app-") and not f.startswith("__") @@ -150,6 +193,86 @@ def _(func: F) -> F: return _ +def is_express_app(app_path: str) -> bool: + # We can't use .shiny.express._is_express.is_express_app() here because that would + # create a circular import. + if not os.path.exists(app_path): + return False + + with open(app_path) as f: + for line in f: + if "from shiny.express" in line: + return True + elif "import shiny.express" in line: + return True + return False + + +class ExampleNotFoundException(FileNotFoundError): + def __init__( + self, + file_names: list[str] | str, + dir: str, + type: Optional[Literal["core", "express"]] = None, + ) -> None: + self.type = type or os.environ.get("SHINY_MODE") or "core" + self.file_names = [file_names] if isinstance(file_names, str) else file_names + self.dir = dir + + def __str__(self): + if self.type in ("core", "express"): + # Capitalize first letter + type = "a Shiny Express" if self.type == "express" else "a Shiny Core" + else: + type = "an" + + return ( + f"Could not find {type} example file named " + + f"{' or '.join(self.file_names)} in {self.dir}." + ) + + +class ExpressExampleNotFoundException(ExampleNotFoundException): + def __init__( + self, + file_names: list[str] | str, + dir: str, + ) -> None: + super().__init__(file_names, dir, "express") + + +def app_choose_core_or_express(app_path: Optional[str] = None) -> str: + app_path = app_path or "app.py" + + if os.environ.get("SHINY_MODE") == "express": + if is_express_app(app_path): + return app_path + + app_path = app_path.replace("-core.py", ".py") + + path, ext = os.path.splitext(app_path) + app_path_express = f"{path}-express{ext}" + + if not is_express_app(app_path_express): + raise ExpressExampleNotFoundException( + [os.path.basename(app_path), os.path.basename(app_path_express)], + os.path.dirname(app_path), + ) + + return app_path_express + + if os.path.basename(app_path) == "app.py" and not os.path.exists(app_path): + app_path = app_path.replace("app.py", "app-core.py") + + if not os.path.exists(app_path): + raise ExampleNotFoundException( + os.path.basename(app_path), + os.path.dirname(app_path), + ) + + return app_path + + def get_decorated_source_directory(func: FuncType) -> str: if hasattr(func, "__module__"): path = os.path.abspath(str(sys.modules[func.__module__].__file__)) diff --git a/shiny/api-examples/Module/app.py b/shiny/api-examples/Module/app-core.py similarity index 100% rename from shiny/api-examples/Module/app.py rename to shiny/api-examples/Module/app-core.py diff --git a/shiny/api-examples/Progress/app.py b/shiny/api-examples/Progress/app-core.py similarity index 100% rename from shiny/api-examples/Progress/app.py rename to shiny/api-examples/Progress/app-core.py diff --git a/shiny/api-examples/Progress/app-express.py b/shiny/api-examples/Progress/app-express.py new file mode 100644 index 000000000..b4bde03f6 --- /dev/null +++ b/shiny/api-examples/Progress/app-express.py @@ -0,0 +1,21 @@ +import asyncio + +from shiny import reactive +from shiny.express import input, render, ui + +ui.input_action_button("button", "Compute") + + +@render.text +@reactive.event(input.button) +async def compute(): + with ui.Progress(min=1, max=15) as p: + p.set(message="Calculation in progress", detail="This may take a while...") + + for i in range(1, 15): + p.set(i, message="Computing") + await asyncio.sleep(0.1) + # Normally use time.sleep() instead, but it doesn't yet work in Pyodide. + # https://github.com/pyodide/pyodide/issues/2354 + + return "Done computing!" diff --git a/shiny/api-examples/Renderer/app.py b/shiny/api-examples/Renderer/app-core.py similarity index 100% rename from shiny/api-examples/Renderer/app.py rename to shiny/api-examples/Renderer/app-core.py diff --git a/shiny/api-examples/SafeException/app.py b/shiny/api-examples/SafeException/app-core.py similarity index 100% rename from shiny/api-examples/SafeException/app.py rename to shiny/api-examples/SafeException/app-core.py diff --git a/shiny/api-examples/SilentCancelOutputException/app.py b/shiny/api-examples/SilentCancelOutputException/app-core.py similarity index 100% rename from shiny/api-examples/SilentCancelOutputException/app.py rename to shiny/api-examples/SilentCancelOutputException/app-core.py diff --git a/shiny/api-examples/SilentException/app.py b/shiny/api-examples/SilentException/app-core.py similarity index 100% rename from shiny/api-examples/SilentException/app.py rename to shiny/api-examples/SilentException/app-core.py diff --git a/shiny/api-examples/Value/app.py b/shiny/api-examples/Value/app-core.py similarity index 72% rename from shiny/api-examples/Value/app.py rename to shiny/api-examples/Value/app-core.py index fe0112954..04479492a 100644 --- a/shiny/api-examples/Value/app.py +++ b/shiny/api-examples/Value/app-core.py @@ -1,10 +1,10 @@ from shiny import App, Inputs, Outputs, Session, reactive, render, ui -app_ui = ui.page_fluid( - ui.input_action_button("minus", "-1"), - " ", - ui.input_action_button("plus", "+1"), - ui.br(), +app_ui = ui.page_sidebar( + ui.sidebar( + ui.input_action_button("minus", "-1"), + ui.input_action_button("plus", "+1"), + ), ui.output_text("value"), ) @@ -12,13 +12,13 @@ def server(input: Inputs, output: Outputs, session: Session): val = reactive.Value(0) - @reactive.Effect + @reactive.effect @reactive.event(input.minus) def _(): newVal = val.get() - 1 val.set(newVal) - @reactive.Effect + @reactive.effect @reactive.event(input.plus) def _(): newVal = val.get() + 1 diff --git a/shiny/api-examples/Value/app-express.py b/shiny/api-examples/Value/app-express.py new file mode 100644 index 000000000..80a55e165 --- /dev/null +++ b/shiny/api-examples/Value/app-express.py @@ -0,0 +1,28 @@ +from shiny import reactive +from shiny.express import input, render, ui + +val = reactive.Value(0) + + +@reactive.effect +@reactive.event(input.minus) +def _(): + newVal = val.get() - 1 + val.set(newVal) + + +@reactive.effect +@reactive.event(input.plus) +def _(): + newVal = val.get() + 1 + val.set(newVal) + + +with ui.sidebar(): + ui.input_action_button("minus", "-1") + ui.input_action_button("plus", "+1") + + +@render.text +def value(): + return str(val.get()) diff --git a/shiny/api-examples/accordion/app.py b/shiny/api-examples/accordion/app-core.py similarity index 100% rename from shiny/api-examples/accordion/app.py rename to shiny/api-examples/accordion/app-core.py diff --git a/shiny/api-examples/accordion_panel/app.py b/shiny/api-examples/accordion_panel/app-core.py similarity index 100% rename from shiny/api-examples/accordion_panel/app.py rename to shiny/api-examples/accordion_panel/app-core.py diff --git a/shiny/api-examples/as_fill_item/app.py b/shiny/api-examples/as_fill_item/app-core.py similarity index 100% rename from shiny/api-examples/as_fill_item/app.py rename to shiny/api-examples/as_fill_item/app-core.py diff --git a/shiny/api-examples/as_fillable_container/app.py b/shiny/api-examples/as_fillable_container/app-core.py similarity index 100% rename from shiny/api-examples/as_fillable_container/app.py rename to shiny/api-examples/as_fillable_container/app-core.py diff --git a/shiny/api-examples/calc/app.py b/shiny/api-examples/calc/app-core.py similarity index 64% rename from shiny/api-examples/calc/app.py rename to shiny/api-examples/calc/app-core.py index 603313593..fe2620253 100644 --- a/shiny/api-examples/calc/app.py +++ b/shiny/api-examples/calc/app-core.py @@ -4,16 +4,18 @@ from shiny import App, Inputs, Outputs, Session, reactive, render, ui app_ui = ui.page_fluid( - ui.input_action_button("first", "Invalidate first (slow) computation"), - " ", - ui.input_action_button("second", "Invalidate second (fast) computation"), - ui.br(), - ui.output_ui("result"), + ui.card( + ui.layout_columns( + ui.input_action_button("first", "Invalidate first (slow) computation"), + ui.input_action_button("second", "Invalidate second (fast) computation"), + ), + ui.output_text_verbatim("result"), + ) ) def server(input: Inputs, output: Outputs, session: Session): - @reactive.Calc + @reactive.calc def first(): input.first() p = ui.Progress() @@ -23,12 +25,12 @@ def first(): p.close() return random.randint(1, 1000) - @reactive.Calc + @reactive.calc def second(): input.second() return random.randint(1, 1000) - @render.ui + @render.text def result(): return first() + second() diff --git a/shiny/api-examples/calc/app-express.py b/shiny/api-examples/calc/app-express.py new file mode 100644 index 000000000..2361cea80 --- /dev/null +++ b/shiny/api-examples/calc/app-express.py @@ -0,0 +1,32 @@ +import random +import time + +from shiny import reactive +from shiny.express import input, render, ui + + +@reactive.calc +def first(): + input.first() + p = ui.Progress() + for i in range(30): + p.set(i / 30, message="Computing, please wait...") + time.sleep(0.1) + p.close() + return random.randint(1, 1000) + + +@reactive.calc +def second(): + input.second() + return random.randint(1, 1000) + + +with ui.card(): + with ui.layout_columns(): + ui.input_action_button("first", "Invalidate first (slow) computation") + ui.input_action_button("second", "Invalidate second (fast) computation") + + @render.text + def result(): + return first() + second() diff --git a/shiny/api-examples/card/app.py b/shiny/api-examples/card/app-core.py similarity index 100% rename from shiny/api-examples/card/app.py rename to shiny/api-examples/card/app-core.py diff --git a/shiny/api-examples/card_body/app.py b/shiny/api-examples/card_body/app-core.py similarity index 100% rename from shiny/api-examples/card_body/app.py rename to shiny/api-examples/card_body/app-core.py diff --git a/shiny/api-examples/card_footer/app.py b/shiny/api-examples/card_footer/app-core.py similarity index 100% rename from shiny/api-examples/card_footer/app.py rename to shiny/api-examples/card_footer/app-core.py diff --git a/shiny/api-examples/card_header/app.py b/shiny/api-examples/card_header/app-core.py similarity index 100% rename from shiny/api-examples/card_header/app.py rename to shiny/api-examples/card_header/app-core.py diff --git a/shiny/api-examples/close/app.py b/shiny/api-examples/close/app-core.py similarity index 100% rename from shiny/api-examples/close/app.py rename to shiny/api-examples/close/app-core.py diff --git a/shiny/api-examples/data_frame/app.py b/shiny/api-examples/data_frame/app-core.py similarity index 100% rename from shiny/api-examples/data_frame/app.py rename to shiny/api-examples/data_frame/app-core.py diff --git a/shiny/api-examples/download/app.py b/shiny/api-examples/download/app-core.py similarity index 100% rename from shiny/api-examples/download/app.py rename to shiny/api-examples/download/app-core.py diff --git a/shiny/api-examples/download_button/app.py b/shiny/api-examples/download_button/app-core.py similarity index 100% rename from shiny/api-examples/download_button/app.py rename to shiny/api-examples/download_button/app-core.py diff --git a/shiny/api-examples/download_link/app.py b/shiny/api-examples/download_link/app-core.py similarity index 100% rename from shiny/api-examples/download_link/app.py rename to shiny/api-examples/download_link/app-core.py diff --git a/shiny/api-examples/dynamic_route/app.py b/shiny/api-examples/dynamic_route/app-core.py similarity index 100% rename from shiny/api-examples/dynamic_route/app.py rename to shiny/api-examples/dynamic_route/app-core.py diff --git a/shiny/api-examples/effect/app.py b/shiny/api-examples/effect/app-core.py similarity index 100% rename from shiny/api-examples/effect/app.py rename to shiny/api-examples/effect/app-core.py diff --git a/shiny/api-examples/effect/app-express.py b/shiny/api-examples/effect/app-express.py new file mode 100644 index 000000000..363ed50cd --- /dev/null +++ b/shiny/api-examples/effect/app-express.py @@ -0,0 +1,15 @@ +from shiny import reactive +from shiny.express import input, ui + +ui.input_action_button("show", "Show modal dialog") + + +@reactive.effect +@reactive.event(input.show) +def show_important_message(): + m = ui.modal( + "This is a somewhat important message.", + easy_close=True, + footer=None, + ) + ui.modal_show(m) diff --git a/shiny/api-examples/event/app.py b/shiny/api-examples/event/app-core.py similarity index 100% rename from shiny/api-examples/event/app.py rename to shiny/api-examples/event/app-core.py diff --git a/shiny/api-examples/extended_task/app.py b/shiny/api-examples/extended_task/app-core.py similarity index 100% rename from shiny/api-examples/extended_task/app.py rename to shiny/api-examples/extended_task/app-core.py diff --git a/shiny/api-examples/file_reader/app.py b/shiny/api-examples/file_reader/app-core.py similarity index 100% rename from shiny/api-examples/file_reader/app.py rename to shiny/api-examples/file_reader/app-core.py diff --git a/shiny/api-examples/include_css/app.py b/shiny/api-examples/include_css/app-core.py similarity index 100% rename from shiny/api-examples/include_css/app.py rename to shiny/api-examples/include_css/app-core.py diff --git a/shiny/api-examples/include_js/app.py b/shiny/api-examples/include_js/app-core.py similarity index 100% rename from shiny/api-examples/include_js/app.py rename to shiny/api-examples/include_js/app-core.py diff --git a/shiny/api-examples/input_action_button/app.py b/shiny/api-examples/input_action_button/app-core.py similarity index 100% rename from shiny/api-examples/input_action_button/app.py rename to shiny/api-examples/input_action_button/app-core.py diff --git a/shiny/api-examples/input_action_link/app.py b/shiny/api-examples/input_action_link/app-core.py similarity index 100% rename from shiny/api-examples/input_action_link/app.py rename to shiny/api-examples/input_action_link/app-core.py diff --git a/shiny/api-examples/input_action_link/app-express.py b/shiny/api-examples/input_action_link/app-express.py new file mode 100644 index 000000000..39939d756 --- /dev/null +++ b/shiny/api-examples/input_action_link/app-express.py @@ -0,0 +1,20 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny import reactive +from shiny.express import input, render, ui + +ui.input_slider("n", "Number of observations", min=0, max=1000, value=500) +ui.input_action_link("go", "Go!") + + +@render.plot(alt="A histogram") +# reactive.event() to invalidate the plot when the button is pressed but not when +# the slider is changed +@reactive.event(input.go, ignore_none=False) +def plot(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(input.n()) + fig, ax = plt.subplots() + ax.hist(x, bins=30, density=True) + return fig diff --git a/shiny/api-examples/input_checkbox/app.py b/shiny/api-examples/input_checkbox/app-core.py similarity index 100% rename from shiny/api-examples/input_checkbox/app.py rename to shiny/api-examples/input_checkbox/app-core.py diff --git a/shiny/api-examples/input_checkbox/app-express.py b/shiny/api-examples/input_checkbox/app-express.py new file mode 100644 index 000000000..1c7e87f7e --- /dev/null +++ b/shiny/api-examples/input_checkbox/app-express.py @@ -0,0 +1,8 @@ +from shiny.express import input, render, ui + +ui.input_checkbox("somevalue", "Some value", False) + + +@render.ui +def value(): + return input.somevalue() diff --git a/shiny/api-examples/input_checkbox_group/app.py b/shiny/api-examples/input_checkbox_group/app-core.py similarity index 100% rename from shiny/api-examples/input_checkbox_group/app.py rename to shiny/api-examples/input_checkbox_group/app-core.py diff --git a/shiny/api-examples/input_checkbox_group/app-express.py b/shiny/api-examples/input_checkbox_group/app-express.py new file mode 100644 index 000000000..9f3b9652b --- /dev/null +++ b/shiny/api-examples/input_checkbox_group/app-express.py @@ -0,0 +1,18 @@ +from shiny import req +from shiny.express import input, render, ui + +ui.input_checkbox_group( + "colors", + "Choose color(s):", + { + "red": ui.span("Red", style="color: #FF0000;"), + "green": ui.span("Green", style="color: #00AA00;"), + "blue": ui.span("Blue", style="color: #0000AA;"), + }, +) + + +@render.ui +def val(): + req(input.colors()) + return "You chose " + ", ".join(input.colors()) diff --git a/shiny/api-examples/input_date/app.py b/shiny/api-examples/input_date/app-core.py similarity index 100% rename from shiny/api-examples/input_date/app.py rename to shiny/api-examples/input_date/app-core.py diff --git a/shiny/api-examples/input_date/app-express.py b/shiny/api-examples/input_date/app-express.py new file mode 100644 index 000000000..bd696beb3 --- /dev/null +++ b/shiny/api-examples/input_date/app-express.py @@ -0,0 +1,24 @@ +from datetime import date + +from shiny.express import ui + +ui.input_date("date1", "Date:", value="2016-02-29") +# Default value is the date in client's time zone +ui.input_date("date2", "Date:") +# value is always yyyy-mm-dd, even if the display format is different +ui.input_date("date3", "Date:", value="2016-02-29", format="mm/dd/yy") +# Pass in a Date object +ui.input_date("date4", "Date:", value=date(2016, 2, 29)) +# Use different language and different first day of week +ui.input_date("date5", "Date:", language="ru", weekstart=1) +# Start with decade view instead of default month view +ui.input_date("date6", "Date:", startview="decade") +# Disable Mondays and Tuesdays. +ui.input_date("date7", "Date:", daysofweekdisabled=[1, 2]) +# Disable specific dates. +ui.input_date( + "date8", + "Date:", + value="2016-02-29", + datesdisabled=["2016-03-01", "2016-03-02"], +) diff --git a/shiny/api-examples/input_date_range/app.py b/shiny/api-examples/input_date_range/app-core.py similarity index 100% rename from shiny/api-examples/input_date_range/app.py rename to shiny/api-examples/input_date_range/app-core.py diff --git a/shiny/api-examples/input_date_range/app-express.py b/shiny/api-examples/input_date_range/app-express.py new file mode 100644 index 000000000..66f79c5c4 --- /dev/null +++ b/shiny/api-examples/input_date_range/app-express.py @@ -0,0 +1,27 @@ +from datetime import date + +from shiny.express import ui + +ui.input_date_range("daterange1", "Date range:", start="2001-01-01", end="2010-12-31") +# Default start and end is the current date in the client's time zone +ui.input_date_range("daterange2", "Date range:") +# start and end are always specified in yyyy-mm-dd, even if the display +# format is different +ui.input_date_range( + "daterange3", + "Date range:", + start="2001-01-01", + end="2010-12-31", + min="2001-01-01", + max="2012-12-21", + format="mm/dd/yy", + separator=" - ", +) +# Pass in Date objects +ui.input_date_range( + "daterange4", "Date range:", start=date(2001, 1, 1), end=date(2010, 12, 31) +) +# Use different language and different first day of week +ui.input_date_range("daterange5", "Date range:", language="de", weekstart=1) +# Start with decade view instead of default month view +ui.input_date_range("daterange6", "Date range:", startview="decade") diff --git a/shiny/api-examples/input_file/app.py b/shiny/api-examples/input_file/app-core.py similarity index 100% rename from shiny/api-examples/input_file/app.py rename to shiny/api-examples/input_file/app-core.py diff --git a/shiny/api-examples/input_file/app-express.py b/shiny/api-examples/input_file/app-express.py new file mode 100644 index 000000000..81222e4f7 --- /dev/null +++ b/shiny/api-examples/input_file/app-express.py @@ -0,0 +1,48 @@ +import pandas as pd + +from shiny import reactive +from shiny.express import input, render, ui +from shiny.types import FileInfo + +ui.input_file("file1", "Choose CSV File", accept=[".csv"], multiple=False) +ui.input_checkbox_group( + "stats", + "Summary Stats", + choices=["Row Count", "Column Count", "Column Names"], + selected=["Row Count", "Column Count", "Column Names"], +) + + +@reactive.Calc +def parsed_file(): + file: list[FileInfo] | None = input.file1() + if file is None: + return pd.DataFrame() + return pd.read_csv(file[0]["datapath"]) # pyright: ignore[reportUnknownMemberType] + + +@render.table +def summary(): + df = parsed_file() + + if df.empty: + return pd.DataFrame() + + # Get the row count, column count, and column names of the DataFrame + row_count = df.shape[0] + column_count = df.shape[1] + names = df.columns.tolist() + column_names = ", ".join(str(name) for name in names) + + # Create a new DataFrame to display the information + info_df = pd.DataFrame( + { + "Row Count": [row_count], + "Column Count": [column_count], + "Column Names": [column_names], + } + ) + + # input.stats() is a list of strings; subset the columns based on the selected + # checkboxes + return info_df.loc[:, input.stats()] diff --git a/shiny/api-examples/input_numeric/app.py b/shiny/api-examples/input_numeric/app-core.py similarity index 100% rename from shiny/api-examples/input_numeric/app.py rename to shiny/api-examples/input_numeric/app-core.py diff --git a/shiny/api-examples/input_numeric/app-express.py b/shiny/api-examples/input_numeric/app-express.py new file mode 100644 index 000000000..7e06f1c0a --- /dev/null +++ b/shiny/api-examples/input_numeric/app-express.py @@ -0,0 +1,8 @@ +from shiny.express import input, render, ui + +ui.input_numeric("obs", "Observations:", 10, min=1, max=100) + + +@render.code +def value(): + return input.obs() diff --git a/shiny/api-examples/input_password/app.py b/shiny/api-examples/input_password/app-core.py similarity index 100% rename from shiny/api-examples/input_password/app.py rename to shiny/api-examples/input_password/app-core.py diff --git a/shiny/api-examples/input_password/app-express.py b/shiny/api-examples/input_password/app-express.py new file mode 100644 index 000000000..4366c5303 --- /dev/null +++ b/shiny/api-examples/input_password/app-express.py @@ -0,0 +1,11 @@ +from shiny import reactive +from shiny.express import input, render, ui + +ui.input_password("password", "Password:") +ui.input_action_button("go", "Go") + + +@render.code +@reactive.event(input.go) +def value(): + return input.password() diff --git a/shiny/api-examples/input_radio_buttons/app.py b/shiny/api-examples/input_radio_buttons/app-core.py similarity index 100% rename from shiny/api-examples/input_radio_buttons/app.py rename to shiny/api-examples/input_radio_buttons/app-core.py diff --git a/shiny/api-examples/input_radio_buttons/app-express.py b/shiny/api-examples/input_radio_buttons/app-express.py new file mode 100644 index 000000000..5354f4134 --- /dev/null +++ b/shiny/api-examples/input_radio_buttons/app-express.py @@ -0,0 +1,15 @@ +from shiny.express import input, render, ui + +ui.input_radio_buttons( + "rb", + "Choose one:", + { + "html": ui.HTML("Red Text"), + "text": "Normal text", + }, +) + + +@render.express +def val(): + "You chose " + input.rb() diff --git a/shiny/api-examples/input_select/app.py b/shiny/api-examples/input_select/app-core.py similarity index 100% rename from shiny/api-examples/input_select/app.py rename to shiny/api-examples/input_select/app-core.py diff --git a/shiny/api-examples/input_select/app-express.py b/shiny/api-examples/input_select/app-express.py new file mode 100644 index 000000000..c45482f0c --- /dev/null +++ b/shiny/api-examples/input_select/app-express.py @@ -0,0 +1,16 @@ +from shiny.express import input, render, ui + +ui.input_select( + "state", + "Choose a state:", + { + "East Coast": {"NY": "New York", "NJ": "New Jersey", "CT": "Connecticut"}, + "West Coast": {"WA": "Washington", "OR": "Oregon", "CA": "California"}, + "Midwest": {"MN": "Minnesota", "WI": "Wisconsin", "IA": "Iowa"}, + }, +) + + +@render.text +def value(): + return "You choose: " + str(input.state()) diff --git a/shiny/api-examples/input_selectize/app.py b/shiny/api-examples/input_selectize/app-core.py similarity index 97% rename from shiny/api-examples/input_selectize/app.py rename to shiny/api-examples/input_selectize/app-core.py index bc2dd9bd9..23a63d376 100644 --- a/shiny/api-examples/input_selectize/app.py +++ b/shiny/api-examples/input_selectize/app-core.py @@ -17,7 +17,7 @@ ), ui.output_text("value"), ui.input_selectize( - "state", + "state2", "Selectize Options", states, multiple=True, @@ -32,7 +32,7 @@ ), ), ui.input_selectize( - "state", + "state3", "Selectize plugins", states, multiple=True, diff --git a/shiny/api-examples/input_selectize/app-express.py b/shiny/api-examples/input_selectize/app-express.py new file mode 100644 index 000000000..d58e04154 --- /dev/null +++ b/shiny/api-examples/input_selectize/app-express.py @@ -0,0 +1,45 @@ +from html import escape # noqa: F401 + +from shiny.express import input, render, ui + +states = { + "East Coast": {"NY": "New York", "NJ": "New Jersey", "CT": "Connecticut"}, + "West Coast": {"WA": "Washington", "OR": "Oregon", "CA": "California"}, + "Midwest": {"MN": "Minnesota", "WI": "Wisconsin", "IA": "Iowa"}, +} + +ui.input_selectize( + "state", + "Choose a state:", + states, + multiple=True, +) + + +@render.text +def value(): + return "You choose: " + str(input.state()) + + +ui.input_selectize( + "state2", + "Selectize Options", + states, + multiple=True, + options=( + { + "placeholder": "Enter text", + "render": ui.js_eval( + '{option: function(item, escape) {return "
Select " + escape(item.label) + "
";}}' + ), + "create": True, + } + ), +) +ui.input_selectize( + "state3", + "Selectize plugins", + states, + multiple=True, + options={"plugins": ["clear_button"]}, +) diff --git a/shiny/api-examples/input_slider/app.py b/shiny/api-examples/input_slider/app-core.py similarity index 100% rename from shiny/api-examples/input_slider/app.py rename to shiny/api-examples/input_slider/app-core.py diff --git a/shiny/api-examples/input_slider/app-express.py b/shiny/api-examples/input_slider/app-express.py new file mode 100644 index 000000000..e6e701594 --- /dev/null +++ b/shiny/api-examples/input_slider/app-express.py @@ -0,0 +1,16 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny.express import input, render, ui + +ui.input_slider("obs", "Number of bins:", min=10, max=100, value=30) + + +@render.plot +def distPlot(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + + fig, ax = plt.subplots() + ax.hist(x, input.obs(), density=True) + return fig diff --git a/shiny/api-examples/input_switch/app.py b/shiny/api-examples/input_switch/app-core.py similarity index 86% rename from shiny/api-examples/input_switch/app.py rename to shiny/api-examples/input_switch/app-core.py index a438d8195..f05cba431 100644 --- a/shiny/api-examples/input_switch/app.py +++ b/shiny/api-examples/input_switch/app-core.py @@ -2,12 +2,12 @@ app_ui = ui.page_fluid( ui.input_switch("somevalue", "Some value", False), - ui.output_ui("value"), + ui.output_text("value"), ) def server(input: Inputs, output: Outputs, session: Session): - @render.ui + @render.text def value(): return input.somevalue() diff --git a/shiny/api-examples/input_switch/app-express.py b/shiny/api-examples/input_switch/app-express.py new file mode 100644 index 000000000..efd623850 --- /dev/null +++ b/shiny/api-examples/input_switch/app-express.py @@ -0,0 +1,8 @@ +from shiny.express import input, render, ui + +ui.input_switch("somevalue", "Some value", False) + + +@render.text +def value(): + return input.somevalue() diff --git a/shiny/api-examples/input_text/app.py b/shiny/api-examples/input_text/app-core.py similarity index 100% rename from shiny/api-examples/input_text/app.py rename to shiny/api-examples/input_text/app-core.py diff --git a/shiny/api-examples/input_text/app-express.py b/shiny/api-examples/input_text/app-express.py new file mode 100644 index 000000000..8c0c30d69 --- /dev/null +++ b/shiny/api-examples/input_text/app-express.py @@ -0,0 +1,8 @@ +from shiny.express import input, render, ui + +ui.input_text("caption", "Caption:", "Data summary") + + +@render.code +def value(): + return input.caption() diff --git a/shiny/api-examples/input_text_area/app.py b/shiny/api-examples/input_text_area/app-core.py similarity index 100% rename from shiny/api-examples/input_text_area/app.py rename to shiny/api-examples/input_text_area/app-core.py diff --git a/shiny/api-examples/input_text_area/app-express.py b/shiny/api-examples/input_text_area/app-express.py new file mode 100644 index 000000000..4841f56ee --- /dev/null +++ b/shiny/api-examples/input_text_area/app-express.py @@ -0,0 +1,25 @@ +from shiny.express import input, render, ui + +ui.input_text_area( + "caption_regular", + "Caption:", + "Data summary\nwith\nmultiple\nlines", +) + + +@render.text +def value_regular(): + return input.caption_regular() + + +ui.input_text_area( + "caption_autoresize", + ui.markdown("Caption (w/ `autoresize=True`):"), + "Data summary\nwith\nmultiple\nlines", + autoresize=True, +) + + +@render.text +def value_autoresize(): + return input.caption_autoresize() diff --git a/shiny/api-examples/insert_accordion_panel/app.py b/shiny/api-examples/insert_accordion_panel/app-core.py similarity index 100% rename from shiny/api-examples/insert_accordion_panel/app.py rename to shiny/api-examples/insert_accordion_panel/app-core.py diff --git a/shiny/api-examples/insert_ui/app.py b/shiny/api-examples/insert_ui/app-core.py similarity index 100% rename from shiny/api-examples/insert_ui/app.py rename to shiny/api-examples/insert_ui/app-core.py diff --git a/shiny/api-examples/invalidate_later/app.py b/shiny/api-examples/invalidate_later/app-core.py similarity index 60% rename from shiny/api-examples/invalidate_later/app.py rename to shiny/api-examples/invalidate_later/app-core.py index 7966d01f0..66de8233d 100644 --- a/shiny/api-examples/invalidate_later/app.py +++ b/shiny/api-examples/invalidate_later/app-core.py @@ -2,16 +2,11 @@ from shiny import App, Inputs, Outputs, Session, reactive, render, ui -app_ui = ui.page_fluid(ui.output_ui("value")) +app_ui = ui.page_fluid(ui.output_text("value")) def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - def _(): - reactive.invalidate_later(0.5) - print("Random int: ", random.randint(0, 10000)) - - @render.ui + @render.text def value(): reactive.invalidate_later(0.5) return "Random int: " + str(random.randint(0, 10000)) diff --git a/shiny/api-examples/invalidate_later/app-express.py b/shiny/api-examples/invalidate_later/app-express.py new file mode 100644 index 000000000..270c5e889 --- /dev/null +++ b/shiny/api-examples/invalidate_later/app-express.py @@ -0,0 +1,10 @@ +import random + +from shiny import reactive +from shiny.express import render + + +@render.text +def value(): + reactive.invalidate_later(0.5) + return "Random int: " + str(random.randint(0, 10000)) diff --git a/shiny/api-examples/isolate/app.py b/shiny/api-examples/isolate/app-core.py similarity index 100% rename from shiny/api-examples/isolate/app.py rename to shiny/api-examples/isolate/app-core.py diff --git a/shiny/api-examples/isolate/app-express.py b/shiny/api-examples/isolate/app-express.py new file mode 100644 index 000000000..52f8b9c38 --- /dev/null +++ b/shiny/api-examples/isolate/app-express.py @@ -0,0 +1,23 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny import reactive +from shiny.express import input, render, ui + +ui.input_slider("n", "Number of observations", min=0, max=1000, value=500) +ui.input_action_button("go", "Go!", class_="btn-success") + + +@render.plot(alt="A histogram") +def plot(): + # Take a reactive dependency on the action button... + input.go() + + # ...but don't take a reactive dependency on the slider + with reactive.isolate(): + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(input.n()) + + fig, ax = plt.subplots() + ax.hist(x, bins=30, density=True) + return fig diff --git a/shiny/api-examples/layout_column_wrap/app.py b/shiny/api-examples/layout_column_wrap/app-core.py similarity index 100% rename from shiny/api-examples/layout_column_wrap/app.py rename to shiny/api-examples/layout_column_wrap/app-core.py diff --git a/shiny/api-examples/layout_column_wrap/app-express.py b/shiny/api-examples/layout_column_wrap/app-express.py new file mode 100644 index 000000000..0754d8b74 --- /dev/null +++ b/shiny/api-examples/layout_column_wrap/app-express.py @@ -0,0 +1,19 @@ +from shiny.express import ui + +with ui.hold() as a_card: + with ui.card(): + "A simple card" + +# Always has 2 columns (on non-mobile) +with ui.layout_column_wrap(width=1 / 2): + a_card + a_card + a_card + +ui.hr() + +# Has three columns when viewport is wider than 750px +with ui.layout_column_wrap(width="250px"): + a_card + a_card + a_card diff --git a/shiny/api-examples/layout_columns/app.py b/shiny/api-examples/layout_columns/app-core.py similarity index 100% rename from shiny/api-examples/layout_columns/app.py rename to shiny/api-examples/layout_columns/app-core.py diff --git a/shiny/api-examples/layout_columns/app-express.py b/shiny/api-examples/layout_columns/app-express.py new file mode 100644 index 000000000..768056d5c --- /dev/null +++ b/shiny/api-examples/layout_columns/app-express.py @@ -0,0 +1,38 @@ +from model_plots import ( + plot_accuracy_over_time, + plot_feature_importance, + plot_loss_over_time, +) + +from shiny.express import render, ui + +ui.page_opts(title="Model Dashboard") + +ui.markdown("Using `ui.layout_columns()` for the layout.") + + +with ui.layout_columns( + col_widths={"sm": (5, 7, 12)}, + # row_heights=(2, 3), + # height="700px", +): + with ui.card(full_screen=True): + ui.card_header("Loss Over Time") + + @render.plot + def loss_over_time(): + return plot_loss_over_time() + + with ui.card(full_screen=True): + ui.card_header("Accuracy Over Time") + + @render.plot + def accuracy_over_time(): + return plot_accuracy_over_time() + + with ui.card(full_screen=True): + ui.card_header("Feature Importance") + + @render.plot + def feature_importance(): + return plot_feature_importance() diff --git a/shiny/api-examples/layout_sidebar/app.py b/shiny/api-examples/layout_sidebar/app-core.py similarity index 100% rename from shiny/api-examples/layout_sidebar/app.py rename to shiny/api-examples/layout_sidebar/app-core.py diff --git a/shiny/api-examples/layout_sidebar/app-express.py b/shiny/api-examples/layout_sidebar/app-express.py new file mode 100644 index 000000000..10482a396 --- /dev/null +++ b/shiny/api-examples/layout_sidebar/app-express.py @@ -0,0 +1,17 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny.express import input, render, ui + +with ui.layout_sidebar(): + with ui.sidebar(): + ui.input_slider("n", "N", min=0, max=100, value=20) + + @render.plot(alt="A histogram") + def plot() -> object: + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + + fig, ax = plt.subplots() + ax.hist(x, input.n(), density=True) + return fig diff --git a/shiny/api-examples/markdown/app.py b/shiny/api-examples/markdown/app-core.py similarity index 100% rename from shiny/api-examples/markdown/app.py rename to shiny/api-examples/markdown/app-core.py diff --git a/shiny/api-examples/markdown/app-express.py b/shiny/api-examples/markdown/app-express.py new file mode 100644 index 000000000..f1db29501 --- /dev/null +++ b/shiny/api-examples/markdown/app-express.py @@ -0,0 +1,13 @@ +from shiny.express import ui + +ui.markdown( + """ + # Hello World + + This is **markdown** and here is some `code`: + + ```python + print('Hello world!') + ``` + """ +) diff --git a/shiny/api-examples/modal/app.py b/shiny/api-examples/modal/app-core.py similarity index 100% rename from shiny/api-examples/modal/app.py rename to shiny/api-examples/modal/app-core.py diff --git a/shiny/api-examples/nav_panel/app.py b/shiny/api-examples/nav_panel/app-core.py similarity index 100% rename from shiny/api-examples/nav_panel/app.py rename to shiny/api-examples/nav_panel/app-core.py diff --git a/shiny/api-examples/navset_hidden/app.py b/shiny/api-examples/navset_hidden/app-core.py similarity index 100% rename from shiny/api-examples/navset_hidden/app.py rename to shiny/api-examples/navset_hidden/app-core.py diff --git a/shiny/api-examples/notification_show/app.py b/shiny/api-examples/notification_show/app-core.py similarity index 100% rename from shiny/api-examples/notification_show/app.py rename to shiny/api-examples/notification_show/app-core.py diff --git a/shiny/api-examples/on_ended/app.py b/shiny/api-examples/on_ended/app-core.py similarity index 100% rename from shiny/api-examples/on_ended/app.py rename to shiny/api-examples/on_ended/app-core.py diff --git a/shiny/api-examples/on_flush/app.py b/shiny/api-examples/on_flush/app-core.py similarity index 100% rename from shiny/api-examples/on_flush/app.py rename to shiny/api-examples/on_flush/app-core.py diff --git a/shiny/api-examples/on_flushed/app.py b/shiny/api-examples/on_flushed/app-core.py similarity index 100% rename from shiny/api-examples/on_flushed/app.py rename to shiny/api-examples/on_flushed/app-core.py diff --git a/shiny/api-examples/output_image/app.py b/shiny/api-examples/output_image/app-core.py similarity index 100% rename from shiny/api-examples/output_image/app.py rename to shiny/api-examples/output_image/app-core.py diff --git a/shiny/api-examples/output_plot/app.py b/shiny/api-examples/output_plot/app-core.py similarity index 100% rename from shiny/api-examples/output_plot/app.py rename to shiny/api-examples/output_plot/app-core.py diff --git a/shiny/api-examples/output_table/app.py b/shiny/api-examples/output_table/app-core.py similarity index 100% rename from shiny/api-examples/output_table/app.py rename to shiny/api-examples/output_table/app-core.py diff --git a/shiny/api-examples/output_text/app.py b/shiny/api-examples/output_text/app-core.py similarity index 100% rename from shiny/api-examples/output_text/app.py rename to shiny/api-examples/output_text/app-core.py diff --git a/shiny/api-examples/output_transformer/app.py b/shiny/api-examples/output_transformer/app-core.py similarity index 100% rename from shiny/api-examples/output_transformer/app.py rename to shiny/api-examples/output_transformer/app-core.py diff --git a/shiny/api-examples/output_ui/app.py b/shiny/api-examples/output_ui/app-core.py similarity index 100% rename from shiny/api-examples/output_ui/app.py rename to shiny/api-examples/output_ui/app-core.py diff --git a/shiny/api-examples/page_fixed/app.py b/shiny/api-examples/page_fixed/app-core.py similarity index 100% rename from shiny/api-examples/page_fixed/app.py rename to shiny/api-examples/page_fixed/app-core.py diff --git a/shiny/api-examples/page_fluid/app.py b/shiny/api-examples/page_fluid/app-core.py similarity index 100% rename from shiny/api-examples/page_fluid/app.py rename to shiny/api-examples/page_fluid/app-core.py diff --git a/shiny/api-examples/page_sidebar/app.py b/shiny/api-examples/page_sidebar/app-core.py similarity index 98% rename from shiny/api-examples/page_sidebar/app.py rename to shiny/api-examples/page_sidebar/app-core.py index ba84c3fe8..8ce58f8b8 100644 --- a/shiny/api-examples/page_sidebar/app.py +++ b/shiny/api-examples/page_sidebar/app-core.py @@ -14,7 +14,6 @@ def server(input: Inputs, output: Outputs, session: Session): - @output @render.plot(alt="A histogram") def plot() -> object: np.random.seed(19680801) diff --git a/shiny/api-examples/page_sidebar/app-express.py b/shiny/api-examples/page_sidebar/app-express.py new file mode 100644 index 000000000..4781f851c --- /dev/null +++ b/shiny/api-examples/page_sidebar/app-express.py @@ -0,0 +1,17 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny.express import input, render, ui + +with ui.sidebar(): + ui.input_slider("n", "N", min=0, max=100, value=20) + + +@render.plot(alt="A histogram") +def plot() -> object: + np.random.seed(19680801) + x = 100 + 15 * np.random.randn(437) + + fig, ax = plt.subplots() + ax.hist(x, input.n(), density=True) + return fig diff --git a/shiny/api-examples/panel_absolute/app.py b/shiny/api-examples/panel_absolute/app-core.py similarity index 96% rename from shiny/api-examples/panel_absolute/app.py rename to shiny/api-examples/panel_absolute/app-core.py index 10fab125e..3340e5f93 100644 --- a/shiny/api-examples/panel_absolute/app.py +++ b/shiny/api-examples/panel_absolute/app-core.py @@ -9,7 +9,7 @@ draggable=True, width="300px", right="50px", - top="50%", + top="25%", ), ) diff --git a/shiny/api-examples/panel_absolute/app-express.py b/shiny/api-examples/panel_absolute/app-express.py new file mode 100644 index 000000000..8cf254ecb --- /dev/null +++ b/shiny/api-examples/panel_absolute/app-express.py @@ -0,0 +1,8 @@ +from shiny.express import ui + +ui.h2("A basic absolute panel example") + +with ui.panel_absolute(draggable=True, width="300px", right="50px", top="25%"): + with ui.panel_well(): + "Drag me around!" + ui.input_slider("n", "N", min=0, max=100, value=20) diff --git a/shiny/api-examples/panel_conditional/app.py b/shiny/api-examples/panel_conditional/app-core.py similarity index 100% rename from shiny/api-examples/panel_conditional/app.py rename to shiny/api-examples/panel_conditional/app-core.py diff --git a/shiny/api-examples/panel_conditional/app-express.py b/shiny/api-examples/panel_conditional/app-express.py new file mode 100644 index 000000000..8f5fbb229 --- /dev/null +++ b/shiny/api-examples/panel_conditional/app-express.py @@ -0,0 +1,12 @@ +from shiny.express import ui + +ui.input_checkbox("show", "Show radio buttons", False) + +with ui.panel_conditional("input.show"): + ui.input_radio_buttons("radio", "Choose ", ["slider", "select"]) + +with ui.panel_conditional("input.show && input.radio === 'slider'"): + ui.input_slider("slider", None, min=0, max=100, value=50) + +with ui.panel_conditional("input.show && input.radio === 'select'"): + ui.input_select("slider", None, ["A", "B", "C"]) diff --git a/shiny/api-examples/panel_title/app.py b/shiny/api-examples/panel_title/app-core.py similarity index 100% rename from shiny/api-examples/panel_title/app.py rename to shiny/api-examples/panel_title/app-core.py diff --git a/shiny/api-examples/poll/app.py b/shiny/api-examples/poll/app-core.py similarity index 89% rename from shiny/api-examples/poll/app.py rename to shiny/api-examples/poll/app-core.py index c9411d28a..f6ab621b6 100644 --- a/shiny/api-examples/poll/app.py +++ b/shiny/api-examples/poll/app-core.py @@ -89,9 +89,8 @@ def stock_quotes() -> pd.DataFrame: # === Define the Shiny UI and server =============================== app_ui = ui.page_fluid( - ui.row( - ui.column( - 8, + ui.layout_columns( + ui.card( ui.markdown( """ # `shiny.reactive.poll` demo @@ -100,11 +99,14 @@ def stock_quotes() -> pd.DataFrame: case, an in-memory sqlite3) with the help of `shiny.reactive.poll`. """ ), - class_="mb-3", + ui.input_selectize( + "symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True + ), + ui.output_data_frame("table"), + fill=False, ), - ), - ui.input_selectize("symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True), - ui.output_ui("table"), + col_widths=[8, 4], + ) ) @@ -115,13 +117,9 @@ def filtered_quotes(): df = df[df["symbol"].isin(input.symbols())] return df - @render.ui + @render.data_frame def table(): - return ui.HTML( - filtered_quotes().to_html( - index=False, classes="table font-monospace w-auto" - ) - ) + return filtered_quotes() app = App(app_ui, server) diff --git a/shiny/api-examples/popover/app.py b/shiny/api-examples/popover/app-core.py similarity index 100% rename from shiny/api-examples/popover/app.py rename to shiny/api-examples/popover/app-core.py diff --git a/shiny/api-examples/remove_accordion_panel/app.py b/shiny/api-examples/remove_accordion_panel/app-core.py similarity index 100% rename from shiny/api-examples/remove_accordion_panel/app.py rename to shiny/api-examples/remove_accordion_panel/app-core.py diff --git a/shiny/api-examples/remove_ui/app.py b/shiny/api-examples/remove_ui/app-core.py similarity index 100% rename from shiny/api-examples/remove_ui/app.py rename to shiny/api-examples/remove_ui/app-core.py diff --git a/shiny/api-examples/render_express/app.py b/shiny/api-examples/render_express/app-core.py similarity index 100% rename from shiny/api-examples/render_express/app.py rename to shiny/api-examples/render_express/app-core.py diff --git a/shiny/api-examples/render_image/app.py b/shiny/api-examples/render_image/app-core.py similarity index 100% rename from shiny/api-examples/render_image/app.py rename to shiny/api-examples/render_image/app-core.py diff --git a/shiny/api-examples/req/app.py b/shiny/api-examples/req/app-core.py similarity index 100% rename from shiny/api-examples/req/app.py rename to shiny/api-examples/req/app-core.py diff --git a/shiny/api-examples/row/app.py b/shiny/api-examples/row/app-core.py similarity index 100% rename from shiny/api-examples/row/app.py rename to shiny/api-examples/row/app-core.py diff --git a/shiny/api-examples/send_custom_message/app.py b/shiny/api-examples/send_custom_message/app-core.py similarity index 100% rename from shiny/api-examples/send_custom_message/app.py rename to shiny/api-examples/send_custom_message/app-core.py diff --git a/shiny/api-examples/showcase_bottom/app.py b/shiny/api-examples/showcase_bottom/app-core.py similarity index 100% rename from shiny/api-examples/showcase_bottom/app.py rename to shiny/api-examples/showcase_bottom/app-core.py diff --git a/shiny/api-examples/showcase_left_center/app.py b/shiny/api-examples/showcase_left_center/app-core.py similarity index 100% rename from shiny/api-examples/showcase_left_center/app.py rename to shiny/api-examples/showcase_left_center/app-core.py diff --git a/shiny/api-examples/showcase_top_right/app.py b/shiny/api-examples/showcase_top_right/app-core.py similarity index 100% rename from shiny/api-examples/showcase_top_right/app.py rename to shiny/api-examples/showcase_top_right/app-core.py diff --git a/shiny/api-examples/sidebar/app.py b/shiny/api-examples/sidebar/app-core.py similarity index 100% rename from shiny/api-examples/sidebar/app.py rename to shiny/api-examples/sidebar/app-core.py diff --git a/shiny/api-examples/sidebar/app-express.py b/shiny/api-examples/sidebar/app-express.py new file mode 100644 index 000000000..17e80bb47 --- /dev/null +++ b/shiny/api-examples/sidebar/app-express.py @@ -0,0 +1,42 @@ +from shiny.express import input, render, ui + +ui.page_opts(fillable=True) + +with ui.card(): + with ui.layout_sidebar(): + with ui.sidebar(id="sidebar_left", open="desktop"): + "Left sidebar content" + + @render.text + def state_left(): + return f"input.sidebar_left(): {input.sidebar_left()}" + + +with ui.card(): + with ui.layout_sidebar(): + with ui.sidebar(id="sidebar_right", position="right", open="desktop"): + "Right sidebar content" + + @render.text + def state_right(): + return f"input.sidebar_right(): {input.sidebar_right()}" + + +with ui.card(): + with ui.layout_sidebar(): + with ui.sidebar(id="sidebar_closed", open="closed"): + "Closed sidebar content" + + @render.text + def state_closed(): + return f"input.sidebar_closed(): {input.sidebar_closed()}" + + +with ui.card(): + with ui.layout_sidebar(): + with ui.sidebar(id="sidebar_always", open="always"): + "Always sidebar content" + + @render.text + def state_always(): + return f"input.sidebar_always(): {input.sidebar_always()}" diff --git a/shiny/api-examples/template/app.py b/shiny/api-examples/template/app-core.py similarity index 100% rename from shiny/api-examples/template/app.py rename to shiny/api-examples/template/app-core.py diff --git a/shiny/api-examples/todo_list/app.py b/shiny/api-examples/todo_list/app-core.py similarity index 100% rename from shiny/api-examples/todo_list/app.py rename to shiny/api-examples/todo_list/app-core.py diff --git a/shiny/api-examples/tooltip/app.py b/shiny/api-examples/tooltip/app-core.py similarity index 100% rename from shiny/api-examples/tooltip/app.py rename to shiny/api-examples/tooltip/app-core.py diff --git a/shiny/api-examples/update_accordion/app.py b/shiny/api-examples/update_accordion/app-core.py similarity index 100% rename from shiny/api-examples/update_accordion/app.py rename to shiny/api-examples/update_accordion/app-core.py diff --git a/shiny/api-examples/update_accordion_panel/app.py b/shiny/api-examples/update_accordion_panel/app-core.py similarity index 100% rename from shiny/api-examples/update_accordion_panel/app.py rename to shiny/api-examples/update_accordion_panel/app-core.py diff --git a/shiny/api-examples/update_action_button/app.py b/shiny/api-examples/update_action_button/app-core.py similarity index 100% rename from shiny/api-examples/update_action_button/app.py rename to shiny/api-examples/update_action_button/app-core.py diff --git a/shiny/api-examples/update_checkbox/app.py b/shiny/api-examples/update_checkbox/app-core.py similarity index 100% rename from shiny/api-examples/update_checkbox/app.py rename to shiny/api-examples/update_checkbox/app-core.py diff --git a/shiny/api-examples/update_checkbox_group/app.py b/shiny/api-examples/update_checkbox_group/app-core.py similarity index 100% rename from shiny/api-examples/update_checkbox_group/app.py rename to shiny/api-examples/update_checkbox_group/app-core.py diff --git a/shiny/api-examples/update_date/app.py b/shiny/api-examples/update_date/app-core.py similarity index 100% rename from shiny/api-examples/update_date/app.py rename to shiny/api-examples/update_date/app-core.py diff --git a/shiny/api-examples/update_date_range/app.py b/shiny/api-examples/update_date_range/app-core.py similarity index 100% rename from shiny/api-examples/update_date_range/app.py rename to shiny/api-examples/update_date_range/app-core.py diff --git a/shiny/api-examples/update_navs/app.py b/shiny/api-examples/update_navs/app-core.py similarity index 100% rename from shiny/api-examples/update_navs/app.py rename to shiny/api-examples/update_navs/app-core.py diff --git a/shiny/api-examples/update_numeric/app.py b/shiny/api-examples/update_numeric/app-core.py similarity index 100% rename from shiny/api-examples/update_numeric/app.py rename to shiny/api-examples/update_numeric/app-core.py diff --git a/shiny/api-examples/update_popover/app.py b/shiny/api-examples/update_popover/app-core.py similarity index 100% rename from shiny/api-examples/update_popover/app.py rename to shiny/api-examples/update_popover/app-core.py diff --git a/shiny/api-examples/update_radio_buttons/app.py b/shiny/api-examples/update_radio_buttons/app-core.py similarity index 100% rename from shiny/api-examples/update_radio_buttons/app.py rename to shiny/api-examples/update_radio_buttons/app-core.py diff --git a/shiny/api-examples/update_select/app.py b/shiny/api-examples/update_select/app-core.py similarity index 100% rename from shiny/api-examples/update_select/app.py rename to shiny/api-examples/update_select/app-core.py diff --git a/shiny/api-examples/update_selectize/app.py b/shiny/api-examples/update_selectize/app-core.py similarity index 100% rename from shiny/api-examples/update_selectize/app.py rename to shiny/api-examples/update_selectize/app-core.py diff --git a/shiny/api-examples/update_sidebar/app.py b/shiny/api-examples/update_sidebar/app-core.py similarity index 100% rename from shiny/api-examples/update_sidebar/app.py rename to shiny/api-examples/update_sidebar/app-core.py diff --git a/shiny/api-examples/update_slider/app.py b/shiny/api-examples/update_slider/app-core.py similarity index 100% rename from shiny/api-examples/update_slider/app.py rename to shiny/api-examples/update_slider/app-core.py diff --git a/shiny/api-examples/update_text/app.py b/shiny/api-examples/update_text/app-core.py similarity index 100% rename from shiny/api-examples/update_text/app.py rename to shiny/api-examples/update_text/app-core.py diff --git a/shiny/api-examples/update_tooltip/app.py b/shiny/api-examples/update_tooltip/app-core.py similarity index 100% rename from shiny/api-examples/update_tooltip/app.py rename to shiny/api-examples/update_tooltip/app-core.py diff --git a/shiny/api-examples/value_box/app.py b/shiny/api-examples/value_box/app-core.py similarity index 100% rename from shiny/api-examples/value_box/app.py rename to shiny/api-examples/value_box/app-core.py diff --git a/shiny/api-examples/www_dir/app.py b/shiny/api-examples/www_dir/app-core.py similarity index 100% rename from shiny/api-examples/www_dir/app.py rename to shiny/api-examples/www_dir/app-core.py diff --git a/shiny/reactive/_core.py b/shiny/reactive/_core.py index d7f0219c8..24c9de1ae 100644 --- a/shiny/reactive/_core.py +++ b/shiny/reactive/_core.py @@ -22,7 +22,7 @@ from .. import _utils from .._datastructures import PriorityQueueFIFO -from .._docstring import add_example, no_example +from .._docstring import add_example, no_example, no_example_express from ..types import MISSING, MISSING_TYPE if TYPE_CHECKING: @@ -199,7 +199,7 @@ def isolate(self) -> Generator[None, None, None]: _reactive_environment = ReactiveEnvironment() -@add_example() +@no_example_express(add_example()) @contextlib.contextmanager def isolate() -> Generator[None, None, None]: """ diff --git a/shiny/types.py b/shiny/types.py index e116e8454..963144412 100644 --- a/shiny/types.py +++ b/shiny/types.py @@ -16,7 +16,7 @@ from htmltools import TagChild -from ._docstring import add_example +from ._docstring import add_example, no_example_express from ._typing_extensions import NotRequired, TypedDict if TYPE_CHECKING: @@ -55,7 +55,7 @@ class FileInfo(TypedDict): """The path to the file on the server.""" -@add_example(ex_dir="./api-examples/output_image") +@no_example_express(add_example(ex_dir="./api-examples/output_image")) class ImgData(TypedDict): """ Return type for :class:`~shiny.render.image`. @@ -79,7 +79,7 @@ class ImgData(TypedDict): """TODO """ -@add_example() +@no_example_express(add_example()) class SafeException(Exception): """ Throw a safe exception. @@ -93,7 +93,7 @@ class SafeException(Exception): pass -@add_example() +@no_example_express(add_example()) class SilentException(Exception): """ Throw a silent exception. @@ -116,7 +116,7 @@ class SilentException(Exception): pass -@add_example() +@no_example_express(add_example()) class SilentCancelOutputException(Exception): """ Throw a silent exception and don't clear output