solara-ui 1.31.0__py2.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 (439) 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 +734 -0
  5. solara/alias.py +6 -0
  6. solara/autorouting.py +546 -0
  7. solara/cache.py +303 -0
  8. solara/checks.html +71 -0
  9. solara/checks.py +224 -0
  10. solara/comm.py +28 -0
  11. solara/components/__init__.py +59 -0
  12. solara/components/alert.py +155 -0
  13. solara/components/applayout.py +393 -0
  14. solara/components/button.py +85 -0
  15. solara/components/card.py +87 -0
  16. solara/components/checkbox.py +50 -0
  17. solara/components/code_highlight_css.py +11 -0
  18. solara/components/code_highlight_css.vue +63 -0
  19. solara/components/columns.py +159 -0
  20. solara/components/component_vue.py +110 -0
  21. solara/components/cross_filter.py +335 -0
  22. solara/components/dataframe.py +546 -0
  23. solara/components/datatable.py +221 -0
  24. solara/components/datatable.vue +175 -0
  25. solara/components/details.py +21 -0
  26. solara/components/download.vue +35 -0
  27. solara/components/echarts.py +75 -0
  28. solara/components/echarts.vue +128 -0
  29. solara/components/figure_altair.py +39 -0
  30. solara/components/file_browser.py +182 -0
  31. solara/components/file_download.py +199 -0
  32. solara/components/file_drop.py +139 -0
  33. solara/components/file_drop.vue +83 -0
  34. solara/components/file_list_widget.vue +78 -0
  35. solara/components/head.py +27 -0
  36. solara/components/head_tag.py +49 -0
  37. solara/components/head_tag.vue +60 -0
  38. solara/components/image.py +173 -0
  39. solara/components/input.py +436 -0
  40. solara/components/link.py +55 -0
  41. solara/components/markdown.py +378 -0
  42. solara/components/markdown_editor.py +25 -0
  43. solara/components/markdown_editor.vue +362 -0
  44. solara/components/matplotlib.py +74 -0
  45. solara/components/meta.py +47 -0
  46. solara/components/misc.py +333 -0
  47. solara/components/pivot_table.py +258 -0
  48. solara/components/pivot_table.vue +158 -0
  49. solara/components/progress.py +47 -0
  50. solara/components/select.py +182 -0
  51. solara/components/select.vue +27 -0
  52. solara/components/slider.py +442 -0
  53. solara/components/slider_date.vue +56 -0
  54. solara/components/spinner-solara.vue +105 -0
  55. solara/components/spinner.py +30 -0
  56. solara/components/sql_code.py +33 -0
  57. solara/components/sql_code.vue +128 -0
  58. solara/components/style.py +105 -0
  59. solara/components/switch.py +68 -0
  60. solara/components/tab_navigation.py +37 -0
  61. solara/components/title.py +90 -0
  62. solara/components/title.vue +38 -0
  63. solara/components/togglebuttons.py +200 -0
  64. solara/components/tooltip.py +61 -0
  65. solara/datatypes.py +143 -0
  66. solara/express.py +241 -0
  67. solara/hooks/__init__.py +4 -0
  68. solara/hooks/dataframe.py +99 -0
  69. solara/hooks/misc.py +263 -0
  70. solara/hooks/use_reactive.py +129 -0
  71. solara/hooks/use_thread.py +129 -0
  72. solara/kitchensink.py +8 -0
  73. solara/lab/__init__.py +34 -0
  74. solara/lab/components/__init__.py +6 -0
  75. solara/lab/components/chat.py +203 -0
  76. solara/lab/components/confirmation_dialog.py +163 -0
  77. solara/lab/components/cross_filter.py +7 -0
  78. solara/lab/components/input_date.py +298 -0
  79. solara/lab/components/menu.py +181 -0
  80. solara/lab/components/menu.vue +38 -0
  81. solara/lab/components/tabs.py +274 -0
  82. solara/lab/components/theming.py +98 -0
  83. solara/lab/components/theming.vue +72 -0
  84. solara/lab/hooks/__init__.py +0 -0
  85. solara/lab/hooks/dataframe.py +12 -0
  86. solara/lab/toestand.py +3 -0
  87. solara/lab/utils/__init__.py +2 -0
  88. solara/lab/utils/cookies.py +5 -0
  89. solara/lab/utils/dataframe.py +115 -0
  90. solara/lab/utils/headers.py +5 -0
  91. solara/layout.py +44 -0
  92. solara/lifecycle.py +46 -0
  93. solara/minisettings.py +133 -0
  94. solara/py.typed +0 -0
  95. solara/reactive.py +93 -0
  96. solara/routing.py +268 -0
  97. solara/scope/__init__.py +88 -0
  98. solara/scope/types.py +55 -0
  99. solara/server/__init__.py +0 -0
  100. solara/server/app.py +491 -0
  101. solara/server/assets/custom.css +1 -0
  102. solara/server/assets/custom.js +1 -0
  103. solara/server/assets/favicon.png +0 -0
  104. solara/server/assets/favicon.svg +5 -0
  105. solara/server/assets/style.css +1665 -0
  106. solara/server/assets/theme-dark.css +437 -0
  107. solara/server/assets/theme-light.css +420 -0
  108. solara/server/assets/theme.js +3 -0
  109. solara/server/cdn_helper.py +77 -0
  110. solara/server/esm.py +69 -0
  111. solara/server/fastapi.py +5 -0
  112. solara/server/flask.py +286 -0
  113. solara/server/jupyter/__init__.py +2 -0
  114. solara/server/jupyter/cdn_handler.py +28 -0
  115. solara/server/jupyter/server_extension.py +29 -0
  116. solara/server/jupytertools.py +46 -0
  117. solara/server/kernel.py +338 -0
  118. solara/server/kernel_context.py +357 -0
  119. solara/server/patch.py +552 -0
  120. solara/server/reload.py +242 -0
  121. solara/server/server.py +456 -0
  122. solara/server/settings.py +215 -0
  123. solara/server/shell.py +251 -0
  124. solara/server/starlette.py +601 -0
  125. solara/server/static/ansi.js +270 -0
  126. solara/server/static/highlight-dark.css +82 -0
  127. solara/server/static/highlight.css +43 -0
  128. solara/server/static/main-vuetify.js +260 -0
  129. solara/server/static/main.js +163 -0
  130. solara/server/static/solara_bootstrap.py +129 -0
  131. solara/server/static/sun.svg +23 -0
  132. solara/server/static/webworker.js +42 -0
  133. solara/server/telemetry.py +212 -0
  134. solara/server/templates/index.html.j2 +1 -0
  135. solara/server/templates/loader-plain.css +11 -0
  136. solara/server/templates/loader-plain.html +20 -0
  137. solara/server/templates/loader-solara.css +111 -0
  138. solara/server/templates/loader-solara.html +40 -0
  139. solara/server/templates/plain.html +82 -0
  140. solara/server/templates/solara.html.j2 +446 -0
  141. solara/server/threaded.py +75 -0
  142. solara/server/utils.py +30 -0
  143. solara/server/websocket.py +45 -0
  144. solara/settings.py +56 -0
  145. solara/tasks.py +837 -0
  146. solara/template/button.py +16 -0
  147. solara/template/markdown.py +42 -0
  148. solara/template/portal/.flake8 +6 -0
  149. solara/template/portal/.pre-commit-config.yaml +28 -0
  150. solara/template/portal/LICENSE +21 -0
  151. solara/template/portal/Procfile +7 -0
  152. solara/template/portal/mypy.ini +3 -0
  153. solara/template/portal/pyproject.toml +26 -0
  154. solara/template/portal/solara_portal/__init__.py +4 -0
  155. solara/template/portal/solara_portal/components/__init__.py +2 -0
  156. solara/template/portal/solara_portal/components/article.py +28 -0
  157. solara/template/portal/solara_portal/components/data.py +28 -0
  158. solara/template/portal/solara_portal/components/header.py +6 -0
  159. solara/template/portal/solara_portal/components/layout.py +6 -0
  160. solara/template/portal/solara_portal/content/articles/equis-in-vidi.md +85 -0
  161. solara/template/portal/solara_portal/content/articles/substiterat-vati.md +70 -0
  162. solara/template/portal/solara_portal/data.py +60 -0
  163. solara/template/portal/solara_portal/pages/__init__.py +67 -0
  164. solara/template/portal/solara_portal/pages/article/__init__.py +26 -0
  165. solara/template/portal/solara_portal/pages/tabular.py +29 -0
  166. solara/template/portal/solara_portal/pages/viz/__init__.py +70 -0
  167. solara/template/portal/solara_portal/pages/viz/overview.py +14 -0
  168. solara/test/__init__.py +0 -0
  169. solara/test/pytest_plugin.py +697 -0
  170. solara/toestand.py +772 -0
  171. solara/util.py +308 -0
  172. solara/website/__init__.py +0 -0
  173. solara/website/assets/custom.css +468 -0
  174. solara/website/assets/images/logo-small.png +0 -0
  175. solara/website/assets/images/logo.svg +17 -0
  176. solara/website/assets/images/logo_white.svg +50 -0
  177. solara/website/assets/theme.js +8 -0
  178. solara/website/components/__init__.py +5 -0
  179. solara/website/components/algolia.vue +24 -0
  180. solara/website/components/algolia_api.vue +187 -0
  181. solara/website/components/docs.py +118 -0
  182. solara/website/components/header.py +72 -0
  183. solara/website/components/hero.py +15 -0
  184. solara/website/components/mailchimp.py +12 -0
  185. solara/website/components/mailchimp.vue +47 -0
  186. solara/website/components/markdown.py +30 -0
  187. solara/website/components/notebook.py +171 -0
  188. solara/website/pages/__init__.py +575 -0
  189. solara/website/pages/apps/__init__.py +16 -0
  190. solara/website/pages/apps/authorization/__init__.py +119 -0
  191. solara/website/pages/apps/authorization/admin.py +12 -0
  192. solara/website/pages/apps/authorization/users.py +12 -0
  193. solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
  194. solara/website/pages/apps/layout-demo.py +40 -0
  195. solara/website/pages/apps/multipage/__init__.py +38 -0
  196. solara/website/pages/apps/multipage/page1.py +26 -0
  197. solara/website/pages/apps/multipage/page2.py +34 -0
  198. solara/website/pages/apps/scatter.py +136 -0
  199. solara/website/pages/apps/scrolling.py +63 -0
  200. solara/website/pages/apps/tutorial-streamlit.py +18 -0
  201. solara/website/pages/changelog/__init__.py +8 -0
  202. solara/website/pages/changelog/changelog.md +204 -0
  203. solara/website/pages/contact/__init__.py +8 -0
  204. solara/website/pages/contact/contact.md +17 -0
  205. solara/website/pages/doc_use_download.py +85 -0
  206. solara/website/pages/documentation/__init__.py +184 -0
  207. solara/website/pages/documentation/advanced/__init__.py +9 -0
  208. solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
  209. solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +6 -0
  210. solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +196 -0
  211. solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +125 -0
  212. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +162 -0
  213. solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +69 -0
  214. solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +49 -0
  215. solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +124 -0
  216. solara/website/pages/documentation/advanced/content/15-reference/00-overview.md +3 -0
  217. solara/website/pages/documentation/advanced/content/15-reference/40-static_files.md +31 -0
  218. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +36 -0
  219. solara/website/pages/documentation/advanced/content/15-reference/60-static-site-generation.md +59 -0
  220. solara/website/pages/documentation/advanced/content/15-reference/70-search.md +34 -0
  221. solara/website/pages/documentation/advanced/content/15-reference/80-reloading.md +34 -0
  222. solara/website/pages/documentation/advanced/content/15-reference/90-notebook-support.md +7 -0
  223. solara/website/pages/documentation/advanced/content/15-reference/95-caching.md +148 -0
  224. solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
  225. solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +35 -0
  226. solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +42 -0
  227. solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +28 -0
  228. solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +108 -0
  229. solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +23 -0
  230. solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +7 -0
  231. solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +166 -0
  232. solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +18 -0
  233. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +240 -0
  234. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +97 -0
  235. solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +12 -0
  236. solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +1 -0
  237. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +171 -0
  238. solara/website/pages/documentation/advanced/content/40-development/00-overview.md +0 -0
  239. solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +45 -0
  240. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +76 -0
  241. solara/website/pages/documentation/api/__init__.py +19 -0
  242. solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
  243. solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +23 -0
  244. solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +22 -0
  245. solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +22 -0
  246. solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +22 -0
  247. solara/website/pages/documentation/api/hooks/__init__.py +9 -0
  248. solara/website/pages/documentation/api/hooks/use_cross_filter.py +25 -0
  249. solara/website/pages/documentation/api/hooks/use_dark_effective.py +12 -0
  250. solara/website/pages/documentation/api/hooks/use_effect.md +43 -0
  251. solara/website/pages/documentation/api/hooks/use_effect.py +9 -0
  252. solara/website/pages/documentation/api/hooks/use_exception.py +33 -0
  253. solara/website/pages/documentation/api/hooks/use_memo.md +16 -0
  254. solara/website/pages/documentation/api/hooks/use_memo.py +9 -0
  255. solara/website/pages/documentation/api/hooks/use_previous.py +33 -0
  256. solara/website/pages/documentation/api/hooks/use_reactive.py +16 -0
  257. solara/website/pages/documentation/api/hooks/use_state.py +10 -0
  258. solara/website/pages/documentation/api/hooks/use_state_or_update.py +69 -0
  259. solara/website/pages/documentation/api/hooks/use_thread.md +58 -0
  260. solara/website/pages/documentation/api/hooks/use_thread.py +44 -0
  261. solara/website/pages/documentation/api/hooks/use_trait_observe.py +12 -0
  262. solara/website/pages/documentation/api/routing/__init__.py +9 -0
  263. solara/website/pages/documentation/api/routing/generate_routes.py +10 -0
  264. solara/website/pages/documentation/api/routing/generate_routes_directory.py +10 -0
  265. solara/website/pages/documentation/api/routing/resolve_path.py +35 -0
  266. solara/website/pages/documentation/api/routing/route.py +31 -0
  267. solara/website/pages/documentation/api/routing/use_route.py +80 -0
  268. solara/website/pages/documentation/api/routing/use_router.py +16 -0
  269. solara/website/pages/documentation/api/utilities/__init__.py +9 -0
  270. solara/website/pages/documentation/api/utilities/component_vue.py +10 -0
  271. solara/website/pages/documentation/api/utilities/computed.py +16 -0
  272. solara/website/pages/documentation/api/utilities/display.py +16 -0
  273. solara/website/pages/documentation/api/utilities/get_kernel_id.py +16 -0
  274. solara/website/pages/documentation/api/utilities/get_session_id.py +16 -0
  275. solara/website/pages/documentation/api/utilities/memoize.py +35 -0
  276. solara/website/pages/documentation/api/utilities/on_kernel_start.py +27 -0
  277. solara/website/pages/documentation/api/utilities/reactive.py +16 -0
  278. solara/website/pages/documentation/api/utilities/widget.py +104 -0
  279. solara/website/pages/documentation/components/__init__.py +12 -0
  280. solara/website/pages/documentation/components/advanced/__init__.py +9 -0
  281. solara/website/pages/documentation/components/advanced/link.py +27 -0
  282. solara/website/pages/documentation/components/advanced/meta.py +20 -0
  283. solara/website/pages/documentation/components/advanced/style.py +45 -0
  284. solara/website/pages/documentation/components/common.py +9 -0
  285. solara/website/pages/documentation/components/data/__init__.py +9 -0
  286. solara/website/pages/documentation/components/data/dataframe.py +44 -0
  287. solara/website/pages/documentation/components/data/pivot_table.py +81 -0
  288. solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
  289. solara/website/pages/documentation/components/enterprise/avatar.py +24 -0
  290. solara/website/pages/documentation/components/enterprise/avatar_menu.py +25 -0
  291. solara/website/pages/documentation/components/input/__init__.py +9 -0
  292. solara/website/pages/documentation/components/input/button.py +23 -0
  293. solara/website/pages/documentation/components/input/checkbox.py +10 -0
  294. solara/website/pages/documentation/components/input/file_browser.py +32 -0
  295. solara/website/pages/documentation/components/input/file_drop.py +76 -0
  296. solara/website/pages/documentation/components/input/input.py +19 -0
  297. solara/website/pages/documentation/components/input/select.py +22 -0
  298. solara/website/pages/documentation/components/input/slider.py +29 -0
  299. solara/website/pages/documentation/components/input/switch.py +10 -0
  300. solara/website/pages/documentation/components/input/togglebuttons.py +21 -0
  301. solara/website/pages/documentation/components/lab/__init__.py +9 -0
  302. solara/website/pages/documentation/components/lab/chat.py +109 -0
  303. solara/website/pages/documentation/components/lab/confirmation_dialog.py +55 -0
  304. solara/website/pages/documentation/components/lab/cookies_headers.py +48 -0
  305. solara/website/pages/documentation/components/lab/input_date.py +20 -0
  306. solara/website/pages/documentation/components/lab/menu.py +22 -0
  307. solara/website/pages/documentation/components/lab/tab.py +25 -0
  308. solara/website/pages/documentation/components/lab/tabs.py +45 -0
  309. solara/website/pages/documentation/components/lab/task.py +11 -0
  310. solara/website/pages/documentation/components/lab/theming.py +72 -0
  311. solara/website/pages/documentation/components/lab/use_task.py +11 -0
  312. solara/website/pages/documentation/components/layout/__init__.py +9 -0
  313. solara/website/pages/documentation/components/layout/app_bar.py +16 -0
  314. solara/website/pages/documentation/components/layout/app_bar_title.py +16 -0
  315. solara/website/pages/documentation/components/layout/app_layout.py +24 -0
  316. solara/website/pages/documentation/components/layout/card.py +15 -0
  317. solara/website/pages/documentation/components/layout/card_actions.py +16 -0
  318. solara/website/pages/documentation/components/layout/column.py +30 -0
  319. solara/website/pages/documentation/components/layout/columns.py +27 -0
  320. solara/website/pages/documentation/components/layout/columns_responsive.py +68 -0
  321. solara/website/pages/documentation/components/layout/griddraggable.py +62 -0
  322. solara/website/pages/documentation/components/layout/gridfixed.py +21 -0
  323. solara/website/pages/documentation/components/layout/hbox.py +18 -0
  324. solara/website/pages/documentation/components/layout/row.py +30 -0
  325. solara/website/pages/documentation/components/layout/sidebar.py +24 -0
  326. solara/website/pages/documentation/components/layout/vbox.py +19 -0
  327. solara/website/pages/documentation/components/output/__init__.py +9 -0
  328. solara/website/pages/documentation/components/output/file_download.py +11 -0
  329. solara/website/pages/documentation/components/output/html.py +21 -0
  330. solara/website/pages/documentation/components/output/image.py +11 -0
  331. solara/website/pages/documentation/components/output/markdown.py +57 -0
  332. solara/website/pages/documentation/components/output/markdown_editor.py +51 -0
  333. solara/website/pages/documentation/components/output/sql_code.py +85 -0
  334. solara/website/pages/documentation/components/output/tooltip.py +11 -0
  335. solara/website/pages/documentation/components/page/__init__.py +9 -0
  336. solara/website/pages/documentation/components/page/head.py +18 -0
  337. solara/website/pages/documentation/components/page/title.py +27 -0
  338. solara/website/pages/documentation/components/status/__init__.py +9 -0
  339. solara/website/pages/documentation/components/status/error.py +40 -0
  340. solara/website/pages/documentation/components/status/info.py +40 -0
  341. solara/website/pages/documentation/components/status/progress.py +10 -0
  342. solara/website/pages/documentation/components/status/spinner.py +11 -0
  343. solara/website/pages/documentation/components/status/success.py +40 -0
  344. solara/website/pages/documentation/components/status/warning.py +47 -0
  345. solara/website/pages/documentation/components/viz/__init__.py +9 -0
  346. solara/website/pages/documentation/components/viz/altair.py +42 -0
  347. solara/website/pages/documentation/components/viz/echarts.py +75 -0
  348. solara/website/pages/documentation/components/viz/matplotlib.py +30 -0
  349. solara/website/pages/documentation/components/viz/plotly.py +63 -0
  350. solara/website/pages/documentation/components/viz/plotly_express.py +41 -0
  351. solara/website/pages/documentation/examples/__init__.py +52 -0
  352. solara/website/pages/documentation/examples/ai/__init__.py +11 -0
  353. solara/website/pages/documentation/examples/ai/chatbot.py +95 -0
  354. solara/website/pages/documentation/examples/ai/tokenizer.py +107 -0
  355. solara/website/pages/documentation/examples/basics/__init__.py +10 -0
  356. solara/website/pages/documentation/examples/basics/sine.py +28 -0
  357. solara/website/pages/documentation/examples/fullscreen/__init__.py +10 -0
  358. solara/website/pages/documentation/examples/fullscreen/authorization.py +3 -0
  359. solara/website/pages/documentation/examples/fullscreen/layout_demo.py +3 -0
  360. solara/website/pages/documentation/examples/fullscreen/multipage.py +3 -0
  361. solara/website/pages/documentation/examples/fullscreen/scatter.py +3 -0
  362. solara/website/pages/documentation/examples/fullscreen/scrolling.py +3 -0
  363. solara/website/pages/documentation/examples/fullscreen/tutorial_streamlit.py +3 -0
  364. solara/website/pages/documentation/examples/general/__init__.py +10 -0
  365. solara/website/pages/documentation/examples/general/custom_storage.py +70 -0
  366. solara/website/pages/documentation/examples/general/deploy_model.py +115 -0
  367. solara/website/pages/documentation/examples/general/live_update.py +38 -0
  368. solara/website/pages/documentation/examples/general/login_oauth.py +81 -0
  369. solara/website/pages/documentation/examples/general/mycard.vue +58 -0
  370. solara/website/pages/documentation/examples/general/pokemon_search.py +51 -0
  371. solara/website/pages/documentation/examples/general/vue_component.py +50 -0
  372. solara/website/pages/documentation/examples/ipycanvas.py +49 -0
  373. solara/website/pages/documentation/examples/libraries/__init__.py +10 -0
  374. solara/website/pages/documentation/examples/libraries/altair.py +64 -0
  375. solara/website/pages/documentation/examples/libraries/bqplot.py +39 -0
  376. solara/website/pages/documentation/examples/libraries/ipyleaflet.py +33 -0
  377. solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +66 -0
  378. solara/website/pages/documentation/examples/utilities/__init__.py +10 -0
  379. solara/website/pages/documentation/examples/utilities/calculator.py +157 -0
  380. solara/website/pages/documentation/examples/utilities/countdown_timer.py +64 -0
  381. solara/website/pages/documentation/examples/utilities/todo.py +196 -0
  382. solara/website/pages/documentation/examples/visualization/__init__.py +6 -0
  383. solara/website/pages/documentation/examples/visualization/annotator.py +69 -0
  384. solara/website/pages/documentation/examples/visualization/linked_views.py +84 -0
  385. solara/website/pages/documentation/examples/visualization/plotly.py +44 -0
  386. solara/website/pages/documentation/faq/__init__.py +12 -0
  387. solara/website/pages/documentation/faq/content/99-faq.md +76 -0
  388. solara/website/pages/documentation/getting_started/__init__.py +9 -0
  389. solara/website/pages/documentation/getting_started/content/00-quickstart.md +89 -0
  390. solara/website/pages/documentation/getting_started/content/01-introduction.md +125 -0
  391. solara/website/pages/documentation/getting_started/content/02-installing.md +134 -0
  392. solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
  393. solara/website/pages/documentation/getting_started/content/04-tutorials/10_data_science.py +13 -0
  394. solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +89 -0
  395. solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +124 -0
  396. solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +146 -0
  397. solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +144 -0
  398. solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +64 -0
  399. solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz +0 -0
  400. solara/website/pages/documentation/getting_started/content/04-tutorials/_data_science.ipynb +445 -0
  401. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +1000 -0
  402. solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
  403. solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +223 -0
  404. solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +88 -0
  405. solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
  406. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +273 -0
  407. solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +80 -0
  408. solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
  409. solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +26 -0
  410. solara/website/pages/docutils.py +38 -0
  411. solara/website/pages/showcase/__init__.py +105 -0
  412. solara/website/pages/showcase/domino_code_assist.py +60 -0
  413. solara/website/pages/showcase/planeto_tessa.py +19 -0
  414. solara/website/pages/showcase/solara_dev.py +54 -0
  415. solara/website/pages/showcase/solarathon_2023_team_2.py +22 -0
  416. solara/website/pages/showcase/solarathon_2023_team_4.py +22 -0
  417. solara/website/pages/showcase/solarathon_2023_team_5.py +23 -0
  418. solara/website/pages/showcase/solarathon_2023_team_6.py +34 -0
  419. solara/website/pages/showcase/wanderlust.py +27 -0
  420. solara/website/public/beach.jpeg +0 -0
  421. solara/website/public/logo.svg +6 -0
  422. solara/website/public/social/discord.svg +1 -0
  423. solara/website/public/social/github.svg +1 -0
  424. solara/website/public/social/twitter.svg +3 -0
  425. solara/website/public/success.html +25 -0
  426. solara/website/templates/index.html.j2 +117 -0
  427. solara/website/utils.py +51 -0
  428. solara/widgets/__init__.py +1 -0
  429. solara/widgets/vue/gridlayout.vue +110 -0
  430. solara/widgets/vue/html.vue +4 -0
  431. solara/widgets/vue/navigator.vue +104 -0
  432. solara/widgets/vue/vegalite.vue +115 -0
  433. solara/widgets/widgets.py +66 -0
  434. solara_ui-1.31.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  435. solara_ui-1.31.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  436. solara_ui-1.31.0.dist-info/METADATA +158 -0
  437. solara_ui-1.31.0.dist-info/RECORD +439 -0
  438. solara_ui-1.31.0.dist-info/WHEEL +5 -0
  439. solara_ui-1.31.0.dist-info/licenses/LICENSE +21 -0
