solara-ui 1.31.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. prefix/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  2. prefix/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  3. solara/__init__.py +124 -0
  4. solara/__main__.py +734 -0
  5. solara/alias.py +6 -0
  6. solara/autorouting.py +546 -0
  7. solara/cache.py +303 -0
  8. solara/checks.html +71 -0
  9. solara/checks.py +224 -0
  10. solara/comm.py +28 -0
  11. solara/components/__init__.py +59 -0
  12. solara/components/alert.py +155 -0
  13. solara/components/applayout.py +393 -0
  14. solara/components/button.py +85 -0
  15. solara/components/card.py +87 -0
  16. solara/components/checkbox.py +50 -0
  17. solara/components/code_highlight_css.py +11 -0
  18. solara/components/code_highlight_css.vue +63 -0
  19. solara/components/columns.py +159 -0
  20. solara/components/component_vue.py +110 -0
  21. solara/components/cross_filter.py +335 -0
  22. solara/components/dataframe.py +546 -0
  23. solara/components/datatable.py +221 -0
  24. solara/components/datatable.vue +175 -0
  25. solara/components/details.py +21 -0
  26. solara/components/download.vue +35 -0
  27. solara/components/echarts.py +75 -0
  28. solara/components/echarts.vue +128 -0
  29. solara/components/figure_altair.py +39 -0
  30. solara/components/file_browser.py +182 -0
  31. solara/components/file_download.py +199 -0
  32. solara/components/file_drop.py +139 -0
  33. solara/components/file_drop.vue +83 -0
  34. solara/components/file_list_widget.vue +78 -0
  35. solara/components/head.py +27 -0
  36. solara/components/head_tag.py +49 -0
  37. solara/components/head_tag.vue +60 -0
  38. solara/components/image.py +173 -0
  39. solara/components/input.py +436 -0
  40. solara/components/link.py +55 -0
  41. solara/components/markdown.py +378 -0
  42. solara/components/markdown_editor.py +25 -0
  43. solara/components/markdown_editor.vue +362 -0
  44. solara/components/matplotlib.py +74 -0
  45. solara/components/meta.py +47 -0
  46. solara/components/misc.py +333 -0
  47. solara/components/pivot_table.py +258 -0
  48. solara/components/pivot_table.vue +158 -0
  49. solara/components/progress.py +47 -0
  50. solara/components/select.py +182 -0
  51. solara/components/select.vue +27 -0
  52. solara/components/slider.py +442 -0
  53. solara/components/slider_date.vue +56 -0
  54. solara/components/spinner-solara.vue +105 -0
  55. solara/components/spinner.py +30 -0
  56. solara/components/sql_code.py +33 -0
  57. solara/components/sql_code.vue +128 -0
  58. solara/components/style.py +105 -0
  59. solara/components/switch.py +68 -0
  60. solara/components/tab_navigation.py +37 -0
  61. solara/components/title.py +90 -0
  62. solara/components/title.vue +38 -0
  63. solara/components/togglebuttons.py +200 -0
  64. solara/components/tooltip.py +61 -0
  65. solara/datatypes.py +143 -0
  66. solara/express.py +241 -0
  67. solara/hooks/__init__.py +4 -0
  68. solara/hooks/dataframe.py +99 -0
  69. solara/hooks/misc.py +263 -0
  70. solara/hooks/use_reactive.py +129 -0
  71. solara/hooks/use_thread.py +129 -0
  72. solara/kitchensink.py +8 -0
  73. solara/lab/__init__.py +34 -0
  74. solara/lab/components/__init__.py +6 -0
  75. solara/lab/components/chat.py +203 -0
  76. solara/lab/components/confirmation_dialog.py +163 -0
  77. solara/lab/components/cross_filter.py +7 -0
  78. solara/lab/components/input_date.py +298 -0
  79. solara/lab/components/menu.py +181 -0
  80. solara/lab/components/menu.vue +38 -0
  81. solara/lab/components/tabs.py +274 -0
  82. solara/lab/components/theming.py +98 -0
  83. solara/lab/components/theming.vue +72 -0
  84. solara/lab/hooks/__init__.py +0 -0
  85. solara/lab/hooks/dataframe.py +12 -0
  86. solara/lab/toestand.py +3 -0
  87. solara/lab/utils/__init__.py +2 -0
  88. solara/lab/utils/cookies.py +5 -0
  89. solara/lab/utils/dataframe.py +115 -0
  90. solara/lab/utils/headers.py +5 -0
  91. solara/layout.py +44 -0
  92. solara/lifecycle.py +46 -0
  93. solara/minisettings.py +133 -0
  94. solara/py.typed +0 -0
  95. solara/reactive.py +93 -0
  96. solara/routing.py +268 -0
  97. solara/scope/__init__.py +88 -0
  98. solara/scope/types.py +55 -0
  99. solara/server/__init__.py +0 -0
  100. solara/server/app.py +491 -0
  101. solara/server/assets/custom.css +1 -0
  102. solara/server/assets/custom.js +1 -0
  103. solara/server/assets/favicon.png +0 -0
  104. solara/server/assets/favicon.svg +5 -0
  105. solara/server/assets/style.css +1665 -0
  106. solara/server/assets/theme-dark.css +437 -0
  107. solara/server/assets/theme-light.css +420 -0
  108. solara/server/assets/theme.js +3 -0
  109. solara/server/cdn_helper.py +77 -0
  110. solara/server/esm.py +69 -0
  111. solara/server/fastapi.py +5 -0
  112. solara/server/flask.py +286 -0
  113. solara/server/jupyter/__init__.py +2 -0
  114. solara/server/jupyter/cdn_handler.py +28 -0
  115. solara/server/jupyter/server_extension.py +29 -0
  116. solara/server/jupytertools.py +46 -0
  117. solara/server/kernel.py +338 -0
  118. solara/server/kernel_context.py +357 -0
  119. solara/server/patch.py +552 -0
  120. solara/server/reload.py +242 -0
  121. solara/server/server.py +456 -0
  122. solara/server/settings.py +215 -0
  123. solara/server/shell.py +251 -0
  124. solara/server/starlette.py +601 -0
  125. solara/server/static/ansi.js +270 -0
  126. solara/server/static/highlight-dark.css +82 -0
  127. solara/server/static/highlight.css +43 -0
  128. solara/server/static/main-vuetify.js +260 -0
  129. solara/server/static/main.js +163 -0
  130. solara/server/static/solara_bootstrap.py +129 -0
  131. solara/server/static/sun.svg +23 -0
  132. solara/server/static/webworker.js +42 -0
  133. solara/server/telemetry.py +212 -0
  134. solara/server/templates/index.html.j2 +1 -0
  135. solara/server/templates/loader-plain.css +11 -0
  136. solara/server/templates/loader-plain.html +20 -0
  137. solara/server/templates/loader-solara.css +111 -0
  138. solara/server/templates/loader-solara.html +40 -0
  139. solara/server/templates/plain.html +82 -0
  140. solara/server/templates/solara.html.j2 +446 -0
  141. solara/server/threaded.py +75 -0
  142. solara/server/utils.py +30 -0
  143. solara/server/websocket.py +45 -0
  144. solara/settings.py +56 -0
  145. solara/tasks.py +837 -0
  146. solara/template/button.py +16 -0
  147. solara/template/markdown.py +42 -0
  148. solara/template/portal/.flake8 +6 -0
  149. solara/template/portal/.pre-commit-config.yaml +28 -0
  150. solara/template/portal/LICENSE +21 -0
  151. solara/template/portal/Procfile +7 -0
  152. solara/template/portal/mypy.ini +3 -0
  153. solara/template/portal/pyproject.toml +26 -0
  154. solara/template/portal/solara_portal/__init__.py +4 -0
  155. solara/template/portal/solara_portal/components/__init__.py +2 -0
  156. solara/template/portal/solara_portal/components/article.py +28 -0
  157. solara/template/portal/solara_portal/components/data.py +28 -0
  158. solara/template/portal/solara_portal/components/header.py +6 -0
  159. solara/template/portal/solara_portal/components/layout.py +6 -0
  160. solara/template/portal/solara_portal/content/articles/equis-in-vidi.md +85 -0
  161. solara/template/portal/solara_portal/content/articles/substiterat-vati.md +70 -0
  162. solara/template/portal/solara_portal/data.py +60 -0
  163. solara/template/portal/solara_portal/pages/__init__.py +67 -0
  164. solara/template/portal/solara_portal/pages/article/__init__.py +26 -0
  165. solara/template/portal/solara_portal/pages/tabular.py +29 -0
  166. solara/template/portal/solara_portal/pages/viz/__init__.py +70 -0
  167. solara/template/portal/solara_portal/pages/viz/overview.py +14 -0
  168. solara/test/__init__.py +0 -0
  169. solara/test/pytest_plugin.py +697 -0
  170. solara/toestand.py +772 -0
  171. solara/util.py +308 -0
  172. solara/website/__init__.py +0 -0
  173. solara/website/assets/custom.css +468 -0
  174. solara/website/assets/images/logo-small.png +0 -0
  175. solara/website/assets/images/logo.svg +17 -0
  176. solara/website/assets/images/logo_white.svg +50 -0
  177. solara/website/assets/theme.js +8 -0
  178. solara/website/components/__init__.py +5 -0
  179. solara/website/components/algolia.vue +24 -0
  180. solara/website/components/algolia_api.vue +187 -0
  181. solara/website/components/docs.py +118 -0
  182. solara/website/components/header.py +72 -0
  183. solara/website/components/hero.py +15 -0
  184. solara/website/components/mailchimp.py +12 -0
  185. solara/website/components/mailchimp.vue +47 -0
  186. solara/website/components/markdown.py +30 -0
  187. solara/website/components/notebook.py +171 -0
  188. solara/website/pages/__init__.py +575 -0
  189. solara/website/pages/apps/__init__.py +16 -0
  190. solara/website/pages/apps/authorization/__init__.py +119 -0
  191. solara/website/pages/apps/authorization/admin.py +12 -0
  192. solara/website/pages/apps/authorization/users.py +12 -0
  193. solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
  194. solara/website/pages/apps/layout-demo.py +40 -0
  195. solara/website/pages/apps/multipage/__init__.py +38 -0
  196. solara/website/pages/apps/multipage/page1.py +26 -0
  197. solara/website/pages/apps/multipage/page2.py +34 -0
  198. solara/website/pages/apps/scatter.py +136 -0
  199. solara/website/pages/apps/scrolling.py +63 -0
  200. solara/website/pages/apps/tutorial-streamlit.py +18 -0
  201. solara/website/pages/changelog/__init__.py +8 -0
  202. solara/website/pages/changelog/changelog.md +204 -0
  203. solara/website/pages/contact/__init__.py +8 -0
  204. solara/website/pages/contact/contact.md +17 -0
  205. solara/website/pages/doc_use_download.py +85 -0
  206. solara/website/pages/documentation/__init__.py +184 -0
  207. solara/website/pages/documentation/advanced/__init__.py +9 -0
  208. solara/website/pages/documentation/advanced/content/00-overview.md +1 -0
  209. solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +6 -0
  210. solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +196 -0
  211. solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +125 -0
  212. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +162 -0
  213. solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +69 -0
  214. solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +49 -0
  215. solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +124 -0
  216. solara/website/pages/documentation/advanced/content/15-reference/00-overview.md +3 -0
  217. solara/website/pages/documentation/advanced/content/15-reference/40-static_files.md +31 -0
  218. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +36 -0
  219. solara/website/pages/documentation/advanced/content/15-reference/60-static-site-generation.md +59 -0
  220. solara/website/pages/documentation/advanced/content/15-reference/70-search.md +34 -0
  221. solara/website/pages/documentation/advanced/content/15-reference/80-reloading.md +34 -0
  222. solara/website/pages/documentation/advanced/content/15-reference/90-notebook-support.md +7 -0
  223. solara/website/pages/documentation/advanced/content/15-reference/95-caching.md +148 -0
  224. solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +10 -0
  225. solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +35 -0
  226. solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +42 -0
  227. solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +28 -0
  228. solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +108 -0
  229. solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +23 -0
  230. solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +7 -0
  231. solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +166 -0
  232. solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +18 -0
  233. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +240 -0
  234. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +97 -0
  235. solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +12 -0
  236. solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +1 -0
  237. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +171 -0
  238. solara/website/pages/documentation/advanced/content/40-development/00-overview.md +0 -0
  239. solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +45 -0
  240. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +76 -0
  241. solara/website/pages/documentation/api/__init__.py +19 -0
  242. solara/website/pages/documentation/api/cross_filter/__init__.py +9 -0
  243. solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +23 -0
  244. solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +22 -0
  245. solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +22 -0
  246. solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +22 -0
  247. solara/website/pages/documentation/api/hooks/__init__.py +9 -0
  248. solara/website/pages/documentation/api/hooks/use_cross_filter.py +25 -0
  249. solara/website/pages/documentation/api/hooks/use_dark_effective.py +12 -0
  250. solara/website/pages/documentation/api/hooks/use_effect.md +43 -0
  251. solara/website/pages/documentation/api/hooks/use_effect.py +9 -0
  252. solara/website/pages/documentation/api/hooks/use_exception.py +33 -0
  253. solara/website/pages/documentation/api/hooks/use_memo.md +16 -0
  254. solara/website/pages/documentation/api/hooks/use_memo.py +9 -0
  255. solara/website/pages/documentation/api/hooks/use_previous.py +33 -0
  256. solara/website/pages/documentation/api/hooks/use_reactive.py +16 -0
  257. solara/website/pages/documentation/api/hooks/use_state.py +10 -0
  258. solara/website/pages/documentation/api/hooks/use_state_or_update.py +69 -0
  259. solara/website/pages/documentation/api/hooks/use_thread.md +58 -0
  260. solara/website/pages/documentation/api/hooks/use_thread.py +44 -0
  261. solara/website/pages/documentation/api/hooks/use_trait_observe.py +12 -0
  262. solara/website/pages/documentation/api/routing/__init__.py +9 -0
  263. solara/website/pages/documentation/api/routing/generate_routes.py +10 -0
  264. solara/website/pages/documentation/api/routing/generate_routes_directory.py +10 -0
  265. solara/website/pages/documentation/api/routing/resolve_path.py +35 -0
  266. solara/website/pages/documentation/api/routing/route.py +31 -0
  267. solara/website/pages/documentation/api/routing/use_route.py +80 -0
  268. solara/website/pages/documentation/api/routing/use_router.py +16 -0
  269. solara/website/pages/documentation/api/utilities/__init__.py +9 -0
  270. solara/website/pages/documentation/api/utilities/component_vue.py +10 -0
  271. solara/website/pages/documentation/api/utilities/computed.py +16 -0
  272. solara/website/pages/documentation/api/utilities/display.py +16 -0
  273. solara/website/pages/documentation/api/utilities/get_kernel_id.py +16 -0
  274. solara/website/pages/documentation/api/utilities/get_session_id.py +16 -0
  275. solara/website/pages/documentation/api/utilities/memoize.py +35 -0
  276. solara/website/pages/documentation/api/utilities/on_kernel_start.py +27 -0
  277. solara/website/pages/documentation/api/utilities/reactive.py +16 -0
  278. solara/website/pages/documentation/api/utilities/widget.py +104 -0
  279. solara/website/pages/documentation/components/__init__.py +12 -0
  280. solara/website/pages/documentation/components/advanced/__init__.py +9 -0
  281. solara/website/pages/documentation/components/advanced/link.py +27 -0
  282. solara/website/pages/documentation/components/advanced/meta.py +20 -0
  283. solara/website/pages/documentation/components/advanced/style.py +45 -0
  284. solara/website/pages/documentation/components/common.py +9 -0
  285. solara/website/pages/documentation/components/data/__init__.py +9 -0
  286. solara/website/pages/documentation/components/data/dataframe.py +44 -0
  287. solara/website/pages/documentation/components/data/pivot_table.py +81 -0
  288. solara/website/pages/documentation/components/enterprise/__init__.py +9 -0
  289. solara/website/pages/documentation/components/enterprise/avatar.py +24 -0
  290. solara/website/pages/documentation/components/enterprise/avatar_menu.py +25 -0
  291. solara/website/pages/documentation/components/input/__init__.py +9 -0
  292. solara/website/pages/documentation/components/input/button.py +23 -0
  293. solara/website/pages/documentation/components/input/checkbox.py +10 -0
  294. solara/website/pages/documentation/components/input/file_browser.py +32 -0
  295. solara/website/pages/documentation/components/input/file_drop.py +76 -0
  296. solara/website/pages/documentation/components/input/input.py +19 -0
  297. solara/website/pages/documentation/components/input/select.py +22 -0
  298. solara/website/pages/documentation/components/input/slider.py +29 -0
  299. solara/website/pages/documentation/components/input/switch.py +10 -0
  300. solara/website/pages/documentation/components/input/togglebuttons.py +21 -0
  301. solara/website/pages/documentation/components/lab/__init__.py +9 -0
  302. solara/website/pages/documentation/components/lab/chat.py +109 -0
  303. solara/website/pages/documentation/components/lab/confirmation_dialog.py +55 -0
  304. solara/website/pages/documentation/components/lab/cookies_headers.py +48 -0
  305. solara/website/pages/documentation/components/lab/input_date.py +20 -0
  306. solara/website/pages/documentation/components/lab/menu.py +22 -0
  307. solara/website/pages/documentation/components/lab/tab.py +25 -0
  308. solara/website/pages/documentation/components/lab/tabs.py +45 -0
  309. solara/website/pages/documentation/components/lab/task.py +11 -0
  310. solara/website/pages/documentation/components/lab/theming.py +72 -0
  311. solara/website/pages/documentation/components/lab/use_task.py +11 -0
  312. solara/website/pages/documentation/components/layout/__init__.py +9 -0
  313. solara/website/pages/documentation/components/layout/app_bar.py +16 -0
  314. solara/website/pages/documentation/components/layout/app_bar_title.py +16 -0
  315. solara/website/pages/documentation/components/layout/app_layout.py +24 -0
  316. solara/website/pages/documentation/components/layout/card.py +15 -0
  317. solara/website/pages/documentation/components/layout/card_actions.py +16 -0
  318. solara/website/pages/documentation/components/layout/column.py +30 -0
  319. solara/website/pages/documentation/components/layout/columns.py +27 -0
  320. solara/website/pages/documentation/components/layout/columns_responsive.py +68 -0
  321. solara/website/pages/documentation/components/layout/griddraggable.py +62 -0
  322. solara/website/pages/documentation/components/layout/gridfixed.py +21 -0
  323. solara/website/pages/documentation/components/layout/hbox.py +18 -0
  324. solara/website/pages/documentation/components/layout/row.py +30 -0
  325. solara/website/pages/documentation/components/layout/sidebar.py +24 -0
  326. solara/website/pages/documentation/components/layout/vbox.py +19 -0
  327. solara/website/pages/documentation/components/output/__init__.py +9 -0
  328. solara/website/pages/documentation/components/output/file_download.py +11 -0
  329. solara/website/pages/documentation/components/output/html.py +21 -0
  330. solara/website/pages/documentation/components/output/image.py +11 -0
  331. solara/website/pages/documentation/components/output/markdown.py +57 -0
  332. solara/website/pages/documentation/components/output/markdown_editor.py +51 -0
  333. solara/website/pages/documentation/components/output/sql_code.py +85 -0
  334. solara/website/pages/documentation/components/output/tooltip.py +11 -0
  335. solara/website/pages/documentation/components/page/__init__.py +9 -0
  336. solara/website/pages/documentation/components/page/head.py +18 -0
  337. solara/website/pages/documentation/components/page/title.py +27 -0
  338. solara/website/pages/documentation/components/status/__init__.py +9 -0
  339. solara/website/pages/documentation/components/status/error.py +40 -0
  340. solara/website/pages/documentation/components/status/info.py +40 -0
  341. solara/website/pages/documentation/components/status/progress.py +10 -0
  342. solara/website/pages/documentation/components/status/spinner.py +11 -0
  343. solara/website/pages/documentation/components/status/success.py +40 -0
  344. solara/website/pages/documentation/components/status/warning.py +47 -0
  345. solara/website/pages/documentation/components/viz/__init__.py +9 -0
  346. solara/website/pages/documentation/components/viz/altair.py +42 -0
  347. solara/website/pages/documentation/components/viz/echarts.py +75 -0
  348. solara/website/pages/documentation/components/viz/matplotlib.py +30 -0
  349. solara/website/pages/documentation/components/viz/plotly.py +63 -0
  350. solara/website/pages/documentation/components/viz/plotly_express.py +41 -0
  351. solara/website/pages/documentation/examples/__init__.py +52 -0
  352. solara/website/pages/documentation/examples/ai/__init__.py +11 -0
  353. solara/website/pages/documentation/examples/ai/chatbot.py +95 -0
  354. solara/website/pages/documentation/examples/ai/tokenizer.py +107 -0
  355. solara/website/pages/documentation/examples/basics/__init__.py +10 -0
  356. solara/website/pages/documentation/examples/basics/sine.py +28 -0
  357. solara/website/pages/documentation/examples/fullscreen/__init__.py +10 -0
  358. solara/website/pages/documentation/examples/fullscreen/authorization.py +3 -0
  359. solara/website/pages/documentation/examples/fullscreen/layout_demo.py +3 -0
  360. solara/website/pages/documentation/examples/fullscreen/multipage.py +3 -0
  361. solara/website/pages/documentation/examples/fullscreen/scatter.py +3 -0
  362. solara/website/pages/documentation/examples/fullscreen/scrolling.py +3 -0
  363. solara/website/pages/documentation/examples/fullscreen/tutorial_streamlit.py +3 -0
  364. solara/website/pages/documentation/examples/general/__init__.py +10 -0
  365. solara/website/pages/documentation/examples/general/custom_storage.py +70 -0
  366. solara/website/pages/documentation/examples/general/deploy_model.py +115 -0
  367. solara/website/pages/documentation/examples/general/live_update.py +38 -0
  368. solara/website/pages/documentation/examples/general/login_oauth.py +81 -0
  369. solara/website/pages/documentation/examples/general/mycard.vue +58 -0
  370. solara/website/pages/documentation/examples/general/pokemon_search.py +51 -0
  371. solara/website/pages/documentation/examples/general/vue_component.py +50 -0
  372. solara/website/pages/documentation/examples/ipycanvas.py +49 -0
  373. solara/website/pages/documentation/examples/libraries/__init__.py +10 -0
  374. solara/website/pages/documentation/examples/libraries/altair.py +64 -0
  375. solara/website/pages/documentation/examples/libraries/bqplot.py +39 -0
  376. solara/website/pages/documentation/examples/libraries/ipyleaflet.py +33 -0
  377. solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +66 -0
  378. solara/website/pages/documentation/examples/utilities/__init__.py +10 -0
  379. solara/website/pages/documentation/examples/utilities/calculator.py +157 -0
  380. solara/website/pages/documentation/examples/utilities/countdown_timer.py +64 -0
  381. solara/website/pages/documentation/examples/utilities/todo.py +196 -0
  382. solara/website/pages/documentation/examples/visualization/__init__.py +6 -0
  383. solara/website/pages/documentation/examples/visualization/annotator.py +69 -0
  384. solara/website/pages/documentation/examples/visualization/linked_views.py +84 -0
  385. solara/website/pages/documentation/examples/visualization/plotly.py +44 -0
  386. solara/website/pages/documentation/faq/__init__.py +12 -0
  387. solara/website/pages/documentation/faq/content/99-faq.md +76 -0
  388. solara/website/pages/documentation/getting_started/__init__.py +9 -0
  389. solara/website/pages/documentation/getting_started/content/00-quickstart.md +89 -0
  390. solara/website/pages/documentation/getting_started/content/01-introduction.md +125 -0
  391. solara/website/pages/documentation/getting_started/content/02-installing.md +134 -0
  392. solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +14 -0
  393. solara/website/pages/documentation/getting_started/content/04-tutorials/10_data_science.py +13 -0
  394. solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +89 -0
  395. solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +124 -0
  396. solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +146 -0
  397. solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +144 -0
  398. solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +64 -0
  399. solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz +0 -0
  400. solara/website/pages/documentation/getting_started/content/04-tutorials/_data_science.ipynb +445 -0
  401. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +1000 -0
  402. solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +11 -0
  403. solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +223 -0
  404. solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +88 -0
  405. solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +7 -0
  406. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +273 -0
  407. solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +80 -0
  408. solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
  409. solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +26 -0
  410. solara/website/pages/docutils.py +38 -0
  411. solara/website/pages/showcase/__init__.py +105 -0
  412. solara/website/pages/showcase/domino_code_assist.py +60 -0
  413. solara/website/pages/showcase/planeto_tessa.py +19 -0
  414. solara/website/pages/showcase/solara_dev.py +54 -0
  415. solara/website/pages/showcase/solarathon_2023_team_2.py +22 -0
  416. solara/website/pages/showcase/solarathon_2023_team_4.py +22 -0
  417. solara/website/pages/showcase/solarathon_2023_team_5.py +23 -0
  418. solara/website/pages/showcase/solarathon_2023_team_6.py +34 -0
  419. solara/website/pages/showcase/wanderlust.py +27 -0
  420. solara/website/public/beach.jpeg +0 -0
  421. solara/website/public/logo.svg +6 -0
  422. solara/website/public/social/discord.svg +1 -0
  423. solara/website/public/social/github.svg +1 -0
  424. solara/website/public/social/twitter.svg +3 -0
  425. solara/website/public/success.html +25 -0
  426. solara/website/templates/index.html.j2 +117 -0
  427. solara/website/utils.py +51 -0
  428. solara/widgets/__init__.py +1 -0
  429. solara/widgets/vue/gridlayout.vue +110 -0
  430. solara/widgets/vue/html.vue +4 -0
  431. solara/widgets/vue/navigator.vue +104 -0
  432. solara/widgets/vue/vegalite.vue +115 -0
  433. solara/widgets/widgets.py +66 -0
  434. solara_ui-1.31.0.data/data/etc/jupyter/jupyter_notebook_config.d/solara.json +7 -0
  435. solara_ui-1.31.0.data/data/etc/jupyter/jupyter_server_config.d/solara.json +7 -0
  436. solara_ui-1.31.0.dist-info/METADATA +158 -0
  437. solara_ui-1.31.0.dist-info/RECORD +439 -0
  438. solara_ui-1.31.0.dist-info/WHEEL +5 -0
  439. solara_ui-1.31.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,601 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import math
