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/server/flask.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import mimetypes
|
|
4
|
+
import os
|
|
5
|
+
import threading
|
|
6
|
+
from http.server import HTTPServer
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
import flask
|
|
13
|
+
import simple_websocket
|
|
14
|
+
from flask import Blueprint, Flask, abort, request, send_from_directory, url_for
|
|
15
|
+
from flask_sock import Sock
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import solara_enterprise # type: ignore
|
|
19
|
+
|
|
20
|
+
del solara_enterprise
|
|
21
|
+
|
|
22
|
+
has_solara_enterprise = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
has_solara_enterprise = False
|
|
25
|
+
|
|
26
|
+
if has_solara_enterprise:
|
|
27
|
+
from solara_enterprise.auth.flask import allowed # type: ignore
|
|
28
|
+
from solara_enterprise.auth.flask import (
|
|
29
|
+
authorize,
|
|
30
|
+
get_user,
|
|
31
|
+
init_flask,
|
|
32
|
+
login,
|
|
33
|
+
logout,
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
|
|
37
|
+
def allowed():
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
import solara
|
|
42
|
+
import solara.settings
|
|
43
|
+
from solara.server.threaded import ServerBase
|
|
44
|
+
|
|
45
|
+
from . import app as appmod
|
|
46
|
+
from . import cdn_helper, kernel_context, server, settings, websocket
|
|
47
|
+
|
|
48
|
+
os.environ["SERVER_SOFTWARE"] = "solara/" + str(solara.__version__)
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger("solara.server.flask")
|
|
51
|
+
blueprint = Blueprint("blueprint-solara", __name__)
|
|
52
|
+
websocket_extension = Sock()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class WebsocketWrapper(websocket.WebsocketWrapper):
|
|
56
|
+
ws: simple_websocket.Server
|
|
57
|
+
|
|
58
|
+
def __init__(self, ws: simple_websocket.Server) -> None:
|
|
59
|
+
self.ws = ws
|
|
60
|
+
self.lock = threading.Lock()
|
|
61
|
+
|
|
62
|
+
def close(self):
|
|
63
|
+
with self.lock:
|
|
64
|
+
self.ws.close()
|
|
65
|
+
|
|
66
|
+
def send_text(self, data: str) -> None:
|
|
67
|
+
with self.lock:
|
|
68
|
+
try:
|
|
69
|
+
self.ws.send(data)
|
|
70
|
+
except simple_websocket.ws.ConnectionClosed:
|
|
71
|
+
raise websocket.WebSocketDisconnect()
|
|
72
|
+
|
|
73
|
+
def send_bytes(self, data: bytes) -> None:
|
|
74
|
+
with self.lock:
|
|
75
|
+
try:
|
|
76
|
+
self.ws.send(data)
|
|
77
|
+
except simple_websocket.ws.ConnectionClosed:
|
|
78
|
+
raise websocket.WebSocketDisconnect()
|
|
79
|
+
|
|
80
|
+
async def receive(self):
|
|
81
|
+
from anyio import to_thread
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
return await to_thread.run_sync(lambda: self.ws.receive()) # type: ignore
|
|
85
|
+
except simple_websocket.ws.ConnectionClosed:
|
|
86
|
+
raise websocket.WebSocketDisconnect()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ServerFlask(ServerBase):
|
|
90
|
+
server: Any
|
|
91
|
+
name = "flask"
|
|
92
|
+
|
|
93
|
+
def __init__(self, port: int, host: str = "localhost", flask_app=None, url_prefix="", **kwargs):
|
|
94
|
+
super().__init__(port, host, **kwargs)
|
|
95
|
+
self.app = flask_app or app
|
|
96
|
+
self.url_prefix = url_prefix
|
|
97
|
+
self.server = None
|
|
98
|
+
|
|
99
|
+
def has_started(self):
|
|
100
|
+
return server.is_ready(f"http://{self.host}:{self.port}{self.url_prefix}")
|
|
101
|
+
|
|
102
|
+
def signal_stop(self):
|
|
103
|
+
if self.server:
|
|
104
|
+
assert isinstance(self.server, HTTPServer)
|
|
105
|
+
self.server.shutdown() # type: ignore
|
|
106
|
+
|
|
107
|
+
def serve(self):
|
|
108
|
+
from werkzeug.serving import make_server
|
|
109
|
+
|
|
110
|
+
self.server = make_server(self.host, self.port, self.app, threaded=True) # type: ignore
|
|
111
|
+
assert isinstance(self.server, HTTPServer)
|
|
112
|
+
self.started.set()
|
|
113
|
+
self.server.serve_forever(poll_interval=0.05) # type: ignore
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@blueprint.route("/jupyter/api/kernels/<id>")
|
|
117
|
+
def kernels(id):
|
|
118
|
+
if not allowed():
|
|
119
|
+
abort(401)
|
|
120
|
+
return {"name": "lala", "id": "dsa"}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@websocket_extension.route("/jupyter/api/kernels/<kernel_id>/<name>")
|
|
124
|
+
def kernels_connection(ws: simple_websocket.Server, kernel_id: str, name: str):
|
|
125
|
+
if not settings.main.base_url:
|
|
126
|
+
settings.main.base_url = url_for("blueprint-solara.read_root", _external=True)
|
|
127
|
+
if settings.oauth.private and not has_solara_enterprise:
|
|
128
|
+
raise RuntimeError("SOLARA_OAUTH_PRIVATE requires solara-enterprise")
|
|
129
|
+
if has_solara_enterprise:
|
|
130
|
+
user = get_user()
|
|
131
|
+
if user is None and settings.oauth.private:
|
|
132
|
+
logger.error("app is private, requires login")
|
|
133
|
+
ws.close(1008, "app is private, requires login") # policy violation
|
|
134
|
+
return
|
|
135
|
+
else:
|
|
136
|
+
user = None
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
page_id = request.args["session_id"]
|
|
140
|
+
session_id = request.cookies.get(server.COOKIE_KEY_SESSION_ID)
|
|
141
|
+
logger.info("Solara kernel requested for session_id=%s kernel_id=%s", session_id, kernel_id)
|
|
142
|
+
if session_id is None:
|
|
143
|
+
logger.warning("no session cookie")
|
|
144
|
+
session_id = "session-id-cookie-unavailable:" + str(uuid4())
|
|
145
|
+
ws_wrapper = WebsocketWrapper(ws)
|
|
146
|
+
headers_dict: Dict[str, List[str]] = {}
|
|
147
|
+
for k, v in request.headers.__iter__():
|
|
148
|
+
if k not in headers_dict.keys():
|
|
149
|
+
headers_dict[k] = [v]
|
|
150
|
+
else:
|
|
151
|
+
headers_dict[k].append(v)
|
|
152
|
+
asyncio.run(
|
|
153
|
+
server.app_loop(
|
|
154
|
+
ws_wrapper,
|
|
155
|
+
cookies=request.cookies.to_dict(),
|
|
156
|
+
headers=headers_dict,
|
|
157
|
+
session_id=session_id,
|
|
158
|
+
kernel_id=kernel_id,
|
|
159
|
+
page_id=page_id,
|
|
160
|
+
user=user,
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
except: # noqa
|
|
164
|
+
logger.exception("Error in kernel handler")
|
|
165
|
+
raise
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@blueprint.route("/_solara/api/close/<kernel_id>", methods=["GET", "POST"])
|
|
169
|
+
def close(kernel_id: str):
|
|
170
|
+
page_id = request.args["session_id"]
|
|
171
|
+
context = kernel_context.contexts.get(kernel_id, None)
|
|
172
|
+
if context is not None:
|
|
173
|
+
context.page_close(page_id)
|
|
174
|
+
return ""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@blueprint.route("/static/public/<path:path>")
|
|
178
|
+
def public(path):
|
|
179
|
+
if not allowed():
|
|
180
|
+
abort(401)
|
|
181
|
+
directories = [app.directory.parent / "public" for app in appmod.apps.values()]
|
|
182
|
+
for directory in directories:
|
|
183
|
+
file = directory / path
|
|
184
|
+
if file.exists():
|
|
185
|
+
return send_from_directory(directory, path)
|
|
186
|
+
return flask.Response("not found", status=404)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@blueprint.route("/static/assets/<path:path>")
|
|
190
|
+
def assets(path):
|
|
191
|
+
if not allowed():
|
|
192
|
+
abort(401)
|
|
193
|
+
directories = server.asset_directories()
|
|
194
|
+
for directory in directories:
|
|
195
|
+
file = directory / path
|
|
196
|
+
if file.exists():
|
|
197
|
+
return send_from_directory(directory, path)
|
|
198
|
+
return flask.Response("not found", status=404)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@blueprint.route("/jupyter/nbextensions/<dir>/<filename>")
|
|
202
|
+
def nbext(dir, filename):
|
|
203
|
+
if not allowed():
|
|
204
|
+
abort(401)
|
|
205
|
+
for directory in server.nbextensions_directories:
|
|
206
|
+
file = directory / dir / filename
|
|
207
|
+
if file.exists():
|
|
208
|
+
return send_from_directory(directory, dir + "/" + filename)
|
|
209
|
+
return flask.Response("not found", status=404)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@blueprint.route("/static/<path:path>")
|
|
213
|
+
def serve_static(path):
|
|
214
|
+
if not allowed():
|
|
215
|
+
abort(401)
|
|
216
|
+
return send_from_directory(server.solara_static, path)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if solara.settings.assets.proxy:
|
|
220
|
+
|
|
221
|
+
@blueprint.route(f"/{cdn_helper.cdn_url_path}/<path:path>")
|
|
222
|
+
def cdn(path):
|
|
223
|
+
if not allowed():
|
|
224
|
+
abort(401)
|
|
225
|
+
cache_directory = settings.assets.proxy_cache_dir
|
|
226
|
+
try:
|
|
227
|
+
content = cdn_helper.get_data(Path(cache_directory), path)
|
|
228
|
+
except PermissionError:
|
|
229
|
+
return flask.Response("not found", status=404)
|
|
230
|
+
mime = mimetypes.guess_type(path)
|
|
231
|
+
return flask.Response(content, mimetype=mime[0])
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@blueprint.route("/", defaults={"path": ""})
|
|
235
|
+
@blueprint.route("/<path:path>")
|
|
236
|
+
def read_root(path):
|
|
237
|
+
root_path = url_for(".read_root")
|
|
238
|
+
if root_path.endswith("/"):
|
|
239
|
+
root_path = root_path[:-1]
|
|
240
|
+
|
|
241
|
+
if not settings.main.base_url:
|
|
242
|
+
settings.main.base_url = url_for("blueprint-solara.read_root", _external=True)
|
|
243
|
+
|
|
244
|
+
session_id = request.cookies.get(server.COOKIE_KEY_SESSION_ID) or str(uuid4())
|
|
245
|
+
if root_path:
|
|
246
|
+
path = flask.request.path[len(root_path) :]
|
|
247
|
+
content = server.read_root(path, root_path=root_path)
|
|
248
|
+
if content is None:
|
|
249
|
+
if not allowed():
|
|
250
|
+
abort(401)
|
|
251
|
+
return flask.Response("not found", status=404)
|
|
252
|
+
|
|
253
|
+
if not allowed():
|
|
254
|
+
return login()
|
|
255
|
+
|
|
256
|
+
samesite = "lax"
|
|
257
|
+
secure = False
|
|
258
|
+
# we want samesite, so we can set a cookie when embedded in an iframe, such as on huggingface
|
|
259
|
+
# however, samesite=none requires Secure https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
|
|
260
|
+
# when hosted on the localhost domain we can always set the Secure flag
|
|
261
|
+
# to allow samesite https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies
|
|
262
|
+
o = urlparse(request.base_url)
|
|
263
|
+
if request.headers.get("x-forwarded-proto", "http") == "https" or o.hostname == "localhost":
|
|
264
|
+
samesite = "none"
|
|
265
|
+
secure = True
|
|
266
|
+
|
|
267
|
+
assert session_id is not None
|
|
268
|
+
response = flask.Response(content, mimetype="text/html")
|
|
269
|
+
response.set_cookie(server.COOKIE_KEY_SESSION_ID, value=session_id, secure=secure, samesite=samesite)
|
|
270
|
+
return response
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if has_solara_enterprise:
|
|
274
|
+
blueprint.route("/_solara/auth/authorize")(authorize)
|
|
275
|
+
blueprint.route("/_solara/auth/logout")(logout)
|
|
276
|
+
blueprint.route("/_solara/auth/login")(login)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@blueprint.route("/readyz")
|
|
280
|
+
def readyz():
|
|
281
|
+
json, status = server.readyz()
|
|
282
|
+
return flask.Response(json, mimetype="application/json", status=status)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# using the blueprint and websocket blueprint makes it easier to integrate into other applications
|
|
286
|
+
websocket_extension.init_app(blueprint)
|
|
287
|
+
app = Flask(__name__, static_url_path="/_static") # do not intervere with out static files
|
|
288
|
+
app.register_blueprint(blueprint)
|
|
289
|
+
if has_solara_enterprise:
|
|
290
|
+
init_flask(app)
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
app.run(debug=False, port=8765)
|
|
294
|
+
|
|
295
|
+
# we can only call this at the module level, which means that the solara script cannot import this
|
|
296
|
+
# module. This is a difference with the asgi standard, which provides a lifecycle hook (see starlette.py)
|
|
297
|
+
appmod.ensure_apps_initialized()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import mimetypes
|
|
3
|
+
|
|
4
|
+
import tornado.web
|
|
5
|
+
from jupyter_server.base.handlers import JupyterHandler
|
|
6
|
+
|
|
7
|
+
from solara.server import settings
|
|
8
|
+
from solara.server.cdn_helper import get_data
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("Solara.cdn")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CdnHandler(JupyterHandler):
|
|
14
|
+
def initialize(self, cache_directory=settings.assets.proxy_cache_dir):
|
|
15
|
+
self.cache_directory = cache_directory
|
|
16
|
+
logging.info("Using %r as cache directory", self.cache_directory)
|
|
17
|
+
|
|
18
|
+
async def get(self, path=None):
|
|
19
|
+
try:
|
|
20
|
+
content = get_data(self.cache_directory, path)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logger.warning(e)
|
|
23
|
+
raise tornado.web.HTTPError(500)
|
|
24
|
+
|
|
25
|
+
mime = mimetypes.guess_type(path)
|
|
26
|
+
if mime[0] is not None:
|
|
27
|
+
self.set_header("Content-Type", mime[0])
|
|
28
|
+
self.write(content)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from jupyter_server.utils import url_path_join
|
|
2
|
+
|
|
3
|
+
from solara.server.cdn_helper import cdn_url_path
|
|
4
|
+
from solara.server.jupyter.cdn_handler import CdnHandler
|
|
5
|
+
from .solara import SolaraHandler, Assets, ReadyZ
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _jupyter_server_extension_paths():
|
|
9
|
+
return [{"module": "solara.server.jupyter.server_extension"}]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _load_jupyter_server_extension(server_app):
|
|
13
|
+
# a dummy app, so that server.read_root can be used
|
|
14
|
+
import solara.server.app
|
|
15
|
+
|
|
16
|
+
solara.server.app.apps["__default__"] = solara.server.app.AppScript("solara.server.jupyter.solara:Page")
|
|
17
|
+
solara.server.app.apps["__default__"].init()
|
|
18
|
+
|
|
19
|
+
web_app = server_app.web_app
|
|
20
|
+
|
|
21
|
+
host_pattern = ".*$"
|
|
22
|
+
base_url = url_path_join(web_app.settings["base_url"])
|
|
23
|
+
|
|
24
|
+
web_app.add_handlers(
|
|
25
|
+
host_pattern,
|
|
26
|
+
[
|
|
27
|
+
(url_path_join(base_url, f"/{cdn_url_path}/(.*)"), CdnHandler, {}), # kept for backward compatibility
|
|
28
|
+
(url_path_join(base_url, f"/solara/{cdn_url_path}/(.*)"), CdnHandler, {}),
|
|
29
|
+
(url_path_join(base_url, "/solara/static/assets/(.*)"), Assets, {}),
|
|
30
|
+
(url_path_join(base_url, "/solara/readyz"), ReadyZ, {}),
|
|
31
|
+
(url_path_join(base_url, "/solara(.*)"), SolaraHandler, {}),
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# For backward compatibility
|
|
37
|
+
load_jupyter_server_extension = _load_jupyter_server_extension
|
|
38
|
+
|
|
39
|
+
# For future compatibility
|
|
40
|
+
_jupyter_server_extension_points = _jupyter_server_extension_paths
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import tornado.web
|
|
7
|
+
from jupyter_server.base.handlers import JupyterHandler
|
|
8
|
+
import solara.server.server as server
|
|
9
|
+
|
|
10
|
+
from solara.server.utils import path_is_child_of
|
|
11
|
+
import solara
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("solara.server.jupyter.solara")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@solara.component
|
|
17
|
+
def Page():
|
|
18
|
+
solara.Error("Hi, you should not see this, we only support ipypopout for now")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SolaraHandler(JupyterHandler):
|
|
22
|
+
async def get(self, path=None):
|
|
23
|
+
try:
|
|
24
|
+
# base url ends with /
|
|
25
|
+
base_url = self.settings["base_url"]
|
|
26
|
+
# root_path's do not end with /
|
|
27
|
+
jupyter_root_path = ""
|
|
28
|
+
if base_url and base_url.endswith("/"):
|
|
29
|
+
jupyter_root_path = base_url[:-1]
|
|
30
|
+
root_path = f"{jupyter_root_path}/solara"
|
|
31
|
+
content = server.read_root(path="", root_path=root_path, jupyter_root_path=jupyter_root_path)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logger.exception(e)
|
|
34
|
+
raise tornado.web.HTTPError(500)
|
|
35
|
+
|
|
36
|
+
if content is None:
|
|
37
|
+
raise tornado.web.HTTPError(404)
|
|
38
|
+
|
|
39
|
+
self.set_header("Content-Type", "text/html")
|
|
40
|
+
self.write(content)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# similar to voila
|
|
44
|
+
class MultiStaticFileHandler(tornado.web.StaticFileHandler):
|
|
45
|
+
"""A static file handler that 'merges' a list of directories
|
|
46
|
+
|
|
47
|
+
If initialized like this::
|
|
48
|
+
|
|
49
|
+
application = web.Application([
|
|
50
|
+
(r"/content/(.*)", web.MultiStaticFileHandler, {"paths": ["/var/1", "/var/2"]}),
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
A file will be looked up in /var/1 first, then in /var/2.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def initialize(self, paths, default_filename=None): # type: ignore
|
|
58
|
+
self.roots = paths
|
|
59
|
+
super().initialize(path=paths[0], default_filename=default_filename)
|
|
60
|
+
|
|
61
|
+
def get_absolute_path(self, root: str, path: str) -> str: # type: ignore
|
|
62
|
+
# find the first absolute path that exists
|
|
63
|
+
self.root = self.roots[0]
|
|
64
|
+
abspath = os.path.abspath(os.path.join(root, path))
|
|
65
|
+
for root in self.roots[1:]:
|
|
66
|
+
abspath = os.path.abspath(os.path.join(root, path))
|
|
67
|
+
if os.path.exists(abspath):
|
|
68
|
+
self.root = root # make sure all the other methods in the base class know how to find the file
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
# tornado probably already does a version of this, to make sure it behaves as the rest of the solara
|
|
72
|
+
# server, we do it again
|
|
73
|
+
if not path_is_child_of(Path(abspath), Path(self.root)):
|
|
74
|
+
raise PermissionError(f"Trying to read from outside of cache directory: {abspath} is not a subdir of {self.root}")
|
|
75
|
+
|
|
76
|
+
return abspath
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Assets(MultiStaticFileHandler):
|
|
80
|
+
def initialize(self): # type: ignore
|
|
81
|
+
super().initialize(server.asset_directories())
|
|
82
|
+
logging.error("Using %r as assets directories", self.roots)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ReadyZ(JupyterHandler):
|
|
86
|
+
def get(self):
|
|
87
|
+
json_data, status = server.readyz()
|
|
88
|
+
json_response = json.dumps(json_data)
|
|
89
|
+
self.set_header("Content-Type", "application/json")
|
|
90
|
+
self.set_status(status)
|
|
91
|
+
self.write(json_response)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
# based on notebook config, but to reduce the dependencies
|
|
6
|
+
# and with modern typing and use of pathlib
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def recursive_update(target, new):
|
|
10
|
+
"""Recursively update one dictionary using another.
|
|
11
|
+
|
|
12
|
+
None values will delete their keys.
|
|
13
|
+
"""
|
|
14
|
+
for k, v in new.items():
|
|
15
|
+
if isinstance(v, dict):
|
|
16
|
+
if k not in target:
|
|
17
|
+
target[k] = {}
|
|
18
|
+
recursive_update(target[k], v)
|
|
19
|
+
if not target[k]:
|
|
20
|
+
# Prune empty subdicts
|
|
21
|
+
del target[k]
|
|
22
|
+
|
|
23
|
+
elif v is None:
|
|
24
|
+
target.pop(k, None)
|
|
25
|
+
|
|
26
|
+
else:
|
|
27
|
+
target[k] = v
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_config(directory: Path, config_name: str):
|
|
31
|
+
paths = [directory / f"{config_name}.json"]
|
|
32
|
+
paths.extend((directory / f"{config_name}.d/").glob("*.json"))
|
|
33
|
+
data: dict = {}
|
|
34
|
+
for path in paths:
|
|
35
|
+
if path.is_file():
|
|
36
|
+
with open(path, encoding="utf-8") as f:
|
|
37
|
+
recursive_update(data, json.load(f))
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_config(directories: List[Path], config_name: str):
|
|
42
|
+
config: dict = {}
|
|
43
|
+
# step through back to front, to ensure front of the list is top priority
|
|
44
|
+
for directory in directories[::-1]:
|
|
45
|
+
recursive_update(config, _get_config(directory, config_name))
|
|
46
|
+
return config
|