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.
Files changed (464) hide show
  1. prefix/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  2. prefix/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  3. solara/__init__.py +124 -0
  4. solara/__main__.py +765 -0
  5. solara/_stores.py +297 -0
  6. solara/alias.py +6 -0
  7. solara/autorouting.py +555 -0
  8. solara/cache.py +305 -0
  9. solara/checks.html +71 -0
  10. solara/checks.py +227 -0
  11. solara/comm.py +28 -0
  12. solara/components/__init__.py +77 -0
  13. solara/components/alert.py +155 -0
  14. solara/components/applayout.py +397 -0
  15. solara/components/button.py +85 -0
  16. solara/components/card.py +87 -0
  17. solara/components/checkbox.py +50 -0
  18. solara/components/code_highlight_css.py +11 -0
  19. solara/components/code_highlight_css.vue +63 -0
  20. solara/components/columns.py +159 -0
  21. solara/components/component_vue.py +134 -0
  22. solara/components/cross_filter.py +335 -0
  23. solara/components/dataframe.py +546 -0
  24. solara/components/datatable.py +214 -0
  25. solara/components/datatable.vue +175 -0
  26. solara/components/details.py +56 -0
  27. solara/components/download.vue +35 -0
  28. solara/components/echarts.py +86 -0
  29. solara/components/echarts.vue +139 -0
  30. solara/components/figure_altair.py +39 -0
  31. solara/components/file_browser.py +181 -0
  32. solara/components/file_download.py +199 -0
  33. solara/components/file_drop.py +159 -0
  34. solara/components/file_drop.vue +83 -0
  35. solara/components/file_list_widget.vue +78 -0
  36. solara/components/head.py +27 -0
  37. solara/components/head_tag.py +49 -0
  38. solara/components/head_tag.vue +60 -0
  39. solara/components/image.py +173 -0
  40. solara/components/input.py +456 -0
  41. solara/components/input_text_area.py +86 -0
  42. solara/components/link.py +55 -0
  43. solara/components/markdown.py +441 -0
  44. solara/components/markdown_editor.py +33 -0
  45. solara/components/markdown_editor.vue +359 -0
  46. solara/components/matplotlib.py +74 -0
  47. solara/components/meta.py +47 -0
  48. solara/components/misc.py +333 -0
  49. solara/components/pivot_table.py +258 -0
  50. solara/components/pivot_table.vue +158 -0
  51. solara/components/progress.py +47 -0
  52. solara/components/select.py +182 -0
  53. solara/components/select.vue +27 -0
  54. solara/components/slider.py +442 -0
  55. solara/components/slider_date.vue +56 -0
  56. solara/components/spinner-solara.vue +105 -0
  57. solara/components/spinner.py +45 -0
  58. solara/components/sql_code.py +41 -0
  59. solara/components/sql_code.vue +125 -0
  60. solara/components/style.py +105 -0
  61. solara/components/switch.py +71 -0
  62. solara/components/tab_navigation.py +37 -0
  63. solara/components/title.py +90 -0
  64. solara/components/title.vue +38 -0
  65. solara/components/togglebuttons.py +200 -0
  66. solara/components/tooltip.py +61 -0
  67. solara/core.py +42 -0
  68. solara/datatypes.py +143 -0
  69. solara/express.py +241 -0
  70. solara/hooks/__init__.py +4 -0
  71. solara/hooks/dataframe.py +99 -0
  72. solara/hooks/misc.py +263 -0
  73. solara/hooks/use_reactive.py +151 -0
  74. solara/hooks/use_thread.py +129 -0
  75. solara/kitchensink.py +8 -0
  76. solara/lab/__init__.py +34 -0
  77. solara/lab/components/__init__.py +7 -0
  78. solara/lab/components/chat.py +215 -0
  79. solara/lab/components/confirmation_dialog.py +163 -0
  80. solara/lab/components/cross_filter.py +7 -0
  81. solara/lab/components/input_date.py +339 -0
  82. solara/lab/components/input_time.py +133 -0
  83. solara/lab/components/menu.py +181 -0
  84. solara/lab/components/menu.vue +38 -0
  85. solara/lab/components/tabs.py +274 -0
  86. solara/lab/components/theming.py +98 -0
  87. solara/lab/components/theming.vue +72 -0
  88. solara/lab/hooks/__init__.py +0 -0
  89. solara/lab/hooks/dataframe.py +2 -0
  90. solara/lab/toestand.py +3 -0
  91. solara/lab/utils/__init__.py +2 -0
  92. solara/lab/utils/cookies.py +5 -0
  93. solara/lab/utils/dataframe.py +165 -0
  94. solara/lab/utils/headers.py +5 -0
  95. solara/layout.py +44 -0
  96. solara/lifecycle.py +46 -0
  97. solara/minisettings.py +141 -0
  98. solara/py.typed +0 -0
  99. solara/reactive.py +99 -0
  100. solara/routing.py +268 -0
  101. solara/scope/__init__.py +88 -0
  102. solara/scope/types.py +55 -0
  103. solara/server/__init__.py +0 -0
  104. solara/server/app.py +527 -0
  105. solara/server/assets/custom.css +1 -0
  106. solara/server/assets/custom.js +1 -0
  107. solara/server/assets/favicon.png +0 -0
  108. solara/server/assets/favicon.svg +5 -0
  109. solara/server/assets/style.css +1681 -0
  110. solara/server/assets/theme-dark.css +437 -0
  111. solara/server/assets/theme-light.css +420 -0
  112. solara/server/assets/theme.js +3 -0
  113. solara/server/cdn_helper.py +91 -0
  114. solara/server/esm.py +71 -0
  115. solara/server/fastapi.py +5 -0
  116. solara/server/flask.py +297 -0
  117. solara/server/jupyter/__init__.py +2 -0
  118. solara/server/jupyter/cdn_handler.py +28 -0
  119. solara/server/jupyter/server_extension.py +40 -0
  120. solara/server/jupyter/solara.py +91 -0
  121. solara/server/jupytertools.py +46 -0
  122. solara/server/kernel.py +388 -0
  123. solara/server/kernel_context.py +467 -0
  124. solara/server/patch.py +564 -0
  125. solara/server/pyinstaller/__init__.py +9 -0
  126. solara/server/pyinstaller/hook-ipyreact.py +5 -0
  127. solara/server/pyinstaller/hook-ipyvuetify.py +5 -0
  128. solara/server/pyinstaller/hook-solara.py +9 -0
  129. solara/server/qt.py +113 -0
  130. solara/server/reload.py +251 -0
  131. solara/server/server.py +484 -0
  132. solara/server/settings.py +249 -0
  133. solara/server/shell.py +269 -0
  134. solara/server/starlette.py +770 -0
  135. solara/server/static/ansi.js +270 -0
  136. solara/server/static/highlight-dark.css +82 -0
  137. solara/server/static/highlight.css +43 -0
  138. solara/server/static/main-vuetify.js +272 -0
  139. solara/server/static/main.js +163 -0
  140. solara/server/static/solara_bootstrap.py +129 -0
  141. solara/server/static/sun.svg +23 -0
  142. solara/server/static/webworker.js +42 -0
  143. solara/server/telemetry.py +212 -0
  144. solara/server/templates/index.html.j2 +1 -0
  145. solara/server/templates/loader-plain.css +11 -0
  146. solara/server/templates/loader-plain.html +20 -0
  147. solara/server/templates/loader-solara.css +111 -0
  148. solara/server/templates/loader-solara.html +40 -0
  149. solara/server/templates/plain.html +82 -0
  150. solara/server/templates/solara.html.j2 +486 -0
  151. solara/server/threaded.py +84 -0
  152. solara/server/utils.py +44 -0
  153. solara/server/websocket.py +45 -0
  154. solara/settings.py +86 -0
  155. solara/tasks.py +893 -0
  156. solara/template/button.py +16 -0
  157. solara/template/markdown.py +42 -0
  158. solara/template/portal/.flake8 +6 -0
  159. solara/template/portal/.pre-commit-config.yaml +28 -0
  160. solara/template/portal/LICENSE +21 -0
  161. solara/template/portal/Procfile +7 -0
  162. solara/template/portal/mypy.ini +3 -0
  163. solara/template/portal/pyproject.toml +26 -0
  164. solara/template/portal/solara_portal/__init__.py +4 -0
  165. solara/template/portal/solara_portal/components/__init__.py +2 -0
  166. solara/template/portal/solara_portal/components/article.py +28 -0
  167. solara/template/portal/solara_portal/components/data.py +28 -0
  168. solara/template/portal/solara_portal/components/header.py +6 -0
  169. solara/template/portal/solara_portal/components/layout.py +6 -0
  170. solara/template/portal/solara_portal/content/articles/equis-in-vidi.md +85 -0
  171. solara/template/portal/solara_portal/content/articles/substiterat-vati.md +70 -0
  172. solara/template/portal/solara_portal/data.py +60 -0
  173. solara/template/portal/solara_portal/pages/__init__.py +67 -0
  174. solara/template/portal/solara_portal/pages/article/__init__.py +26 -0
  175. solara/template/portal/solara_portal/pages/tabular.py +29 -0
  176. solara/template/portal/solara_portal/pages/viz/__init__.py +70 -0
  177. solara/template/portal/solara_portal/pages/viz/overview.py +14 -0
  178. solara/test/__init__.py +0 -0
  179. solara/test/pytest_plugin.py +783 -0
  180. solara/toestand.py +998 -0
  181. solara/util.py +348 -0
  182. solara/validate_hooks.py +258 -0
  183. solara/website/__init__.py +0 -0
  184. solara/website/assets/custom.css +444 -0
  185. solara/website/assets/images/logo-small.png +0 -0
  186. solara/website/assets/images/logo.svg +17 -0
  187. solara/website/assets/images/logo_white.svg +50 -0
  188. solara/website/assets/theme.js +8 -0
  189. solara/website/components/__init__.py +5 -0
  190. solara/website/components/algolia.py +6 -0
  191. solara/website/components/algolia.vue +24 -0
  192. solara/website/components/algolia_api.vue +202 -0
  193. solara/website/components/breadcrumbs.py +28 -0
  194. solara/website/components/contact.py +144 -0
  195. solara/website/components/docs.py +143 -0
  196. solara/website/components/header.py +75 -0
  197. solara/website/components/mailchimp.py +12 -0
  198. solara/website/components/mailchimp.vue +47 -0
  199. solara/website/components/markdown.py +99 -0
  200. solara/website/components/markdown_nav.vue +34 -0
  201. solara/website/components/notebook.py +171 -0
  202. solara/website/components/sidebar.py +105 -0
  203. solara/website/pages/__init__.py +370 -0
  204. solara/website/pages/about/__init__.py +9 -0
  205. solara/website/pages/about/about.md +3 -0
  206. solara/website/pages/apps/__init__.py +16 -0
  207. solara/website/pages/apps/authorization/__init__.py +119 -0
  208. solara/website/pages/apps/authorization/admin.py +12 -0
  209. solara/website/pages/apps/authorization/users.py +12 -0
  210. solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
  211. solara/website/pages/apps/layout-demo.py +40 -0
  212. solara/website/pages/apps/multipage/__init__.py +38 -0
  213. solara/website/pages/apps/multipage/page1.py +26 -0
  214. solara/website/pages/apps/multipage/page2.py +34 -0
  215. solara/website/pages/apps/scatter.py +136 -0
  216. solara/website/pages/apps/scrolling.py +63 -0
  217. solara/website/pages/apps/tutorial-streamlit.py +18 -0
  218. solara/website/pages/careers/__init__.py +27 -0
  219. solara/website/pages/changelog/__init__.py +10 -0
  220. solara/website/pages/changelog/changelog.md +372 -0
  221. solara/website/pages/contact/__init__.py +34 -0
  222. solara/website/pages/doc_use_download.py +85 -0
  223. solara/website/pages/documentation/__init__.py +90 -0
  224. solara/website/pages/documentation/advanced/__init__.py +9 -0
  225. solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
  226. solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +6 -0
  227. solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +196 -0
  228. solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +125 -0
  229. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +417 -0
  230. solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +69 -0
  231. solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +50 -0
  232. solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +124 -0
  233. solara/website/pages/documentation/advanced/content/15-reference/00-overview.md +3 -0
  234. solara/website/pages/documentation/advanced/content/15-reference/40-static_files.md +31 -0
  235. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +72 -0
  236. solara/website/pages/documentation/advanced/content/15-reference/60-static-site-generation.md +59 -0
  237. solara/website/pages/documentation/advanced/content/15-reference/70-search.md +34 -0
  238. solara/website/pages/documentation/advanced/content/15-reference/80-reloading.md +34 -0
  239. solara/website/pages/documentation/advanced/content/15-reference/90-notebook-support.md +7 -0
  240. solara/website/pages/documentation/advanced/content/15-reference/95-caching.md +148 -0
  241. solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
  242. solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +35 -0
  243. solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +42 -0
  244. solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +28 -0
  245. solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +108 -0
  246. solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +23 -0
  247. solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +192 -0
  248. solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +166 -0
  249. solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +18 -0
  250. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +256 -0
  251. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +108 -0
  252. solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +12 -0
  253. solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +7 -0
  254. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +187 -0
  255. solara/website/pages/documentation/advanced/content/40-development/00-overview.md +0 -0
  256. solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +45 -0
  257. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +76 -0
  258. solara/website/pages/documentation/api/__init__.py +19 -0
  259. solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
  260. solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +22 -0
  261. solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +20 -0
  262. solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +20 -0
  263. solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +20 -0
  264. solara/website/pages/documentation/api/hooks/__init__.py +9 -0
  265. solara/website/pages/documentation/api/hooks/use_cross_filter.py +23 -0
  266. solara/website/pages/documentation/api/hooks/use_dark_effective.py +12 -0
  267. solara/website/pages/documentation/api/hooks/use_effect.md +43 -0
  268. solara/website/pages/documentation/api/hooks/use_effect.py +9 -0
  269. solara/website/pages/documentation/api/hooks/use_exception.py +31 -0
  270. solara/website/pages/documentation/api/hooks/use_memo.md +16 -0
  271. solara/website/pages/documentation/api/hooks/use_memo.py +9 -0
  272. solara/website/pages/documentation/api/hooks/use_previous.py +30 -0
  273. solara/website/pages/documentation/api/hooks/use_reactive.py +16 -0
  274. solara/website/pages/documentation/api/hooks/use_state.py +10 -0
  275. solara/website/pages/documentation/api/hooks/use_state_or_update.py +66 -0
  276. solara/website/pages/documentation/api/hooks/use_thread.md +64 -0
  277. solara/website/pages/documentation/api/hooks/use_thread.py +42 -0
  278. solara/website/pages/documentation/api/hooks/use_trait_observe.py +12 -0
  279. solara/website/pages/documentation/api/routing/__init__.py +9 -0
  280. solara/website/pages/documentation/api/routing/generate_routes.py +10 -0
  281. solara/website/pages/documentation/api/routing/generate_routes_directory.py +10 -0
  282. solara/website/pages/documentation/api/routing/resolve_path.py +35 -0
  283. solara/website/pages/documentation/api/routing/route.py +29 -0
  284. solara/website/pages/documentation/api/routing/use_route.py +76 -0
  285. solara/website/pages/documentation/api/routing/use_router.py +16 -0
  286. solara/website/pages/documentation/api/utilities/__init__.py +9 -0
  287. solara/website/pages/documentation/api/utilities/component_vue.py +10 -0
  288. solara/website/pages/documentation/api/utilities/computed.py +16 -0
  289. solara/website/pages/documentation/api/utilities/display.py +16 -0
  290. solara/website/pages/documentation/api/utilities/get_kernel_id.py +16 -0
  291. solara/website/pages/documentation/api/utilities/get_session_id.py +16 -0
  292. solara/website/pages/documentation/api/utilities/memoize.py +35 -0
  293. solara/website/pages/documentation/api/utilities/on_kernel_start.py +44 -0
  294. solara/website/pages/documentation/api/utilities/reactive.py +16 -0
  295. solara/website/pages/documentation/api/utilities/widget.py +104 -0
  296. solara/website/pages/documentation/components/__init__.py +12 -0
  297. solara/website/pages/documentation/components/advanced/__init__.py +9 -0
  298. solara/website/pages/documentation/components/advanced/link.py +25 -0
  299. solara/website/pages/documentation/components/advanced/meta.py +17 -0
  300. solara/website/pages/documentation/components/advanced/style.py +43 -0
  301. solara/website/pages/documentation/components/common.py +9 -0
  302. solara/website/pages/documentation/components/data/__init__.py +9 -0
  303. solara/website/pages/documentation/components/data/dataframe.py +44 -0
  304. solara/website/pages/documentation/components/data/pivot_table.py +81 -0
  305. solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
  306. solara/website/pages/documentation/components/enterprise/avatar.py +24 -0
  307. solara/website/pages/documentation/components/enterprise/avatar_menu.py +25 -0
  308. solara/website/pages/documentation/components/input/__init__.py +9 -0
  309. solara/website/pages/documentation/components/input/button.py +23 -0
  310. solara/website/pages/documentation/components/input/checkbox.py +10 -0
  311. solara/website/pages/documentation/components/input/file_browser.py +30 -0
  312. solara/website/pages/documentation/components/input/file_drop.py +76 -0
  313. solara/website/pages/documentation/components/input/input.py +43 -0
  314. solara/website/pages/documentation/components/input/select.py +22 -0
  315. solara/website/pages/documentation/components/input/slider.py +29 -0
  316. solara/website/pages/documentation/components/input/switch.py +10 -0
  317. solara/website/pages/documentation/components/input/togglebuttons.py +21 -0
  318. solara/website/pages/documentation/components/lab/__init__.py +9 -0
  319. solara/website/pages/documentation/components/lab/chat.py +109 -0
  320. solara/website/pages/documentation/components/lab/confirmation_dialog.py +55 -0
  321. solara/website/pages/documentation/components/lab/cookies_headers.py +48 -0
  322. solara/website/pages/documentation/components/lab/input_date.py +20 -0
  323. solara/website/pages/documentation/components/lab/input_time.py +15 -0
  324. solara/website/pages/documentation/components/lab/menu.py +22 -0
  325. solara/website/pages/documentation/components/lab/tab.py +25 -0
  326. solara/website/pages/documentation/components/lab/tabs.py +45 -0
  327. solara/website/pages/documentation/components/lab/task.py +11 -0
  328. solara/website/pages/documentation/components/lab/theming.py +74 -0
  329. solara/website/pages/documentation/components/lab/use_task.py +11 -0
  330. solara/website/pages/documentation/components/layout/__init__.py +9 -0
  331. solara/website/pages/documentation/components/layout/app_bar.py +16 -0
  332. solara/website/pages/documentation/components/layout/app_bar_title.py +16 -0
  333. solara/website/pages/documentation/components/layout/app_layout.py +24 -0
  334. solara/website/pages/documentation/components/layout/card.py +15 -0
  335. solara/website/pages/documentation/components/layout/card_actions.py +16 -0
  336. solara/website/pages/documentation/components/layout/column.py +30 -0
  337. solara/website/pages/documentation/components/layout/columns.py +27 -0
  338. solara/website/pages/documentation/components/layout/columns_responsive.py +66 -0
  339. solara/website/pages/documentation/components/layout/details.py +13 -0
  340. solara/website/pages/documentation/components/layout/griddraggable.py +62 -0
  341. solara/website/pages/documentation/components/layout/gridfixed.py +19 -0
  342. solara/website/pages/documentation/components/layout/hbox.py +18 -0
  343. solara/website/pages/documentation/components/layout/row.py +30 -0
  344. solara/website/pages/documentation/components/layout/sidebar.py +24 -0
  345. solara/website/pages/documentation/components/layout/vbox.py +19 -0
  346. solara/website/pages/documentation/components/output/__init__.py +9 -0
  347. solara/website/pages/documentation/components/output/file_download.py +11 -0
  348. solara/website/pages/documentation/components/output/html.py +19 -0
  349. solara/website/pages/documentation/components/output/image.py +11 -0
  350. solara/website/pages/documentation/components/output/markdown.py +57 -0
  351. solara/website/pages/documentation/components/output/markdown_editor.py +51 -0
  352. solara/website/pages/documentation/components/output/sql_code.py +83 -0
  353. solara/website/pages/documentation/components/output/tooltip.py +11 -0
  354. solara/website/pages/documentation/components/page/__init__.py +9 -0
  355. solara/website/pages/documentation/components/page/head.py +15 -0
  356. solara/website/pages/documentation/components/page/title.py +25 -0
  357. solara/website/pages/documentation/components/status/__init__.py +9 -0
  358. solara/website/pages/documentation/components/status/error.py +39 -0
  359. solara/website/pages/documentation/components/status/info.py +39 -0
  360. solara/website/pages/documentation/components/status/progress.py +10 -0
  361. solara/website/pages/documentation/components/status/spinner.py +11 -0
  362. solara/website/pages/documentation/components/status/success.py +40 -0
  363. solara/website/pages/documentation/components/status/warning.py +47 -0
  364. solara/website/pages/documentation/components/viz/__init__.py +9 -0
  365. solara/website/pages/documentation/components/viz/altair.py +42 -0
  366. solara/website/pages/documentation/components/viz/echarts.py +77 -0
  367. solara/website/pages/documentation/components/viz/matplotlib.py +30 -0
  368. solara/website/pages/documentation/components/viz/plotly.py +63 -0
  369. solara/website/pages/documentation/components/viz/plotly_express.py +41 -0
  370. solara/website/pages/documentation/examples/__init__.py +54 -0
  371. solara/website/pages/documentation/examples/ai/__init__.py +11 -0
  372. solara/website/pages/documentation/examples/ai/chatbot.py +113 -0
  373. solara/website/pages/documentation/examples/ai/tokenizer.py +107 -0
  374. solara/website/pages/documentation/examples/basics/__init__.py +10 -0
  375. solara/website/pages/documentation/examples/basics/sine.py +28 -0
  376. solara/website/pages/documentation/examples/fullscreen/__init__.py +10 -0
  377. solara/website/pages/documentation/examples/fullscreen/authorization.py +3 -0
  378. solara/website/pages/documentation/examples/fullscreen/layout_demo.py +3 -0
  379. solara/website/pages/documentation/examples/fullscreen/multipage.py +3 -0
  380. solara/website/pages/documentation/examples/fullscreen/scatter.py +3 -0
  381. solara/website/pages/documentation/examples/fullscreen/scrolling.py +3 -0
  382. solara/website/pages/documentation/examples/fullscreen/tutorial_streamlit.py +3 -0
  383. solara/website/pages/documentation/examples/general/__init__.py +10 -0
  384. solara/website/pages/documentation/examples/general/custom_storage.py +70 -0
  385. solara/website/pages/documentation/examples/general/deploy_model.py +115 -0
  386. solara/website/pages/documentation/examples/general/live_update.py +32 -0
  387. solara/website/pages/documentation/examples/general/login_oauth.py +81 -0
  388. solara/website/pages/documentation/examples/general/mycard.vue +58 -0
  389. solara/website/pages/documentation/examples/general/pokemon_search.py +51 -0
  390. solara/website/pages/documentation/examples/general/vue_component.py +50 -0
  391. solara/website/pages/documentation/examples/ipycanvas.py +49 -0
  392. solara/website/pages/documentation/examples/libraries/__init__.py +10 -0
  393. solara/website/pages/documentation/examples/libraries/altair.py +65 -0
  394. solara/website/pages/documentation/examples/libraries/bqplot.py +39 -0
  395. solara/website/pages/documentation/examples/libraries/ipyleaflet.py +33 -0
  396. solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +66 -0
  397. solara/website/pages/documentation/examples/utilities/__init__.py +10 -0
  398. solara/website/pages/documentation/examples/utilities/calculator.py +157 -0
  399. solara/website/pages/documentation/examples/utilities/countdown_timer.py +62 -0
  400. solara/website/pages/documentation/examples/utilities/todo.py +196 -0
  401. solara/website/pages/documentation/examples/visualization/__init__.py +6 -0
  402. solara/website/pages/documentation/examples/visualization/annotator.py +67 -0
  403. solara/website/pages/documentation/examples/visualization/linked_views.py +81 -0
  404. solara/website/pages/documentation/examples/visualization/plotly.py +44 -0
  405. solara/website/pages/documentation/faq/__init__.py +12 -0
  406. solara/website/pages/documentation/faq/content/99-faq.md +112 -0
  407. solara/website/pages/documentation/getting_started/__init__.py +9 -0
  408. solara/website/pages/documentation/getting_started/content/00-quickstart.md +107 -0
  409. solara/website/pages/documentation/getting_started/content/01-introduction.md +125 -0
  410. solara/website/pages/documentation/getting_started/content/02-installing.md +134 -0
  411. solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
  412. solara/website/pages/documentation/getting_started/content/04-tutorials/10_data_science.py +13 -0
  413. solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +89 -0
  414. solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +124 -0
  415. solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +146 -0
  416. solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +144 -0
  417. solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +65 -0
  418. solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz +0 -0
  419. solara/website/pages/documentation/getting_started/content/04-tutorials/_data_science.ipynb +445 -0
  420. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +1021 -0
  421. solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
  422. solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +228 -0
  423. solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +278 -0
  424. solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
  425. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +305 -0
  426. solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +80 -0
  427. solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
  428. solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +26 -0
  429. solara/website/pages/docutils.py +38 -0
  430. solara/website/pages/home.vue +1199 -0
  431. solara/website/pages/our_team/__init__.py +83 -0
  432. solara/website/pages/pricing/__init__.py +31 -0
  433. solara/website/pages/roadmap/__init__.py +11 -0
  434. solara/website/pages/roadmap/roadmap.md +47 -0
  435. solara/website/pages/scale_ipywidgets.py +45 -0
  436. solara/website/pages/showcase/__init__.py +105 -0
  437. solara/website/pages/showcase/domino_code_assist.py +60 -0
  438. solara/website/pages/showcase/planeto_tessa.py +19 -0
  439. solara/website/pages/showcase/solara_dev.py +54 -0
  440. solara/website/pages/showcase/solarathon_2023_team_2.py +22 -0
  441. solara/website/pages/showcase/solarathon_2023_team_4.py +22 -0
  442. solara/website/pages/showcase/solarathon_2023_team_5.py +23 -0
  443. solara/website/pages/showcase/solarathon_2023_team_6.py +34 -0
  444. solara/website/pages/showcase/wanderlust.py +27 -0
  445. solara/website/public/beach.jpeg +0 -0
  446. solara/website/public/logo.svg +6 -0
  447. solara/website/public/social/discord.svg +1 -0
  448. solara/website/public/social/github.svg +1 -0
  449. solara/website/public/social/twitter.svg +3 -0
  450. solara/website/public/success.html +25 -0
  451. solara/website/templates/index.html.j2 +117 -0
  452. solara/website/utils.py +51 -0
  453. solara/widgets/__init__.py +1 -0
  454. solara/widgets/vue/gridlayout.vue +107 -0
  455. solara/widgets/vue/html.vue +4 -0
  456. solara/widgets/vue/navigator.vue +134 -0
  457. solara/widgets/vue/vegalite.vue +130 -0
  458. solara/widgets/widgets.py +74 -0
  459. solara_ui-1.45.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  460. solara_ui-1.45.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  461. solara_ui-1.45.0.dist-info/METADATA +162 -0
  462. solara_ui-1.45.0.dist-info/RECORD +464 -0
  463. solara_ui-1.45.0.dist-info/WHEEL +4 -0
  464. solara_ui-1.45.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,467 @@
