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
solara/toestand.py ADDED
@@ -0,0 +1,998 @@
1
+ import dataclasses
2
+ import inspect
3
+ import logging
4
+ import os
5
+ import sys
6
+ import threading
7
+ from types import FrameType
8
+ import warnings
9
+ import copy
10
+ from abc import ABC, abstractmethod
11
+ from collections import defaultdict
12
+ from operator import getitem
13
+ from typing import (
14
+ Any,
15
+ Callable,
16
+ ContextManager,
17
+ Dict,
18
+ Generic,
19
+ Optional,
20
+ Set,
21
+ Tuple,
22
+ TypeVar,
23
+ Union,
24
+ cast,
25
+ overload,
26
+ )
27
+
28
+ import react_ipywidgets as react
29
+ import reacton.core
30
+ from solara.util import equals_extra
31
+
32
+ import solara
33
+ import solara.settings
34
+ from solara import _using_solara_server
35
+ from solara.util import nullcontext
36
+
37
+ T = TypeVar("T")
38
+ TS = TypeVar("TS")
39
+ S = TypeVar("S") # used for state
40
+ logger = logging.getLogger("solara.toestand")
41
+
42
+ _DEBUG = False
43
+
44
+
45
+ class ThreadLocal(threading.local):
46
+ reactive_used: Optional[Set["ValueBase"]] = None
47
+ reactive_watch: Optional[Callable[["ValueBase"], None]] = None
48
+
49
+
50
+ thread_local = ThreadLocal()
51
+
52
+
53
+ # these hooks should go into react-ipywidgets
54
+ def use_sync_external_store(subscribe: Callable[[Callable[[], None]], Callable[[], None]], get_snapshot: Callable[[], Any]):
55
+ _, set_counter = react.use_state(0)
56
+
57
+ def force_update():
58
+ set_counter(lambda x: x + 1)
59
+
60
+ state = get_snapshot()
61
+ prev_state = react.use_ref(state)
62
+
63
+ def update_state():
64
+ prev_state.current = state
65
+
66
+ react.use_effect(update_state)
67
+
68
+ def on_store_change(_ignore_new_state=None):
69
+ new_state = get_snapshot()
70
+ if not equals_extra(new_state, prev_state.current):
71
+ prev_state.current = new_state
72
+ force_update()
73
+
74
+ react.use_effect(lambda: subscribe(on_store_change), [])
75
+ return state
76
+
77
+
78
+ def use_sync_external_store_with_selector(subscribe, get_snapshot: Callable[[], Any], selector):
79
+ return use_sync_external_store(subscribe, lambda: selector(get_snapshot()))
80
+
81
+
82
+ def merge_state(d1: S, **kwargs) -> S:
83
+ if dataclasses.is_dataclass(d1):
84
+ return dataclasses.replace(d1, **kwargs) # type: ignore
85
+ if "pydantic" in sys.modules and isinstance(d1, sys.modules["pydantic"].BaseModel):
86
+ module = sys.modules["pydantic"]
87
+ version_major = int(module.__version__.split(".")[0])
88
+ if version_major >= 2:
89
+ return d1.model_copy(update=kwargs)
90
+ else:
91
+ return d1.copy(update=kwargs)
92
+ return cast(S, {**cast(dict, d1), **kwargs})
93
+
94
+
95
+ class ValueBase(Generic[T]):
96
+ def __init__(self, merge: Callable = merge_state, equals=equals_extra):
97
+ self.merge = merge
98
+ self.equals = equals
99
+ self.listeners: Dict[str, Set[Tuple[Callable[[T], None], Optional[ContextManager]]]] = defaultdict(set)
100
+ self.listeners2: Dict[str, Set[Tuple[Callable[[T, T], None], Optional[ContextManager]]]] = defaultdict(set)
101
+
102
+ # make sure all boolean operations give type errors
103
+ if not solara.settings.main.allow_reactive_boolean:
104
+
105
+ def __bool__(self):
106
+ raise TypeError("Reactive vars are not allowed in boolean expressions, did you mean to use .value?")
107
+
108
+ def __eq__(self, other):
109
+ raise TypeError(f"'==' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
110
+
111
+ def __ne__(self, other):
112
+ raise TypeError(f"'!=' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
113
+
114
+ # If we explicitly define __eq__, we need to explicitly define __hash__ as well
115
+ # Otherwise our class is marked unhashable
116
+ __hash__ = object.__hash__
117
+
118
+ def __lt__(self, other):
119
+ raise TypeError(f"'<' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
120
+
121
+ def __le__(self, other):
122
+ raise TypeError(f"'<=' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
123
+
124
+ def __gt__(self, other):
125
+ raise TypeError(f"'>' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
126
+
127
+ def __ge__(self, other):
128
+ raise TypeError(f"'>=' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
129
+
130
+ def __len__(self):
131
+ raise TypeError("'len(...)' is not supported for a Reactive, did you mean to use .value?")
132
+
133
+ @property
134
+ def lock(self):
135
+ raise NotImplementedError
136
+
137
+ @property
138
+ def value(self) -> T:
139
+ return self.get()
140
+
141
+ @value.setter
142
+ def value(self, value: T):
143
+ self.set(value)
144
+
145
+ def set(self, value: T):
146
+ raise NotImplementedError
147
+
148
+ def peek(self) -> T:
149
+ raise NotImplementedError
150
+
151
+ def get(self) -> T:
152
+ raise NotImplementedError
153
+
154
+ def _get_scope_key(self):
155
+ raise NotImplementedError
156
+
157
+ def subscribe(self, listener: Callable[[T], None], scope: Optional[ContextManager] = None):
158
+ if scope is not None:
159
+ warnings.warn("scope argument should not be used, it was only for internal use")
160
+ del scope
161
+ scope_id = self._get_scope_key()
162
+ rc = reacton.core.get_render_context(required=False)
163
+ if _using_solara_server():
164
+ import solara.server.kernel_context
165
+
166
+ kernel = solara.server.kernel_context.get_current_context() if solara.server.kernel_context.has_current_context() else nullcontext()
167
+ else:
168
+ kernel = nullcontext()
169
+ context = Context(rc, kernel)
170
+
171
+ self.listeners[scope_id].add((listener, context))
172
+
173
+ def cleanup():
174
+ self.listeners[scope_id].remove((listener, context))
175
+
176
+ return cleanup
177
+
178
+ def subscribe_change(self, listener: Callable[[T, T], None], scope: Optional[ContextManager] = None):
179
+ if scope is not None:
180
+ warnings.warn("scope argument should not be used, it was only for internal use")
181
+ del scope
182
+ scope_id = self._get_scope_key()
183
+ rc = reacton.core.get_render_context(required=False)
184
+ if _using_solara_server():
185
+ import solara.server.kernel_context
186
+
187
+ kernel = solara.server.kernel_context.get_current_context() if solara.server.kernel_context.has_current_context() else nullcontext()
188
+ else:
189
+ kernel = nullcontext()
190
+ context = Context(rc, kernel)
191
+ self.listeners2[scope_id].add((listener, context))
192
+
193
+ def cleanup():
194
+ self.listeners2[scope_id].remove((listener, context))
195
+
196
+ return cleanup
197
+
198
+ def fire(self, new: T, old: T):
199
+ logger.info("value change from %s to %s, will fire events", old, new)
200
+ scope_id = self._get_scope_key()
201
+ contexts = set()
202
+ for listener, context in self.listeners[scope_id].copy():
203
+ contexts.add(context)
204
+ for listener2, context in self.listeners2[scope_id].copy():
205
+ contexts.add(context)
206
+ if contexts:
207
+ for context in contexts:
208
+ with context or nullcontext():
209
+ for listener, context_listener in self.listeners[scope_id].copy():
210
+ if context == context_listener:
211
+ listener(new)
212
+ for listener2, context_listener in self.listeners2[scope_id].copy():
213
+ if context == context_listener:
214
+ listener2(new, old)
215
+
216
+ def update(self, _f=None, **kwargs):
217
+ if _f is not None:
218
+ assert not kwargs
219
+ with self.lock:
220
+ kwargs = _f(self.get())
221
+ with self.lock:
222
+ # important to have this part thread-safe
223
+ new = self.merge(self.get(), **kwargs)
224
+ self.set(new)
225
+
226
+ def use_value(self) -> T:
227
+ # .use with the default argument doesn't give good type inference
228
+ return self.use()
229
+
230
+ def use(self, selector: Callable[[T], TS] = lambda x: x) -> TS: # type: ignore
231
+ return selector(self.value)
232
+
233
+ def use_state(self) -> Tuple[T, Callable[[T], None]]:
234
+ setter = self.set
235
+ value = self.use() # type: ignore
236
+ return value, setter
237
+
238
+ @property
239
+ def fields(self) -> T:
240
+ # we lie about the return type, but in combination with
241
+ # setter we can make type safe setters (see docs/tests)
242
+ return cast(T, Fields(self))
243
+
244
+ def setter(self, field: TS) -> Callable[[TS], None]:
245
+ _field = cast(FieldBase, field)
246
+
247
+ def setter(new_value: TS):
248
+ _field.set(new_value)
249
+
250
+ return cast(Callable[[TS], None], setter)
251
+
252
+ def _check_mutation(self):
253
+ pass
254
+
255
+
256
+ # the default store for now, stores in a global dict, or when in a solara
257
+ # context, in the solara user context
258
+
259
+
260
+ class KernelStore(ValueBase[S], ABC):
261
+ _global_dict: Dict[str, S] = {} # outside of solara context, this is used
262
+ # we keep a counter per type, so the storage keys we generate are deterministic
263
+ _type_counter: Dict[Any, int] = defaultdict(int)
264
+ scope_lock = threading.RLock()
265
+
266
+ def __init__(self, key: str, equals: Callable[[Any, Any], bool] = equals_extra):
267
+ super().__init__(equals=equals)
268
+ self.storage_key = key
269
+ self._global_dict = {}
270
+ # since a set can trigger events, which can trigger new updates, we need a recursive lock
271
+ self._lock = threading.RLock()
272
+ self.local = threading.local()
273
+
274
+ @property
275
+ def lock(self):
276
+ return self._lock
277
+
278
+ def _get_scope_key(self):
279
+ scope_dict, scope_id = self._get_dict()
280
+ return scope_id
281
+
282
+ def _get_dict(self):
283
+ scope_dict = self._global_dict
284
+ scope_id = "global"
285
+ if _using_solara_server():
286
+ import solara.server.kernel_context
287
+
288
+ try:
289
+ context = solara.server.kernel_context.get_current_context()
290
+ except RuntimeError: # noqa
291
+ pass # do we need to be more strict?
292
+ else:
293
+ scope_dict = cast(Dict[str, S], context.user_dicts)
294
+ scope_id = context.id
295
+ return cast(Dict[str, S], scope_dict), scope_id
296
+
297
+ def peek(self):
298
+ return self.get()
299
+
300
+ def get(self):
301
+ scope_dict, scope_id = self._get_dict()
302
+ if self.storage_key not in scope_dict:
303
+ with self.scope_lock:
304
+ if self.storage_key not in scope_dict:
305
+ # we assume immutable, so don't make a copy
306
+ scope_dict[self.storage_key] = self.initial_value()
307
+ return scope_dict[self.storage_key]
308
+
309
+ def clear(self):
310
+ scope_dict, scope_id = self._get_dict()
311
+ if self.storage_key in scope_dict:
312
+ del scope_dict[self.storage_key]
313
+
314
+ def set(self, value: S):
315
+ scope_dict, scope_id = self._get_dict()
316
+ old = self.get()
317
+ if self.equals(old, value):
318
+ return
319
+ scope_dict[self.storage_key] = value
320
+
321
+ if _DEBUG:
322
+ import traceback
323
+
324
+ traceback.print_stack(limit=17, file=sys.stdout)
325
+
326
+ print("change old", old) # noqa
327
+ print("change new", value) # noqa
328
+
329
+ self.fire(value, old)
330
+
331
+ @abstractmethod
332
+ def initial_value(self) -> S:
333
+ pass
334
+
335
+ def _check_mutation(self):
336
+ pass
337
+
338
+
339
+ def _is_internal_module(file_name: str):
340
+ file_name_parts = file_name.split(os.sep)
341
+ if len(file_name_parts) < 2:
342
+ return False
343
+ return (
344
+ file_name_parts[-2:] == ["solara", "toestand.py"]
345
+ or file_name_parts[-2:] == ["solara", "reactive.py"]
346
+ or file_name_parts[-2:] == ["solara", "_stores.py"]
347
+ or file_name_parts[-3:] == ["solara", "hooks", "use_reactive.py"]
348
+ or file_name_parts[-2:] == ["reacton", "core.py"]
349
+ # If we use SomeClass[K](...) we go via the typing module, so we need to skip that as well
350
+ or (file_name_parts[-2].startswith("python") and file_name_parts[-1] == "typing.py")
351
+ )
352
+
353
+
354
+ def _find_outside_solara_frame() -> Optional[FrameType]:
355
+ # the module where the call stack origined from
356
+ current_frame: Optional[FrameType] = None
357
+ module_frame = None
358
+
359
+ # _getframe is not guaranteed to exist in all Python implementations,
360
+ # but is much faster than the inspect module
361
+ if hasattr(sys, "_getframe"):
362
+ current_frame = sys._getframe(1)
363
+ else:
364
+ current_frame = inspect.currentframe()
365
+
366
+ while current_frame is not None:
367
+ file_name = current_frame.f_code.co_filename
368
+ # Skip most common cases, i.e. toestand.py, reactive.py, use_reactive.py, Reacton's core.py, and the typing module
369
+ if not _is_internal_module(file_name):
370
+ module_frame = current_frame
371
+ break
372
+ current_frame = current_frame.f_back
373
+
374
+ return module_frame
375
+
376
+
377
+ class KernelStoreValue(KernelStore[S]):
378
+ default_value: S
379
+ _traceback: Optional[inspect.Traceback]
380
+ _default_value_copy: Optional[S]
381
+
382
+ def __init__(self, default_value: S, key=None, equals: Callable[[Any, Any], bool] = equals_extra, unwrap=lambda x: x):
383
+ self.default_value = default_value
384
+ self._unwrap = unwrap
385
+ self.equals = equals
386
+ self._mutation_detection = solara.settings.storage.mutation_detection
387
+ if self._mutation_detection:
388
+ frame = _find_outside_solara_frame()
389
+ if frame is not None:
390
+ self._traceback = inspect.getframeinfo(frame)
391
+ else:
392
+ self._traceback = None
393
+ self._default_value_copy = copy.deepcopy(default_value)
394
+ if not self.equals(self._unwrap(self.default_value), self._unwrap(self._default_value_copy)):
395
+ msg = """The equals function for this reactive value returned False when comparing a deepcopy to itself.
396
+
397
+ This reactive variable will not be able to detect mutations correctly, and is therefore disabled.
398
+
399
+ To avoid this warning, and to ensure that mutation detection works correctly, please provide a better equals function to the reactive variable.
400
+ A good choice for dataframes and numpy arrays might be solara.util.equals_pickle, which will also attempt to compare the pickled values of the objects.
401
+
402
+ Example:
403
+ df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
404
+ reactive_df = solara.reactive(df, equals=solara.util.equals_pickle)
405
+ """
406
+ tb = self._traceback
407
+ if tb:
408
+ if tb.code_context:
409
+ code = tb.code_context[0]
410
+ else:
411
+ code = "<No code context available>"
412
+ msg += f"This warning was triggered from:\n{tb.filename}:{tb.lineno}\n{code}"
413
+ warnings.warn(msg)
414
+ self._mutation_detection = False
415
+ cls = type(default_value)
416
+ if key is None:
417
+ with KernelStoreValue.scope_lock:
418
+ index = self._type_counter[cls]
419
+ self._type_counter[cls] += 1
420
+ key = cls.__module__ + ":" + cls.__name__ + ":" + str(index)
421
+ super().__init__(key=key, equals=equals)
422
+
423
+ def initial_value(self) -> S:
424
+ self._check_mutation()
425
+ return self.default_value
426
+
427
+ def _check_mutation(self):
428
+ if not self._mutation_detection:
429
+ return
430
+ initial = self._unwrap(self._default_value_copy)
431
+ current = self._unwrap(self.default_value)
432
+ if not self.equals(initial, current):
433
+ tb = self._traceback
434
+ if tb:
435
+ if tb.code_context:
436
+ code = tb.code_context[0].strip()
437
+ else:
438
+ code = "No code context available"
439
+ msg = f"Reactive variable was initialized at {tb.filename}:{tb.lineno} with {initial!r}, but was mutated to {current!r}.\n{code}"
440
+ else:
441
+ msg = f"Reactive variable was initialized with a value of {initial!r}, but was mutated to {current!r} (unable to report the location in the source code)."
442
+ raise ValueError(msg)
443
+
444
+
445
+ def _create_key_callable(f: Callable[[], S]):
446
+ try:
447
+ prefix = f.__qualname__
448
+ except Exception:
449
+ prefix = repr(f)
450
+ with KernelStore.scope_lock:
451
+ index = KernelStore._type_counter[prefix]
452
+ KernelStore._type_counter[prefix] += 1
453
+ try:
454
+ key = f.__module__ + ":" + prefix + ":" + str(index)
455
+ except Exception:
456
+ key = prefix + ":" + str(index)
457
+ return key
458
+
459
+
460
+ class KernelStoreFactory(KernelStore[S]):
461
+ def __init__(self, factory: Callable[[], S], key=None, equals: Callable[[Any, Any], bool] = equals_extra):
462
+ self.factory = factory
463
+ key = key or _create_key_callable(factory)
464
+ super().__init__(key=key, equals=equals)
465
+
466
+ def initial_value(self) -> S:
467
+ return self.factory()
468
+
469
+
470
+ def mutation_detection_storage(default_value: S, key=None, equals=None) -> ValueBase[S]:
471
+ from solara.util import equals_pickle as default_equals
472
+ from ._stores import MutateDetectorStore, StoreValue, _PublicValueNotSet, _SetValueNotSet
473
+
474
+ kernel_store = KernelStoreValue[StoreValue[S]](
475
+ StoreValue[S](private=default_value, public=_PublicValueNotSet(), get_traceback=None, set_value=_SetValueNotSet(), set_traceback=None),
476
+ key=key,
477
+ unwrap=lambda x: x.private,
478
+ )
479
+ return MutateDetectorStore[S](kernel_store, equals=equals or default_equals)
480
+
481
+
482
+ def default_storage(default_value: S, key=None, equals=None) -> ValueBase[S]:
483
+ # in solara v2 we will also do this when mutation_detection is None
484
+ # and we do not run on production mode
485
+ if solara.settings.storage.mutation_detection is True:
486
+ return mutation_detection_storage(default_value, key=key, equals=equals)
487
+ else:
488
+ return KernelStoreValue[S](default_value, key=key, equals=equals or equals_extra)
489
+
490
+
491
+ def _call_storage_factory(default_value: S, key=None, equals=None) -> ValueBase[S]:
492
+ factory = solara.settings.storage.get_factory()
493
+ return factory(default_value, key=key, equals=equals)
494
+
495
+
496
+ class Reactive(ValueBase[S]):
497
+ _storage: ValueBase[S]
498
+
499
+ def __init__(self, default_value: Union[S, ValueBase[S]], key=None, equals=None):
500
+ super().__init__()
501
+ if not isinstance(default_value, ValueBase):
502
+ self._storage = _call_storage_factory(default_value, key=key, equals=equals)
503
+ else:
504
+ self._storage = default_value
505
+ self.__post__init__()
506
+ self._name = None
507
+ self._owner = None
508
+
509
+ def __set_name__(self, owner, name):
510
+ self._name = name
511
+ self._owner = owner
512
+
513
+ def __repr__(self):
514
+ value = self.peek()
515
+ if self._name:
516
+ return f"<Reactive {self._owner.__name__}.{self._name} value={value!r} id={hex(id(self))}>"
517
+ else:
518
+ return f"<Reactive value={value!r} id={hex(id(self))}>"
519
+
520
+ def __str__(self):
521
+ if self._name:
522
+ return f"{self._owner.__name__}.{self._name}={self.value!r}"
523
+ else:
524
+ return f"{self.value!r}"
525
+
526
+ @property
527
+ def lock(self):
528
+ return self._storage.lock
529
+
530
+ def __post__init__(self):
531
+ pass
532
+
533
+ def update(self, *args, **kwargs):
534
+ self._storage.update(*args, **kwargs)
535
+
536
+ def set(self, value: S):
537
+ if value is self:
538
+ raise ValueError("Can't set a reactive to itself")
539
+ self._storage.set(value)
540
+
541
+ def get(self, add_watch=None) -> S:
542
+ if add_watch is not None:
543
+ warnings.warn("add_watch is deprecated, use .peek()", DeprecationWarning)
544
+ if thread_local.reactive_used is not None:
545
+ thread_local.reactive_used.add(self)
546
+ if thread_local.reactive_watch is not None:
547
+ thread_local.reactive_watch(self)
548
+ # peek to avoid parents also adding themselves to the reactive_used set
549
+ return self._storage.peek()
550
+
551
+ def peek(self) -> S:
552
+ """Return the value without automatically subscribing to listeners."""
553
+ return self._storage.peek()
554
+
555
+ def subscribe(self, listener: Callable[[S], None], scope: Optional[ContextManager] = None):
556
+ return self._storage.subscribe(listener, scope=scope)
557
+
558
+ def subscribe_change(self, listener: Callable[[S, S], None], scope: Optional[ContextManager] = None):
559
+ return self._storage.subscribe_change(listener, scope=scope)
560
+
561
+ def computed(self, f: Callable[[S], T]) -> "Computed[T]":
562
+ def func():
563
+ return f(self.get())
564
+
565
+ return Computed(func, key=f.__qualname__)
566
+
567
+
568
+ class Singleton(Reactive[S]):
569
+ _storage: KernelStore[S]
570
+
571
+ def __init__(self, factory: Callable[[], S], key=None):
572
+ import solara.lifecycle
573
+
574
+ super().__init__(KernelStoreFactory(factory, key=key))
575
+
576
+ # reset on kernel restart (e.g. hot reload)
577
+ def reset():
578
+ def cleanup():
579
+ self._storage.clear()
580
+
581
+ return cleanup
582
+
583
+ solara.lifecycle.on_kernel_start(reset)
584
+
585
+ def __set__(self, obj, value):
586
+ raise AttributeError("Can't set a singleton")
587
+
588
+
589
+ class Computed(Reactive[S]):
590
+ _storage: KernelStore[S]
591
+
592
+ def __init__(self, f: Callable[[], S], key=None):
593
+ import solara.lifecycle
594
+
595
+ self.f = f
596
+
597
+ def on_change(*ignore):
598
+ with self._auto_subscriber.value:
599
+ self.set(f())
600
+
601
+ import functools
602
+
603
+ self._auto_subscriber = Singleton(functools.wraps(AutoSubscribeContextManager)(lambda: AutoSubscribeContextManager(on_change)))
604
+
605
+ @functools.wraps(f)
606
+ def factory():
607
+ v = self._auto_subscriber.value
608
+ with v:
609
+ return f()
610
+
611
+ super().__init__(KernelStoreFactory(factory, key=key))
612
+
613
+ # reset on kernel restart (e.g. hot reload)
614
+ def reset():
615
+ def cleanup():
616
+ self._storage.clear()
617
+
618
+ return cleanup
619
+
620
+ solara.lifecycle.on_kernel_start(reset)
621
+
622
+ def __repr__(self):
623
+ value = super().__repr__()
624
+ return "<Computed" + value[len("<Reactive") : -1]
625
+
626
+
627
+ @overload
628
+ def computed(
629
+ f: None,
630
+ *,
631
+ key: Optional[str] = ...,
632
+ ) -> Callable[[Callable[[], T]], Reactive[T]]: ...
633
+
634
+
635
+ @overload
636
+ def computed(
637
+ f: Callable[[], T],
638
+ *,
639
+ key: Optional[str] = ...,
640
+ ) -> Reactive[T]: ...
641
+
642
+
643
+ def computed(
644
+ f: Union[None, Callable[[], T]],
645
+ *,
646
+ key: Optional[str] = None,
647
+ ) -> Union[Callable[[Callable[[], T]], Reactive[T]], Reactive[T]]:
648
+ """Creates a reactive variable that is set to the return value of the function.
649
+
650
+ The value will be updated when any of the reactive variables used in the function
651
+ change.
652
+
653
+ ## Example
654
+
655
+ ```solara
656
+ import solara
657
+ import solara.lab
658
+
659
+
660
+ a = solara.reactive(1)
661
+ b = solara.reactive(2)
662
+
663
+ @solara.lab.computed
664
+ def total():
665
+ return a.value + b.value
666
+
667
+ def reset():
668
+ a.value = 1
669
+ b.value = 2
670
+
671
+ @solara.component
672
+ def Page():
673
+ print(a, b, total)
674
+ solara.IntSlider("a", value=a)
675
+ solara.IntSlider("b", value=b)
676
+ solara.Text(f"a + b = {total.value}")
677
+ solara.Button("reset", on_click=reset)
678
+ ```
679
+
680
+ """
681
+
682
+ def wrapper(f: Callable[[], T]):
683
+ return Computed(f, key=key)
684
+
685
+ if f is None:
686
+ return wrapper
687
+ else:
688
+ return wrapper(f)
689
+
690
+
691
+ class ReactiveField(Reactive[T]):
692
+ def __init__(self, field: "FieldBase", equals: Callable[[Any, Any], bool] = equals_extra):
693
+ # super().__init__() # type: ignore
694
+ # We skip the Reactive constructor, because we do not need it, but we do
695
+ # want to be an instanceof for use in use_reactive
696
+ ValueBase.__init__(self, equals=equals)
697
+ self._field = field
698
+ field = field
699
+ while not isinstance(field, ValueBase):
700
+ field = field._parent
701
+ self._root = field
702
+ assert isinstance(self._root, ValueBase)
703
+
704
+ def __str__(self):
705
+ return str(self._field)
706
+
707
+ def __repr__(self):
708
+ return f"<Reactive field {self._field}>"
709
+
710
+ @property
711
+ def lock(self):
712
+ return self._root.lock
713
+
714
+ def subscribe(self, listener: Callable[[T], None], scope: Optional[ContextManager] = None):
715
+ def on_change(new, old):
716
+ try:
717
+ new_value = self._field.get(new)
718
+ except IndexError:
719
+ return # the current design choice to silently drop the update message
720
+ except KeyError:
721
+ return # same
722
+ old_value = self._field.get(old)
723
+ if not self.equals(new_value, old_value):
724
+ listener(new_value)
725
+
726
+ return self._root.subscribe_change(on_change, scope=scope)
727
+
728
+ def subscribe_change(self, listener: Callable[[T, T], None], scope: Optional[ContextManager] = None):
729
+ def on_change(new, old):
730
+ try:
731
+ new_value = self._field.get(new)
732
+ except IndexError:
733
+ return # see subscribe
734
+ except KeyError:
735
+ return # see subscribe
736
+ old_value = self._field.get(old)
737
+ if not self.equals(new_value, old_value):
738
+ listener(new_value, old_value)
739
+
740
+ return self._root.subscribe_change(on_change, scope=scope)
741
+
742
+ def get(self, add_watch=None) -> T:
743
+ if add_watch is not None:
744
+ warnings.warn("add_watch is deprecated, use .peek()", DeprecationWarning)
745
+ if thread_local.reactive_used is not None:
746
+ thread_local.reactive_used.add(self)
747
+ if thread_local.reactive_watch is not None:
748
+ thread_local.reactive_watch(self)
749
+ # peek to avoid parents also adding themselves to the reactive_used set
750
+ return self._field.peek()
751
+
752
+ def peek(self) -> T:
753
+ return self._field.peek()
754
+
755
+ def set(self, value: T):
756
+ self._field.set(value)
757
+
758
+ def update(self, *args, **kwargs):
759
+ ValueBase.update(cast(ValueBase, self), *args, **kwargs)
760
+
761
+
762
+ def Ref(field: T) -> Reactive[T]:
763
+ _field = cast(FieldBase, field)
764
+ return cast(Reactive[T], ReactiveField[T](_field))
765
+
766
+
767
+ class FieldBase:
768
+ _parent: Any
769
+
770
+ def __getattr__(self, key):
771
+ if key in ["_parent", "set", "_lock"] or key.startswith("__"):
772
+ return self.__dict__[key]
773
+ return FieldAttr(self, key)
774
+
775
+ def __getitem__(self, key):
776
+ return FieldItem(self, key)
777
+
778
+ def get(self, obj=None):
779
+ raise NotImplementedError
780
+
781
+ def set(self, value):
782
+ raise NotImplementedError
783
+
784
+
785
+ class Fields(FieldBase):
786
+ def __init__(self, state: ValueBase):
787
+ self._parent = state
788
+ self._lock = state.lock
789
+
790
+ def get(self, obj=None):
791
+ # we are at the root, so override the object
792
+ # so we can get the 'old' value
793
+ if obj is not None:
794
+ return obj
795
+ return self._parent.get()
796
+
797
+ def peek(self, obj=None):
798
+ # we are at the root, so override the object
799
+ # so we can get the 'old' value
800
+ if obj is not None:
801
+ return obj
802
+ return self._parent.peek()
803
+
804
+ def set(self, value):
805
+ self._parent.set(value)
806
+
807
+ def __repr__(self):
808
+ return repr(self._parent)
809
+
810
+
811
+ class FieldAttr(FieldBase):
812
+ def __init__(self, parent, key: str):
813
+ self._parent = parent
814
+ self.key = key
815
+ self._lock = parent._lock
816
+
817
+ def get(self, obj=None):
818
+ obj = self._parent.get(obj)
819
+ return getattr(obj, self.key)
820
+
821
+ def peek(self, obj=None):
822
+ obj = self._parent.peek(obj)
823
+ return getattr(obj, self.key)
824
+
825
+ def set(self, value):
826
+ with self._lock:
827
+ parent_value = self._parent.peek()
828
+ if isinstance(self.key, str):
829
+ parent_value = merge_state(parent_value, **{self.key: value})
830
+ self._parent.set(parent_value)
831
+ else:
832
+ raise TypeError(f"Type of key {self.key!r} is not supported")
833
+
834
+ def __str__(self):
835
+ return f".{self.key}"
836
+
837
+ def __repr__(self):
838
+ return f"<Field {self._parent}{self}>"
839
+
840
+
841
+ class FieldItem(FieldBase):
842
+ def __init__(self, parent, key: str):
843
+ self._parent = parent
844
+ self.key = key
845
+ self._lock = parent._lock
846
+
847
+ def get(self, obj=None):
848
+ obj = self._parent.get(obj)
849
+ return getitem(obj, self.key)
850
+
851
+ def peek(self, obj=None):
852
+ obj = self._parent.peek(obj)
853
+ return getitem(obj, self.key)
854
+
855
+ def set(self, value):
856
+ with self._lock:
857
+ parent_value = self._parent.peek()
858
+ if isinstance(self.key, int) and isinstance(parent_value, (list, tuple)):
859
+ parent_type = type(parent_value)
860
+ parent_value = parent_value.copy() # type: ignore
861
+ parent_value[self.key] = value
862
+ self._parent.set(parent_type(parent_value))
863
+ else:
864
+ parent_value = merge_state(parent_value, **{self.key: value})
865
+ self._parent.set(parent_value)
866
+
867
+
868
+ class AutoSubscribeContextManagerBase:
869
+ # a render loop might trigger a new render loop of a differtent render context
870
+ # so we want to save, and restore the current reactive_used
871
+ reactive_used: Optional[Set[ValueBase]] = None
872
+ subscribed: Dict[ValueBase, Callable]
873
+ subscribed_previous_run: Dict[ValueBase, Callable]
874
+ on_change: Callable[[], None]
875
+ previous_reactive_watch: Optional[Callable[["ValueBase"], None]] = None
876
+
877
+ def __init__(self):
878
+ self.subscribed = {}
879
+ self.subscribed_previous_run = {}
880
+ self.on_change = lambda: None
881
+
882
+ def unsubscribe_previous(self):
883
+ removed = set(self.subscribed_previous_run or set()) - set(self.subscribed)
884
+ if removed:
885
+ for reactive in removed:
886
+ unsubscribe = self.subscribed_previous_run[reactive]
887
+ unsubscribe()
888
+ del self.subscribed_previous_run[reactive]
889
+
890
+ def add(self, reactive: ValueBase):
891
+ relevant_reactive = reactive
892
+ if isinstance(reactive, ValueSubField):
893
+ root = reactive._root
894
+ if root in self.subscribed or root in self.subscribed_previous_run:
895
+ # we already subscribed to this reactive's root
896
+ return
897
+ else:
898
+ # we are subscribing to this reactive's root
899
+ pass
900
+
901
+ # TODO: we could see if we are the root of any of the subscribed fields,
902
+ # and remove that field.
903
+ if relevant_reactive not in self.subscribed:
904
+ if relevant_reactive not in self.subscribed_previous_run:
905
+ unsubscribe = relevant_reactive.subscribe_change(lambda *args: self.on_change())
906
+ self.subscribed[relevant_reactive] = unsubscribe
907
+ else:
908
+ self.subscribed[relevant_reactive] = self.subscribed_previous_run[relevant_reactive]
909
+
910
+ def unsubscribe_all(self):
911
+ for reactive in self.subscribed:
912
+ unsubscribe = self.subscribed[reactive]
913
+ unsubscribe()
914
+
915
+ def __enter__(self):
916
+ self.subscribed = {}
917
+ self.reactive_used_before = thread_local.reactive_used
918
+ self.previous_reactive_watch = thread_local.reactive_watch
919
+ thread_local.reactive_watch = self.add
920
+ self.reactive_used = thread_local.reactive_used = set()
921
+
922
+ def __exit__(self, exc_type, exc_val, exc_tb):
923
+ thread_local.reactive_used = self.reactive_used_before
924
+ thread_local.reactive_watch = self.previous_reactive_watch
925
+ self.unsubscribe_previous()
926
+ self.subscribed_previous_run = self.subscribed.copy()
927
+
928
+
929
+ class Context:
930
+ def __init__(self, render_context, kernel_context):
931
+ # combine the render context *and* the kernel context into one context
932
+ self.render_context = render_context
933
+ self.kernel_context = kernel_context
934
+
935
+ def __enter__(self):
936
+ if self.render_context is not None:
937
+ self.render_context.__enter__()
938
+ self.kernel_context.__enter__()
939
+
940
+ def __exit__(self, exc_type, exc_val, exc_tb):
941
+ if self.render_context is not None:
942
+ # this will trigger a render
943
+ res1 = self.render_context.__exit__(exc_type, exc_val, exc_tb)
944
+ else:
945
+ res1 = None
946
+
947
+ # pop the current context from the stack
948
+ res2 = self.kernel_context.__exit__(exc_type, exc_val, exc_tb)
949
+ return res1 or res2
950
+
951
+ def __eq__(self, value: object) -> bool:
952
+ if not isinstance(value, Context):
953
+ return False
954
+ return self.render_context == value.render_context and self.kernel_context == value.kernel_context
955
+
956
+ def __hash__(self) -> int:
957
+ return hash(id(self.render_context)) ^ hash(id(self.kernel_context))
958
+
959
+ def __repr__(self) -> str:
960
+ return f"Context(render_context={self.render_context}, kernel_context={self.kernel_context})"
961
+
962
+
963
+ class AutoSubscribeContextManagerReacton(AutoSubscribeContextManagerBase):
964
+ def __init__(self, element: solara.Element):
965
+ self.element = element
966
+ super().__init__()
967
+
968
+ def __enter__(self):
969
+ _, set_counter = solara.use_state(0, key="auto_subscribe_force_update_counter")
970
+
971
+ def force_update():
972
+ # can we do just x+1 to collapse multiple updates into one?
973
+ set_counter(lambda x: x + 1)
974
+
975
+ super().__enter__()
976
+ self.on_change = force_update
977
+
978
+ def on_close():
979
+ def cleanup():
980
+ self.unsubscribe_all()
981
+
982
+ return cleanup
983
+
984
+ solara.use_effect(on_close, [])
985
+
986
+
987
+ class AutoSubscribeContextManager(AutoSubscribeContextManagerBase):
988
+ def __init__(self, on_change: Callable[[], None]):
989
+ super().__init__()
990
+ self.on_change = on_change
991
+
992
+
993
+ # alias for compatibility
994
+ State = Reactive
995
+ ValueSubField = ReactiveField
996
+
997
+ auto_subscribe_context_manager = AutoSubscribeContextManagerReacton
998
+ reacton.core._component_context_manager_classes.append(auto_subscribe_context_manager)