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/kernel.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import pdb
|
|
4
|
+
import queue
|
|
5
|
+
import re
|
|
6
|
+
import struct
|
|
7
|
+
import warnings
|
|
8
|
+
from binascii import b2a_base64
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Set, Union
|
|
11
|
+
|
|
12
|
+
import ipykernel
|
|
13
|
+
import ipykernel.kernelbase
|
|
14
|
+
import jupyter_client.session as session
|
|
15
|
+
from dateutil.tz import tzlocal # type: ignore
|
|
16
|
+
from ipykernel.comm import CommManager
|
|
17
|
+
from zmq.eventloop.zmqstream import ZMQStream
|
|
18
|
+
|
|
19
|
+
import solara
|
|
20
|
+
from solara.server.shell import SolaraInteractiveShell
|
|
21
|
+
|
|
22
|
+
from . import settings, websocket
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("solara.server.kernel")
|
|
25
|
+
ipykernel_major = int(ipykernel.__version__.split(".")[0])
|
|
26
|
+
|
|
27
|
+
jsonmodule = json
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# from jupyter_client/jsonutil.py
|
|
31
|
+
def _ensure_tzinfo(dt: datetime) -> datetime:
|
|
32
|
+
"""Ensure a datetime object has tzinfo
|
|
33
|
+
If no tzinfo is present, add tzlocal
|
|
34
|
+
"""
|
|
35
|
+
if not dt.tzinfo:
|
|
36
|
+
# No more naïve datetime objects!
|
|
37
|
+
warnings.warn(
|
|
38
|
+
"Interpreting naive datetime as local %s. Please add timezone info to timestamps." % dt,
|
|
39
|
+
DeprecationWarning,
|
|
40
|
+
stacklevel=4,
|
|
41
|
+
)
|
|
42
|
+
dt = dt.replace(tzinfo=tzlocal())
|
|
43
|
+
return dt
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def json_default(obj):
|
|
47
|
+
"""default function for packing objects in JSON."""
|
|
48
|
+
if isinstance(obj, datetime):
|
|
49
|
+
obj = _ensure_tzinfo(obj)
|
|
50
|
+
return obj.isoformat().replace("+00:00", "Z")
|
|
51
|
+
elif isinstance(obj, bytes):
|
|
52
|
+
return b2a_base64(obj).decode("ascii")
|
|
53
|
+
if type(obj).__module__ == "numpy":
|
|
54
|
+
import numpy as np
|
|
55
|
+
|
|
56
|
+
if isinstance(obj, np.number):
|
|
57
|
+
return obj.item()
|
|
58
|
+
else:
|
|
59
|
+
raise TypeError("%r is not JSON serializable" % obj)
|
|
60
|
+
else:
|
|
61
|
+
raise TypeError("%r is not JSON serializable" % obj)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def json_dumps(data):
|
|
65
|
+
try:
|
|
66
|
+
return jsonmodule.dumps(data)
|
|
67
|
+
except TypeError:
|
|
68
|
+
logger.warning("Invalid JSON, will try a with a more forgiving json encoder")
|
|
69
|
+
return jsonmodule.dumps(
|
|
70
|
+
data,
|
|
71
|
+
default=json_default,
|
|
72
|
+
ensure_ascii=False,
|
|
73
|
+
allow_nan=False,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
ipykernel_version = tuple(map(int, re.split(r"\D+", ipykernel.__version__)[:3]))
|
|
78
|
+
if ipykernel_version >= (6, 18, 0):
|
|
79
|
+
import comm.base_comm
|
|
80
|
+
|
|
81
|
+
class Comm(comm.base_comm.BaseComm):
|
|
82
|
+
kernel: Union[ipykernel.kernelbase.Kernel, None]
|
|
83
|
+
|
|
84
|
+
def __init__(self, **kwargs) -> None:
|
|
85
|
+
if ipykernel.kernelbase.Kernel.initialized():
|
|
86
|
+
self.kernel = ipykernel.kernelbase.Kernel.instance()
|
|
87
|
+
else:
|
|
88
|
+
self.kernel = None
|
|
89
|
+
super().__init__(**kwargs)
|
|
90
|
+
|
|
91
|
+
def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
|
|
92
|
+
if self.kernel is None or self.kernel.session is None:
|
|
93
|
+
return
|
|
94
|
+
data = {} if data is None else data
|
|
95
|
+
metadata = {} if metadata is None else metadata
|
|
96
|
+
content = dict(data=data, comm_id=self.comm_id, **keys)
|
|
97
|
+
self.kernel.session.send(
|
|
98
|
+
self.kernel.iopub_socket,
|
|
99
|
+
msg_type,
|
|
100
|
+
content,
|
|
101
|
+
metadata=metadata,
|
|
102
|
+
parent=self.kernel.get_parent("shell"),
|
|
103
|
+
ident=self.topic,
|
|
104
|
+
buffers=buffers,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
comm.create_comm = Comm
|
|
108
|
+
|
|
109
|
+
def get_comm_manager():
|
|
110
|
+
from .kernel_context import get_current_context, has_current_context
|
|
111
|
+
|
|
112
|
+
if has_current_context():
|
|
113
|
+
return get_current_context().kernel.comm_manager
|
|
114
|
+
else:
|
|
115
|
+
return global_comm_manager
|
|
116
|
+
|
|
117
|
+
global_comm_manager = comm.get_comm_manager()
|
|
118
|
+
comm.get_comm_manager = get_comm_manager
|
|
119
|
+
|
|
120
|
+
# from notebook.base.zmqhandlers import serialize_binary_message
|
|
121
|
+
# this saves us a dependency on notebook/jupyter_server when e.g.
|
|
122
|
+
# running on pyodide
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _fix_msg(msg):
|
|
126
|
+
# makes sure the msg can be json serializable
|
|
127
|
+
# instead of using a callable like in jupyter_client (i.e. json_default)
|
|
128
|
+
# we replace the keys we know are problematic
|
|
129
|
+
# this allows us to use a faster json serializer in the future
|
|
130
|
+
if "header" in msg and "date" in msg["header"]:
|
|
131
|
+
# this is what jupyter_client.jsonutil.json_default does
|
|
132
|
+
msg["header"]["date"] = msg["header"]["date"].isoformat().replace("+00:00", "Z")
|
|
133
|
+
if "parent_header" in msg and "date" in msg["parent_header"]:
|
|
134
|
+
# date is already a string if it's copied from the header that is not turned into a datetime
|
|
135
|
+
# maybe we should do that in server.py
|
|
136
|
+
date = msg["parent_header"]["date"]
|
|
137
|
+
if isinstance(date, datetime):
|
|
138
|
+
msg["parent_header"]["date"] = date.isoformat().replace("+00:00", "Z")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def serialize_binary_message(msg):
|
|
142
|
+
"""serialize a message as a binary blob
|
|
143
|
+
|
|
144
|
+
Header:
|
|
145
|
+
|
|
146
|
+
4 bytes: number of msg parts (nbufs) as 32b int
|
|
147
|
+
4 * nbufs bytes: offset for each buffer as integer as 32b int
|
|
148
|
+
|
|
149
|
+
Offsets are from the start of the buffer, including the header.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
|
|
154
|
+
The message serialized to bytes.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
# don't modify msg or buffer list in-place
|
|
158
|
+
msg = msg.copy()
|
|
159
|
+
buffers = list(msg.pop("buffers"))
|
|
160
|
+
bmsg = json_dumps(msg).encode("utf8")
|
|
161
|
+
buffers.insert(0, bmsg)
|
|
162
|
+
nbufs = len(buffers)
|
|
163
|
+
offsets = [4 * (nbufs + 1)]
|
|
164
|
+
for buf in buffers[:-1]:
|
|
165
|
+
offsets.append(offsets[-1] + len(buf))
|
|
166
|
+
offsets_buf = struct.pack("!" + "I" * (nbufs + 1), nbufs, *offsets)
|
|
167
|
+
buffers.insert(0, offsets_buf)
|
|
168
|
+
return b"".join(buffers)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def deserialize_binary_message(bmsg):
|
|
172
|
+
"""deserialize a message from a binary blog
|
|
173
|
+
|
|
174
|
+
Header:
|
|
175
|
+
|
|
176
|
+
4 bytes: number of msg parts (nbufs) as 32b int
|
|
177
|
+
4 * nbufs bytes: offset for each buffer as integer as 32b int
|
|
178
|
+
|
|
179
|
+
Offsets are from the start of the buffer, including the header.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
message dictionary
|
|
184
|
+
"""
|
|
185
|
+
nbufs = struct.unpack("!i", bmsg[:4])[0]
|
|
186
|
+
offsets = list(struct.unpack("!" + "I" * nbufs, bmsg[4 : 4 * (nbufs + 1)]))
|
|
187
|
+
offsets.append(None)
|
|
188
|
+
bufs = []
|
|
189
|
+
for start, stop in zip(offsets[:-1], offsets[1:]):
|
|
190
|
+
bufs.append(bmsg[start:stop])
|
|
191
|
+
msg = json.loads(bufs[0].decode("utf8"))
|
|
192
|
+
msg["buffers"] = bufs[1:]
|
|
193
|
+
return msg
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
SESSION_KEY = b"solara"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class WebsocketStream:
|
|
200
|
+
def __init__(self, session, channel: str):
|
|
201
|
+
self.session = session
|
|
202
|
+
self.channel = channel
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class WebsocketStreamWrapper(ZMQStream):
|
|
206
|
+
def __init__(self, websocket, channel):
|
|
207
|
+
self.websocket = websocket
|
|
208
|
+
self.channel = channel
|
|
209
|
+
|
|
210
|
+
def flush(self, *ignore):
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def send_websockets(websockets: Set[websocket.WebsocketWrapper], binary_msg):
|
|
215
|
+
for ws in list(websockets):
|
|
216
|
+
try:
|
|
217
|
+
ws.send(binary_msg)
|
|
218
|
+
except websocket.WebSocketDisconnect:
|
|
219
|
+
# ignore the exception, we tried to send while websocket closed
|
|
220
|
+
# just remove it from the websocket set
|
|
221
|
+
try:
|
|
222
|
+
# websocket can be modified by another thread
|
|
223
|
+
websockets.remove(ws)
|
|
224
|
+
except KeyError:
|
|
225
|
+
pass # already removed
|
|
226
|
+
except Exception as e: # noqa
|
|
227
|
+
logger.exception("Error sending message: %s, closing websocket", e)
|
|
228
|
+
try:
|
|
229
|
+
ws.close()
|
|
230
|
+
except websocket.WebSocketDisconnect:
|
|
231
|
+
pass # was already closed, which triggered the original exception
|
|
232
|
+
except Exception as e2: # noqa
|
|
233
|
+
logger.exception("Error closing websocket: %s %s", e, e2)
|
|
234
|
+
try:
|
|
235
|
+
# websocket can be modified by another thread
|
|
236
|
+
websockets.remove(ws)
|
|
237
|
+
except KeyError:
|
|
238
|
+
pass # already removed
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class SessionWebsocket(session.Session):
|
|
242
|
+
def __init__(self, *args, **kwargs):
|
|
243
|
+
super().__init__(*args, **kwargs)
|
|
244
|
+
self.websockets: Set[websocket.WebsocketWrapper] = set() # map from .. msg id to websocket?
|
|
245
|
+
|
|
246
|
+
def close(self):
|
|
247
|
+
for ws in list(self.websockets):
|
|
248
|
+
try:
|
|
249
|
+
ws.close()
|
|
250
|
+
except: # noqa
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
def send(
|
|
254
|
+
self,
|
|
255
|
+
stream,
|
|
256
|
+
msg_or_type,
|
|
257
|
+
content=None,
|
|
258
|
+
parent=None,
|
|
259
|
+
ident=None,
|
|
260
|
+
buffers=None,
|
|
261
|
+
track=False,
|
|
262
|
+
header=None,
|
|
263
|
+
metadata=None,
|
|
264
|
+
):
|
|
265
|
+
if stream is None:
|
|
266
|
+
return # can happen when the kernel is closed but someone was still trying to send a message
|
|
267
|
+
try:
|
|
268
|
+
if isinstance(msg_or_type, dict):
|
|
269
|
+
msg = msg_or_type
|
|
270
|
+
else:
|
|
271
|
+
msg = self.msg(
|
|
272
|
+
msg_or_type,
|
|
273
|
+
content=content,
|
|
274
|
+
parent=parent,
|
|
275
|
+
header=header,
|
|
276
|
+
metadata=metadata,
|
|
277
|
+
)
|
|
278
|
+
_fix_msg(msg)
|
|
279
|
+
msg["channel"] = stream.channel
|
|
280
|
+
# not using pdb guard for performance reasons
|
|
281
|
+
try:
|
|
282
|
+
if buffers:
|
|
283
|
+
msg["buffers"] = [memoryview(k).cast("b") for k in buffers]
|
|
284
|
+
wire_message = serialize_binary_message(msg)
|
|
285
|
+
else:
|
|
286
|
+
wire_message = json_dumps(msg)
|
|
287
|
+
except Exception:
|
|
288
|
+
logger.exception("Could not serialize message: %r", msg)
|
|
289
|
+
if settings.main.use_pdb:
|
|
290
|
+
pdb.post_mortem()
|
|
291
|
+
raise
|
|
292
|
+
send_websockets(self.websockets, wire_message)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.exception("Error sending message: %s", e)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class Kernel(ipykernel.kernelbase.Kernel):
|
|
298
|
+
session: SessionWebsocket # type: ignore
|
|
299
|
+
# Ideally we have `session = Instance(Session, allow_none=True)`, but MyPy does not like it
|
|
300
|
+
|
|
301
|
+
implementation = "solara"
|
|
302
|
+
implementation_version = solara.__version__
|
|
303
|
+
banner = "solara"
|
|
304
|
+
|
|
305
|
+
def __init__(self):
|
|
306
|
+
super().__init__()
|
|
307
|
+
self.session = SessionWebsocket(parent=self, key=SESSION_KEY)
|
|
308
|
+
self.msg_queue = queue.Queue() # type: ignore
|
|
309
|
+
self.stream = self.iopub_socket = WebsocketStream(self.session, "iopub")
|
|
310
|
+
# on github action the next line gives a mypy error:
|
|
311
|
+
# solara/server/kernel.py:111: error: "SessionWebsocket" has no attribute "stream"
|
|
312
|
+
# not sure why we cannot reproduce that locally
|
|
313
|
+
self.session.stream = self.iopub_socket # type: ignore
|
|
314
|
+
if ipykernel_version >= (6, 18, 0):
|
|
315
|
+
# from this version on, ipykernel uses the comm package https://github.com/ipython/ipykernel/pull/973
|
|
316
|
+
self.comm_manager = CommManager(parent=self, kernel=self)
|
|
317
|
+
import ipywidgets.widgets.widget
|
|
318
|
+
|
|
319
|
+
if hasattr(ipywidgets.widgets.widget, "Comm"):
|
|
320
|
+
ipywidgets.widgets.widget.Comm = Comm
|
|
321
|
+
ipywidgets.widgets.widget.Widget.comm.klass = Comm
|
|
322
|
+
else:
|
|
323
|
+
self.comm_manager = CommManager(parent=self, kernel=self)
|
|
324
|
+
self.log = logging.getLogger("fake")
|
|
325
|
+
|
|
326
|
+
comm_msg_types = ["comm_open", "comm_msg", "comm_close"]
|
|
327
|
+
for msg_type in comm_msg_types:
|
|
328
|
+
self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
|
|
329
|
+
self.shell = SolaraInteractiveShell()
|
|
330
|
+
self.shell.display_pub.session = self.session
|
|
331
|
+
self.shell.display_pub.pub_socket = self.iopub_socket
|
|
332
|
+
|
|
333
|
+
def close(self):
|
|
334
|
+
if self.comm_manager is None:
|
|
335
|
+
raise RuntimeError("Kernel already closed")
|
|
336
|
+
self.session.close()
|
|
337
|
+
self._cleanup_references()
|
|
338
|
+
|
|
339
|
+
def _cleanup_references(self):
|
|
340
|
+
try:
|
|
341
|
+
# all of these reduce the circular references
|
|
342
|
+
# making it easier for the garbage collector to clean up
|
|
343
|
+
self.shell_handlers.clear()
|
|
344
|
+
self.control_handlers.clear()
|
|
345
|
+
for comm_object in list(self.comm_manager.comms.values()): # type: ignore
|
|
346
|
+
comm_object.close()
|
|
347
|
+
self.comm_manager.targets.clear() # type: ignore
|
|
348
|
+
# self.comm_manager.kernel points to us, but we cannot set it to None
|
|
349
|
+
# so we remove the circular reference by setting the comm_manager to None
|
|
350
|
+
self.comm_manager = None # type: ignore
|
|
351
|
+
self.session.parent = None # type: ignore
|
|
352
|
+
|
|
353
|
+
self.shell.display_pub.session = None # type: ignore
|
|
354
|
+
self.shell.display_pub.pub_socket = None # type: ignore
|
|
355
|
+
del self.shell.__dict__
|
|
356
|
+
self.shell = None # type: ignore
|
|
357
|
+
self.session.websockets.clear()
|
|
358
|
+
self.session.stream = None # type: ignore
|
|
359
|
+
self.session = None # type: ignore
|
|
360
|
+
self.stream.session = None # type: ignore
|
|
361
|
+
self.stream = None # type: ignore
|
|
362
|
+
self.iopub_socket = None # type: ignore
|
|
363
|
+
except Exception:
|
|
364
|
+
logger.exception("Error cleaning up references from kernel, not fatal")
|
|
365
|
+
|
|
366
|
+
async def _flush_control_queue(self):
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
# these don't work from non-main thread, and we do not care about them I think
|
|
370
|
+
# TODO: it seems that if post_handler_hook is not override, the flask reload tests fails
|
|
371
|
+
# for unknown reason
|
|
372
|
+
def pre_handler_hook(self, *args):
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
def post_handler_hook(self, *args):
|
|
376
|
+
pass
|
|
377
|
+
|
|
378
|
+
def set_parent(self, ident, parent, channel="shell"):
|
|
379
|
+
"""Overridden from parent to tell the display hook and output streams
|
|
380
|
+
about the parent message.
|
|
381
|
+
"""
|
|
382
|
+
if ipykernel_major < 6:
|
|
383
|
+
# the channel argument was added in 6.0
|
|
384
|
+
super().set_parent(ident, parent)
|
|
385
|
+
else:
|
|
386
|
+
super().set_parent(ident, parent, channel)
|
|
387
|
+
if channel == "shell":
|
|
388
|
+
self.shell.set_parent(parent)
|