5
+ import os
6
+ import sys
7
+ import threading
8
+ import typing
9
+ from typing import Any, Dict, List, Optional, Set, Union, cast
10
+ from uuid import uuid4
11
+
12
+ import anyio
13
+ import starlette.websockets
14
+ import uvicorn.server
15
+ import websockets.legacy.http
16
+
17
+ try:
18
+ import solara_enterprise
19
+
20
+ del solara_enterprise
21
+ has_solara_enterprise = True
22
+ except ImportError:
23
+ has_solara_enterprise = False
24
+ if has_solara_enterprise and sys.version_info[:2] > (3, 6):
25
+ has_auth_support = True
26
+ from solara_enterprise.auth.middleware import MutateDetectSessionMiddleware
27
+ from solara_enterprise.auth.starlette import (
28
+ AuthBackend,
29
+ authorize,
30
+ get_user,
31
+ login,
32
+ logout,
33
+ )
34
+ else:
35
+ has_auth_support = False
36
+
37
+ from starlette.applications import Starlette
38
+ from starlette.exceptions import HTTPException
39
+ from starlette.middleware import Middleware
40
+ from starlette.middleware.authentication import AuthenticationMiddleware
41
+ from starlette.middleware.gzip import GZipMiddleware
42
+ from starlette.requests import HTTPConnection, Request
43
+ from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse, Response
44
+ from starlette.routing import Mount, Route, WebSocketRoute
45
+ from starlette.staticfiles import StaticFiles
46
+ from starlette.types import Receive, Scope, Send
47
+
48
+ import solara
49
+ import solara.settings
50
+ from solara.server.threaded import ServerBase
51
+
52
+ from . import app as appmod
53
+ from . import kernel_context, server, settings, telemetry, websocket
54
+ from .cdn_helper import cdn_url_path, get_path
55
+
56
+ os.environ["SERVER_SOFTWARE"] = "solara/" + str(solara.__version__)
57
+ limiter: Optional[anyio.CapacityLimiter] = None
58
+ lock = threading.Lock()
59
+
60
+
61
+ def _ensure_limiter():
62
+ # in older anyios (<4) the limiter can only be created in an async context
63
+ # so we call this in a starlette handler
64
+ global limiter
65
+ if limiter is None:
66
+ with lock:
67
+ if limiter is None:
68
+ limiter = anyio.CapacityLimiter(settings.kernel.max_count if settings.kernel.max_count is not None else math.inf)
69
+
70
+
71
+ logger = logging.getLogger("solara.server.fastapi")
72
+ # if we add these to the router, the server_test does not run (404's)
73
+ prefix = ""
74
+
75
+ # The limit for starlette's http traffic should come from h11's DEFAULT_MAX_INCOMPLETE_EVENT_SIZE=16kb
76
+ # In practice, testing with 132kb cookies (server_test.py:test_large_cookie) seems to work fine.
77
+ # For the websocket, the limit is set to 4kb till 10.4, see
78
+ # * https://github.com/aaugustin/websockets/blob/10.4/src/websockets/legacy/http.py#L14
79
+ # Later releases should set this to 8kb. See
80
+ # * https://github.com/aaugustin/websockets/commit/8ce4739b7efed3ac78b287da7fb5e537f78e72aa
81
+ # * https://github.com/aaugustin/websockets/issues/743
82
+ # Since starlette seems to accept really large values for http, lets do the same for websockets
83
+ # An arbitrarily large value we settled on for now is 32kb
84
+ # If we don't do this, users with many cookies will fail to get a websocket connection.
85
+ websockets.legacy.http.MAX_LINE = 1024 * 32
86
+
87
+
88
+ class WebsocketDebugInfo:
89
+ lock = threading.Lock()
90
+ attempts = 0
91
+ connecting = 0
92
+ open = 0
93
+ closed = 0
94
+
95
+
96
+ class WebsocketWrapper(websocket.WebsocketWrapper):
97
+ ws: starlette.websockets.WebSocket
98
+
99
+ def __init__(self, ws: starlette.websockets.WebSocket, portal: Optional[anyio.from_thread.BlockingPortal]) -> None:
100
+ self.ws = ws
101
+ self.portal = portal
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()
107
+ if settings.main.experimental_performance:
108
+ self.task = asyncio.ensure_future(self.process_messages_task())
109
+
110
+ async def process_messages_task(self):
111
+ while True:
112
+ await asyncio.sleep(0.05)
113
+ while len(self.to_send) > 0:
114
+ first = self.to_send.pop(0)
115
+ if isinstance(first, bytes):
116
+ await self.ws.send_bytes(first)
117
+ else:
118
+ await self.ws.send_text(first)
119
+
120
+ def close(self):
121
+ if self.portal is None:
122
+ asyncio.ensure_future(self.ws.close())
123
+ else:
124
+ self.portal.call(self.ws.close) # type: ignore
125
+
126
+ def send_text(self, data: str) -> None:
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)
131
+ else:
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
136
+
137
+ def send_bytes(self, data: bytes) -> None:
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)
142
+ else:
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
147
+
148
+ async def receive(self):
149
+ if self.portal is None:
150
+ message = await asyncio.ensure_future(self.ws.receive())
151
+ else:
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
157
+
158
+ message = await asyncio.wrap_future(fut)
159
+ if "text" in message:
160
+ return message["text"]
161
+ elif "bytes" in message:
162
+ return message["bytes"]
163
+ elif message.get("type") == "websocket.disconnect":
164
+ raise websocket.WebSocketDisconnect()
165
+ else:
166
+ raise RuntimeError(f"Unknown message type {message}")
167
+
168
+
169
+ class ServerStarlette(ServerBase):
170
+ server: uvicorn.server.Server
171
+ name = "starlette"
172
+
173
+ def __init__(self, port: int, host: str = "localhost", starlette_app=None, **kwargs):
174
+ super().__init__(port, host, **kwargs)
175
+ self.app = starlette_app or app
176
+
177
+ def has_started(self):
178
+ return self.server.started
179
+
180
+ def signal_stop(self):
181
+ self.server.should_exit = True
182
+ # this cause uvicorn to not wait for background tasks, e.g.:
183
+ # <Task pending name='Task-55'
184
+ # coro=<WebSocketProtocol.run_asgi() running at
185
+ # /.../uvicorn/protocols/websockets/websockets_impl.py:184>
186
+ # wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x16896aa00>()]>
187
+ # cb=[WebSocketProtocol.on_task_complete()]>
188
+ self.server.force_exit = True
189
+ self.server.lifespan.should_exit = True
190
+
191
+ def serve(self):
192
+ from uvicorn.config import Config
193
+ from uvicorn.server import Server
194
+
195
+ if sys.version_info[:2] < (3, 7):
196
+ # make python 3.6 work
197
+ import asyncio
198
+
199
+ loop = asyncio.new_event_loop()
200
+ asyncio.set_event_loop(loop)
201
+
202
+ # uvloop will trigger a: RuntimeError: There is no current event loop in thread 'fastapi-thread'
203
+ config = Config(self.app, host=self.host, port=self.port, **self.kwargs, access_log=False, loop="asyncio")
204
+ self.server = Server(config=config)
205
+ self.started.set()
206
+ self.server.run()
207
+
208
+
209
+ async def kernels(id):
210
+ return JSONResponse({"name": "lala", "id": "dsa"})
211
+
212
+
213
+ async def kernel_connection(ws: starlette.websockets.WebSocket):
214
+ _ensure_limiter()
215
+ try:
216
+ with WebsocketDebugInfo.lock:
217
+ WebsocketDebugInfo.attempts += 1
218
+ WebsocketDebugInfo.connecting += 1
219
+ await _kernel_connection(ws)
220
+ finally:
221
+ with WebsocketDebugInfo.lock:
222
+ WebsocketDebugInfo.closed += 1
223
+ WebsocketDebugInfo.open -= 1
224
+
225
+
226
+ async def _kernel_connection(ws: starlette.websockets.WebSocket):
227
+ session_id = ws.cookies.get(server.COOKIE_KEY_SESSION_ID)
228
+
229
+ if settings.oauth.private and not has_auth_support:
230
+ raise RuntimeError("SOLARA_OAUTH_PRIVATE requires solara-enterprise")
231
+ if has_auth_support and "session" in ws.scope:
232
+ user = get_user(ws)
233
+ if user is None and settings.oauth.private:
234
+ await ws.accept()
235
+ logger.error("app is private, requires login")
236
+ await ws.close(code=1008, reason="app is private, requires login")
237
+ return
238
+ else:
239
+ user = None
240
+
241
+ if not session_id:
242
+ logger.warning("no session cookie")
243
+ session_id = "session-id-cookie-unavailable:" + str(uuid4())
244
+ # we use the jupyter session_id query parameter as the key/id
245
+ # for a page scope.
246
+ page_id = ws.query_params["session_id"]
247
+ if not page_id:
248
+ logger.error("no page_id")
249
+ kernel_id = ws.path_params["kernel_id"]
250
+ if not kernel_id:
251
+ logger.error("no kernel_id")
252
+ await ws.close()
253
+ return
254
+ logger.info("Solara kernel requested for session_id=%s kernel_id=%s", session_id, kernel_id)
255
+ await ws.accept()
256
+ with WebsocketDebugInfo.lock:
257
+ WebsocketDebugInfo.connecting -= 1
258
+ WebsocketDebugInfo.open += 1
259
+
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():
276
+ try:
277
+ await run(ws_wrapper)
278
+ except: # noqa
279
+ if portal is not None:
280
+ await portal.stop(cancel_remaining=True)
281
+ raise
282
+ finally:
283
+ telemetry.connection_close(session_id)
284
+
285
+ # sometimes throws: RuntimeError: Already running asyncio in this thread
286
+ anyio.run(run_wrapper) # type: ignore
287
+
288
+ # this portal allows us to sync call the websocket calls from this current event loop we are in
289
+ # each websocket however, is handled from a separate thread
290
+ try:
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)
299
+ finally:
300
+ if settings.main.experimental_performance:
301
+ try:
302
+ ws_wrapper.task.cancel()
303
+ except: # noqa
304
+ logger.exception("error cancelling websocket task")
305
+ try:
306
+ await ws.close()
307
+ except: # noqa
308
+ pass
309
+
310
+
311
+ def close(request: Request):
312
+ kernel_id = request.path_params["kernel_id"]
313
+ page_id = request.query_params["session_id"]
314
+ if kernel_id in kernel_context.contexts:
315
+ context = kernel_context.contexts[kernel_id]
316
+ context.page_close(page_id)
317
+ response = HTMLResponse(content="", status_code=200)
318
+ return response
319
+
320
+
321
+ async def root(request: Request, fullpath: str = ""):
322
+ if settings.oauth.private and not has_auth_support:
323
+ raise RuntimeError("SOLARA_OAUTH_PRIVATE requires solara-enterprise")
324
+ root_path = settings.main.root_path or ""
325
+ if not settings.main.base_url:
326
+ settings.main.base_url = str(request.base_url)
327
+ # if not explicltly set,
328
+ if settings.main.root_path is None:
329
+ # use the default root path from the app, which seems to also include the path
330
+ # if we are mounted under a path
331
+ scope = request.scope
332
+ root_path = scope.get("route_root_path", scope.get("root_path", ""))
333
+ logger.debug("root_path: %s", root_path)
334
+ # or use the script-name header, for instance when running under a reverse proxy
335
+ script_name = request.headers.get("script-name")
336
+ if script_name:
337
+ logger.debug("override root_path using script-name header from %s to %s", root_path, script_name)
338
+ root_path = script_name
339
+ script_name = request.headers.get("x-script-name")
340
+ if script_name:
341
+ logger.debug("override root_path using x-script-name header from %s to %s", root_path, script_name)
342
+ root_path = script_name
343
+ settings.main.root_path = root_path
344
+
345
+ request_path = request.url.path
346
+ if request_path.startswith(root_path):
347
+ request_path = request_path[len(root_path) :]
348
+ if request_path in server._redirects.keys():
349
+ return RedirectResponse(server._redirects[request_path])
350
+
351
+ content = server.read_root(request_path, root_path)
352
+ if content is None:
353
+ if settings.oauth.private and not request.user.is_authenticated:
354
+ raise HTTPException(status_code=401, detail="Unauthorized")
355
+ return HTMLResponse(content="Page not found by Solara router", status_code=404)
356
+
357
+ if settings.oauth.private and not request.user.is_authenticated:
358
+ from solara_enterprise.auth.starlette import login
359
+
360
+ return await login(request)
361
+
362
+ response = HTMLResponse(content=content)
363
+ session_id = request.cookies.get(server.COOKIE_KEY_SESSION_ID) or str(uuid4())
364
+ samesite = "lax"
365
+ secure = False
366
+ # we want samesite, so we can set a cookie when embedded in an iframe, such as on huggingface
367
+ # however, samesite=none requires Secure https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
368
+ # when hosted on the localhost domain we can always set the Secure flag
369
+ # to allow samesite https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies
370
+ if request.headers.get("x-forwarded-proto", "http") == "https" or request.base_url.hostname == "localhost":
371
+ samesite = "none"
372
+ secure = True
373
+ response.set_cookie(
374
+ server.COOKIE_KEY_SESSION_ID,
375
+ value=session_id,
376
+ expires="Fri, 01 Jan 2038 00:00:00 GMT",
377
+ samesite=samesite, # type: ignore
378
+ secure=secure, # type: ignore
379
+ ) # type: ignore
380
+ return response
381
+
382
+
383
+ class StaticFilesOptionalAuth(StaticFiles):
384
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
385
+ conn = HTTPConnection(scope)
386
+ if settings.oauth.private and not has_auth_support:
387
+ raise RuntimeError("SOLARA_OAUTH_PRIVATE requires solara-enterprise")
388
+ if has_auth_support and settings.oauth.private and not conn.user.is_authenticated:
389
+ raise HTTPException(status_code=401, detail="Unauthorized")
390
+ await super().__call__(scope, receive, send)
391
+
392
+
393
+ class StaticNbFiles(StaticFilesOptionalAuth):
394
+ def get_directories(
395
+ self,
396
+ directory: Union[str, "os.PathLike[str]", None] = None,
397
+ packages=None, # type: ignore
398
+ ) -> List[Union[str, "os.PathLike[str]"]]:
399
+ return cast(List[Union[str, "os.PathLike[str]"]], server.nbextensions_directories)
400
+
401
+ # follow symlinks
402
+ # from https://github.com/encode/starlette/pull/1377/files
403
+ def lookup_path(self, path: str) -> typing.Tuple[str, typing.Optional[os.stat_result]]:
404
+ for directory in self.all_directories:
405
+ original_path = os.path.join(directory, path)
406
+ full_path = os.path.realpath(original_path)
407
+ directory = os.path.realpath(directory)
408
+ try:
409
+ return full_path, os.stat(full_path)
410
+ except (FileNotFoundError, NotADirectoryError):
411
+ continue
412
+ return "", None
413
+
414
+
415
+ class StaticPublic(StaticFilesOptionalAuth):
416
+ def lookup_path(self, *args, **kwargs):
417
+ self.all_directories = self.get_directories(None, None)
418
+ return super().lookup_path(*args, **kwargs)
419
+
420
+ def get_directories(
421
+ self,
422
+ directory: Union[str, "os.PathLike[str]", None] = None,
423
+ packages=None, # type: ignore
424
+ ) -> List[Union[str, "os.PathLike[str]"]]:
425
+ # we only know the .directory at runtime (after startup)
426
+ # which means we cannot pass the directory to the StaticFiles constructor
427
+ return cast(List[Union[str, "os.PathLike[str]"]], [app.directory.parent / "public" for app in appmod.apps.values()])
428
+
429
+
430
+ class StaticAssets(StaticFilesOptionalAuth):
431
+ def lookup_path(self, *args, **kwargs):
432
+ self.all_directories = self.get_directories(None, None)
433
+ return super().lookup_path(*args, **kwargs)
434
+
435
+ def get_directories(
436
+ self,
437
+ directory: Union[str, "os.PathLike[str]", None] = None,
438
+ packages=None, # type: ignore
439
+ ) -> List[Union[str, "os.PathLike[str]"]]:
440
+ # we only know the .directory at runtime (after startup)
441
+ # which means we cannot pass the directory to the StaticFiles constructor
442
+ overrides = [app.directory.parent / "assets" for app in appmod.apps.values()]
443
+ default = server.solara_static.parent / "assets"
444
+ return cast(List[Union[str, "os.PathLike[str]"]], [*overrides, default])
445
+
446
+
447
+ class StaticCdn(StaticFilesOptionalAuth):
448
+ def lookup_path(self, path: str) -> typing.Tuple[str, typing.Optional[os.stat_result]]:
449
+ try:
450
+ full_path = str(get_path(settings.assets.proxy_cache_dir, path))
451
+ except Exception:
452
+ return "", None
453
+
454
+ return full_path, os.stat(full_path)
455
+
456
+
457
+ def on_startup():
458
+ # TODO: configure and set max number of threads
459
+ # see https://github.com/encode/starlette/issues/1724
460
+ telemetry.server_start()
461
+
462
+
463
+ def on_shutdown():
464
+ telemetry.server_stop()
465
+
466
+
467
+ def readyz(request: Request):
468
+ json, status = server.readyz()
469
+ return JSONResponse(json, status_code=status)
470
+
471
+
472
+ async def resourcez(request: Request):
473
+ _ensure_limiter()
474
+ assert limiter is not None
475
+ data: Dict[str, Any] = {}
476
+ verbose = request.query_params.get("verbose", None) is not None
477
+ data["websockets"] = {
478
+ "attempts": WebsocketDebugInfo.attempts,
479
+ "connecting": WebsocketDebugInfo.connecting,
480
+ "open": WebsocketDebugInfo.open,
481
+ "closed": WebsocketDebugInfo.closed,
482
+ }
483
+ from . import patch
484
+
485
+ data["threads"] = {
486
+ "created": patch.ThreadDebugInfo.created,
487
+ "running": patch.ThreadDebugInfo.running,
488
+ "stopped": patch.ThreadDebugInfo.stopped,
489
+ "active": threading.active_count(),
490
+ }
491
+ contexts = list(kernel_context.contexts.values())
492
+ data["kernels"] = {
493
+ "total": len(contexts),
494
+ "has_connected": len([k for k in contexts if kernel_context.PageStatus.CONNECTED in k.page_status.values()]),
495
+ "has_disconnected": len([k for k in contexts if kernel_context.PageStatus.DISCONNECTED in k.page_status.values()]),
496
+ "has_closed": len([k for k in contexts if kernel_context.PageStatus.CLOSED in k.page_status.values()]),
497
+ "limiter": {
498
+ "total_tokens": limiter.total_tokens,
499
+ "borrowed_tokens": limiter.borrowed_tokens,
500
+ "available_tokens": limiter.available_tokens,
501
+ },
502
+ }
503
+ default_limiter = anyio.to_thread.current_default_thread_limiter()
504
+ data["anyio.to_thread.limiter"] = {
505
+ "total_tokens": default_limiter.total_tokens,
506
+ "borrowed_tokens": default_limiter.borrowed_tokens,
507
+ "available_tokens": default_limiter.available_tokens,
508
+ }
509
+ if verbose:
510
+ try:
511
+ import psutil
512
+
513
+ def expand(named_tuple):
514
+ return {key: getattr(named_tuple, key) for key in named_tuple._fields}
515
+
516
+ data["cpu"] = {}
517
+ try:
518
+ data["cpu"]["percent"] = psutil.cpu_percent()
519
+ except Exception as e:
520
+ data["cpu"]["percent"] = str(e)
521
+ try:
522
+ data["cpu"]["count"] = psutil.cpu_count()
523
+ except Exception as e:
524
+ data["cpu"]["count"] = str(e)
525
+ try:
526
+ data["cpu"]["times"] = expand(psutil.cpu_times())
527
+ data["cpu"]["times"]["per_cpu"] = [expand(x) for x in psutil.cpu_times(percpu=True)]
528
+ except Exception as e:
529
+ data["cpu"]["times"] = str(e)
530
+ try:
531
+ data["cpu"]["times_percent"] = expand(psutil.cpu_times_percent())
532
+ data["cpu"]["times_percent"]["per_cpu"] = [expand(x) for x in psutil.cpu_times_percent(percpu=True)]
533
+ except Exception as e:
534
+ data["cpu"]["times_percent"] = str(e)
535
+ try:
536
+ memory = psutil.virtual_memory()
537
+ except Exception as e:
538
+ data["memory"] = str(e)
539
+ else:
540
+ data["memory"] = {
541
+ "bytes": expand(memory),
542
+ "GB": {key: getattr(memory, key) / 1024**3 for key in memory._fields},
543
+ }
544
+
545
+ except ModuleNotFoundError:
546
+ pass
547
+
548
+ json_string = json.dumps(data, indent=2)
549
+ return Response(content=json_string, media_type="application/json")
550
+
551
+
552
+ middleware = [
553
+ Middleware(GZipMiddleware, minimum_size=1000),
554
+ ]
555
+
556
+ if has_auth_support:
557
+ middleware = [
558
+ *middleware,
559
+ Middleware(
560
+ MutateDetectSessionMiddleware,
561
+ secret_key=settings.session.secret_key, # type: ignore
562
+ session_cookie="solara-session", # type: ignore
563
+ https_only=settings.session.https_only, # type: ignore
564
+ same_site=settings.session.same_site, # type: ignore
565
+ ),
566
+ Middleware(AuthenticationMiddleware, backend=AuthBackend()),
567
+ ]
568
+
569
+ routes_auth = []
570
+ if has_auth_support:
571
+ routes_auth = [
572
+ Route("/_solara/auth/authorize", endpoint=authorize), #
573
+ Route("/_solara/auth/logout", endpoint=logout),
574
+ Route("/_solara/auth/login", endpoint=login),
575
+ ]
576
+ routes = [
577
+ Route("/readyz", endpoint=readyz),
578
+ Route("/resourcez", endpoint=resourcez),
579
+ *routes_auth,
580
+ Route("/jupyter/api/kernels/{id}", endpoint=kernels),
581
+ WebSocketRoute("/jupyter/api/kernels/{kernel_id}/{name}", endpoint=kernel_connection),
582
+ Route("/", endpoint=root),
583
+ Route("/{fullpath}", endpoint=root),
584
+ Route("/_solara/api/close/{kernel_id}", endpoint=close, methods=["POST"]),
585
+ # only enable when the proxy is turned on, otherwise if the directory does not exists we will get an exception
586
+ *([Mount(f"/{cdn_url_path}", app=StaticCdn(directory=settings.assets.proxy_cache_dir))] if solara.settings.assets.proxy else []),
587
+ Mount(f"{prefix}/static/public", app=StaticPublic()),
588
+ Mount(f"{prefix}/static/assets", app=StaticAssets()),
589
+ Mount(f"{prefix}/static/nbextensions", app=StaticNbFiles()),
590
+ Mount(f"{prefix}/static", app=StaticFilesOptionalAuth(directory=server.solara_static)),
591
+ Route("/{fullpath:path}", endpoint=root),
592
+ ]
593
+
594
+ app = Starlette(routes=routes, on_startup=[on_startup], on_shutdown=[on_shutdown], middleware=middleware)
595
+
596
+ # Uncomment the lines below to test solara mouted under a subpath
597
+ # def myroot(request: Request):
598
+ # return JSONResponse({"framework": "solara"})
599
+
600
+ # routes_test_sub = [Route("/", endpoint=myroot), Mount("/foo/", routes=routes)]
601
+ # app = Starlette(routes=routes_test_sub, on_startup=[on_startup], on_shutdown=[on_shutdown], middleware=middleware)