solara 1.29.1__py2.py3-none-any.whl → 1.30.1__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 (286) hide show
  1. solara/__init__.py +5 -5
  2. solara/__main__.py +6 -2
  3. solara/autorouting.py +88 -56
  4. solara/cache.py +2 -2
  5. solara/checks.html +1 -1
  6. solara/components/__init__.py +1 -1
  7. solara/components/applayout.py +5 -5
  8. solara/components/button.py +4 -4
  9. solara/components/card.py +1 -2
  10. solara/components/component_vue.py +9 -8
  11. solara/components/cross_filter.py +6 -7
  12. solara/components/datatable.py +2 -3
  13. solara/components/figure_altair.py +1 -1
  14. solara/components/file_download.py +2 -2
  15. solara/components/file_drop.py +76 -33
  16. solara/components/file_drop.vue +24 -10
  17. solara/components/head.py +1 -2
  18. solara/components/head_tag.py +2 -3
  19. solara/components/image.py +1 -1
  20. solara/components/link.py +3 -4
  21. solara/components/meta.py +1 -1
  22. solara/components/misc.py +5 -9
  23. solara/datatypes.py +2 -2
  24. solara/hooks/use_reactive.py +2 -2
  25. solara/lab/components/confirmation_dialog.py +1 -1
  26. solara/lab/components/tabs.py +6 -6
  27. solara/reactive.py +1 -1
  28. solara/routing.py +9 -8
  29. solara/scope/__init__.py +3 -2
  30. solara/server/app.py +44 -1
  31. solara/server/assets/style.css +6 -0
  32. solara/server/esm.py +28 -4
  33. solara/server/kernel_context.py +78 -7
  34. solara/server/patch.py +3 -0
  35. solara/server/reload.py +2 -2
  36. solara/server/server.py +3 -3
  37. solara/server/settings.py +1 -0
  38. solara/server/starlette.py +66 -33
  39. solara/server/static/solara_bootstrap.py +1 -1
  40. solara/server/templates/solara.html.j2 +62 -42
  41. solara/tasks.py +1 -6
  42. solara/util.py +23 -1
  43. solara/website/assets/custom.css +56 -0
  44. solara/website/components/__init__.py +1 -0
  45. solara/website/components/algolia_api.vue +157 -0
  46. solara/website/components/docs.py +118 -0
  47. solara/website/components/header.py +20 -10
  48. solara/website/components/hero.py +1 -1
  49. solara/website/components/markdown.py +30 -0
  50. solara/website/pages/__init__.py +234 -20
  51. solara/website/pages/apps/jupyter-dashboard-1.py +1 -1
  52. solara/website/pages/apps/multipage/__init__.py +1 -1
  53. solara/website/pages/apps/multipage/page2.py +1 -1
  54. solara/website/pages/apps/scatter.py +21 -7
  55. solara/website/pages/changelog/__init__.py +8 -0
  56. solara/website/pages/{docs/content/95-changelog.md → changelog/changelog.md} +43 -2
  57. solara/website/pages/contact/__init__.py +8 -0
  58. solara/website/pages/documentation/__init__.py +184 -0
  59. solara/website/pages/{docs → documentation/advanced}/__init__.py +2 -2
  60. solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
  61. solara/website/pages/{docs → documentation/advanced}/content/10-howto/00-overview.md +5 -0
  62. solara/website/pages/{docs/content/10-howto/20-multipage.md → documentation/advanced/content/10-howto/10-multipage.md} +9 -5
  63. solara/website/pages/{docs/content/10-howto/30-layout.md → documentation/advanced/content/10-howto/20-layout.md} +26 -21
  64. solara/website/pages/{docs/content/10-howto/50-testing.md → documentation/advanced/content/10-howto/30-testing.md} +5 -0
  65. solara/website/pages/{docs/content/10-howto/51-debugging.md → documentation/advanced/content/10-howto/31-debugging.md} +4 -1
  66. solara/website/pages/{docs/content/10-howto/80-embed.md → documentation/advanced/content/10-howto/40-embed.md} +7 -1
  67. solara/website/pages/{docs/content/10-howto/ipywidget_libraries.md → documentation/advanced/content/10-howto/50-ipywidget_libraries.md} +4 -0
  68. solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
  69. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/05-ipywidgets.md +5 -0
  70. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/06-ipyvuetify.md +5 -1
  71. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/10-reacton.md +4 -0
  72. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/12-reacton-basics.md +5 -1
  73. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/15-anatomy.md +6 -2
  74. solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +7 -0
  75. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/18-containers.md +9 -3
  76. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/20-solara.md +5 -0
  77. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/40-routing.md +13 -9
  78. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/50-solara-server.md +6 -1
  79. solara/website/pages/{docs → documentation/advanced}/content/20-understanding/60-voila.md +5 -0
  80. solara/website/pages/{docs/content/50-enterprise → documentation/advanced/content/30-enterprise}/10-oauth.md +7 -3
  81. solara/website/pages/{docs/content/10-howto → documentation/advanced/content/40-development}/01-contribute.md +9 -5
  82. solara/website/pages/{docs/content/90-development → documentation/advanced/content/40-development}/10-setup.md +6 -2
  83. solara/website/pages/documentation/api/__init__.py +19 -0
  84. solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
  85. solara/website/pages/documentation/api/hooks/__init__.py +9 -0
  86. solara/website/pages/{api → documentation/api/hooks}/use_effect.md +3 -3
  87. solara/website/pages/{api → documentation/api/hooks}/use_effect.py +2 -1
  88. solara/website/pages/{api → documentation/api/hooks}/use_memo.md +2 -2
  89. solara/website/pages/{api → documentation/api/hooks}/use_memo.py +2 -1
  90. solara/website/pages/{api → documentation/api/hooks}/use_reactive.py +1 -2
  91. solara/website/pages/{api → documentation/api/hooks}/use_state.py +1 -2
  92. solara/website/pages/documentation/api/routing/__init__.py +9 -0
  93. solara/website/pages/{api → documentation/api/routing}/generate_routes.py +1 -2
  94. solara/website/pages/{api → documentation/api/routing}/generate_routes_directory.py +1 -2
  95. solara/website/pages/{api → documentation/api/routing}/use_route.py +2 -2
  96. solara/website/pages/{api → documentation/api/routing}/use_router.py +1 -2
  97. solara/website/pages/documentation/api/utilities/__init__.py +9 -0
  98. solara/website/pages/{api → documentation/api/utilities}/component_vue.py +1 -2
  99. solara/website/pages/{api → documentation/api/utilities}/computed.py +2 -2
  100. solara/website/pages/{api → documentation/api/utilities}/display.py +1 -2
  101. solara/website/pages/{api → documentation/api/utilities}/get_kernel_id.py +2 -2
  102. solara/website/pages/{api → documentation/api/utilities}/get_session_id.py +2 -2
  103. solara/website/pages/{api → documentation/api/utilities}/on_kernel_start.py +9 -3
  104. solara/website/pages/{api → documentation/api/utilities}/reactive.py +1 -2
  105. solara/website/pages/{api → documentation/api/utilities}/widget.py +2 -2
  106. solara/website/pages/documentation/components/__init__.py +12 -0
  107. solara/website/pages/documentation/components/advanced/__init__.py +9 -0
  108. solara/website/pages/documentation/components/data/__init__.py +9 -0
  109. solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
  110. solara/website/pages/{api → documentation/components/enterprise}/avatar.py +1 -2
  111. solara/website/pages/{api → documentation/components/enterprise}/avatar_menu.py +1 -2
  112. solara/website/pages/documentation/components/input/__init__.py +9 -0
  113. solara/website/pages/{api → documentation/components/input}/checkbox.py +1 -2
  114. solara/website/pages/documentation/components/input/file_drop.py +75 -0
  115. solara/website/pages/{api → documentation/components/input}/input.py +1 -2
  116. solara/website/pages/{api → documentation/components/input}/select.py +1 -2
  117. solara/website/pages/{api → documentation/components/input}/slider.py +1 -2
  118. solara/website/pages/{api → documentation/components/input}/switch.py +1 -2
  119. solara/website/pages/{api → documentation/components/input}/togglebuttons.py +1 -2
  120. solara/website/pages/documentation/components/lab/__init__.py +9 -0
  121. solara/website/pages/{api → documentation/components/lab}/chat.py +2 -3
  122. solara/website/pages/{api → documentation/components/lab}/cookies_headers.py +1 -1
  123. solara/website/pages/{api → documentation/components/lab}/input_date.py +1 -2
  124. solara/website/pages/{api → documentation/components/lab}/menu.py +1 -2
  125. solara/website/pages/{api → documentation/components/lab}/task.py +1 -2
  126. solara/website/pages/{api → documentation/components/lab}/theming.py +1 -2
  127. solara/website/pages/{api → documentation/components/lab}/use_task.py +1 -2
  128. solara/website/pages/documentation/components/layout/__init__.py +9 -0
  129. solara/website/pages/{api → documentation/components/layout}/app_bar.py +1 -2
  130. solara/website/pages/{api → documentation/components/layout}/app_bar_title.py +1 -2
  131. solara/website/pages/{api → documentation/components/layout}/card.py +1 -2
  132. solara/website/pages/{api → documentation/components/layout}/card_actions.py +1 -2
  133. solara/website/pages/{api → documentation/components/layout}/griddraggable.py +1 -1
  134. solara/website/pages/{api → documentation/components/layout}/gridfixed.py +1 -1
  135. solara/website/pages/{api → documentation/components/layout}/hbox.py +1 -1
  136. solara/website/pages/{api → documentation/components/layout}/vbox.py +1 -1
  137. solara/website/pages/documentation/components/output/__init__.py +9 -0
  138. solara/website/pages/{api → documentation/components/output}/file_download.py +1 -2
  139. solara/website/pages/{api → documentation/components/output}/image.py +1 -2
  140. solara/website/pages/{api → documentation/components/output}/tooltip.py +1 -2
  141. solara/website/pages/documentation/components/page/__init__.py +9 -0
  142. solara/website/pages/documentation/components/status/__init__.py +9 -0
  143. solara/website/pages/{api → documentation/components/status}/error.py +3 -3
  144. solara/website/pages/{api → documentation/components/status}/info.py +3 -3
  145. solara/website/pages/{api → documentation/components/status}/progress.py +1 -2
  146. solara/website/pages/{api → documentation/components/status}/spinner.py +1 -2
  147. solara/website/pages/{api → documentation/components/status}/success.py +3 -3
  148. solara/website/pages/{api → documentation/components/status}/warning.py +3 -3
  149. solara/website/pages/documentation/components/viz/__init__.py +9 -0
  150. solara/website/pages/{api → documentation/components/viz}/plotly.py +1 -0
  151. solara/website/pages/{examples → documentation/examples}/__init__.py +3 -43
  152. solara/website/pages/{examples → documentation/examples}/general/custom_storage.py +1 -1
  153. solara/website/pages/{examples → documentation/examples}/general/deploy_model.py +3 -3
  154. solara/website/pages/{examples → documentation/examples}/general/login_oauth.py +1 -1
  155. solara/website/pages/{examples → documentation/examples}/general/vue_component.py +1 -2
  156. solara/website/pages/{examples → documentation/examples}/libraries/altair.py +2 -3
  157. solara/website/pages/{examples → documentation/examples}/libraries/ipyleaflet_advanced.py +1 -1
  158. solara/website/pages/{examples → documentation/examples}/utilities/countdown_timer.py +1 -1
  159. solara/website/pages/{examples → documentation/examples}/visualization/plotly.py +1 -2
  160. solara/website/pages/documentation/faq/__init__.py +12 -0
  161. solara/website/pages/{docs → documentation/faq}/content/99-faq.md +4 -1
  162. solara/website/pages/documentation/getting_started/__init__.py +9 -0
  163. solara/website/pages/{docs/content/03-quickstart.md → documentation/getting_started/content/00-quickstart.md} +7 -2
  164. solara/website/pages/{docs/content/00-introduction.md → documentation/getting_started/content/01-introduction.md} +20 -16
  165. solara/website/pages/{docs → documentation/getting_started}/content/02-installing.md +5 -1
  166. solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
  167. solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/20-web-app.md +9 -4
  168. solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/30-ipywidgets.md +13 -9
  169. solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/40-streamlit.md +17 -12
  170. solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/50-dash.md +7 -3
  171. solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/_data_science.ipynb +5 -5
  172. solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
  173. solara/website/pages/{docs/content/07-fundamentals → documentation/getting_started/content/05-fundamentals}/10-components.md +7 -2
  174. solara/website/pages/{docs/content/07-fundamentals → documentation/getting_started/content/05-fundamentals}/50-state-management.md +8 -3
  175. solara/website/pages/documentation/getting_started/content/06-reference/00-overview.md +3 -0
  176. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/40-static_files.md +4 -0
  177. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/41-asset-files.md +5 -1
  178. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/60-static-site-generation.md +5 -1
  179. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/70-search.md +5 -1
  180. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/80-reloading.md +7 -3
  181. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/90-notebook-support.md +4 -1
  182. solara/website/pages/{docs/content/15-reference → documentation/getting_started/content/06-reference}/95-caching.md +6 -1
  183. solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
  184. solara/website/pages/{docs/content/30-deploying → documentation/getting_started/content/07-deploying}/10-self-hosted.md +7 -3
  185. solara/website/pages/{docs/content/30-deploying → documentation/getting_started/content/07-deploying}/20-cloud-hosted.md +5 -1
  186. solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
  187. solara/website/pages/{docs → documentation/getting_started}/content/90-troubleshoot.md +4 -0
  188. solara/website/pages/showcase/__init__.py +1 -1
  189. solara/website/pages/showcase/domino_code_assist.py +1 -1
  190. solara/website/pages/showcase/solara_dev.py +1 -1
  191. solara/website/public/social/discord.svg +1 -0
  192. solara/website/public/social/github.svg +1 -0
  193. solara/website/public/social/twitter.svg +3 -0
  194. {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/METADATA +8 -7
  195. solara-1.30.1.dist-info/RECORD +437 -0
  196. {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/WHEEL +1 -1
  197. solara/website/pages/api/__init__.py +0 -292
  198. solara/website/pages/api/default_layout.py +0 -16
  199. solara/website/pages/api/file_drop.py +0 -36
  200. solara/website/pages/docs/content/04-tutorial/00-overview.md +0 -9
  201. solara/website/pages/docs/content/07-fundamentals/00-overview.md +0 -7
  202. solara/website/pages/docs/content/15-reference/00-overview.md +0 -6
  203. solara/website/pages/docs/content/20-understanding/00-introduction.md +0 -4
  204. solara/website/pages/docs/content/20-understanding/17-rules-of-hooks.md +0 -3
  205. solara/website/pages/docs/content/30-deploying/00-overview.md +0 -3
  206. solara/website/pages/docs/content/lab/00-what-is-lab.md +0 -3
  207. solara-1.29.1.dist-info/RECORD +0 -411
  208. /solara/website/pages/{docs/content/99-contact.md → contact/contact.md} +0 -0
  209. /solara/website/pages/{docs/content/50-enterprise → documentation/advanced/content/30-enterprise}/00-overview.md +0 -0
  210. /solara/website/pages/{docs/content/__init__.py → documentation/advanced/content/40-development/00-overview.md} +0 -0
  211. /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_dataframe.py +0 -0
  212. /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_report.py +0 -0
  213. /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_select.py +0 -0
  214. /solara/website/pages/{api → documentation/api/cross_filter}/cross_filter_slider.py +0 -0
  215. /solara/website/pages/{api → documentation/api/hooks}/use_cross_filter.py +0 -0
  216. /solara/website/pages/{api → documentation/api/hooks}/use_dark_effective.py +0 -0
  217. /solara/website/pages/{api → documentation/api/hooks}/use_exception.py +0 -0
  218. /solara/website/pages/{api → documentation/api/hooks}/use_previous.py +0 -0
  219. /solara/website/pages/{api → documentation/api/hooks}/use_state_or_update.py +0 -0
  220. /solara/website/pages/{api → documentation/api/hooks}/use_thread.md +0 -0
  221. /solara/website/pages/{api → documentation/api/hooks}/use_thread.py +0 -0
  222. /solara/website/pages/{api → documentation/api/hooks}/use_trait_observe.py +0 -0
  223. /solara/website/pages/{api → documentation/api/routing}/resolve_path.py +0 -0
  224. /solara/website/pages/{api → documentation/api/routing}/route.py +0 -0
  225. /solara/website/pages/{api → documentation/api/utilities}/memoize.py +0 -0
  226. /solara/website/pages/{api → documentation/components/advanced}/link.py +0 -0
  227. /solara/website/pages/{api → documentation/components/advanced}/meta.py +0 -0
  228. /solara/website/pages/{api → documentation/components/advanced}/style.py +0 -0
  229. /solara/website/pages/{api → documentation/components}/common.py +0 -0
  230. /solara/website/pages/{api → documentation/components/data}/dataframe.py +0 -0
  231. /solara/website/pages/{api → documentation/components/data}/pivot_table.py +0 -0
  232. /solara/website/pages/{api → documentation/components/input}/button.py +0 -0
  233. /solara/website/pages/{api → documentation/components/input}/file_browser.py +0 -0
  234. /solara/website/pages/{api → documentation/components/lab}/confirmation_dialog.py +0 -0
  235. /solara/website/pages/{api → documentation/components/lab}/tab.py +0 -0
  236. /solara/website/pages/{api → documentation/components/lab}/tabs.py +0 -0
  237. /solara/website/pages/{api → documentation/components/layout}/app_layout.py +0 -0
  238. /solara/website/pages/{api → documentation/components/layout}/column.py +0 -0
  239. /solara/website/pages/{api → documentation/components/layout}/columns.py +0 -0
  240. /solara/website/pages/{api → documentation/components/layout}/columns_responsive.py +0 -0
  241. /solara/website/pages/{api → documentation/components/layout}/row.py +0 -0
  242. /solara/website/pages/{api → documentation/components/layout}/sidebar.py +0 -0
  243. /solara/website/pages/{api → documentation/components/output}/html.py +0 -0
  244. /solara/website/pages/{api → documentation/components/output}/markdown.py +0 -0
  245. /solara/website/pages/{api → documentation/components/output}/markdown_editor.py +0 -0
  246. /solara/website/pages/{api → documentation/components/output}/sql_code.py +0 -0
  247. /solara/website/pages/{api → documentation/components/page}/head.py +0 -0
  248. /solara/website/pages/{api → documentation/components/page}/title.py +0 -0
  249. /solara/website/pages/{api → documentation/components/viz}/altair.py +0 -0
  250. /solara/website/pages/{api → documentation/components/viz}/echarts.py +0 -0
  251. /solara/website/pages/{api → documentation/components/viz}/matplotlib.py +0 -0
  252. /solara/website/pages/{api → documentation/components/viz}/plotly_express.py +0 -0
  253. /solara/website/pages/{examples → documentation/examples}/ai/__init__.py +0 -0
  254. /solara/website/pages/{examples → documentation/examples}/ai/chatbot.py +0 -0
  255. /solara/website/pages/{examples → documentation/examples}/ai/tokenizer.py +0 -0
  256. /solara/website/pages/{examples → documentation/examples}/basics/__init__.py +0 -0
  257. /solara/website/pages/{examples → documentation/examples}/basics/sine.py +0 -0
  258. /solara/website/pages/{examples → documentation/examples}/fullscreen/__init__.py +0 -0
  259. /solara/website/pages/{examples → documentation/examples}/fullscreen/authorization.py +0 -0
  260. /solara/website/pages/{examples → documentation/examples}/fullscreen/layout_demo.py +0 -0
  261. /solara/website/pages/{examples → documentation/examples}/fullscreen/multipage.py +0 -0
  262. /solara/website/pages/{examples → documentation/examples}/fullscreen/scatter.py +0 -0
  263. /solara/website/pages/{examples → documentation/examples}/fullscreen/scrolling.py +0 -0
  264. /solara/website/pages/{examples → documentation/examples}/fullscreen/tutorial_streamlit.py +0 -0
  265. /solara/website/pages/{examples → documentation/examples}/general/__init__.py +0 -0
  266. /solara/website/pages/{examples → documentation/examples}/general/live_update.py +0 -0
  267. /solara/website/pages/{examples → documentation/examples}/general/mycard.vue +0 -0
  268. /solara/website/pages/{examples → documentation/examples}/general/pokemon_search.py +0 -0
  269. /solara/website/pages/{examples → documentation/examples}/ipycanvas.py +0 -0
  270. /solara/website/pages/{examples → documentation/examples}/libraries/__init__.py +0 -0
  271. /solara/website/pages/{examples → documentation/examples}/libraries/bqplot.py +0 -0
  272. /solara/website/pages/{examples → documentation/examples}/libraries/ipyleaflet.py +0 -0
  273. /solara/website/pages/{examples → documentation/examples}/utilities/__init__.py +0 -0
  274. /solara/website/pages/{examples → documentation/examples}/utilities/calculator.py +0 -0
  275. /solara/website/pages/{examples → documentation/examples}/utilities/todo.py +0 -0
  276. /solara/website/pages/{examples → documentation/examples}/visualization/__init__.py +0 -0
  277. /solara/website/pages/{examples → documentation/examples}/visualization/annotator.py +0 -0
  278. /solara/website/pages/{examples → documentation/examples}/visualization/linked_views.py +0 -0
  279. /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/10_data_science.py +0 -0
  280. /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/60-jupyter-dashboard-part1.py +0 -0
  281. /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/SF_crime_sample.csv.gz +0 -0
  282. /solara/website/pages/{docs/content/04-tutorial → documentation/getting_started/content/04-tutorials}/_jupyter_dashboard_1.ipynb +0 -0
  283. {solara-1.29.1.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  284. {solara-1.29.1.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  285. {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/entry_points.txt +0 -0
  286. {solara-1.29.1.dist-info → solara-1.30.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,22 @@
1
1
  import asyncio
2
+
3
+ try:
4
+ import contextvars
5
+ except ModuleNotFoundError:
6
+ contextvars = None # type: ignore
7
+
2
8
  import dataclasses
3
9
  import enum
10
+ import inspect
4
11
  import logging
5
12
  import os
6
13
  import pickle
7
14
  import threading
8
15
  import time
16
+ import typing
9
17
  from pathlib import Path
10
- from typing import Any, Callable, Dict, List, Optional, cast
18
+ from types import FrameType, ModuleType
19
+ from typing import Any, Callable, Dict, List, NamedTuple, Optional, cast
11
20
 
12
21
  import ipywidgets as widgets
13
22
  import reacton
@@ -36,11 +45,46 @@ class PageStatus(enum.Enum):
36
45
  CLOSED = "closed"
37
46
 
38
47
 
39
- _on_kernel_start_callbacks: List[Callable[[], Optional[Callable[[], None]]]] = []
48
+ class _on_kernel_callback_entry(NamedTuple):
49
+ callback: Callable[[], Optional[Callable[[], None]]]
50
+ callpoint: Optional[Path]
51
+ module: Optional[ModuleType]
52
+ cleanup: Callable[[], None]
53
+
54
+
55
+ _on_kernel_start_callbacks: List[_on_kernel_callback_entry] = []
56
+
57
+
58
+ def _find_root_module_frame() -> Optional[FrameType]:
59
+ # basically the module where the call stack origined from
60
+ current_frame = inspect.currentframe()
61
+ root_module_frame = None
62
+
63
+ while current_frame is not None:
64
+ if current_frame.f_code.co_name == "<module>":
65
+ root_module_frame = current_frame
66
+ break
67
+ current_frame = current_frame.f_back
68
+
69
+ return root_module_frame
40
70
 
41
71
 
42
- def on_kernel_start(f: Callable[[], Optional[Callable[[], None]]]):
43
- _on_kernel_start_callbacks.append(f)
72
+ def on_kernel_start(f: Callable[[], Optional[Callable[[], None]]]) -> Callable[[], None]:
73
+ root = _find_root_module_frame()
74
+ path: Optional[Path] = None
75
+ module: Optional[ModuleType] = None
76
+ if root is not None:
77
+ path_str = inspect.getsourcefile(root)
78
+ module = inspect.getmodule(root)
79
+ if path_str is not None:
80
+ path = Path(path_str)
81
+
82
+ def cleanup():
83
+ return _on_kernel_start_callbacks.remove(kce)
84
+
85
+ kce = _on_kernel_callback_entry(f, path, module, cleanup)
86
+ _on_kernel_start_callbacks.append(kce)
87
+ return cleanup
44
88
 
45
89
 
46
90
  @dataclasses.dataclass
@@ -74,7 +118,7 @@ class VirtualKernelContext:
74
118
 
75
119
  def __post_init__(self):
76
120
  with self:
77
- for f in _on_kernel_start_callbacks:
121
+ for (f, *_) in _on_kernel_start_callbacks:
78
122
  cleanup = f()
79
123
  if cleanup:
80
124
  self.on_close(cleanup)
@@ -106,6 +150,9 @@ class VirtualKernelContext:
106
150
  current_context[key] = local.kernel_context_stack.pop()
107
151
 
108
152
  def close(self):
153
+ if self.closed_event.is_set():
154
+ logger.error("Tried to close a kernel context that is already closed: %s", self.id)
155
+ return
109
156
  logger.info("Shut down virtual kernel: %s", self.id)
110
157
  with self:
111
158
  for f in reversed(self._on_close_callbacks):
@@ -255,12 +302,35 @@ def create_dummy_context():
255
302
  return kernel_context
256
303
 
257
304
 
305
+ if contextvars is not None:
306
+ if typing.TYPE_CHECKING:
307
+ async_context_id = contextvars.ContextVar[str]("async_context_id")
308
+ else:
309
+ async_context_id = contextvars.ContextVar("async_context_id")
310
+ async_context_id.set("default")
311
+ else:
312
+ async_context_id = None
313
+
314
+
258
315
  def get_current_thread_key() -> str:
259
- thread = threading.current_thread()
260
- return get_thread_key(thread)
316
+ if not solara.server.settings.kernel.threaded:
317
+ if async_context_id is not None:
318
+ try:
319
+ key = async_context_id.get()
320
+ except LookupError:
321
+ raise RuntimeError("no kernel context set")
322
+ else:
323
+ raise RuntimeError("No threading support, and no contextvars support (Python 3.6 is not supported for this)")
324
+ else:
325
+ thread = threading.current_thread()
326
+ key = get_thread_key(thread)
327
+ return key
261
328
 
262
329
 
263
330
  def get_thread_key(thread: threading.Thread) -> str:
331
+ if not solara.server.settings.kernel.threaded:
332
+ if async_context_id is not None:
333
+ return async_context_id.get()
264
334
  thread_key = thread._name + str(thread._ident) # type: ignore
265
335
  return thread_key
266
336
 
@@ -318,6 +388,7 @@ def initialize_virtual_kernel(session_id: str, kernel_id: str, websocket: websoc
318
388
  widgets.register_comm_target(kernel)
319
389
  appmodule.register_solara_comm_target(kernel)
320
390
  with context:
391
+ assert has_current_context()
321
392
  assert kernel is Kernel.instance()
322
393
  kernel.shell_stream = WebsocketStreamWrapper(websocket, "shell")
323
394
  kernel.control_stream = WebsocketStreamWrapper(websocket, "control")
solara/server/patch.py CHANGED
@@ -295,7 +295,10 @@ def _WidgetContextAwareThread__bootstrap(self):
295
295
  # we need to call this manually, because set_context_for_thread
296
296
  # uses this, and the original _bootstrap calls it too late for us
297
297
  self._set_ident()
298
+ if kernel_context.async_context_id is not None:
299
+ kernel_context.async_context_id.set(self.current_context.id)
298
300
  kernel_context.set_context_for_thread(self.current_context, self)
301
+
299
302
  shell = self.current_context.kernel.shell
300
303
  shell.display_pub.register_hook(shell.display_in_reacton_hook)
301
304
  try:
solara/server/reload.py CHANGED
@@ -152,13 +152,13 @@ class Reloader:
152
152
  self._first = False
153
153
 
154
154
  def _on_change(self, name):
155
- # used for testing
156
- self.reload_event_next.set()
157
155
  # flag that we need to reload all modules next time
158
156
  self.requires_reload = True
159
157
  # and forward callback
160
158
  if self.on_change:
161
159
  self.on_change(name)
160
+ # used for testing
161
+ self.reload_event_next.set()
162
162
 
163
163
  def close(self):
164
164
  self.watcher.close()
solara/server/server.py CHANGED
@@ -13,7 +13,6 @@ import ipyvue
13
13
  import ipywidgets
14
14
  import jinja2
15
15
  import requests
16
-
17
16
  import solara
18
17
  import solara.routing
19
18
  import solara.settings
@@ -35,6 +34,7 @@ ipykernel_major = int(ipykernel.__version__.split(".")[0])
35
34
  ipywidgets_major = int(ipywidgets.__version__.split(".")[0])
36
35
  cache_memory = solara.cache.Memory(max_items=128)
37
36
  vue3 = ipyvue.__version__.startswith("3")
37
+ _redirects: Dict[str, str] = {}
38
38
 
39
39
  # first look at the project directory, then the builtin solara directory
40
40
 
@@ -302,9 +302,9 @@ def read_root(path: str, root_path: str = "", render_kwargs={}, use_nbextensions
302
302
  code = content_utf8
303
303
  elif embed:
304
304
  content_utf8 = content.decode("utf-8")
305
- code = f"<style>/*\npath={path}\n*/\n{content_utf8}</style>"
305
+ code = f'<style class="solara-template-css">/*\npath={path}\n*/\n{content_utf8}</style>'
306
306
  else:
307
- code = f'<link rel="stylesheet" type="text/css" href="{url}">'
307
+ code = f'<link rel="stylesheet" type="text/css" href="{url}" class="solara-template-css">'
308
308
  return Markup(code)
309
309
 
310
310
  def include_js(path: str, module=False) -> Markup:
solara/server/settings.py CHANGED
@@ -86,6 +86,7 @@ class Assets(BaseSettings):
86
86
  class Kernel(BaseSettings):
87
87
  cull_timeout: str = "24h"
88
88
  max_count: Optional[int] = None
89
+ threaded: bool = solara.util.has_threads
89
90
 
90
91
  class Config:
91
92
  env_prefix = "solara_kernel_"
@@ -6,7 +6,7 @@ import os
6
6
  import sys
7
7
  import threading
8
8
  import typing
9
- from typing import Any, Dict, List, Optional, Union, cast
9
+ from typing import Any, Dict, List, Optional, Set, Union, cast
10
10
  from uuid import uuid4
11
11
 
12
12
  import anyio
@@ -40,7 +40,7 @@ from starlette.middleware import Middleware
40
40
  from starlette.middleware.authentication import AuthenticationMiddleware
41
41
  from starlette.middleware.gzip import GZipMiddleware
42
42
  from starlette.requests import HTTPConnection, Request
43
- from starlette.responses import HTMLResponse, JSONResponse, Response
43
+ from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse, Response
44
44
  from starlette.routing import Mount, Route, WebSocketRoute
45
45
  from starlette.staticfiles import StaticFiles
46
46
  from starlette.types import Receive, Scope, Send
@@ -96,10 +96,14 @@ class WebsocketDebugInfo:
96
96
  class WebsocketWrapper(websocket.WebsocketWrapper):
97
97
  ws: starlette.websockets.WebSocket
98
98
 
99
- def __init__(self, ws: starlette.websockets.WebSocket, portal: anyio.from_thread.BlockingPortal) -> None:
99
+ def __init__(self, ws: starlette.websockets.WebSocket, portal: Optional[anyio.from_thread.BlockingPortal]) -> None:
100
100
  self.ws = ws
101
101
  self.portal = portal
102
102
  self.to_send: List[Union[str, bytes]] = []
103
+ # following https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task
104
+ # we store a strong reference
105
+ self.tasks: Set[asyncio.Task] = set()
106
+ self.event_loop = asyncio.get_event_loop()
103
107
  if settings.main.experimental_performance:
104
108
  self.task = asyncio.ensure_future(self.process_messages_task())
105
109
 
@@ -114,28 +118,44 @@ class WebsocketWrapper(websocket.WebsocketWrapper):
114
118
  await self.ws.send_text(first)
115
119
 
116
120
  def close(self):
117
- self.portal.call(self.ws.close) # type: ignore
121
+ if self.portal is None:
122
+ asyncio.ensure_future(self.ws.close())
123
+ else:
124
+ self.portal.call(self.ws.close) # type: ignore
118
125
 
119
126
  def send_text(self, data: str) -> None:
120
- if settings.main.experimental_performance:
121
- self.to_send.append(data)
127
+ if self.portal is None:
128
+ task = self.event_loop.create_task(self.ws.send_text(data))
129
+ self.tasks.add(task)
130
+ task.add_done_callback(self.tasks.discard)
122
131
  else:
123
- self.portal.call(self.ws.send_bytes, data) # type: ignore
132
+ if settings.main.experimental_performance:
133
+ self.to_send.append(data)
134
+ else:
135
+ self.portal.call(self.ws.send_bytes, data) # type: ignore
124
136
 
125
137
  def send_bytes(self, data: bytes) -> None:
126
- if settings.main.experimental_performance:
127
- self.to_send.append(data)
138
+ if self.portal is None:
139
+ task = self.event_loop.create_task(self.ws.send_bytes(data))
140
+ self.tasks.add(task)
141
+ task.add_done_callback(self.tasks.discard)
128
142
  else:
129
- self.portal.call(self.ws.send_bytes, data) # type: ignore
143
+ if settings.main.experimental_performance:
144
+ self.to_send.append(data)
145
+ else:
146
+ self.portal.call(self.ws.send_bytes, data) # type: ignore
130
147
 
131
148
  async def receive(self):
132
- if hasattr(self.portal, "start_task_soon"):
133
- # version 3+
134
- fut = self.portal.start_task_soon(self.ws.receive) # type: ignore
149
+ if self.portal is None:
150
+ message = await asyncio.ensure_future(self.ws.receive())
135
151
  else:
136
- fut = self.portal.spawn_task(self.ws.receive) # type: ignore
152
+ if hasattr(self.portal, "start_task_soon"):
153
+ # version 3+
154
+ fut = self.portal.start_task_soon(self.ws.receive) # type: ignore
155
+ else:
156
+ fut = self.portal.spawn_task(self.ws.receive) # type: ignore
137
157
 
138
- message = await asyncio.wrap_future(fut)
158
+ message = await asyncio.wrap_future(fut)
139
159
  if "text" in message:
140
160
  return message["text"]
141
161
  elif "bytes" in message:
@@ -237,35 +257,45 @@ async def _kernel_connection(ws: starlette.websockets.WebSocket):
237
257
  WebsocketDebugInfo.connecting -= 1
238
258
  WebsocketDebugInfo.open += 1
239
259
 
240
- def websocket_thread_runner(ws: starlette.websockets.WebSocket, portal: anyio.from_thread.BlockingPortal):
241
- async def run():
260
+ async def run(ws_wrapper: WebsocketWrapper):
261
+ if kernel_context.async_context_id is not None:
262
+ kernel_context.async_context_id.set(uuid4().hex)
263
+ assert session_id is not None
264
+ assert kernel_id is not None
265
+ telemetry.connection_open(session_id)
266
+ headers_dict: Dict[str, List[str]] = {}
267
+ for k, v in ws.headers.items():
268
+ if k not in headers_dict.keys():
269
+ headers_dict[k] = [v]
270
+ else:
271
+ headers_dict[k].append(v)
272
+ await server.app_loop(ws_wrapper, ws.cookies, headers_dict, session_id, kernel_id, page_id, user)
273
+
274
+ def websocket_thread_runner(ws_wrapper: WebsocketWrapper, portal: anyio.from_thread.BlockingPortal):
275
+ async def run_wrapper():
242
276
  try:
243
- assert session_id is not None
244
- assert kernel_id is not None
245
- telemetry.connection_open(session_id)
246
- headers_dict: Dict[str, List[str]] = {}
247
- for k, v in ws.headers.items():
248
- if k not in headers_dict.keys():
249
- headers_dict[k] = [v]
250
- else:
251
- headers_dict[k].append(v)
252
- await server.app_loop(ws_wrapper, ws.cookies, headers_dict, session_id, kernel_id, page_id, user)
277
+ await run(ws_wrapper)
253
278
  except: # noqa
254
- await portal.stop(cancel_remaining=True)
279
+ if portal is not None:
280
+ await portal.stop(cancel_remaining=True)
255
281
  raise
256
282
  finally:
257
283
  telemetry.connection_close(session_id)
258
284
 
259
285
  # sometimes throws: RuntimeError: Already running asyncio in this thread
260
- anyio.run(run) # type: ignore
286
+ anyio.run(run_wrapper) # type: ignore
261
287
 
262
288
  # this portal allows us to sync call the websocket calls from this current event loop we are in
263
289
  # each websocket however, is handled from a separate thread
264
290
  try:
265
- async with anyio.from_thread.BlockingPortal() as portal:
266
- ws_wrapper = WebsocketWrapper(ws, portal)
267
- thread_return = anyio.to_thread.run_sync(websocket_thread_runner, ws, portal, limiter=limiter) # type: ignore
268
- await thread_return
291
+ if settings.kernel.threaded:
292
+ async with anyio.from_thread.BlockingPortal() as portal:
293
+ ws_wrapper = WebsocketWrapper(ws, portal)
294
+ thread_return = anyio.to_thread.run_sync(websocket_thread_runner, ws_wrapper, portal, limiter=limiter) # type: ignore
295
+ await thread_return
296
+ else:
297
+ ws_wrapper = WebsocketWrapper(ws, None)
298
+ await run(ws_wrapper)
269
299
  finally:
270
300
  if settings.main.experimental_performance:
271
301
  try:
@@ -315,6 +345,9 @@ async def root(request: Request, fullpath: str = ""):
315
345
  request_path = request.url.path
316
346
  if request_path.startswith(root_path):
317
347
  request_path = request_path[len(root_path) :]
348
+ if request_path in server._redirects.keys():
349
+ return RedirectResponse(server._redirects[request_path])
350
+
318
351
  content = server.read_root(request_path, root_path)
319
352
  if content is None:
320
353
  if settings.oauth.private and not request.user.is_authenticated:
@@ -119,7 +119,7 @@ async def main():
119
119
  ]
120
120
  for dep in requirements:
121
121
  await micropip.install(dep, keep_going=True)
122
- await micropip.install("/wheels/solara-1.29.1-py2.py3-none-any.whl", keep_going=True)
122
+ await micropip.install("/wheels/solara-1.30.1-py2.py3-none-any.whl", keep_going=True)
123
123
  import solara
124
124
 
125
125
  el = solara.Warning("lala")
@@ -11,13 +11,11 @@
11
11
 
12
12
  {% block header %}
13
13
 
14
-
15
14
  {{ pre_rendered_css | safe }}
16
-
17
15
  {% if vue3 == True %}
18
- <link href="{{cdn}}/@widgetti/solara-vuetify3-app@5.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet"></link>
16
+ <link href="{{cdn}}/@widgetti/solara-vuetify3-app@5.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet" class="solara-template-css"></link>
19
17
  {% else %}
20
- <link href="{{cdn}}/@widgetti/solara-vuetify-app@10.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet"></link>
18
+ <link href="{{cdn}}/@widgetti/solara-vuetify-app@10.0.1/dist/main{{ipywidget_major_version}}.css" rel="stylesheet" class="solara-template-css"></link>
21
19
  {% endif %}
22
20
 
23
21
 
@@ -27,8 +25,7 @@
27
25
  {{ resources.include_css("/static/highlight.css") }}
28
26
  {{ resources.include_css("/static/highlight-dark.css") }}
29
27
  {{ resources.include_css("/static/assets/style.css") }}
30
- <style id="jupyter-theme-css">
31
- {{ resources.include_css("/static/assets/theme-"~theme.variant~".css") }}
28
+ <style id="jupyter-theme-css" class="solara-template-css">
32
29
  </style>
33
30
  {{ resources.include_css("/static/assets/custom.css") }}
34
31
 
@@ -38,9 +35,24 @@
38
35
  "kernelId": "1234"
39
36
  }
40
37
  </script>
41
- <style>
38
+ <style class="solara-template-css">
42
39
  {% include "loader-"~theme.loader~".css" %}
43
40
  </style>
41
+ <!-- Include Vuetify background colours so static html from SSG renders the right general colour theme
42
+ before first render. We remove these after Vue takes over rendering to avoid collisions -->
43
+ <style id="pre-render-theme">
44
+ .theme--light .v-sheet {
45
+ background-color: #fff;
46
+ border-color: #fff;
47
+ color: rgba(0,0,0,.87)
48
+ }
49
+
50
+ .theme--dark .v-sheet {
51
+ background-color: #1e1e1e;
52
+ border-color: #1e1e1e;
53
+ color: #fff
54
+ }
55
+ </style>
44
56
  {% endblock header %}
45
57
  </head>
46
58
  {% raw -%}
@@ -175,6 +187,38 @@
175
187
  {# next div is used in ssg code to see if vue took over rendering #}
176
188
  <div id="pre-rendered-html-present" style="display: none"></div>
177
189
  </div>
190
+ <script>
191
+ var theme = {{ theme | tojson | safe }}
192
+ function getThemeVariant() {
193
+ if (localStorage.getItem(':solara:theme.variant')) {
194
+ return JSON.parse(localStorage.getItem(':solara:theme.variant'))
195
+ }
196
+ return theme.variant;
197
+ }
198
+ if (localStorage.getItem(':solara:theme.variant')) {
199
+ theme.variant = JSON.parse(localStorage.getItem(':solara:theme.variant'))
200
+ }
201
+ function prefersDarkScheme() {
202
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
203
+ }
204
+ function inDarkMode() {
205
+ if (getThemeVariant() == 'auto') {
206
+ return prefersDarkScheme();
207
+ }
208
+ else {
209
+ return getThemeVariant() == 'dark';
210
+ }
211
+ }
212
+ // Init theme
213
+ let appContainer = document.getElementById('app');
214
+ if (inDarkMode()) {
215
+ appContainer.classList.remove('theme--light');
216
+ appContainer.classList.add('theme--dark');
217
+ } else {
218
+ appContainer.classList.remove('theme--dark');
219
+ appContainer.classList.add('theme--light');
220
+ }
221
+ </script>
178
222
  {% block after_pre_rendered_html %}{% endblock %}
179
223
  {% if vue3 == True %}
180
224
  <link href="{{cdn}}/@widgetti/solara-vuetify3-app@5.0.1/dist/fonts.css" rel="stylesheet"></link>
@@ -194,6 +238,12 @@
194
238
  <script>
195
239
  solara.rootPath = {{ root_path | tojson | safe}};
196
240
  console.log("rootPath", solara.rootPath);
241
+
242
+ async function changeThemeCSS(theme) {
243
+ let css = await fetch(`${solara.rootPath}/static/assets/theme-${theme}.css`).then(r => r.text());
244
+ document.getElementById('jupyter-theme-css').innerHTML = css;
245
+ }
246
+ changeThemeCSS(inDarkMode() ? 'dark' : 'light');
197
247
  </script>
198
248
 
199
249
  {{ resources.include_js("/static/assets/custom.js") }}
@@ -208,27 +258,6 @@
208
258
  solara.production = {{ production | tojson | safe }};
209
259
  const themeVariants = ['light', 'dark', 'auto']
210
260
  solara.preRendered = {{ pre_rendered_html | safe | length | tojson }} > 0
211
- var theme = {{ theme | tojson | safe }}
212
- function getThemeVariant() {
213
- if (localStorage.getItem(':solara:theme.variant')) {
214
- return JSON.parse(localStorage.getItem(':solara:theme.variant'))
215
- }
216
- return theme.variant;
217
- }
218
- if (localStorage.getItem(':solara:theme.variant')) {
219
- theme.variant = JSON.parse(localStorage.getItem(':solara:theme.variant'))
220
- }
221
- function prefersDarkScheme() {
222
- return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
223
- }
224
- function inDarkMode() {
225
- if (getThemeVariant() == 'auto') {
226
- return prefersDarkScheme();
227
- }
228
- else {
229
- return getThemeVariant() == 'dark';
230
- }
231
- }
232
261
  </script>
233
262
 
234
263
  <script>
@@ -256,11 +285,7 @@
256
285
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
257
286
  this.$vuetify.theme.dark = inDarkMode();
258
287
  });
259
- if (this.$vuetify.theme.dark) {
260
- this.changeThemeCSS('dark');
261
- } else {
262
- this.changeThemeCSS('light');
263
- }
288
+ document.getElementById('pre-render-theme').remove();
264
289
  },
265
290
  methods: {
266
291
  stateReset() {
@@ -275,10 +300,6 @@
275
300
  reload() {
276
301
  location.reload();
277
302
  },
278
- async changeThemeCSS(theme) {
279
- let css = await fetch(`/static/assets/theme-${theme}.css`).then(r => r.text());
280
- document.getElementById('jupyter-theme-css').innerHTML = css;
281
- }
282
303
  },
283
304
  watch: {
284
305
  kernelBusy: function (value) {
@@ -325,15 +346,14 @@
325
346
  }
326
347
  },
327
348
  '$vuetify.theme.dark': function (value) {
328
- let app = document.getElementById('app');
329
349
  if ( value ) {
330
350
  this.changeThemeCSS('dark');
331
- app.classList.remove('theme--light');
332
- app.classList.add('theme--dark');
351
+ appContainer.classList.remove('theme--light');
352
+ appContainer.classList.add('theme--dark');
333
353
  } else {
334
354
  this.changeThemeCSS('light');
335
- app.classList.remove('theme--dark');
336
- app.classList.add('theme--light');
355
+ appContainer.classList.remove('theme--dark');
356
+ appContainer.classList.add('theme--light');
337
357
  }
338
358
  }
339
359
  },
solara/tasks.py CHANGED
@@ -32,12 +32,7 @@ P = typing_extensions.ParamSpec("P")
32
32
 
33
33
  logger = logging.getLogger("solara.task")
34
34
 
35
- try:
36
- threading.Thread(target=lambda: None).start()
37
- has_threads = True
38
- except RuntimeError:
39
- has_threads = False
40
- has_threads
35
+ has_threads = solara.util.has_threads
41
36
 
42
37
 
43
38
  class TaskState(Enum):
solara/util.py CHANGED
@@ -1,12 +1,15 @@
1
1
  import base64
2
2
  import contextlib
3
+ import gzip
3
4
  import hashlib
5
+ import json
4
6
  import os
5
7
  import sys
6
8
  import threading
7
9
  from collections import abc
8
10
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Dict, List, Tuple, Union
11
+ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
12
+ from urllib.parse import urlencode
10
13
 
11
14
  if TYPE_CHECKING:
12
15
  import numpy as np
@@ -21,6 +24,12 @@ SOLARA_ALLOW_OTHER_TRACER = os.environ.get("SOLARA_ALLOW_OTHER_TRACER", False) i
21
24
  ipyvuetify_major_version = int(ipyvuetify.__version__.split(".")[0])
22
25
  ipywidgets_major = int(ipywidgets.__version__.split(".")[0])
23
26
 
27
+ try:
28
+ threading.Thread(target=lambda: None).start()
29
+ has_threads = True
30
+ except RuntimeError:
31
+ has_threads = False
32
+
24
33
 
25
34
  def github_url(file):
26
35
  rel_path = os.path.relpath(file, Path(solara.__file__).parent.parent)
@@ -28,6 +37,19 @@ def github_url(file):
28
37
  return github_url
29
38
 
30
39
 
40
+ def pycafe_url(*, path: Path, requirements: Optional[List[str]] = None):
41
+ json_object = {"code": path.read_text("utf8")}
42
+ if requirements:
43
+ json_object["requirements"] = "\n".join(requirements)
44
+ json_text = json.dumps(json_object)
45
+ # gzip -> base64
46
+ compressed_json_text = gzip.compress(json_text.encode("utf8"))
47
+ base64_text = base64.b64encode(compressed_json_text).decode("utf8")
48
+ query = urlencode({"c": base64_text})
49
+ base_url = "https://app.py.cafe"
50
+ return f"{base_url}/snippet/solara?{query}"
51
+
52
+
31
53
  def github_edit_url(file):
32
54
  # e.g. https://github.com/widgetti/solara/edit/master/solara/__init__.py
33
55
  rel_path = os.path.relpath(file, Path(solara.__file__).parent.parent)