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/tasks.py ADDED
@@ -0,0 +1,893 @@
1
+ import sys
2
+ import abc
3
+ import asyncio
4
+ import dataclasses
5
+ import functools
6
+ import inspect
7
+ import logging
8
+ import threading
9
+ from enum import Enum
10
+ from typing import (
11
+ Any,
12
+ Callable,
13
+ Coroutine,
14
+ Generic,
15
+ List,
16
+ Optional,
17
+ TypeVar,
18
+ Union,
19
+ cast,
20
+ overload,
21
+ )
22
+
23
+ import typing_extensions
24
+
25
+ import solara
26
+ import solara.util
27
+ from solara.toestand import Singleton
28
+ from solara import _using_solara_server
29
+
30
+ from .toestand import Ref as ref
31
+
32
+ if sys.version_info >= (3, 8):
33
+ from typing import Literal
34
+ else:
35
+ from typing_extensions import Literal
36
+
37
+
38
+ R = TypeVar("R")
39
+ T = TypeVar("T")
40
+ P = typing_extensions.ParamSpec("P")
41
+
42
+ logger = logging.getLogger("solara.task")
43
+
44
+ has_threads = solara.util.has_threads
45
+ _main_event_loop: Optional[asyncio.AbstractEventLoop] = None
46
+ try:
47
+ # this will be the event loop in Jupyter/IPython
48
+ # on Python >=3.12, get_running_loop() is preferred
49
+ if sys.version_info >= (3, 12):
50
+ _main_event_loop = asyncio.get_running_loop()
51
+ else:
52
+ _main_event_loop = asyncio.get_event_loop()
53
+ except RuntimeError:
54
+ pass
55
+
56
+
57
+ def _get_current_task():
58
+ # asyncio.current_task() is not available in Python 3.6
59
+ if sys.version_info >= (3, 7):
60
+ return asyncio.current_task()
61
+ else:
62
+ return asyncio.Task.current_task()
63
+
64
+
65
+ class TaskState(Enum):
66
+ NOTCALLED = 1
67
+ STARTING = 2
68
+ WAITING = 3
69
+ RUNNING = 4
70
+ ERROR = 5
71
+ FINISHED = 6
72
+ CANCELLED = 7
73
+
74
+
75
+ @dataclasses.dataclass(frozen=True)
76
+ class TaskResult(Generic[T]):
77
+ value: Optional[T] = None
78
+ latest: Optional[T] = None
79
+ exception: Optional[Exception] = None
80
+ # only useful if you want to know details about the state like STARTING or WAITING
81
+ _state: TaskState = TaskState.NOTCALLED
82
+ progress: Optional[float] = None
83
+
84
+ @property
85
+ def not_called(self):
86
+ return self._state == TaskState.NOTCALLED
87
+
88
+ @property
89
+ def pending(self):
90
+ return self._state in (TaskState.STARTING, TaskState.WAITING, TaskState.RUNNING)
91
+
92
+ @property
93
+ def finished(self):
94
+ return self._state == TaskState.FINISHED
95
+
96
+ @property
97
+ def cancelled(self):
98
+ return self._state == TaskState.CANCELLED
99
+
100
+ @property
101
+ def error(self):
102
+ return self._state == TaskState.ERROR
103
+
104
+
105
+ class Task(Generic[P, R], abc.ABC):
106
+ def __init__(self, key: str):
107
+ self._result = solara.Reactive(
108
+ TaskResult[R](
109
+ value=None,
110
+ _state=TaskState.NOTCALLED,
111
+ ),
112
+ key="solara.tasks:TaskResult:" + key,
113
+ )
114
+ self._last_value: Optional[R] = None
115
+ self._last_progress: Optional[float] = None
116
+ self._latest = ref(self._result.fields.latest)
117
+ self._value = ref(self._result.fields.value)
118
+ self._error = ref(self._result.fields.error)
119
+ self._finished = ref(self._result.fields.finished)
120
+ self._cancelled = ref(self._result.fields.cancelled)
121
+ self._pending = ref(self._result.fields.pending)
122
+ self._not_called = ref(self._result.fields.not_called)
123
+ self._progress = ref(self._result.fields.progress)
124
+ self._exception = ref(self._result.fields.exception)
125
+ self._state_ = ref(self._result.fields._state)
126
+ # used for tests only
127
+ self._start_event = threading.Event()
128
+ self._start_event.set()
129
+
130
+ @property
131
+ def result(self) -> TaskResult[R]:
132
+ return self._result.value
133
+
134
+ @property
135
+ def latest(self) -> Optional[R]:
136
+ return self._latest.value
137
+
138
+ @property
139
+ def value(self) -> Optional[R]:
140
+ return self._value.value
141
+
142
+ @property
143
+ def _state(self) -> TaskState:
144
+ return self._state_.value
145
+
146
+ @property
147
+ def error(self) -> bool:
148
+ return self._error.value
149
+
150
+ @property
151
+ def finished(self) -> bool:
152
+ return self._finished.value
153
+
154
+ @property
155
+ def cancelled(self) -> bool:
156
+ return self._cancelled.value
157
+
158
+ @property
159
+ def pending(self) -> bool:
160
+ return self._pending.value
161
+
162
+ @property
163
+ def not_called(self) -> bool:
164
+ return self._not_called.value
165
+
166
+ @property
167
+ def progress(self) -> Optional[float]:
168
+ return self._progress.value
169
+
170
+ @progress.setter
171
+ def progress(self, value: Optional[float]) -> None:
172
+ self._last_progress = value
173
+ self._progress.value = value
174
+
175
+ @property
176
+ def exception(self) -> Optional[Exception]:
177
+ return self._exception.value
178
+
179
+ @abc.abstractmethod
180
+ def retry(self) -> None: ...
181
+
182
+ @abc.abstractmethod
183
+ def cancel(self) -> None: ...
184
+
185
+ @abc.abstractmethod
186
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
187
+
188
+ @abc.abstractmethod
189
+ def is_current(self) -> bool: ...
190
+
191
+ def _prestart(self):
192
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
193
+
194
+
195
+ class _CancelledErrorInOurTask(BaseException):
196
+ pass
197
+
198
+
199
+ class TaskAsyncio(Task[P, R]):
200
+ current_task: Optional[asyncio.Task] = None
201
+ current_future: Optional[asyncio.Future] = None
202
+ _cancel: Optional[Callable[[], None]] = None
203
+ _retry: Optional[Callable[[], None]] = None
204
+
205
+ def __init__(self, run_in_thread: bool, function: Callable[P, Coroutine[Any, Any, R]], key: str):
206
+ self.run_in_thread = run_in_thread
207
+ self.function = function
208
+ super().__init__(key)
209
+
210
+ def cancel(self) -> None:
211
+ if self._cancel:
212
+ self._cancel()
213
+ else:
214
+ raise RuntimeError("Cannot cancel task, never started")
215
+
216
+ def retry(self):
217
+ if self._retry:
218
+ self._retry()
219
+ else:
220
+ raise RuntimeError("Cannot retry task, never started")
221
+
222
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
223
+ self._last_progress = None
224
+ current_task: asyncio.Task[None]
225
+ if self.current_task:
226
+ self.current_task.cancel()
227
+
228
+ def retry():
229
+ self(*args, **kwargs)
230
+
231
+ def cancel():
232
+ event_loop = current_task.get_loop()
233
+ # cancel after cancel is a no-op
234
+ self._cancel = lambda: None
235
+ if _get_current_task() == current_task:
236
+ if event_loop == asyncio.get_event_loop():
237
+ # we got called in our own task and event loop
238
+ raise _CancelledErrorInOurTask()
239
+ else:
240
+ current_task.cancel()
241
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
242
+ else:
243
+ current_task.cancel()
244
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
245
+
246
+ self._cancel = cancel
247
+ self._retry = retry
248
+ if _using_solara_server():
249
+ import solara.server.kernel_context
250
+
251
+ context = solara.server.kernel_context.get_current_context()
252
+ call_event_loop = context.event_loop
253
+ else:
254
+ call_event_loop = _main_event_loop or asyncio.get_event_loop()
255
+
256
+ self.current_future = future = call_event_loop.create_future()
257
+
258
+ if self.run_in_thread:
259
+ thread_event_loop = asyncio.new_event_loop()
260
+ self.current_task = current_task = thread_event_loop.create_task(self._async_run(call_event_loop, future, args, kwargs))
261
+
262
+ def runs_in_thread():
263
+ try:
264
+ thread_event_loop.run_until_complete(current_task)
265
+ except asyncio.CancelledError as e:
266
+ call_event_loop.call_soon_threadsafe(future.set_exception, e)
267
+ except Exception as e:
268
+ logger.exception("error running in thread")
269
+ call_event_loop.call_soon_threadsafe(future.set_exception, e)
270
+ raise
271
+
272
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
273
+ thread = threading.Thread(target=runs_in_thread)
274
+ thread.start()
275
+ else:
276
+ self.current_task = current_task = asyncio.create_task(self._async_run(call_event_loop, future, args, kwargs))
277
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
278
+
279
+ def is_current(self):
280
+ running_task = self.current_task
281
+ assert running_task is not None
282
+ return (self.current_task == _get_current_task()) and not running_task.cancelled()
283
+
284
+ async def _async_run(self, call_event_loop: asyncio.AbstractEventLoop, future: asyncio.Future, args, kwargs) -> None:
285
+ self._start_event.wait()
286
+
287
+ task_for_this_call = _get_current_task()
288
+ assert task_for_this_call is not None
289
+
290
+ if self.is_current():
291
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
292
+
293
+ async def runner():
294
+ try:
295
+ if self.is_current():
296
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.RUNNING)
297
+ self._last_value = value = await self.function(*args, **kwargs)
298
+ if self.is_current() and not task_for_this_call.cancelled(): # type: ignore
299
+ self._result.value = TaskResult[R](value=value, latest=value, _state=TaskState.FINISHED, progress=self._last_progress)
300
+ logger.info("setting result to %r", value)
301
+ call_event_loop.call_soon_threadsafe(future.set_result, value)
302
+ except Exception as e:
303
+ if self.is_current():
304
+ logger.exception(e)
305
+ self._result.value = TaskResult[R](latest=self._last_value, exception=e, _state=TaskState.ERROR)
306
+ call_event_loop.call_soon_threadsafe(future.set_exception, e)
307
+ # Although this seems like an easy way to handle cancellation, an early cancelled task will never execute
308
+ # so this code will never execute, so we need to handle this in the cancel function in __call__
309
+ # except asyncio.CancelledError as e:
310
+ # if self.is_current():
311
+ # self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
312
+ # call_event_loop.call_soon_threadsafe(future.set_exception, e)
313
+ # But... if we call cancel in our own task, we still need to do it from this place
314
+ except _CancelledErrorInOurTask as e:
315
+ try:
316
+ # maybe there is a different way to get a full stack trace?
317
+ raise asyncio.CancelledError() from e
318
+ except asyncio.CancelledError as e:
319
+ if self.is_current():
320
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
321
+ call_event_loop.call_soon_threadsafe(future.set_exception, e)
322
+
323
+ await runner()
324
+
325
+
326
+ class TaskThreaded(Task[P, R]):
327
+ _current_cancel_event: Optional[threading.Event] = None
328
+ _current_thread: Optional[threading.Thread] = None
329
+ _last_finished_event: Optional[threading.Event] = None
330
+ _cancel: Optional[Callable[[], None]] = None
331
+ _retry: Optional[Callable[[], None]] = None
332
+
333
+ def __init__(self, function: Callable[P, R], key: str):
334
+ super().__init__(key)
335
+ self.__qualname__ = function.__qualname__
336
+ self.function = function
337
+ self.lock = threading.Lock()
338
+ self._local = threading.local()
339
+
340
+ def cancel(self) -> None:
341
+ if self._cancel:
342
+ self._cancel()
343
+ else:
344
+ raise RuntimeError("Cannot cancel task, never started")
345
+
346
+ def retry(self):
347
+ if self._retry:
348
+ self._retry()
349
+ else:
350
+ raise RuntimeError("Cannot retry task, never started")
351
+
352
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
353
+ self._last_finished_event = _last_finished_event = threading.Event()
354
+ self._current_cancel_event = cancel_event = threading.Event()
355
+ self._last_progress = None
356
+
357
+ def retry():
358
+ self(*args, **kwargs)
359
+
360
+ def cancel():
361
+ cancel_event.set()
362
+ if threading.current_thread() == current_thread:
363
+ raise solara.util.CancelledError()
364
+ self._current_cancel_event = None
365
+
366
+ self._retry = retry
367
+ self._cancel = cancel
368
+
369
+ with self.lock:
370
+ previous_thread = self._current_thread
371
+ self._current_thread = current_thread = threading.Thread(
372
+ target=lambda: self._run(_last_finished_event, previous_thread, cancel_event, args, kwargs), daemon=False
373
+ )
374
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.STARTING)
375
+ current_thread.start()
376
+
377
+ def is_current(self):
378
+ cancel_event = getattr(self._local, "cancel_event", None)
379
+ if cancel_event is not None and cancel_event.is_set():
380
+ return False
381
+ return self._current_thread == threading.current_thread()
382
+
383
+ def _run(self, _last_finished_event, previous_thread: Optional[threading.Thread], cancel_event, args, kwargs) -> None:
384
+ # use_thread has this as default, which can make code run 10x slower
385
+ self._start_event.wait()
386
+ intrusive_cancel = False
387
+ wait_on_previous = False
388
+ self._local.cancel_event = cancel_event
389
+
390
+ def runner():
391
+ if wait_on_previous:
392
+ if previous_thread and previous_thread.is_alive():
393
+ if self.is_current():
394
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.WAITING)
395
+ # don't start before the previous is stopped
396
+ try:
397
+ previous_thread.join()
398
+ except: # noqa
399
+ pass
400
+ if self.is_current():
401
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.RUNNING)
402
+ else:
403
+ # early stop
404
+ return
405
+
406
+ callback = self.function
407
+ try:
408
+ guard = solara.util.cancel_guard(cancel_event) if intrusive_cancel else solara.util.nullcontext()
409
+ try:
410
+ # we only use the cancel_guard context manager around
411
+ # the function calls to f. We don't want to guard around
412
+ # a call to react, since that might slow down rendering
413
+ # during rendering
414
+ with guard:
415
+ if self.is_current():
416
+ value = callback(*args, **kwargs)
417
+ if inspect.isgenerator(value):
418
+ generator = value
419
+ self._last_value = None
420
+ while True:
421
+ try:
422
+ with guard:
423
+ self._last_value = value = next(generator)
424
+ if self.is_current():
425
+ self._result.value = TaskResult[R](latest=value, value=value, _state=TaskState.RUNNING, progress=self._last_progress)
426
+ except StopIteration:
427
+ break
428
+ if self.is_current():
429
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.FINISHED, progress=self._last_progress)
430
+ else:
431
+ self._last_value = value
432
+ if self.is_current():
433
+ self._result.value = TaskResult[R](latest=value, value=value, _state=TaskState.FINISHED, progress=self._last_progress)
434
+ except Exception as e:
435
+ if self.is_current():
436
+ logger.exception(e)
437
+ self._last_value = None
438
+ self._result.value = TaskResult[R](latest=self._last_value, exception=e, _state=TaskState.ERROR)
439
+ return
440
+ except solara.util.CancelledError:
441
+ pass
442
+ # this means this thread is cancelled not be request, but because
443
+ # a new thread is running, we can ignore this
444
+ finally:
445
+ if self._current_thread == threading.current_thread():
446
+ self.running_thread = None
447
+ logger.info("thread done!")
448
+ if cancel_event.is_set():
449
+ self._result.value = TaskResult[R](latest=self._last_value, _state=TaskState.CANCELLED)
450
+ _last_finished_event.set()
451
+
452
+ try:
453
+ runner()
454
+ except Exception:
455
+ logger.exception("error running in thread")
456
+ raise
457
+
458
+
459
+ # TODO: Not sure if we want to use this, or have all local variables in Task subclasses be reactive vars
460
+ class Proxy:
461
+ def __init__(self, factory):
462
+ self._instance = Singleton(factory)
463
+
464
+ def __getattr__(self, name):
465
+ return getattr(self._instance.value, name)
466
+
467
+ def __setattr__(self, name, value):
468
+ if name == "_instance":
469
+ super().__setattr__(name, value)
470
+ else:
471
+ setattr(self._instance.value, name, value)
472
+
473
+ def __call__(self, *args, **kwargs):
474
+ return self._instance.value(*args, **kwargs)
475
+
476
+
477
+ @overload
478
+ def task(
479
+ f: None = None,
480
+ *,
481
+ prefer_threaded: bool = ...,
482
+ ) -> Callable[[Callable[P, R]], Task[P, R]]: ...
483
+
484
+
485
+ @overload
486
+ def task(
487
+ f: Callable[P, Union[Coroutine[Any, Any, R], R]],
488
+ *,
489
+ prefer_threaded: bool = ...,
490
+ ) -> Task[P, R]: ...
491
+
492
+
493
+ def task(
494
+ f: Union[None, Callable[P, Union[Coroutine[Any, Any, R], R]]] = None,
495
+ *,
496
+ prefer_threaded: bool = True,
497
+ ) -> Union[Callable[[Callable[P, R]], Task[P, R]], Task[P, R]]:
498
+ """Decorator to turn a function or coroutine function into a task.
499
+
500
+ Lets you run code in the background, with the UI available to the user. This is useful for long running tasks, like downloading data or processing data.
501
+
502
+ The task decorator turns a function or coroutine function (`async def foo(...)` - here foo is called a coroutine function) into a task object.
503
+ A task is a callable that will run the function or coroutine function in a separate thread
504
+ Note that on platforms where threads are supported, asyncio tasks will still be executed in threads (unless the
505
+ `prefer_thread=False` argument is passed). Because a coroutine function might still call long running blocking code.
506
+ Running the asyncio task in a thread will still result in a responsive UI when executed in a separate thread.
507
+
508
+ The task object will execute the function only once per virtual kernel and will only store one result per virtual kernel.
509
+ When called multiple times, the previously started thread or asyncio task result will be ignored.
510
+
511
+ A running thread or asyncio task can check if it is still the current task by calling `task.is_current()`.
512
+ If `task.is_current()` returns False, the task should stop running and return early.
513
+
514
+ The return value of the function is available as the `.value` reactive property on the task object, meaning that if a
515
+ component accesses it, the component will automatically re-run when the value changes, like a [reactive variable](/api/reactive).
516
+
517
+ ## Task object
518
+
519
+ The task object has the following attributes/values which are all reactive:
520
+
521
+ * `.value`: Contains the return value of the function (Only valid if `.finished` is true, else None).
522
+ * `.exception`: The exception raised by the function, if any (Only valid if `.error` is true, else None).
523
+ * `.latest` The last return value of the function, useful for showing out-of-date data while the task is running.
524
+ * `.progress` A readable and writable reactive property which can be used for communicating progress to the user.
525
+
526
+ The state of the task can be queried with the following attributes, which are all reactive:
527
+
528
+ * `.not_called`: True if the task has not been called yet.
529
+ * `.pending`: True if the task is asked to run, but did not finish yet, did not error and did not get cancelled.
530
+ When true, often a loading or busy indicator is shown to the user.
531
+ * `.finished`: True if the task has finished running. The result is available in the `.value` attribute as
532
+ well as the `.latest` attribute.
533
+ * `.cancelled`: True if the task was cancelled (by calling `.cancel()`).
534
+ * `.error`: True if the function has raised an exception.
535
+
536
+ The following methods are available:
537
+
538
+ * `(*args, **kwargs)` : Call the task with the given arguments and keyword arguments. The task will only run once per virtual kernel.
539
+ * `.cancel()`: Cancels the task.
540
+ * `is_current()`: Returns True if the task is still the current task, and should continue running.
541
+ Will return False when a new call to the task is made, and this function is being called from the the
542
+ previous thread or asyncio.
543
+
544
+ ## State diagram
545
+
546
+ The following state diagram shows the possible states of a task and how
547
+ each state transitions to another state.
548
+
549
+ ```mermaid
550
+ stateDiagram-v2
551
+ not_called --> pending: task()
552
+ pending --> finished
553
+ pending --> error: exception
554
+ pending --> pending: task()
555
+ pending --> cancelled: task.cancel()
556
+ finished --> pending: task()
557
+ error --> pending: task()
558
+ cancelled --> pending: task()
559
+ ```
560
+
561
+ Note that calling the task (as indicated by `task()`) can be done from any state.
562
+
563
+
564
+ ## Example
565
+
566
+ ### Async task
567
+
568
+
569
+ ```solara
570
+ import asyncio
571
+ import solara
572
+ from solara.lab import task
573
+
574
+ @task
575
+ async def fetch_data():
576
+ await asyncio.sleep(2)
577
+ return "The answer is 42"
578
+
579
+ @solara.component
580
+ def Page():
581
+ solara.Button("Fetch data", on_click=fetch_data)
582
+ solara.ProgressLinear(fetch_data.pending)
583
+
584
+ if fetch_data.finished:
585
+ solara.Text(fetch_data.value)
586
+ elif fetch_data.not_called:
587
+ solara.Text("Click the button to fetch data")
588
+ # Optional state check
589
+ # elif fetch_data.cancelled:
590
+ # solara.Text("Cancelled the fetch")
591
+ # elif fetch_data.error:
592
+ # solara.Error(str(fetch_data.exception))
593
+
594
+ ```
595
+
596
+ ### Threaded task
597
+
598
+ ```solara
599
+ import time
600
+ import solara
601
+ from solara.lab import task
602
+
603
+ @task
604
+ def fetch_data():
605
+ time.sleep(2)
606
+ return "The answer is 42"
607
+
608
+
609
+ @solara.component
610
+ def Page():
611
+ solara.Button("Fetch data", on_click=fetch_data)
612
+ solara.ProgressLinear(fetch_data.pending)
613
+
614
+ if fetch_data.finished:
615
+ solara.Text(fetch_data.value)
616
+ elif fetch_data.not_called:
617
+ solara.Text("Click the button to fetch data")
618
+ # Optional state check
619
+ # elif fetch_data.cancelled:
620
+ # solara.Text("Cancelled the fetch")
621
+ # elif fetch_data.error:
622
+ # solara.Error(str(fetch_data.exception))
623
+ ```
624
+
625
+ Note that both examples are very similar. In the first example however, we wrap a coroutine function
626
+ which can use `asyncio.sleep`. In the second example, we use a regular function, which uses `time.sleep`.
627
+ If the coroutine function would use `time.sleep` in combination with `prefer_threaded=False`,
628
+ the UI would be unresponsive for 2 seconds.
629
+
630
+
631
+ ### Showing a progress bar
632
+
633
+
634
+ Using the `.progress` attribute, you can show a progress bar to the user. This is useful for long running tasks
635
+ but requires a bit more work.
636
+
637
+ ```solara
638
+ import time
639
+ import solara
640
+ from solara.lab import task
641
+
642
+
643
+ @task
644
+ def my_calculation():
645
+ total = 0
646
+ for i in range(10):
647
+ my_calculation.progress = (i + 1) * 10.0
648
+ time.sleep(0.4)
649
+ if not my_calculation.is_current():
650
+ # a new call was made before this call was finished
651
+ return
652
+ total += i**2
653
+ return total
654
+
655
+
656
+ @solara.component
657
+ def Page():
658
+ solara.Button("Run calculation", on_click=my_calculation)
659
+ solara.ProgressLinear(my_calculation.progress if my_calculation.pending else False)
660
+
661
+ if my_calculation.finished:
662
+ solara.Text(f"Calculation result: {my_calculation.value}")
663
+ elif my_calculation.not_called:
664
+ solara.Text("Click the button to fetch data")
665
+ # Optional state check
666
+ # elif my_calculation.cancelled:
667
+ # solara.Text("Cancelled the fetch")
668
+ # elif my_calculation.error:
669
+ # solara.Error(str(my_calculation.exception))
670
+ ```
671
+
672
+ ### Out-of-date data
673
+
674
+ ```solara
675
+ import time
676
+ import solara
677
+ from solara.lab import task
678
+
679
+
680
+ @task
681
+ def my_calculation():
682
+ total = 0
683
+ for i in range(10):
684
+ time.sleep(0.1)
685
+ total += i**2
686
+ return total
687
+
688
+
689
+ @solara.component
690
+ def Page():
691
+ solara.ProgressLinear(my_calculation.pending)
692
+ solara.Button("Run simulation", on_click=my_calculation)
693
+ print(my_calculation.pending, my_calculation.value)
694
+
695
+ if my_calculation.finished:
696
+ solara.Text(f"Simulation result: {my_calculation.value}")
697
+ if my_calculation.pending and my_calculation.latest:
698
+ solara.Text(f"Simulation previous result: {my_calculation.latest}", style={"opacity": ".3"})
699
+ elif my_calculation.not_called:
700
+ solara.Text("Click the button to fetch data")
701
+ ```
702
+
703
+ ## Arguments
704
+
705
+ - `f`: Function to turn into task or None
706
+ - `prefer_threaded` - bool: Will run coroutine functions as a task in a thread when threads are available.
707
+ This ensures that even when a coroutine functions calls a blocking function the UI is still responsive.
708
+ On platform where threads are not supported (like Pyodide / WASM / Emscripten / PyScript), a coroutine
709
+ function will always run in the current event loop.
710
+
711
+ ```
712
+
713
+ """
714
+
715
+ def wrapper(f: Union[None, Callable[P, Union[Coroutine[Any, Any, R], R]]]) -> Task[P, R]:
716
+ # we use wraps to make the key of the reactive variable more unique
717
+ # and less likely to mixup during hot reloads
718
+ @functools.wraps(f) # type: ignore
719
+ def create_task():
720
+ if inspect.iscoroutinefunction(f):
721
+ return TaskAsyncio[P, R](prefer_threaded and has_threads, f, key=solara.toestand._create_key_callable(create_task))
722
+ else:
723
+ return TaskThreaded[P, R](cast(Callable[P, R], f), key=solara.toestand._create_key_callable(create_task))
724
+
725
+ return cast(Task[P, R], Proxy(create_task))
726
+
727
+ if f is None:
728
+ return wrapper
729
+ else:
730
+ return wrapper(f)
731
+
732
+
733
+ # Quotes around Task[...] are needed in Python <= 3.9, since ParamSpec doesn't properly support non-type arguments
734
+ # i.e. [] is taken as a value instead of a type
735
+ # See https://github.com/python/typing_extensions/issues/126 and related issues
736
+ @overload
737
+ def use_task(
738
+ f: None = None,
739
+ *,
740
+ dependencies: Literal[None] = ...,
741
+ raise_error=...,
742
+ prefer_threaded=...,
743
+ ) -> Callable[[Callable[[], R]], "Task[[], R]"]: ...
744
+
745
+
746
+ @overload
747
+ def use_task(
748
+ f: Callable[[], R],
749
+ *,
750
+ dependencies: Literal[None] = ...,
751
+ raise_error=...,
752
+ prefer_threaded=...,
753
+ ) -> "Task[[], R]": ...
754
+
755
+
756
+ @overload
757
+ def use_task(
758
+ f: None = None,
759
+ *,
760
+ dependencies: List = ...,
761
+ raise_error=...,
762
+ prefer_threaded=...,
763
+ ) -> Callable[[Callable[[], R]], "Task[[], R]"]: ...
764
+
765
+
766
+ @overload
767
+ def use_task(
768
+ f: Callable[[], R],
769
+ *,
770
+ dependencies: List = ...,
771
+ raise_error=...,
772
+ prefer_threaded=...,
773
+ ) -> "Task[[], R]": ...
774
+
775
+
776
+ def use_task(
777
+ f: Union[None, Callable[[], R]] = None,
778
+ *,
779
+ dependencies: Union[None, List] = [],
780
+ raise_error=True,
781
+ prefer_threaded=True,
782
+ ) -> Union[Callable[[Callable[[], R]], "Task[[], R]"], "Task[[], R]"]:
783
+ """A hook that runs a function or coroutine function as a task and returns the result.
784
+
785
+ Allows you to run code in the background, with the UI available to the user. This is useful for long running tasks,
786
+ like downloading data or processing data.
787
+
788
+ Unlike with the [`@task`](/api/task) decorator, the result is not globally shared, but only available to the component that called `use_task`.
789
+
790
+ Note that unlike the [`@task`](/api/task) decorator, the task is invoked immediately when dependencies are passed. To prevent this, pass `dependencies=None`.
791
+
792
+
793
+ ## Example
794
+
795
+ ### Running in a thread
796
+
797
+ ```solara
798
+ import time
799
+ import solara
800
+ from solara.lab import use_task, Task
801
+
802
+
803
+ @solara.component
804
+ def Page():
805
+ number = solara.use_reactive(4)
806
+
807
+ def square():
808
+ time.sleep(1)
809
+ return number.value**2
810
+
811
+ result: Task[int] = use_task(square, dependencies=[number.value])
812
+
813
+ solara.InputInt("Square", value=number, continuous_update=True)
814
+ if result.finished:
815
+ solara.Success(f"Square of {number} == {result.value}")
816
+ solara.ProgressLinear(result.pending)
817
+ ```
818
+
819
+ ### Running in an asyncio task
820
+
821
+ Note that the only difference is our function is now a coroutine function,
822
+ and we use `asyncio.sleep` instead of `time.sleep`.
823
+
824
+ ```solara
825
+ import asyncio
826
+ import solara
827
+ from solara.lab import use_task, Task
828
+
829
+
830
+ @solara.component
831
+ def Page():
832
+ number = solara.use_reactive(4)
833
+
834
+ async def square():
835
+ await asyncio.sleep(1)
836
+ return number.value**2
837
+
838
+ result: Task[int] = use_task(square, dependencies=[number.value])
839
+
840
+ solara.InputInt("Square", value=number, continuous_update=True)
841
+ if result.finished:
842
+ solara.Success(f"Square of {number} == {result.value}")
843
+ solara.ProgressLinear(result.pending)
844
+ ```
845
+
846
+ ## Arguments
847
+
848
+ - `f`: The function or coroutine to run as a task.
849
+ - `dependencies`: A list of dependencies that will trigger a rerun of the task when changed, the task will run automatically execute when the `dependencies=None`
850
+ - `raise_error`: If true, an error in the task will be raised. If false, the error should be handled by the
851
+ user and is available in the `.exception` attribute of the task result object.
852
+ - `prefer_threaded` - bool: Will run coroutine functions as a task in a thread when threads are available.
853
+ This ensures that even when a coroutine functions calls a blocking function the UI is still responsive.
854
+ On platform where threads are not supported (like Pyodide / WASM / Emscripten / PyScript), a coroutine
855
+ function will always run in the current event loop.
856
+
857
+
858
+ """
859
+
860
+ def wrapper(f):
861
+ def create_task() -> "Task[[], R]":
862
+ return task(f, prefer_threaded=prefer_threaded)
863
+
864
+ task_instance = solara.use_memo(create_task, dependencies=[])
865
+ # we always update the function so we do not have stale data in the function
866
+ task_instance.function = f # type: ignore
867
+
868
+ def _prestart():
869
+ if dependencies is not None:
870
+ # we do not want to be in a state of .finished when the dependencies change
871
+ # otherwise user code might render a stale value with the new dependencies
872
+ task_instance._prestart()
873
+
874
+ solara.use_memo(_prestart, dependencies=dependencies)
875
+
876
+ def run():
877
+ if dependencies is not None:
878
+ # but we only want to execute it as an effect, which makes
879
+ # sure that if the user assigns to a task object, the function f
880
+ # starts after the assignment is executed
881
+ task_instance()
882
+
883
+ solara.use_effect(run, dependencies=dependencies)
884
+ if raise_error:
885
+ if task_instance.error:
886
+ assert task_instance.exception is not None
887
+ raise task_instance.exception
888
+ return task_instance
889
+
890
+ if f is None:
891
+ return wrapper
892
+ else:
893
+ return wrapper(f)