solara-ui 1.45.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prefix/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
- prefix/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
- solara/__init__.py +124 -0
- solara/__main__.py +765 -0
- solara/_stores.py +297 -0
- solara/alias.py +6 -0
- solara/autorouting.py +555 -0
- solara/cache.py +305 -0
- solara/checks.html +71 -0
- solara/checks.py +227 -0
- solara/comm.py +28 -0
- solara/components/__init__.py +77 -0
- solara/components/alert.py +155 -0
- solara/components/applayout.py +397 -0
- solara/components/button.py +85 -0
- solara/components/card.py +87 -0
- solara/components/checkbox.py +50 -0
- solara/components/code_highlight_css.py +11 -0
- solara/components/code_highlight_css.vue +63 -0
- solara/components/columns.py +159 -0
- solara/components/component_vue.py +134 -0
- solara/components/cross_filter.py +335 -0
- solara/components/dataframe.py +546 -0
- solara/components/datatable.py +214 -0
- solara/components/datatable.vue +175 -0
- solara/components/details.py +56 -0
- solara/components/download.vue +35 -0
- solara/components/echarts.py +86 -0
- solara/components/echarts.vue +139 -0
- solara/components/figure_altair.py +39 -0
- solara/components/file_browser.py +181 -0
- solara/components/file_download.py +199 -0
- solara/components/file_drop.py +159 -0
- solara/components/file_drop.vue +83 -0
- solara/components/file_list_widget.vue +78 -0
- solara/components/head.py +27 -0
- solara/components/head_tag.py +49 -0
- solara/components/head_tag.vue +60 -0
- solara/components/image.py +173 -0
- solara/components/input.py +456 -0
- solara/components/input_text_area.py +86 -0
- solara/components/link.py +55 -0
- solara/components/markdown.py +441 -0
- solara/components/markdown_editor.py +33 -0
- solara/components/markdown_editor.vue +359 -0
- solara/components/matplotlib.py +74 -0
- solara/components/meta.py +47 -0
- solara/components/misc.py +333 -0
- solara/components/pivot_table.py +258 -0
- solara/components/pivot_table.vue +158 -0
- solara/components/progress.py +47 -0
- solara/components/select.py +182 -0
- solara/components/select.vue +27 -0
- solara/components/slider.py +442 -0
- solara/components/slider_date.vue +56 -0
- solara/components/spinner-solara.vue +105 -0
- solara/components/spinner.py +45 -0
- solara/components/sql_code.py +41 -0
- solara/components/sql_code.vue +125 -0
- solara/components/style.py +105 -0
- solara/components/switch.py +71 -0
- solara/components/tab_navigation.py +37 -0
- solara/components/title.py +90 -0
- solara/components/title.vue +38 -0
- solara/components/togglebuttons.py +200 -0
- solara/components/tooltip.py +61 -0
- solara/core.py +42 -0
- solara/datatypes.py +143 -0
- solara/express.py +241 -0
- solara/hooks/__init__.py +4 -0
- solara/hooks/dataframe.py +99 -0
- solara/hooks/misc.py +263 -0
- solara/hooks/use_reactive.py +151 -0
- solara/hooks/use_thread.py +129 -0
- solara/kitchensink.py +8 -0
- solara/lab/__init__.py +34 -0
- solara/lab/components/__init__.py +7 -0
- solara/lab/components/chat.py +215 -0
- solara/lab/components/confirmation_dialog.py +163 -0
- solara/lab/components/cross_filter.py +7 -0
- solara/lab/components/input_date.py +339 -0
- solara/lab/components/input_time.py +133 -0
- solara/lab/components/menu.py +181 -0
- solara/lab/components/menu.vue +38 -0
- solara/lab/components/tabs.py +274 -0
- solara/lab/components/theming.py +98 -0
- solara/lab/components/theming.vue +72 -0
- solara/lab/hooks/__init__.py +0 -0
- solara/lab/hooks/dataframe.py +2 -0
- solara/lab/toestand.py +3 -0
- solara/lab/utils/__init__.py +2 -0
- solara/lab/utils/cookies.py +5 -0
- solara/lab/utils/dataframe.py +165 -0
- solara/lab/utils/headers.py +5 -0
- solara/layout.py +44 -0
- solara/lifecycle.py +46 -0
- solara/minisettings.py +141 -0
- solara/py.typed +0 -0
- solara/reactive.py +99 -0
- solara/routing.py +268 -0
- solara/scope/__init__.py +88 -0
- solara/scope/types.py +55 -0
- solara/server/__init__.py +0 -0
- solara/server/app.py +527 -0
- solara/server/assets/custom.css +1 -0
- solara/server/assets/custom.js +1 -0
- solara/server/assets/favicon.png +0 -0
- solara/server/assets/favicon.svg +5 -0
- solara/server/assets/style.css +1681 -0
- solara/server/assets/theme-dark.css +437 -0
- solara/server/assets/theme-light.css +420 -0
- solara/server/assets/theme.js +3 -0
- solara/server/cdn_helper.py +91 -0
- solara/server/esm.py +71 -0
- solara/server/fastapi.py +5 -0
- solara/server/flask.py +297 -0
- solara/server/jupyter/__init__.py +2 -0
- solara/server/jupyter/cdn_handler.py +28 -0
- solara/server/jupyter/server_extension.py +40 -0
- solara/server/jupyter/solara.py +91 -0
- solara/server/jupytertools.py +46 -0
- solara/server/kernel.py +388 -0
- solara/server/kernel_context.py +467 -0
- solara/server/patch.py +564 -0
- solara/server/pyinstaller/__init__.py +9 -0
- solara/server/pyinstaller/hook-ipyreact.py +5 -0
- solara/server/pyinstaller/hook-ipyvuetify.py +5 -0
- solara/server/pyinstaller/hook-solara.py +9 -0
- solara/server/qt.py +113 -0
- solara/server/reload.py +251 -0
- solara/server/server.py +484 -0
- solara/server/settings.py +249 -0
- solara/server/shell.py +269 -0
- solara/server/starlette.py +770 -0
- solara/server/static/ansi.js +270 -0
- solara/server/static/highlight-dark.css +82 -0
- solara/server/static/highlight.css +43 -0
- solara/server/static/main-vuetify.js +272 -0
- solara/server/static/main.js +163 -0
- solara/server/static/solara_bootstrap.py +129 -0
- solara/server/static/sun.svg +23 -0
- solara/server/static/webworker.js +42 -0
- solara/server/telemetry.py +212 -0
- solara/server/templates/index.html.j2 +1 -0
- solara/server/templates/loader-plain.css +11 -0
- solara/server/templates/loader-plain.html +20 -0
- solara/server/templates/loader-solara.css +111 -0
- solara/server/templates/loader-solara.html +40 -0
- solara/server/templates/plain.html +82 -0
- solara/server/templates/solara.html.j2 +486 -0
- solara/server/threaded.py +84 -0
- solara/server/utils.py +44 -0
- solara/server/websocket.py +45 -0
- solara/settings.py +86 -0
- solara/tasks.py +893 -0
- solara/template/button.py +16 -0
- solara/template/markdown.py +42 -0
- solara/template/portal/.flake8 +6 -0
- solara/template/portal/.pre-commit-config.yaml +28 -0
- solara/template/portal/LICENSE +21 -0
- solara/template/portal/Procfile +7 -0
- solara/template/portal/mypy.ini +3 -0
- solara/template/portal/pyproject.toml +26 -0
- solara/template/portal/solara_portal/__init__.py +4 -0
- solara/template/portal/solara_portal/components/__init__.py +2 -0
- solara/template/portal/solara_portal/components/article.py +28 -0
- solara/template/portal/solara_portal/components/data.py +28 -0
- solara/template/portal/solara_portal/components/header.py +6 -0
- solara/template/portal/solara_portal/components/layout.py +6 -0
- solara/template/portal/solara_portal/content/articles/equis-in-vidi.md +85 -0
- solara/template/portal/solara_portal/content/articles/substiterat-vati.md +70 -0
- solara/template/portal/solara_portal/data.py +60 -0
- solara/template/portal/solara_portal/pages/__init__.py +67 -0
- solara/template/portal/solara_portal/pages/article/__init__.py +26 -0
- solara/template/portal/solara_portal/pages/tabular.py +29 -0
- solara/template/portal/solara_portal/pages/viz/__init__.py +70 -0
- solara/template/portal/solara_portal/pages/viz/overview.py +14 -0
- solara/test/__init__.py +0 -0
- solara/test/pytest_plugin.py +783 -0
- solara/toestand.py +998 -0
- solara/util.py +348 -0
- solara/validate_hooks.py +258 -0
- solara/website/__init__.py +0 -0
- solara/website/assets/custom.css +444 -0
- solara/website/assets/images/logo-small.png +0 -0
- solara/website/assets/images/logo.svg +17 -0
- solara/website/assets/images/logo_white.svg +50 -0
- solara/website/assets/theme.js +8 -0
- solara/website/components/__init__.py +5 -0
- solara/website/components/algolia.py +6 -0
- solara/website/components/algolia.vue +24 -0
- solara/website/components/algolia_api.vue +202 -0
- solara/website/components/breadcrumbs.py +28 -0
- solara/website/components/contact.py +144 -0
- solara/website/components/docs.py +143 -0
- solara/website/components/header.py +75 -0
- solara/website/components/mailchimp.py +12 -0
- solara/website/components/mailchimp.vue +47 -0
- solara/website/components/markdown.py +99 -0
- solara/website/components/markdown_nav.vue +34 -0
- solara/website/components/notebook.py +171 -0
- solara/website/components/sidebar.py +105 -0
- solara/website/pages/__init__.py +370 -0
- solara/website/pages/about/__init__.py +9 -0
- solara/website/pages/about/about.md +3 -0
- solara/website/pages/apps/__init__.py +16 -0
- solara/website/pages/apps/authorization/__init__.py +119 -0
- solara/website/pages/apps/authorization/admin.py +12 -0
- solara/website/pages/apps/authorization/users.py +12 -0
- solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
- solara/website/pages/apps/layout-demo.py +40 -0
- solara/website/pages/apps/multipage/__init__.py +38 -0
- solara/website/pages/apps/multipage/page1.py +26 -0
- solara/website/pages/apps/multipage/page2.py +34 -0
- solara/website/pages/apps/scatter.py +136 -0
- solara/website/pages/apps/scrolling.py +63 -0
- solara/website/pages/apps/tutorial-streamlit.py +18 -0
- solara/website/pages/careers/__init__.py +27 -0
- solara/website/pages/changelog/__init__.py +10 -0
- solara/website/pages/changelog/changelog.md +372 -0
- solara/website/pages/contact/__init__.py +34 -0
- solara/website/pages/doc_use_download.py +85 -0
- solara/website/pages/documentation/__init__.py +90 -0
- solara/website/pages/documentation/advanced/__init__.py +9 -0
- solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
- solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +6 -0
- solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +196 -0
- solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +125 -0
- solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +417 -0
- solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +69 -0
- solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +50 -0
- solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +124 -0
- solara/website/pages/documentation/advanced/content/15-reference/00-overview.md +3 -0
- solara/website/pages/documentation/advanced/content/15-reference/40-static_files.md +31 -0
- solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +72 -0
- solara/website/pages/documentation/advanced/content/15-reference/60-static-site-generation.md +59 -0
- solara/website/pages/documentation/advanced/content/15-reference/70-search.md +34 -0
- solara/website/pages/documentation/advanced/content/15-reference/80-reloading.md +34 -0
- solara/website/pages/documentation/advanced/content/15-reference/90-notebook-support.md +7 -0
- solara/website/pages/documentation/advanced/content/15-reference/95-caching.md +148 -0
- solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
- solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +35 -0
- solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +42 -0
- solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +28 -0
- solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +108 -0
- solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +23 -0
- solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +192 -0
- solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +166 -0
- solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +18 -0
- solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +256 -0
- solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +108 -0
- solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +12 -0
- solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +7 -0
- solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +187 -0
- solara/website/pages/documentation/advanced/content/40-development/00-overview.md +0 -0
- solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +45 -0
- solara/website/pages/documentation/advanced/content/40-development/10-setup.md +76 -0
- solara/website/pages/documentation/api/__init__.py +19 -0
- solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +22 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +20 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +20 -0
- solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +20 -0
- solara/website/pages/documentation/api/hooks/__init__.py +9 -0
- solara/website/pages/documentation/api/hooks/use_cross_filter.py +23 -0
- solara/website/pages/documentation/api/hooks/use_dark_effective.py +12 -0
- solara/website/pages/documentation/api/hooks/use_effect.md +43 -0
- solara/website/pages/documentation/api/hooks/use_effect.py +9 -0
- solara/website/pages/documentation/api/hooks/use_exception.py +31 -0
- solara/website/pages/documentation/api/hooks/use_memo.md +16 -0
- solara/website/pages/documentation/api/hooks/use_memo.py +9 -0
- solara/website/pages/documentation/api/hooks/use_previous.py +30 -0
- solara/website/pages/documentation/api/hooks/use_reactive.py +16 -0
- solara/website/pages/documentation/api/hooks/use_state.py +10 -0
- solara/website/pages/documentation/api/hooks/use_state_or_update.py +66 -0
- solara/website/pages/documentation/api/hooks/use_thread.md +64 -0
- solara/website/pages/documentation/api/hooks/use_thread.py +42 -0
- solara/website/pages/documentation/api/hooks/use_trait_observe.py +12 -0
- solara/website/pages/documentation/api/routing/__init__.py +9 -0
- solara/website/pages/documentation/api/routing/generate_routes.py +10 -0
- solara/website/pages/documentation/api/routing/generate_routes_directory.py +10 -0
- solara/website/pages/documentation/api/routing/resolve_path.py +35 -0
- solara/website/pages/documentation/api/routing/route.py +29 -0
- solara/website/pages/documentation/api/routing/use_route.py +76 -0
- solara/website/pages/documentation/api/routing/use_router.py +16 -0
- solara/website/pages/documentation/api/utilities/__init__.py +9 -0
- solara/website/pages/documentation/api/utilities/component_vue.py +10 -0
- solara/website/pages/documentation/api/utilities/computed.py +16 -0
- solara/website/pages/documentation/api/utilities/display.py +16 -0
- solara/website/pages/documentation/api/utilities/get_kernel_id.py +16 -0
- solara/website/pages/documentation/api/utilities/get_session_id.py +16 -0
- solara/website/pages/documentation/api/utilities/memoize.py +35 -0
- solara/website/pages/documentation/api/utilities/on_kernel_start.py +44 -0
- solara/website/pages/documentation/api/utilities/reactive.py +16 -0
- solara/website/pages/documentation/api/utilities/widget.py +104 -0
- solara/website/pages/documentation/components/__init__.py +12 -0
- solara/website/pages/documentation/components/advanced/__init__.py +9 -0
- solara/website/pages/documentation/components/advanced/link.py +25 -0
- solara/website/pages/documentation/components/advanced/meta.py +17 -0
- solara/website/pages/documentation/components/advanced/style.py +43 -0
- solara/website/pages/documentation/components/common.py +9 -0
- solara/website/pages/documentation/components/data/__init__.py +9 -0
- solara/website/pages/documentation/components/data/dataframe.py +44 -0
- solara/website/pages/documentation/components/data/pivot_table.py +81 -0
- solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
- solara/website/pages/documentation/components/enterprise/avatar.py +24 -0
- solara/website/pages/documentation/components/enterprise/avatar_menu.py +25 -0
- solara/website/pages/documentation/components/input/__init__.py +9 -0
- solara/website/pages/documentation/components/input/button.py +23 -0
- solara/website/pages/documentation/components/input/checkbox.py +10 -0
- solara/website/pages/documentation/components/input/file_browser.py +30 -0
- solara/website/pages/documentation/components/input/file_drop.py +76 -0
- solara/website/pages/documentation/components/input/input.py +43 -0
- solara/website/pages/documentation/components/input/select.py +22 -0
- solara/website/pages/documentation/components/input/slider.py +29 -0
- solara/website/pages/documentation/components/input/switch.py +10 -0
- solara/website/pages/documentation/components/input/togglebuttons.py +21 -0
- solara/website/pages/documentation/components/lab/__init__.py +9 -0
- solara/website/pages/documentation/components/lab/chat.py +109 -0
- solara/website/pages/documentation/components/lab/confirmation_dialog.py +55 -0
- solara/website/pages/documentation/components/lab/cookies_headers.py +48 -0
- solara/website/pages/documentation/components/lab/input_date.py +20 -0
- solara/website/pages/documentation/components/lab/input_time.py +15 -0
- solara/website/pages/documentation/components/lab/menu.py +22 -0
- solara/website/pages/documentation/components/lab/tab.py +25 -0
- solara/website/pages/documentation/components/lab/tabs.py +45 -0
- solara/website/pages/documentation/components/lab/task.py +11 -0
- solara/website/pages/documentation/components/lab/theming.py +74 -0
- solara/website/pages/documentation/components/lab/use_task.py +11 -0
- solara/website/pages/documentation/components/layout/__init__.py +9 -0
- solara/website/pages/documentation/components/layout/app_bar.py +16 -0
- solara/website/pages/documentation/components/layout/app_bar_title.py +16 -0
- solara/website/pages/documentation/components/layout/app_layout.py +24 -0
- solara/website/pages/documentation/components/layout/card.py +15 -0
- solara/website/pages/documentation/components/layout/card_actions.py +16 -0
- solara/website/pages/documentation/components/layout/column.py +30 -0
- solara/website/pages/documentation/components/layout/columns.py +27 -0
- solara/website/pages/documentation/components/layout/columns_responsive.py +66 -0
- solara/website/pages/documentation/components/layout/details.py +13 -0
- solara/website/pages/documentation/components/layout/griddraggable.py +62 -0
- solara/website/pages/documentation/components/layout/gridfixed.py +19 -0
- solara/website/pages/documentation/components/layout/hbox.py +18 -0
- solara/website/pages/documentation/components/layout/row.py +30 -0
- solara/website/pages/documentation/components/layout/sidebar.py +24 -0
- solara/website/pages/documentation/components/layout/vbox.py +19 -0
- solara/website/pages/documentation/components/output/__init__.py +9 -0
- solara/website/pages/documentation/components/output/file_download.py +11 -0
- solara/website/pages/documentation/components/output/html.py +19 -0
- solara/website/pages/documentation/components/output/image.py +11 -0
- solara/website/pages/documentation/components/output/markdown.py +57 -0
- solara/website/pages/documentation/components/output/markdown_editor.py +51 -0
- solara/website/pages/documentation/components/output/sql_code.py +83 -0
- solara/website/pages/documentation/components/output/tooltip.py +11 -0
- solara/website/pages/documentation/components/page/__init__.py +9 -0
- solara/website/pages/documentation/components/page/head.py +15 -0
- solara/website/pages/documentation/components/page/title.py +25 -0
- solara/website/pages/documentation/components/status/__init__.py +9 -0
- solara/website/pages/documentation/components/status/error.py +39 -0
- solara/website/pages/documentation/components/status/info.py +39 -0
- solara/website/pages/documentation/components/status/progress.py +10 -0
- solara/website/pages/documentation/components/status/spinner.py +11 -0
- solara/website/pages/documentation/components/status/success.py +40 -0
- solara/website/pages/documentation/components/status/warning.py +47 -0
- solara/website/pages/documentation/components/viz/__init__.py +9 -0
- solara/website/pages/documentation/components/viz/altair.py +42 -0
- solara/website/pages/documentation/components/viz/echarts.py +77 -0
- solara/website/pages/documentation/components/viz/matplotlib.py +30 -0
- solara/website/pages/documentation/components/viz/plotly.py +63 -0
- solara/website/pages/documentation/components/viz/plotly_express.py +41 -0
- solara/website/pages/documentation/examples/__init__.py +54 -0
- solara/website/pages/documentation/examples/ai/__init__.py +11 -0
- solara/website/pages/documentation/examples/ai/chatbot.py +113 -0
- solara/website/pages/documentation/examples/ai/tokenizer.py +107 -0
- solara/website/pages/documentation/examples/basics/__init__.py +10 -0
- solara/website/pages/documentation/examples/basics/sine.py +28 -0
- solara/website/pages/documentation/examples/fullscreen/__init__.py +10 -0
- solara/website/pages/documentation/examples/fullscreen/authorization.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/layout_demo.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/multipage.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/scatter.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/scrolling.py +3 -0
- solara/website/pages/documentation/examples/fullscreen/tutorial_streamlit.py +3 -0
- solara/website/pages/documentation/examples/general/__init__.py +10 -0
- solara/website/pages/documentation/examples/general/custom_storage.py +70 -0
- solara/website/pages/documentation/examples/general/deploy_model.py +115 -0
- solara/website/pages/documentation/examples/general/live_update.py +32 -0
- solara/website/pages/documentation/examples/general/login_oauth.py +81 -0
- solara/website/pages/documentation/examples/general/mycard.vue +58 -0
- solara/website/pages/documentation/examples/general/pokemon_search.py +51 -0
- solara/website/pages/documentation/examples/general/vue_component.py +50 -0
- solara/website/pages/documentation/examples/ipycanvas.py +49 -0
- solara/website/pages/documentation/examples/libraries/__init__.py +10 -0
- solara/website/pages/documentation/examples/libraries/altair.py +65 -0
- solara/website/pages/documentation/examples/libraries/bqplot.py +39 -0
- solara/website/pages/documentation/examples/libraries/ipyleaflet.py +33 -0
- solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +66 -0
- solara/website/pages/documentation/examples/utilities/__init__.py +10 -0
- solara/website/pages/documentation/examples/utilities/calculator.py +157 -0
- solara/website/pages/documentation/examples/utilities/countdown_timer.py +62 -0
- solara/website/pages/documentation/examples/utilities/todo.py +196 -0
- solara/website/pages/documentation/examples/visualization/__init__.py +6 -0
- solara/website/pages/documentation/examples/visualization/annotator.py +67 -0
- solara/website/pages/documentation/examples/visualization/linked_views.py +81 -0
- solara/website/pages/documentation/examples/visualization/plotly.py +44 -0
- solara/website/pages/documentation/faq/__init__.py +12 -0
- solara/website/pages/documentation/faq/content/99-faq.md +112 -0
- solara/website/pages/documentation/getting_started/__init__.py +9 -0
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +107 -0
- solara/website/pages/documentation/getting_started/content/01-introduction.md +125 -0
- solara/website/pages/documentation/getting_started/content/02-installing.md +134 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/10_data_science.py +13 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +89 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +124 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +146 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +144 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +65 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz +0 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/_data_science.ipynb +445 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +1021 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +228 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +278 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +305 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +80 -0
- solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
- solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +26 -0
- solara/website/pages/docutils.py +38 -0
- solara/website/pages/home.vue +1199 -0
- solara/website/pages/our_team/__init__.py +83 -0
- solara/website/pages/pricing/__init__.py +31 -0
- solara/website/pages/roadmap/__init__.py +11 -0
- solara/website/pages/roadmap/roadmap.md +47 -0
- solara/website/pages/scale_ipywidgets.py +45 -0
- solara/website/pages/showcase/__init__.py +105 -0
- solara/website/pages/showcase/domino_code_assist.py +60 -0
- solara/website/pages/showcase/planeto_tessa.py +19 -0
- solara/website/pages/showcase/solara_dev.py +54 -0
- solara/website/pages/showcase/solarathon_2023_team_2.py +22 -0
- solara/website/pages/showcase/solarathon_2023_team_4.py +22 -0
- solara/website/pages/showcase/solarathon_2023_team_5.py +23 -0
- solara/website/pages/showcase/solarathon_2023_team_6.py +34 -0
- solara/website/pages/showcase/wanderlust.py +27 -0
- solara/website/public/beach.jpeg +0 -0
- solara/website/public/logo.svg +6 -0
- solara/website/public/social/discord.svg +1 -0
- solara/website/public/social/github.svg +1 -0
- solara/website/public/social/twitter.svg +3 -0
- solara/website/public/success.html +25 -0
- solara/website/templates/index.html.j2 +117 -0
- solara/website/utils.py +51 -0
- solara/widgets/__init__.py +1 -0
- solara/widgets/vue/gridlayout.vue +107 -0
- solara/widgets/vue/html.vue +4 -0
- solara/widgets/vue/navigator.vue +134 -0
- solara/widgets/vue/vegalite.vue +130 -0
- solara/widgets/widgets.py +74 -0
- solara_ui-1.45.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
- solara_ui-1.45.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
- solara_ui-1.45.0.dist-info/METADATA +162 -0
- solara_ui-1.45.0.dist-info/RECORD +464 -0
- solara_ui-1.45.0.dist-info/WHEEL +4 -0
- solara_ui-1.45.0.dist-info/licenses/LICENSE +21 -0
solara/tasks.py
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import abc
|
|
3
|
+
import asyncio
|
|
4
|
+
import dataclasses
|
|
5
|
+
import functools
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import (
|
|
11
|
+
Any,
|
|
12
|
+
Callable,
|
|
13
|
+
Coroutine,
|
|
14
|
+
Generic,
|
|
15
|
+
List,
|
|
16
|
+
Optional,
|
|
17
|
+
TypeVar,
|
|
18
|
+
Union,
|
|
19
|
+
cast,
|
|
20
|
+
overload,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
import typing_extensions
|
|
24
|
+
|
|
25
|
+
import solara
|
|
26
|
+
import solara.util
|
|
27
|
+
from solara.toestand import Singleton
|
|
28
|
+
from solara import _using_solara_server
|
|
29
|
+
|
|
30
|
+
from .toestand import Ref as ref
|
|
31
|
+
|
|
32
|
+
if sys.version_info >= (3, 8):
|
|
33
|
+
from typing import Literal
|
|
34
|
+
else:
|
|
35
|
+
from typing_extensions import Literal
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
R = TypeVar("R")
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
P = typing_extensions.ParamSpec("P")
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger("solara.task")
|
|
43
|
+
|
|
44
|
+
has_threads = solara.util.has_threads
|
|
45
|
+
_main_event_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
46
|
+
try:
|
|
47
|
+
# this will be the event loop in Jupyter/IPython
|
|
48
|
+
# on Python >=3.12, get_running_loop() is preferred
|
|
49
|
+
if sys.version_info >= (3, 12):
|
|
50
|
+
_main_event_loop = asyncio.get_running_loop()
|
|
51
|
+
else:
|
|
52
|
+
_main_event_loop = asyncio.get_event_loop()
|
|
53
|
+
except RuntimeError:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_current_task():
|
|
58
|
+
# asyncio.current_task() is not available in Python 3.6
|
|
59
|
+
if sys.version_info >= (3, 7):
|
|
60
|
+
return asyncio.current_task()
|
|
61
|
+
else:
|
|
62
|
+
return asyncio.Task.current_task()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TaskState(Enum):
|
|
66
|
+
NOTCALLED = 1
|
|
67
|
+
STARTING = 2
|
|
68
|
+
WAITING = 3
|
|
69
|
+
RUNNING = 4
|
|
70
|
+
ERROR = 5
|
|
71
|
+
FINISHED = 6
|
|
72
|
+
CANCELLED = 7
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclasses.dataclass(frozen=True)
|
|
76
|
+
class TaskResult(Generic[T]):
|
|
77
|
+
value: Optional[T] = None
|
|
78
|
+
latest: Optional[T] = None
|
|
79
|
+
exception: Optional[Exception] = None
|
|
80
|
+
# only useful if you want to know details about the state like STARTING or WAITING
|
|
81
|
+
_state: TaskState = TaskState.NOTCALLED
|
|
82
|
+
progress: Optional[float] = None
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def not_called(self):
|
|
86
|
+
return self._state == TaskState.NOTCALLED
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def pending(self):
|
|
90
|
+
return self._state in (TaskState.STARTING, TaskState.WAITING, TaskState.RUNNING)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def finished(self):
|
|
94
|
+
return self._state == TaskState.FINISHED
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def cancelled(self):
|
|
98
|
+
return self._state == TaskState.CANCELLED
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def error(self):
|
|
102
|
+
return self._state == TaskState.ERROR
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Task(Generic[P, R], abc.ABC):
|
|
106
|
+
def __init__(self, key: str):
|
|
107
|
+
self._result = solara.Reactive(
|
|
108
|
+
TaskResult[R](
|
|
109
|
+
value=None,
|
|
110
|
+
_state=TaskState.NOTCALLED,
|
|
111
|
+
),
|
|
112
|
+
key="solara.tasks:TaskResult:" + key,
|
|
113
|
+
)
|
|
114
|
+
self._last_value: Optional[R] = None
|
|
115
|
+
self._last_progress: Optional[float] = None
|
|
116
|
+
self._latest = ref(self._result.fields.latest)
|
|
117
|
+
self._value = ref(self._result.fields.value)
|
|
118
|
+
self._error = ref(self._result.fields.error)
|
|
119
|
+
self._finished = ref(self._result.fields.finished)
|
|
120
|
+
self._cancelled = ref(self._result.fields.cancelled)
|
|
121
|
+
self._pending = ref(self._result.fields.pending)
|
|
122
|
+
self._not_called = ref(self._result.fields.not_called)
|
|
123
|
+
self._progress = ref(self._result.fields.progress)
|
|
124
|
+
self._exception = ref(self._result.fields.exception)
|
|
125
|
+
self._state_ = ref(self._result.fields._state)
|
|
126
|
+
# used for tests only
|
|
127
|
+
self._start_event = threading.Event()
|
|
128
|
+
self._start_event.set()
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def result(self) -> TaskResult[R]:
|
|
132
|
+
return self._result.value
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def latest(self) -> Optional[R]:
|
|
136
|
+
return self._latest.value
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def value(self) -> Optional[R]:
|
|
140
|
+
return self._value.value
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def _state(self) -> TaskState:
|
|
144
|
+
return self._state_.value
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def error(self) -> bool:
|
|
148
|
+
return self._error.value
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def finished(self) -> bool:
|
|
152
|
+
return self._finished.value
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def cancelled(self) -> bool:
|
|
156
|
+
return self._cancelled.value
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def pending(self) -> bool:
|
|
160
|
+
return self._pending.value
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def not_called(self) -> bool:
|
|
164
|
+
return self._not_called.value
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def progress(self) -> Optional[float]:
|
|
168
|
+
return self._progress.value
|
|
169
|
+
|
|
170
|
+
@progress.setter
|
|
171
|
+
def progress(self, value: Optional[float]) -> None:
|
|
172
|
+
self._last_progress = value
|
|
173
|
+
self._progress.value = value
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def exception(self) -> Optional[Exception]:
|
|
177
|
+
return self._exception.value
|
|
178
|
+
|
|
179
|
+
@abc.abstractmethod
|
|
180
|
+
def retry(self) -> None: ...
|
|
181
|
+
|
|
182
|
+
@abc.abstractmethod
|
|
183
|
+
def cancel(self) -> None: ...
|
|
184
|
+
|
|
185
|
+
@abc.abstractmethod
|
|
186
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
|
|
187
|
+
|
|
188
|
+
@abc.abstractmethod
|
|
189
|
+
def is_current(self) -> bool: ...
|
|
190
|
+
|
|
191
|
+
def _prestart(self):
|
|
192
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class _CancelledErrorInOurTask(BaseException):
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TaskAsyncio(Task[P, R]):
|
|
200
|
+
current_task: Optional[asyncio.Task] = None
|
|
201
|
+
current_future: Optional[asyncio.Future] = None
|
|
202
|
+
_cancel: Optional[Callable[[], None]] = None
|
|
203
|
+
_retry: Optional[Callable[[], None]] = None
|
|
204
|
+
|
|
205
|
+
def __init__(self, run_in_thread: bool, function: Callable[P, Coroutine[Any, Any, R]], key: str):
|
|
206
|
+
self.run_in_thread = run_in_thread
|
|
207
|
+
self.function = function
|
|
208
|
+
super().__init__(key)
|
|
209
|
+
|
|
210
|
+
def cancel(self) -> None:
|
|
211
|
+
if self._cancel:
|
|
212
|
+
self._cancel()
|
|
213
|
+
else:
|
|
214
|
+
raise RuntimeError("Cannot cancel task, never started")
|
|
215
|
+
|
|
216
|
+
def retry(self):
|
|
217
|
+
if self._retry:
|
|
218
|
+
self._retry()
|
|
219
|
+
else:
|
|
220
|
+
raise RuntimeError("Cannot retry task, never started")
|
|
221
|
+
|
|
222
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
|
223
|
+
self._last_progress = None
|
|
224
|
+
current_task: asyncio.Task[None]
|
|
225
|
+
if self.current_task:
|
|
226
|
+
self.current_task.cancel()
|
|
227
|
+
|
|
228
|
+
def retry():
|
|
229
|
+
self(*args, **kwargs)
|
|
230
|
+
|
|
231
|
+
def cancel():
|
|
232
|
+
event_loop = current_task.get_loop()
|
|
233
|
+
# cancel after cancel is a no-op
|
|
234
|
+
self._cancel = lambda: None
|
|
235
|
+
if _get_current_task() == current_task:
|
|
236
|
+
if event_loop == asyncio.get_event_loop():
|
|
237
|
+
# we got called in our own task and event loop
|
|
238
|
+
raise _CancelledErrorInOurTask()
|
|
239
|
+
else:
|
|
240
|
+
current_task.cancel()
|
|
241
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
|
|
242
|
+
else:
|
|
243
|
+
current_task.cancel()
|
|
244
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
|
|
245
|
+
|
|
246
|
+
self._cancel = cancel
|
|
247
|
+
self._retry = retry
|
|
248
|
+
if _using_solara_server():
|
|
249
|
+
import solara.server.kernel_context
|
|
250
|
+
|
|
251
|
+
context = solara.server.kernel_context.get_current_context()
|
|
252
|
+
call_event_loop = context.event_loop
|
|
253
|
+
else:
|
|
254
|
+
call_event_loop = _main_event_loop or asyncio.get_event_loop()
|
|
255
|
+
|
|
256
|
+
self.current_future = future = call_event_loop.create_future()
|
|
257
|
+
|
|
258
|
+
if self.run_in_thread:
|
|
259
|
+
thread_event_loop = asyncio.new_event_loop()
|
|
260
|
+
self.current_task = current_task = thread_event_loop.create_task(self._async_run(call_event_loop, future, args, kwargs))
|
|
261
|
+
|
|
262
|
+
def runs_in_thread():
|
|
263
|
+
try:
|
|
264
|
+
thread_event_loop.run_until_complete(current_task)
|
|
265
|
+
except asyncio.CancelledError as e:
|
|
266
|
+
call_event_loop.call_soon_threadsafe(future.set_exception, e)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.exception("error running in thread")
|
|
269
|
+
call_event_loop.call_soon_threadsafe(future.set_exception, e)
|
|
270
|
+
raise
|
|
271
|
+
|
|
272
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
|
|
273
|
+
thread = threading.Thread(target=runs_in_thread)
|
|
274
|
+
thread.start()
|
|
275
|
+
else:
|
|
276
|
+
self.current_task = current_task = asyncio.create_task(self._async_run(call_event_loop, future, args, kwargs))
|
|
277
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
|
|
278
|
+
|
|
279
|
+
def is_current(self):
|
|
280
|
+
running_task = self.current_task
|
|
281
|
+
assert running_task is not None
|
|
282
|
+
return (self.current_task == _get_current_task()) and not running_task.cancelled()
|
|
283
|
+
|
|
284
|
+
async def _async_run(self, call_event_loop: asyncio.AbstractEventLoop, future: asyncio.Future, args, kwargs) -> None:
|
|
285
|
+
self._start_event.wait()
|
|
286
|
+
|
|
287
|
+
task_for_this_call = _get_current_task()
|
|
288
|
+
assert task_for_this_call is not None
|
|
289
|
+
|
|
290
|
+
if self.is_current():
|
|
291
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
|
|
292
|
+
|
|
293
|
+
async def runner():
|
|
294
|
+
try:
|
|
295
|
+
if self.is_current():
|
|
296
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.RUNNING)
|
|
297
|
+
self._last_value = value = await self.function(*args, **kwargs)
|
|
298
|
+
if self.is_current() and not task_for_this_call.cancelled(): # type: ignore
|
|
299
|
+
self._result.value = TaskResult[R](value=value, latest=value, _state=TaskState.FINISHED, progress=self._last_progress)
|
|
300
|
+
logger.info("setting result to %r", value)
|
|
301
|
+
call_event_loop.call_soon_threadsafe(future.set_result, value)
|
|
302
|
+
except Exception as e:
|
|
303
|
+
if self.is_current():
|
|
304
|
+
logger.exception(e)
|
|
305
|
+
self._result.value = TaskResult[R](latest=self._last_value, exception=e, _state=TaskState.ERROR)
|
|
306
|
+
call_event_loop.call_soon_threadsafe(future.set_exception, e)
|
|
307
|
+
# Although this seems like an easy way to handle cancellation, an early cancelled task will never execute
|
|
308
|
+
# so this code will never execute, so we need to handle this in the cancel function in __call__
|
|
309
|
+
# except asyncio.CancelledError as e:
|
|
310
|
+
# if self.is_current():
|
|
311
|
+
# self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
|
|
312
|
+
# call_event_loop.call_soon_threadsafe(future.set_exception, e)
|
|
313
|
+
# But... if we call cancel in our own task, we still need to do it from this place
|
|
314
|
+
except _CancelledErrorInOurTask as e:
|
|
315
|
+
try:
|
|
316
|
+
# maybe there is a different way to get a full stack trace?
|
|
317
|
+
raise asyncio.CancelledError() from e
|
|
318
|
+
except asyncio.CancelledError as e:
|
|
319
|
+
if self.is_current():
|
|
320
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
|
|
321
|
+
call_event_loop.call_soon_threadsafe(future.set_exception, e)
|
|
322
|
+
|
|
323
|
+
await runner()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class TaskThreaded(Task[P, R]):
|
|
327
|
+
_current_cancel_event: Optional[threading.Event] = None
|
|
328
|
+
_current_thread: Optional[threading.Thread] = None
|
|
329
|
+
_last_finished_event: Optional[threading.Event] = None
|
|
330
|
+
_cancel: Optional[Callable[[], None]] = None
|
|
331
|
+
_retry: Optional[Callable[[], None]] = None
|
|
332
|
+
|
|
333
|
+
def __init__(self, function: Callable[P, R], key: str):
|
|
334
|
+
super().__init__(key)
|
|
335
|
+
self.__qualname__ = function.__qualname__
|
|
336
|
+
self.function = function
|
|
337
|
+
self.lock = threading.Lock()
|
|
338
|
+
self._local = threading.local()
|
|
339
|
+
|
|
340
|
+
def cancel(self) -> None:
|
|
341
|
+
if self._cancel:
|
|
342
|
+
self._cancel()
|
|
343
|
+
else:
|
|
344
|
+
raise RuntimeError("Cannot cancel task, never started")
|
|
345
|
+
|
|
346
|
+
def retry(self):
|
|
347
|
+
if self._retry:
|
|
348
|
+
self._retry()
|
|
349
|
+
else:
|
|
350
|
+
raise RuntimeError("Cannot retry task, never started")
|
|
351
|
+
|
|
352
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
|
353
|
+
self._last_finished_event = _last_finished_event = threading.Event()
|
|
354
|
+
self._current_cancel_event = cancel_event = threading.Event()
|
|
355
|
+
self._last_progress = None
|
|
356
|
+
|
|
357
|
+
def retry():
|
|
358
|
+
self(*args, **kwargs)
|
|
359
|
+
|
|
360
|
+
def cancel():
|
|
361
|
+
cancel_event.set()
|
|
362
|
+
if threading.current_thread() == current_thread:
|
|
363
|
+
raise solara.util.CancelledError()
|
|
364
|
+
self._current_cancel_event = None
|
|
365
|
+
|
|
366
|
+
self._retry = retry
|
|
367
|
+
self._cancel = cancel
|
|
368
|
+
|
|
369
|
+
with self.lock:
|
|
370
|
+
previous_thread = self._current_thread
|
|
371
|
+
self._current_thread = current_thread = threading.Thread(
|
|
372
|
+
target=lambda: self._run(_last_finished_event, previous_thread, cancel_event, args, kwargs), daemon=False
|
|
373
|
+
)
|
|
374
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
|
|
375
|
+
current_thread.start()
|
|
376
|
+
|
|
377
|
+
def is_current(self):
|
|
378
|
+
cancel_event = getattr(self._local, "cancel_event", None)
|
|
379
|
+
if cancel_event is not None and cancel_event.is_set():
|
|
380
|
+
return False
|
|
381
|
+
return self._current_thread == threading.current_thread()
|
|
382
|
+
|
|
383
|
+
def _run(self, _last_finished_event, previous_thread: Optional[threading.Thread], cancel_event, args, kwargs) -> None:
|
|
384
|
+
# use_thread has this as default, which can make code run 10x slower
|
|
385
|
+
self._start_event.wait()
|
|
386
|
+
intrusive_cancel = False
|
|
387
|
+
wait_on_previous = False
|
|
388
|
+
self._local.cancel_event = cancel_event
|
|
389
|
+
|
|
390
|
+
def runner():
|
|
391
|
+
if wait_on_previous:
|
|
392
|
+
if previous_thread and previous_thread.is_alive():
|
|
393
|
+
if self.is_current():
|
|
394
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.WAITING)
|
|
395
|
+
# don't start before the previous is stopped
|
|
396
|
+
try:
|
|
397
|
+
previous_thread.join()
|
|
398
|
+
except: # noqa
|
|
399
|
+
pass
|
|
400
|
+
if self.is_current():
|
|
401
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.RUNNING)
|
|
402
|
+
else:
|
|
403
|
+
# early stop
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
callback = self.function
|
|
407
|
+
try:
|
|
408
|
+
guard = solara.util.cancel_guard(cancel_event) if intrusive_cancel else solara.util.nullcontext()
|
|
409
|
+
try:
|
|
410
|
+
# we only use the cancel_guard context manager around
|
|
411
|
+
# the function calls to f. We don't want to guard around
|
|
412
|
+
# a call to react, since that might slow down rendering
|
|
413
|
+
# during rendering
|
|
414
|
+
with guard:
|
|
415
|
+
if self.is_current():
|
|
416
|
+
value = callback(*args, **kwargs)
|
|
417
|
+
if inspect.isgenerator(value):
|
|
418
|
+
generator = value
|
|
419
|
+
self._last_value = None
|
|
420
|
+
while True:
|
|
421
|
+
try:
|
|
422
|
+
with guard:
|
|
423
|
+
self._last_value = value = next(generator)
|
|
424
|
+
if self.is_current():
|
|
425
|
+
self._result.value = TaskResult[R](latest=value, value=value, _state=TaskState.RUNNING, progress=self._last_progress)
|
|
426
|
+
except StopIteration:
|
|
427
|
+
break
|
|
428
|
+
if self.is_current():
|
|
429
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.FINISHED, progress=self._last_progress)
|
|
430
|
+
else:
|
|
431
|
+
self._last_value = value
|
|
432
|
+
if self.is_current():
|
|
433
|
+
self._result.value = TaskResult[R](latest=value, value=value, _state=TaskState.FINISHED, progress=self._last_progress)
|
|
434
|
+
except Exception as e:
|
|
435
|
+
if self.is_current():
|
|
436
|
+
logger.exception(e)
|
|
437
|
+
self._last_value = None
|
|
438
|
+
self._result.value = TaskResult[R](latest=self._last_value, exception=e, _state=TaskState.ERROR)
|
|
439
|
+
return
|
|
440
|
+
except solara.util.CancelledError:
|
|
441
|
+
pass
|
|
442
|
+
# this means this thread is cancelled not be request, but because
|
|
443
|
+
# a new thread is running, we can ignore this
|
|
444
|
+
finally:
|
|
445
|
+
if self._current_thread == threading.current_thread():
|
|
446
|
+
self.running_thread = None
|
|
447
|
+
logger.info("thread done!")
|
|
448
|
+
if cancel_event.is_set():
|
|
449
|
+
self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
|
|
450
|
+
_last_finished_event.set()
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
runner()
|
|
454
|
+
except Exception:
|
|
455
|
+
logger.exception("error running in thread")
|
|
456
|
+
raise
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# TODO: Not sure if we want to use this, or have all local variables in Task subclasses be reactive vars
|
|
460
|
+
class Proxy:
|
|
461
|
+
def __init__(self, factory):
|
|
462
|
+
self._instance = Singleton(factory)
|
|
463
|
+
|
|
464
|
+
def __getattr__(self, name):
|
|
465
|
+
return getattr(self._instance.value, name)
|
|
466
|
+
|
|
467
|
+
def __setattr__(self, name, value):
|
|
468
|
+
if name == "_instance":
|
|
469
|
+
super().__setattr__(name, value)
|
|
470
|
+
else:
|
|
471
|
+
setattr(self._instance.value, name, value)
|
|
472
|
+
|
|
473
|
+
def __call__(self, *args, **kwargs):
|
|
474
|
+
return self._instance.value(*args, **kwargs)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@overload
|
|
478
|
+
def task(
|
|
479
|
+
f: None = None,
|
|
480
|
+
*,
|
|
481
|
+
prefer_threaded: bool = ...,
|
|
482
|
+
) -> Callable[[Callable[P, R]], Task[P, R]]: ...
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@overload
|
|
486
|
+
def task(
|
|
487
|
+
f: Callable[P, Union[Coroutine[Any, Any, R], R]],
|
|
488
|
+
*,
|
|
489
|
+
prefer_threaded: bool = ...,
|
|
490
|
+
) -> Task[P, R]: ...
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def task(
|
|
494
|
+
f: Union[None, Callable[P, Union[Coroutine[Any, Any, R], R]]] = None,
|
|
495
|
+
*,
|
|
496
|
+
prefer_threaded: bool = True,
|
|
497
|
+
) -> Union[Callable[[Callable[P, R]], Task[P, R]], Task[P, R]]:
|
|
498
|
+
"""Decorator to turn a function or coroutine function into a task.
|
|
499
|
+
|
|
500
|
+
Lets you run code in the background, with the UI available to the user. This is useful for long running tasks, like downloading data or processing data.
|
|
501
|
+
|
|
502
|
+
The task decorator turns a function or coroutine function (`async def foo(...)` - here foo is called a coroutine function) into a task object.
|
|
503
|
+
A task is a callable that will run the function or coroutine function in a separate thread
|
|
504
|
+
Note that on platforms where threads are supported, asyncio tasks will still be executed in threads (unless the
|
|
505
|
+
`prefer_thread=False` argument is passed). Because a coroutine function might still call long running blocking code.
|
|
506
|
+
Running the asyncio task in a thread will still result in a responsive UI when executed in a separate thread.
|
|
507
|
+
|
|
508
|
+
The task object will execute the function only once per virtual kernel and will only store one result per virtual kernel.
|
|
509
|
+
When called multiple times, the previously started thread or asyncio task result will be ignored.
|
|
510
|
+
|
|
511
|
+
A running thread or asyncio task can check if it is still the current task by calling `task.is_current()`.
|
|
512
|
+
If `task.is_current()` returns False, the task should stop running and return early.
|
|
513
|
+
|
|
514
|
+
The return value of the function is available as the `.value` reactive property on the task object, meaning that if a
|
|
515
|
+
component accesses it, the component will automatically re-run when the value changes, like a [reactive variable](/api/reactive).
|
|
516
|
+
|
|
517
|
+
## Task object
|
|
518
|
+
|
|
519
|
+
The task object has the following attributes/values which are all reactive:
|
|
520
|
+
|
|
521
|
+
* `.value`: Contains the return value of the function (Only valid if `.finished` is true, else None).
|
|
522
|
+
* `.exception`: The exception raised by the function, if any (Only valid if `.error` is true, else None).
|
|
523
|
+
* `.latest` The last return value of the function, useful for showing out-of-date data while the task is running.
|
|
524
|
+
* `.progress` A readable and writable reactive property which can be used for communicating progress to the user.
|
|
525
|
+
|
|
526
|
+
The state of the task can be queried with the following attributes, which are all reactive:
|
|
527
|
+
|
|
528
|
+
* `.not_called`: True if the task has not been called yet.
|
|
529
|
+
* `.pending`: True if the task is asked to run, but did not finish yet, did not error and did not get cancelled.
|
|
530
|
+
When true, often a loading or busy indicator is shown to the user.
|
|
531
|
+
* `.finished`: True if the task has finished running. The result is available in the `.value` attribute as
|
|
532
|
+
well as the `.latest` attribute.
|
|
533
|
+
* `.cancelled`: True if the task was cancelled (by calling `.cancel()`).
|
|
534
|
+
* `.error`: True if the function has raised an exception.
|
|
535
|
+
|
|
536
|
+
The following methods are available:
|
|
537
|
+
|
|
538
|
+
* `(*args, **kwargs)` : Call the task with the given arguments and keyword arguments. The task will only run once per virtual kernel.
|
|
539
|
+
* `.cancel()`: Cancels the task.
|
|
540
|
+
* `is_current()`: Returns True if the task is still the current task, and should continue running.
|
|
541
|
+
Will return False when a new call to the task is made, and this function is being called from the the
|
|
542
|
+
previous thread or asyncio.
|
|
543
|
+
|
|
544
|
+
## State diagram
|
|
545
|
+
|
|
546
|
+
The following state diagram shows the possible states of a task and how
|
|
547
|
+
each state transitions to another state.
|
|
548
|
+
|
|
549
|
+
```mermaid
|
|
550
|
+
stateDiagram-v2
|
|
551
|
+
not_called --> pending: task()
|
|
552
|
+
pending --> finished
|
|
553
|
+
pending --> error: exception
|
|
554
|
+
pending --> pending: task()
|
|
555
|
+
pending --> cancelled: task.cancel()
|
|
556
|
+
finished --> pending: task()
|
|
557
|
+
error --> pending: task()
|
|
558
|
+
cancelled --> pending: task()
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Note that calling the task (as indicated by `task()`) can be done from any state.
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
## Example
|
|
565
|
+
|
|
566
|
+
### Async task
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
```solara
|
|
570
|
+
import asyncio
|
|
571
|
+
import solara
|
|
572
|
+
from solara.lab import task
|
|
573
|
+
|
|
574
|
+
@task
|
|
575
|
+
async def fetch_data():
|
|
576
|
+
await asyncio.sleep(2)
|
|
577
|
+
return "The answer is 42"
|
|
578
|
+
|
|
579
|
+
@solara.component
|
|
580
|
+
def Page():
|
|
581
|
+
solara.Button("Fetch data", on_click=fetch_data)
|
|
582
|
+
solara.ProgressLinear(fetch_data.pending)
|
|
583
|
+
|
|
584
|
+
if fetch_data.finished:
|
|
585
|
+
solara.Text(fetch_data.value)
|
|
586
|
+
elif fetch_data.not_called:
|
|
587
|
+
solara.Text("Click the button to fetch data")
|
|
588
|
+
# Optional state check
|
|
589
|
+
# elif fetch_data.cancelled:
|
|
590
|
+
# solara.Text("Cancelled the fetch")
|
|
591
|
+
# elif fetch_data.error:
|
|
592
|
+
# solara.Error(str(fetch_data.exception))
|
|
593
|
+
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Threaded task
|
|
597
|
+
|
|
598
|
+
```solara
|
|
599
|
+
import time
|
|
600
|
+
import solara
|
|
601
|
+
from solara.lab import task
|
|
602
|
+
|
|
603
|
+
@task
|
|
604
|
+
def fetch_data():
|
|
605
|
+
time.sleep(2)
|
|
606
|
+
return "The answer is 42"
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@solara.component
|
|
610
|
+
def Page():
|
|
611
|
+
solara.Button("Fetch data", on_click=fetch_data)
|
|
612
|
+
solara.ProgressLinear(fetch_data.pending)
|
|
613
|
+
|
|
614
|
+
if fetch_data.finished:
|
|
615
|
+
solara.Text(fetch_data.value)
|
|
616
|
+
elif fetch_data.not_called:
|
|
617
|
+
solara.Text("Click the button to fetch data")
|
|
618
|
+
# Optional state check
|
|
619
|
+
# elif fetch_data.cancelled:
|
|
620
|
+
# solara.Text("Cancelled the fetch")
|
|
621
|
+
# elif fetch_data.error:
|
|
622
|
+
# solara.Error(str(fetch_data.exception))
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Note that both examples are very similar. In the first example however, we wrap a coroutine function
|
|
626
|
+
which can use `asyncio.sleep`. In the second example, we use a regular function, which uses `time.sleep`.
|
|
627
|
+
If the coroutine function would use `time.sleep` in combination with `prefer_threaded=False`,
|
|
628
|
+
the UI would be unresponsive for 2 seconds.
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
### Showing a progress bar
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
Using the `.progress` attribute, you can show a progress bar to the user. This is useful for long running tasks
|
|
635
|
+
but requires a bit more work.
|
|
636
|
+
|
|
637
|
+
```solara
|
|
638
|
+
import time
|
|
639
|
+
import solara
|
|
640
|
+
from solara.lab import task
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@task
|
|
644
|
+
def my_calculation():
|
|
645
|
+
total = 0
|
|
646
|
+
for i in range(10):
|
|
647
|
+
my_calculation.progress = (i + 1) * 10.0
|
|
648
|
+
time.sleep(0.4)
|
|
649
|
+
if not my_calculation.is_current():
|
|
650
|
+
# a new call was made before this call was finished
|
|
651
|
+
return
|
|
652
|
+
total += i**2
|
|
653
|
+
return total
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@solara.component
|
|
657
|
+
def Page():
|
|
658
|
+
solara.Button("Run calculation", on_click=my_calculation)
|
|
659
|
+
solara.ProgressLinear(my_calculation.progress if my_calculation.pending else False)
|
|
660
|
+
|
|
661
|
+
if my_calculation.finished:
|
|
662
|
+
solara.Text(f"Calculation result: {my_calculation.value}")
|
|
663
|
+
elif my_calculation.not_called:
|
|
664
|
+
solara.Text("Click the button to fetch data")
|
|
665
|
+
# Optional state check
|
|
666
|
+
# elif my_calculation.cancelled:
|
|
667
|
+
# solara.Text("Cancelled the fetch")
|
|
668
|
+
# elif my_calculation.error:
|
|
669
|
+
# solara.Error(str(my_calculation.exception))
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Out-of-date data
|
|
673
|
+
|
|
674
|
+
```solara
|
|
675
|
+
import time
|
|
676
|
+
import solara
|
|
677
|
+
from solara.lab import task
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
@task
|
|
681
|
+
def my_calculation():
|
|
682
|
+
total = 0
|
|
683
|
+
for i in range(10):
|
|
684
|
+
time.sleep(0.1)
|
|
685
|
+
total += i**2
|
|
686
|
+
return total
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@solara.component
|
|
690
|
+
def Page():
|
|
691
|
+
solara.ProgressLinear(my_calculation.pending)
|
|
692
|
+
solara.Button("Run simulation", on_click=my_calculation)
|
|
693
|
+
print(my_calculation.pending, my_calculation.value)
|
|
694
|
+
|
|
695
|
+
if my_calculation.finished:
|
|
696
|
+
solara.Text(f"Simulation result: {my_calculation.value}")
|
|
697
|
+
if my_calculation.pending and my_calculation.latest:
|
|
698
|
+
solara.Text(f"Simulation previous result: {my_calculation.latest}", style={"opacity": ".3"})
|
|
699
|
+
elif my_calculation.not_called:
|
|
700
|
+
solara.Text("Click the button to fetch data")
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
## Arguments
|
|
704
|
+
|
|
705
|
+
- `f`: Function to turn into task or None
|
|
706
|
+
- `prefer_threaded` - bool: Will run coroutine functions as a task in a thread when threads are available.
|
|
707
|
+
This ensures that even when a coroutine functions calls a blocking function the UI is still responsive.
|
|
708
|
+
On platform where threads are not supported (like Pyodide / WASM / Emscripten / PyScript), a coroutine
|
|
709
|
+
function will always run in the current event loop.
|
|
710
|
+
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
"""
|
|
714
|
+
|
|
715
|
+
def wrapper(f: Union[None, Callable[P, Union[Coroutine[Any, Any, R], R]]]) -> Task[P, R]:
|
|
716
|
+
# we use wraps to make the key of the reactive variable more unique
|
|
717
|
+
# and less likely to mixup during hot reloads
|
|
718
|
+
@functools.wraps(f) # type: ignore
|
|
719
|
+
def create_task():
|
|
720
|
+
if inspect.iscoroutinefunction(f):
|
|
721
|
+
return TaskAsyncio[P, R](prefer_threaded and has_threads, f, key=solara.toestand._create_key_callable(create_task))
|
|
722
|
+
else:
|
|
723
|
+
return TaskThreaded[P, R](cast(Callable[P, R], f), key=solara.toestand._create_key_callable(create_task))
|
|
724
|
+
|
|
725
|
+
return cast(Task[P, R], Proxy(create_task))
|
|
726
|
+
|
|
727
|
+
if f is None:
|
|
728
|
+
return wrapper
|
|
729
|
+
else:
|
|
730
|
+
return wrapper(f)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
# Quotes around Task[...] are needed in Python <= 3.9, since ParamSpec doesn't properly support non-type arguments
|
|
734
|
+
# i.e. [] is taken as a value instead of a type
|
|
735
|
+
# See https://github.com/python/typing_extensions/issues/126 and related issues
|
|
736
|
+
@overload
|
|
737
|
+
def use_task(
|
|
738
|
+
f: None = None,
|
|
739
|
+
*,
|
|
740
|
+
dependencies: Literal[None] = ...,
|
|
741
|
+
raise_error=...,
|
|
742
|
+
prefer_threaded=...,
|
|
743
|
+
) -> Callable[[Callable[[], R]], "Task[[], R]"]: ...
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
@overload
|
|
747
|
+
def use_task(
|
|
748
|
+
f: Callable[[], R],
|
|
749
|
+
*,
|
|
750
|
+
dependencies: Literal[None] = ...,
|
|
751
|
+
raise_error=...,
|
|
752
|
+
prefer_threaded=...,
|
|
753
|
+
) -> "Task[[], R]": ...
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
@overload
|
|
757
|
+
def use_task(
|
|
758
|
+
f: None = None,
|
|
759
|
+
*,
|
|
760
|
+
dependencies: List = ...,
|
|
761
|
+
raise_error=...,
|
|
762
|
+
prefer_threaded=...,
|
|
763
|
+
) -> Callable[[Callable[[], R]], "Task[[], R]"]: ...
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
@overload
|
|
767
|
+
def use_task(
|
|
768
|
+
f: Callable[[], R],
|
|
769
|
+
*,
|
|
770
|
+
dependencies: List = ...,
|
|
771
|
+
raise_error=...,
|
|
772
|
+
prefer_threaded=...,
|
|
773
|
+
) -> "Task[[], R]": ...
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def use_task(
|
|
777
|
+
f: Union[None, Callable[[], R]] = None,
|
|
778
|
+
*,
|
|
779
|
+
dependencies: Union[None, List] = [],
|
|
780
|
+
raise_error=True,
|
|
781
|
+
prefer_threaded=True,
|
|
782
|
+
) -> Union[Callable[[Callable[[], R]], "Task[[], R]"], "Task[[], R]"]:
|
|
783
|
+
"""A hook that runs a function or coroutine function as a task and returns the result.
|
|
784
|
+
|
|
785
|
+
Allows you to run code in the background, with the UI available to the user. This is useful for long running tasks,
|
|
786
|
+
like downloading data or processing data.
|
|
787
|
+
|
|
788
|
+
Unlike with the [`@task`](/api/task) decorator, the result is not globally shared, but only available to the component that called `use_task`.
|
|
789
|
+
|
|
790
|
+
Note that unlike the [`@task`](/api/task) decorator, the task is invoked immediately when dependencies are passed. To prevent this, pass `dependencies=None`.
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
## Example
|
|
794
|
+
|
|
795
|
+
### Running in a thread
|
|
796
|
+
|
|
797
|
+
```solara
|
|
798
|
+
import time
|
|
799
|
+
import solara
|
|
800
|
+
from solara.lab import use_task, Task
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
@solara.component
|
|
804
|
+
def Page():
|
|
805
|
+
number = solara.use_reactive(4)
|
|
806
|
+
|
|
807
|
+
def square():
|
|
808
|
+
time.sleep(1)
|
|
809
|
+
return number.value**2
|
|
810
|
+
|
|
811
|
+
result: Task[int] = use_task(square, dependencies=[number.value])
|
|
812
|
+
|
|
813
|
+
solara.InputInt("Square", value=number, continuous_update=True)
|
|
814
|
+
if result.finished:
|
|
815
|
+
solara.Success(f"Square of {number} == {result.value}")
|
|
816
|
+
solara.ProgressLinear(result.pending)
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Running in an asyncio task
|
|
820
|
+
|
|
821
|
+
Note that the only difference is our function is now a coroutine function,
|
|
822
|
+
and we use `asyncio.sleep` instead of `time.sleep`.
|
|
823
|
+
|
|
824
|
+
```solara
|
|
825
|
+
import asyncio
|
|
826
|
+
import solara
|
|
827
|
+
from solara.lab import use_task, Task
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
@solara.component
|
|
831
|
+
def Page():
|
|
832
|
+
number = solara.use_reactive(4)
|
|
833
|
+
|
|
834
|
+
async def square():
|
|
835
|
+
await asyncio.sleep(1)
|
|
836
|
+
return number.value**2
|
|
837
|
+
|
|
838
|
+
result: Task[int] = use_task(square, dependencies=[number.value])
|
|
839
|
+
|
|
840
|
+
solara.InputInt("Square", value=number, continuous_update=True)
|
|
841
|
+
if result.finished:
|
|
842
|
+
solara.Success(f"Square of {number} == {result.value}")
|
|
843
|
+
solara.ProgressLinear(result.pending)
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
## Arguments
|
|
847
|
+
|
|
848
|
+
- `f`: The function or coroutine to run as a task.
|
|
849
|
+
- `dependencies`: A list of dependencies that will trigger a rerun of the task when changed, the task will run automatically execute when the `dependencies=None`
|
|
850
|
+
- `raise_error`: If true, an error in the task will be raised. If false, the error should be handled by the
|
|
851
|
+
user and is available in the `.exception` attribute of the task result object.
|
|
852
|
+
- `prefer_threaded` - bool: Will run coroutine functions as a task in a thread when threads are available.
|
|
853
|
+
This ensures that even when a coroutine functions calls a blocking function the UI is still responsive.
|
|
854
|
+
On platform where threads are not supported (like Pyodide / WASM / Emscripten / PyScript), a coroutine
|
|
855
|
+
function will always run in the current event loop.
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
"""
|
|
859
|
+
|
|
860
|
+
def wrapper(f):
|
|
861
|
+
def create_task() -> "Task[[], R]":
|
|
862
|
+
return task(f, prefer_threaded=prefer_threaded)
|
|
863
|
+
|
|
864
|
+
task_instance = solara.use_memo(create_task, dependencies=[])
|
|
865
|
+
# we always update the function so we do not have stale data in the function
|
|
866
|
+
task_instance.function = f # type: ignore
|
|
867
|
+
|
|
868
|
+
def _prestart():
|
|
869
|
+
if dependencies is not None:
|
|
870
|
+
# we do not want to be in a state of .finished when the dependencies change
|
|
871
|
+
# otherwise user code might render a stale value with the new dependencies
|
|
872
|
+
task_instance._prestart()
|
|
873
|
+
|
|
874
|
+
solara.use_memo(_prestart, dependencies=dependencies)
|
|
875
|
+
|
|
876
|
+
def run():
|
|
877
|
+
if dependencies is not None:
|
|
878
|
+
# but we only want to execute it as an effect, which makes
|
|
879
|
+
# sure that if the user assigns to a task object, the function f
|
|
880
|
+
# starts after the assignment is executed
|
|
881
|
+
task_instance()
|
|
882
|
+
|
|
883
|
+
solara.use_effect(run, dependencies=dependencies)
|
|
884
|
+
if raise_error:
|
|
885
|
+
if task_instance.error:
|
|
886
|
+
assert task_instance.exception is not None
|
|
887
|
+
raise task_instance.exception
|
|
888
|
+
return task_instance
|
|
889
|
+
|
|
890
|
+
if f is None:
|
|
891
|
+
return wrapper
|
|
892
|
+
else:
|
|
893
|
+
return wrapper(f)
|