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/__main__.py
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import site
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import typing
|
|
8
|
+
import webbrowser
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import rich
|
|
13
|
+
import rich_click as click
|
|
14
|
+
import uvicorn
|
|
15
|
+
from rich import print as rprint
|
|
16
|
+
from uvicorn.main import LEVEL_CHOICES, LOOP_CHOICES
|
|
17
|
+
|
|
18
|
+
import solara
|
|
19
|
+
from solara.server import settings
|
|
20
|
+
import solara.server.threaded
|
|
21
|
+
|
|
22
|
+
from .server import telemetry
|
|
23
|
+
|
|
24
|
+
print_mutex = threading.Lock()
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from solara_enterprise.ssg import ssg_crawl
|
|
28
|
+
except ImportError:
|
|
29
|
+
|
|
30
|
+
def ssg_crawl(*args, **kwargs): # type: ignore
|
|
31
|
+
raise RuntimeError('SSG not available, please install solara-enterprise (pip install "solara-enterprise[ssg]"')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
HERE = Path(__file__).parent
|
|
35
|
+
|
|
36
|
+
LOGGING_CONFIG: dict = {
|
|
37
|
+
"version": 1,
|
|
38
|
+
"disable_existing_loggers": False,
|
|
39
|
+
"formatters": {
|
|
40
|
+
"default": {
|
|
41
|
+
"()": "uvicorn.logging.DefaultFormatter",
|
|
42
|
+
"fmt": "%(levelprefix)s %(message)s",
|
|
43
|
+
"use_colors": None,
|
|
44
|
+
},
|
|
45
|
+
"access": {
|
|
46
|
+
"()": "uvicorn.logging.AccessFormatter",
|
|
47
|
+
"fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
"handlers": {
|
|
51
|
+
"default": {
|
|
52
|
+
"formatter": "default",
|
|
53
|
+
"class": "logging.StreamHandler",
|
|
54
|
+
"stream": "ext://sys.stderr",
|
|
55
|
+
},
|
|
56
|
+
"rich": {
|
|
57
|
+
"class": "rich.logging.RichHandler",
|
|
58
|
+
},
|
|
59
|
+
"access": {
|
|
60
|
+
"formatter": "access",
|
|
61
|
+
"class": "logging.StreamHandler",
|
|
62
|
+
"stream": "ext://sys.stdout",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"loggers": {
|
|
66
|
+
"solara": {"handlers": ["default"], "level": "ERROR"},
|
|
67
|
+
"reacton": {"handlers": ["default"], "level": "ERROR"},
|
|
68
|
+
# "react": {"handlers": ["rich"], "level": "DEBUG"},
|
|
69
|
+
"uvicorn": {"handlers": ["default"], "level": "ERROR"},
|
|
70
|
+
"uvicorn.error": {"level": "ERROR"},
|
|
71
|
+
"uvicorn.access": {"handlers": ["access"], "level": "ERROR", "propagate": False},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _check_version():
|
|
77
|
+
import requests
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
response = requests.get("https://pypi.org/pypi/solara/json", timeout=0.5)
|
|
81
|
+
latest_version = response.json()["info"]["version"]
|
|
82
|
+
except: # noqa: E722
|
|
83
|
+
# in case of a firewall, or timeout, we just abort
|
|
84
|
+
return
|
|
85
|
+
if latest_version != solara.__version__:
|
|
86
|
+
with print_mutex:
|
|
87
|
+
print(f"New version of Solara available: {latest_version}. You have {solara.__version__}. Please upgrade using:") # noqa: T201
|
|
88
|
+
print(f'\t$ pip install "solara=={latest_version}"') # noqa: T201
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def find_all_packages_paths():
|
|
92
|
+
paths = []
|
|
93
|
+
# sitepackages = set([os.path.dirname(k) for k in site.getsitepackages()])
|
|
94
|
+
sitepackages = {k for k in site.getsitepackages()}
|
|
95
|
+
paths.extend(list(sitepackages))
|
|
96
|
+
for name, module in sys.modules.items():
|
|
97
|
+
if hasattr(module, "__path__"):
|
|
98
|
+
try:
|
|
99
|
+
path = module.__path__[0]
|
|
100
|
+
except: # noqa: E722
|
|
101
|
+
pass # happens for namespace packages it seems
|
|
102
|
+
# print(f"Error for {name}")
|
|
103
|
+
# if path:
|
|
104
|
+
# skip = False
|
|
105
|
+
# for sitepackage in sitepackages:
|
|
106
|
+
# if path.startswith(sitepackage):
|
|
107
|
+
# skip = True
|
|
108
|
+
# if not skip:
|
|
109
|
+
# print(name, path, skip)
|
|
110
|
+
paths.append(str(path))
|
|
111
|
+
# print("PATHS", paths)
|
|
112
|
+
return paths
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class EnumType(click.Choice):
|
|
116
|
+
def __init__(self, enum: typing.Type[Enum], case_sensitive=False):
|
|
117
|
+
self._enum = enum
|
|
118
|
+
super().__init__(choices=[item.name for item in enum], case_sensitive=case_sensitive)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@click.group()
|
|
122
|
+
def cli():
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
production_default = False
|
|
127
|
+
if "SOLARA_MODE" in os.environ:
|
|
128
|
+
# settings.main.mode by default is set to production,
|
|
129
|
+
# which is a good default for when you embed in a flask
|
|
130
|
+
# app for instance, but not for the CLI, which app developers
|
|
131
|
+
# usually run.
|
|
132
|
+
production_default = settings.main.mode == "production"
|
|
133
|
+
# Note that in the CLI we do set this value to "development"
|
|
134
|
+
# or "production" based on the --production flag
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@cli.command()
|
|
138
|
+
@click.option(
|
|
139
|
+
"--port",
|
|
140
|
+
default=int(os.environ.get("PORT", 8765)),
|
|
141
|
+
help="Port to run the server on, 0 for a random free port, default to $PORT or 8765.",
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--host",
|
|
145
|
+
default=settings.main.host,
|
|
146
|
+
help="Host to listen on. Defaults to the $HOST environment or $SOLARA_HOST when available or localhost when not given.",
|
|
147
|
+
)
|
|
148
|
+
@click.option("--dev/--no-dev", default=None, help="Deprecated: use --auto-restart/-a", hidden=True)
|
|
149
|
+
@click.option("--production", is_flag=True, default=production_default, help="Run in production mode: https://solara.dev/docs/understanding/solara-server")
|
|
150
|
+
@click.option("--reload", is_flag=True, default=None, help="Deprecated: use --auto-restart/-a", hidden=True)
|
|
151
|
+
@click.option("-a", "--auto-restart", is_flag=True, default=False, help="Enable auto-restarting of server when the solara server code changes.")
|
|
152
|
+
@click.option("--tracer/--no-tracer", default=False)
|
|
153
|
+
@click.option("--timing/--no-timing", default=False)
|
|
154
|
+
@click.option("--open/--no-open", default=True)
|
|
155
|
+
@click.option(
|
|
156
|
+
"--restart-dir",
|
|
157
|
+
"restart_dirs",
|
|
158
|
+
multiple=True,
|
|
159
|
+
help="Set restart directories explicitly, instead of using the current working directory.",
|
|
160
|
+
type=click.Path(exists=True),
|
|
161
|
+
)
|
|
162
|
+
@click.option(
|
|
163
|
+
"--restart-exclude",
|
|
164
|
+
"restart_excludes",
|
|
165
|
+
multiple=True,
|
|
166
|
+
help="Set glob patterns to exclude while watching for files. Includes "
|
|
167
|
+
"'.*, .py[cod], .sw.*, ~*' by default; these defaults can be overridden "
|
|
168
|
+
"with `--restart-include`. This option has no effect unless watchgod is "
|
|
169
|
+
"installed.",
|
|
170
|
+
)
|
|
171
|
+
@click.option(
|
|
172
|
+
"--workers",
|
|
173
|
+
default=None,
|
|
174
|
+
type=int,
|
|
175
|
+
help="Number of worker processes. Defaults to the $WEB_CONCURRENCY environment variable if available, or 1. Not valid with --auto-restart/-a.",
|
|
176
|
+
)
|
|
177
|
+
@click.option(
|
|
178
|
+
"--env-file",
|
|
179
|
+
type=click.Path(exists=True),
|
|
180
|
+
default=None,
|
|
181
|
+
help="Environment configuration file.",
|
|
182
|
+
show_default=True,
|
|
183
|
+
)
|
|
184
|
+
@click.option(
|
|
185
|
+
"--log-config",
|
|
186
|
+
type=click.Path(exists=True),
|
|
187
|
+
default=None,
|
|
188
|
+
help="Logging configuration file. Supported formats: .ini, .json, .yaml.",
|
|
189
|
+
show_default=True,
|
|
190
|
+
)
|
|
191
|
+
@click.option(
|
|
192
|
+
"--log-level",
|
|
193
|
+
type=LEVEL_CHOICES,
|
|
194
|
+
default=None,
|
|
195
|
+
help="Log level. [default: info]",
|
|
196
|
+
show_default=True,
|
|
197
|
+
)
|
|
198
|
+
@click.option(
|
|
199
|
+
"--log-level-uvicorn",
|
|
200
|
+
type=LEVEL_CHOICES,
|
|
201
|
+
default="error",
|
|
202
|
+
help="Log level. [default: error]",
|
|
203
|
+
show_default=True,
|
|
204
|
+
)
|
|
205
|
+
@click.option(
|
|
206
|
+
"--access-log/--no-access-log",
|
|
207
|
+
is_flag=True,
|
|
208
|
+
default=True,
|
|
209
|
+
help="Enable/Disable access log.",
|
|
210
|
+
)
|
|
211
|
+
@click.option(
|
|
212
|
+
"--root-path",
|
|
213
|
+
type=str,
|
|
214
|
+
default="",
|
|
215
|
+
help="Set the ASGI 'root_path' for applications submounted below a given URL path.",
|
|
216
|
+
)
|
|
217
|
+
@click.option(
|
|
218
|
+
"--theme-loader",
|
|
219
|
+
type=str,
|
|
220
|
+
default=settings.theme.loader,
|
|
221
|
+
help=f"Loader to use when the app is not yet shown to the user. [default: {settings.theme.loader!r}]",
|
|
222
|
+
)
|
|
223
|
+
@click.option(
|
|
224
|
+
"--theme-variant",
|
|
225
|
+
type=settings.ThemeVariant,
|
|
226
|
+
default=settings.theme.variant.name,
|
|
227
|
+
help=f"Use light or dark variant, or auto detect (auto). [default: {settings.theme.variant.name}",
|
|
228
|
+
)
|
|
229
|
+
@click.option(
|
|
230
|
+
"--dark",
|
|
231
|
+
type=bool,
|
|
232
|
+
default=settings.theme.variant == settings.ThemeVariant.dark,
|
|
233
|
+
help="Use dark theme. Shorthand for --theme-variant=dark",
|
|
234
|
+
)
|
|
235
|
+
@click.option(
|
|
236
|
+
"--theme-variant-user-selectable/--no-theme-variant-user-selectable",
|
|
237
|
+
type=bool,
|
|
238
|
+
hidden=True,
|
|
239
|
+
help="Deprecated.",
|
|
240
|
+
)
|
|
241
|
+
@click.option("--pdb/--no-pdb", "use_pdb", default=False, help="Enter debugger on error")
|
|
242
|
+
@click.argument("app")
|
|
243
|
+
@click.option(
|
|
244
|
+
"--loop",
|
|
245
|
+
type=LOOP_CHOICES,
|
|
246
|
+
default="auto",
|
|
247
|
+
help="Event loop implementation.",
|
|
248
|
+
show_default=True,
|
|
249
|
+
)
|
|
250
|
+
@click.option(
|
|
251
|
+
"--ssg/--no-ssg",
|
|
252
|
+
is_flag=True,
|
|
253
|
+
default=settings.ssg.enabled,
|
|
254
|
+
help="(pre) Render static pages.",
|
|
255
|
+
)
|
|
256
|
+
@click.option(
|
|
257
|
+
"--search/--no-search",
|
|
258
|
+
is_flag=True,
|
|
259
|
+
default=settings.search.enabled,
|
|
260
|
+
help="Enable search (requires ssg generated pages).",
|
|
261
|
+
)
|
|
262
|
+
@click.option(
|
|
263
|
+
"--check-version/--no-check-version",
|
|
264
|
+
is_flag=True,
|
|
265
|
+
default=True,
|
|
266
|
+
help="Check installed version again pypi version.",
|
|
267
|
+
)
|
|
268
|
+
@click.option(
|
|
269
|
+
"--qt",
|
|
270
|
+
is_flag=True,
|
|
271
|
+
default=False,
|
|
272
|
+
help="Instead of opening a browser, open a Qt window. Will also stop the server when the window is closed. (experimental)",
|
|
273
|
+
)
|
|
274
|
+
def run(
|
|
275
|
+
app,
|
|
276
|
+
host,
|
|
277
|
+
port,
|
|
278
|
+
open,
|
|
279
|
+
auto_restart: bool,
|
|
280
|
+
reload: bool,
|
|
281
|
+
restart_dirs: typing.Optional[typing.List[str]],
|
|
282
|
+
restart_excludes: typing.List[str],
|
|
283
|
+
dev: bool,
|
|
284
|
+
production: bool,
|
|
285
|
+
tracer: bool,
|
|
286
|
+
timing: bool,
|
|
287
|
+
loop: str,
|
|
288
|
+
workers: int,
|
|
289
|
+
env_file: str,
|
|
290
|
+
root_path: str,
|
|
291
|
+
log_config: str,
|
|
292
|
+
log_level: str,
|
|
293
|
+
log_level_uvicorn: str,
|
|
294
|
+
access_log: bool,
|
|
295
|
+
use_pdb: bool,
|
|
296
|
+
theme_loader: str,
|
|
297
|
+
theme_variant: settings.ThemeVariant,
|
|
298
|
+
dark: bool,
|
|
299
|
+
theme_variant_user_selectable: bool,
|
|
300
|
+
ssg: bool,
|
|
301
|
+
search: bool,
|
|
302
|
+
check_version: bool = True,
|
|
303
|
+
qt=False,
|
|
304
|
+
):
|
|
305
|
+
"""Run a Solara app."""
|
|
306
|
+
if dev is not None:
|
|
307
|
+
print("solara: --dev is deprecated, use --auto-restart/-a instead", file=sys.stderr) # noqa: T201
|
|
308
|
+
auto_restart = dev
|
|
309
|
+
if reload is not None:
|
|
310
|
+
print("solara: --reload is deprecated, use --auto-restart/-a instead", file=sys.stderr) # noqa: T201
|
|
311
|
+
auto_restart = reload
|
|
312
|
+
if check_version:
|
|
313
|
+
threading.Thread(target=_check_version, daemon=True).run()
|
|
314
|
+
|
|
315
|
+
# uvicorn calls it reload, we call it auto restart
|
|
316
|
+
reload = auto_restart
|
|
317
|
+
del auto_restart
|
|
318
|
+
settings.ssg.enabled = ssg
|
|
319
|
+
settings.search.enabled = search
|
|
320
|
+
reload_dirs = restart_dirs if restart_dirs else None
|
|
321
|
+
if port == 0:
|
|
322
|
+
port = solara.server.threaded.get_free_port()
|
|
323
|
+
del restart_dirs
|
|
324
|
+
url = f"http://{host}:{port}"
|
|
325
|
+
|
|
326
|
+
failed = False
|
|
327
|
+
if reload:
|
|
328
|
+
telemetry._auto_restart_enabled = True
|
|
329
|
+
solara_root = Path(solara.__file__).parent
|
|
330
|
+
|
|
331
|
+
reload_dirs = list(reload_dirs if reload_dirs else [])
|
|
332
|
+
|
|
333
|
+
# we restart the server when solara or react changes, in principle we should do
|
|
334
|
+
# that for all dependencies of the server, but these are changing most often
|
|
335
|
+
# during development
|
|
336
|
+
# We exclude the website, that will be handled by solara/server/reload.py
|
|
337
|
+
reload_dirs = [str(solara_root), str(Path(solara.__file__).parent)]
|
|
338
|
+
try:
|
|
339
|
+
import solara_enterprise
|
|
340
|
+
|
|
341
|
+
reload_dirs.append(str(Path(solara_enterprise.__file__).parent))
|
|
342
|
+
del solara_enterprise
|
|
343
|
+
except ImportError:
|
|
344
|
+
pass
|
|
345
|
+
reload_excludes = restart_excludes if restart_excludes else []
|
|
346
|
+
del restart_excludes
|
|
347
|
+
reload_excludes = [str(solara_root / "website"), str(solara_root / "template")]
|
|
348
|
+
app_path = Path(app)
|
|
349
|
+
if app_path.exists():
|
|
350
|
+
# if app is not a child of the current working directory
|
|
351
|
+
# uvicorn crashes
|
|
352
|
+
if not str(app_path.resolve()).startswith(str(Path.cwd().resolve())):
|
|
353
|
+
reload_excludes.append(str(app_path.resolve()))
|
|
354
|
+
del app_path
|
|
355
|
+
del solara_root
|
|
356
|
+
reload = True
|
|
357
|
+
# avoid sending many restarts
|
|
358
|
+
settings.telemetry.mixpanel_enable = False
|
|
359
|
+
else:
|
|
360
|
+
del restart_excludes
|
|
361
|
+
|
|
362
|
+
if production:
|
|
363
|
+
settings.main.mode = "production"
|
|
364
|
+
else:
|
|
365
|
+
settings.main.mode = "development"
|
|
366
|
+
server = None
|
|
367
|
+
|
|
368
|
+
# TODO: we might want to support this, but it needs to be started from the main thread
|
|
369
|
+
# and then uvicorn needs to be started from a thread
|
|
370
|
+
# def open_webview():
|
|
371
|
+
# import webview
|
|
372
|
+
|
|
373
|
+
# while not failed and (server is None or not server.started):
|
|
374
|
+
# time.sleep(0.1)
|
|
375
|
+
# if not failed:
|
|
376
|
+
# window = webview.create_window("Hello world", url, resizable=True)
|
|
377
|
+
# window.on_top = True
|
|
378
|
+
# # window.show()
|
|
379
|
+
# webview.start(debug=True)
|
|
380
|
+
|
|
381
|
+
def open_browser():
|
|
382
|
+
while not failed and (server is None or not server.started):
|
|
383
|
+
time.sleep(0.1)
|
|
384
|
+
if not failed:
|
|
385
|
+
if qt:
|
|
386
|
+
from .server.qt import run_qt
|
|
387
|
+
|
|
388
|
+
run_qt(url)
|
|
389
|
+
else:
|
|
390
|
+
webbrowser.open(url)
|
|
391
|
+
|
|
392
|
+
# with qt, we open the browser in the main thread (qt wants that)
|
|
393
|
+
# otherwise, we open the browser in a separate thread
|
|
394
|
+
if open and not qt:
|
|
395
|
+
threading.Thread(target=open_browser, daemon=True).start()
|
|
396
|
+
|
|
397
|
+
with print_mutex:
|
|
398
|
+
rich.print(f"Solara server is starting at {url}")
|
|
399
|
+
|
|
400
|
+
if log_level is not None:
|
|
401
|
+
LOGGING_CONFIG["loggers"]["solara"]["level"] = log_level.upper()
|
|
402
|
+
# LOGGING_CONFIG["loggers"]["reacton"]["level"] = log_level.upper()
|
|
403
|
+
|
|
404
|
+
log_level = log_level_uvicorn
|
|
405
|
+
del log_level_uvicorn
|
|
406
|
+
|
|
407
|
+
kwargs = locals().copy()
|
|
408
|
+
# cgi vars: https://datatracker.ietf.org/doc/html/rfc3875
|
|
409
|
+
os.environ["SOLARA_APP"] = app
|
|
410
|
+
os.environ["SERVER_NAME"] = host
|
|
411
|
+
os.environ["SERVER_PORT"] = str(port)
|
|
412
|
+
|
|
413
|
+
kwargs["app"] = "solara.server.starlette:app"
|
|
414
|
+
kwargs["log_config"] = LOGGING_CONFIG if log_config is None else log_config
|
|
415
|
+
kwargs["loop"] = loop
|
|
416
|
+
settings.main.use_pdb = use_pdb
|
|
417
|
+
settings.theme.loader = theme_loader
|
|
418
|
+
if dark:
|
|
419
|
+
theme_variant = settings.ThemeVariant.dark
|
|
420
|
+
settings.theme.variant = theme_variant
|
|
421
|
+
settings.main.tracer = tracer
|
|
422
|
+
settings.main.timing = timing
|
|
423
|
+
items = (
|
|
424
|
+
"theme_variant_user_selectable dark theme_variant theme_loader use_pdb server open_browser open url failed dev tracer"
|
|
425
|
+
" timing ssg search check_version production qt".split()
|
|
426
|
+
)
|
|
427
|
+
for item in items:
|
|
428
|
+
del kwargs[item]
|
|
429
|
+
|
|
430
|
+
def start_server():
|
|
431
|
+
nonlocal server
|
|
432
|
+
nonlocal failed
|
|
433
|
+
try:
|
|
434
|
+
# we manually create the server instead of calling uvicorn.run
|
|
435
|
+
# because we can then access the server variable and check if it is
|
|
436
|
+
# running.
|
|
437
|
+
config = uvicorn.Config(**kwargs)
|
|
438
|
+
server = uvicorn.Server(config=config)
|
|
439
|
+
if reload:
|
|
440
|
+
sock = config.bind_socket()
|
|
441
|
+
from uvicorn.supervisors import ChangeReload
|
|
442
|
+
|
|
443
|
+
ChangeReload(
|
|
444
|
+
config,
|
|
445
|
+
target=run_with_settings(
|
|
446
|
+
server, main=settings.main.dict(), theme=settings.theme.dict(), ssg=settings.ssg.dict(), search=settings.search.dict()
|
|
447
|
+
),
|
|
448
|
+
sockets=[sock],
|
|
449
|
+
).run()
|
|
450
|
+
else:
|
|
451
|
+
server.run()
|
|
452
|
+
except: # noqa
|
|
453
|
+
failed = True
|
|
454
|
+
raise
|
|
455
|
+
|
|
456
|
+
def ssg_run():
|
|
457
|
+
while not failed and (server is None or not server.started):
|
|
458
|
+
time.sleep(0.1)
|
|
459
|
+
if not failed:
|
|
460
|
+
assert server is not None
|
|
461
|
+
base_url = f"http://{server.config.host}:{server.config.port}"
|
|
462
|
+
rprint("Running Static Site Generator pre-render background task")
|
|
463
|
+
ssg_crawl(base_url)
|
|
464
|
+
if settings.search.enabled:
|
|
465
|
+
from solara_enterprise.search.index import build_index
|
|
466
|
+
|
|
467
|
+
build_index("")
|
|
468
|
+
|
|
469
|
+
# in dev mode we run the ssg in the child process (see run_with_settings)
|
|
470
|
+
if not dev and settings.ssg.enabled:
|
|
471
|
+
threading.Thread(target=ssg_run, daemon=True).start()
|
|
472
|
+
|
|
473
|
+
# if we don't have to wait for SSG, we can build the index right away
|
|
474
|
+
if not settings.ssg.enabled and settings.search.enabled:
|
|
475
|
+
from solara_enterprise.search.index import build_index
|
|
476
|
+
|
|
477
|
+
build_index("")
|
|
478
|
+
|
|
479
|
+
# TODO: if we want to use webview, it should be sth like this
|
|
480
|
+
if qt:
|
|
481
|
+
server_thread = threading.Thread(target=start_server, daemon=True)
|
|
482
|
+
server_thread.start()
|
|
483
|
+
open_browser()
|
|
484
|
+
else:
|
|
485
|
+
start_server()
|
|
486
|
+
# server_thread.join()
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@cli.command()
|
|
490
|
+
@click.argument("app")
|
|
491
|
+
@click.option("--port", default=int(os.environ.get("PORT", 8765)))
|
|
492
|
+
@click.option("--host", default=settings.main.host)
|
|
493
|
+
@click.option(
|
|
494
|
+
"--headed/--no-headed",
|
|
495
|
+
is_flag=True,
|
|
496
|
+
default=settings.ssg.headed,
|
|
497
|
+
help="Show browser window if true.",
|
|
498
|
+
)
|
|
499
|
+
def ssg(app: str, port: int, host: str, headed: bool):
|
|
500
|
+
"""Static site generation"""
|
|
501
|
+
settings.ssg.headed = headed
|
|
502
|
+
settings.ssg.enabled = True
|
|
503
|
+
settings.main.mode = "production" # always override this
|
|
504
|
+
os.environ["SOLARA_APP"] = app
|
|
505
|
+
from solara.server.starlette import ServerStarlette
|
|
506
|
+
|
|
507
|
+
server = ServerStarlette(port=port, host=host)
|
|
508
|
+
server.serve_threaded()
|
|
509
|
+
server.wait_until_serving()
|
|
510
|
+
|
|
511
|
+
base_url = f"http://{server.server.config.host}:{server.server.config.port}"
|
|
512
|
+
|
|
513
|
+
ssg_crawl(base_url)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@cli.command()
|
|
517
|
+
def deploy():
|
|
518
|
+
rprint("...")
|
|
519
|
+
import time
|
|
520
|
+
|
|
521
|
+
time.sleep(1)
|
|
522
|
+
rprint("Want your app to run instantly on awesomeapp-mystartup-gh.solara.run?")
|
|
523
|
+
rprint("\tCheck out https://solara.dev/documentation/getting_started/deploying/cloud-hosted")
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@cli.command()
|
|
527
|
+
@click.argument("app")
|
|
528
|
+
def search(app: str):
|
|
529
|
+
"""Build search index based on ssg output"""
|
|
530
|
+
os.environ["SOLARA_APP"] = app
|
|
531
|
+
from solara_enterprise.search.index import build_index
|
|
532
|
+
|
|
533
|
+
build_index("")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class run_with_settings:
|
|
537
|
+
"""This cross a process boundary, and takes the serialized settings with it"""
|
|
538
|
+
|
|
539
|
+
def __init__(self, server: uvicorn.Server, main: typing.Dict, theme: typing.Dict, ssg: typing.Dict, search: typing.Dict):
|
|
540
|
+
self.server = server
|
|
541
|
+
self.main = main
|
|
542
|
+
self.theme = theme
|
|
543
|
+
self.ssg = ssg
|
|
544
|
+
self.search = search
|
|
545
|
+
|
|
546
|
+
def __call__(self, *args, **kwargs):
|
|
547
|
+
# this is now in the new process, where we need to re-apply the settings
|
|
548
|
+
failed = False
|
|
549
|
+
|
|
550
|
+
def ssg_run():
|
|
551
|
+
while not failed and (self.server is None or not self.server.started):
|
|
552
|
+
time.sleep(0.1)
|
|
553
|
+
if not failed:
|
|
554
|
+
assert self.server is not None
|
|
555
|
+
base_url = f"http://{self.server.config.host}:{self.server.config.port}"
|
|
556
|
+
rprint("Running Static Site Generator pre-render background task")
|
|
557
|
+
ssg_crawl(base_url)
|
|
558
|
+
if settings.search.enabled:
|
|
559
|
+
from solara_enterprise.search.index import build_index
|
|
560
|
+
|
|
561
|
+
build_index("")
|
|
562
|
+
|
|
563
|
+
settings.main = settings.MainSettings(**self.main)
|
|
564
|
+
settings.theme = settings.ThemeSettings(**self.theme)
|
|
565
|
+
settings.ssg = settings.SSG(**self.ssg)
|
|
566
|
+
settings.search = settings.Search(**self.search)
|
|
567
|
+
if settings.ssg.enabled:
|
|
568
|
+
threading.Thread(target=ssg_run, daemon=True).start()
|
|
569
|
+
try:
|
|
570
|
+
return self.server.run(*args, **kwargs)
|
|
571
|
+
except: # noqa
|
|
572
|
+
failed = True
|
|
573
|
+
raise
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
@cli.command()
|
|
577
|
+
@click.option("--port", default=int(os.environ.get("PORT", 8000)))
|
|
578
|
+
def staticserve(port):
|
|
579
|
+
"""Experimental static serving"""
|
|
580
|
+
import http.server
|
|
581
|
+
import os
|
|
582
|
+
from functools import partial
|
|
583
|
+
|
|
584
|
+
print(f"http://localhost:{port}/") # noqa
|
|
585
|
+
wk_dir = os.getcwd() + "/staticbuild"
|
|
586
|
+
http.server.SimpleHTTPRequestHandler.extensions_map[".js"] = "application/javascript"
|
|
587
|
+
http.server.SimpleHTTPRequestHandler.extensions_map = {k: v + ";charset=UTF-8" for k, v in http.server.SimpleHTTPRequestHandler.extensions_map.items()}
|
|
588
|
+
http.server.test(HandlerClass=partial(http.server.SimpleHTTPRequestHandler, directory=wk_dir), port=port, bind="") # type: ignore
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@cli.command()
|
|
592
|
+
def staticbuild():
|
|
593
|
+
"""Experimental static build"""
|
|
594
|
+
# imports locals, otherwise .run() does not use the cli arguments
|
|
595
|
+
import solara.server
|
|
596
|
+
import solara.server.server
|
|
597
|
+
|
|
598
|
+
server_path = HERE / "server"
|
|
599
|
+
assets_path = server_path / "assets"
|
|
600
|
+
static_path = server_path / "static"
|
|
601
|
+
|
|
602
|
+
target_dir = Path("staticbuild")
|
|
603
|
+
target_dir.mkdir(exist_ok=True)
|
|
604
|
+
|
|
605
|
+
from .server import settings
|
|
606
|
+
|
|
607
|
+
copytree(settings.assets.proxy_cache_dir, target_dir / "_solara/cdn/")
|
|
608
|
+
|
|
609
|
+
static_dir_target = target_dir / "static"
|
|
610
|
+
static_dir_target.mkdir(exist_ok=True)
|
|
611
|
+
|
|
612
|
+
assets_dir_target = target_dir / "static" / "assets"
|
|
613
|
+
assets_dir_target.mkdir(exist_ok=True)
|
|
614
|
+
copytree(assets_path, assets_dir_target)
|
|
615
|
+
|
|
616
|
+
for path in (
|
|
617
|
+
list(static_path.glob("*.css"))
|
|
618
|
+
+ list(static_path.glob("*.js"))
|
|
619
|
+
+ list(static_path.glob("*.ps"))
|
|
620
|
+
+ list(static_path.glob("*.py"))
|
|
621
|
+
+ list(static_path.glob("*.png"))
|
|
622
|
+
+ list(static_path.glob("*.svg"))
|
|
623
|
+
):
|
|
624
|
+
shutil.copy(path, static_dir_target)
|
|
625
|
+
|
|
626
|
+
include_nbextensions = True
|
|
627
|
+
if include_nbextensions:
|
|
628
|
+
directories = solara.server.server.get_nbextensions_directories()
|
|
629
|
+
nbextensions, ignore = solara.server.server.get_nbextensions()
|
|
630
|
+
for name in nbextensions:
|
|
631
|
+
for directory in directories:
|
|
632
|
+
if (directory / (name + ".js")).exists():
|
|
633
|
+
src = (directory / (name + ".js")).parent
|
|
634
|
+
dst: Path = (static_dir_target / "nbextensions" / (name + ".js")).parent
|
|
635
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
636
|
+
# shutil.copytree(src, dst)
|
|
637
|
+
copytree(src, dst)
|
|
638
|
+
|
|
639
|
+
target_dir_wheels = target_dir / "wheels"
|
|
640
|
+
target_dir_wheels.mkdir(exist_ok=True)
|
|
641
|
+
version = solara.__version__
|
|
642
|
+
for path in [Path(f"dist/solara-{version}-py2.py3-none-any.whl")]:
|
|
643
|
+
shutil.copy(path, target_dir_wheels)
|
|
644
|
+
|
|
645
|
+
target_dir_static_dist = target_dir / "static/dist"
|
|
646
|
+
target_dir_static_dist.mkdir(exist_ok=True)
|
|
647
|
+
voila = server_path / "dist"
|
|
648
|
+
for path in list(voila.glob("*.js")) + list(voila.glob("*.woff")):
|
|
649
|
+
shutil.copy(path, target_dir_static_dist)
|
|
650
|
+
|
|
651
|
+
index_html = solara.server.server.read_root("", render_kwargs={"for_pyodide": True}, use_nbextensions=True)
|
|
652
|
+
assert index_html is not None
|
|
653
|
+
(target_dir / "index.html").write_text(index_html)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@click.group()
|
|
657
|
+
def create():
|
|
658
|
+
"""Quickly create a solara script or project."""
|
|
659
|
+
pass
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@create.command()
|
|
663
|
+
@click.argument(
|
|
664
|
+
"target",
|
|
665
|
+
type=click.Path(exists=False),
|
|
666
|
+
default="sol.py",
|
|
667
|
+
required=False,
|
|
668
|
+
)
|
|
669
|
+
def button(target: typing.Optional[Path]):
|
|
670
|
+
"""Create a button with a click counter."""
|
|
671
|
+
write_script("button", target)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@create.command()
|
|
675
|
+
@click.argument(
|
|
676
|
+
"target",
|
|
677
|
+
type=click.Path(exists=False),
|
|
678
|
+
default="sol.py",
|
|
679
|
+
required=False,
|
|
680
|
+
)
|
|
681
|
+
def markdown(target: typing.Optional[Path] = None):
|
|
682
|
+
"""Create a markdown editor."""
|
|
683
|
+
write_script("markdown", target)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def write_script(name: str, target: typing.Optional[Path]):
|
|
687
|
+
code = (HERE / "template" / f"{name}.py").read_text(encoding="utf-8")
|
|
688
|
+
if target is None:
|
|
689
|
+
target = Path("sol.py")
|
|
690
|
+
else:
|
|
691
|
+
target = Path(target)
|
|
692
|
+
target.parent.mkdir(exist_ok=True)
|
|
693
|
+
target.write_text(code)
|
|
694
|
+
rprint(f"Wrote: {target.resolve()}")
|
|
695
|
+
rprint(f"Run as:\n\t $ solara run {target.resolve()}")
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
# recursively copy a directory and allow for existing directories
|
|
699
|
+
def copytree(src: Path, dst: Path, copy_function=shutil.copy2, ignore: typing.Callable[[Path], bool] = lambda x: False, rename=lambda x: x):
|
|
700
|
+
print(src, " -> ", dst) # noqa
|
|
701
|
+
if not src.exists():
|
|
702
|
+
return
|
|
703
|
+
if not dst.exists():
|
|
704
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
705
|
+
for item in src.iterdir():
|
|
706
|
+
if ignore(item):
|
|
707
|
+
continue
|
|
708
|
+
if item.is_dir():
|
|
709
|
+
copytree(item, dst / rename(item).name, ignore=ignore, copy_function=copy_function, rename=rename)
|
|
710
|
+
else:
|
|
711
|
+
copy_function(item, dst / rename(item).name)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@create.command()
|
|
715
|
+
@click.argument(
|
|
716
|
+
"target",
|
|
717
|
+
type=click.Path(exists=False),
|
|
718
|
+
default="solara_portal",
|
|
719
|
+
required=False,
|
|
720
|
+
)
|
|
721
|
+
def portal(target: Path):
|
|
722
|
+
"""Create a full Python project template for a data portal"""
|
|
723
|
+
target = Path(target)
|
|
724
|
+
name = target.name
|
|
725
|
+
package_name = name.replace("-", "_")
|
|
726
|
+
|
|
727
|
+
def copy_function(src: Path, dst: Path):
|
|
728
|
+
dst.write_bytes(
|
|
729
|
+
src.read_bytes().replace(b"solara-portal", name.encode("utf8")).replace(b"solara_portal", package_name.encode("utf8")),
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
def rename(path: Path):
|
|
733
|
+
if path.name == "solara_portal":
|
|
734
|
+
return path.parent / package_name
|
|
735
|
+
else:
|
|
736
|
+
return path
|
|
737
|
+
|
|
738
|
+
copytree(
|
|
739
|
+
HERE / "template" / "portal",
|
|
740
|
+
target,
|
|
741
|
+
ignore=lambda p: p.name.startswith("__") and p.name != "__init__.py",
|
|
742
|
+
copy_function=copy_function,
|
|
743
|
+
rename=rename,
|
|
744
|
+
)
|
|
745
|
+
rprint(f"Wrote: {target.resolve()}")
|
|
746
|
+
rprint(f"Install as:\n\t $ (cd {target}; pip install -e .)")
|
|
747
|
+
rprint(f"Run as:\n\t $ solara run {package_name}.pages")
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
cli.add_command(run)
|
|
751
|
+
cli.add_command(staticbuild)
|
|
752
|
+
cli.add_command(create)
|
|
753
|
+
cli.add_command(ssg)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def main():
|
|
757
|
+
args = sys.argv
|
|
758
|
+
# skip everything after -- if it exists
|
|
759
|
+
if "--" in args:
|
|
760
|
+
args = args[: args.index("--")]
|
|
761
|
+
cli(args[1:])
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
if __name__ == "__main__":
|
|
765
|
+
main()
|