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,697 @@
1
+ import contextlib
2
+ import inspect
3
+ import json
4
+ import logging
5
+ import os
6
+ import shlex
7
+ import subprocess
8
+ import sys
9
+ import textwrap
10
+ import threading
11
+ import typing
12
+ import urllib.parse
13
+ import uuid
14
+ from io import BytesIO
15
+ from pathlib import Path
16
+ from typing import Any, Callable, Dict, Generator, List, Union
17
+
18
+ import ipywidgets as widgets
19
+ import pytest
20
+ import requests
21
+ from IPython.display import display
22
+
23
+ import solara.server.app
24
+ import solara.server.kernel_context
25
+ import solara.server.server
26
+ import solara.server.settings
27
+ from solara.server import reload
28
+ from solara.server.starlette import ServerStarlette
29
+ from solara.server.threaded import ServerBase
30
+
31
+ if typing.TYPE_CHECKING:
32
+ import playwright.sync_api
33
+
34
+
35
+ logger = logging.getLogger("solara.pytest_plugin")
36
+
37
+ TEST_PORT_START = int(os.environ.get("PORT", "18765")) + 100 # do not interfere with the solara integration tests
38
+ TEST_HOST = solara.server.settings.main.host
39
+ TIMEOUT = float(os.environ.get("SOLARA_PW_TIMEOUT", "18"))
40
+
41
+
42
+ @pytest.fixture(scope="session")
43
+ def solara_server(pytestconfig: Any, request):
44
+ port = pytestconfig.getoption("--solara-port")
45
+ host = pytestconfig.getoption("--solara-host")
46
+ webserver = ServerStarlette(port, host)
47
+
48
+ try:
49
+ webserver.serve_threaded()
50
+ webserver.wait_until_serving()
51
+ yield webserver
52
+ finally:
53
+ webserver.stop_serving()
54
+
55
+
56
+ @pytest.fixture(scope="session")
57
+ def context_session(
58
+ browser: "playwright.sync_api.Browser",
59
+ browser_context_args: Dict,
60
+ pytestconfig: Any,
61
+ request: pytest.FixtureRequest,
62
+ ) -> Generator["playwright.sync_api.BrowserContext", None, None]:
63
+ from playwright.sync_api import Error, Page
64
+ from pytest_playwright.pytest_playwright import _build_artifact_test_folder
65
+ from slugify import slugify # type: ignore
66
+
67
+ pages: List[Page] = []
68
+ context = browser.new_context(**browser_context_args)
69
+ context.on("page", lambda page: pages.append(page))
70
+
71
+ tracing_option = pytestconfig.getoption("--tracing")
72
+ capture_trace = tracing_option in ["on", "retain-on-failure"]
73
+ if capture_trace:
74
+ context.tracing.start(
75
+ title=slugify(request.node.nodeid),
76
+ screenshots=True,
77
+ snapshots=True,
78
+ sources=True,
79
+ )
80
+
81
+ # decrease timeout from 30s to 10s
82
+ context.set_default_timeout(TIMEOUT * 1000)
83
+ yield context
84
+
85
+ # If request.node is missing rep_call, then some error happened during execution
86
+ # that prevented teardown, but should still be counted as a failure
87
+ failed = request.node.rep_call.failed if hasattr(request.node, "rep_call") else True
88
+
89
+ if capture_trace:
90
+ retain_trace = tracing_option == "on" or (failed and tracing_option == "retain-on-failure")
91
+ if retain_trace:
92
+ trace_path = _build_artifact_test_folder(pytestconfig, request, "trace.zip")
93
+ context.tracing.stop(path=trace_path)
94
+ else:
95
+ context.tracing.stop()
96
+
97
+ screenshot_option = pytestconfig.getoption("--screenshot")
98
+ capture_screenshot = screenshot_option == "on" or (failed and screenshot_option == "only-on-failure")
99
+ if capture_screenshot:
100
+ for index, page in enumerate(pages):
101
+ human_readable_status = "failed" if failed else "finished"
102
+ screenshot_path = _build_artifact_test_folder(pytestconfig, request, f"test-{human_readable_status}-{index+1}.png")
103
+ try:
104
+ page.screenshot(timeout=5000, path=screenshot_path)
105
+ except Error:
106
+ pass
107
+
108
+ context.close()
109
+
110
+ video_option = pytestconfig.getoption("--video")
111
+ preserve_video = video_option == "on" or (failed and video_option == "retain-on-failure")
112
+ if preserve_video:
113
+ for page in pages:
114
+ video = page.video
115
+ if not video:
116
+ continue
117
+ try:
118
+ video_path = video.path()
119
+ file_name = os.path.basename(video_path)
120
+ video.save_as(path=_build_artifact_test_folder(pytestconfig, request, file_name))
121
+ except Error:
122
+ # Silent catch empty videos.
123
+ pass
124
+
125
+
126
+ # page fixture that keeps open all the time, is faster
127
+ @pytest.fixture(scope="session")
128
+ def page_session(context_session: "playwright.sync_api.BrowserContext"):
129
+ page = context_session.new_page()
130
+
131
+ def log(msg):
132
+ print("BROWSER LOG:", msg.text) # noqa
133
+ logger.debug("PAGE LOG: %s", msg.text)
134
+
135
+ page.on("console", log)
136
+ yield page
137
+ page.close()
138
+
139
+
140
+ @pytest.fixture()
141
+ def solara_app(solara_server):
142
+ used_app = None
143
+
144
+ @contextlib.contextmanager
145
+ def run(app: Union[solara.server.app.AppScript, str]):
146
+ nonlocal used_app
147
+ if "__default__" in solara.server.app.apps:
148
+ solara.server.app.apps["__default__"].close()
149
+ if isinstance(app, str):
150
+ app = solara.server.app.AppScript(app)
151
+ used_app = app
152
+ solara.server.app.apps["__default__"] = app
153
+ try:
154
+ yield
155
+ finally:
156
+ if app.type == solara.server.app.AppType.MODULE:
157
+ if app.name in sys.modules and app.name.startswith("tests.integration.testapp"):
158
+ del sys.modules[app.name]
159
+ if app.name in reload.reloader.watched_modules:
160
+ reload.reloader.watched_modules.remove(app.name)
161
+
162
+ yield run
163
+ if used_app:
164
+ used_app.close()
165
+
166
+
167
+ run_events: Dict[str, threading.Event] = {}
168
+ used_contexts: Dict[str, solara.server.kernel_context.VirtualKernelContext] = {}
169
+
170
+
171
+ @solara.component
172
+ def SyncWrapper():
173
+ global run_calls
174
+ router = solara.use_router()
175
+ values = urllib.parse.parse_qs(router.search, keep_blank_values=True)
176
+ id = values.get("id", [None])[0] # type: ignore
177
+ if id is None:
178
+ solara.Error("No id found in url")
179
+ else:
180
+ import reacton.ipywidgets as w
181
+
182
+ used_contexts[id] = solara.server.kernel_context.get_current_context()
183
+ run_events[id].set()
184
+
185
+ return w.VBox(children=[w.HTML(value="Test in solara"), w.VBox()])
186
+
187
+
188
+ @contextlib.contextmanager
189
+ def _solara_test(solara_server, solara_app, page_session: "playwright.sync_api.Page", require_vuetify_warmup: bool):
190
+ with solara_app("solara.test.pytest_plugin:SyncWrapper"):
191
+ id = str(uuid.uuid4())
192
+ run_events[id] = run_event = threading.Event()
193
+ page_session.goto(solara_server.base_url + f"?id={id}")
194
+ try:
195
+ assert run_event.wait(10)
196
+ context = used_contexts[id]
197
+ with context:
198
+ test_output_warmup = widgets.Output()
199
+ test_output = widgets.Output()
200
+ try:
201
+ page_session.locator("text=Test in solara").wait_for()
202
+ assert context.container
203
+ context.container.children[0].children[1].children[1].children = [test_output_warmup] # type: ignore
204
+ with test_output_warmup:
205
+ page_session.add_style_tag(
206
+ content="""
207
+ .solara-content-main {
208
+ animation-duration: 0s !important;
209
+ }
210
+ """
211
+ )
212
+ if require_vuetify_warmup:
213
+ warmup()
214
+ button = page_session.locator(".solara-warmup-widget")
215
+ button.wait_for()
216
+ page_session.evaluate("document.fonts.ready")
217
+ button.click()
218
+ button.wait_for(state="detached")
219
+ page_session.evaluate("document.fonts.ready")
220
+ context.container.children[0].children[1].children[1].children = [test_output] # type: ignore
221
+ with test_output:
222
+ yield
223
+ finally:
224
+ test_output.close()
225
+ test_output_warmup.close()
226
+ finally:
227
+ del run_events[id]
228
+ page_session.goto("about:blank")
229
+ if id in used_contexts:
230
+ # handle when run_event.wait(10) fails
231
+ del used_contexts[id]
232
+ assert context.closed_event.wait(10)
233
+
234
+
235
+ @pytest.fixture()
236
+ def solara_test(solara_server, solara_app, page_session: "playwright.sync_api.Page", pytestconfig: Any):
237
+ require_vuetify_warmup = pytestconfig.getoption("solara_vuetify_warmup")
238
+ with _solara_test(solara_server, solara_app, page_session, require_vuetify_warmup):
239
+ yield
240
+
241
+
242
+ class ServerVoila(ServerBase):
243
+ popen = None
244
+
245
+ def __init__(self, notebook_path, port: int, host: str = "localhost", **kwargs):
246
+ self.notebook_path = notebook_path
247
+ super().__init__(port, host)
248
+
249
+ def has_started(self):
250
+ try:
251
+ return requests.get(self.base_url).status_code // 100 in [2, 3]
252
+ except requests.exceptions.ConnectionError:
253
+ return False
254
+
255
+ def signal_stop(self):
256
+ if self.popen is None:
257
+ return
258
+ self.popen.terminate()
259
+ self.popen.kill()
260
+
261
+ def serve(self):
262
+ if self.has_started():
263
+ raise RuntimeError("Jupyter server already running, use lsof -i :{self.port} to find the process and kill it")
264
+ cmd = (
265
+ "voila --no-browser --VoilaTest.log_level=DEBUG --Voila.port_retries=0 --VoilaExecutor.timeout=240"
266
+ f" --Voila.port={self.port} --show_tracebacks=True {self.notebook_path} --enable_nbextensions=True"
267
+ )
268
+ logger.info(f"Starting Voila server at {self.base_url} with command {cmd}")
269
+ args = shlex.split(cmd)
270
+ self.popen = subprocess.Popen(args, shell=False, stdout=sys.stdout, stderr=sys.stderr, stdin=None)
271
+ self.started.set()
272
+
273
+
274
+ class ServerJupyter(ServerBase):
275
+ popen = None
276
+
277
+ def __init__(self, notebook_path, port: int, host: str = "localhost", **kwargs):
278
+ self.notebook_path = notebook_path
279
+ super().__init__(port, host)
280
+
281
+ def has_started(self):
282
+ try:
283
+ return requests.get(self.base_url).status_code // 100 in [2, 3]
284
+ except requests.exceptions.ConnectionError:
285
+ return False
286
+
287
+ def signal_stop(self):
288
+ if self.popen is None:
289
+ return
290
+ self.popen.terminate()
291
+ self.popen.kill()
292
+
293
+ def serve(self):
294
+ if self.has_started():
295
+ raise RuntimeError(f"Jupyter server already running, use lsof -i :{self.port} to find the process and kill it")
296
+ cmd = f'jupyter lab --port={self.port} --no-browser --ServerApp.token="" --port-retries=0 {self.notebook_path}'
297
+ logger.info(f"Starting Jupyter (lab) server at {self.base_url} with command {cmd}")
298
+ args = shlex.split(cmd)
299
+ self.popen = subprocess.Popen(args, shell=False, stdout=sys.stdout, stderr=sys.stderr, stdin=None)
300
+ self.started.set()
301
+
302
+
303
+ @pytest.fixture(scope="session")
304
+ def voila_server(pytestconfig: Any, notebook_path):
305
+ port = pytestconfig.getoption("--voila-port")
306
+ host = pytestconfig.getoption("--solara-host")
307
+ write_notebook(["print('hello')"], notebook_path)
308
+ server = ServerVoila(notebook_path, port, host)
309
+ try:
310
+ server.serve_threaded()
311
+ server.wait_until_serving()
312
+ yield server
313
+ finally:
314
+ server.stop_serving()
315
+
316
+
317
+ @pytest.fixture(scope="session")
318
+ def jupyter_server(pytestconfig: Any, notebook_path):
319
+ port = pytestconfig.getoption("--jupyter-port")
320
+ host = pytestconfig.getoption("--solara-host")
321
+ write_notebook(["print('hello')"], notebook_path)
322
+ server = ServerJupyter(notebook_path, port, host)
323
+ try:
324
+ server.serve_threaded()
325
+ server.wait_until_serving()
326
+ yield server
327
+ finally:
328
+ server.stop_serving()
329
+
330
+
331
+ def code_from_function(f) -> str:
332
+ lines = inspect.getsourcelines(f)[0]
333
+ lines = lines[1:]
334
+ return textwrap.dedent("".join(lines))
335
+
336
+
337
+ def write_notebook(codes: List[str], path: str):
338
+ notebook = {
339
+ "cells": [{"cell_type": "code", "execution_count": None, "id": "df77670d", "metadata": {}, "outputs": [], "source": [code]} for code in codes],
340
+ "metadata": {
341
+ "kernelspec": {"display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3"},
342
+ "language_info": {
343
+ "codemirror_mode": {"name": "ipython", "version": 3},
344
+ "file_extension": ".py",
345
+ "mimetype": "text/x-python",
346
+ "name": "python",
347
+ "nbconvert_exporter": "python",
348
+ "pygments_lexer": "ipython3",
349
+ "version": "3.9.16",
350
+ },
351
+ },
352
+ "nbformat": 4,
353
+ "nbformat_minor": 5,
354
+ }
355
+
356
+ with open(path, "w") as file:
357
+ json.dump(notebook, file)
358
+
359
+
360
+ @pytest.fixture(scope="session")
361
+ def notebook_path(tmp_path_factory):
362
+ path = tmp_path_factory.mktemp("notebooks") / "notebook.ipynb"
363
+ yield str(path)
364
+
365
+
366
+ def warmup():
367
+ import ipyvuetify as v
368
+
369
+ # give it a scope so we do not collide with the user's code
370
+ def scoped():
371
+ button = v.Btn(children=["Warmup js/css/fonts", v.Icon(children=["mdi-check"])], class_="solara-warmup-widget")
372
+ container = v.Container(
373
+ children=[button],
374
+ class_="ma-2 snapshot-container",
375
+ )
376
+
377
+ def remove_button(*ignore):
378
+ container.children = []
379
+ container.style_ = "display: none"
380
+
381
+ button.on_event("click", remove_button)
382
+ return container
383
+
384
+ display(scoped())
385
+
386
+
387
+ def create_runner_voila(voila_server, notebook_path, page_session: "playwright.sync_api.Page", require_vuetify_warmup: bool):
388
+ count = 0
389
+ base_url = voila_server.base_url
390
+
391
+ def run(f: Callable, locals={}):
392
+ nonlocal count
393
+ path = Path(f.__code__.co_filename)
394
+ cwd = str(path.parent)
395
+ code_setup = f"""
396
+ import os
397
+ os.chdir({cwd!r})
398
+ \n"""
399
+ for name, value in locals.items():
400
+ code_setup += f"{name} = {value!r}\n"
401
+ if require_vuetify_warmup:
402
+ write_notebook([code_setup, code_from_function(warmup), code_from_function(f)], notebook_path)
403
+ else:
404
+ write_notebook([code_setup, code_from_function(f)], notebook_path)
405
+ page_session.goto(base_url + f"?v={count}")
406
+ if require_vuetify_warmup:
407
+ button = page_session.locator(".solara-warmup-widget")
408
+ button.wait_for()
409
+ page_session.evaluate("document.fonts.ready")
410
+ button.click()
411
+ button.wait_for(state="detached")
412
+ page_session.evaluate("document.fonts.ready")
413
+ count += 1
414
+
415
+ return run
416
+
417
+
418
+ def create_runner_jupyter_lab(jupyter_server, notebook_path, page_session: "playwright.sync_api.Page", require_vuetify_warmup: bool):
419
+ count = 0
420
+ base_url = jupyter_server.base_url
421
+
422
+ def run(f: Callable, locals={}):
423
+ nonlocal count
424
+ path = Path(f.__code__.co_filename)
425
+ cwd = str(path.parent)
426
+ code_setup = f"""
427
+ import os
428
+ os.chdir({cwd!r})
429
+ \n"""
430
+ if require_vuetify_warmup:
431
+ code_setup += """
432
+ import ipyvuetify as v;
433
+ display(v.Btn(children=['Warmup js/css/fonts', v.Icon(children=["mdi-check"])], class_="solara-warmup-widget"))
434
+ \n"""
435
+ for name, value in locals.items():
436
+ code_setup += f"{name} = {value!r}\n"
437
+
438
+ write_notebook([code_setup, code_from_function(f)], notebook_path)
439
+ page_session.goto(base_url + f"/lab/workspaces/solara-test/tree/notebook.ipynb?reset&v={count}")
440
+ page_session.locator('css=[data-command="runmenu:run"]').wait_for()
441
+ # close the file browser tab, it does not give a consistent width each time
442
+ # which leads to fractional pixel x, which causes the screenshot to be different
443
+ # by 1 pixel
444
+ # first make sure the page is loaded
445
+ page_session.locator('css=[data-id="filebrowser"]').wait_for()
446
+ page_session.locator('css=[data-command="filebrowser:create-main-launcher"]').wait_for()
447
+ # close
448
+ page_session.locator('css=[data-id="filebrowser"]').click()
449
+ # make sure it is closed
450
+ page_session.locator('css=[data-command="filebrowser:create-main-launcher"]').wait_for(state="hidden")
451
+
452
+ page_session.locator('button:has-text("No Kernel")').wait_for(state="detached")
453
+ page_session.locator('css=[data-status="idle"]').wait_for()
454
+ page_session.locator('css=[data-command="runmenu:run"]').click()
455
+ if require_vuetify_warmup:
456
+ page_session.locator(".solara-warmup-widget").wait_for()
457
+ page_session.evaluate("document.fonts.ready")
458
+ page_session.locator('css=[data-command="runmenu:run"]').click()
459
+ count += 1
460
+
461
+ return run
462
+
463
+
464
+ def create_runner_jupyter_notebook(jupyter_server, notebook_path, page_session: "playwright.sync_api.Page", require_vuetify_warmup: bool):
465
+ count = 0
466
+ base_url = jupyter_server.base_url
467
+
468
+ def run(f: Callable, locals={}):
469
+ nonlocal count
470
+ path = Path(f.__code__.co_filename)
471
+ cwd = str(path.parent)
472
+ code_setup = f"""
473
+ import os
474
+ os.chdir({cwd!r})
475
+ """
476
+ if require_vuetify_warmup:
477
+ code_setup += """
478
+ import ipyvuetify as v;
479
+ display(v.Btn(children=['Warmup js/css/fonts', v.Icon(children=["mdi-check"])], class_="solara-warmup-widget"))
480
+ \n"""
481
+ for name, value in locals.items():
482
+ code_setup += f"{name} = {value!r}\n"
483
+ write_notebook([code_setup, code_from_function(f)], notebook_path)
484
+ page_session.goto(base_url + f"/notebooks/notebook.ipynb?v={count}")
485
+ page_session.locator(".prompt_container >> nth=0").wait_for()
486
+ page_session.locator("text=Kernel starting, please wait...").wait_for(state="detached")
487
+ page_session.locator("Kernel Ready").wait_for(state="detached")
488
+ page_session.locator('css=[data-jupyter-action="jupyter-notebook:run-cell-and-select-next"]').click()
489
+ if require_vuetify_warmup:
490
+ page_session.locator(".solara-warmup-widget").wait_for()
491
+ page_session.evaluate("document.fonts.ready")
492
+ page_session.locator('css=[data-jupyter-action="jupyter-notebook:run-cell-and-select-next"]').click()
493
+ count += 1
494
+
495
+ return run
496
+
497
+
498
+ @contextlib.contextmanager
499
+ def create_runner_solara(solara_server, solara_app, page_session: "playwright.sync_api.Page", require_vuetify_warmup: bool):
500
+ count = 0
501
+
502
+ def run(f: Callable, locals={}):
503
+ nonlocal count
504
+ path = Path(f.__code__.co_filename)
505
+ cwd = str(path.parent)
506
+ current_dir = os.getcwd()
507
+ os.chdir(cwd)
508
+ import sys
509
+
510
+ sys.path.append(cwd)
511
+ try:
512
+ f()
513
+ finally:
514
+ os.chdir(current_dir)
515
+ sys.path.remove(cwd)
516
+ count += 1
517
+
518
+ with _solara_test(solara_server, solara_app, page_session, require_vuetify_warmup):
519
+ yield run
520
+
521
+
522
+ runners = os.environ.get("SOLARA_TEST_RUNNERS", "solara,voila,jupyter_lab,jupyter_notebook").split(",")
523
+
524
+
525
+ @pytest.fixture(params=runners)
526
+ def ipywidgets_runner(
527
+ solara_server,
528
+ solara_app,
529
+ voila_server,
530
+ jupyter_server,
531
+ notebook_path,
532
+ page_session: "playwright.sync_api.Page",
533
+ request,
534
+ pytestconfig: Any,
535
+ ):
536
+ runner = request.param
537
+ require_vuetify_warmup = pytestconfig.getoption("solara_vuetify_warmup")
538
+ if runner == "solara":
539
+ with create_runner_solara(solara_server, solara_app, page_session, require_vuetify_warmup) as runner:
540
+ yield runner
541
+ elif runner == "voila":
542
+ yield create_runner_voila(voila_server, notebook_path, page_session, require_vuetify_warmup)
543
+ elif runner == "jupyter_lab":
544
+ yield create_runner_jupyter_lab(jupyter_server, notebook_path, page_session, require_vuetify_warmup)
545
+ elif runner == "jupyter_notebook":
546
+ yield create_runner_jupyter_notebook(jupyter_server, notebook_path, page_session, require_vuetify_warmup)
547
+ else:
548
+ raise RuntimeError(f"Unknown runner {runner}")
549
+
550
+
551
+ @pytest.fixture(scope="session")
552
+ def solara_snapshots_directory(request: Any) -> Path:
553
+ path = Path(request.config.rootpath) / "tests" / "ui" / "snapshots"
554
+ if not path.exists():
555
+ path.mkdir(exist_ok=True, parents=True)
556
+ return path
557
+
558
+
559
+ def compare_default(reference, result, threshold=0.1):
560
+ from PIL import Image
561
+ from pixelmatch.contrib.PIL import pixelmatch
562
+
563
+ difference = Image.new("RGB", reference.size)
564
+ diff = pixelmatch(reference, result, difference, threshold=threshold)
565
+ return diff, difference
566
+
567
+
568
+ @pytest.fixture
569
+ def assert_solara_snapshot(pytestconfig: Any, request: Any, browser_name: str, solara_snapshots_directory) -> Callable:
570
+ from PIL import Image
571
+
572
+ testname = f"{str(Path(request.node.name))}".replace("[", "-").replace("]", "").replace(" ", "-").replace(",", "-")
573
+ directory = solara_snapshots_directory / request.node.location[0]
574
+ output_dir = Path(pytestconfig.getoption("--output")) / request.node.location[0]
575
+ if not directory.exists():
576
+ directory.mkdir(exist_ok=True, parents=True)
577
+ if not output_dir.exists():
578
+ output_dir.mkdir(exist_ok=True, parents=True)
579
+
580
+ def assert_implementation(
581
+ image: bytes,
582
+ compare: Callable = compare_default,
583
+ testname=testname,
584
+ format="{prefix}{testname}-{platform}{postfix}-{type}.png",
585
+ prefix="",
586
+ postfix="",
587
+ ):
588
+ update_snapshot = pytestconfig.getoption("--solara-update-snapshots")
589
+ format_kwargs = dict(testname=testname, platform=sys.platform, browser=browser_name, prefix=prefix, postfix=postfix)
590
+ path_reference = directory / format.format(**format_kwargs, type="reference").format(**format_kwargs)
591
+ path_reference_output = output_dir / format.format(**format_kwargs, type="reference").format(**format_kwargs)
592
+ path_previous = output_dir / format.format(**format_kwargs, type="previous").format(**format_kwargs)
593
+ path_diff = output_dir / format.format(**format_kwargs, type="diff").format(**format_kwargs)
594
+ if not path_reference.exists():
595
+ if update_snapshot:
596
+ path_reference.write_bytes(image)
597
+ else:
598
+ # CI run, store the reference, but fail
599
+ path_reference_output.write_bytes(image)
600
+ raise AssertionError(
601
+ f'Snapshot {path_reference} did not exist, file written. Run `cp "{path_reference_output}" "{path_reference}"` '
602
+ "Commit this file and rerun the CI. Or run with --solara-update-snapshots to update it."
603
+ )
604
+ else:
605
+ if update_snapshot:
606
+ path_reference.write_bytes(image)
607
+ else:
608
+ reference = Image.open(path_reference)
609
+ result = Image.open(BytesIO(image))
610
+ difference = None
611
+
612
+ def write():
613
+ if update_snapshot:
614
+ path_reference.write_bytes(image)
615
+ else:
616
+ # CI run, update the reference in the output dir, and store the previous run next to it
617
+ path_reference_output.write_bytes(image)
618
+ reference.save(path_previous)
619
+ if difference is not None:
620
+ difference.save(path_diff)
621
+
622
+ # the error msg of the default compare is not very helpful
623
+ if reference.size != result.size:
624
+ write()
625
+ raise AssertionError(
626
+ f"Snapshot {path_reference} has a different size than the result {reference.size} != {result.size}."
627
+ f'Run `cp "{path_reference_output}" "{path_reference}"` Commit this file and rerun the CI. '
628
+ "Or run with --solara-update-snapshots to update it."
629
+ )
630
+ diff, difference = compare(reference, result)
631
+ if diff > 0:
632
+ write()
633
+ raise AssertionError(
634
+ f'Snapshot {path_reference} does not match, Run `cp "{path_reference_output}" "{path_reference}"` Commit this file and rerun the CI. '
635
+ "Or run with --solara-update-snapshots to update it."
636
+ )
637
+
638
+ return assert_implementation
639
+
640
+
641
+ def pytest_addoption(parser: Any) -> None:
642
+ group = parser.getgroup("solara", "Solara")
643
+ group.addoption(
644
+ "--solara-update-snapshots",
645
+ action="store_true",
646
+ default=False,
647
+ help="Do not compare, but store the snapshots.",
648
+ )
649
+ group.addoption(
650
+ "--solara-update-snapshots-ci",
651
+ action="store_true",
652
+ default=False,
653
+ help="On compare failure, store to the reference image. Useful for running in CI and downloading the snapshots.",
654
+ )
655
+ group.addoption(
656
+ "--solara-host",
657
+ default=TEST_HOST,
658
+ help="Host or IP all servers will bind to (solara, jupyter, voila)",
659
+ )
660
+ group.addoption(
661
+ "--solara-port",
662
+ type=int,
663
+ default=TEST_PORT_START + 0,
664
+ help="Port the solara server is running on for the test",
665
+ )
666
+ group.addoption(
667
+ "--jupyter-port",
668
+ type=int,
669
+ default=TEST_PORT_START + 1,
670
+ help="Port the jupyter server is running on for the test (for classic notebook and juptyer lab)",
671
+ )
672
+ group.addoption(
673
+ "--voila-port",
674
+ type=int,
675
+ default=TEST_PORT_START + 2,
676
+ help="Port the voila server is running on for the test (for classic notebook and juptyer lab)",
677
+ )
678
+ vuetify_warmup = os.environ.get("SOLARA_TEST_VUETIFY_WARMUP", "true") in ["true", "True", "1", "on", "On"]
679
+ help = (
680
+ "Load/not load the vuetify fonts and css before running the test, leading to more stable screenshots. If (ipy)vuetify is not used this can be disabled."
681
+ )
682
+ if vuetify_warmup:
683
+ group.addoption(
684
+ "--no-solara-vuetify-warmup",
685
+ action="store_false",
686
+ default=vuetify_warmup,
687
+ help=help,
688
+ dest="solara_vuetify_warmup",
689
+ )
690
+ else:
691
+ group.addoption(
692
+ "--solara-vuetify-warmup",
693
+ action="store_false",
694
+ default=vuetify_warmup,
695
+ help=help,
696
+ dest="solara_vuetify_warmup",
697
+ )