From 189807e7cb39804f1781a0854432eb52e33c47d4 Mon Sep 17 00:00:00 2001 From: Joe Cheng Date: Tue, 23 Jan 2024 14:44:58 -0800 Subject: [PATCH 01/45] Create two different api doc folders, one for Core and one for Express --- docs/Makefile | 3 +- docs/_quarto.yml | 6 +- docs/_quartodoc-core.yml | 315 ++++++++++++++++++ ...{_quartodoc.yml => _quartodoc-express.yml} | 16 +- 4 files changed, 323 insertions(+), 17 deletions(-) create mode 100644 docs/_quartodoc-core.yml rename docs/{_quartodoc.yml => _quartodoc-express.yml} (96%) diff --git a/docs/Makefile b/docs/Makefile index 583ca792c..77694d6f9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -51,7 +51,8 @@ quartodoc: $(PYBIN) ## Build qmd files for API docs $(eval export IN_QUARTODOC=true) . $(PYBIN)/activate \ && quartodoc interlinks \ - && quartodoc build --config _quartodoc.yml --verbose + && quartodoc build --config _quartodoc-core.yml --verbose \ + && quartodoc build --config _quartodoc-express.yml --verbose site: ## Build website . $(PYBIN)/activate \ diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 3e1093ae7..43800dad0 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -18,8 +18,10 @@ website: pinned: true search: true left: - - text: "API" - file: api/index.qmd + - text: "API (Express)" + file: api-express/index.qmd + - text: "API (Core)" + file: api-core/index.qmd right: - icon: github href: https://github.com/posit-dev/py-shiny diff --git a/docs/_quartodoc-core.yml b/docs/_quartodoc-core.yml new file mode 100644 index 000000000..69c829e47 --- /dev/null +++ b/docs/_quartodoc-core.yml @@ -0,0 +1,315 @@ +quartodoc: + style: pkgdown + dir: api/core + out_index: index.qmd + package: shiny + rewrite_all_pages: false + sidebar: api/core/_sidebar.yml + dynamic: true + renderer: + style: _renderer.py + show_signature_annotations: false + sections: + - title: Page containers + desc: Create a user interface page container. + contents: + - ui.page_sidebar + - ui.page_navbar + - ui.page_sidebar + - ui.page_fillable + - ui.page_fluid + - ui.page_fixed + - ui.page_bootstrap + - ui.page_auto + - ui.page_output + - title: UI Layouts + desc: Control the layout of multiple UI components. + contents: + - ui.sidebar + - ui.layout_sidebar + - ui.layout_columns + - ui.layout_column_wrap + - ui.card + - ui.card_header + - ui.card_footer + - ui.popover + - ui.tooltip + - ui.accordion + - ui.accordion_panel + - ui.column + - ui.row + - title: UI Inputs + desc: Create UI that prompts the user for input values or interaction. + contents: + - ui.input_select + - ui.input_selectize + - ui.input_slider + - ui.input_date + - ui.input_date_range + - ui.input_checkbox + - ui.input_checkbox_group + - ui.input_switch + - ui.input_radio_buttons + - ui.input_numeric + - ui.input_text + - ui.input_text_area + - ui.input_password + - ui.input_action_button + - ui.input_action_link + - title: Value boxes + desc: Prominently display a value and label in a box that can be expanded to show more information. + contents: + - ui.value_box + - ui.value_box_theme + - ui.showcase_bottom + - ui.showcase_left_center + - ui.showcase_top_right + - title: Navigation (tab) panels + desc: Create segments of UI content. + contents: + - ui.nav_panel + - ui.nav_spacer + - ui.nav_menu + - ui.nav_control + - ui.navset_bar + - ui.navset_tab + - ui.navset_pill + - ui.navset_underline + - ui.navset_card_tab + - ui.navset_card_pill + - ui.navset_card_underline + - ui.navset_pill_list + - ui.navset_hidden + - title: UI panels + desc: Visually group together a section of UI components. + contents: + - ui.panel_absolute + - ui.panel_fixed + - ui.panel_conditional + - ui.panel_title + - ui.panel_well + - title: Uploads & downloads + desc: Allow users to upload and download files. + contents: + - ui.input_file + - ui.download_button + - title: Custom UI + desc: Lower-level UI functions for creating custom HTML/CSS/JS + contents: + - ui.HTML # uses justattributes.rst template + - ui.TagList # uses class.rst template + - name: ui.tags # uses tags.rst template + children: embedded + - ui.markdown + - ui.include_css + - ui.include_js + - ui.insert_ui + - ui.remove_ui + - ui.fill.as_fillable_container + - ui.fill.as_fill_item + - ui.fill.remove_all_fill + # - ui.fill.is_fillable_container + # - ui.fill.is_fill_item + - ui.css.as_css_unit + - ui.css.as_css_padding + - title: Update inputs + desc: Programmatically update input values. + contents: + - name: ui.update_select + dynamic: true + - name: ui.update_selectize + dynamic: true + - name: ui.update_slider + dynamic: true + - ui.update_date + - name: ui.update_date_range + dynamic: true + - name: ui.update_checkbox + dynamic: true + - name: ui.update_checkbox_group + dynamic: true + - name: ui.update_switch + dynamic: true + - name: ui.update_radio_buttons + dynamic: true + - name: ui.update_numeric + dynamic: true + - ui.update_text + - name: ui.update_text_area + dynamic: "shiny.ui.update_text" + - name: ui.update_navs + dynamic: true + - title: Update UI Layouts + desc: "" + contents: + - ui.update_sidebar + - ui.update_tooltip + - ui.update_popover + - ui.update_accordion + - ui.update_accordion_panel + - ui.insert_accordion_panel + - ui.remove_accordion_panel + - title: Rendering outputs + desc: "UI (output_*()) and server (render)ing functions for generating content server-side." + contents: + - ui.output_plot + - ui.output_image + - ui.output_table + - ui.output_data_frame + - ui.output_text + - ui.output_code + - ui.output_text_verbatim + - ui.output_ui + - render.plot + - render.image + - render.table + - render.text + - render.ui + - render.data_frame + - render.DataGrid + - render.DataTable + - kind: page + path: Renderer + flatten: true + summary: + name: "Create output renderers" + desc: "Package author methods for creating new output renderers." + contents: + - render.renderer.Renderer + - name: render.renderer.Jsonifiable + dynamic: false + - name: render.renderer.ValueFn + dynamic: false + - name: render.renderer.AsyncValueFn + dynamic: false + - name: render.renderer.RendererT + dynamic: false + - title: Reactive programming + desc: "" + contents: + - reactive.calc + - reactive.effect + - reactive.value + - reactive.Calc + - reactive.Effect + - reactive.Value + - reactive.event + - reactive.isolate + - reactive.invalidate_later + - reactive.flush + - reactive.poll + - reactive.file_reader + - reactive.lock + - req + - title: Create and run applications + desc: "" + contents: + - run_app + # uses class.rst template + - App + - Inputs + - Outputs + - Session + - title: Display messages + desc: "" + contents: + - ui.help_text + - ui.notification_show + - ui.notification_remove + - ui.modal + - ui.modal_show + - ui.modal_remove + - ui.modal_button + - ui.Progress # uses class.rst + - title: Modules + desc: "" + contents: + # uses class.rst template + - module.ui + - module.server + - title: Developer facing tools + desc: "" + contents: + - session.get_current_session + - session.require_active_session + - session.session_context + - reactive.get_current_context + - name: input_handler.input_handlers + dynamic: true + - title: Types + desc: "" + contents: + - kind: page + path: MiscTypes + flatten: true + summary: + name: "Miscellaneous types" + desc: "" + contents: + - types.MISSING_TYPE + - types.MISSING + - types.FileInfo + - types.ImgData + - types.NavSetArg + - ui.Sidebar + - ui.CardItem + - ui.AccordionPanel + - name: ui.css.CssUnit + dynamic: false + - ui._input_slider.SliderValueArg + - ui._input_slider.SliderStepArg + - kind: page + path: TagTypes + summary: + name: "Tag types" + desc: "" + flatten: true + package: null + contents: + - name: htmltools.Tag + dynamic: false + - name: htmltools.TagAttrs + dynamic: false + - name: htmltools.TagAttrValue + dynamic: false + - name: htmltools.TagChild + dynamic: false + - name: htmltools.TagList + dynamic: false + - kind: page + path: ExceptionTypes + summary: + name: "Exception types" + desc: "" + flatten: true + contents: + - types.SilentException + - types.SilentCancelOutputException + - types.SafeException + - title: Deprecated + desc: "" + contents: + - ui.panel_main + - ui.panel_sidebar + - ui.nav + - render.transformer.resolve_value_fn + - title: Experimental + desc: "These methods are under consideration and are considered unstable. However, if there is a method you are excited about, please let us know!" + contents: + - kind: page + path: ExCard + summary: + name: "Card" + desc: "Cards are a common organizing unit for modern user interfaces (UI). At their core, they're just rectangular containers with borders and padding. However, when utilized properly to group related information, they help users better digest, engage, and navigate through content." + flatten: true + contents: + - name: experimental.ui.card_body + dynamic: false + - name: experimental.ui.card_title + dynamic: false + - name: experimental.ui.card_image + dynamic: false + - name: experimental.ui.ImgContainer + dynamic: false + - name: experimental.ui.WrapperCallable + dynamic: false diff --git a/docs/_quartodoc.yml b/docs/_quartodoc-express.yml similarity index 96% rename from docs/_quartodoc.yml rename to docs/_quartodoc-express.yml index f33556b4e..a625c6af4 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc-express.yml @@ -1,27 +1,15 @@ quartodoc: style: pkgdown - dir: api + dir: api/express out_index: index.qmd package: shiny rewrite_all_pages: false - sidebar: api/_sidebar.yml + sidebar: api/express/_sidebar.yml dynamic: true renderer: style: _renderer.py show_signature_annotations: false sections: - - title: Page containers - desc: Create a user interface page container. - contents: - - ui.page_sidebar - - ui.page_navbar - - ui.page_sidebar - - ui.page_fillable - - ui.page_fluid - - ui.page_fixed - - ui.page_bootstrap - - ui.page_auto - - ui.page_output - title: UI Layouts desc: Control the layout of multiple UI components. contents: From 45d35a6fa2b7c0732dd25b26efeea7f52148688a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:06:09 -0500 Subject: [PATCH 02/45] ex(page_sidebar): remove `@output` decorator --- shiny/api-examples/page_sidebar/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shiny/api-examples/page_sidebar/app.py b/shiny/api-examples/page_sidebar/app.py index ba84c3fe8..8ce58f8b8 100644 --- a/shiny/api-examples/page_sidebar/app.py +++ b/shiny/api-examples/page_sidebar/app.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) From b17f0f6400b835e80e59f002e9f5da15496ff07e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:06:42 -0500 Subject: [PATCH 03/45] expressify: api-examples/page_sidebar --- .../api-examples/page_sidebar/app-express.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 shiny/api-examples/page_sidebar/app-express.py 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..54696d6fd --- /dev/null +++ b/shiny/api-examples/page_sidebar/app-express.py @@ -0,0 +1,20 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny import render +from shiny.express import input, ui + +ui.page_opts(page_fn=page_fixed) + +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 From 37f1fe9e561c893fe66eae9ca79a4560032b99d6 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:10:36 -0500 Subject: [PATCH 04/45] expressify: api-examples/sidebar --- shiny/api-examples/sidebar/app-express.py | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 shiny/api-examples/sidebar/app-express.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..16665fd13 --- /dev/null +++ b/shiny/api-examples/sidebar/app-express.py @@ -0,0 +1,43 @@ +from shiny import render +from shiny.express import input, 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()}" From 5ff52bbfd34eedba34d574996c0e71971a893fad Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:38:20 -0500 Subject: [PATCH 05/45] expressify: api-examples/layout_column_wrap --- .../layout_column_wrap/app-express.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 shiny/api-examples/layout_column_wrap/app-express.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..a5731350d --- /dev/null +++ b/shiny/api-examples/layout_column_wrap/app-express.py @@ -0,0 +1,18 @@ +import shiny +from shiny.express import ui + +a_card = shiny.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 From efd57b234b6a1301946de35dec30005e1c2c5a17 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:43:21 -0500 Subject: [PATCH 06/45] expressify: api-examples/layout_columns --- .../layout_columns/app-express.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 shiny/api-examples/layout_columns/app-express.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..1362b01eb --- /dev/null +++ b/shiny/api-examples/layout_columns/app-express.py @@ -0,0 +1,35 @@ +from model_plots import * # model plots and cards + +from shiny import render +from shiny.express import input, 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() From 6c09496f75f81b849e7df0a3eeaf29668cc6334a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:44:39 -0500 Subject: [PATCH 07/45] expressify: api-examples/layout_sidebar --- .../api-examples/layout_sidebar/app-express.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 shiny/api-examples/layout_sidebar/app-express.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..93e80442f --- /dev/null +++ b/shiny/api-examples/layout_sidebar/app-express.py @@ -0,0 +1,18 @@ +import matplotlib.pyplot as plt +import numpy as np + +from shiny import render +from shiny.express import input, 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 From d941199bf4e8f716377644aa84e6bf947afc0982 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 10:45:26 -0500 Subject: [PATCH 08/45] expressify: api-examples/markdown --- shiny/api-examples/markdown/app-express.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 shiny/api-examples/markdown/app-express.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..11cb38c87 --- /dev/null +++ b/shiny/api-examples/markdown/app-express.py @@ -0,0 +1,13 @@ +from shiny.express import input, ui + +ui.markdown( + """ + # Hello World + + This is **markdown** and here is some `code`: + + ```python + print('Hello world!') + ``` + """ +) From 930759c16480ca3c1a2bd3d69b1445f1a0fc61f7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 12:16:43 -0500 Subject: [PATCH 09/45] expressify: api-examples/panel_absolute --- shiny/api-examples/panel_absolute/app-express.py | 8 ++++++++ shiny/api-examples/panel_absolute/app.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 shiny/api-examples/panel_absolute/app-express.py 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_absolute/app.py b/shiny/api-examples/panel_absolute/app.py index 10fab125e..3340e5f93 100644 --- a/shiny/api-examples/panel_absolute/app.py +++ b/shiny/api-examples/panel_absolute/app.py @@ -9,7 +9,7 @@ draggable=True, width="300px", right="50px", - top="50%", + top="25%", ), ) From 81f0314b3186609f8403123256be6e6245008d78 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 16 Jan 2024 12:18:28 -0500 Subject: [PATCH 10/45] expressify: api-examples/panel_conditional --- shiny/api-examples/panel_conditional/app-express.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 shiny/api-examples/panel_conditional/app-express.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..8c81ea531 --- /dev/null +++ b/shiny/api-examples/panel_conditional/app-express.py @@ -0,0 +1,12 @@ +from shiny.express import input, 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"]) From 40233974805715f8050d420a8f14ead299e3028a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 24 Jan 2024 17:21:31 -0500 Subject: [PATCH 11/45] Apply suggestions from code review Co-authored-by: Carson Sievert --- shiny/api-examples/layout_column_wrap/app-express.py | 5 +++-- shiny/api-examples/layout_columns/app-express.py | 3 +-- shiny/api-examples/layout_sidebar/app-express.py | 3 +-- shiny/api-examples/markdown/app-express.py | 2 +- shiny/api-examples/page_sidebar/app-express.py | 3 +-- shiny/api-examples/sidebar/app-express.py | 3 +-- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/shiny/api-examples/layout_column_wrap/app-express.py b/shiny/api-examples/layout_column_wrap/app-express.py index a5731350d..0754d8b74 100644 --- a/shiny/api-examples/layout_column_wrap/app-express.py +++ b/shiny/api-examples/layout_column_wrap/app-express.py @@ -1,7 +1,8 @@ -import shiny from shiny.express import ui -a_card = shiny.ui.card("A simple card") +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): diff --git a/shiny/api-examples/layout_columns/app-express.py b/shiny/api-examples/layout_columns/app-express.py index 1362b01eb..593ba321d 100644 --- a/shiny/api-examples/layout_columns/app-express.py +++ b/shiny/api-examples/layout_columns/app-express.py @@ -1,7 +1,6 @@ from model_plots import * # model plots and cards -from shiny import render -from shiny.express import input, ui +from shiny.express import input, render, ui ui.page_opts(title="Model Dashboard") diff --git a/shiny/api-examples/layout_sidebar/app-express.py b/shiny/api-examples/layout_sidebar/app-express.py index 93e80442f..10482a396 100644 --- a/shiny/api-examples/layout_sidebar/app-express.py +++ b/shiny/api-examples/layout_sidebar/app-express.py @@ -1,8 +1,7 @@ import matplotlib.pyplot as plt import numpy as np -from shiny import render -from shiny.express import input, ui +from shiny.express import input, render, ui with ui.layout_sidebar(): with ui.sidebar(): diff --git a/shiny/api-examples/markdown/app-express.py b/shiny/api-examples/markdown/app-express.py index 11cb38c87..f1db29501 100644 --- a/shiny/api-examples/markdown/app-express.py +++ b/shiny/api-examples/markdown/app-express.py @@ -1,4 +1,4 @@ -from shiny.express import input, ui +from shiny.express import ui ui.markdown( """ diff --git a/shiny/api-examples/page_sidebar/app-express.py b/shiny/api-examples/page_sidebar/app-express.py index 54696d6fd..1b0beab3d 100644 --- a/shiny/api-examples/page_sidebar/app-express.py +++ b/shiny/api-examples/page_sidebar/app-express.py @@ -1,8 +1,7 @@ import matplotlib.pyplot as plt import numpy as np -from shiny import render -from shiny.express import input, ui +from shiny.express import input, render, ui ui.page_opts(page_fn=page_fixed) diff --git a/shiny/api-examples/sidebar/app-express.py b/shiny/api-examples/sidebar/app-express.py index 16665fd13..17e80bb47 100644 --- a/shiny/api-examples/sidebar/app-express.py +++ b/shiny/api-examples/sidebar/app-express.py @@ -1,5 +1,4 @@ -from shiny import render -from shiny.express import input, ui +from shiny.express import input, render, ui ui.page_opts(fillable=True) From 4f602f8e24530959b2ef52c8ebf486578621aadf Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 24 Jan 2024 17:21:47 -0500 Subject: [PATCH 12/45] Update shiny/api-examples/panel_conditional/app-express.py Co-authored-by: Carson Sievert --- shiny/api-examples/panel_conditional/app-express.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/api-examples/panel_conditional/app-express.py b/shiny/api-examples/panel_conditional/app-express.py index 8c81ea531..8f5fbb229 100644 --- a/shiny/api-examples/panel_conditional/app-express.py +++ b/shiny/api-examples/panel_conditional/app-express.py @@ -1,4 +1,4 @@ -from shiny.express import input, ui +from shiny.express import ui ui.input_checkbox("show", "Show radio buttons", False) From 9c5a87c76d03387d979108723b1f395af85b8a34 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Thu, 25 Jan 2024 08:41:56 -0400 Subject: [PATCH 13/45] Linting --- shiny/api-examples/layout_columns/app-express.py | 8 ++++++-- shiny/api-examples/page_sidebar/app-express.py | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/shiny/api-examples/layout_columns/app-express.py b/shiny/api-examples/layout_columns/app-express.py index 593ba321d..768056d5c 100644 --- a/shiny/api-examples/layout_columns/app-express.py +++ b/shiny/api-examples/layout_columns/app-express.py @@ -1,6 +1,10 @@ -from model_plots import * # model plots and cards +from model_plots import ( + plot_accuracy_over_time, + plot_feature_importance, + plot_loss_over_time, +) -from shiny.express import input, render, ui +from shiny.express import render, ui ui.page_opts(title="Model Dashboard") diff --git a/shiny/api-examples/page_sidebar/app-express.py b/shiny/api-examples/page_sidebar/app-express.py index 1b0beab3d..4781f851c 100644 --- a/shiny/api-examples/page_sidebar/app-express.py +++ b/shiny/api-examples/page_sidebar/app-express.py @@ -3,8 +3,6 @@ from shiny.express import input, render, ui -ui.page_opts(page_fn=page_fixed) - with ui.sidebar(): ui.input_slider("n", "N", min=0, max=100, value=20) From e24d44e60493a7923efad7a37ae265142dd1cd44 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Thu, 25 Jan 2024 08:59:47 -0400 Subject: [PATCH 14/45] expressify-input-examples (#1057) --- .../input_action_link/app-express.py | 20 ++++++++ .../input_checkbox/app-express.py | 8 ++++ .../input_checkbox_group/app-express.py | 18 +++++++ shiny/api-examples/input_date/app-express.py | 24 ++++++++++ .../input_date_range/app-express.py | 27 +++++++++++ shiny/api-examples/input_file/app-express.py | 48 +++++++++++++++++++ .../api-examples/input_numeric/app-express.py | 8 ++++ .../input_password/app-express.py | 11 +++++ .../input_radio_buttons/app-express.py | 15 ++++++ .../api-examples/input_select/app-express.py | 16 +++++++ .../input_selectize/app-express.py | 45 +++++++++++++++++ shiny/api-examples/input_selectize/app.py | 4 +- .../api-examples/input_slider/app-express.py | 16 +++++++ .../api-examples/input_switch/app-express.py | 8 ++++ shiny/api-examples/input_switch/app.py | 4 +- shiny/api-examples/input_text/app-express.py | 8 ++++ .../input_text_area/app-express.py | 25 ++++++++++ 17 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 shiny/api-examples/input_action_link/app-express.py create mode 100644 shiny/api-examples/input_checkbox/app-express.py create mode 100644 shiny/api-examples/input_checkbox_group/app-express.py create mode 100644 shiny/api-examples/input_date/app-express.py create mode 100644 shiny/api-examples/input_date_range/app-express.py create mode 100644 shiny/api-examples/input_file/app-express.py create mode 100644 shiny/api-examples/input_numeric/app-express.py create mode 100644 shiny/api-examples/input_password/app-express.py create mode 100644 shiny/api-examples/input_radio_buttons/app-express.py create mode 100644 shiny/api-examples/input_select/app-express.py create mode 100644 shiny/api-examples/input_selectize/app-express.py create mode 100644 shiny/api-examples/input_slider/app-express.py create mode 100644 shiny/api-examples/input_switch/app-express.py create mode 100644 shiny/api-examples/input_text/app-express.py create mode 100644 shiny/api-examples/input_text_area/app-express.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-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-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-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-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-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-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-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-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-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-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_selectize/app.py b/shiny/api-examples/input_selectize/app.py index bc2dd9bd9..23a63d376 100644 --- a/shiny/api-examples/input_selectize/app.py +++ b/shiny/api-examples/input_selectize/app.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_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-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_switch/app.py b/shiny/api-examples/input_switch/app.py index a438d8195..f05cba431 100644 --- a/shiny/api-examples/input_switch/app.py +++ b/shiny/api-examples/input_switch/app.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_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-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() From 147aaa37035c7b01ecdd040f945f0d4c5a7bfdc0 Mon Sep 17 00:00:00 2001 From: Carson Date: Thu, 25 Jan 2024 11:19:27 -0600 Subject: [PATCH 15/45] Introduce a express/core specific quartodoc renderer --- docs/_quarto.yml | 8 ++++---- docs/_quartodoc-core.yml | 2 +- docs/_quartodoc-express.yml | 2 +- docs/{_renderer.py => _renderer_core.py} | 19 +++++++++++++++++++ docs/_renderer_express.py | 8 ++++++++ 5 files changed, 33 insertions(+), 6 deletions(-) rename docs/{_renderer.py => _renderer_core.py} (90%) create mode 100644 docs/_renderer_express.py diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 43800dad0..d87d67dc5 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -18,10 +18,10 @@ website: pinned: true search: true left: - - text: "API (Express)" - file: api-express/index.qmd - - text: "API (Core)" - file: api-core/index.qmd + - text: "Express API" + file: api/express/index.qmd + - text: "Core API" + file: api/core/index.qmd right: - icon: github href: https://github.com/posit-dev/py-shiny diff --git a/docs/_quartodoc-core.yml b/docs/_quartodoc-core.yml index 69c829e47..5363d3904 100644 --- a/docs/_quartodoc-core.yml +++ b/docs/_quartodoc-core.yml @@ -7,7 +7,7 @@ quartodoc: sidebar: api/core/_sidebar.yml dynamic: true renderer: - style: _renderer.py + style: _renderer_core.py show_signature_annotations: false sections: - title: Page containers diff --git a/docs/_quartodoc-express.yml b/docs/_quartodoc-express.yml index a625c6af4..dcb588183 100644 --- a/docs/_quartodoc-express.yml +++ b/docs/_quartodoc-express.yml @@ -7,7 +7,7 @@ quartodoc: sidebar: api/express/_sidebar.yml dynamic: true renderer: - style: _renderer.py + style: _renderer_express.py show_signature_annotations: false sections: - title: UI Layouts diff --git a/docs/_renderer.py b/docs/_renderer_core.py similarity index 90% rename from docs/_renderer.py rename to docs/_renderer_core.py index 47c47f7e5..c0aff1c13 100644 --- a/docs/_renderer.py +++ b/docs/_renderer_core.py @@ -30,6 +30,7 @@ class FileContentJson(TypedDict): class Renderer(MdRenderer): style = "shiny" + express_api = False @dispatch def render(self, el: qast.DocstringSectionSeeAlso): @@ -51,6 +52,24 @@ def render(self, el: Union[dc.Object, dc.Alias]): converted = convert_rst_link_to_md(rendered) + # If we're rendering the API reference for Express, try our best to + # keep you in the Express site. For example, something like shiny.ui.input_text() + # simply gets re-exported as shiny.express.ui.input_text(), but it's docstrings + # will link to shiny.ui, not shiny.express.ui. This fixes that. + if self.express_api: + converted = converted.replace("shiny.ui.", "shiny.express.ui.") + # If this el happens to point to itself, it's probably intentionally + # pointing to Core (i.e., express context managers mention that they + # wrap Core functions), so don't change that. + # TODO: we want to be more agressive about context managers always + # pointing to the Core docs? + if f"shiny.express.ui.{el.name}" in converted: + print(f"Changing Express link to Core for: {el.name}") + converted = converted.replace( + f"shiny.express.ui.{el.name}", f"shiny.ui.{el.name}" + ) + converted = converted.replace("shiny.render.", "shiny.express.render.") + check_if_missing_expected_example(el, converted) assert_no_sphinx_comments(el, converted) diff --git a/docs/_renderer_express.py b/docs/_renderer_express.py new file mode 100644 index 000000000..afd50e01a --- /dev/null +++ b/docs/_renderer_express.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from _renderer_core import Renderer as CoreRenderer + + +class Renderer(CoreRenderer): + style = "shiny-express" + express_api = True From fbe772600309ab8b2f16e948ba88858d93f8e9b7 Mon Sep 17 00:00:00 2001 From: Carson Date: Thu, 25 Jan 2024 11:21:12 -0600 Subject: [PATCH 16/45] First pass at grooming Express function reference --- docs/_quartodoc-express.yml | 453 +++++++++++------------------------- 1 file changed, 142 insertions(+), 311 deletions(-) diff --git a/docs/_quartodoc-express.yml b/docs/_quartodoc-express.yml index dcb588183..5e4242aa9 100644 --- a/docs/_quartodoc-express.yml +++ b/docs/_quartodoc-express.yml @@ -10,337 +10,168 @@ quartodoc: style: _renderer_express.py show_signature_annotations: false sections: - - title: UI Layouts - desc: Control the layout of multiple UI components. - contents: - - ui.sidebar - - ui.layout_sidebar - - ui.layout_columns - - ui.layout_column_wrap - - ui.card - - ui.card_header - - ui.card_footer - - ui.popover - - ui.tooltip - - ui.accordion - - ui.accordion_panel - - ui.column - - ui.row - - title: UI Inputs - desc: Create UI that prompts the user for input values or interaction. - contents: - - ui.input_select - - ui.input_selectize - - ui.input_slider - - ui.input_date - - ui.input_date_range - - ui.input_checkbox - - ui.input_checkbox_group - - ui.input_switch - - ui.input_radio_buttons - - ui.input_numeric - - ui.input_text - - ui.input_text_area - - ui.input_password - - ui.input_action_button - - ui.input_action_link - - title: Value boxes - desc: Prominently display a value and label in a box that can be expanded to show more information. - contents: - - ui.value_box - - ui.value_box_theme - - ui.showcase_bottom - - ui.showcase_left_center - - ui.showcase_top_right - - title: Navigation (tab) panels - desc: Create segments of UI content. - contents: - - ui.nav_panel - - ui.nav_spacer - - ui.nav_menu - - ui.nav_control - - ui.navset_bar - - ui.navset_tab - - ui.navset_pill - - ui.navset_underline - - ui.navset_card_tab - - ui.navset_card_pill - - ui.navset_card_underline - - ui.navset_pill_list - - ui.navset_hidden - - title: UI panels - desc: Visually group together a section of UI components. - contents: - - ui.panel_absolute - - ui.panel_fixed - - ui.panel_conditional - - ui.panel_title - - ui.panel_well - - title: Uploads & downloads - desc: Allow users to upload and download files. + - title: Input components + desc: Gather user input. + contents: + - express.ui.input_select + - express.ui.input_selectize + - express.ui.input_slider + - express.ui.input_date + - express.ui.input_date_range + - express.ui.input_checkbox + - express.ui.input_checkbox_group + - express.ui.input_switch + - express.ui.input_radio_buttons + - express.ui.input_numeric + - express.ui.input_text + - express.ui.input_text_area + - express.ui.input_password + - express.ui.input_action_button + - express.ui.input_action_link + - title: Output components + desc: Reactively render output. + contents: + - express.render.plot + - express.render.table + - express.render.DataTable + - express.render.data_frame + - express.render.DataGrid + - express.render.text + - express.render.ui + - express.render.download + - express.render.image + - express.render.express + - title: Layouts and other UI tools + desc: Tools for creating, arranging, and styling UI components. + contents: + - express.ui.page_opts + - express.ui.sidebar + - express.ui.layout_columns + - express.ui.layout_column_wrap + - express.ui.card + - express.ui.card_header + - express.ui.card_footer + - express.ui.value_box + - express.ui.value_box_theme + - express.ui.popover + - express.ui.tooltip + - express.ui.accordion + - express.ui.accordion_panel + - express.ui.layout_sidebar + - title: Navigate multiple panels + desc: Create a set of panels that can be navigated between. + contents: + - express.ui.nav_panel + - express.ui.navset_card_underline + - express.ui.navset_card_tab + - express.ui.navset_card_pill + - express.ui.nav_spacer + - express.ui.nav_menu + - express.ui.nav_control + - express.ui.navset_bar + - express.ui.navset_tab + - express.ui.navset_pill + - express.ui.navset_underline + - express.ui.navset_pill_list + - express.ui.navset_hidden + - title: Reactive programming + desc: Create reactive functions and dependencies. contents: - - ui.input_file - - ui.download_button - - title: Custom UI - desc: Lower-level UI functions for creating custom HTML/CSS/JS + - reactive.calc + - reactive.effect + - reactive.value + - reactive.event + - reactive.isolate + - reactive.invalidate_later + - reactive.flush + - reactive.poll + - reactive.file_reader + - reactive.lock + - req + - title: Reusable Express code + desc: Create reusable Express code. contents: - - ui.HTML # uses justattributes.rst template - - ui.TagList # uses class.rst template - - name: ui.tags # uses tags.rst template - children: embedded - - ui.markdown - - ui.include_css - - ui.include_js - - ui.insert_ui - - ui.remove_ui - - ui.fill.as_fillable_container - - ui.fill.as_fill_item - - ui.fill.remove_all_fill - # - ui.fill.is_fillable_container - # - ui.fill.is_fill_item - - ui.css.as_css_unit - - ui.css.as_css_padding + - express.ui.hold + - express.expressify - title: Update inputs desc: Programmatically update input values. contents: - - name: ui.update_select + - name: express.ui.update_select dynamic: true - - name: ui.update_selectize + - name: express.ui.update_selectize dynamic: true - - name: ui.update_slider + - name: express.ui.update_slider dynamic: true - ui.update_date - - name: ui.update_date_range + - name: express.ui.update_date_range dynamic: true - - name: ui.update_checkbox + - name: express.ui.update_checkbox dynamic: true - - name: ui.update_checkbox_group + - name: express.ui.update_checkbox_group dynamic: true - - name: ui.update_switch + - name: express.ui.update_switch dynamic: true - - name: ui.update_radio_buttons + - name: express.ui.update_radio_buttons dynamic: true - - name: ui.update_numeric + - name: express.ui.update_numeric dynamic: true - ui.update_text - - name: ui.update_text_area + - name: express.ui.update_text_area dynamic: "shiny.ui.update_text" - - name: ui.update_navs + - name: express.ui.update_navs dynamic: true - title: Update UI Layouts desc: "" contents: - - ui.update_sidebar - - ui.update_tooltip - - ui.update_popover - - ui.update_accordion - - ui.update_accordion_panel - - ui.insert_accordion_panel - - ui.remove_accordion_panel - - title: Rendering outputs - desc: "UI (output_*()) and server (render)ing functions for generating content server-side." - contents: - - ui.output_plot - - ui.output_image - - ui.output_table - - ui.output_data_frame - - ui.output_text - - ui.output_code - - ui.output_text_verbatim - - ui.output_ui - - render.plot - - render.image - - render.table - - render.text - - render.ui - - render.data_frame - - render.DataGrid - - render.DataTable - - kind: page - path: Renderer - flatten: true - summary: - name: "Create output renderers" - desc: "Package author methods for creating new output renderers." - contents: - - render.renderer.Renderer - - name: render.renderer.Jsonifiable - dynamic: false - - name: render.renderer.ValueFn - dynamic: false - - name: render.renderer.AsyncValueFn - dynamic: false - - name: render.renderer.RendererT - dynamic: false - - title: Reactive programming - desc: "" - contents: - - reactive.calc - - reactive.effect - - reactive.value - - reactive.Calc - - reactive.Effect - - reactive.Value - - reactive.event - - reactive.isolate - - reactive.invalidate_later - - reactive.flush - - reactive.poll - - reactive.file_reader - - reactive.lock - - req - - title: Create and run applications - desc: "" - contents: - - run_app - # uses class.rst template - - App - - Inputs - - Outputs - - Session + - express.ui.update_sidebar + - express.ui.update_tooltip + - express.ui.update_popover + - express.ui.update_accordion + - express.ui.update_accordion_panel + - express.ui.insert_accordion_panel + - express.ui.remove_accordion_panel - title: Display messages - desc: "" - contents: - - ui.help_text - - ui.notification_show - - ui.notification_remove - - ui.modal - - ui.modal_show - - ui.modal_remove - - ui.modal_button - - ui.Progress # uses class.rst - - title: Modules - desc: "" - contents: - # uses class.rst template - - module.ui - - module.server - - title: Developer facing tools - desc: "" - contents: - - session.get_current_session - - session.require_active_session - - session.session_context - - reactive.get_current_context - - name: input_handler.input_handlers - dynamic: true - - title: Types - desc: "" - contents: - - kind: page - path: MiscTypes - flatten: true - summary: - name: "Miscellaneous types" - desc: "" - contents: - - types.MISSING_TYPE - - types.MISSING - - types.FileInfo - - types.ImgData - - types.NavSetArg - - ui.Sidebar - - ui.CardItem - - ui.AccordionPanel - - name: ui.css.CssUnit - dynamic: false - - ui._input_slider.SliderValueArg - - ui._input_slider.SliderStepArg - - kind: page - path: TagTypes - summary: - name: "Tag types" - desc: "" - flatten: true - package: null - contents: - - name: htmltools.Tag - dynamic: false - - name: htmltools.TagAttrs - dynamic: false - - name: htmltools.TagAttrValue - dynamic: false - - name: htmltools.TagChild - dynamic: false - - name: htmltools.TagList - dynamic: false - - kind: page - path: ExceptionTypes - summary: - name: "Exception types" - desc: "" - flatten: true - contents: - - types.SilentException - - types.SilentCancelOutputException - - types.SafeException - - title: Shiny Express - desc: Functions for Shiny Express applications - contents: - - kind: page - path: ContextManagerComponents - summary: - name: "Context manager components" - desc: "" - flatten: true - contents: - - express.ui.sidebar - - express.ui.layout_sidebar - - express.ui.layout_column_wrap - - express.ui.layout_columns - - express.ui.card - - express.ui.accordion - - express.ui.accordion_panel - - express.ui.nav_panel - - express.ui.nav_control - - express.ui.nav_menu - - express.ui.navset_bar - - express.ui.navset_card_pill - - express.ui.navset_card_tab - - express.ui.navset_card_underline - - express.ui.navset_hidden - - express.ui.navset_pill - - express.ui.navset_pill_list - - express.ui.navset_tab - - express.ui.navset_underline - - express.ui.value_box - - express.ui.panel_title - - express.ui.panel_well - - express.ui.panel_conditional - - express.ui.panel_fixed - - express.ui.panel_absolute - - kind: page - path: PageFunctions - summary: - name: "Page functions" - desc: "" - flatten: true - contents: - - express.ui.page_opts - - title: Deprecated - desc: "" - contents: - - ui.panel_main - - ui.panel_sidebar - - ui.nav - - render.transformer.resolve_value_fn - - title: Experimental - desc: "These methods are under consideration and are considered unstable. However, if there is a method you are excited about, please let us know!" + desc: Display messages to the user. + contents: + - express.ui.help_text + - express.ui.notification_show + - express.ui.notification_remove + - express.ui.modal + - express.ui.modal_show + - express.ui.modal_remove + - express.ui.modal_button + - express.ui.Progress # uses class.rst + - title: UI panels + desc: Visually group together a section of UI components. contents: - - kind: page - path: ExCard - summary: - name: "Card" - desc: "Cards are a common organizing unit for modern user interfaces (UI). At their core, they're just rectangular containers with borders and padding. However, when utilized properly to group related information, they help users better digest, engage, and navigate through content." - flatten: true - contents: - - name: experimental.ui.card_body - dynamic: false - - name: experimental.ui.card_title - dynamic: false - - name: experimental.ui.card_image - dynamic: false - - name: experimental.ui.ImgContainer - dynamic: false - - name: experimental.ui.WrapperCallable - dynamic: false + - express.ui.panel_absolute + - express.ui.panel_fixed + - express.ui.panel_title + - express.ui.panel_well + - title: Dynamic UI + desc: Dynamically show/hide UI elements. + contents: + - express.ui.panel_conditional + - express.ui.insert_ui + - express.ui.remove_ui + - title: UI as HTML + desc: Tools for creating HTML/CSS/JS + contents: + - express.ui.markdown + - express.ui.include_css + - express.ui.include_js + - express.ui.HTML # uses justattributes.rst template + - name: express.ui.tags # uses tags.rst template + children: embedded + - express.ui.TagList # uses class.rst template + # TODO: should these be included? + # - express.ui.fill.as_fillable_container + # - express.ui.fill.as_fill_item + # - express.ui.fill.remove_all_fill + # - express.ui.css.as_css_unit + # - express.ui.css.as_css_padding + - title: Express developer tooling + desc: + contents: + - express.is_express_app + - express.wrap_express_app From 3dd82579456e5e7891d9924bcbffc64463825d96 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 25 Jan 2024 11:34:15 -0500 Subject: [PATCH 17/45] Update `@add_examples()` to work with split core/express docs --- docs/Makefile | 4 +-- shiny/_docstring.py | 77 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 77694d6f9..f1f9e0360 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -51,8 +51,8 @@ quartodoc: $(PYBIN) ## Build qmd files for API docs $(eval export IN_QUARTODOC=true) . $(PYBIN)/activate \ && quartodoc interlinks \ - && quartodoc build --config _quartodoc-core.yml --verbose \ - && quartodoc build --config _quartodoc-express.yml --verbose + && SHINY_MODE="core" quartodoc build --config _quartodoc-core.yml --verbose \ + && SHINY_MODE="express" quartodoc build --config _quartodoc-express.yml --verbose site: ## Build website . $(PYBIN)/activate \ diff --git a/shiny/_docstring.py b/shiny/_docstring.py index a45a4edce..cac1688a4 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -50,7 +50,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 +60,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 +96,29 @@ 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" + example_file = app_choose_core_or_express( + os.path.join(example_dir, app_file_name) + ) 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 +162,53 @@ 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 + + +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 FileNotFoundError( + f"Could not find an Express app file named either '{os.path.basename(app_path)}' " + + f"or '{os.path.basename(app_path_express)}' in {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 FileNotFoundError( + f"Could not find an example app file named '{os.path.basename(app_path)}' " + + f"in {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__)) From c7b2c85c6d677ef44ebe606e1e8f835eb17b3c08 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Thu, 25 Jan 2024 17:13:07 -0500 Subject: [PATCH 18/45] Update doc's `make quartodoc` to make a combined objects.json file --- docs/.gitignore | 2 ++ docs/Makefile | 14 ++++++-- docs/_combine_objects_json.py | 64 +++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 docs/_combine_objects_json.py diff --git a/docs/.gitignore b/docs/.gitignore index a3135790a..b65bacee6 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -5,3 +5,5 @@ _sidebar.yml /.quarto/ objects.json site_libs/ +_objects_core.json +_objects_express.json diff --git a/docs/Makefile b/docs/Makefile index 77694d6f9..c81892065 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,13 +46,23 @@ deps: $(PYBIN) ## Install build dependencies $(PYBIN)/pip install pip --upgrade $(PYBIN)/pip install -e ..[doc] -quartodoc: $(PYBIN) ## Build qmd files for API docs + +# `quartodoc interlinks` creates links in `_inv` +quartodoc_impl: $(PYBIN) ## Build qmd files for API docs $(eval export SHINY_ADD_EXAMPLES=true) $(eval export IN_QUARTODOC=true) . $(PYBIN)/activate \ && quartodoc interlinks \ && quartodoc build --config _quartodoc-core.yml --verbose \ - && quartodoc build --config _quartodoc-express.yml --verbose + && mv objects.json _objects_core.json \ + && quartodoc build --config _quartodoc-express.yml --verbose \ + && mv objects.json _objects_express.json + +quartodoc_post: $(PYBIN) ## Post-process qmd files for API docs + . $(PYBIN)/activate \ + && python _combine_objects_json.py + +quartodoc: quartodoc_impl quartodoc_post site: ## Build website . $(PYBIN)/activate \ diff --git a/docs/_combine_objects_json.py b/docs/_combine_objects_json.py new file mode 100644 index 000000000..9446bf339 --- /dev/null +++ b/docs/_combine_objects_json.py @@ -0,0 +1,64 @@ +import json +from dataclasses import asdict, dataclass +from typing import Literal, TypedDict + + +@dataclass +class QuartodocObject: + project: str + version: str + count: int + items: list["QuartodocObjectItem"] + + +@dataclass +class QuartodocObjectItem: + name: str + domain: str + + # function: "shiny.ui.page_sidebar" + # class: "shiny.render.renderer._renderer.Renderer" + # attribute: "shiny.render.renderer._renderer.Renderer.output_id" + role: Literal["function", "class", "attribute", "module"] + priority: str + uri: str + dispname: str + + +def read_objects_file(path: str) -> QuartodocObject: + with open(path) as file: + content = json.load(file) + items = [QuartodocObjectItem(**item) for item in content.pop("items")] + return QuartodocObject(**content, items=items) + + +def write_objects_file(objects: QuartodocObject, path: str) -> None: + with open(path, "w") as file: + json.dump(objects, file, indent=4, default=lambda dc: dc.__dict__) + + +print("\nCombinging objects json files...") +objects_core = read_objects_file("_objects_core.json") +objects_express = read_objects_file("_objects_express.json") + +items_map: dict[str, QuartodocObjectItem] = {} + +for item in [*objects_core.items, *objects_express.items]: + if item.name in items_map: + continue + items_map[item.name] = item + +objects_ret = QuartodocObject( + project="shiny", + version="1", + count=len(items_map.values()), + items=[*items_map.values()], +) + + +print("Core:", objects_core.count) +print("Express:", objects_express.count) +print("Combined:", objects_ret.count) + +# Save combined objects file info +write_objects_file(objects_ret, "objects.json") From c2c3f4306aca7ea6195f4fa1fe125267f5c304c7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 25 Jan 2024 17:30:00 -0500 Subject: [PATCH 19/45] Add `@no_example_express()` re-decorator --- shiny/_docstring.py | 67 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/shiny/_docstring.py b/shiny/_docstring.py index cac1688a4..fec1df04f 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 @@ -177,6 +197,39 @@ def is_express_app(app_path: str) -> bool: 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" @@ -190,9 +243,9 @@ def app_choose_core_or_express(app_path: Optional[str] = None) -> str: app_path_express = f"{path}-express{ext}" if not is_express_app(app_path_express): - raise FileNotFoundError( - f"Could not find an Express app file named either '{os.path.basename(app_path)}' " - + f"or '{os.path.basename(app_path_express)}' in {os.path.dirname(app_path)}." + raise ExpressExampleNotFoundException( + [os.path.basename(app_path), os.path.basename(app_path_express)], + os.path.dirname(app_path), ) return app_path_express @@ -201,9 +254,9 @@ def app_choose_core_or_express(app_path: Optional[str] = None) -> str: app_path = app_path.replace("app.py", "app-core.py") if not os.path.exists(app_path): - raise FileNotFoundError( - f"Could not find an example app file named '{os.path.basename(app_path)}' " - + f"in {os.path.dirname(app_path)}." + raise ExampleNotFoundException( + os.path.basename(app_path), + os.path.dirname(app_path), ) return app_path From 14b0836dda092a6c3b67ea4c15fc08f718200f2c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 25 Jan 2024 17:30:15 -0500 Subject: [PATCH 20/45] Use `@no_example_express()` in a few places --- shiny/reactive/_core.py | 4 ++-- shiny/types.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) 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 From f9c9ddd403be4ebed2ccbb8374664dc8f9042468 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Fri, 26 Jan 2024 11:41:51 -0400 Subject: [PATCH 21/45] Rename app.py examples to app-core.py (#1076) --- shiny/api-examples/Module/{app.py => app-core.py} | 0 shiny/api-examples/Progress/{app.py => app-core.py} | 0 shiny/api-examples/Renderer/{app.py => app-core.py} | 0 shiny/api-examples/SafeException/{app.py => app-core.py} | 0 .../SilentCancelOutputException/{app.py => app-core.py} | 0 shiny/api-examples/SilentException/{app.py => app-core.py} | 0 shiny/api-examples/Value/{app.py => app-core.py} | 0 shiny/api-examples/accordion/{app.py => app-core.py} | 0 shiny/api-examples/accordion_panel/{app.py => app-core.py} | 0 shiny/api-examples/as_fill_item/{app.py => app-core.py} | 0 shiny/api-examples/as_fillable_container/{app.py => app-core.py} | 0 shiny/api-examples/calc/{app.py => app-core.py} | 0 shiny/api-examples/card/{app.py => app-core.py} | 0 shiny/api-examples/card_body/{app.py => app-core.py} | 0 shiny/api-examples/card_footer/{app.py => app-core.py} | 0 shiny/api-examples/card_header/{app.py => app-core.py} | 0 shiny/api-examples/close/{app.py => app-core.py} | 0 shiny/api-examples/data_frame/{app.py => app-core.py} | 0 shiny/api-examples/download/{app.py => app-core.py} | 0 shiny/api-examples/download_button/{app.py => app-core.py} | 0 shiny/api-examples/download_link/{app.py => app-core.py} | 0 shiny/api-examples/dynamic_route/{app.py => app-core.py} | 0 shiny/api-examples/effect/{app.py => app-core.py} | 0 shiny/api-examples/event/{app.py => app-core.py} | 0 shiny/api-examples/extended_task/{app.py => app-core.py} | 0 shiny/api-examples/file_reader/{app.py => app-core.py} | 0 shiny/api-examples/include_css/{app.py => app-core.py} | 0 shiny/api-examples/include_js/{app.py => app-core.py} | 0 shiny/api-examples/input_action_button/{app.py => app-core.py} | 0 shiny/api-examples/input_action_link/{app.py => app-core.py} | 0 shiny/api-examples/input_checkbox/{app.py => app-core.py} | 0 shiny/api-examples/input_checkbox_group/{app.py => app-core.py} | 0 shiny/api-examples/input_date/{app.py => app-core.py} | 0 shiny/api-examples/input_date_range/{app.py => app-core.py} | 0 shiny/api-examples/input_file/{app.py => app-core.py} | 0 shiny/api-examples/input_numeric/{app.py => app-core.py} | 0 shiny/api-examples/input_password/{app.py => app-core.py} | 0 shiny/api-examples/input_radio_buttons/{app.py => app-core.py} | 0 shiny/api-examples/input_select/{app.py => app-core.py} | 0 shiny/api-examples/input_selectize/{app.py => app-core.py} | 0 shiny/api-examples/input_slider/{app.py => app-core.py} | 0 shiny/api-examples/input_switch/{app.py => app-core.py} | 0 shiny/api-examples/input_text/{app.py => app-core.py} | 0 shiny/api-examples/input_text_area/{app.py => app-core.py} | 0 shiny/api-examples/insert_accordion_panel/{app.py => app-core.py} | 0 shiny/api-examples/insert_ui/{app.py => app-core.py} | 0 shiny/api-examples/invalidate_later/{app.py => app-core.py} | 0 shiny/api-examples/isolate/{app.py => app-core.py} | 0 shiny/api-examples/layout_column_wrap/{app.py => app-core.py} | 0 shiny/api-examples/layout_columns/{app.py => app-core.py} | 0 shiny/api-examples/layout_sidebar/{app.py => app-core.py} | 0 shiny/api-examples/markdown/{app.py => app-core.py} | 0 shiny/api-examples/modal/{app.py => app-core.py} | 0 shiny/api-examples/nav_panel/{app.py => app-core.py} | 0 shiny/api-examples/navset_hidden/{app.py => app-core.py} | 0 shiny/api-examples/notification_show/{app.py => app-core.py} | 0 shiny/api-examples/on_ended/{app.py => app-core.py} | 0 shiny/api-examples/on_flush/{app.py => app-core.py} | 0 shiny/api-examples/on_flushed/{app.py => app-core.py} | 0 shiny/api-examples/output_image/{app.py => app-core.py} | 0 shiny/api-examples/output_plot/{app.py => app-core.py} | 0 shiny/api-examples/output_table/{app.py => app-core.py} | 0 shiny/api-examples/output_text/{app.py => app-core.py} | 0 shiny/api-examples/output_transformer/{app.py => app-core.py} | 0 shiny/api-examples/output_ui/{app.py => app-core.py} | 0 shiny/api-examples/page_fixed/{app.py => app-core.py} | 0 shiny/api-examples/page_fluid/{app.py => app-core.py} | 0 shiny/api-examples/page_sidebar/{app.py => app-core.py} | 0 shiny/api-examples/panel_absolute/{app.py => app-core.py} | 0 shiny/api-examples/panel_conditional/{app.py => app-core.py} | 0 shiny/api-examples/panel_title/{app.py => app-core.py} | 0 shiny/api-examples/poll/{app.py => app-core.py} | 0 shiny/api-examples/popover/{app.py => app-core.py} | 0 shiny/api-examples/remove_accordion_panel/{app.py => app-core.py} | 0 shiny/api-examples/remove_ui/{app.py => app-core.py} | 0 shiny/api-examples/render_express/{app.py => app-core.py} | 0 shiny/api-examples/render_image/{app.py => app-core.py} | 0 shiny/api-examples/req/{app.py => app-core.py} | 0 shiny/api-examples/row/{app.py => app-core.py} | 0 shiny/api-examples/send_custom_message/{app.py => app-core.py} | 0 shiny/api-examples/showcase_bottom/{app.py => app-core.py} | 0 shiny/api-examples/showcase_left_center/{app.py => app-core.py} | 0 shiny/api-examples/showcase_top_right/{app.py => app-core.py} | 0 shiny/api-examples/sidebar/{app.py => app-core.py} | 0 shiny/api-examples/template/{app.py => app-core.py} | 0 shiny/api-examples/todo_list/{app.py => app-core.py} | 0 shiny/api-examples/tooltip/{app.py => app-core.py} | 0 shiny/api-examples/update_accordion/{app.py => app-core.py} | 0 shiny/api-examples/update_accordion_panel/{app.py => app-core.py} | 0 shiny/api-examples/update_action_button/{app.py => app-core.py} | 0 shiny/api-examples/update_checkbox/{app.py => app-core.py} | 0 shiny/api-examples/update_checkbox_group/{app.py => app-core.py} | 0 shiny/api-examples/update_date/{app.py => app-core.py} | 0 shiny/api-examples/update_date_range/{app.py => app-core.py} | 0 shiny/api-examples/update_navs/{app.py => app-core.py} | 0 shiny/api-examples/update_numeric/{app.py => app-core.py} | 0 shiny/api-examples/update_popover/{app.py => app-core.py} | 0 shiny/api-examples/update_radio_buttons/{app.py => app-core.py} | 0 shiny/api-examples/update_select/{app.py => app-core.py} | 0 shiny/api-examples/update_selectize/{app.py => app-core.py} | 0 shiny/api-examples/update_sidebar/{app.py => app-core.py} | 0 shiny/api-examples/update_slider/{app.py => app-core.py} | 0 shiny/api-examples/update_text/{app.py => app-core.py} | 0 shiny/api-examples/update_tooltip/{app.py => app-core.py} | 0 shiny/api-examples/value_box/{app.py => app-core.py} | 0 shiny/api-examples/www_dir/{app.py => app-core.py} | 0 106 files changed, 0 insertions(+), 0 deletions(-) rename shiny/api-examples/Module/{app.py => app-core.py} (100%) rename shiny/api-examples/Progress/{app.py => app-core.py} (100%) rename shiny/api-examples/Renderer/{app.py => app-core.py} (100%) rename shiny/api-examples/SafeException/{app.py => app-core.py} (100%) rename shiny/api-examples/SilentCancelOutputException/{app.py => app-core.py} (100%) rename shiny/api-examples/SilentException/{app.py => app-core.py} (100%) rename shiny/api-examples/Value/{app.py => app-core.py} (100%) rename shiny/api-examples/accordion/{app.py => app-core.py} (100%) rename shiny/api-examples/accordion_panel/{app.py => app-core.py} (100%) rename shiny/api-examples/as_fill_item/{app.py => app-core.py} (100%) rename shiny/api-examples/as_fillable_container/{app.py => app-core.py} (100%) rename shiny/api-examples/calc/{app.py => app-core.py} (100%) rename shiny/api-examples/card/{app.py => app-core.py} (100%) rename shiny/api-examples/card_body/{app.py => app-core.py} (100%) rename shiny/api-examples/card_footer/{app.py => app-core.py} (100%) rename shiny/api-examples/card_header/{app.py => app-core.py} (100%) rename shiny/api-examples/close/{app.py => app-core.py} (100%) rename shiny/api-examples/data_frame/{app.py => app-core.py} (100%) rename shiny/api-examples/download/{app.py => app-core.py} (100%) rename shiny/api-examples/download_button/{app.py => app-core.py} (100%) rename shiny/api-examples/download_link/{app.py => app-core.py} (100%) rename shiny/api-examples/dynamic_route/{app.py => app-core.py} (100%) rename shiny/api-examples/effect/{app.py => app-core.py} (100%) rename shiny/api-examples/event/{app.py => app-core.py} (100%) rename shiny/api-examples/extended_task/{app.py => app-core.py} (100%) rename shiny/api-examples/file_reader/{app.py => app-core.py} (100%) rename shiny/api-examples/include_css/{app.py => app-core.py} (100%) rename shiny/api-examples/include_js/{app.py => app-core.py} (100%) rename shiny/api-examples/input_action_button/{app.py => app-core.py} (100%) rename shiny/api-examples/input_action_link/{app.py => app-core.py} (100%) rename shiny/api-examples/input_checkbox/{app.py => app-core.py} (100%) rename shiny/api-examples/input_checkbox_group/{app.py => app-core.py} (100%) rename shiny/api-examples/input_date/{app.py => app-core.py} (100%) rename shiny/api-examples/input_date_range/{app.py => app-core.py} (100%) rename shiny/api-examples/input_file/{app.py => app-core.py} (100%) rename shiny/api-examples/input_numeric/{app.py => app-core.py} (100%) rename shiny/api-examples/input_password/{app.py => app-core.py} (100%) rename shiny/api-examples/input_radio_buttons/{app.py => app-core.py} (100%) rename shiny/api-examples/input_select/{app.py => app-core.py} (100%) rename shiny/api-examples/input_selectize/{app.py => app-core.py} (100%) rename shiny/api-examples/input_slider/{app.py => app-core.py} (100%) rename shiny/api-examples/input_switch/{app.py => app-core.py} (100%) rename shiny/api-examples/input_text/{app.py => app-core.py} (100%) rename shiny/api-examples/input_text_area/{app.py => app-core.py} (100%) rename shiny/api-examples/insert_accordion_panel/{app.py => app-core.py} (100%) rename shiny/api-examples/insert_ui/{app.py => app-core.py} (100%) rename shiny/api-examples/invalidate_later/{app.py => app-core.py} (100%) rename shiny/api-examples/isolate/{app.py => app-core.py} (100%) rename shiny/api-examples/layout_column_wrap/{app.py => app-core.py} (100%) rename shiny/api-examples/layout_columns/{app.py => app-core.py} (100%) rename shiny/api-examples/layout_sidebar/{app.py => app-core.py} (100%) rename shiny/api-examples/markdown/{app.py => app-core.py} (100%) rename shiny/api-examples/modal/{app.py => app-core.py} (100%) rename shiny/api-examples/nav_panel/{app.py => app-core.py} (100%) rename shiny/api-examples/navset_hidden/{app.py => app-core.py} (100%) rename shiny/api-examples/notification_show/{app.py => app-core.py} (100%) rename shiny/api-examples/on_ended/{app.py => app-core.py} (100%) rename shiny/api-examples/on_flush/{app.py => app-core.py} (100%) rename shiny/api-examples/on_flushed/{app.py => app-core.py} (100%) rename shiny/api-examples/output_image/{app.py => app-core.py} (100%) rename shiny/api-examples/output_plot/{app.py => app-core.py} (100%) rename shiny/api-examples/output_table/{app.py => app-core.py} (100%) rename shiny/api-examples/output_text/{app.py => app-core.py} (100%) rename shiny/api-examples/output_transformer/{app.py => app-core.py} (100%) rename shiny/api-examples/output_ui/{app.py => app-core.py} (100%) rename shiny/api-examples/page_fixed/{app.py => app-core.py} (100%) rename shiny/api-examples/page_fluid/{app.py => app-core.py} (100%) rename shiny/api-examples/page_sidebar/{app.py => app-core.py} (100%) rename shiny/api-examples/panel_absolute/{app.py => app-core.py} (100%) rename shiny/api-examples/panel_conditional/{app.py => app-core.py} (100%) rename shiny/api-examples/panel_title/{app.py => app-core.py} (100%) rename shiny/api-examples/poll/{app.py => app-core.py} (100%) rename shiny/api-examples/popover/{app.py => app-core.py} (100%) rename shiny/api-examples/remove_accordion_panel/{app.py => app-core.py} (100%) rename shiny/api-examples/remove_ui/{app.py => app-core.py} (100%) rename shiny/api-examples/render_express/{app.py => app-core.py} (100%) rename shiny/api-examples/render_image/{app.py => app-core.py} (100%) rename shiny/api-examples/req/{app.py => app-core.py} (100%) rename shiny/api-examples/row/{app.py => app-core.py} (100%) rename shiny/api-examples/send_custom_message/{app.py => app-core.py} (100%) rename shiny/api-examples/showcase_bottom/{app.py => app-core.py} (100%) rename shiny/api-examples/showcase_left_center/{app.py => app-core.py} (100%) rename shiny/api-examples/showcase_top_right/{app.py => app-core.py} (100%) rename shiny/api-examples/sidebar/{app.py => app-core.py} (100%) rename shiny/api-examples/template/{app.py => app-core.py} (100%) rename shiny/api-examples/todo_list/{app.py => app-core.py} (100%) rename shiny/api-examples/tooltip/{app.py => app-core.py} (100%) rename shiny/api-examples/update_accordion/{app.py => app-core.py} (100%) rename shiny/api-examples/update_accordion_panel/{app.py => app-core.py} (100%) rename shiny/api-examples/update_action_button/{app.py => app-core.py} (100%) rename shiny/api-examples/update_checkbox/{app.py => app-core.py} (100%) rename shiny/api-examples/update_checkbox_group/{app.py => app-core.py} (100%) rename shiny/api-examples/update_date/{app.py => app-core.py} (100%) rename shiny/api-examples/update_date_range/{app.py => app-core.py} (100%) rename shiny/api-examples/update_navs/{app.py => app-core.py} (100%) rename shiny/api-examples/update_numeric/{app.py => app-core.py} (100%) rename shiny/api-examples/update_popover/{app.py => app-core.py} (100%) rename shiny/api-examples/update_radio_buttons/{app.py => app-core.py} (100%) rename shiny/api-examples/update_select/{app.py => app-core.py} (100%) rename shiny/api-examples/update_selectize/{app.py => app-core.py} (100%) rename shiny/api-examples/update_sidebar/{app.py => app-core.py} (100%) rename shiny/api-examples/update_slider/{app.py => app-core.py} (100%) rename shiny/api-examples/update_text/{app.py => app-core.py} (100%) rename shiny/api-examples/update_tooltip/{app.py => app-core.py} (100%) rename shiny/api-examples/value_box/{app.py => app-core.py} (100%) rename shiny/api-examples/www_dir/{app.py => app-core.py} (100%) 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/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 100% rename from shiny/api-examples/Value/app.py rename to shiny/api-examples/Value/app-core.py 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 100% rename from shiny/api-examples/calc/app.py rename to shiny/api-examples/calc/app-core.py 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/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_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_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_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_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_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_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_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_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_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_selectize/app.py b/shiny/api-examples/input_selectize/app-core.py similarity index 100% rename from shiny/api-examples/input_selectize/app.py rename to shiny/api-examples/input_selectize/app-core.py 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_switch/app.py b/shiny/api-examples/input_switch/app-core.py similarity index 100% rename from shiny/api-examples/input_switch/app.py rename to shiny/api-examples/input_switch/app-core.py 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_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/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 100% rename from shiny/api-examples/invalidate_later/app.py rename to shiny/api-examples/invalidate_later/app-core.py 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/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_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_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/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/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 100% rename from shiny/api-examples/page_sidebar/app.py rename to shiny/api-examples/page_sidebar/app-core.py diff --git a/shiny/api-examples/panel_absolute/app.py b/shiny/api-examples/panel_absolute/app-core.py similarity index 100% rename from shiny/api-examples/panel_absolute/app.py rename to shiny/api-examples/panel_absolute/app-core.py 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_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 100% rename from shiny/api-examples/poll/app.py rename to shiny/api-examples/poll/app-core.py 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/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 From 4abf8f7c6feffbfec4ea6565a8e313856c2c166f Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Fri, 26 Jan 2024 13:19:54 -0400 Subject: [PATCH 22/45] Expressify reactive examples (#1078) Co-authored-by: Garrick Aden-Buie --- shiny/api-examples/Progress/app-express.py | 21 ++++++++++++ shiny/api-examples/Value/app-core.py | 14 ++++---- shiny/api-examples/Value/app-express.py | 28 ++++++++++++++++ shiny/api-examples/calc/app-core.py | 18 ++++++----- shiny/api-examples/calc/app-express.py | 32 +++++++++++++++++++ shiny/api-examples/effect/app-express.py | 15 +++++++++ .../api-examples/invalidate_later/app-core.py | 9 ++---- .../invalidate_later/app-express.py | 10 ++++++ shiny/api-examples/isolate/app-express.py | 23 +++++++++++++ shiny/api-examples/poll/app-core.py | 24 +++++++------- 10 files changed, 159 insertions(+), 35 deletions(-) create mode 100644 shiny/api-examples/Progress/app-express.py create mode 100644 shiny/api-examples/Value/app-express.py create mode 100644 shiny/api-examples/calc/app-express.py create mode 100644 shiny/api-examples/effect/app-express.py create mode 100644 shiny/api-examples/invalidate_later/app-express.py create mode 100644 shiny/api-examples/isolate/app-express.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/Value/app-core.py b/shiny/api-examples/Value/app-core.py index fe0112954..04479492a 100644 --- a/shiny/api-examples/Value/app-core.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/calc/app-core.py b/shiny/api-examples/calc/app-core.py index 603313593..fe2620253 100644 --- a/shiny/api-examples/calc/app-core.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/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/invalidate_later/app-core.py b/shiny/api-examples/invalidate_later/app-core.py index 7966d01f0..66de8233d 100644 --- a/shiny/api-examples/invalidate_later/app-core.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-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/poll/app-core.py b/shiny/api-examples/poll/app-core.py index c9411d28a..f6ab621b6 100644 --- a/shiny/api-examples/poll/app-core.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) From 524fb6a31b352ea9ad1d3d68985dc2b11831b1e4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 12:50:42 -0500 Subject: [PATCH 23/45] temp: `add_examples()` warns about missing docs --- docs/Makefile | 8 +++++++- shiny/_docstring.py | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index a37b25d4e..3efd51267 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -52,11 +52,17 @@ quartodoc_impl: $(PYBIN) ## Build qmd files for API docs $(eval export SHINY_ADD_EXAMPLES=true) $(eval export IN_QUARTODOC=true) . $(PYBIN)/activate \ + && echo "::group::quartodoc interlinks" \ && quartodoc interlinks \ + && echo "::endgroup::" \ + && echo "::group::quartodoc build core docs" \ && SHINY_MODE="core" quartodoc build --config _quartodoc-core.yml --verbose \ && mv objects.json _objects_core.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 + && mv objects.json _objects_express.json \ + && echo "::endgroup::" quartodoc_post: $(PYBIN) ## Post-process qmd files for API docs . $(PYBIN)/activate \ diff --git a/shiny/_docstring.py b/shiny/_docstring.py index fec1df04f..724ebb32a 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -129,9 +129,20 @@ def _(func: F) -> F: ) app_file_name = app_file or "app.py" - example_file = app_choose_core_or_express( - os.path.join(example_dir, app_file_name) - ) + 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): From 20b0dff2aafb68c628d74ee0d6adcbb778e44afb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 12:59:40 -0500 Subject: [PATCH 24/45] don't error in quarto build step if in express docs build --- docs/_renderer_core.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/_renderer_core.py b/docs/_renderer_core.py index c0aff1c13..2f30f6124 100644 --- a/docs/_renderer_core.py +++ b/docs/_renderer_core.py @@ -259,6 +259,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 From 0d308276b61fc80ad1ab50a35443f08970f6000a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 13:01:15 -0500 Subject: [PATCH 25/45] import os --- docs/_renderer_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_renderer_core.py b/docs/_renderer_core.py index 2f30f6124..96239aa9e 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 From 79543e8a4e1d539a8c623a93717c4dbcaedb3bdf Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Fri, 26 Jan 2024 13:21:38 -0500 Subject: [PATCH 26/45] Use shortened description; Only add `...` if title is too long --- docs/_renderer_core.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/_renderer_core.py b/docs/_renderer_core.py index c0aff1c13..1daa47398 100644 --- a/docs/_renderer_core.py +++ b/docs/_renderer_core.py @@ -134,19 +134,21 @@ def summarize(self, obj: Union[dc.Object, dc.Alias]) -> str: ): description = docstring_parts[0].value - # ## Approach: Always return the full description! - return description + # # ## Approach: Always return the full description! + # return description - # ## Alternative: Add ellipsis if the lines are cut off + # # Alternative: Add ellipsis if the lines are cut off - # # If the description is more than one line, only show the first line. - # # Add `...` to indicate the description was truncated - # parts = description.split("\n") - # short = parts[0] - # if len(parts) > 1: - # short += "…" + # If the description is more than one line, only show the first line. + # Add `...` to indicate the description was truncated + parts = description.split("\n") + short = parts[0] + if len(parts) > 1 and parts[1].strip() != "": + short += "…" - # return short + short = convert_rst_link_to_md(short) + + return short return "" From 8978926efec04d1f8ead5bcbc30d37349897f854 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Fri, 26 Jan 2024 13:21:51 -0500 Subject: [PATCH 27/45] Add old `output_transformer` to docs --- docs/_quartodoc-core.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_quartodoc-core.yml b/docs/_quartodoc-core.yml index 573a499c9..5bcefc44c 100644 --- a/docs/_quartodoc-core.yml +++ b/docs/_quartodoc-core.yml @@ -374,6 +374,7 @@ quartodoc: - ui.panel_main - ui.panel_sidebar - ui.nav + - render.transformer.output_transformer - render.transformer.resolve_value_fn - title: Experimental desc: "These methods are under consideration and are considered unstable. However, if there is a method you are excited about, please let us know!" From dbf501a28175749c43787020aa1e73cdcffd5b76 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Fri, 26 Jan 2024 13:22:04 -0500 Subject: [PATCH 28/45] docs: suspend_display -> ui.hold --- shiny/render/renderer/_renderer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shiny/render/renderer/_renderer.py b/shiny/render/renderer/_renderer.py index 5367ac299..ba4b28d88 100644 --- a/shiny/render/renderer/_renderer.py +++ b/shiny/render/renderer/_renderer.py @@ -122,10 +122,10 @@ class Renderer(Generic[IT]): * In Express mode, the output renderer will automatically render its UI via `.auto_output_ui(self, id: str)`. This helper method allows App authors to skip adding a `ui.output_*` function to their UI, making Express mode even more - concise. If more control is needed over the UI, `@suspend_display` can be used to - suppress the auto rendering of the UI. When using `@suspend_display` on a - renderer, the renderer's UI will need to be added to the app to connect the - rendered output to Shiny's reactive graph. + concise. If more control is needed over the UI, `@ui.hold` can be used to suppress + the auto rendering of the UI. When using `@ui.hold` on a renderer, the renderer's + UI will need to be added to the app to connect the rendered output to Shiny's + reactive graph. * The `render` method is responsible for executing the value function and performing any transformations for the output value to be JSON-serializable (`None` is a valid value!). To avoid the boilerplate of resolving the value function and From 971b57e735fc723c942470882f2df89adcbaafaf Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Fri, 26 Jan 2024 13:22:16 -0500 Subject: [PATCH 29/45] Add docs for `as_fillable_container()` --- shiny/ui/fill/_fill.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/shiny/ui/fill/_fill.py b/shiny/ui/fill/_fill.py index d1c26ac99..3ccb2f048 100644 --- a/shiny/ui/fill/_fill.py +++ b/shiny/ui/fill/_fill.py @@ -34,6 +34,33 @@ def as_fillable_container( tag: TagT, ) -> TagT: + """ + Coerce a tag to a fillable container. + + Filling layouts are built on the foundation of _fillable containers_ and _fill + items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is + why most UI components (e.g., :func:`~shiny.ui.card`, + :func:`~shiny.ui.layout_sidebar`) possess both `fillable` and `fill` arguments (to + control their fill behavior). However, sometimes it's useful to add, remove, and/or + test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, which these + functions are designed to do. + + Parameters + ---------- + tag + a Tag object. + + Returns + ------- + : + A copy of the original :class:`~htmltools.Tag` object (`tag`) with additional + attributes (and an :class:`~htmltools.HTMLDependency`). + + See Also + -------- + * :func:`~shiny.ui.fill.as_fill_item` + * :func:`~shiny.ui.fill.remove_all_fill` + """ res = copy(tag) res.add_class(FILL_CONTAINER_CLASS) res.append(fill_dependency()) From 7bb5f336ba207e5eb30d0b6a8e874f2c36ea789b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 13:22:59 -0500 Subject: [PATCH 30/45] fix comment --- shiny/_docstring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/_docstring.py b/shiny/_docstring.py index 724ebb32a..8ecc13c08 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -221,7 +221,7 @@ def __init__( def __str__(self): if self.type in ("core", "express"): - ## Capitalize first letter + # Capitalize first letter type = "a Shiny Express" if self.type == "express" else "a Shiny Core" else: type = "an" From 36094cca63c1944ed8e8936b637d90dc81b39ce5 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Fri, 26 Jan 2024 13:34:56 -0500 Subject: [PATCH 31/45] Use first paragraph for summary description --- docs/_renderer_core.py | 23 ++++++++++++++++------- shiny/render/transformer/_transformer.py | 4 +++- shiny/ui/_modal.py | 4 +++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/_renderer_core.py b/docs/_renderer_core.py index 1daa47398..a46d51292 100644 --- a/docs/_renderer_core.py +++ b/docs/_renderer_core.py @@ -137,15 +137,24 @@ def summarize(self, obj: Union[dc.Object, dc.Alias]) -> str: # # ## Approach: Always return the full description! # return description - # # Alternative: Add ellipsis if the lines are cut off - - # If the description is more than one line, only show the first line. - # Add `...` to indicate the description was truncated parts = description.split("\n") - short = parts[0] - if len(parts) > 1 and parts[1].strip() != "": - short += "…" + # # Alternative: Add ellipsis if the lines are cut off + # # If the description is more than one line, only show the first line. + # # Add `...` to indicate the description was truncated + # short = parts[0] + # if len(parts) > 1 and parts[1].strip() != "": + # short += "…" + + # Alternative: Add take the first paragraph as the description summary + short_parts: list[str] = [] + # Capture the first paragraph (lines until first empty line) + for part in parts: + if part.strip() == "": + break + short_parts.append(part) + + short = " ".join(short_parts) short = convert_rst_link_to_md(short) return short diff --git a/shiny/render/transformer/_transformer.py b/shiny/render/transformer/_transformer.py index 5c95a74c7..7af1a68b9 100644 --- a/shiny/render/transformer/_transformer.py +++ b/shiny/render/transformer/_transformer.py @@ -590,7 +590,9 @@ def output_transformer( | Callable[[TransformFn[IT, P, OT]], OutputTransformer[IT, OT, P]] ): """ - Output transformer decorator + Deprecated. Please use :class:`~shiny.render.renderer.Renderer` instead. + + Output transformer decorator. This decorator method is a convenience method to generate the appropriate types and internal implementation for an overloaded renderer method. This method will provide diff --git a/shiny/ui/_modal.py b/shiny/ui/_modal.py index 8ea91a78d..c24d164ab 100644 --- a/shiny/ui/_modal.py +++ b/shiny/ui/_modal.py @@ -20,7 +20,9 @@ @add_example(ex_dir="../api-examples/modal") def modal_button(label: TagChild, icon: TagChild = None, **kwargs: TagAttrValue) -> Tag: """ - Creates a button that will dismiss a :func:`~shiny.ui.modal`. :func:`~shiny.ui.modal_button` is usually + Creates a button that will dismiss a :func:`~shiny.ui.modal`. + + :func:`~shiny.ui.modal_button` is usually passed to the `footer` of a :func:`~shiny.ui.modal` to add a button to the footer that will close the :func:`~shiny.ui.modal`. From 03d1e69ba000fe0b8858fed293c551cad0b2b95f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 13:57:39 -0500 Subject: [PATCH 32/45] don't need gha group around `quartodoc interlinks` --- docs/Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 3efd51267..47a6af731 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -52,9 +52,7 @@ quartodoc_impl: $(PYBIN) ## Build qmd files for API docs $(eval export SHINY_ADD_EXAMPLES=true) $(eval export IN_QUARTODOC=true) . $(PYBIN)/activate \ - && echo "::group::quartodoc interlinks" \ && quartodoc interlinks \ - && echo "::endgroup::" \ && echo "::group::quartodoc build core docs" \ && SHINY_MODE="core" quartodoc build --config _quartodoc-core.yml --verbose \ && mv objects.json _objects_core.json \ From 457020d940486269882b06b3b32232c5275f08cf Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 14:12:52 -0500 Subject: [PATCH 33/45] docs: consolidate renderer back into one file --- docs/_quartodoc-core.yml | 2 +- docs/_quartodoc-express.yml | 2 +- docs/{_renderer_core.py => _renderer.py} | 4 ++-- docs/_renderer_express.py | 8 -------- 4 files changed, 4 insertions(+), 12 deletions(-) rename docs/{_renderer_core.py => _renderer.py} (98%) delete mode 100644 docs/_renderer_express.py diff --git a/docs/_quartodoc-core.yml b/docs/_quartodoc-core.yml index 5bcefc44c..404f8cd97 100644 --- a/docs/_quartodoc-core.yml +++ b/docs/_quartodoc-core.yml @@ -7,7 +7,7 @@ quartodoc: sidebar: api/core/_sidebar.yml dynamic: true renderer: - style: _renderer_core.py + style: _renderer.py show_signature_annotations: false sections: - title: Page containers diff --git a/docs/_quartodoc-express.yml b/docs/_quartodoc-express.yml index 5e4242aa9..443044fba 100644 --- a/docs/_quartodoc-express.yml +++ b/docs/_quartodoc-express.yml @@ -7,7 +7,7 @@ quartodoc: sidebar: api/express/_sidebar.yml dynamic: true renderer: - style: _renderer_express.py + style: _renderer.py show_signature_annotations: false sections: - title: Input components diff --git a/docs/_renderer_core.py b/docs/_renderer.py similarity index 98% rename from docs/_renderer_core.py rename to docs/_renderer.py index 332a4ab93..6c1c75a57 100644 --- a/docs/_renderer_core.py +++ b/docs/_renderer.py @@ -31,7 +31,7 @@ class FileContentJson(TypedDict): class Renderer(MdRenderer): style = "shiny" - express_api = False + express_api = os.environ.get("SHINY_MODE", "core") == "express" @dispatch def render(self, el: qast.DocstringSectionSeeAlso): @@ -62,7 +62,7 @@ def render(self, el: Union[dc.Object, dc.Alias]): # If this el happens to point to itself, it's probably intentionally # pointing to Core (i.e., express context managers mention that they # wrap Core functions), so don't change that. - # TODO: we want to be more agressive about context managers always + # TODO: we want to be more aggressive about context managers always # pointing to the Core docs? if f"shiny.express.ui.{el.name}" in converted: print(f"Changing Express link to Core for: {el.name}") diff --git a/docs/_renderer_express.py b/docs/_renderer_express.py deleted file mode 100644 index afd50e01a..000000000 --- a/docs/_renderer_express.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import annotations - -from _renderer_core import Renderer as CoreRenderer - - -class Renderer(CoreRenderer): - style = "shiny-express" - express_api = True From dfc9a13d3334e8c404f4884a28b57f4647b43ff6 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 14:22:55 -0500 Subject: [PATCH 34/45] split quartodoc target into a series of smaller targets --- docs/Makefile | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 47a6af731..13b80efd6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,27 +46,35 @@ deps: $(PYBIN) ## Install build dependencies $(PYBIN)/pip install pip --upgrade $(PYBIN)/pip install -e ..[doc] +quartodoc_post: $(PYBIN) ## Post-process qmd files for API docs + . $(PYBIN)/activate \ + && python _combine_objects_json.py + +quartodoc: quartodoc_build_core quartodoc_build_express quartodoc_post -# `quartodoc interlinks` creates links in `_inv` -quartodoc_impl: $(PYBIN) ## Build qmd files for API docs +quartodoc_interlinks: $(PYBIN) ## Create interlinks for API docs + . $(PYBIN)/activate \ + && quartodoc interlinks + +quartodoc_build_core: $(PYBIN) quartodoc_interlinks ## Build core API docs $(eval export SHINY_ADD_EXAMPLES=true) $(eval export IN_QUARTODOC=true) + $(eval export SHINY_MODE=core) . $(PYBIN)/activate \ - && quartodoc interlinks \ - && echo "::group::quartodoc build core docs" \ - && SHINY_MODE="core" quartodoc build --config _quartodoc-core.yml --verbose \ + && echo "::group::quartodoc build core docs" \ + && quartodoc build --config _quartodoc-core.yml --verbose \ && mv objects.json _objects_core.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 +quartodoc_build_express: $(PYBIN) quartodoc_interlinks ## Build express API docs + $(eval export SHINY_ADD_EXAMPLES=true) + $(eval export IN_QUARTODOC=true) + $(eval export SHINY_MODE=express) . $(PYBIN)/activate \ - && python _combine_objects_json.py - -quartodoc: quartodoc_impl quartodoc_post + && echo "::group::quartodoc build express docs" \ + && quartodoc build --config _quartodoc-express.yml --verbose \ + && mv objects.json _objects_express.json \ + && echo "::endgroup::" site: ## Build website . $(PYBIN)/activate \ From dacd74efc9206ff049a50f6d703da033f5e8b2ce Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 15:00:26 -0500 Subject: [PATCH 35/45] Add create_doc_example_{core,express}_fixture() --- tests/playwright/conftest.py | 36 ++++++++++++++++--- .../layout_columns/test_layout_columns.py | 4 +-- .../shiny/components/test_sidebar.py | 4 +-- .../inputs/test_input_action_button_link.py | 4 +-- .../shiny/inputs/test_input_checkbox.py | 4 +-- .../shiny/inputs/test_input_checkbox_group.py | 4 +-- .../shiny/inputs/test_input_date.py | 4 +-- .../shiny/inputs/test_input_date_range.py | 4 +-- .../shiny/inputs/test_input_numeric.py | 4 +-- .../shiny/inputs/test_input_password.py | 4 +-- .../shiny/inputs/test_input_radio_buttons.py | 4 +-- .../shiny/inputs/test_input_select.py | 4 +-- .../shiny/inputs/test_input_selectize.py | 4 +-- .../shiny/inputs/test_input_slider.py | 6 ++-- .../shiny/inputs/test_input_switch.py | 4 +-- .../shiny/inputs/test_input_text.py | 4 +-- .../shiny/inputs/test_input_text_area.py | 4 +-- .../shiny/outputs/test_output_image.py | 4 +-- .../shiny/outputs/test_output_plot.py | 4 +-- .../shiny/outputs/test_output_table.py | 4 +-- .../shiny/outputs/test_output_text.py | 4 +-- .../shiny/outputs/test_output_ui.py | 4 +-- .../render_express/test_render_express.py | 4 +-- 23 files changed, 76 insertions(+), 50 deletions(-) diff --git a/tests/playwright/conftest.py b/tests/playwright/conftest.py index ce7de4cfd..3554b6595 100644 --- a/tests/playwright/conftest.py +++ b/tests/playwright/conftest.py @@ -20,7 +20,7 @@ __all__ = ( "ShinyAppProc", "create_app_fixture", - "create_doc_example_fixture", + "create_doc_example_core_fixture", "create_example_fixture", "local_app", "run_shiny_app", @@ -191,18 +191,44 @@ def fixture_func(): )(fixture_func) -def create_example_fixture(example_name: str, scope: str = "module"): +def create_example_fixture( + example_name: str, + example_file: str = "app.py", + scope: str = "module", +): """Used to create app fixtures from apps in py-shiny/examples""" - return create_app_fixture(here_root / "examples" / example_name / "app.py", scope) + return create_app_fixture( + here_root / "examples" / example_name / example_file, scope + ) -def create_doc_example_fixture(example_name: str, scope: str = "module"): +def create_doc_example_fixture( + example_name: str, + example_file: str = "app.py", + scope: str = "module", +): """Used to create app fixtures from apps in py-shiny/shiny/api-examples""" return create_app_fixture( - here_root / "shiny/api-examples" / example_name / "app.py", scope + here_root / "shiny/api-examples" / example_name / example_file, scope ) +def create_doc_example_core_fixture( + example_name: str, + scope: str = "module", +): + """Used to create app fixtures from ``app-core.py`` example apps in py-shiny/shiny/api-examples""" + return create_doc_example_fixture(example_name, "app-core.py", scope) + + +def create_doc_example_express_fixture( + example_name: str, + scope: str = "module", +): + """Used to create app fixtures from ``app-express.py`` example apps in py-shiny/shiny/api-examples""" + return create_doc_example_fixture(example_name, "app-express.py", scope) + + def create_deploys_fixture(app: Union[PurePath, str], scope: str = "module"): """Used to create app fixtures from apps in tests/playwright/deploys/apps""" return create_app_fixture( diff --git a/tests/playwright/shiny/components/layout_columns/test_layout_columns.py b/tests/playwright/shiny/components/layout_columns/test_layout_columns.py index 97848e683..38f421c22 100644 --- a/tests/playwright/shiny/components/layout_columns/test_layout_columns.py +++ b/tests/playwright/shiny/components/layout_columns/test_layout_columns.py @@ -2,12 +2,12 @@ from typing import TypeVar -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from playwright.sync_api import Page T = TypeVar("T") -app = create_doc_example_fixture("layout_columns") +app = create_doc_example_core_fixture("layout_columns") def not_null(x: T | None) -> T: diff --git a/tests/playwright/shiny/components/test_sidebar.py b/tests/playwright/shiny/components/test_sidebar.py index a7c6eed01..3c0eebd34 100644 --- a/tests/playwright/shiny/components/test_sidebar.py +++ b/tests/playwright/shiny/components/test_sidebar.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import OutputTextVerbatim, Sidebar from playwright.sync_api import Page -app = create_doc_example_fixture("sidebar") +app = create_doc_example_core_fixture("sidebar") def test_sidebar_position_and_open(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_action_button_link.py b/tests/playwright/shiny/inputs/test_input_action_button_link.py index ae70bc5bb..66edf7682 100644 --- a/tests/playwright/shiny/inputs/test_input_action_button_link.py +++ b/tests/playwright/shiny/inputs/test_input_action_button_link.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputActionButton, InputActionLink from playwright.sync_api import Page, expect -app = create_doc_example_fixture("update_action_button") +app = create_doc_example_core_fixture("update_action_button") def test_input_action_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_checkbox.py b/tests/playwright/shiny/inputs/test_input_checkbox.py index 178b6a80c..687c5aca2 100644 --- a/tests/playwright/shiny/inputs/test_input_checkbox.py +++ b/tests/playwright/shiny/inputs/test_input_checkbox.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputCheckbox, OutputUi from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_checkbox") +app = create_doc_example_core_fixture("input_checkbox") def test_input_checkbox_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_checkbox_group.py b/tests/playwright/shiny/inputs/test_input_checkbox_group.py index b887856c4..aab1262c2 100644 --- a/tests/playwright/shiny/inputs/test_input_checkbox_group.py +++ b/tests/playwright/shiny/inputs/test_input_checkbox_group.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputCheckboxGroup from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_checkbox_group") +app = create_doc_example_core_fixture("input_checkbox_group") def test_input_checkbox_group_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_date.py b/tests/playwright/shiny/inputs/test_input_date.py index 4f3180120..ceac65dd1 100644 --- a/tests/playwright/shiny/inputs/test_input_date.py +++ b/tests/playwright/shiny/inputs/test_input_date.py @@ -4,11 +4,11 @@ import typing from typing import Literal -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputDate from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_date") +app = create_doc_example_core_fixture("input_date") def expect_date( diff --git a/tests/playwright/shiny/inputs/test_input_date_range.py b/tests/playwright/shiny/inputs/test_input_date_range.py index 368099da8..778f6d1d7 100644 --- a/tests/playwright/shiny/inputs/test_input_date_range.py +++ b/tests/playwright/shiny/inputs/test_input_date_range.py @@ -4,11 +4,11 @@ import typing from typing import Literal -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputDateRange from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_date_range") +app = create_doc_example_core_fixture("input_date_range") def expect_date_range( diff --git a/tests/playwright/shiny/inputs/test_input_numeric.py b/tests/playwright/shiny/inputs/test_input_numeric.py index 9bcd828bc..503be64c3 100644 --- a/tests/playwright/shiny/inputs/test_input_numeric.py +++ b/tests/playwright/shiny/inputs/test_input_numeric.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputNumeric, OutputTextVerbatim from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_numeric") +app = create_doc_example_core_fixture("input_numeric") # def shinypage(page: Page) -> Page: diff --git a/tests/playwright/shiny/inputs/test_input_password.py b/tests/playwright/shiny/inputs/test_input_password.py index 6f747a504..ea83fe17d 100644 --- a/tests/playwright/shiny/inputs/test_input_password.py +++ b/tests/playwright/shiny/inputs/test_input_password.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputActionButton, InputPassword, OutputTextVerbatim from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_password") +app = create_doc_example_core_fixture("input_password") def test_input_password_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_radio_buttons.py b/tests/playwright/shiny/inputs/test_input_radio_buttons.py index 5a0b0b650..b19d888b0 100644 --- a/tests/playwright/shiny/inputs/test_input_radio_buttons.py +++ b/tests/playwright/shiny/inputs/test_input_radio_buttons.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputRadioButtons from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_radio_buttons") +app = create_doc_example_core_fixture("input_radio_buttons") def test_input_checkbox_group_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_select.py b/tests/playwright/shiny/inputs/test_input_select.py index eb2a66728..9d4316912 100644 --- a/tests/playwright/shiny/inputs/test_input_select.py +++ b/tests/playwright/shiny/inputs/test_input_select.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputSelect from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_select") +app = create_doc_example_core_fixture("input_select") def test_input_select_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_selectize.py b/tests/playwright/shiny/inputs/test_input_selectize.py index aeddc7918..a99d2c361 100644 --- a/tests/playwright/shiny/inputs/test_input_selectize.py +++ b/tests/playwright/shiny/inputs/test_input_selectize.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputSelectize from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_select") +app = create_doc_example_core_fixture("input_select") def test_input_selectize_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_slider.py b/tests/playwright/shiny/inputs/test_input_slider.py index 8303ca71a..5da4cfaac 100644 --- a/tests/playwright/shiny/inputs/test_input_slider.py +++ b/tests/playwright/shiny/inputs/test_input_slider.py @@ -1,9 +1,9 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputSlider, OutputTextVerbatim from playwright.sync_api import Page, expect -slider_app = create_doc_example_fixture("input_slider") -template_app = create_doc_example_fixture("template") +slider_app = create_doc_example_core_fixture("input_slider") +template_app = create_doc_example_core_fixture("template") def test_input_slider_kitchen(page: Page, slider_app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_switch.py b/tests/playwright/shiny/inputs/test_input_switch.py index 50d38398b..39034bf2f 100644 --- a/tests/playwright/shiny/inputs/test_input_switch.py +++ b/tests/playwright/shiny/inputs/test_input_switch.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputSwitch, OutputUi from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_switch") +app = create_doc_example_core_fixture("input_switch") def test_input_switch_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_text.py b/tests/playwright/shiny/inputs/test_input_text.py index bdd29f203..d2b045c69 100644 --- a/tests/playwright/shiny/inputs/test_input_text.py +++ b/tests/playwright/shiny/inputs/test_input_text.py @@ -1,10 +1,10 @@ import re -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputText, OutputTextVerbatim from playwright.sync_api import Page, expect -app = create_doc_example_fixture("input_text") +app = create_doc_example_core_fixture("input_text") def test_input_text_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_text_area.py b/tests/playwright/shiny/inputs/test_input_text_area.py index 97addf556..40ab6ccd0 100644 --- a/tests/playwright/shiny/inputs/test_input_text_area.py +++ b/tests/playwright/shiny/inputs/test_input_text_area.py @@ -1,10 +1,10 @@ import re -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputTextArea, OutputTextVerbatim from playwright.sync_api import Locator, Page, expect -app = create_doc_example_fixture("input_text_area") +app = create_doc_example_core_fixture("input_text_area") default_txt = "Data summary\nwith\nmultiple\nlines" diff --git a/tests/playwright/shiny/outputs/test_output_image.py b/tests/playwright/shiny/outputs/test_output_image.py index c2051263e..76c18a3b9 100644 --- a/tests/playwright/shiny/outputs/test_output_image.py +++ b/tests/playwright/shiny/outputs/test_output_image.py @@ -1,10 +1,10 @@ import re -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import OutputImage from playwright.sync_api import Page -app = create_doc_example_fixture("output_image") +app = create_doc_example_core_fixture("output_image") def test_output_image_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/outputs/test_output_plot.py b/tests/playwright/shiny/outputs/test_output_plot.py index df166aef8..5999badf5 100644 --- a/tests/playwright/shiny/outputs/test_output_plot.py +++ b/tests/playwright/shiny/outputs/test_output_plot.py @@ -1,10 +1,10 @@ import re -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import OutputPlot from playwright.sync_api import Page -app = create_doc_example_fixture("output_plot") +app = create_doc_example_core_fixture("output_plot") def test_output_plot_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/outputs/test_output_table.py b/tests/playwright/shiny/outputs/test_output_table.py index c773ce911..72e55efdb 100644 --- a/tests/playwright/shiny/outputs/test_output_table.py +++ b/tests/playwright/shiny/outputs/test_output_table.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import OutputTable from playwright.sync_api import Page -app = create_doc_example_fixture("output_table") +app = create_doc_example_core_fixture("output_table") def test_output_plot_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/outputs/test_output_text.py b/tests/playwright/shiny/outputs/test_output_text.py index 72a16c434..bca59a0a4 100644 --- a/tests/playwright/shiny/outputs/test_output_text.py +++ b/tests/playwright/shiny/outputs/test_output_text.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputText, OutputText, OutputTextVerbatim from playwright.sync_api import Page -app = create_doc_example_fixture("output_text") +app = create_doc_example_core_fixture("output_text") def test_output_text_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/outputs/test_output_ui.py b/tests/playwright/shiny/outputs/test_output_ui.py index fc5a93804..44508b6d7 100644 --- a/tests/playwright/shiny/outputs/test_output_ui.py +++ b/tests/playwright/shiny/outputs/test_output_ui.py @@ -1,8 +1,8 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputActionButton, InputSlider, InputText, OutputUi from playwright.sync_api import Page, expect -app = create_doc_example_fixture("output_ui") +app = create_doc_example_core_fixture("output_ui") def test_output_ui_kitchen(page: Page, app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/shiny-express/render_express/test_render_express.py b/tests/playwright/shiny/shiny-express/render_express/test_render_express.py index f60f31478..5abd75a4e 100644 --- a/tests/playwright/shiny/shiny-express/render_express/test_render_express.py +++ b/tests/playwright/shiny/shiny-express/render_express/test_render_express.py @@ -1,7 +1,7 @@ -from conftest import ShinyAppProc, create_doc_example_fixture +from conftest import ShinyAppProc, create_doc_example_core_fixture from playwright.sync_api import Page, expect -app = create_doc_example_fixture("render_express") +app = create_doc_example_core_fixture("render_express") EXPECT_TIMEOUT = 30 * 1000 From cae09c442edaf6e7ae3057515c4e7c3bf0af325a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 15:18:58 -0500 Subject: [PATCH 36/45] tests: Test all example apps that are `app.py` or start with `app-*.py` --- tests/playwright/examples/example_apps.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index d09007e97..4dd0f6895 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -20,10 +20,15 @@ def get_apps(path: str) -> typing.List[str]: for folder in os.listdir(full_path): folder_path = os.path.join(full_path, folder) if os.path.isdir(folder_path): - app_path = os.path.join(folder_path, "app.py") - if os.path.isfile(app_path): - # Return relative app path - app_paths.append(os.path.join(path, folder, "app.py")) + folder_files = os.listdir(folder_path) + for file in folder_files: + if os.path.isdir(os.path.join(folder_path, file)): + continue + if not file.endswith(".py"): + continue + if file == "app.py" or file.startswith("app-"): + # Return relative app path + app_paths.append(os.path.join(path, folder, file)) return app_paths From b1a6044badde70db914511b96d877a5137355b05 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 15:52:51 -0500 Subject: [PATCH 37/45] fix poll example --- shiny/api-examples/poll/app-core.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/shiny/api-examples/poll/app-core.py b/shiny/api-examples/poll/app-core.py index f6ab621b6..6079a0be4 100644 --- a/shiny/api-examples/poll/app-core.py +++ b/shiny/api-examples/poll/app-core.py @@ -89,23 +89,20 @@ def stock_quotes() -> pd.DataFrame: # === Define the Shiny UI and server =============================== app_ui = ui.page_fluid( - ui.layout_columns( - ui.card( - ui.markdown( - """ - # `shiny.reactive.poll` demo - - This example app shows how to stream results from a database (in this - case, an in-memory sqlite3) with the help of `shiny.reactive.poll`. - """ - ), - ui.input_selectize( - "symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True - ), - ui.output_data_frame("table"), - fill=False, + ui.card( + ui.markdown( + """ + # `shiny.reactive.poll` demo + + This example app shows how to stream results from a database (in this + case, an in-memory sqlite3) with the help of `shiny.reactive.poll`. + """ + ), + ui.input_selectize( + "symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True ), - col_widths=[8, 4], + ui.output_data_frame("table"), + fill=False, ) ) From 512334212753e89fdba65cd27c814b7ac157e25b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 15:56:18 -0500 Subject: [PATCH 38/45] temp: ignore sidebar max_height_mobile warning --- tests/playwright/examples/example_apps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index 4dd0f6895..a2e56fdd3 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -74,6 +74,9 @@ def get_apps(path: str) -> typing.List[str]: "render_express": [*express_warnings], } app_allow_external_errors: typing.List[str] = [ + # TODO-garrick-future: Remove after fixing sidebar max_height_mobile warning + "UserWarning: The `shiny.ui.sidebar(max_height_mobile=)`", + "res = self.fn(*self.args, **self.kwargs)", # if shiny express app detected "Detected Shiny Express app", # pandas >= 2.2.0 From 37bf76ec05001fca0cf2ec5cdf4be55966771aeb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 16:39:17 -0500 Subject: [PATCH 39/45] Apply suggestions from code review Co-authored-by: Carson Sievert --- docs/_combine_objects_json.py | 2 +- shiny/_docstring.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/_combine_objects_json.py b/docs/_combine_objects_json.py index 9446bf339..fbb2a2215 100644 --- a/docs/_combine_objects_json.py +++ b/docs/_combine_objects_json.py @@ -37,7 +37,7 @@ def write_objects_file(objects: QuartodocObject, path: str) -> None: json.dump(objects, file, indent=4, default=lambda dc: dc.__dict__) -print("\nCombinging objects json files...") +print("\nCombining objects json files...") objects_core = read_objects_file("_objects_core.json") objects_express = read_objects_file("_objects_express.json") diff --git a/shiny/_docstring.py b/shiny/_docstring.py index 8ecc13c08..e92502e9d 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -134,13 +134,13 @@ def _(func: F) -> F: os.path.join(example_dir, app_file_name) ) except ExampleNotFoundException as e: - func_dir = get_decorated_source_directory(func).split("py-shiny/")[1] + file = "shiny/" + func_dir.split("shiny/")[1] if "__code__" in dir(func): print( - f"::warning file={func_dir},line={func.__code__.co_firstlineno}::{e}" + f"::warning file={file},line={func.__code__.co_firstlineno}::{e}" ) else: - print(f"::warning file={func_dir}::{e}") + print(f"::warning file={file}::{e}") return func @@ -215,7 +215,7 @@ def __init__( dir: str, type: Optional[Literal["core", "express"]] = None, ) -> None: - self.type = type or os.environ.get("SHINY_MODE") or "core" + self.type = type or os.environ.get("SHINY_MODE", "core") self.file_names = [file_names] if isinstance(file_names, str) else file_names self.dir = dir From 2bba14a7b5b4129f8c4933c93fc8ba8c4a14bf78 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 17:50:16 -0500 Subject: [PATCH 40/45] improve makefile target list a little more --- docs/Makefile | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 13b80efd6..2de2d63cf 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,17 +46,15 @@ deps: $(PYBIN) ## Install build dependencies $(PYBIN)/pip install pip --upgrade $(PYBIN)/pip install -e ..[doc] -quartodoc_post: $(PYBIN) ## Post-process qmd files for API docs - . $(PYBIN)/activate \ - && python _combine_objects_json.py - -quartodoc: quartodoc_build_core quartodoc_build_express quartodoc_post +quartodoc: quartodoc_build_core quartodoc_build_express quartodoc_post ## Build quartodocs for express and core -quartodoc_interlinks: $(PYBIN) ## Create interlinks for API docs +## Build interlinks for API docs +quartodoc_interlinks: $(PYBIN) . $(PYBIN)/activate \ && quartodoc interlinks -quartodoc_build_core: $(PYBIN) quartodoc_interlinks ## Build core API docs +## Build core API docs +quartodoc_build_core: $(PYBIN) quartodoc_interlinks $(eval export SHINY_ADD_EXAMPLES=true) $(eval export IN_QUARTODOC=true) $(eval export SHINY_MODE=core) @@ -66,7 +64,8 @@ quartodoc_build_core: $(PYBIN) quartodoc_interlinks ## Build core API docs && mv objects.json _objects_core.json \ && echo "::endgroup::" -quartodoc_build_express: $(PYBIN) quartodoc_interlinks ## Build express API docs +## Build express API docs +quartodoc_build_express: $(PYBIN) quartodoc_interlinks $(eval export SHINY_ADD_EXAMPLES=true) $(eval export IN_QUARTODOC=true) $(eval export SHINY_MODE=express) @@ -76,6 +75,11 @@ quartodoc_build_express: $(PYBIN) quartodoc_interlinks ## Build express API docs && mv objects.json _objects_express.json \ && echo "::endgroup::" +## Clean up after quartodoc build +quartodoc_post: $(PYBIN) + . $(PYBIN)/activate \ + && python _combine_objects_json.py + site: ## Build website . $(PYBIN)/activate \ && quarto render From c50fb2c4db04ac1d1bdbe243d80fd9779198e95a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 17:54:19 -0500 Subject: [PATCH 41/45] One `@no_example()` decorator * @no_example() ignores both * @no_example('core') ignores only core * @no_example('express') ignores only express --- docs/_renderer.py | 16 +++++++++++----- shiny/_docstring.py | 37 +++++++++++++++++------------------- shiny/_main.py | 2 +- shiny/module.py | 4 ++-- shiny/reactive/_core.py | 11 ++++++----- shiny/reactive/_reactives.py | 3 ++- shiny/render/_dataframe.py | 2 +- shiny/render/_render.py | 4 +++- shiny/session/_utils.py | 4 ++-- shiny/types.py | 14 +++++++++----- shiny/ui/_bootstrap.py | 6 +++--- shiny/ui/_input_update.py | 4 ++-- shiny/ui/_navs.py | 24 +++++++++++------------ shiny/ui/_notification.py | 2 +- shiny/ui/_output.py | 2 +- shiny/ui/_page.py | 10 +++++----- shiny/ui/_sidebar.py | 6 +++--- shiny/ui/_valuebox.py | 2 +- shiny/ui/fill/_fill.py | 2 +- 19 files changed, 83 insertions(+), 72 deletions(-) diff --git a/docs/_renderer.py b/docs/_renderer.py index 6c1c75a57..493d88c50 100644 --- a/docs/_renderer.py +++ b/docs/_renderer.py @@ -271,8 +271,8 @@ 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() + if os.environ.get("SHINY_MODE", "core") == "express": + # We aren't done with all of the express examples yet return if re.search(r"(^|\n)#{2,6} Examples\n", converted): @@ -283,9 +283,15 @@ def check_if_missing_expected_example(el, converted): # Only check Shiny objects for examples return - if hasattr(el, "decorators") and "no_example" in [ - d.value.canonical_name for d in el.decorators - ]: + def is_no_ex_decorator(x): + if x == "no_example()": + return True + + return x == f'no_example("{os.environ.get("SHINY_MODE", "core")}")' + + if hasattr(el, "decorators") and any( + [is_no_ex_decorator(d.value.canonical_name) for d in el.decorators] + ): # When an example is intentionally omitted, we mark the fn with `@no_example` return diff --git a/shiny/_docstring.py b/shiny/_docstring.py index e92502e9d..bd4ca7396 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -2,7 +2,6 @@ import os import sys -from functools import wraps from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, TypeVar @@ -26,27 +25,21 @@ def find_api_examples_dir(start_dir: str) -> Optional[str]: F = TypeVar("F", bound=FuncType) -def no_example(func: F) -> F: - return func - - -def no_example_express(decorator: Callable[..., F]) -> F | Callable[..., F]: +def no_example(type: Optional[Literal["express", "core"]] = None) -> Callable[[F], F]: """ - Prevent ``@add_example()`` from throwing an error about missing Express examples. + Prevent ``@add_example()`` from throwing an error about missing 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 + def decorator(func: F) -> F: + current = getattr(func, "__no_example", []) + if type is None: + current.extend(["express", "core"]) + else: + current.append(type) + setattr(func, "__no_example", current) # noqa: B010 + return func - return wrapper_decorator + return decorator # This class is used to mark docstrings when @add_example() is used, so that an error @@ -109,6 +102,10 @@ def _(func: F) -> F: func.__doc__ = DocStringWithExample(func.__doc__) return func + current_mode = os.getenv("SHINY_MODE", "core") + if current_mode in getattr(func, "__no_example", []): + return func + func_dir = get_decorated_source_directory(func) fn_name = func.__name__ @@ -137,10 +134,10 @@ def _(func: F) -> F: file = "shiny/" + func_dir.split("shiny/")[1] if "__code__" in dir(func): print( - f"::warning file={file},line={func.__code__.co_firstlineno}::{e}" + f"::warning file={file},line={func.__code__.co_firstlineno}::{fn_name} - {e}" ) else: - print(f"::warning file={file}::{e}") + print(f"::warning file={file}::{fn_name} - {e}") return func diff --git a/shiny/_main.py b/shiny/_main.py index 56180606c..caf9212ba 100644 --- a/shiny/_main.py +++ b/shiny/_main.py @@ -143,7 +143,7 @@ def main() -> None: help="Launch app browser after app starts, using the Python webbrowser module.", show_default=True, ) -@no_example +@no_example() def run( app: str | shiny.App, host: str, diff --git a/shiny/module.py b/shiny/module.py index 2045e3a93..b3e0643a4 100644 --- a/shiny/module.py +++ b/shiny/module.py @@ -15,7 +15,7 @@ R = TypeVar("R") -@no_example +@no_example() def ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]: def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R: with namespace_context(id): @@ -24,7 +24,7 @@ def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R: return wrapper -@no_example +@no_example() def server( fn: Callable[Concatenate[Inputs, Outputs, Session, P], R] ) -> Callable[Concatenate[str, P], R]: diff --git a/shiny/reactive/_core.py b/shiny/reactive/_core.py index 24c9de1ae..9dfa64156 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, no_example_express +from .._docstring import add_example, no_example from ..types import MISSING, MISSING_TYPE if TYPE_CHECKING: @@ -199,7 +199,8 @@ def isolate(self) -> Generator[None, None, None]: _reactive_environment = ReactiveEnvironment() -@no_example_express(add_example()) +@add_example() +@no_example("express") @contextlib.contextmanager def isolate() -> Generator[None, None, None]: """ @@ -247,7 +248,7 @@ def get_current_context() -> Context: return _reactive_environment.current_context() -@no_example +@no_example() async def flush() -> None: """ Run any pending invalidations (i.e., flush the reactive environment). @@ -260,7 +261,7 @@ async def flush() -> None: await _reactive_environment.flush() -@no_example +@no_example() def on_flushed( func: Callable[[], Awaitable[None]], once: bool = False ) -> Callable[[], None]: @@ -288,7 +289,7 @@ def on_flushed( return _reactive_environment.on_flushed(func, once) -@no_example +@no_example() def lock() -> asyncio.Lock: """ A lock that should be held whenever manipulating the reactive graph. diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index 10061a2e1..60c003dfa 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -30,7 +30,7 @@ ) from .. import _utils -from .._docstring import add_example +from .._docstring import add_example, no_example from .._utils import is_async_callable, run_coro_sync from .._validation import req from ..types import MISSING, MISSING_TYPE, ActionButtonValue, SilentException @@ -679,6 +679,7 @@ def effect( @add_example() +@no_example("express") def effect( fn: Optional[EffectFunction | EffectFunctionAsync] = None, *, diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index 3a5d0343f..a24996c77 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -108,7 +108,7 @@ def to_payload(self) -> Jsonifiable: return res -@no_example +@no_example() class DataTable(AbstractTabularData): """ Holds the data and options for a :class:`~shiny.render.data_frame` output, for a diff --git a/shiny/render/_render.py b/shiny/render/_render.py index 067ce0369..adb9cf6df 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -27,7 +27,7 @@ from .. import _utils from .. import ui as _ui -from .._docstring import add_example +from .._docstring import add_example, no_example from .._namespaces import ResolvedId from .._typing_extensions import Self from ..session import get_current_session, require_active_session @@ -61,6 +61,7 @@ @add_example(ex_dir="../api-examples/output_text") +@no_example("express") class text(Renderer[str]): """ Reactively render text. @@ -188,6 +189,7 @@ async def transform(self, value: str) -> Jsonifiable: @add_example(ex_dir="../api-examples/output_plot") +@no_example("express") class plot(Renderer[object]): """ Reactively render a plot object as an HTML image. diff --git a/shiny/session/_utils.py b/shiny/session/_utils.py index 2d399cb2d..2bbaaf1fd 100644 --- a/shiny/session/_utils.py +++ b/shiny/session/_utils.py @@ -34,7 +34,7 @@ class RenderedDeps(TypedDict): _default_session: Optional[Session] = None -@no_example +@no_example() def get_current_session() -> Optional[Session]: """ Get the current user session. @@ -76,7 +76,7 @@ def session_context(session: Optional[Session]): _current_session.reset(token) -@no_example +@no_example() def require_active_session(session: Optional[Session]) -> Session: """ Raise an exception if no Shiny session is currently active. diff --git a/shiny/types.py b/shiny/types.py index 963144412..09b722492 100644 --- a/shiny/types.py +++ b/shiny/types.py @@ -16,7 +16,7 @@ from htmltools import TagChild -from ._docstring import add_example, no_example_express +from ._docstring import add_example, no_example from ._typing_extensions import NotRequired, TypedDict if TYPE_CHECKING: @@ -55,7 +55,8 @@ class FileInfo(TypedDict): """The path to the file on the server.""" -@no_example_express(add_example(ex_dir="./api-examples/output_image")) +@add_example(ex_dir="./api-examples/output_image") +@no_example("express") class ImgData(TypedDict): """ Return type for :class:`~shiny.render.image`. @@ -79,7 +80,8 @@ class ImgData(TypedDict): """TODO """ -@no_example_express(add_example()) +@add_example() +@no_example("express") class SafeException(Exception): """ Throw a safe exception. @@ -93,7 +95,8 @@ class SafeException(Exception): pass -@no_example_express(add_example()) +@add_example() +@no_example("express") class SilentException(Exception): """ Throw a silent exception. @@ -116,7 +119,8 @@ class SilentException(Exception): pass -@no_example_express(add_example()) +@add_example() +@no_example("express") class SilentCancelOutputException(Exception): """ Throw a silent exception and don't clear output diff --git a/shiny/ui/_bootstrap.py b/shiny/ui/_bootstrap.py index 35184f3b7..dc767442d 100644 --- a/shiny/ui/_bootstrap.py +++ b/shiny/ui/_bootstrap.py @@ -109,7 +109,7 @@ def column( return div({"class": cls}, *args, **kwargs) -@no_example +@no_example() def panel_well(*args: TagChild | TagAttrs, **kwargs: TagAttrValue) -> Tag: """ Create a well panel. @@ -233,7 +233,7 @@ def panel_title( return TagList(get_window_title(title, window_title), title) -@no_example +@no_example() def panel_fixed( *args: TagChild | TagAttrs, top: Optional[str] = None, @@ -385,7 +385,7 @@ def panel_absolute( return TagList(deps, divTag, tags.script(f'$(".draggable").draggable({dragOpts});')) -@no_example +@no_example() def help_text(*args: TagChild | TagAttrs, **kwargs: TagAttrValue) -> Tag: """ Create a help text element diff --git a/shiny/ui/_input_update.py b/shiny/ui/_input_update.py index 703db03c0..c979876c1 100644 --- a/shiny/ui/_input_update.py +++ b/shiny/ui/_input_update.py @@ -113,7 +113,7 @@ def update_action_button( # ----------------------------------------------------------------------------- # input_task_button.py # ----------------------------------------------------------------------------- -@no_example +@no_example() def update_task_button( id: str, *, @@ -213,7 +213,7 @@ def update_checkbox( session.send_input_message(id, drop_none(msg)) -@no_example +@no_example() @doc_format(note=_note) def update_switch( id: str, diff --git a/shiny/ui/_navs.py b/shiny/ui/_navs.py index a692e4aaa..d3cca5e34 100644 --- a/shiny/ui/_navs.py +++ b/shiny/ui/_navs.py @@ -157,7 +157,7 @@ def nav_panel( ) -@no_example +@no_example() def nav_control(*args: TagChild) -> NavPanel: """ Place a control in the navigation container. @@ -189,7 +189,7 @@ def nav_control(*args: TagChild) -> NavPanel: return NavPanel(tags.li(*args)) -@no_example +@no_example() def nav_spacer() -> NavPanel: """ Create space between nav items. @@ -296,7 +296,7 @@ def menu_string_as_nav(x: str | NavSetArg) -> NavSetArg: return NavPanel(nav) -@no_example +@no_example() def nav_menu( title: TagChild, *args: NavPanel | str, @@ -404,7 +404,7 @@ def layout(self, nav: Tag, content: Tag) -> TagList | Tag: # ----------------------------------------------------------------------------- # Navigation containers # ----------------------------------------------------------------------------- -@no_example +@no_example() def navset_tab( *args: NavSetArg, id: Optional[str] = None, @@ -461,7 +461,7 @@ def navset_tab( ) -@no_example +@no_example() def navset_pill( *args: NavSetArg, id: Optional[str] = None, @@ -517,7 +517,7 @@ def navset_pill( ) -@no_example +@no_example() def navset_underline( *args: NavSetArg, id: Optional[str] = None, @@ -684,7 +684,7 @@ def layout(self, nav: Tag, content: Tag) -> Tag: ) -@no_example +@no_example() def navset_card_tab( *args: NavSetArg, id: Optional[str] = None, @@ -747,7 +747,7 @@ def navset_card_tab( ) -@no_example +@no_example() def navset_card_pill( *args: NavSetArg, id: Optional[str] = None, @@ -813,7 +813,7 @@ def navset_card_pill( ) -@no_example +@no_example() def navset_card_underline( *args: NavSetArg, id: Optional[str] = None, @@ -912,7 +912,7 @@ def layout(self, nav: TagChild, content: TagChild) -> Tag: ) -@no_example +@no_example() def navset_pill_list( *args: NavSetArg | MetadataNode, id: Optional[str] = None, @@ -1148,7 +1148,7 @@ def _make_tabs_fillable( # TODO-future; Content should not be indented unless when called from `page_navbar()` -@no_example +@no_example() def navset_bar( *args: NavSetArg | MetadataNode | Sequence[MetadataNode], title: TagChild, @@ -1393,7 +1393,7 @@ def navset_tab_card( # Deprecated 2023-12-07 -@no_example +@no_example() def nav( title: TagChild, *args: TagChild, diff --git a/shiny/ui/_notification.py b/shiny/ui/_notification.py index 033909397..33593dce2 100644 --- a/shiny/ui/_notification.py +++ b/shiny/ui/_notification.py @@ -93,7 +93,7 @@ def notification_show( return id -@no_example +@no_example() def notification_remove(id: str, *, session: Optional[Session] = None) -> str: """ Remove a notification. diff --git a/shiny/ui/_output.py b/shiny/ui/_output.py index bd4119068..2b21ae193 100644 --- a/shiny/ui/_output.py +++ b/shiny/ui/_output.py @@ -271,7 +271,7 @@ def output_text( return container(id=resolve_id(id), class_="shiny-text-output") -@no_example +@no_example() def output_code(id: str, placeholder: bool = True) -> Tag: """ Create a output container for code (monospaced text). diff --git a/shiny/ui/_page.py b/shiny/ui/_page.py index a9e4125d9..707884ab4 100644 --- a/shiny/ui/_page.py +++ b/shiny/ui/_page.py @@ -110,7 +110,7 @@ def page_sidebar( ) -@no_example +@no_example() def page_navbar( *args: NavSetArg | MetadataNode | Sequence[MetadataNode], title: Optional[str | Tag | TagList] = None, @@ -255,7 +255,7 @@ def page_navbar( ) -@no_example +@no_example() def page_fillable( *args: TagChild | TagAttrs, padding: Optional[CssUnit | list[CssUnit]] = None, @@ -407,7 +407,7 @@ def page_fixed( # TODO: implement theme (just Bootswatch for now?) -@no_example +@no_example() def page_bootstrap( *args: TagChild | TagAttrs, title: Optional[str] = None, @@ -449,7 +449,7 @@ def page_bootstrap( ) -@no_example +@no_example() def page_auto( *args: TagChild | TagAttrs, title: str | MISSING_TYPE = MISSING, @@ -633,7 +633,7 @@ def _page_auto_fixed( ) -@no_example +@no_example() def page_output(id: str) -> Tag: """ Create a page container where the entire body is a UI output. diff --git a/shiny/ui/_sidebar.py b/shiny/ui/_sidebar.py index fb2e0b8fe..90de9507a 100644 --- a/shiny/ui/_sidebar.py +++ b/shiny/ui/_sidebar.py @@ -41,7 +41,7 @@ ) -@no_example +@no_example() class Sidebar: """ A sidebar object @@ -579,7 +579,7 @@ def _sidebar_init_js() -> Tag: # Deprecated 2023-06-13 # Includes: DeprecatedPanelSidebar -@no_example +@no_example() def panel_sidebar( *args: TagChild | TagAttrs, width: int = 4, @@ -602,7 +602,7 @@ def panel_sidebar( # Deprecated 2023-06-13 # Includes: DeprecatedPanelMain -@no_example +@no_example() def panel_main( *args: TagChild | TagAttrs, width: int = 8, diff --git a/shiny/ui/_valuebox.py b/shiny/ui/_valuebox.py index ef35ec55f..b4bc7a8f1 100644 --- a/shiny/ui/_valuebox.py +++ b/shiny/ui/_valuebox.py @@ -227,7 +227,7 @@ class ValueBoxTheme: bg: str | None -@no_example +@no_example() def value_box_theme( name: Optional[str] = None, *, diff --git a/shiny/ui/fill/_fill.py b/shiny/ui/fill/_fill.py index 3ccb2f048..6c210c038 100644 --- a/shiny/ui/fill/_fill.py +++ b/shiny/ui/fill/_fill.py @@ -104,7 +104,7 @@ def as_fill_item( return res -@no_example +@no_example() def remove_all_fill( tag: TagT, ) -> TagT: From 9e53f640b0cc4a3bcfbc51c0dcebd86acfa2a7b1 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 17:56:02 -0500 Subject: [PATCH 42/45] update docstring --- shiny/_docstring.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shiny/_docstring.py b/shiny/_docstring.py index bd4ca7396..4c00430af 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -73,8 +73,10 @@ 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/``. + * Examples for the ``shiny`` package are in ``shiny/api-examples/``. We also place + Express examples in this directory adjacent to their Core counterparts. + * Examples for the ``shiny.experimental`` subpackage are in + ``shiny/experimental/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 From 8fd7e50c5134e29cb9caf3c5652e5829b89f6a35 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 17:58:22 -0500 Subject: [PATCH 43/45] no_example: rename param `mode` --- shiny/_docstring.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/shiny/_docstring.py b/shiny/_docstring.py index 4c00430af..10281861f 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -25,17 +25,25 @@ def find_api_examples_dir(start_dir: str) -> Optional[str]: F = TypeVar("F", bound=FuncType) -def no_example(type: Optional[Literal["express", "core"]] = None) -> Callable[[F], F]: +def no_example(mode: Optional[Literal["express", "core"]] = None) -> Callable[[F], F]: """ Prevent ``@add_example()`` from throwing an error about missing examples. + + Parameters + ---------- + mode: + If ``"express"``, ``@add_example()`` will not throw an error if the current + mode is Express. If ``"core"``, ``@add_example()`` will not throw an error if + the current mode is Core. If ``None``, ``@add_example()`` will not throw an + error in either mode. """ def decorator(func: F) -> F: current = getattr(func, "__no_example", []) - if type is None: + if mode is None: current.extend(["express", "core"]) else: - current.append(type) + current.append(mode) setattr(func, "__no_example", current) # noqa: B010 return func From a119844b600b91cccbc6bcca738f6749bfc33390 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 18:04:48 -0500 Subject: [PATCH 44/45] remove custom example (we'll come back to this) --- shiny/ui/_navs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/ui/_navs.py b/shiny/ui/_navs.py index d3cca5e34..507218091 100644 --- a/shiny/ui/_navs.py +++ b/shiny/ui/_navs.py @@ -97,7 +97,7 @@ def tagify(self) -> None: ) -@add_example(app_file="app-basic.py") +@add_example() def nav_panel( title: TagChild, *args: TagChild, From 7f001bd8ca86fc22f219d9f7b0ed8ad3dcc61273 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 26 Jan 2024 18:49:29 -0500 Subject: [PATCH 45/45] Update docs/_renderer.py Co-authored-by: Carson Sievert --- docs/_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_renderer.py b/docs/_renderer.py index 493d88c50..92930c8ee 100644 --- a/docs/_renderer.py +++ b/docs/_renderer.py @@ -272,7 +272,7 @@ 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", "core") == "express": - # We aren't done with all of the express examples yet + # TODO: remove once we are done porting express examples return if re.search(r"(^|\n)#{2,6} Examples\n", converted):