1
+ import asyncio
2
+ import sys
3
+
4
+ try:
5
+ import contextvars
6
+ except ModuleNotFoundError:
7
+ contextvars = None # type: ignore
8
+
9
+ import concurrent.futures
10
+ import contextlib
11
+ import dataclasses
12
+ import enum
13
+ import logging
14
+ import os
15
+ import pickle
16
+ import threading
17
+ import time
18
+ import typing
19
+ from pathlib import Path
20
+ from typing import Any, Callable, Dict, List, Optional, cast
21
+
22
+ import ipywidgets as widgets
23
+ import reacton
24
+ from ipywidgets import DOMWidget, Widget
25
+
26
+ import solara.server.settings
27
+ import solara.util
28
+
29
+ from . import kernel, websocket
30
+ from .. import lifecycle
31
+ from .kernel import Kernel, WebsocketStreamWrapper
32
+
33
+ WebSocket = Any
34
+ logger = logging.getLogger("solara.server.app")
35
+
36
+
37
+ class Local(threading.local):
38
+ kernel_context_stack: Optional[List[Optional["VirtualKernelContext"]]] = None
39
+
40
+
41
+ local = Local()
42
+
43
+
44
+ class PageStatus(enum.Enum):
45
+ CONNECTED = "connected"
46
+ DISCONNECTED = "disconnected"
47
+ CLOSED = "closed"
48
+
49
+
50
+ @dataclasses.dataclass
51
+ class VirtualKernelContext:
52
+ id: str
53
+ kernel: kernel.Kernel
54
+ # we keep track of the session id to prevent kernel hijacking
55
+ # to 'steal' a kernel, one would need to know the session id
56
+ # *and* the kernel id
57
+ session_id: str
58
+ control_sockets: List[WebSocket] = dataclasses.field(default_factory=list)
59
+ # this is the 'private' version of the normally global ipywidgets.Widgets.widget dict
60
+ # see patch.py
61
+ widgets: Dict[str, Widget] = dataclasses.field(default_factory=dict)
62
+ # same, for ipyvue templates
63
+ # see patch.py
64
+ templates: Dict[str, Widget] = dataclasses.field(default_factory=dict)
65
+ user_dicts: Dict[str, Dict] = dataclasses.field(default_factory=dict)
66
+ # anything we need to attach to the context
67
+ # e.g. for a react app the render context, so that we can store/restore the state
68
+ app_object: Optional[Any] = None
69
+ reload: Callable = lambda: None # noqa: E731
70
+ state: Any = None
71
+ container: Optional[DOMWidget] = None
72
+ # we track which pages are connected to implement kernel culling
73
+ page_status: Dict[str, PageStatus] = dataclasses.field(default_factory=dict)
74
+ # only used for testing
75
+ _last_kernel_cull_task: "Optional[asyncio.Future[None]]" = None
76
+ _last_kernel_cull_future: "Optional[concurrent.futures.Future[None]]" = None
77
+ closed_event: threading.Event = dataclasses.field(default_factory=threading.Event)
78
+ _on_close_callbacks: List[Callable[[], None]] = dataclasses.field(default_factory=list)
79
+ lock: threading.RLock = dataclasses.field(default_factory=threading.RLock)
80
+ event_loop: asyncio.AbstractEventLoop = dataclasses.field(default_factory=asyncio.get_event_loop)
81
+
82
+ def __post_init__(self):
83
+ with self:
84
+ for f, *_ in lifecycle._on_kernel_start_callbacks:
85
+ cleanup = f()
86
+ if cleanup:
87
+ self.on_close(cleanup)
88
+
89
+ def restart(self):
90
+ # should we do this, or maybe close the context and create a new one?
91
+ with self:
92
+ for f in reversed(self._on_close_callbacks):
93
+ f()
94
+ self._on_close_callbacks.clear()
95
+ self.__post_init__()
96
+
97
+ def display(self, *args):
98
+ print(args) # noqa
99
+
100
+ def on_close(self, f: Callable[[], None]):
101
+ self._on_close_callbacks.append(f)
102
+
103
+ def __enter__(self):
104
+ if local.kernel_context_stack is None:
105
+ local.kernel_context_stack = []
106
+ key = get_current_thread_key()
107
+ local.kernel_context_stack.append(current_context.get(key, None))
108
+ current_context[key] = self
109
+
110
+ def __exit__(self, *args):
111
+ key = get_current_thread_key()
112
+ assert local.kernel_context_stack is not None
113
+ current_context[key] = local.kernel_context_stack.pop()
114
+
115
+ def close(self):
116
+ with self, self.lock:
117
+ for key in self.page_status:
118
+ self.page_status[key] = PageStatus.CLOSED
119
+ if self._last_kernel_cull_task:
120
+ self._last_kernel_cull_task.cancel()
121
+ if self._last_kernel_cull_future:
122
+ self._last_kernel_cull_future.cancel()
123
+ if self.closed_event.is_set():
124
+ logger.error("Tried to close a kernel context that is already closed: %s", self.id)
125
+ return
126
+ logger.info("Shut down virtual kernel: %s", self.id)
127
+ for f in reversed(self._on_close_callbacks):
128
+ f()
129
+ if self.app_object is not None:
130
+ if isinstance(self.app_object, reacton.core._RenderContext):
131
+ try:
132
+ self.app_object.close()
133
+ except Exception as e:
134
+ logger.exception("Could not close render context: %s", e)
135
+ # we want to continue, so we at least close all widgets
136
+ widgets.Widget.close_all()
137
+ # what if we reference each other
138
+ # import gc
139
+ # gc.collect()
140
+ self.kernel.close()
141
+ self.kernel = None # type: ignore
142
+ if self.id in contexts:
143
+ del contexts[self.id]
144
+ del current_context[get_current_thread_key()]
145
+ # We saw in memleak_test that there are sometimes other entries in current_context
146
+ # In which _DummyThread's reference this context, so we remove those references too
147
+ # TODO: Think about what to do with those Threads
148
+ _contexts = current_context.copy()
149
+ for key, _ctx in _contexts.items():
150
+ if _ctx is self:
151
+ del current_context[key]
152
+ self.closed_event.set()
153
+
154
+ def _state_reset(self):
155
+ state_directory = Path(".") / "states"
156
+ state_directory.mkdir(exist_ok=True)
157
+ path = state_directory / f"{self.id}.pickle"
158
+ path = path.absolute()
159
+ try:
160
+ path.unlink()
161
+ except: # noqa
162
+ pass
163
+ del contexts[self.id]
164
+ key = get_current_thread_key()
165
+ del current_context[key]
166
+
167
+ def state_save(self, state_directory: os.PathLike):
168
+ path = Path(state_directory) / f"{self.id}.pickle"
169
+ render_context = self.app_object
170
+ if render_context is not None:
171
+ render_context = cast(reacton.core._RenderContext, render_context)
172
+ state = render_context.state_get()
173
+ with path.open("wb") as f:
174
+ logger.debug("State: %r", state)
175
+ pickle.dump(state, f)
176
+
177
+ def page_connect(self, page_id: str):
178
+ if self.closed_event.is_set():
179
+ raise RuntimeError("Cannot connect a page to a closed kernel")
180
+ logger.info("Connect page %s for kernel %s", page_id, self.id)
181
+ with self.lock:
182
+ if self.closed_event.is_set():
183
+ raise RuntimeError("Cannot connect a page to a closed kernel")
184
+ if page_id in self.page_status and self.page_status.get(page_id) == PageStatus.CLOSED:
185
+ raise RuntimeError("Cannot connect a page that is already closed")
186
+ self.page_status[page_id] = PageStatus.CONNECTED
187
+ if self._last_kernel_cull_task:
188
+ logger.info("Cancelling previous kernel cull task for virtual kernel %s", self.id)
189
+ self._last_kernel_cull_task.cancel()
190
+
191
+ def _bump_kernel_cull(self):
192
+ async def kernel_cull():
193
+ try:
194
+ cull_timeout_sleep_seconds = solara.util.parse_timedelta(solara.server.settings.kernel.cull_timeout)
195
+ logger.info("Scheduling kernel cull, will wait for max %s before shutting down the virtual kernel %s", cull_timeout_sleep_seconds, self.id)
196
+ await asyncio.sleep(cull_timeout_sleep_seconds)
197
+ logger.info("Timeout reached, checking if we should be shutting down virtual kernel %s", self.id)
198
+ with self.lock:
199
+ has_connected_pages = PageStatus.CONNECTED in self.page_status.values()
200
+ if has_connected_pages:
201
+ logger.info("We have (re)connected pages, keeping the virtual kernel %s alive", self.id)
202
+ else:
203
+ logger.info("No connected pages, and timeout reached, shutting down virtual kernel %s", self.id)
204
+ self.close()
205
+ if current_event_loop is not None and future is not None:
206
+ try:
207
+ current_event_loop.call_soon_threadsafe(future.set_result, None)
208
+ except RuntimeError:
209
+ pass # event loop already closed, happens during testing
210
+ except asyncio.CancelledError:
211
+ if current_event_loop is not None and future is not None:
212
+ try:
213
+ if sys.version_info >= (3, 9):
214
+ current_event_loop.call_soon_threadsafe(future.cancel, "cancelled because a new cull task was scheduled")
215
+ else:
216
+ current_event_loop.call_soon_threadsafe(future.cancel)
217
+ except RuntimeError:
218
+ pass # event loop already closed, happens during testing
219
+ raise
220
+
221
+ async def create_task():
222
+ task = asyncio.create_task(kernel_cull())
223
+ # create a reference to the task so we can cancel it later
224
+ self._last_kernel_cull_task = task
225
+ await task
226
+
227
+ with self.lock:
228
+ future: "Optional[asyncio.Future[None]]" = None
229
+ current_event_loop: Optional[asyncio.AbstractEventLoop] = None
230
+ try:
231
+ future = asyncio.Future()
232
+ current_event_loop = asyncio.get_event_loop()
233
+ except RuntimeError:
234
+ pass
235
+ if self._last_kernel_cull_task:
236
+ logger.info("Cancelling previous kernel cull tas for virtual kernel %s", self.id)
237
+ self._last_kernel_cull_task.cancel()
238
+
239
+ logger.info("Scheduling kernel cull for virtual kernel %s", self.id)
240
+
241
+ async def create_task():
242
+ task = asyncio.create_task(kernel_cull())
243
+ # create a reference to the task so we can cancel it later
244
+ self._last_kernel_cull_task = task
245
+ try:
246
+ await task
247
+ except RuntimeError:
248
+ pass # event loop already closed, happens during testing
249
+
250
+ self._last_kernel_cull_future = asyncio.run_coroutine_threadsafe(create_task(), keep_alive_event_loop)
251
+ return future
252
+
253
+ def page_disconnect(self, page_id: str) -> "Optional[asyncio.Future[None]]":
254
+ """Signal that a page has disconnected, and schedule a kernel cull if needed.
255
+
256
+ During the kernel reconnect window, we will keep the kernel alive, even if all pages have disconnected.
257
+
258
+ Will return a future that is set when the kernel cull is done, when an event loop is available.
259
+ The scheduled kernel cull can be cancelled when a new page connects, a new disconnect is scheduled,
260
+ or a page if explicitly closed.
261
+ """
262
+
263
+ logger.info("Disconnect page %s for kernel %s", page_id, self.id)
264
+ future: "asyncio.Future[None]" = asyncio.Future()
265
+ with self.lock:
266
+ if self.page_status[page_id] == PageStatus.CLOSED:
267
+ # this happens when the close beackon call happens before the websocket disconnect
268
+ logger.info("Page %s already closed for kernel %s", page_id, self.id)
269
+ future.set_result(None)
270
+ return future
271
+ assert self.page_status[page_id] == PageStatus.CONNECTED, "cannot disconnect a page that is in state: %r" % self.page_status[page_id]
272
+ self.page_status[page_id] = PageStatus.DISCONNECTED
273
+ has_connected_pages = PageStatus.CONNECTED in self.page_status.values()
274
+ if not has_connected_pages:
275
+ # when we have no connected pages, we will schedule a kernel cull
276
+ future = self._bump_kernel_cull()
277
+ else:
278
+ logger.info("Still have connected pages, do nothing for kernel %s", self.id)
279
+ future.set_result(None)
280
+ return future
281
+
282
+ def page_close(self, page_id: str):
283
+ """Signal that a page has closed, close the context if needed and schedule a kernel cull if needed.
284
+
285
+ Closing the browser tab or a page navigation means an explicit close, which is
286
+ different from a websocket/page disconnect, which we might want to recover from.
287
+
288
+ """
289
+ future: "Optional[asyncio.Future[None]]" = None
290
+
291
+ try:
292
+ future = asyncio.Future()
293
+ except RuntimeError:
294
+ pass
295
+ else:
296
+ future.set_result(None)
297
+
298
+ logger.info("page status: %s", self.page_status)
299
+ with self.lock:
300
+ if self.closed_event.is_set():
301
+ logger.info("Kernel %s was already closed when page %s attempted to close", self.id, page_id)
302
+ return
303
+ if self.page_status[page_id] == PageStatus.CLOSED:
304
+ logger.info("Page %s already closed for kernel %s", page_id, self.id)
305
+ return
306
+ self.page_status[page_id] = PageStatus.CLOSED
307
+ logger.info("Close page %s for kernel %s", page_id, self.id)
308
+ has_connected_pages = PageStatus.CONNECTED in self.page_status.values()
309
+ has_disconnected_pages = PageStatus.DISCONNECTED in self.page_status.values()
310
+ # if we have disconnected pages, we may have cancelled the kernel cull task
311
+ # if we still have connected pages, it will go to a disconnected state again
312
+ # which will also trigger a new kernel cull
313
+ if has_disconnected_pages:
314
+ future = self._bump_kernel_cull()
315
+ if not (has_connected_pages or has_disconnected_pages):
316
+ logger.info("No connected or disconnected pages, shutting down virtual kernel %s", self.id)
317
+ self.close()
318
+ else:
319
+ logger.info("Still have connected or disconnected pages, keeping virtual kernel %s alive", self.id)
320
+ return future
321
+
322
+
323
+ try:
324
+ # Normal Python
325
+ keep_alive_event_loop = asyncio.new_event_loop()
326
+
327
+ def _run():
328
+ asyncio.set_event_loop(keep_alive_event_loop)
329
+ try:
330
+ keep_alive_event_loop.run_forever()
331
+ except Exception:
332
+ logger.exception("Error in keep alive event loop")
333
+ raise
334
+
335
+ threading.Thread(target=_run, daemon=True).start()
336
+ except RuntimeError:
337
+ # Emscripten/pyodide/lite
338
+ keep_alive_event_loop = asyncio.get_event_loop()
339
+
340
+ contexts: Dict[str, VirtualKernelContext] = {}
341
+ # maps from thread key to VirtualKernelContext, if VirtualKernelContext is None, it exists, but is not set as current
342
+ current_context: Dict[str, Optional[VirtualKernelContext]] = {}
343
+
344
+
345
+ def create_dummy_context():
346
+ from . import kernel
347
+
348
+ kernel_context = VirtualKernelContext(
349
+ id="dummy",
350
+ session_id="dummy",
351
+ kernel=kernel.Kernel(),
352
+ )
353
+ return kernel_context
354
+
355
+
356
+ if contextvars is not None:
357
+ if typing.TYPE_CHECKING:
358
+ async_context_id = contextvars.ContextVar[str]("async_context_id")
359
+ else:
360
+ async_context_id = contextvars.ContextVar("async_context_id")
361
+ async_context_id.set("default")
362
+ else:
363
+ async_context_id = None
364
+
365
+
366
+ def get_current_thread_key() -> str:
367
+ if not solara.server.settings.kernel.threaded:
368
+ if async_context_id is not None:
369
+ try:
370
+ key = async_context_id.get()
371
+ except LookupError:
372
+ raise RuntimeError("no kernel context set")
373
+ else:
374
+ raise RuntimeError("No threading support, and no contextvars support (Python 3.6 is not supported for this)")
375
+ else:
376
+ thread = threading.current_thread()
377
+ key = get_thread_key(thread)
378
+ return key
379
+
380
+
381
+ def get_thread_key(thread: threading.Thread) -> str:
382
+ if not solara.server.settings.kernel.threaded:
383
+ if async_context_id is not None:
384
+ return async_context_id.get()
385
+ thread_key = thread._name + str(thread._ident) # type: ignore
386
+ return thread_key
387
+
388
+
389
+ def set_context_for_thread(context: VirtualKernelContext, thread: threading.Thread):
390
+ key = get_thread_key(thread)
391
+ current_context[key] = context
392
+
393
+
394
+ def clear_context_for_thread(thread: threading.Thread):
395
+ key = get_thread_key(thread)
396
+ current_context.pop(key, None)
397
+
398
+
399
+ def has_current_context() -> bool:
400
+ thread_key = get_current_thread_key()
401
+ return (thread_key in current_context) and (current_context[thread_key] is not None)
402
+
403
+
404
+ def get_current_context() -> VirtualKernelContext:
405
+ thread_key = get_current_thread_key()
406
+ if thread_key not in current_context:
407
+ raise RuntimeError(
408
+ f"Tried to get the current context for thread {thread_key}, but no known context found. This might be a bug in Solara. "
409
+ f"(known contexts: {list(current_context.keys())}"
410
+ )
411
+ context = current_context[thread_key]
412
+ if context is None:
413
+ raise RuntimeError(
414
+ f"Tried to get the current context for thread {thread_key!r}, although the context is know, it was not set for this thread. "
415
+ + "This might be a bug in Solara."
416
+ )
417
+ return context
418
+
419
+
420
+ def set_current_context(context: Optional[VirtualKernelContext]):
421
+ thread_key = get_current_thread_key()
422
+ current_context[thread_key] = context
423
+
424
+
425
+ @contextlib.contextmanager
426
+ def without_context():
427
+ context = None
428
+ try:
429
+ context = get_current_context()
430
+ except RuntimeError:
431
+ pass
432
+ thread_key = get_current_thread_key()
433
+ current_context[thread_key] = None
434
+ try:
435
+ yield
436
+ finally:
437
+ current_context[thread_key] = context
438
+
439
+
440
+ def initialize_virtual_kernel(session_id: str, kernel_id: str, websocket: websocket.WebsocketWrapper):
441
+ from solara.server import app as appmodule
442
+
443
+ if kernel_id in contexts:
444
+ logger.info("reusing virtual kernel: %s", kernel_id)
445
+ context = contexts[kernel_id]
446
+ if context.session_id != session_id:
447
+ logger.critical("Session id mismatch when reusing kernel (hack attempt?): %s != %s", context.session_id, session_id)
448
+ websocket.send_text("Session id mismatch when reusing kernel (hack attempt?)")
449
+ # to avoid very fast reconnects (we are in a thread anyway)
450
+ time.sleep(0.5)
451
+ raise ValueError("Session id mismatch")
452
+ kernel = context.kernel
453
+ else:
454
+ kernel = Kernel()
455
+ logger.info("new virtual kernel: %s", kernel_id)
456
+ context = contexts[kernel_id] = VirtualKernelContext(id=kernel_id, session_id=session_id, kernel=kernel, control_sockets=[], widgets={}, templates={})
457
+
458
+ with context:
459
+ widgets.register_comm_target(kernel)
460
+ appmodule.register_solara_comm_target(kernel)
461
+ with context:
462
+ assert has_current_context()
463
+ assert kernel is Kernel.instance()
464
+ kernel.shell_stream = WebsocketStreamWrapper(websocket, "shell")
465
+ kernel.control_stream = WebsocketStreamWrapper(websocket, "control")
466
+ kernel.session.websockets.add(websocket)
467
+ return context