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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="echarts" class="solara-echarts" v-bind="attributes"></div>
|
|
3
|
+
</template>
|
|
4
|
+
<script>
|
|
5
|
+
module.exports = {
|
|
6
|
+
mounted() {
|
|
7
|
+
const version = "5.4.0";
|
|
8
|
+
(async () => {
|
|
9
|
+
const echarts = (
|
|
10
|
+
await this.import([`${this.getCdn()}/echarts@${version}/dist/echarts.js`])
|
|
11
|
+
)[0];
|
|
12
|
+
this.echarts = echarts;
|
|
13
|
+
this.create();
|
|
14
|
+
})();
|
|
15
|
+
if(this.responsive){
|
|
16
|
+
this.resizeObserver = new ResizeObserver(entries => {
|
|
17
|
+
for (let entry of entries) {
|
|
18
|
+
if (entry.target === this.$refs.echarts) {
|
|
19
|
+
this.handleContainerResize();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
this.resizeObserver.observe(this.$refs.echarts);
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
beforeDestroy() {
|
|
27
|
+
if (this.resizeObserver) {
|
|
28
|
+
this.resizeObserver.unobserve(this.$refs.echarts);
|
|
29
|
+
this.resizeObserver.disconnect();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
watch: {
|
|
33
|
+
option() {
|
|
34
|
+
// notMerge, otherwise we're left with axes etc
|
|
35
|
+
// see https://echarts.apache.org/en/api.html#echartsInstance.setOption
|
|
36
|
+
this.chart.setOption(this.option, true);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
methods: {
|
|
40
|
+
create() {
|
|
41
|
+
this.chart = this.echarts.init(this.$refs.echarts);
|
|
42
|
+
Object.keys(this.maps).forEach((mapName) => {
|
|
43
|
+
this.echarts.registerMap(mapName, this.maps[mapName]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.chart.setOption(this.option, true);
|
|
47
|
+
const eventProps = [
|
|
48
|
+
"componentType",
|
|
49
|
+
"seriesType",
|
|
50
|
+
"seriesIndex",
|
|
51
|
+
"seriesName",
|
|
52
|
+
"name",
|
|
53
|
+
"dataIndex",
|
|
54
|
+
"data",
|
|
55
|
+
"dataType",
|
|
56
|
+
"value",
|
|
57
|
+
"color",
|
|
58
|
+
];
|
|
59
|
+
this.chart.on("click", (fullEvent) => {
|
|
60
|
+
const eventData = {};
|
|
61
|
+
eventProps.forEach((prop) => {
|
|
62
|
+
eventData[prop] = fullEvent[prop];
|
|
63
|
+
});
|
|
64
|
+
this.on_click(eventData);
|
|
65
|
+
});
|
|
66
|
+
this.chart.on("mouseover", (fullEvent) => {
|
|
67
|
+
const eventData = {};
|
|
68
|
+
eventProps.forEach((prop) => {
|
|
69
|
+
eventData[prop] = fullEvent[prop];
|
|
70
|
+
});
|
|
71
|
+
if (this.on_mouseover_enabled) this.on_mouseover(eventData);
|
|
72
|
+
});
|
|
73
|
+
this.chart.on("mouseout", (fullEvent) => {
|
|
74
|
+
const eventData = {};
|
|
75
|
+
eventProps.forEach((prop) => {
|
|
76
|
+
eventData[prop] = fullEvent[prop];
|
|
77
|
+
});
|
|
78
|
+
if (this.on_mouseout_enabled) this.on_mouseout(eventData);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
handleContainerResize() {
|
|
82
|
+
if (this.chart) {
|
|
83
|
+
this.chart.resize();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
import(deps) {
|
|
87
|
+
return this.loadRequire().then(() => {
|
|
88
|
+
if (window.jupyterVue) {
|
|
89
|
+
// in jupyterlab, we take Vue from ipyvue/jupyterVue
|
|
90
|
+
define("vue", [], () => window.jupyterVue.Vue);
|
|
91
|
+
} else {
|
|
92
|
+
define("vue", ["jupyter-vue"], (jupyterVue) => jupyterVue.Vue);
|
|
93
|
+
}
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
requirejs(deps, (...modules) => resolve(modules));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
loadRequire() {
|
|
100
|
+
/* Needed in lab */
|
|
101
|
+
if (window.requirejs) {
|
|
102
|
+
console.log("require found");
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
}
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const script = document.createElement("script");
|
|
107
|
+
script.src = `${this.getCdn()}/requirejs@2.3.6/require.js`;
|
|
108
|
+
script.onload = resolve;
|
|
109
|
+
script.onerror = reject;
|
|
110
|
+
document.head.appendChild(script);
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
getJupyterBaseUrl() {
|
|
114
|
+
// if base url is set, we use ./ for relative paths compared to the base url
|
|
115
|
+
if (document.getElementsByTagName("base").length) {
|
|
116
|
+
return "./";
|
|
117
|
+
}
|
|
118
|
+
const labConfigData = document.getElementById("jupyter-config-data");
|
|
119
|
+
if (labConfigData) {
|
|
120
|
+
/* lab and Voila */
|
|
121
|
+
return JSON.parse(labConfigData.textContent).baseUrl;
|
|
122
|
+
}
|
|
123
|
+
let base = document.body.dataset.baseUrl || document.baseURI;
|
|
124
|
+
if (!base.endsWith("/")) {
|
|
125
|
+
base += "/";
|
|
126
|
+
}
|
|
127
|
+
return base;
|
|
128
|
+
},
|
|
129
|
+
getCdn() {
|
|
130
|
+
return this.cdn || (window.solara ? window.solara.cdn : `${this.getJupyterBaseUrl()}_solara/cdn`);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<style id="solara-markdown-editor">
|
|
137
|
+
.solara-echarts {
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Any, Callable
|
|
2
|
+
|
|
3
|
+
import solara
|
|
4
|
+
import solara.widgets
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@solara.component
|
|
8
|
+
def FigureAltair(
|
|
9
|
+
chart,
|
|
10
|
+
on_click: Callable[[Any], None] = None,
|
|
11
|
+
on_hover: Callable[[Any], None] = None,
|
|
12
|
+
):
|
|
13
|
+
"""Renders an Altair chart using VegaLite.
|
|
14
|
+
|
|
15
|
+
See also [our altair example](/documentation/examples/libraries/altair).
|
|
16
|
+
|
|
17
|
+
## Arguments
|
|
18
|
+
|
|
19
|
+
- chart: Altair chart
|
|
20
|
+
- on_click: Callback function for click events.
|
|
21
|
+
- on_hover: Callback function for hover events.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
import altair as alt
|
|
25
|
+
|
|
26
|
+
with alt.renderers.enable("mimetype"):
|
|
27
|
+
bundle = chart._repr_mimebundle_()[0]
|
|
28
|
+
key4 = "application/vnd.vegalite.v4+json"
|
|
29
|
+
key5 = "application/vnd.vegalite.v5+json"
|
|
30
|
+
if key4 not in bundle and key5 not in bundle:
|
|
31
|
+
raise KeyError(f"{key4} and {key5} not in mimebundle:\n\n{bundle}")
|
|
32
|
+
spec = bundle.get(key5, bundle.get(key4))
|
|
33
|
+
return solara.widgets.VegaLite.element(
|
|
34
|
+
spec=spec, on_click=on_click, listen_to_click=on_click is not None, on_hover=on_hover, listen_to_hover=on_hover is not None
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# alias for backward compatibility
|
|
39
|
+
AltairChart = FigureAltair
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from os.path import isfile, join
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Callable, Dict, List, Optional, Union, cast
|
|
5
|
+
|
|
6
|
+
import humanize
|
|
7
|
+
import ipyvuetify as vy
|
|
8
|
+
import traitlets
|
|
9
|
+
|
|
10
|
+
import solara
|
|
11
|
+
from solara.components import Div
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_dir(path, filter: Callable[[Path], bool] = lambda x: True, directory_first: bool = False) -> List[dict]:
|
|
15
|
+
def mk_item(n):
|
|
16
|
+
full_path = join(path, n)
|
|
17
|
+
is_file = isfile(full_path)
|
|
18
|
+
return {"name": n, "is_file": is_file, "size": humanize.naturalsize(os.stat(full_path).st_size) if is_file else None}
|
|
19
|
+
|
|
20
|
+
files = [mk_item(k) for k in os.listdir(path) if not k.startswith(".") if filter(Path(path) / k)]
|
|
21
|
+
sorted_files = sorted(files, key=lambda item: (item["is_file"] == directory_first, item["name"].lower()))
|
|
22
|
+
|
|
23
|
+
return sorted_files
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FileListWidget(vy.VuetifyTemplate):
|
|
27
|
+
template_file = (__file__, "file_list_widget.vue")
|
|
28
|
+
|
|
29
|
+
files = traitlets.List(cast(List[Dict], [])).tag(sync=True)
|
|
30
|
+
clicked = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
|
|
31
|
+
double_clicked = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
|
|
32
|
+
scroll_pos = traitlets.Int(allow_none=True).tag(sync=True)
|
|
33
|
+
|
|
34
|
+
def test_click(self, path: Union[Path, str], double_click=False):
|
|
35
|
+
"""Simulate a click or double click at the Python side"""
|
|
36
|
+
matches = [k for k in self.files if k["name"] == str(path)]
|
|
37
|
+
if len(matches) == 0:
|
|
38
|
+
names = [k["name"] for k in self.files]
|
|
39
|
+
raise NameError(f"Could not find {path}, possible filenames: {names}")
|
|
40
|
+
item = matches[0]
|
|
41
|
+
if double_click:
|
|
42
|
+
self.double_clicked = item
|
|
43
|
+
else:
|
|
44
|
+
self.clicked = item
|
|
45
|
+
|
|
46
|
+
def __contains__(self, name):
|
|
47
|
+
"""Test if filename/directory name is in the current directory."""
|
|
48
|
+
return name in [k["name"] for k in self.files]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@solara.component
|
|
52
|
+
def FileBrowser(
|
|
53
|
+
directory: Union[None, str, Path, solara.Reactive[Path]] = None,
|
|
54
|
+
on_directory_change: Optional[Callable[[Path], None]] = None,
|
|
55
|
+
on_path_select: Optional[Callable[[Optional[Path]], None]] = None,
|
|
56
|
+
on_file_open: Optional[Callable[[Path], None]] = None,
|
|
57
|
+
filter: Callable[[Path], bool] = lambda x: True,
|
|
58
|
+
directory_first: bool = False,
|
|
59
|
+
on_file_name: Optional[Callable[[str], None]] = None,
|
|
60
|
+
start_directory=None,
|
|
61
|
+
can_select=False,
|
|
62
|
+
):
|
|
63
|
+
"""File/directory browser at the server side.
|
|
64
|
+
|
|
65
|
+
There are two modes possible
|
|
66
|
+
|
|
67
|
+
* `can_select=False`
|
|
68
|
+
* `on_file_open`: Triggered when **single** clicking a file or directory.
|
|
69
|
+
* `on_path_select`: Never triggered
|
|
70
|
+
* `on_directory_change`: Triggered when clicking a directory
|
|
71
|
+
* `can_select=True`
|
|
72
|
+
* `on_file_open`: Triggered when **double** clicking a file or directory.
|
|
73
|
+
* `on_path_select`: Triggered when clicking a file or directory
|
|
74
|
+
* `on_directory_change`: Triggered when double clicking a directory
|
|
75
|
+
|
|
76
|
+
## Arguments
|
|
77
|
+
|
|
78
|
+
* `directory`: The directory to start in. If `None` the current working directory is used.
|
|
79
|
+
* `on_directory_change`: Depends on mode, see above.
|
|
80
|
+
* `on_path_select`: Depends on mode, see above.
|
|
81
|
+
* `on_file_open`: Depends on mode, see above.
|
|
82
|
+
* `filter`: A function that takes a `Path` and returns `True` if the file/directory should be shown.
|
|
83
|
+
* `directory_first`: If `True` directories are shown before files. Default: `False`.
|
|
84
|
+
* `on_file_name`: (deprecated) Use on_file_open instead.
|
|
85
|
+
* `start_directory`: (deprecated) Use directory instead.
|
|
86
|
+
"""
|
|
87
|
+
if start_directory is not None:
|
|
88
|
+
directory = start_directory # pragma: no cover
|
|
89
|
+
if directory is None:
|
|
90
|
+
directory = os.getcwd() # pragma: no cover
|
|
91
|
+
if isinstance(directory, str):
|
|
92
|
+
directory = Path(directory)
|
|
93
|
+
current_dir = solara.use_reactive(directory)
|
|
94
|
+
selected, set_selected = solara.use_state(None)
|
|
95
|
+
double_clicked, set_double_clicked = solara.use_state(None)
|
|
96
|
+
warning, set_warning = solara.use_state(cast(Optional[str], None))
|
|
97
|
+
scroll_pos_stack, set_scroll_pos_stack = solara.use_state(cast(List[int], []))
|
|
98
|
+
scroll_pos, set_scroll_pos = solara.use_state(0)
|
|
99
|
+
selected, set_selected = solara.use_state(None)
|
|
100
|
+
|
|
101
|
+
def change_dir(new_dir: Path):
|
|
102
|
+
if os.access(new_dir, os.R_OK):
|
|
103
|
+
current_dir.value = new_dir
|
|
104
|
+
if on_directory_change:
|
|
105
|
+
on_directory_change(new_dir)
|
|
106
|
+
set_warning(None)
|
|
107
|
+
return True
|
|
108
|
+
else:
|
|
109
|
+
set_warning(f"[no read access to {new_dir}]")
|
|
110
|
+
|
|
111
|
+
def on_item(item, double_click):
|
|
112
|
+
if item is None:
|
|
113
|
+
if can_select and on_path_select:
|
|
114
|
+
on_path_select(None)
|
|
115
|
+
return
|
|
116
|
+
if item["name"] == "..":
|
|
117
|
+
new_dir = current_dir.value.parent
|
|
118
|
+
action_change_directory = (can_select and double_click) or (not can_select and not double_click)
|
|
119
|
+
if action_change_directory and change_dir(new_dir):
|
|
120
|
+
if scroll_pos_stack:
|
|
121
|
+
last_pos = scroll_pos_stack[-1]
|
|
122
|
+
set_scroll_pos_stack(scroll_pos_stack[:-1])
|
|
123
|
+
set_scroll_pos(last_pos)
|
|
124
|
+
set_selected(None)
|
|
125
|
+
set_double_clicked(None)
|
|
126
|
+
if on_path_select and can_select:
|
|
127
|
+
on_path_select(None)
|
|
128
|
+
if can_select and not double_click:
|
|
129
|
+
if on_path_select:
|
|
130
|
+
on_path_select(new_dir)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
path = current_dir.value / item["name"]
|
|
134
|
+
is_file = item["is_file"]
|
|
135
|
+
if (can_select and double_click) or (not can_select and not double_click):
|
|
136
|
+
if is_file:
|
|
137
|
+
if on_file_open:
|
|
138
|
+
on_file_open(path)
|
|
139
|
+
if on_file_name is not None:
|
|
140
|
+
on_file_name(str(path))
|
|
141
|
+
else:
|
|
142
|
+
if change_dir(path):
|
|
143
|
+
set_scroll_pos_stack(scroll_pos_stack + [scroll_pos])
|
|
144
|
+
set_scroll_pos(0)
|
|
145
|
+
set_selected(None)
|
|
146
|
+
set_double_clicked(None)
|
|
147
|
+
if on_path_select and can_select:
|
|
148
|
+
on_path_select(None)
|
|
149
|
+
elif can_select and not double_click:
|
|
150
|
+
if on_path_select:
|
|
151
|
+
on_path_select(path)
|
|
152
|
+
else: # not can_select and double_click is ignored
|
|
153
|
+
raise RuntimeError("Combination should not happen") # pragma: no cover
|
|
154
|
+
|
|
155
|
+
def on_click(item):
|
|
156
|
+
set_selected(item)
|
|
157
|
+
on_item(item, False)
|
|
158
|
+
|
|
159
|
+
def on_double_click(item):
|
|
160
|
+
set_double_clicked(item)
|
|
161
|
+
if can_select:
|
|
162
|
+
on_item(item, True)
|
|
163
|
+
# otherwise we can ignore it, single click will handle it
|
|
164
|
+
|
|
165
|
+
files = [{"name": "..", "is_file": False}] + list_dir(current_dir.value, filter=filter, directory_first=directory_first)
|
|
166
|
+
with Div(class_="solara-file-browser") as main:
|
|
167
|
+
Div(children=[str(current_dir.value)])
|
|
168
|
+
FileListWidget.element(
|
|
169
|
+
files=files,
|
|
170
|
+
selected=selected,
|
|
171
|
+
clicked=selected,
|
|
172
|
+
on_clicked=on_click,
|
|
173
|
+
double_clicked=double_clicked,
|
|
174
|
+
on_double_clicked=on_double_click,
|
|
175
|
+
scroll_pos=scroll_pos,
|
|
176
|
+
on_scroll_pos=set_scroll_pos,
|
|
177
|
+
).key("FileList")
|
|
178
|
+
if warning:
|
|
179
|
+
Div(style_="font-weight: bold; color: red", children=[warning])
|
|
180
|
+
|
|
181
|
+
return main
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import BinaryIO, Callable, List, Optional, Union, cast
|
|
3
|
+
|
|
4
|
+
import ipyvuetify as vy
|
|
5
|
+
import ipywidgets as widgets
|
|
6
|
+
import traitlets
|
|
7
|
+
|
|
8
|
+
import solara
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FileDownloadWidget(vy.VuetifyTemplate):
|
|
12
|
+
template_file = (__file__, "download.vue")
|
|
13
|
+
children = traitlets.List(cast(List[solara.Element], [])).tag(sync=True, **widgets.widget_serialization)
|
|
14
|
+
filename = traitlets.Unicode().tag(sync=True)
|
|
15
|
+
bytes = traitlets.Bytes(None, allow_none=True).tag(sync=True)
|
|
16
|
+
mime_type = traitlets.Unicode("application/octet-stream").tag(sync=True)
|
|
17
|
+
request_download = traitlets.Bool(False).tag(sync=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@solara.component
|
|
21
|
+
def FileDownload(
|
|
22
|
+
data: Union[str, bytes, BinaryIO, Callable[[], Union[str, bytes, BinaryIO]]],
|
|
23
|
+
filename: Optional[str] = None,
|
|
24
|
+
label: Optional[str] = None,
|
|
25
|
+
icon_name: Optional[str] = "mdi-cloud-download-outline",
|
|
26
|
+
close_file: bool = True,
|
|
27
|
+
mime_type: str = "application/octet-stream",
|
|
28
|
+
string_encoding: str = "utf-8",
|
|
29
|
+
children=[],
|
|
30
|
+
):
|
|
31
|
+
"""Download a file or data.
|
|
32
|
+
|
|
33
|
+
## Simple usage
|
|
34
|
+
|
|
35
|
+
By default, if no children are provided, a button is created with the label "Download: {filename}".
|
|
36
|
+
|
|
37
|
+
```solara
|
|
38
|
+
import solara
|
|
39
|
+
|
|
40
|
+
data = "This is the content of the file"
|
|
41
|
+
|
|
42
|
+
@solara.component
|
|
43
|
+
def Page():
|
|
44
|
+
solara.FileDownload(data, filename="solara-download.txt", label="Download file")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Advanced usage
|
|
48
|
+
|
|
49
|
+
If children are provided, they are displayed instead of the button. The children can be any solara component,
|
|
50
|
+
including a button, markdown text, or an image.
|
|
51
|
+
|
|
52
|
+
```solara
|
|
53
|
+
import solara
|
|
54
|
+
|
|
55
|
+
data = "This is the content of the file"
|
|
56
|
+
|
|
57
|
+
@solara.component
|
|
58
|
+
def Page():
|
|
59
|
+
with solara.FileDownload(data, "solara-download-2.txt"):
|
|
60
|
+
solara.Markdown("Any text, or even an image")
|
|
61
|
+
solara.Image("https://solara.dev/static/public/beach.jpeg", width="200px")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Custom button
|
|
65
|
+
|
|
66
|
+
If children are provided, they are displayed instead of the button. The children can be any solara component,
|
|
67
|
+
including a button, markdown text, or an image.
|
|
68
|
+
|
|
69
|
+
```solara
|
|
70
|
+
import solara
|
|
71
|
+
|
|
72
|
+
data = "This is the content of the file"
|
|
73
|
+
|
|
74
|
+
@solara.component
|
|
75
|
+
def Page():
|
|
76
|
+
with solara.FileDownload(data, "solara-download-2.txt"):
|
|
77
|
+
solara.Button("Custom download button", icon_name="mdi-cloud-download-outline", color="primary")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage with file
|
|
81
|
+
|
|
82
|
+
A file object can be used as data. The file will be closed after downloading by default.
|
|
83
|
+
|
|
84
|
+
```solara
|
|
85
|
+
import solara
|
|
86
|
+
import pandas as pd
|
|
87
|
+
|
|
88
|
+
df = pd.DataFrame({"id": [1, 2, 3], "name": ["John", "Mary", "Bob"]})
|
|
89
|
+
|
|
90
|
+
@solara.component
|
|
91
|
+
def Page():
|
|
92
|
+
file_object = df.to_csv(index=False)
|
|
93
|
+
solara.FileDownload(file_object, "users.csv", mime_type="application/vnd.ms-excel")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If a file like object is used, we try to base the filename on the file object.
|
|
97
|
+
```solara
|
|
98
|
+
import solara
|
|
99
|
+
import solara.website.pages
|
|
100
|
+
import os
|
|
101
|
+
|
|
102
|
+
filename = os.path.dirname(solara.website.__file__) + "/public/beach.jpeg"
|
|
103
|
+
|
|
104
|
+
@solara.component
|
|
105
|
+
def Page():
|
|
106
|
+
# only open the file once by using use_memo
|
|
107
|
+
file_object = solara.use_memo(lambda: open(filename, "rb"), [])
|
|
108
|
+
# no filename is provided, but we can extract it from the file object
|
|
109
|
+
solara.FileDownload(file_object, mime_type="image/jpeg", close_file=False)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Lazy reading
|
|
113
|
+
|
|
114
|
+
Not only is the data lazily uploaded to the browser, but also the data is only read when the download is requested.
|
|
115
|
+
This happens for files by default, but can also be used by passing in a callback function.
|
|
116
|
+
|
|
117
|
+
```solara
|
|
118
|
+
import solara
|
|
119
|
+
import time
|
|
120
|
+
|
|
121
|
+
@solara.component
|
|
122
|
+
def Page():
|
|
123
|
+
def get_data():
|
|
124
|
+
# I run in a thread, so I can do some heavy processing
|
|
125
|
+
time.sleep(3)
|
|
126
|
+
# I only get called when the download is requested
|
|
127
|
+
return "This is the content of the file"
|
|
128
|
+
solara.FileDownload(get_data, "solara-lazy-download.txt")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Arguments
|
|
132
|
+
|
|
133
|
+
* `data`: The data to download. Can be a string, bytes, or a file like object, or a function that returns one of these.
|
|
134
|
+
* `filename`: The name of the file the user will see as default when downloading (default name is "solara-download.dat").
|
|
135
|
+
If a file object is provided, the filename will be extracted from the file object if possible.
|
|
136
|
+
* `label`: The label of the button. If not provided, the label will be "Download: {filename}".
|
|
137
|
+
- `icon_name`: The name of the icon to display on the button ([Overview of available icons](https://pictogrammers.github.io/@mdi/font/4.9.95/)).
|
|
138
|
+
* `close_file`: If a file object is provided, close the file after downloading (default True).
|
|
139
|
+
* `mime_type`: The mime type of the file. If not provided, the mime type will be "application/octet-stream",
|
|
140
|
+
For instance setting it to "application/vnd.ms-excel" will allow the user OS to directly open the
|
|
141
|
+
file into Excel.
|
|
142
|
+
* `string_encoding`: The encoding to use when converting a string to bytes (default "utf-8").
|
|
143
|
+
|
|
144
|
+
## Note on file size
|
|
145
|
+
|
|
146
|
+
Note that the data will be kept in memory when downloading.
|
|
147
|
+
If the file is large (>10 MB), and when using [Solara server](/documentation/advanced/understanding/solara-server), we recommend using the
|
|
148
|
+
[static files directory](/documentation/advanced/reference/static-files) instead.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
request_download, set_request_download = solara.use_state(False)
|
|
152
|
+
|
|
153
|
+
# if the data changes, we 'reset'
|
|
154
|
+
def reset():
|
|
155
|
+
nonlocal request_download
|
|
156
|
+
request_download = False
|
|
157
|
+
set_request_download(False)
|
|
158
|
+
|
|
159
|
+
solara.use_memo(reset, [data])
|
|
160
|
+
|
|
161
|
+
# we only upload to the frontend if clicked
|
|
162
|
+
def get_data() -> Optional[bytes]:
|
|
163
|
+
if request_download:
|
|
164
|
+
if callable(data):
|
|
165
|
+
data_non_lazy = data()
|
|
166
|
+
else:
|
|
167
|
+
data_non_lazy = data
|
|
168
|
+
if hasattr(data_non_lazy, "read"):
|
|
169
|
+
if hasattr(data_non_lazy, "seek"):
|
|
170
|
+
if hasattr(data_non_lazy, "tell") and data_non_lazy.tell() != 0:
|
|
171
|
+
data_non_lazy.seek(0)
|
|
172
|
+
content = data_non_lazy.read() # type: ignore
|
|
173
|
+
if close_file:
|
|
174
|
+
data_non_lazy.close() # type: ignore
|
|
175
|
+
return content
|
|
176
|
+
elif isinstance(data_non_lazy, str):
|
|
177
|
+
return data_non_lazy.encode(string_encoding)
|
|
178
|
+
else:
|
|
179
|
+
return cast(bytes, data_non_lazy)
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
bytes_result: solara.Result[Optional[bytes]] = solara.use_thread(get_data, dependencies=[request_download, data])
|
|
183
|
+
if filename is None and hasattr(data, "name"):
|
|
184
|
+
try:
|
|
185
|
+
filename = Path(data.name).name # type: ignore
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
filename = filename or "solara-download.dat"
|
|
189
|
+
label = label or ("Download: " + filename)
|
|
190
|
+
FileDownloadWidget.element(
|
|
191
|
+
filename=filename,
|
|
192
|
+
bytes=bytes_result.value if bytes_result.state == solara.ResultState.FINISHED else None,
|
|
193
|
+
request_download=request_download,
|
|
194
|
+
on_request_download=set_request_download,
|
|
195
|
+
children=children or [solara.Button(label, loading=bytes_result.state == solara.ResultState.RUNNING, icon_name=icon_name)],
|
|
196
|
+
mime_type=mime_type,
|
|
197
|
+
)
|
|
198
|
+
if bytes_result.state == solara.ResultState.ERROR and bytes_result.error:
|
|
199
|
+
raise bytes_result.error
|