solara/toestand.py ADDED
@@ -0,0 +1,772 @@
1
+ import contextlib
2
+ import dataclasses
3
+ import logging
4
+ import sys
5
+ import threading
6
+ import warnings
7
+ from abc import ABC, abstractmethod
8
+ from collections import defaultdict
9
+ from operator import getitem
10
+ from typing import (
11
+ Any,
12
+ Callable,
13
+ ContextManager,
14
+ Dict,
15
+ Generic,
16
+ Optional,
17
+ Set,
18
+ Tuple,
19
+ TypeVar,
20
+ Union,
21
+ cast,
22
+ overload,
23
+ )
24
+
25
+ import react_ipywidgets as react
26
+ import reacton.core
27
+ from reacton.utils import equals
28
+
29
+ import solara
30
+ from solara import _using_solara_server
31
+
32
+ T = TypeVar("T")
33
+ TS = TypeVar("TS")
34
+ S = TypeVar("S") # used for state
35
+ logger = logging.getLogger("solara.toestand")
36
+
37
+ _DEBUG = False
38
+
39
+
40
+ class ThreadLocal(threading.local):
41
+ reactive_used: Optional[Set["ValueBase"]] = None
42
+
43
+
44
+ thread_local = ThreadLocal()
45
+
46
+
47
+ # these hooks should go into react-ipywidgets
48
+ def use_sync_external_store(subscribe: Callable[[Callable[[], None]], Callable[[], None]], get_snapshot: Callable[[], Any]):
49
+ _, set_counter = react.use_state(0)
50
+
51
+ def force_update():
52
+ set_counter(lambda x: x + 1)
53
+
54
+ state = get_snapshot()
55
+ prev_state = react.use_ref(state)
56
+
57
+ def update_state():
58
+ prev_state.current = state
59
+
60
+ react.use_effect(update_state)
61
+
62
+ def on_store_change(_ignore_new_state=None):
63
+ new_state = get_snapshot()
64
+ if not equals(new_state, prev_state.current):
65
+ prev_state.current = new_state
66
+ force_update()
67
+
68
+ react.use_effect(lambda: subscribe(on_store_change), [])
69
+ return state
70
+
71
+
72
+ def use_sync_external_store_with_selector(subscribe, get_snapshot: Callable[[], Any], selector):
73
+ return use_sync_external_store(subscribe, lambda: selector(get_snapshot()))
74
+
75
+
76
+ def merge_state(d1: S, **kwargs) -> S:
77
+ if dataclasses.is_dataclass(d1):
78
+ return dataclasses.replace(d1, **kwargs) # type: ignore
79
+ if "pydantic" in sys.modules and isinstance(d1, sys.modules["pydantic"].BaseModel):
80
+ return type(d1)(**{**d1.dict(), **kwargs}) # type: ignore
81
+ return cast(S, {**cast(dict, d1), **kwargs})
82
+
83
+
84
+ class ValueBase(Generic[T]):
85
+ def __init__(self, merge: Callable = merge_state):
86
+ self.merge = merge
87
+ self.listeners: Dict[str, Set[Tuple[Callable[[T], None], Optional[ContextManager]]]] = defaultdict(set)
88
+ self.listeners2: Dict[str, Set[Tuple[Callable[[T, T], None], Optional[ContextManager]]]] = defaultdict(set)
89
+
90
+ @property
91
+ def lock(self):
92
+ raise NotImplementedError
93
+
94
+ @property
95
+ def value(self) -> T:
96
+ return self.get()
97
+
98
+ @value.setter
99
+ def value(self, value: T):
100
+ self.set(value)
101
+
102
+ def set(self, value: T):
103
+ raise NotImplementedError
104
+
105
+ def peek(self) -> T:
106
+ raise NotImplementedError
107
+
108
+ def get(self) -> T:
109
+ raise NotImplementedError
110
+
111
+ def _get_scope_key(self):
112
+ raise NotImplementedError
113
+
114
+ def subscribe(self, listener: Callable[[T], None], scope: Optional[ContextManager] = None):
115
+ scope_id = self._get_scope_key()
116
+ self.listeners[scope_id].add((listener, scope))
117
+
118
+ def cleanup():
119
+ self.listeners[scope_id].remove((listener, scope))
120
+
121
+ return cleanup
122
+
123
+ def subscribe_change(self, listener: Callable[[T, T], None], scope: Optional[ContextManager] = None):
124
+ scope_id = self._get_scope_key()
125
+ self.listeners2[scope_id].add((listener, scope))
126
+
127
+ def cleanup():
128
+ self.listeners2[scope_id].remove((listener, scope))
129
+
130
+ return cleanup
131
+
132
+ def fire(self, new: T, old: T):
133
+ logger.info("value change from %s to %s, will fire events", old, new)
134
+ scope_id = self._get_scope_key()
135
+ scopes = set()
136
+ for listener, scope in self.listeners[scope_id].copy():
137
+ if scope is not None:
138
+ scopes.add(scope)
139
+ for listener2, scope in self.listeners2[scope_id].copy():
140
+ if scope is not None:
141
+ scopes.add(scope)
142
+ stack = contextlib.ExitStack()
143
+ with contextlib.ExitStack() as stack:
144
+ for scope in scopes:
145
+ stack.enter_context(scope)
146
+ for listener, scope in self.listeners[scope_id].copy():
147
+ listener(new)
148
+ for listener2, scope in self.listeners2[scope_id].copy():
149
+ listener2(new, old)
150
+
151
+ def update(self, _f=None, **kwargs):
152
+ if _f is not None:
153
+ assert not kwargs
154
+ with self.lock:
155
+ kwargs = _f(self.get())
156
+ with self.lock:
157
+ # important to have this part thread-safe
158
+ new = self.merge(self.get(), **kwargs)
159
+ self.set(new)
160
+
161
+ def use_value(self) -> T:
162
+ # .use with the default argument doesn't give good type inference
163
+ return self.use()
164
+
165
+ def use(self, selector: Callable[[T], TS] = lambda x: x) -> TS: # type: ignore
166
+ return selector(self.value)
167
+
168
+ def use_state(self) -> Tuple[T, Callable[[T], None]]:
169
+ setter = self.set
170
+ value = self.use() # type: ignore
171
+ return value, setter
172
+
173
+ @property
174
+ def fields(self) -> T:
175
+ # we lie about the return type, but in combination with
176
+ # setter we can make type safe setters (see docs/tests)
177
+ return cast(T, Fields(self))
178
+
179
+ def setter(self, field: TS) -> Callable[[TS], None]:
180
+ _field = cast(FieldBase, field)
181
+
182
+ def setter(new_value: TS):
183
+ _field.set(new_value)
184
+
185
+ return cast(Callable[[TS], None], setter)
186
+
187
+
188
+ # the default store for now, stores in a global dict, or when in a solara
189
+ # context, in the solara user context
190
+
191
+
192
+ class KernelStore(ValueBase[S], ABC):
193
+ _global_dict: Dict[str, S] = {} # outside of solara context, this is used
194
+ # we keep a counter per type, so the storage keys we generate are deterministic
195
+ _type_counter: Dict[Any, int] = defaultdict(int)
196
+ scope_lock = threading.RLock()
197
+
198
+ def __init__(self, key=None):
199
+ super().__init__()
200
+ self.storage_key = key
201
+ self._global_dict = {}
202
+ # since a set can trigger events, which can trigger new updates, we need a recursive lock
203
+ self._lock = threading.RLock()
204
+ self.local = threading.local()
205
+
206
+ @property
207
+ def lock(self):
208
+ return self._lock
209
+
210
+ def _get_scope_key(self):
211
+ scope_dict, scope_id = self._get_dict()
212
+ return scope_id
213
+
214
+ def _get_dict(self):
215
+ scope_dict = self._global_dict
216
+ scope_id = "global"
217
+ if _using_solara_server():
218
+ import solara.server.kernel_context
219
+
220
+ try:
221
+ context = solara.server.kernel_context.get_current_context()
222
+ except RuntimeError: # noqa
223
+ pass # do we need to be more strict?
224
+ else:
225
+ scope_dict = cast(Dict[str, S], context.user_dicts)
226
+ scope_id = context.id
227
+ return cast(Dict[str, S], scope_dict), scope_id
228
+
229
+ def peek(self):
230
+ return self.get()
231
+
232
+ def get(self):
233
+ scope_dict, scope_id = self._get_dict()
234
+ if self.storage_key not in scope_dict:
235
+ with self.scope_lock:
236
+ if self.storage_key not in scope_dict:
237
+ # we assume immutable, so don't make a copy
238
+ scope_dict[self.storage_key] = self.initial_value()
239
+ return scope_dict[self.storage_key]
240
+
241
+ def clear(self):
242
+ scope_dict, scope_id = self._get_dict()
243
+ if self.storage_key in scope_dict:
244
+ del scope_dict[self.storage_key]
245
+
246
+ def set(self, value: S):
247
+ scope_dict, scope_id = self._get_dict()
248
+ old = self.get()
249
+ if equals(old, value):
250
+ return
251
+ scope_dict[self.storage_key] = value
252
+
253
+ if _DEBUG:
254
+ import traceback
255
+
256
+ traceback.print_stack(limit=17, file=sys.stdout)
257
+
258
+ print("change old", old) # noqa
259
+ print("change new", value) # noqa
260
+
261
+ self.fire(value, old)
262
+
263
+ @abstractmethod
264
+ def initial_value(self) -> S:
265
+ pass
266
+
267
+
268
+ class KernelStoreValue(KernelStore[S]):
269
+ default_value: S
270
+
271
+ def __init__(self, default_value: S, key=None):
272
+ self.default_value = default_value
273
+ cls = type(default_value)
274
+ if key is None:
275
+ with KernelStoreValue.scope_lock:
276
+ index = self._type_counter[cls]
277
+ self._type_counter[cls] += 1
278
+ key = cls.__module__ + ":" + cls.__name__ + ":" + str(index)
279
+ super().__init__(key=key)
280
+
281
+ def initial_value(self) -> S:
282
+ return self.default_value
283
+
284
+
285
+ class KernelStoreFactory(KernelStore[S]):
286
+ def __init__(self, factory: Callable[[], S], key=None):
287
+ self.factory = factory
288
+ try:
289
+ prefix = factory.__qualname__
290
+ except Exception:
291
+ prefix = repr(factory)
292
+ if key is None:
293
+ with KernelStore.scope_lock:
294
+ index = self._type_counter[prefix]
295
+ self._type_counter[prefix] += 1
296
+ try:
297
+ key = factory.__module__ + ":" + prefix + ":" + str(index)
298
+ except Exception:
299
+ key = prefix + ":" + str(index)
300
+ super().__init__(key=key)
301
+
302
+ def initial_value(self) -> S:
303
+ return self.factory()
304
+
305
+
306
+ class Reactive(ValueBase[S]):
307
+ _storage: ValueBase[S]
308
+
309
+ def __init__(self, default_value: Union[S, ValueBase[S]], key=None):
310
+ super().__init__()
311
+ if not isinstance(default_value, ValueBase):
312
+ self._storage = KernelStoreValue(default_value, key=key)
313
+ else:
314
+ self._storage = default_value
315
+ self.__post__init__()
316
+ self._name = None
317
+ self._owner = None
318
+
319
+ def __set_name__(self, owner, name):
320
+ self._name = name
321
+ self._owner = owner
322
+
323
+ def __repr__(self):
324
+ value = self.peek()
325
+ if self._name:
326
+ return f"<Reactive {self._owner.__name__}.{self._name} value={value!r} id={hex(id(self))}>"
327
+ else:
328
+ return f"<Reactive value={value!r} id={hex(id(self))}>"
329
+
330
+ def __str__(self):
331
+ if self._name:
332
+ return f"{self._owner.__name__}.{self._name}={self.value!r}"
333
+ else:
334
+ return f"{self.value!r}"
335
+
336
+ @property
337
+ def lock(self):
338
+ return self._storage.lock
339
+
340
+ def __post__init__(self):
341
+ pass
342
+
343
+ def update(self, *args, **kwargs):
344
+ self._storage.update(*args, **kwargs)
345
+
346
+ def set(self, value: S):
347
+ if value is self:
348
+ raise ValueError("Can't set a reactive to itself")
349
+ self._storage.set(value)
350
+
351
+ def get(self, add_watch=None) -> S:
352
+ if add_watch is not None:
353
+ warnings.warn("add_watch is deprecated, use .peek()", DeprecationWarning)
354
+ if thread_local.reactive_used is not None:
355
+ thread_local.reactive_used.add(self)
356
+ # peek to avoid parents also adding themselves to the reactive_used set
357
+ return self._storage.peek()
358
+
359
+ def peek(self) -> S:
360
+ """Return the value without automatically subscribing to listeners."""
361
+ return self._storage.peek()
362
+
363
+ def subscribe(self, listener: Callable[[S], None], scope: Optional[ContextManager] = None):
364
+ return self._storage.subscribe(listener, scope=scope)
365
+
366
+ def subscribe_change(self, listener: Callable[[S, S], None], scope: Optional[ContextManager] = None):
367
+ return self._storage.subscribe_change(listener, scope=scope)
368
+
369
+ def computed(self, f: Callable[[S], T]) -> "Computed[T]":
370
+ def func():
371
+ return f(self.get())
372
+
373
+ return Computed(func, key=f.__qualname__)
374
+
375
+
376
+ class Singleton(Reactive[S]):
377
+ _storage: KernelStore[S]
378
+
379
+ def __init__(self, factory: Callable[[], S], key=None):
380
+ import solara.lifecycle
381
+
382
+ super().__init__(KernelStoreFactory(factory, key=key))
383
+
384
+ # reset on kernel restart (e.g. hot reload)
385
+ def reset():
386
+ def cleanup():
387
+ self._storage.clear()
388
+
389
+ return cleanup
390
+
391
+ solara.lifecycle.on_kernel_start(reset)
392
+
393
+ def __set__(self, obj, value):
394
+ raise AttributeError("Can't set a singleton")
395
+
396
+
397
+ class Computed(Reactive[S]):
398
+ _storage: KernelStore[S]
399
+
400
+ def __init__(self, f: Callable[[], S], key=None):
401
+ import solara.lifecycle
402
+
403
+ self.f = f
404
+
405
+ def on_change(*ignore):
406
+ with self._auto_subscriber.value:
407
+ self.set(f())
408
+
409
+ import functools
410
+
411
+ self._auto_subscriber = Singleton(functools.wraps(AutoSubscribeContextManager)(lambda: AutoSubscribeContextManager(on_change)))
412
+
413
+ @functools.wraps(f)
414
+ def factory():
415
+ v = self._auto_subscriber.value
416
+ with v:
417
+ return f()
418
+
419
+ super().__init__(KernelStoreFactory(factory, key=key))
420
+
421
+ # reset on kernel restart (e.g. hot reload)
422
+ def reset():
423
+ def cleanup():
424
+ self._storage.clear()
425
+
426
+ return cleanup
427
+
428
+ solara.lifecycle.on_kernel_start(reset)
429
+
430
+ def __repr__(self):
431
+ value = super().__repr__()
432
+ return "<Computed" + value[len("<Reactive") : -1]
433
+
434
+
435
+ @overload
436
+ def computed(
437
+ f: None,
438
+ *,
439
+ key: Optional[str] = ...,
440
+ ) -> Callable[[Callable[[], T]], Reactive[T]]: ...
441
+
442
+
443
+ @overload
444
+ def computed(
445
+ f: Callable[[], T],
446
+ *,
447
+ key: Optional[str] = ...,
448
+ ) -> Reactive[T]: ...
449
+
450
+
451
+ def computed(
452
+ f: Union[None, Callable[[], T]],
453
+ *,
454
+ key: Optional[str] = None,
455
+ ) -> Union[Callable[[Callable[[], T]], Reactive[T]], Reactive[T]]:
456
+ """Creates a reactive variable that is set to the return value of the function.
457
+
458
+ The value will be updated when any of the reactive variables used in the function
459
+ change.
460
+
461
+ ## Example
462
+
463
+ ```solara
464
+ import solara
465
+ import solara.lab
466
+
467
+
468
+ a = solara.reactive(1)
469
+ b = solara.reactive(2)
470
+
471
+ @solara.lab.computed
472
+ def total():
473
+ return a.value + b.value
474
+
475
+ def reset():
476
+ a.value = 1
477
+ b.value = 2
478
+
479
+ @solara.component
480
+ def Page():
481
+ print(a, b, total)
482
+ solara.IntSlider("a", value=a)
483
+ solara.IntSlider("b", value=b)
484
+ solara.Text(f"a + b = {total.value}")
485
+ solara.Button("reset", on_click=reset)
486
+ ```
487
+
488
+ """
489
+
490
+ def wrapper(f: Callable[[], T]):
491
+ return Computed(f, key=key)
492
+
493
+ if f is None:
494
+ return wrapper
495
+ else:
496
+ return wrapper(f)
497
+
498
+
499
+ class ReactiveField(Reactive[T]):
500
+ def __init__(self, field: "FieldBase"):
501
+ # super().__init__() # type: ignore
502
+ # We skip the Reactive constructor, because we do not need it, but we do
503
+ # want to be an instanceof for use in use_reactive
504
+ ValueBase.__init__(self)
505
+ self._field = field
506
+ field = field
507
+ while not isinstance(field, ValueBase):
508
+ field = field._parent
509
+ self._root = field
510
+ assert isinstance(self._root, ValueBase)
511
+
512
+ def __str__(self):
513
+ return str(self._field)
514
+
515
+ def __repr__(self):
516
+ return f"<Reactive field {self._field}>"
517
+
518
+ @property
519
+ def lock(self):
520
+ return self._root.lock
521
+
522
+ def subscribe(self, listener: Callable[[T], None], scope: Optional[ContextManager] = None):
523
+ def on_change(new, old):
524
+ try:
525
+ new_value = self._field.get(new)
526
+ except IndexError:
527
+ return # the current design choice to silently drop the update message
528
+ except KeyError:
529
+ return # same
530
+ old_value = self._field.get(old)
531
+ if not equals(new_value, old_value):
532
+ listener(new_value)
533
+
534
+ return self._root.subscribe_change(on_change, scope=scope)
535
+
536
+ def subscribe_change(self, listener: Callable[[T, T], None], scope: Optional[ContextManager] = None):
537
+ def on_change(new, old):
538
+ try:
539
+ new_value = self._field.get(new)
540
+ except IndexError:
541
+ return # see subscribe
542
+ except KeyError:
543
+ return # see subscribe
544
+ old_value = self._field.get(old)
545
+ if not equals(new_value, old_value):
546
+ listener(new_value, old_value)
547
+
548
+ return self._root.subscribe_change(on_change, scope=scope)
549
+
550
+ def get(self, add_watch=None) -> T:
551
+ if add_watch is not None:
552
+ warnings.warn("add_watch is deprecated, use .peek()", DeprecationWarning)
553
+ if thread_local.reactive_used is not None:
554
+ thread_local.reactive_used.add(self)
555
+ # peek to avoid parents also adding themselves to the reactive_used set
556
+ return self._field.peek()
557
+
558
+ def peek(self) -> T:
559
+ return self._field.peek()
560
+
561
+ def set(self, value: T):
562
+ self._field.set(value)
563
+
564
+ def update(self, *args, **kwargs):
565
+ ValueBase.update(cast(ValueBase, self), *args, **kwargs)
566
+
567
+
568
+ def Ref(field: T) -> Reactive[T]:
569
+ _field = cast(FieldBase, field)
570
+ return cast(Reactive[T], ReactiveField[T](_field))
571
+
572
+
573
+ class FieldBase:
574
+ _parent: Any
575
+
576
+ def __getattr__(self, key):
577
+ if key in ["_parent", "set", "_lock"] or key.startswith("__"):
578
+ return self.__dict__[key]
579
+ return FieldAttr(self, key)
580
+
581
+ def __getitem__(self, key):
582
+ return FieldItem(self, key)
583
+
584
+ def get(self, obj=None):
585
+ raise NotImplementedError
586
+
587
+ def set(self, value):
588
+ raise NotImplementedError
589
+
590
+
591
+ class Fields(FieldBase):
592
+ def __init__(self, state: ValueBase):
593
+ self._parent = state
594
+ self._lock = state.lock
595
+
596
+ def get(self, obj=None):
597
+ # we are at the root, so override the object
598
+ # so we can get the 'old' value
599
+ if obj is not None:
600
+ return obj
601
+ return self._parent.get()
602
+
603
+ def peek(self, obj=None):
604
+ # we are at the root, so override the object
605
+ # so we can get the 'old' value
606
+ if obj is not None:
607
+ return obj
608
+ return self._parent.peek()
609
+
610
+ def set(self, value):
611
+ self._parent.set(value)
612
+
613
+ def __repr__(self):
614
+ return repr(self._parent)
615
+
616
+
617
+ class FieldAttr(FieldBase):
618
+ def __init__(self, parent, key: str):
619
+ self._parent = parent
620
+ self.key = key
621
+ self._lock = parent._lock
622
+
623
+ def get(self, obj=None):
624
+ obj = self._parent.get(obj)
625
+ return getattr(obj, self.key)
626
+
627
+ def peek(self, obj=None):
628
+ obj = self._parent.peek(obj)
629
+ return getattr(obj, self.key)
630
+
631
+ def set(self, value):
632
+ with self._lock:
633
+ parent_value = self._parent.peek()
634
+ if isinstance(self.key, str):
635
+ parent_value = merge_state(parent_value, **{self.key: value})
636
+ self._parent.set(parent_value)
637
+ else:
638
+ raise TypeError(f"Type of key {self.key!r} is not supported")
639
+
640
+ def __str__(self):
641
+ return f".{self.key}"
642
+
643
+ def __repr__(self):
644
+ return f"<Field {self._parent}{self}>"
645
+
646
+
647
+ class FieldItem(FieldBase):
648
+ def __init__(self, parent, key: str):
649
+ self._parent = parent
650
+ self.key = key
651
+ self._lock = parent._lock
652
+
653
+ def get(self, obj=None):
654
+ obj = self._parent.get(obj)
655
+ return getitem(obj, self.key)
656
+
657
+ def peek(self, obj=None):
658
+ obj = self._parent.peek(obj)
659
+ return getitem(obj, self.key)
660
+
661
+ def set(self, value):
662
+ with self._lock:
663
+ parent_value = self._parent.peek()
664
+ if isinstance(self.key, int) and isinstance(parent_value, (list, tuple)):
665
+ parent_type = type(parent_value)
666
+ parent_value = parent_value.copy() # type: ignore
667
+ parent_value[self.key] = value
668
+ self._parent.set(parent_type(parent_value))
669
+ else:
670
+ parent_value = merge_state(parent_value, **{self.key: value})
671
+ self._parent.set(parent_value)
672
+
673
+
674
+ class AutoSubscribeContextManagerBase:
675
+ # a render loop might trigger a new render loop of a differtent render context
676
+ # so we want to save, and restore the current reactive_used
677
+ reactive_used: Optional[Set[ValueBase]] = None
678
+ reactive_added_previous_run: Optional[Set[ValueBase]] = None
679
+ subscribed: Dict[ValueBase, Callable]
680
+
681
+ def __init__(self):
682
+ self.subscribed = {}
683
+
684
+ def update_subscribers(self, change_handler, scope=None):
685
+ assert self.reactive_used is not None
686
+ reactive_used = self.reactive_used
687
+ # remove subfields for which we already listen to it's root reactive value
688
+ reactive_used_subfields = {k for k in reactive_used if isinstance(k, ValueSubField)}
689
+ reactive_used = reactive_used - reactive_used_subfields
690
+ # only add subfield for which we don't listen to it's parent
691
+ for reactive_used_subfield in reactive_used_subfields:
692
+ if reactive_used_subfield._root not in reactive_used:
693
+ reactive_used.add(reactive_used_subfield)
694
+ added = reactive_used - (self.reactive_added_previous_run or set())
695
+
696
+ removed = (self.reactive_added_previous_run or set()) - reactive_used
697
+
698
+ for reactive in added:
699
+ if reactive not in self.subscribed:
700
+ unsubscribe = reactive.subscribe_change(change_handler, scope=scope)
701
+ self.subscribed[reactive] = unsubscribe
702
+ for reactive in removed:
703
+ unsubscribe = self.subscribed[reactive]
704
+ unsubscribe()
705
+ del self.subscribed[reactive]
706
+ self.reactive_added_previous_run = added
707
+
708
+ def unsubscribe_all(self):
709
+ for reactive in self.subscribed:
710
+ unsubscribe = self.subscribed[reactive]
711
+ unsubscribe()
712
+
713
+ def __enter__(self):
714
+ self.reactive_used_before = thread_local.reactive_used
715
+ self.reactive_used = thread_local.reactive_used = set()
716
+ assert thread_local.reactive_used is self.reactive_used, f"{hex(id(thread_local.reactive_used))} vs {hex(id(self.reactive_used))}"
717
+
718
+ def __exit__(self, exc_type, exc_val, exc_tb):
719
+ thread_local.reactive_used = self.reactive_used_before
720
+
721
+
722
+ class AutoSubscribeContextManagerReacton(AutoSubscribeContextManagerBase):
723
+ def __init__(self, element: solara.Element):
724
+ self.element = element
725
+ super().__init__()
726
+
727
+ def __enter__(self):
728
+ _, set_counter = solara.use_state(0, key="auto_subscribe_force_update_counter")
729
+
730
+ def force_update(new_value, old_value):
731
+ # can we do just x+1 to collapse multiple updates into one?
732
+ set_counter(lambda x: x + 1)
733
+
734
+ super().__enter__()
735
+
736
+ def update_subscribers():
737
+ self.update_subscribers(force_update, scope=reacton.core.get_render_context(required=True))
738
+
739
+ solara.use_effect(update_subscribers, None)
740
+
741
+ def on_close():
742
+ def cleanup():
743
+ assert self.reactive_added_previous_run is not None
744
+ self.unsubscribe_all()
745
+
746
+ return cleanup
747
+
748
+ solara.use_effect(on_close, [])
749
+
750
+
751
+ class AutoSubscribeContextManager(AutoSubscribeContextManagerBase):
752
+ on_change: Callable[[], None]
753
+
754
+ def __init__(self, on_change: Callable[[], None]):
755
+ super().__init__()
756
+ self.on_change = on_change
757
+
758
+ def __enter__(self):
759
+ super().__enter__()
760
+
761
+ def __exit__(self, exc_type, exc_val, exc_tb):
762
+ value = super().__exit__(exc_type, exc_val, exc_tb)
763
+ self.update_subscribers(self.on_change)
764
+ return value
765
+
766
+
767
+ # alias for compatibility
768
+ State = Reactive
769
+ ValueSubField = ReactiveField
770
+
771
+ auto_subscribe_context_manager = AutoSubscribeContextManagerReacton
772
+ reacton.core._component_context_manager_classes.append(auto_subscribe_context_manager)