writer 0.8.3rc4__py3-none-any.whl → 1.25.1rc1__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 (378) hide show
  1. writer/__init__.py +1 -1
  2. writer/abstract.py +1 -1
  3. writer/{ai.py → ai/__init__.py} +867 -163
  4. writer/app_runner.py +596 -241
  5. writer/app_templates/default/.wf/components-blueprints_blueprint-0-0decp3w5erhvl0nw.jsonl +11 -0
  6. writer/app_templates/default/.wf/components-blueprints_root.jsonl +1 -0
  7. writer/app_templates/default/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +27 -0
  8. writer/app_templates/default/.wf/components-root.jsonl +1 -0
  9. writer/app_templates/default/.wf/components-workflows_root.jsonl +1 -0
  10. writer/app_templates/default/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl +1 -0
  11. writer/app_templates/default/.wf/metadata.json +3 -0
  12. writer/app_templates/default/README.md +3 -0
  13. writer/app_templates/default/main.py +16 -0
  14. writer/app_templates/default/requirements.txt +1 -0
  15. writer/app_templates/default/static/README.md +8 -0
  16. writer/app_templates/default/static/agent_builder_demo.png +0 -0
  17. writer/app_templates/default/static/favicon.png +0 -0
  18. writer/app_templates/hello/.wf/components-blueprints_blueprint-0-t84xyhxau9ej3823.jsonl +18 -0
  19. writer/app_templates/hello/.wf/components-blueprints_root.jsonl +1 -0
  20. writer/app_templates/hello/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +15 -0
  21. writer/app_templates/hello/.wf/components-root.jsonl +1 -0
  22. writer/app_templates/hello/.wf/metadata.json +3 -0
  23. writer/app_templates/hello/main.py +16 -0
  24. writer/app_templates/hello/static/README.md +8 -0
  25. writer/app_templates/hello/static/favicon.png +0 -0
  26. writer/app_templates/hello/static/welcome.svg +40 -0
  27. writer/auth.py +7 -2
  28. writer/autogen.py +352 -0
  29. writer/blocks/__init__.py +51 -17
  30. writer/blocks/addtostatelist.py +10 -9
  31. writer/blocks/apitrigger.py +45 -0
  32. writer/blocks/base_block.py +332 -21
  33. writer/blocks/base_trigger.py +14 -0
  34. writer/blocks/calleventhandler.py +39 -35
  35. writer/blocks/changepage.py +48 -0
  36. writer/blocks/code.py +102 -0
  37. writer/blocks/crontrigger.py +49 -0
  38. writer/blocks/foreach.py +70 -53
  39. writer/blocks/httprequest.py +112 -99
  40. writer/blocks/ifelse.py +71 -0
  41. writer/blocks/logmessage.py +34 -39
  42. writer/blocks/parsejson.py +30 -29
  43. writer/blocks/returnvalue.py +7 -7
  44. writer/blocks/runblueprint.py +63 -0
  45. writer/blocks/setstate.py +43 -33
  46. writer/blocks/sharedblueprint.py +86 -0
  47. writer/blocks/uieventtrigger.py +49 -0
  48. writer/blocks/writeraddchatmessage.py +50 -12
  49. writer/blocks/writeraddtokg.py +38 -11
  50. writer/blocks/writeraskkg.py +123 -0
  51. writer/blocks/writerchat.py +80 -61
  52. writer/blocks/writerchatreply.py +279 -0
  53. writer/blocks/writerchatreplywithtoolconfig.py +393 -0
  54. writer/blocks/writerclassification.py +78 -39
  55. writer/blocks/writercompletion.py +49 -44
  56. writer/blocks/writerfileapi.py +85 -0
  57. writer/blocks/writerinitchat.py +24 -12
  58. writer/blocks/writerkeyvaluestorage.py +106 -0
  59. writer/blocks/writernocodeapp.py +35 -37
  60. writer/blocks/writerparsepdf.py +73 -0
  61. writer/blocks/writerstructuredoutput.py +105 -0
  62. writer/blocks/writertoolcalling.py +251 -0
  63. writer/blocks/writervision.py +141 -0
  64. writer/blocks/writerwebsearch.py +175 -0
  65. writer/blueprints.py +839 -0
  66. writer/command_line.py +52 -16
  67. writer/core.py +562 -290
  68. writer/core_ui.py +6 -2
  69. writer/evaluator.py +98 -46
  70. writer/journal.py +227 -0
  71. writer/keyvalue_storage.py +93 -0
  72. writer/logs.py +277 -0
  73. writer/serve.py +625 -327
  74. writer/ss_types.py +101 -12
  75. writer/static/assets/Arrow.dom-GBJpMYQS.js +1 -0
  76. writer/static/assets/BaseMarkdown-Wrvby5J8.js +1 -0
  77. writer/static/assets/BlueprintToolbar-BuXNRxWT.js +1 -0
  78. writer/static/assets/BlueprintToolbar-wpfX0jo_.css +1 -0
  79. writer/static/assets/BuilderApp-PTOI76jZ.js +8 -0
  80. writer/static/assets/BuilderApp-WimUfNZr.css +1 -0
  81. writer/static/assets/BuilderApplicationSelect-DXzy4e_h.js +7 -0
  82. writer/static/assets/BuilderApplicationSelect-XaM1D5fv.css +1 -0
  83. writer/static/assets/BuilderBlueprintLibraryPanel-Ckrhknlh.css +1 -0
  84. writer/static/assets/BuilderBlueprintLibraryPanel-DBDzhTlc.js +1 -0
  85. writer/static/assets/BuilderEmbeddedCodeEditor-B0bcjlhk.css +1 -0
  86. writer/static/assets/BuilderEmbeddedCodeEditor-Dn7eDICN.js +726 -0
  87. writer/static/assets/BuilderGraphSelect-C-LRsO8W.js +7 -0
  88. writer/static/assets/BuilderGraphSelect-D7B61d5s.css +1 -0
  89. writer/static/assets/BuilderInsertionLabel-BhyL9wgn.js +1 -0
  90. writer/static/assets/BuilderInsertionLabel-_YS5WPfq.css +1 -0
  91. writer/static/assets/BuilderInsertionOverlay-D2XS0ij9.css +1 -0
  92. writer/static/assets/BuilderInsertionOverlay-MkAIVruY.js +1 -0
  93. writer/static/assets/BuilderJournal-A0LcEwGI.js +7 -0
  94. writer/static/assets/BuilderJournal-DHv3Pvvm.css +1 -0
  95. writer/static/assets/BuilderModelSelect-CdSo_sih.js +7 -0
  96. writer/static/assets/BuilderModelSelect-Dc4IPLp2.css +1 -0
  97. writer/static/assets/BuilderSettings-BDwZBveu.js +16 -0
  98. writer/static/assets/BuilderSettings-lZkOXEYw.css +1 -0
  99. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-3O6jKBXD.js +4 -0
  100. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-DnX66iRg.css +1 -0
  101. writer/static/assets/BuilderSettingsDeploySharedBlueprint-BR_3ptsd.js +1 -0
  102. writer/static/assets/BuilderSettingsDeploySharedBlueprint-KJTl8gxP.css +1 -0
  103. writer/static/assets/BuilderSettingsHandlers-CBtEQFSo.js +1 -0
  104. writer/static/assets/BuilderSettingsHandlers-DJPeASfz.css +1 -0
  105. writer/static/assets/BuilderSidebarComponentTree-DLltgas5.js +1 -0
  106. writer/static/assets/BuilderSidebarComponentTree-DYu1F793.css +1 -0
  107. writer/static/assets/BuilderSidebarToolkit-CApZNTAq.js +7 -0
  108. writer/static/assets/BuilderSidebarToolkit-CwqbjRv8.css +1 -0
  109. writer/static/assets/BuilderTemplateEditor-CYSDeWgV.css +1 -0
  110. writer/static/assets/BuilderTemplateEditor-DnRDRcA0.js +87 -0
  111. writer/static/assets/BuilderVault-2vGoV0sx.js +1 -0
  112. writer/static/assets/BuilderVault-Cx6oQSES.css +1 -0
  113. writer/static/assets/ComponentRenderer-72hqvEvI.css +1 -0
  114. writer/static/assets/ComponentRenderer-D4Pj1i3s.js +1 -0
  115. writer/static/assets/SharedCopyClipboardButton-BipJKGtz.css +1 -0
  116. writer/static/assets/SharedCopyClipboardButton-DNI9kLe6.js +1 -0
  117. writer/static/assets/WdsCheckbox-DKvpPA4D.css +1 -0
  118. writer/static/assets/WdsCheckbox-edQcn1cf.js +1 -0
  119. writer/static/assets/WdsDropdownMenu-CzzPN9Wg.css +1 -0
  120. writer/static/assets/WdsDropdownMenu-DQnrRBNV.js +1 -0
  121. writer/static/assets/WdsFieldWrapper-Cmufx5Nj.js +1 -0
  122. writer/static/assets/WdsFieldWrapper-CsemOh8D.css +1 -0
  123. writer/static/assets/WdsTabs-DKj7BqI0.css +1 -0
  124. writer/static/assets/WdsTabs-DcfY_zn5.js +1 -0
  125. writer/static/assets/abap-D8nrxEjS.js +6 -0
  126. writer/static/assets/apex-BrXDlLUW.js +6 -0
  127. writer/static/assets/art-paper-D70v1WMA.svg +180 -0
  128. writer/static/assets/azcli-CElzELwZ.js +6 -0
  129. writer/static/assets/bat-CUsyEhik.js +6 -0
  130. writer/static/assets/bicep-BtxyJn6H.js +7 -0
  131. writer/static/assets/cameligo-ClBCoF8h.js +6 -0
  132. writer/static/assets/clojure-B9TqLHAk.js +6 -0
  133. writer/static/assets/codicon-BA2IlpFX.ttf +0 -0
  134. writer/static/assets/coffee-DYsfeylR.js +6 -0
  135. writer/static/assets/cpp-VVGvvgir.js +6 -0
  136. writer/static/assets/csharp-Z6z2stHy.js +6 -0
  137. writer/static/assets/csp-DgZoLDI1.js +6 -0
  138. writer/static/assets/css-KqQ96-gC.js +8 -0
  139. writer/static/assets/css.worker-DvNUQFd1.js +84 -0
  140. writer/static/assets/cssMode-BYq4oZGq.js +9 -0
  141. writer/static/assets/cypher-CYoSlgTu.js +6 -0
  142. writer/static/assets/dart-BGDl7St1.js +6 -0
  143. writer/static/assets/dockerfile-CuCtxA7T.js +6 -0
  144. writer/static/assets/ecl-BCTFAUpS.js +6 -0
  145. writer/static/assets/editor.worker-BVwmgLrR.js +11 -0
  146. writer/static/assets/elixir-C7hRTYZ9.js +6 -0
  147. writer/static/assets/flow9-Bi_qi707.js +6 -0
  148. writer/static/assets/freemarker2-CnNourkO.js +8 -0
  149. writer/static/assets/fsharp-CxaaEKKi.js +6 -0
  150. writer/static/assets/go-DUImKuGY.js +6 -0
  151. writer/static/assets/graphql-D5sGVkLV.js +6 -0
  152. writer/static/assets/handlebars-Bm22yapJ.js +6 -0
  153. writer/static/assets/hcl-zD_CCkZ1.js +6 -0
  154. writer/static/assets/html-CAKAfoZF.js +6 -0
  155. writer/static/assets/html.worker-BJMlcbMU.js +458 -0
  156. writer/static/assets/htmlMode-BGZ97n-V.js +9 -0
  157. writer/static/assets/index-5u5REPT4.js +16 -0
  158. writer/static/assets/index-BKNuk68o.css +1 -0
  159. writer/static/assets/index-BQNXU3IR.js +17 -0
  160. writer/static/assets/index-BQr1pfrb.js +1 -0
  161. writer/static/assets/index-DHXAd5Yn.js +4 -0
  162. writer/static/assets/index-Zki-pfO-.js +8525 -0
  163. writer/static/assets/index.esm-B1ZQtduY.js +17 -0
  164. writer/static/assets/ini-8kKHd4ZL.js +6 -0
  165. writer/static/assets/java-De1axCfe.js +6 -0
  166. writer/static/assets/javascript-X1f02eyK.js +6 -0
  167. writer/static/assets/json.worker-BwvX8PuZ.js +42 -0
  168. writer/static/assets/jsonMode-hT0bNgT8.js +11 -0
  169. writer/static/assets/julia-D3ApGBxz.js +6 -0
  170. writer/static/assets/kotlin-GbSrCElU.js +6 -0
  171. writer/static/assets/less-DNUaDNdz.js +7 -0
  172. writer/static/assets/lexon-Bg9QKxBu.js +6 -0
  173. writer/static/assets/liquid-KmCCiJw2.js +6 -0
  174. writer/static/assets/lua-Crkvc3mc.js +6 -0
  175. writer/static/assets/m3-DsrzVyM1.js +6 -0
  176. writer/static/assets/mapbox-gl-C0cyFYYW.js +2329 -0
  177. writer/static/assets/markdown-CY5IOZuu.js +6 -0
  178. writer/static/assets/marked.esm-273vDTCT.js +45 -0
  179. writer/static/assets/mdx-DtRFauUw.js +6 -0
  180. writer/static/assets/mips-BE8RsGBA.js +6 -0
  181. writer/static/assets/msdax-N5ajIiFQ.js +6 -0
  182. writer/static/assets/mysql-DRxbB97D.js +6 -0
  183. writer/static/assets/objective-c-BHUZy23s.js +6 -0
  184. writer/static/assets/pascal-BemVzBTY.js +6 -0
  185. writer/static/assets/pascaligo-BACCcnx_.js +6 -0
  186. writer/static/assets/pdf-B6-yWJ-Y.js +12 -0
  187. writer/static/assets/pdf.worker.min-CyUfim15.mjs +21 -0
  188. writer/static/assets/perl-CuU66Ptk.js +6 -0
  189. writer/static/assets/pgsql-CQ6TMH2r.js +6 -0
  190. writer/static/assets/php-BvyzZa65.js +6 -0
  191. writer/static/assets/pla-DrIuu9u1.js +6 -0
  192. writer/static/assets/plotly.min-DutuuatZ.js +4030 -0
  193. writer/static/assets/poppins-latin-300-italic-4WBEAciR.woff +0 -0
  194. writer/static/assets/poppins-latin-300-italic-EWCPeN2Y.woff2 +0 -0
  195. writer/static/assets/poppins-latin-300-normal-DCNuMXUj.woff +0 -0
  196. writer/static/assets/poppins-latin-300-normal-Dku2WoCh.woff2 +0 -0
  197. writer/static/assets/poppins-latin-400-italic-B4GYq972.woff2 +0 -0
  198. writer/static/assets/poppins-latin-400-italic-BPejoDS-.woff +0 -0
  199. writer/static/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  200. writer/static/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  201. writer/static/assets/poppins-latin-500-italic-Ce_qjtl5.woff +0 -0
  202. writer/static/assets/poppins-latin-500-italic-o28Otv0U.woff2 +0 -0
  203. writer/static/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  204. writer/static/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  205. writer/static/assets/poppins-latin-600-italic-BhOZippK.woff +0 -0
  206. writer/static/assets/poppins-latin-600-italic-CZ4wqKBi.woff2 +0 -0
  207. writer/static/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  208. writer/static/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  209. writer/static/assets/poppins-latin-700-italic-CW91C-LJ.woff +0 -0
  210. writer/static/assets/poppins-latin-700-italic-RKf6esGj.woff2 +0 -0
  211. writer/static/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  212. writer/static/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  213. writer/static/assets/poppins-latin-ext-300-italic-CBzyU4Pf.woff +0 -0
  214. writer/static/assets/poppins-latin-ext-300-italic-DdDvTq5-.woff2 +0 -0
  215. writer/static/assets/poppins-latin-ext-300-normal-7Zg2msWE.woff2 +0 -0
  216. writer/static/assets/poppins-latin-ext-300-normal-C9p7gvmA.woff +0 -0
  217. writer/static/assets/poppins-latin-ext-400-italic-BiCGV3eO.woff2 +0 -0
  218. writer/static/assets/poppins-latin-ext-400-italic-gsPYOGqV.woff +0 -0
  219. writer/static/assets/poppins-latin-ext-400-normal-CIpeJEZw.woff2 +0 -0
  220. writer/static/assets/poppins-latin-ext-400-normal-Ce_uWq1Z.woff +0 -0
  221. writer/static/assets/poppins-latin-ext-500-italic-CwrTHwbn.woff2 +0 -0
  222. writer/static/assets/poppins-latin-ext-500-italic-jdc8Bv4M.woff +0 -0
  223. writer/static/assets/poppins-latin-ext-500-normal-Bl1-S02S.woff +0 -0
  224. writer/static/assets/poppins-latin-ext-500-normal-H4Q0z8D2.woff2 +0 -0
  225. writer/static/assets/poppins-latin-ext-600-italic-BqeDa496.woff2 +0 -0
  226. writer/static/assets/poppins-latin-ext-600-italic-C7MQPb_A.woff +0 -0
  227. writer/static/assets/poppins-latin-ext-600-normal-Cn4C8475.woff2 +0 -0
  228. writer/static/assets/poppins-latin-ext-600-normal-DB6FJURc.woff +0 -0
  229. writer/static/assets/poppins-latin-ext-700-italic-BAdhB_WS.woff2 +0 -0
  230. writer/static/assets/poppins-latin-ext-700-italic-WKTwQMp8.woff +0 -0
  231. writer/static/assets/poppins-latin-ext-700-normal-CE2WFKmF.woff +0 -0
  232. writer/static/assets/poppins-latin-ext-700-normal-DDaViAzG.woff2 +0 -0
  233. writer/static/assets/postiats-BR_hrfni.js +6 -0
  234. writer/static/assets/powerquery-CKDUeRmd.js +6 -0
  235. writer/static/assets/powershell-Dsa4rhA_.js +6 -0
  236. writer/static/assets/protobuf-CGsvhooB.js +7 -0
  237. writer/static/assets/pug-D2p3uOX2.js +6 -0
  238. writer/static/assets/python-DVhxg746.js +6 -0
  239. writer/static/assets/qsharp-B7F3HtPF.js +6 -0
  240. writer/static/assets/r-3aLoi2fs.js +6 -0
  241. writer/static/assets/razor-DR5Ns_BC.js +6 -0
  242. writer/static/assets/redis-jqFeRM5s.js +6 -0
  243. writer/static/assets/redshift-BriwQgXR.js +6 -0
  244. writer/static/assets/restructuredtext-hbBFZ0w9.js +6 -0
  245. writer/static/assets/ruby-ByThyB2Q.js +6 -0
  246. writer/static/assets/rust-DIEZMp5R.js +6 -0
  247. writer/static/assets/sb-C6Gjjw_x.js +6 -0
  248. writer/static/assets/scala-DZNw3jJB.js +6 -0
  249. writer/static/assets/scheme-55eqh71t.js +6 -0
  250. writer/static/assets/scss-D-OVkc4F.js +8 -0
  251. writer/static/assets/serialization-DJC7NP0N.js +20 -0
  252. writer/static/assets/shell-DSpi8_qN.js +6 -0
  253. writer/static/assets/solidity-BHddiNFS.js +6 -0
  254. writer/static/assets/sophia-D6taVZFb.js +6 -0
  255. writer/static/assets/sparql-LA0C7mUc.js +6 -0
  256. writer/static/assets/sql-C3-3IcFM.js +6 -0
  257. writer/static/assets/st-C4g7059C.js +6 -0
  258. writer/static/assets/swift-DNI1vH3h.js +8 -0
  259. writer/static/assets/systemverilog-DL_FVbcQ.js +6 -0
  260. writer/static/assets/tcl-DVJXmIwd.js +6 -0
  261. writer/static/assets/ts.worker-CwG1rUES.js +37021 -0
  262. writer/static/assets/tsMode-BNUEZzir.js +16 -0
  263. writer/static/assets/twig-BVWDLtw5.js +6 -0
  264. writer/static/assets/typescript-CRVt7Hx0.js +6 -0
  265. writer/static/assets/useBlueprintRun-C00bCxh-.js +1 -0
  266. writer/static/assets/useKeyValueEditor-nDmI7cTJ.js +1 -0
  267. writer/static/assets/useListResources-DLkZhRSJ.js +1 -0
  268. writer/static/assets/vb-Btz91-7U.js +6 -0
  269. writer/static/assets/vega-embed.module-SNP5iNdJ.js +201 -0
  270. writer/static/assets/wgsl-D8V_buCG.js +303 -0
  271. writer/static/assets/xml-C_6-t1tb.js +6 -0
  272. writer/static/assets/yaml-DIw8G7jk.js +6 -0
  273. writer/static/components/annotatedtext.svg +4 -0
  274. writer/static/components/avatar.svg +4 -0
  275. writer/static/components/blueprints_addtostatelist.svg +4 -0
  276. writer/static/components/blueprints_apitrigger.svg +4 -0
  277. writer/static/components/blueprints_calleventhandler.svg +9 -0
  278. writer/static/components/blueprints_category_Logic.svg +4 -0
  279. writer/static/components/blueprints_category_Other.svg +4 -0
  280. writer/static/components/blueprints_category_Triggers.svg +4 -0
  281. writer/static/components/blueprints_category_Writer.svg +25 -0
  282. writer/static/components/blueprints_code.svg +9 -0
  283. writer/static/components/blueprints_crontrigger.svg +6 -0
  284. writer/static/components/blueprints_foreach.svg +4 -0
  285. writer/static/components/blueprints_httprequest.svg +11 -0
  286. writer/static/components/blueprints_logmessage.svg +11 -0
  287. writer/static/components/blueprints_parsejson.svg +4 -0
  288. writer/static/components/blueprints_returnvalue.svg +4 -0
  289. writer/static/components/blueprints_runblueprint.svg +4 -0
  290. writer/static/components/blueprints_setstate.svg +4 -0
  291. writer/static/components/blueprints_uieventtrigger.svg +4 -0
  292. writer/static/components/blueprints_writeraddchatmessage.svg +19 -0
  293. writer/static/components/blueprints_writeraddtokg.svg +19 -0
  294. writer/static/components/blueprints_writerchat.svg +11 -0
  295. writer/static/components/blueprints_writerchatreply.svg +19 -0
  296. writer/static/components/blueprints_writerclassification.svg +24 -0
  297. writer/static/components/blueprints_writercompletion.svg +14 -0
  298. writer/static/components/blueprints_writerinitchat.svg +11 -0
  299. writer/static/components/blueprints_writernocodeapp.svg +14 -0
  300. writer/static/components/button.svg +4 -0
  301. writer/static/components/category_Content.svg +4 -0
  302. writer/static/components/category_Embed.svg +4 -0
  303. writer/static/components/category_Input.svg +5 -0
  304. writer/static/components/category_Layout.svg +9 -0
  305. writer/static/components/category_Other.svg +4 -0
  306. writer/static/components/chatbot.svg +4 -0
  307. writer/static/components/checkboxinput.svg +4 -0
  308. writer/static/components/colorinput.svg +11 -0
  309. writer/static/components/column.svg +4 -0
  310. writer/static/components/columns.svg +4 -0
  311. writer/static/components/dataframe.svg +4 -0
  312. writer/static/components/dateinput.svg +4 -0
  313. writer/static/components/dropdowninput.svg +5 -0
  314. writer/static/components/fileinput.svg +4 -0
  315. writer/static/components/googlemaps.svg +4 -0
  316. writer/static/components/header.svg +4 -0
  317. writer/static/components/heading.svg +9 -0
  318. writer/static/components/horizontalstack.svg +4 -0
  319. writer/static/components/html.svg +9 -0
  320. writer/static/components/icon.svg +4 -0
  321. writer/static/components/iframe.svg +4 -0
  322. writer/static/components/image.svg +11 -0
  323. writer/static/components/jsonviewer.svg +4 -0
  324. writer/static/components/link.svg +12 -0
  325. writer/static/components/mapbox.svg +4 -0
  326. writer/static/components/message.svg +4 -0
  327. writer/static/components/metric.svg +4 -0
  328. writer/static/components/multiselectinput.svg +4 -0
  329. writer/static/components/numberinput.svg +4 -0
  330. writer/static/components/page.svg +50 -0
  331. writer/static/components/pagination.svg +4 -0
  332. writer/static/components/pdf.svg +4 -0
  333. writer/static/components/plotlygraph.svg +7 -0
  334. writer/static/components/progressbar.svg +5 -0
  335. writer/static/components/radioinput.svg +4 -0
  336. writer/static/components/rangeinput.svg +4 -0
  337. writer/static/components/ratinginput.svg +4 -0
  338. writer/static/components/repeater.svg +4 -0
  339. writer/static/components/reuse.svg +4 -0
  340. writer/static/components/section.svg +4 -0
  341. writer/static/components/selectinput.svg +5 -0
  342. writer/static/components/separator.svg +4 -0
  343. writer/static/components/sidebar.svg +4 -0
  344. writer/static/components/sliderinput.svg +4 -0
  345. writer/static/components/step.svg +4 -0
  346. writer/static/components/steps.svg +4 -0
  347. writer/static/components/switchinput.svg +4 -0
  348. writer/static/components/tab.svg +4 -0
  349. writer/static/components/tabs.svg +4 -0
  350. writer/static/components/tags.svg +11 -0
  351. writer/static/components/text.svg +4 -0
  352. writer/static/components/textareainput.svg +11 -0
  353. writer/static/components/textinput.svg +4 -0
  354. writer/static/components/timeinput.svg +4 -0
  355. writer/static/components/timer.svg +4 -0
  356. writer/static/components/vegalitechart.svg +7 -0
  357. writer/static/components/videoplayer.svg +11 -0
  358. writer/static/components/webcamcapture.svg +4 -0
  359. writer/static/favicon.png +0 -0
  360. writer/static/index.html +84 -0
  361. writer/static/status/cancelled.svg +5 -0
  362. writer/static/status/error.svg +5 -0
  363. writer/static/status/skipped.svg +4 -0
  364. writer/static/status/stopped.svg +4 -0
  365. writer/static/status/success.svg +4 -0
  366. writer/sync.py +431 -0
  367. writer/ui.py +2268 -0
  368. writer/vault.py +48 -0
  369. writer/wf_project.py +90 -66
  370. writer-1.25.1rc1.dist-info/METADATA +92 -0
  371. writer-1.25.1rc1.dist-info/RECORD +382 -0
  372. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/WHEEL +1 -1
  373. writer/blocks/runworkflow.py +0 -59
  374. writer/workflows.py +0 -183
  375. writer-0.8.3rc4.dist-info/METADATA +0 -117
  376. writer-0.8.3rc4.dist-info/RECORD +0 -44
  377. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/entry_points.txt +0 -0
  378. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info/licenses}/LICENSE.txt +0 -0
writer/core.py CHANGED
@@ -46,31 +46,38 @@ import writer.blocks
46
46
  import writer.evaluator
47
47
  from writer import core_ui
48
48
  from writer.core_ui import Component
49
+ from writer.logs import use_stdout_redirect
49
50
  from writer.ss_types import (
51
+ BlueprintExecutionError,
52
+ BlueprintExecutionLog,
50
53
  InstancePath,
51
54
  Readable,
52
55
  ServeMode,
53
- WorkflowExecutionLog,
54
56
  WriterEvent,
55
57
  WriterEventResult,
56
58
  WriterFileItem,
57
59
  )
60
+ from writer.vault import writer_vault
58
61
 
59
62
  if TYPE_CHECKING:
60
63
  import pandas
61
64
 
62
65
  from writer.app_runner import AppProcess
66
+ from writer.blueprints import BlueprintRunner
63
67
  from writer.ss_types import AppProcessServerRequest
64
68
 
69
+
65
70
  @dataclasses.dataclass
66
71
  class CurrentRequest:
67
72
  session_id: str
68
- request: 'AppProcessServerRequest'
73
+ request: "AppProcessServerRequest"
74
+
69
75
 
70
76
  _current_request: ContextVar[Optional[CurrentRequest]] = ContextVar("current_request", default=None)
71
77
 
78
+
72
79
  @contextlib.contextmanager
73
- def use_request_context(session_id: str, request: 'AppProcessServerRequest'):
80
+ def use_request_context(session_id: str, request: "AppProcessServerRequest"):
74
81
  """
75
82
  Context manager to set the current request context.
76
83
 
@@ -85,7 +92,8 @@ def use_request_context(session_id: str, request: 'AppProcessServerRequest'):
85
92
  finally:
86
93
  _current_request.set(None)
87
94
 
88
- def get_app_process() -> 'AppProcess':
95
+
96
+ def get_app_process() -> "AppProcess":
89
97
  """
90
98
  Retrieves the Writer Framework process context.
91
99
 
@@ -93,11 +101,12 @@ def get_app_process() -> 'AppProcess':
93
101
  >>> _current_process.bmc_components # get the component tree
94
102
  """
95
103
  from writer.app_runner import AppProcess # Needed during runtime
104
+
96
105
  raw_process: BaseProcess = multiprocessing.current_process()
97
106
  if isinstance(raw_process, AppProcess):
98
107
  return raw_process
99
108
 
100
- raise RuntimeError( "Failed to retrieve the AppProcess: running in wrong context")
109
+ raise RuntimeError("Failed to retrieve the AppProcess: running in wrong context")
101
110
 
102
111
 
103
112
  def import_failure(rvalue: Any = None):
@@ -113,6 +122,7 @@ def import_failure(rvalue: Any = None):
113
122
 
114
123
  :param rvalue: the value to return
115
124
  """
125
+
116
126
  def decorator(func):
117
127
  @functools.wraps(func)
118
128
  def wrapper(*args, **kwargs):
@@ -120,12 +130,13 @@ def import_failure(rvalue: Any = None):
120
130
  return func(*args, **kwargs)
121
131
  except ImportError:
122
132
  return rvalue
133
+
123
134
  return wrapper
135
+
124
136
  return decorator
125
137
 
126
138
 
127
139
  class Config:
128
-
129
140
  is_mail_enabled_for_log: bool = False
130
141
  mode: ServeMode = "run"
131
142
  logger: Optional[logging.Logger] = None
@@ -133,12 +144,13 @@ class Config:
133
144
 
134
145
 
135
146
  class WriterSession:
136
-
137
147
  """
138
148
  Represents a session.
139
149
  """
140
150
 
141
- def __init__(self, session_id: str, cookies: Optional[Dict[str, str]], headers: Optional[Dict[str, str]]) -> None:
151
+ def __init__(
152
+ self, session_id: str, cookies: Optional[Dict[str, str]], headers: Optional[Dict[str, str]]
153
+ ) -> None:
142
154
  self.session_id = session_id
143
155
  self.cookies = cookies
144
156
  self.headers = headers
@@ -149,6 +161,7 @@ class WriterSession:
149
161
  self.session_component_tree = core_ui.build_session_component_tree(base_component_tree)
150
162
  self.event_handler = EventHandler(self)
151
163
  self.userinfo: Optional[dict] = None
164
+ self.queued_messages: List[Any] = []
152
165
 
153
166
  def update_last_active_timestamp(self) -> None:
154
167
  self.last_active_timestamp = int(time.time())
@@ -167,16 +180,17 @@ class MutationSubscription:
167
180
 
168
181
  >>> m = MutationSubscription(path="a.c", handler=myhandler)
169
182
  """
170
- type: Literal['subscription', 'property']
183
+
184
+ type: Literal["subscription", "property"]
171
185
  path: str
172
- handler: Callable # Handler to execute when mutation happens
173
- state: 'State'
186
+ handler: Callable # Handler to execute when mutation happens
187
+ state: "State"
174
188
  property_name: Optional[str] = None
175
189
 
176
190
  def __post_init__(self):
177
191
  if len(self.path) == 0:
178
192
  raise ValueError("path cannot be empty.")
179
-
193
+
180
194
  path_parts = parse_state_variable_expression(self.path)
181
195
  for part in path_parts:
182
196
  if len(part) == 0:
@@ -194,14 +208,17 @@ class MutationSubscription:
194
208
  path_parts = parse_state_variable_expression(self.path)
195
209
  return path_parts[-1]
196
210
 
197
- class StateRecursionWatcher():
211
+
212
+ class StateRecursionWatcher:
198
213
  limit = 128
199
214
 
200
215
  def __init__(self):
201
216
  self.counter_recursion = 0
202
217
 
218
+
203
219
  _state_recursion_watcher = ContextVar("state_recursion_watcher", default=StateRecursionWatcher())
204
220
 
221
+
205
222
  @contextlib.contextmanager
206
223
  def state_recursion_new(key: str):
207
224
  """
@@ -219,6 +236,7 @@ def state_recursion_new(key: str):
219
236
  finally:
220
237
  recursion_watcher.counter_recursion -= 1
221
238
 
239
+
222
240
  class FileWrapper:
223
241
  """
224
242
  A wrapper for either a string pointing to a file or a file-like object with a read() method.
@@ -229,17 +247,18 @@ class FileWrapper:
229
247
  def __init__(self, file: Union[Readable, str], mime_type: Optional[str] = None):
230
248
  if not file:
231
249
  raise ValueError("Must specify a file.")
232
- if not (
233
- callable(getattr(file, "read", None)) or
234
- isinstance(file, str)):
250
+ if not (callable(getattr(file, "read", None)) or isinstance(file, str)):
235
251
  raise ValueError(
236
- "File must provide a read() method or contain a string with a path to a local file.")
252
+ "File must provide a read() method or contain a string with a path to a local file."
253
+ )
237
254
  self.file = file
238
255
  self.mime_type = mime_type
239
256
 
240
257
  def _get_file_stream_as_dataurl(self, f_stream: Readable) -> str:
241
258
  base64_str = base64.b64encode(f_stream.read()).decode("latin-1")
242
- dataurl = f"data:{self.mime_type if self.mime_type is not None else ''};base64,{ base64_str }"
259
+ dataurl = (
260
+ f"data:{self.mime_type if self.mime_type is not None else ''};base64,{ base64_str }"
261
+ )
243
262
  return dataurl
244
263
 
245
264
  def get_as_dataurl(self) -> str:
@@ -253,7 +272,6 @@ class FileWrapper:
253
272
 
254
273
 
255
274
  class BytesWrapper:
256
-
257
275
  """
258
276
  A wrapper for raw byte data.
259
277
  Provides a method for retrieving the data as data URL.
@@ -279,6 +297,7 @@ class StateSerialiser:
279
297
  Serialises user state values before sending them to the front end.
280
298
  Provides JSON-compatible values, including data URLs for binary data.
281
299
  """
300
+
282
301
  def serialise(self, v: Any) -> Union[Dict, List, str, bool, int, float, None]:
283
302
  from writer.ai import Conversation
284
303
  from writer.core_df import EditableDataFrame
@@ -308,8 +327,7 @@ class StateSerialiser:
308
327
  # Checking the MRO allows to determine object type without creating dependencies
309
328
  # to these packages
310
329
 
311
- v_mro = [
312
- f"{x.__module__}.{x.__name__}" for x in inspect.getmro(type(v))]
330
+ v_mro = [f"{x.__module__}.{x.__name__}" for x in inspect.getmro(type(v))]
313
331
 
314
332
  if isinstance(v, (int, float)):
315
333
  if "numpy.float64" in v_mro:
@@ -339,7 +357,8 @@ class StateSerialiser:
339
357
  return self._serialise_dict_recursively(v.to_dict())
340
358
 
341
359
  raise StateSerialiserException(
342
- f"Object of type { type(v) } (MRO: {v_mro}) cannot be serialised.")
360
+ f"Object of type { type(v) } (MRO: {v_mro}) cannot be serialised."
361
+ )
343
362
 
344
363
  def _serialise_dict_recursively(self, d: Dict) -> Dict:
345
364
  return {str(k): self.serialise(v) for k, v in d.items()}
@@ -371,11 +390,13 @@ class StateSerialiser:
371
390
  :return: a arrow file as a dataurl (application/vnd.apache.arrow.file)
372
391
  """
373
392
  import pyarrow.interchange # type: ignore
393
+
374
394
  table = pyarrow.interchange.from_dataframe(df)
375
395
  return self._serialise_pyarrow_table(table)
376
396
 
377
397
  def _serialise_pandas_dataframe(self, df):
378
398
  import pyarrow as pa # type: ignore
399
+
379
400
  pa_table = pa.Table.from_pandas(df, preserve_index=True)
380
401
  return self._serialise_pyarrow_table(pa_table)
381
402
 
@@ -391,6 +412,7 @@ class StateSerialiser:
391
412
  bw = BytesWrapper(buf, "application/vnd.apache.arrow.file")
392
413
  return self.serialise(bw)
393
414
 
415
+
394
416
  class MutableValue:
395
417
  """
396
418
  MutableValue allows you to implement a value whose modification
@@ -405,6 +427,7 @@ class MutableValue:
405
427
  >>> self.value = new_value
406
428
  >>> self.mutate()
407
429
  """
430
+
408
431
  def __init__(self):
409
432
  self._mutated = False
410
433
 
@@ -430,8 +453,8 @@ class MutableValue:
430
453
  """
431
454
  self._mutated = False
432
455
 
433
- class StateProxy:
434
456
 
457
+ class StateProxy:
435
458
  """
436
459
  The root user state and its children (nested states) are instances of this class.
437
460
  Provides proxy functionality to detect state mutations via assignment.
@@ -466,33 +489,31 @@ class StateProxy:
466
489
  def __setitem__(self, key: str, raw_value: Any) -> None:
467
490
  with state_recursion_new(key):
468
491
  if not isinstance(key, str):
469
- raise ValueError(
470
- f"State keys must be strings. Received {str(key)} ({type(key)}).")
492
+ raise ValueError(f"State keys must be strings. Received {str(key)} ({type(key)}).")
471
493
  old_value = self.state.get(key)
472
494
  self.state[key] = raw_value
473
495
 
474
496
  for local_mutation in self.local_mutation_subscriptions:
475
497
  if local_mutation.local_path == key:
476
- if local_mutation.type == 'subscription':
477
- context_data = {
478
- "event": "mutation",
479
- "mutation": local_mutation.path
480
- }
481
- payload = {
482
- "previous_value": old_value,
483
- "new_value": raw_value
484
- }
485
-
486
- EventHandlerExecutor.invoke(local_mutation.handler, {
487
- "state": local_mutation.state,
488
- "context": context_data,
489
- "payload": payload,
490
- "session": _event_handler_session_info(),
491
- "ui": _event_handler_ui_manager()
492
- })
493
- elif local_mutation.type == 'property':
498
+ if local_mutation.type == "subscription":
499
+ context_data = {"event": "mutation", "mutation": local_mutation.path}
500
+ payload = {"previous_value": old_value, "new_value": raw_value}
501
+
502
+ EventHandlerExecutor.invoke(
503
+ local_mutation.handler,
504
+ {
505
+ "state": local_mutation.state,
506
+ "context": context_data,
507
+ "payload": payload,
508
+ "session": _event_handler_session_info(),
509
+ "ui": _event_handler_ui_manager(),
510
+ },
511
+ )
512
+ elif local_mutation.type == "property":
494
513
  assert local_mutation.property_name is not None
495
- self[local_mutation.property_name] = local_mutation.handler(local_mutation.state)
514
+ self[local_mutation.property_name] = local_mutation.handler(
515
+ local_mutation.state
516
+ )
496
517
 
497
518
  self._apply_raw(f"+{key}")
498
519
 
@@ -544,7 +565,7 @@ class StateProxy:
544
565
  for key, value in list(self.state.items()):
545
566
  if key.startswith("_"):
546
567
  continue
547
-
568
+
548
569
  escaped_key = self.escape_key(key)
549
570
  serialised_value = None
550
571
 
@@ -562,20 +583,21 @@ class StateProxy:
562
583
  try:
563
584
  serialised_value = state_serialiser.serialise(value)
564
585
  except BaseException:
565
- raise ValueError(f"""Couldn't serialise value of type "{ type(value) }" for key "{ key }".""")
586
+ raise ValueError(
587
+ f"""Couldn't serialise value of type "{ type(value) }" for key "{ key }"."""
588
+ )
566
589
  serialised_mutations[f"+{escaped_key}"] = serialised_value
567
590
  elif isinstance(value, MutableValue) is True and value.mutated():
568
591
  try:
569
592
  serialised_value = state_serialiser.serialise(value)
570
593
  value.reset_mutation()
571
594
  except BaseException:
572
- raise ValueError(f"""Couldn't serialise value of type "{ type(value) }" for key "{ key }".""")
595
+ raise ValueError(
596
+ f"""Couldn't serialise value of type "{ type(value) }" for key "{ key }"."""
597
+ )
573
598
  serialised_mutations[f"+{escaped_key}"] = serialised_value
574
599
 
575
- deleted_keys = \
576
- {self.escape_key(key)
577
- for key in self.mutated
578
- if key.startswith("-")}
600
+ deleted_keys = {self.escape_key(key) for key in self.mutated if key.startswith("-")}
579
601
  for key in deleted_keys:
580
602
  serialised_mutations[f"{key}"] = None
581
603
 
@@ -592,7 +614,8 @@ class StateProxy:
592
614
  serialised_value = state_serialiser.serialise(value)
593
615
  except BaseException:
594
616
  raise ValueError(
595
- f"""Couldn't serialise value of type "{ type(value) }" for key "{ key }".""")
617
+ f"""Couldn't serialise value of type "{ type(value) }" for key "{ key }"."""
618
+ )
596
619
  serialised[key] = serialised_value
597
620
  return serialised
598
621
 
@@ -620,14 +643,15 @@ def get_annotations(instance) -> Dict[str, Any]:
620
643
  Returns the annotations of the class in a way that works on python 3.9 and python 3.10
621
644
  """
622
645
  if isinstance(instance, type):
623
- ann = instance.__dict__.get('__annotations__', None)
646
+ ann = instance.__dict__.get("__annotations__", None)
624
647
  else:
625
- ann = getattr(instance, '__annotations__', None)
648
+ ann = getattr(instance, "__annotations__", None)
626
649
 
627
650
  if ann is None:
628
651
  ann = {}
629
652
  return ann
630
653
 
654
+
631
655
  class StateMeta(type):
632
656
  """
633
657
  Constructs a class at runtime that extends WriterState or State
@@ -672,15 +696,16 @@ class StateMeta(type):
672
696
  annotations = get_annotations(klass)
673
697
  for key, expected_type in annotations.items():
674
698
  if key == "_state_proxy":
675
- raise AttributeError("_state_proxy is an reserved keyword for Writer Framework, don't use it in annotation.")
699
+ raise AttributeError(
700
+ "_state_proxy is an reserved keyword for Writer Framework, don't use it in annotation."
701
+ )
676
702
 
677
- if not(inspect.isclass(expected_type) and issubclass(expected_type, State)):
703
+ if not (inspect.isclass(expected_type) and issubclass(expected_type, State)):
678
704
  proxy = DictPropertyProxy("_state_proxy", key)
679
705
  setattr(klass, key, proxy)
680
706
 
681
707
 
682
708
  class State(metaclass=StateMeta):
683
-
684
709
  def __init__(self, raw_state: Optional[Dict[str, Any]] = None):
685
710
  final_raw_state = raw_state if raw_state is not None else {}
686
711
 
@@ -703,7 +728,9 @@ class State(metaclass=StateMeta):
703
728
  """
704
729
  self._state_proxy.state = {}
705
730
  for key, value in raw_state.items():
706
- assert not isinstance(value, StateProxy), f"state proxy datatype is not expected in ingest operation, {locals()}"
731
+ assert not isinstance(
732
+ value, StateProxy
733
+ ), f"state proxy datatype is not expected in ingest operation, {locals()}"
707
734
  self._set_state_item(key, value)
708
735
 
709
736
  def to_dict(self) -> dict:
@@ -717,7 +744,6 @@ class State(metaclass=StateMeta):
717
744
  """
718
745
  return self._state_proxy.to_dict()
719
746
 
720
-
721
747
  def to_raw_state(self) -> dict:
722
748
  """
723
749
  Converts a StateProxy and its children into a python dictionary that can be used to recreate the
@@ -735,7 +761,6 @@ class State(metaclass=StateMeta):
735
761
  return self._state_proxy.__repr__()
736
762
 
737
763
  def __getitem__(self, key: str) -> Any:
738
-
739
764
  # Essential to support operation like
740
765
  # state['item']['a'] = state['item']['b']
741
766
  if hasattr(self, key):
@@ -746,7 +771,9 @@ class State(metaclass=StateMeta):
746
771
  return self._state_proxy.__getitem__(key)
747
772
 
748
773
  def __setitem__(self, key: str, raw_value: Any) -> None:
749
- assert not isinstance(raw_value, StateProxy), f"state proxy datatype is not expected, {locals()}"
774
+ assert not isinstance(
775
+ raw_value, StateProxy
776
+ ), f"state proxy datatype is not expected, {locals()}"
750
777
 
751
778
  self._set_state_item(key, raw_value)
752
779
 
@@ -769,15 +796,14 @@ class State(metaclass=StateMeta):
769
796
  return self._state_proxy.__contains__(key)
770
797
 
771
798
  def _set_state_item(self, key: str, value: Any):
772
- """
773
- """
799
+ """ """
774
800
 
775
801
  """
776
802
  At this level, the values that arrive are either States which encapsulate a StateProxy, or another datatype.
777
803
  If there is a StateProxy, it is a fault in the code.
778
804
  """
779
805
  annotations = get_annotations(self)
780
- expected_type = annotations.get(key, None)
806
+ expected_type = cast(Type, annotations.get(key, None))
781
807
  expect_dict = _type_match_dict(expected_type)
782
808
  if isinstance(value, dict) and not expect_dict:
783
809
  """
@@ -786,7 +812,9 @@ class State(metaclass=StateMeta):
786
812
  """
787
813
  state = annotations[key](value) if key in annotations else State()
788
814
  if not isinstance(state, State):
789
- raise ValueError(f"Attribute {key} must inherit of State or requires a dict to accept dictionary")
815
+ raise ValueError(
816
+ f"Attribute {key} must inherit of State or requires a dict to accept dictionary"
817
+ )
790
818
 
791
819
  setattr(self, key, state)
792
820
  state.ingest(value)
@@ -798,11 +826,12 @@ class State(metaclass=StateMeta):
798
826
  else:
799
827
  self._state_proxy[key] = value
800
828
 
801
-
802
- def subscribe_mutation(self,
803
- path: Union[str, List[str]],
804
- handler: Callable[..., Union[None, Awaitable[None]]],
805
- initial_triggered: bool = False) -> None:
829
+ def subscribe_mutation(
830
+ self,
831
+ path: Union[str, List[str]],
832
+ handler: Callable[..., Union[None, Awaitable[None]]],
833
+ initial_triggered: bool = False,
834
+ ) -> None:
806
835
  r"""
807
836
  Automatically triggers a handler when a mutation occurs in the state.
808
837
 
@@ -847,30 +876,35 @@ class State(metaclass=StateMeta):
847
876
  path_parts = parse_state_variable_expression(p)
848
877
  for i, path_part in enumerate(path_parts):
849
878
  if i == len(path_parts) - 1:
850
- local_mutation = MutationSubscription('subscription', p, handler, self)
879
+ local_mutation = MutationSubscription("subscription", p, handler, self)
851
880
  state_proxy.local_mutation_subscriptions.append(local_mutation)
852
881
 
853
882
  # At startup, the application must be informed of the
854
883
  # existing states. To cause this, we trigger manually
855
884
  # the handler.
856
885
  if initial_triggered is True:
857
- EventHandlerExecutor.invoke(handler, {
858
- "state": self,
859
- "context": {"event": "init"},
860
- "payload": {},
861
- "session": {},
862
- "ui": _event_handler_ui_manager()
863
- })
886
+ EventHandlerExecutor.invoke(
887
+ handler,
888
+ {
889
+ "state": self,
890
+ "context": {"event": "init"},
891
+ "payload": {},
892
+ "session": {},
893
+ "ui": _event_handler_ui_manager(),
894
+ },
895
+ )
864
896
 
865
897
  elif path_part in state_proxy:
866
898
  state_proxy = state_proxy[path_part]
867
899
  else:
868
900
  raise ValueError(f"Mutation subscription failed - {p} not found in state")
869
901
 
870
- def calculated_property(self,
871
- property_name: str,
872
- path: Union[str, List[str]],
873
- handler: Callable[..., Union[None, Awaitable[None]]]) -> None:
902
+ def calculated_property(
903
+ self,
904
+ property_name: str,
905
+ path: Union[str, List[str]],
906
+ handler: Callable[..., Union[None, Awaitable[None]]],
907
+ ) -> None:
874
908
  """
875
909
  Update a calculated property when a mutation triggers
876
910
 
@@ -900,7 +934,9 @@ class State(metaclass=StateMeta):
900
934
  path_parts = parse_state_variable_expression(p)
901
935
  for i, path_part in enumerate(path_parts):
902
936
  if i == len(path_parts) - 1:
903
- local_mutation = MutationSubscription('property', p, handler, self, property_name)
937
+ local_mutation = MutationSubscription(
938
+ "property", p, handler, self, property_name
939
+ )
904
940
  state_proxy.local_mutation_subscriptions.append(local_mutation)
905
941
  state_proxy[property_name] = handler(self)
906
942
  elif path_part in state_proxy:
@@ -927,11 +963,11 @@ class WriterState(State):
927
963
 
928
964
  @classmethod
929
965
  def get_new(cls):
930
- """ Returns a new WriterState instance set to the initial state."""
966
+ """Returns a new WriterState instance set to the initial state."""
931
967
 
932
968
  return initial_state.get_clone()
933
969
 
934
- def get_clone(self) -> 'WriterState':
970
+ def get_clone(self) -> "WriterState":
935
971
  """
936
972
  get_clone clones the destination application state for the session.
937
973
 
@@ -949,10 +985,12 @@ class WriterState(State):
949
985
  cloned_mail = copy.deepcopy(self.mail)
950
986
  except BaseException:
951
987
  substitute_state = WriterState()
952
- substitute_state.add_log_entry("error",
953
- "Cannot clone state",
954
- "The state may contain unpickable objects, such as modules.",
955
- traceback.format_exc())
988
+ substitute_state.add_log_entry(
989
+ "error",
990
+ "Cannot clone state",
991
+ "The state may contain unpickable objects, such as modules.",
992
+ traceback.format_exc(),
993
+ )
956
994
  return substitute_state
957
995
 
958
996
  cloned_state = self.__class__(cloned_user_state, cloned_mail)
@@ -960,20 +998,29 @@ class WriterState(State):
960
998
  return cloned_state
961
999
 
962
1000
  def add_mail(self, type: str, payload: Any) -> None:
963
- mail_item = {
964
- "type": type,
965
- "payload": payload
966
- }
967
- self.mail.insert(0, mail_item)
968
-
969
- def add_notification(self, type: Literal["info", "success", "warning", "error"], title: str, message: str) -> None:
970
- self.add_mail("notification", {
971
- "type": type,
972
- "title": title,
973
- "message": message,
974
- })
1001
+ mail_item = {"type": type, "payload": payload}
1002
+ self.mail.append(mail_item)
1003
+
1004
+ def add_notification(
1005
+ self, type: Literal["info", "success", "warning", "error"], title: str, message: str
1006
+ ) -> None:
1007
+ self.add_mail(
1008
+ "notification",
1009
+ {
1010
+ "type": type,
1011
+ "title": title,
1012
+ "message": message,
1013
+ },
1014
+ )
975
1015
 
976
- def _log_entry_in_logger(self, type: Literal["debug", "info", "warning", "error", "critical"], title: str, message: str, code: Optional[str] = None) -> None:
1016
+ def _log_entry_in_logger(
1017
+ self,
1018
+ type: Literal["debug", "info", "warning", "error", "critical"],
1019
+ title: str,
1020
+ message: str,
1021
+ code: Optional[str] = None,
1022
+ blueprint_execution: Optional[BlueprintExecutionLog] = None,
1023
+ ) -> None:
977
1024
  if not Config.logger:
978
1025
  return
979
1026
  log_args: Tuple[str, ...] = ()
@@ -984,11 +1031,11 @@ class WriterState(State):
984
1031
  log_args = (title, message)
985
1032
 
986
1033
  log_colors = {
987
- "debug": "\x1b[36;20m", # Cyan for debug
988
- "info": "\x1b[34;20m", # Blue for info
1034
+ "debug": "\x1b[36;20m", # Cyan for debug
1035
+ "info": "\x1b[34;20m", # Blue for info
989
1036
  "warning": "\x1b[33;20m", # Yellow for warning
990
- "error": "\x1b[31;20m", # Red for error
991
- "critical": "\x1b[35;20m" # Magenta for critical
1037
+ "error": "\x1b[31;20m", # Red for error
1038
+ "critical": "\x1b[35;20m", # Magenta for critical
992
1039
  }
993
1040
 
994
1041
  log_methods = {
@@ -996,41 +1043,69 @@ class WriterState(State):
996
1043
  "info": Config.logger.info,
997
1044
  "warning": Config.logger.warning,
998
1045
  "error": Config.logger.error,
999
- "critical": Config.logger.critical
1046
+ "critical": Config.logger.critical,
1000
1047
  }
1001
1048
 
1002
1049
  log_message = "From app log: " + ("\n%s" * len(log_args))
1003
1050
 
1051
+ if blueprint_execution:
1052
+ log_message += "\n"
1053
+ for entry in blueprint_execution.summary:
1054
+ outcome = entry.get("outcome")
1055
+ if outcome is None:
1056
+ continue
1057
+ component_id = entry.get("componentId")
1058
+ entry_message = entry.get("message")
1059
+ log_message += f"- Id: {component_id} | Outcome: {outcome} | {entry_message}\n"
1060
+
1004
1061
  color = log_colors.get(type, "\x1b[0m") # Default to no color if type not found
1005
- log_method = log_methods.get(type, Config.logger.info) # Default to info level if type not found
1062
+ log_method = log_methods.get(
1063
+ type, Config.logger.info
1064
+ ) # Default to info level if type not found
1006
1065
 
1007
1066
  log_method(f"{color}{log_message}\x1b[0m", *log_args)
1008
1067
 
1009
- def add_log_entry(self, type: Literal["info", "error"], title: str, message: str, code: Optional[str] = None, workflow_execution: Optional[WorkflowExecutionLog] = None) -> None:
1010
- self._log_entry_in_logger(type, title, message, code)
1068
+ def add_log_entry(
1069
+ self,
1070
+ type: Literal["info", "error"],
1071
+ title: str,
1072
+ message: str,
1073
+ code: Optional[str] = None,
1074
+ blueprint_execution: Optional[BlueprintExecutionLog] = None,
1075
+ id: Optional[str] = None,
1076
+ ) -> None:
1077
+ self._log_entry_in_logger(type, title, message, code, blueprint_execution)
1011
1078
  if not Config.is_mail_enabled_for_log:
1012
1079
  return
1013
1080
  shortened_message = None
1014
1081
  if len(message) > WriterState.LOG_ENTRY_MAX_LEN:
1015
- shortened_message = message[0:WriterState.LOG_ENTRY_MAX_LEN] + "..."
1082
+ shortened_message = message[0 : WriterState.LOG_ENTRY_MAX_LEN] + "..."
1016
1083
  else:
1017
1084
  shortened_message = message
1018
- self.add_mail("logEntry", {
1019
- "type": type,
1020
- "title": title,
1021
- "message": shortened_message,
1022
- "code": code,
1023
- "workflowExecution": workflow_execution
1024
- })
1085
+
1086
+ if id is not None:
1087
+ self._remove_duplicates_in_mail(id)
1088
+
1089
+ self.add_mail(
1090
+ "logEntry",
1091
+ {
1092
+ "type": type,
1093
+ "title": title,
1094
+ "message": shortened_message,
1095
+ "code": code,
1096
+ "blueprintExecution": blueprint_execution,
1097
+ "id": id,
1098
+ },
1099
+ )
1025
1100
 
1026
1101
  def file_download(self, data: Any, file_name: str):
1027
1102
  if not isinstance(data, (bytes, FileWrapper, BytesWrapper)):
1028
1103
  raise ValueError(
1029
- "Data for a fileDownload mail must be bytes, a FileWrapper or a BytesWrapper.")
1030
- self.add_mail("fileDownload", {
1031
- "data": state_serialiser.serialise(data),
1032
- "fileName": file_name
1033
- })
1104
+ "Data for a fileDownload mail must be bytes, a FileWrapper or a BytesWrapper."
1105
+ )
1106
+ self.add_mail(
1107
+ "fileDownload", {"data": state_serialiser.serialise(data), "fileName": file_name}
1108
+ )
1034
1109
 
1035
1110
  def open_url(self, url: str):
1036
1111
  self.add_mail("openUrl", url)
@@ -1038,6 +1113,18 @@ class WriterState(State):
1038
1113
  def clear_mail(self) -> None:
1039
1114
  self.mail = []
1040
1115
 
1116
+ def _remove_duplicates_in_mail(self, id: str) -> None:
1117
+ new_mail = []
1118
+ for entry in self.mail:
1119
+ if entry["type"] != "logEntry":
1120
+ new_mail.append(entry)
1121
+ continue
1122
+ log_id = entry["payload"].get("id")
1123
+ if log_id != id:
1124
+ new_mail.append(entry)
1125
+ continue
1126
+ self.mail = new_mail
1127
+
1041
1128
  def set_page(self, active_page_key: str) -> None:
1042
1129
  self.add_mail("pageChange", active_page_key)
1043
1130
 
@@ -1045,10 +1132,7 @@ class WriterState(State):
1045
1132
  self.add_mail("routeVarsChange", route_vars)
1046
1133
 
1047
1134
  def import_stylesheet(self, stylesheet_key: str, path: str) -> None:
1048
- self.add_mail("importStylesheet", {
1049
- "stylesheetKey": stylesheet_key,
1050
- "path": path
1051
- })
1135
+ self.add_mail("importStylesheet", {"stylesheetKey": stylesheet_key, "path": path})
1052
1136
 
1053
1137
  def import_script(self, script_key: str, path: str) -> None:
1054
1138
  """
@@ -1060,25 +1144,18 @@ class WriterState(State):
1060
1144
  >>>
1061
1145
  >>> initial_state.import_script("my_script", "/static/script.js")
1062
1146
  """
1063
- self.add_mail("importScript", {
1064
- "scriptKey": script_key,
1065
- "path": path
1066
- })
1147
+ self.add_mail("importScript", {"scriptKey": script_key, "path": path})
1067
1148
 
1068
1149
  def import_frontend_module(self, module_key: str, specifier: str) -> None:
1069
- self.add_mail("importModule", {
1070
- "moduleKey": module_key,
1071
- "specifier": specifier
1072
- })
1150
+ self.add_mail("importModule", {"moduleKey": module_key, "specifier": specifier})
1073
1151
 
1074
1152
  def call_frontend_function(self, module_key: str, function_name: str, args: List) -> None:
1075
- self.add_mail("functionCall", {
1076
- "moduleKey": module_key,
1077
- "functionName": function_name,
1078
- "args": args
1079
- })
1153
+ self.add_mail(
1154
+ "functionCall", {"moduleKey": module_key, "functionName": function_name, "args": args}
1155
+ )
1156
+
1080
1157
 
1081
- class MiddlewareExecutor():
1158
+ class MiddlewareExecutor:
1082
1159
  """
1083
1160
  A MiddlewareExecutor executes middleware in a controlled context. It allows writer framework
1084
1161
  to manage different implementations of middleware.
@@ -1114,7 +1191,6 @@ class MiddlewareExecutor():
1114
1191
 
1115
1192
 
1116
1193
  class MiddlewareRegistry:
1117
-
1118
1194
  def __init__(self) -> None:
1119
1195
  self.registry: List[MiddlewareExecutor] = []
1120
1196
 
@@ -1131,6 +1207,7 @@ class MiddlewareRegistry:
1131
1207
  """
1132
1208
  return self.registry
1133
1209
 
1210
+
1134
1211
  class EventHandlerRegistry:
1135
1212
  """
1136
1213
  Maps functions registered as event handlers from the user app's core
@@ -1143,10 +1220,138 @@ class EventHandlerRegistry:
1143
1220
 
1144
1221
  class HandlerEntry(TypedDict):
1145
1222
  callable: Callable
1146
- meta: 'EventHandlerRegistry.HandlerMeta'
1223
+ meta: "EventHandlerRegistry.HandlerMeta"
1224
+
1225
+ # === BLUEPRINT HANLDERS ===
1226
+ @staticmethod
1227
+ def stop_blueprint_run(payload: dict, blueprint_runner: "BlueprintRunner"):
1228
+ run_id = payload.pop("run_id", None)
1229
+ if not run_id:
1230
+ raise ValueError("Missing run_id in payload")
1231
+ return blueprint_runner.cancel_blueprint_execution(run_id=run_id)
1232
+
1233
+ @staticmethod
1234
+ def run_blueprint_by_id(
1235
+ payload: dict,
1236
+ context: dict,
1237
+ session: dict,
1238
+ blueprint_runner: "BlueprintRunner",
1239
+ vault: Dict,
1240
+ ):
1241
+ blueprint_id = payload.pop("blueprint_id", None)
1242
+ if not blueprint_id:
1243
+ raise ValueError("Missing blueprint_id in payload")
1244
+ execution_environment = EventHandler._get_blueprint_execution_environment(
1245
+ payload, context, session, vault
1246
+ )
1247
+ return blueprint_runner.run_blueprint(
1248
+ component_id=blueprint_id,
1249
+ execution_environment=execution_environment,
1250
+ title="Blueprint execution triggered on demand",
1251
+ )
1252
+
1253
+ @staticmethod
1254
+ def run_blueprint_by_key(
1255
+ payload: dict,
1256
+ context: dict,
1257
+ session: dict,
1258
+ blueprint_runner: "BlueprintRunner",
1259
+ vault: Dict,
1260
+ ):
1261
+ blueprint_key = payload.pop("blueprint_key", None)
1262
+ if not blueprint_key:
1263
+ raise ValueError("Missing blueprint_key in payload")
1264
+ execution_environment = EventHandler._get_blueprint_execution_environment(
1265
+ payload, context, session, vault
1266
+ )
1267
+ return blueprint_runner.run_blueprint_by_key(
1268
+ blueprint_key=blueprint_key, execution_environment=execution_environment
1269
+ )
1270
+
1271
+ @staticmethod
1272
+ def run_blueprint_via_api(
1273
+ payload: dict,
1274
+ context: dict,
1275
+ session: dict,
1276
+ blueprint_runner: "BlueprintRunner",
1277
+ vault: Dict,
1278
+ ):
1279
+ """
1280
+ This handler is used to run a blueprint via the API.
1281
+ It is used by the frontend to run a blueprint when the user clicks on a button.
1282
+ """
1283
+ blueprint_id = payload.pop("blueprint_id", None)
1284
+ if not blueprint_id:
1285
+ raise ValueError("Missing blueprint_id in payload")
1286
+ trigger_type = payload.pop("trigger_type", None)
1287
+ if not trigger_type:
1288
+ raise ValueError("Missing trigger_type in payload")
1289
+ execution_environment = EventHandler._get_blueprint_execution_environment(
1290
+ payload, context, session, vault
1291
+ )
1292
+ return blueprint_runner.run_blueprint_via_api(
1293
+ blueprint_id=blueprint_id,
1294
+ trigger_type=trigger_type,
1295
+ branch_id=payload.pop("branch_id", None),
1296
+ execution_environment=execution_environment,
1297
+ )
1298
+
1299
+ @staticmethod
1300
+ def run_blueprint_branch(
1301
+ payload: dict,
1302
+ context: dict,
1303
+ session: dict,
1304
+ blueprint_runner: "BlueprintRunner",
1305
+ vault: Dict,
1306
+ ):
1307
+ branch_id = payload.pop("branch_id", None)
1308
+ if not branch_id:
1309
+ raise ValueError("Missing branch_id in payload")
1310
+ execution_environment = EventHandler._get_blueprint_execution_environment(
1311
+ payload, context, session, vault
1312
+ )
1313
+ return blueprint_runner.run_branch(
1314
+ start_node_id=branch_id,
1315
+ branch_out_id=None,
1316
+ execution_environment=execution_environment,
1317
+ title="Branch execution triggered by demand",
1318
+ )
1147
1319
 
1148
1320
  def __init__(self):
1149
- self.handler_map: Dict[str, 'EventHandlerRegistry.HandlerEntry'] = {} # type: ignore
1321
+ self.handler_map: Dict[str, "EventHandlerRegistry.HandlerEntry"] = {
1322
+ "stop_blueprint_run": {
1323
+ "callable": self.stop_blueprint_run,
1324
+ "meta": {"name": "stop_blueprint_run", "args": ["payload", "blueprint_runner"]},
1325
+ },
1326
+ "run_blueprint_by_key": {
1327
+ "callable": self.run_blueprint_by_key,
1328
+ "meta": {
1329
+ "name": "run_blueprint_by_key",
1330
+ "args": ["payload", "context", "session", "blueprint_runner", "vault"],
1331
+ },
1332
+ },
1333
+ "run_blueprint_by_id": {
1334
+ "callable": self.run_blueprint_by_id,
1335
+ "meta": {
1336
+ "name": "run_blueprint_by_id",
1337
+ "args": ["payload", "context", "session", "blueprint_runner", "vault"],
1338
+ },
1339
+ },
1340
+ "run_blueprint_via_api": {
1341
+ "callable": self.run_blueprint_via_api,
1342
+ "meta": {
1343
+ "name": "run_blueprint_via_api",
1344
+ "args": ["payload", "context", "session", "blueprint_runner", "vault"],
1345
+ },
1346
+ },
1347
+ "run_blueprint_branch": {
1348
+ "callable": self.run_blueprint_branch,
1349
+ "meta": {
1350
+ "name": "run_blueprint_branch",
1351
+ "args": ["payload", "context", "session", "blueprint_runner", "vault"],
1352
+ },
1353
+ },
1354
+ }
1150
1355
 
1151
1356
  def __iter__(self):
1152
1357
  return iter(self.handler_map.keys())
@@ -1165,23 +1370,17 @@ class EventHandlerRegistry:
1165
1370
  # and handler __qualname__by a dot
1166
1371
  access_name = f"{module_name}.{handler.__qualname__}"
1167
1372
 
1168
- entry: EventHandlerRegistry.HandlerEntry = \
1169
- {
1170
- "callable": handler,
1171
- "meta": {
1172
- "name": access_name,
1173
- "args": inspect.getfullargspec(handler).args
1174
- }
1175
- }
1373
+ entry: EventHandlerRegistry.HandlerEntry = {
1374
+ "callable": handler,
1375
+ "meta": {"name": access_name, "args": inspect.getfullargspec(handler).args},
1376
+ }
1176
1377
 
1177
1378
  self.handler_map[access_name] = entry
1178
1379
 
1179
1380
  def register_module(self, module: ModuleType):
1180
1381
  if isinstance(module, ModuleType):
1181
- all_fn_names = (x[0] for x in inspect.getmembers(
1182
- module, inspect.isfunction))
1183
- exposed_fn_names = list(
1184
- filter(lambda x: not x.startswith("_"), all_fn_names))
1382
+ all_fn_names = (x[0] for x in inspect.getmembers(module, inspect.isfunction))
1383
+ exposed_fn_names = list(filter(lambda x: not x.startswith("_"), all_fn_names))
1185
1384
 
1186
1385
  for fn_name in exposed_fn_names:
1187
1386
  fn_callable = getattr(module, fn_name)
@@ -1189,25 +1388,18 @@ class EventHandlerRegistry:
1189
1388
  continue
1190
1389
  self.register_handler(fn_callable)
1191
1390
  else:
1192
- raise ValueError(
1193
- f"Attempted to register a non-module object: {module}"
1194
- )
1391
+ raise ValueError(f"Attempted to register a non-module object: {module}")
1195
1392
 
1196
1393
  def find_handler_callable(self, handler_name: str) -> Optional[Callable]:
1197
1394
  if handler_name not in self.handler_map:
1198
1395
  return None
1199
- handler_entry: EventHandlerRegistry.HandlerEntry = \
1200
- self.handler_map[handler_name]
1396
+ handler_entry: EventHandlerRegistry.HandlerEntry = self.handler_map[handler_name]
1201
1397
  return handler_entry["callable"]
1202
1398
 
1203
- def get_handler_meta(
1204
- self,
1205
- handler_name: str
1206
- ) -> "EventHandlerRegistry.HandlerMeta":
1399
+ def get_handler_meta(self, handler_name: str) -> "EventHandlerRegistry.HandlerMeta":
1207
1400
  if handler_name not in self.handler_map:
1208
1401
  raise RuntimeError(f"Handler {handler_name} is not registered")
1209
- entry: EventHandlerRegistry.HandlerEntry = \
1210
- self.handler_map[handler_name]
1402
+ entry: EventHandlerRegistry.HandlerEntry = self.handler_map[handler_name]
1211
1403
  return entry["meta"]
1212
1404
 
1213
1405
  def gather_handler_meta(self) -> List["EventHandlerRegistry.HandlerMeta"]:
@@ -1215,7 +1407,6 @@ class EventHandlerRegistry:
1215
1407
 
1216
1408
 
1217
1409
  class EventDeserialiser:
1218
-
1219
1410
  """Applies transformations to the payload of an incoming event, depending on its type.
1220
1411
 
1221
1412
  The transformation happens in place: the event passed to the transform method is mutated.
@@ -1224,7 +1415,9 @@ class EventDeserialiser:
1224
1415
  applying sanitisation of inputs where relevant."""
1225
1416
 
1226
1417
  def __init__(self, session: "WriterSession"):
1227
- self.evaluator = writer.evaluator.Evaluator(session.session_state, session.session_component_tree)
1418
+ self.evaluator = writer.evaluator.Evaluator(
1419
+ session.session_state, session.session_component_tree
1420
+ )
1228
1421
 
1229
1422
  def transform(self, ev: WriterEvent) -> None:
1230
1423
  # Events without payloads are safe
@@ -1243,8 +1436,7 @@ class EventDeserialiser:
1243
1436
  if ev.isSafe:
1244
1437
  return
1245
1438
  ev.payload = {}
1246
- raise ValueError(
1247
- "No payload transformer available for custom event type.")
1439
+ raise ValueError("No payload transformer available for custom event type.")
1248
1440
  tf_func = getattr(self, func_name)
1249
1441
  try:
1250
1442
  tf_payload = tf_func(ev)
@@ -1259,8 +1451,7 @@ class EventDeserialiser:
1259
1451
  instance_path = ev.instancePath
1260
1452
  if not instance_path:
1261
1453
  raise ValueError("This event cannot be run as a global event.")
1262
- options = self.evaluator.evaluate_field(
1263
- instance_path, "tags", True, "{ }")
1454
+ options = self.evaluator.evaluate_field(instance_path, "tags", True, "{ }")
1264
1455
  if not isinstance(options, dict):
1265
1456
  raise ValueError("Invalid value for tags")
1266
1457
  if payload not in options.keys():
@@ -1273,7 +1464,8 @@ class EventDeserialiser:
1273
1464
  if not instance_path:
1274
1465
  raise ValueError("This event cannot be run as a global event.")
1275
1466
  options = self.evaluator.evaluate_field(
1276
- instance_path, "options", True, """{ "a": "Option A", "b": "Option B" }""")
1467
+ instance_path, "options", True, """{ "a": "Option A", "b": "Option B" }"""
1468
+ )
1277
1469
  if not isinstance(options, dict):
1278
1470
  raise ValueError("Invalid value for options")
1279
1471
  if payload not in options.keys():
@@ -1286,12 +1478,12 @@ class EventDeserialiser:
1286
1478
  if not instance_path:
1287
1479
  raise ValueError("This event cannot be run as a global event.")
1288
1480
  options = self.evaluator.evaluate_field(
1289
- instance_path, "options", True, """{ "a": "Option A", "b": "Option B" }""")
1481
+ instance_path, "options", True, """{ "a": "Option A", "b": "Option B" }"""
1482
+ )
1290
1483
  if not isinstance(options, dict):
1291
1484
  raise ValueError("Invalid value for options")
1292
1485
  if not isinstance(payload, list):
1293
- raise ValueError(
1294
- "Invalid multiple options payload. Expected a list.")
1486
+ raise ValueError("Invalid multiple options payload. Expected a list.")
1295
1487
  if not all(item in options.keys() for item in payload):
1296
1488
  raise ValueError("Unauthorised option")
1297
1489
  return payload
@@ -1310,7 +1502,7 @@ class EventDeserialiser:
1310
1502
  "key": key,
1311
1503
  "ctrl_key": ctrl_key,
1312
1504
  "shift_key": shift_key,
1313
- "meta_key": meta_key
1505
+ "meta_key": meta_key,
1314
1506
  }
1315
1507
  return tf_payload
1316
1508
 
@@ -1319,21 +1511,14 @@ class EventDeserialiser:
1319
1511
  ctrl_key = bool(payload.get("ctrlKey"))
1320
1512
  shift_key = bool(payload.get("shiftKey"))
1321
1513
  meta_key = bool(payload.get("metaKey"))
1322
- tf_payload = {
1323
- "ctrl_key": ctrl_key,
1324
- "shift_key": shift_key,
1325
- "meta_key": meta_key
1326
- }
1514
+ tf_payload = {"ctrl_key": ctrl_key, "shift_key": shift_key, "meta_key": meta_key}
1327
1515
  return tf_payload
1328
1516
 
1329
1517
  def _transform_hashchange(self, ev) -> Dict:
1330
1518
  payload = ev.payload
1331
1519
  page_key = payload.get("pageKey")
1332
1520
  route_vars = dict(payload.get("routeVars"))
1333
- tf_payload = {
1334
- "page_key": page_key,
1335
- "route_vars": route_vars
1336
- }
1521
+ tf_payload = {"page_key": page_key, "route_vars": route_vars}
1337
1522
  return tf_payload
1338
1523
 
1339
1524
  def _transform_page_open(self, ev) -> str:
@@ -1344,10 +1529,7 @@ class EventDeserialiser:
1344
1529
  payload = ev.payload
1345
1530
  page_key = payload.get("pageKey")
1346
1531
  route_vars = dict(payload.get("routeVars"))
1347
- tf_payload = {
1348
- "page_key": page_key,
1349
- "route_vars": route_vars
1350
- }
1532
+ tf_payload = {"page_key": page_key, "route_vars": route_vars}
1351
1533
  return tf_payload
1352
1534
 
1353
1535
  def _transform_chatbot_message(self, ev) -> dict:
@@ -1384,7 +1566,7 @@ class EventDeserialiser:
1384
1566
  return {
1385
1567
  "name": file_item.get("name"),
1386
1568
  "type": file_item.get("type"),
1387
- "data": urllib.request.urlopen(data).read()
1569
+ "data": urllib.request.urlopen(data).read(),
1388
1570
  }
1389
1571
 
1390
1572
  def _transform_file_change(self, ev) -> List[Dict]:
@@ -1401,8 +1583,7 @@ class EventDeserialiser:
1401
1583
  try:
1402
1584
  datetime.date.fromisoformat(payload)
1403
1585
  except ValueError:
1404
- raise ValueError(
1405
- "Date must be in YYYY-MM-DD format or another valid ISO 8601 format.")
1586
+ raise ValueError("Date must be in YYYY-MM-DD format or another valid ISO 8601 format.")
1406
1587
 
1407
1588
  return payload
1408
1589
 
@@ -1412,10 +1593,11 @@ class EventDeserialiser:
1412
1593
  if not isinstance(payload, str):
1413
1594
  raise ValueError("Time must be a string.")
1414
1595
  try:
1415
- time.strptime(payload, '%H:%M')
1596
+ time.strptime(payload, "%H:%M")
1416
1597
  except ValueError:
1417
1598
  raise ValueError(
1418
- "Time must be in hh:mm format (in 24-hour format that includes leading zeros).")
1599
+ "Time must be in hh:mm format (in 24-hour format that includes leading zeros)."
1600
+ )
1419
1601
 
1420
1602
  return payload
1421
1603
 
@@ -1455,7 +1637,7 @@ class EventDeserialiser:
1455
1637
  payload = ev.payload
1456
1638
  if not isinstance(payload, dict):
1457
1639
  return None
1458
-
1640
+
1459
1641
  payload = _deserialize_bigint_format(payload)
1460
1642
  return payload
1461
1643
 
@@ -1475,17 +1657,21 @@ class EventDeserialiser:
1475
1657
  payload = _deserialize_bigint_format(payload)
1476
1658
  return payload
1477
1659
 
1660
+ def _transform_tab_change(self, ev: WriterEvent) -> Optional[str]:
1661
+ payload = ev.payload
1662
+ if not isinstance(payload, str):
1663
+ return None
1664
+ return payload
1478
1665
 
1479
- class SessionManager:
1480
1666
 
1667
+ class SessionManager:
1481
1668
  """
1482
1669
  Stores and manages sessions.
1483
1670
  """
1484
1671
 
1485
1672
  IDLE_SESSION_MAX_SECONDS = 3600
1486
1673
  TOKEN_SIZE_BYTES = 32
1487
- hex_pattern = re.compile(
1488
- r"^[0-9a-fA-F]{" + str(TOKEN_SIZE_BYTES*2) + r"}$")
1674
+ hex_pattern = re.compile(r"^[0-9a-fA-F]{" + str(TOKEN_SIZE_BYTES * 2) + r"}$")
1489
1675
 
1490
1676
  def __init__(self) -> None:
1491
1677
  self.sessions: Dict[str, WriterSession] = {}
@@ -1494,7 +1680,9 @@ class SessionManager:
1494
1680
  def add_verifier(self, verifier: Callable) -> None:
1495
1681
  self.verifiers.append(verifier)
1496
1682
 
1497
- def _verify_before_new_session(self, cookies: Optional[Dict] = None, headers: Optional[Dict] = None) -> bool:
1683
+ def _verify_before_new_session(
1684
+ self, cookies: Optional[Dict] = None, headers: Optional[Dict] = None
1685
+ ) -> bool:
1498
1686
  for verifier in self.verifiers:
1499
1687
  args = inspect.getfullargspec(verifier).args
1500
1688
  arg_values = []
@@ -1509,8 +1697,7 @@ class SessionManager:
1509
1697
  elif verifier_result is True:
1510
1698
  pass
1511
1699
  else:
1512
- raise ValueError(
1513
- "Invalid verifier return value. Must be True or False.")
1700
+ raise ValueError("Invalid verifier return value. Must be True or False.")
1514
1701
  return True
1515
1702
 
1516
1703
  def _check_proposed_session_id(self, proposed_session_id: Optional[str]) -> bool:
@@ -1520,7 +1707,12 @@ class SessionManager:
1520
1707
  return True
1521
1708
  return False
1522
1709
 
1523
- def get_new_session(self, cookies: Optional[Dict] = None, headers: Optional[Dict] = None, proposed_session_id: Optional[str] = None) -> Optional[WriterSession]:
1710
+ def get_new_session(
1711
+ self,
1712
+ cookies: Optional[Dict] = None,
1713
+ headers: Optional[Dict] = None,
1714
+ proposed_session_id: Optional[str] = None,
1715
+ ) -> Optional[WriterSession]:
1524
1716
  if not self._check_proposed_session_id(proposed_session_id):
1525
1717
  return None
1526
1718
  if not self._verify_before_new_session(cookies, headers):
@@ -1534,9 +1726,13 @@ class SessionManager:
1534
1726
  self.sessions[new_id] = new_session
1535
1727
  return new_session
1536
1728
 
1537
- def get_session(self, session_id: Optional[str], restore_initial_mail: bool = False) -> Optional[WriterSession]:
1729
+ def get_session(
1730
+ self, session_id: Optional[str], restore_initial_mail: bool = False
1731
+ ) -> Optional[WriterSession]:
1538
1732
  if session_id is None:
1539
1733
  return None
1734
+ if session_id == "anonymous":
1735
+ return None
1540
1736
 
1541
1737
  session = self.sessions.get(session_id)
1542
1738
  if session is not None and restore_initial_mail is True:
@@ -1556,8 +1752,7 @@ class SessionManager:
1556
1752
  del self.sessions[session_id]
1557
1753
 
1558
1754
  def prune_sessions(self) -> None:
1559
- cutoff_timestamp = int(time.time()) - \
1560
- SessionManager.IDLE_SESSION_MAX_SECONDS
1755
+ cutoff_timestamp = int(time.time()) - SessionManager.IDLE_SESSION_MAX_SECONDS
1561
1756
  prune_sessions = []
1562
1757
  for session_id, session in self.sessions.items():
1563
1758
  if session.last_active_timestamp < cutoff_timestamp:
@@ -1575,21 +1770,21 @@ class SessionManager:
1575
1770
 
1576
1771
 
1577
1772
  class EventHandler:
1578
-
1579
1773
  """
1580
1774
  Handles events in the context of a Session.
1581
1775
  """
1582
1776
 
1583
1777
  def __init__(self, session: WriterSession) -> None:
1584
- import writer.workflows
1778
+ import writer.blueprints
1585
1779
 
1586
1780
  self.session = session
1587
1781
  self.session_state = session.session_state
1588
1782
  self.session_component_tree = session.session_component_tree
1589
1783
  self.deser = EventDeserialiser(session)
1590
- self.evaluator = writer.evaluator.Evaluator(session.session_state, session.session_component_tree)
1591
- self.workflow_runner = writer.workflows.WorkflowRunner(session)
1592
-
1784
+ self.evaluator = writer.evaluator.Evaluator(
1785
+ session.session_state, session.session_component_tree
1786
+ )
1787
+ self.blueprint_runner = writer.blueprints.BlueprintRunner(session)
1593
1788
 
1594
1789
  def _handle_binding(self, event_type, target_component, instance_path, payload) -> None:
1595
1790
  if not target_component.binding:
@@ -1599,28 +1794,44 @@ class EventHandler:
1599
1794
  return
1600
1795
  self.evaluator.set_state(binding["stateRef"], instance_path, payload)
1601
1796
 
1602
- def _get_workflow_callable(self, workflow_key: Optional[str] = None, workflow_id: Optional[str] = None):
1603
- def fn(payload, context, session):
1604
- execution_environment = {
1605
- "payload": payload,
1606
- "context": context,
1607
- "session": session
1608
- }
1609
- if workflow_key:
1610
- return self.workflow_runner.run_workflow_by_key(workflow_key, execution_environment)
1611
- elif workflow_id:
1612
- return self.workflow_runner.run_workflow(workflow_id, execution_environment, "Workflow execution triggered on demand")
1613
- return fn
1797
+ @staticmethod
1798
+ def _get_blueprint_execution_environment(payload, context, session, vault):
1799
+ return {
1800
+ "payload": payload,
1801
+ "context": context,
1802
+ "session": session,
1803
+ "vault": vault,
1804
+ }
1614
1805
 
1615
- def _get_handler_callable(self, handler: str) -> Optional[Callable]:
1616
- if handler.startswith("$runWorkflow_"):
1617
- workflow_key = handler[13:]
1618
- return self._get_workflow_callable(workflow_key=workflow_key)
1806
+ def _get_blueprint_callable(
1807
+ self,
1808
+ blueprint_key: Optional[str] = None,
1809
+ blueprint_id: Optional[str] = None,
1810
+ branch_id: Optional[str] = None,
1811
+ ):
1812
+ def fn(payload, context, session, vault):
1813
+ execution_environment = self._get_blueprint_execution_environment(
1814
+ payload, context, session, vault
1815
+ )
1816
+ if blueprint_key:
1817
+ return self.blueprint_runner.run_blueprint_by_key(
1818
+ blueprint_key, execution_environment
1819
+ )
1820
+ elif blueprint_id:
1821
+ return self.blueprint_runner.run_blueprint(
1822
+ blueprint_id, execution_environment, "Blueprint execution triggered on demand"
1823
+ )
1824
+ elif branch_id:
1825
+ return self.blueprint_runner.run_branch(
1826
+ branch_id,
1827
+ None,
1828
+ execution_environment,
1829
+ "Blueprint branch execution triggered on demand",
1830
+ )
1619
1831
 
1620
- if handler.startswith("$runWorkflowById_"):
1621
- workflow_id = handler[17:]
1622
- return self._get_workflow_callable(workflow_id=workflow_id)
1832
+ return fn
1623
1833
 
1834
+ def _get_handler_callable(self, handler: str) -> Optional[Callable]:
1624
1835
  current_app_process = get_app_process()
1625
1836
  handler_registry = current_app_process.handler_registry
1626
1837
  callable_handler = handler_registry.find_handler_callable(handler)
@@ -1633,51 +1844,73 @@ class EventHandler:
1633
1844
  "state": self.session_state,
1634
1845
  "payload": ev.payload,
1635
1846
  "context": context_data,
1636
- "session":_event_handler_session_info(),
1637
- "ui": _event_handler_ui_manager()
1847
+ "session": _event_handler_session_info(),
1848
+ "ui": _event_handler_ui_manager(),
1849
+ "blueprint_runner": self.blueprint_runner,
1850
+ "vault": writer_vault.get_secrets(),
1638
1851
  }
1639
1852
 
1640
- def _call_handler_callable(
1641
- self,
1642
- handler_callable: Callable,
1643
- calling_arguments: Dict
1644
- ) -> Any:
1853
+ def _call_handler_callable(self, handler_callable: Callable, calling_arguments: Dict) -> Any:
1645
1854
  current_app_process = get_app_process()
1646
1855
  result = None
1647
- captured_stdout = None
1648
- with core_ui.use_component_tree(self.session.session_component_tree), \
1649
- contextlib.redirect_stdout(io.StringIO()) as f:
1856
+ with (
1857
+ core_ui.use_component_tree(self.session.session_component_tree),
1858
+ use_stdout_redirect(
1859
+ lambda entry: self.session_state.add_log_entry("info", "Stdout message", entry)
1860
+ ),
1861
+ ):
1650
1862
  middlewares_executors = current_app_process.middleware_registry.executors()
1651
- result = EventHandlerExecutor.invoke_with_middlewares(middlewares_executors, handler_callable, calling_arguments)
1652
- captured_stdout = f.getvalue()
1653
-
1654
- if captured_stdout:
1655
- self.session_state.add_log_entry("info", "Stdout message", captured_stdout)
1863
+ result = EventHandlerExecutor.invoke_with_middlewares(
1864
+ middlewares_executors, handler_callable, calling_arguments
1865
+ )
1656
1866
 
1657
1867
  return result
1658
1868
 
1659
1869
  def _deserialize(self, ev: WriterEvent):
1660
1870
  try:
1661
1871
  self.deser.transform(ev)
1662
- except BaseException as e:
1872
+ except BaseException as e:
1663
1873
  self.session_state.add_notification(
1664
- "error", "Error", f"A deserialization error occurred when handling event '{ ev.type }'.")
1665
- self.session_state.add_log_entry("error", "Deserialization Failed",
1666
- f"The data sent might be corrupt. A runtime exception was raised when deserializing event '{ ev.type }'.", traceback.format_exc())
1874
+ "error",
1875
+ "Error",
1876
+ f"A deserialization error occurred when handling event '{ ev.type }'.",
1877
+ )
1878
+ self.session_state.add_log_entry(
1879
+ "error",
1880
+ "Deserialization Failed",
1881
+ f"The data sent might be corrupt. A runtime exception was raised when deserializing event '{ ev.type }'.",
1882
+ traceback.format_exc(),
1883
+ )
1667
1884
  raise e
1668
1885
 
1669
1886
  def _handle_global_event(self, ev: WriterEvent):
1670
- if not ev.isSafe:
1671
- error_message = "Attempted executing a global event in an unsafe context."
1672
- self.session_state.add_log_entry("error", "Forbidden operation", error_message, traceback.format_exc())
1673
- raise PermissionError(error_message)
1674
- if not ev.handler:
1675
- raise ValueError("Handler not specified when attempting to execute global event.")
1676
- handler_callable = self._get_handler_callable(ev.handler)
1677
- if not handler_callable:
1678
- return
1679
- calling_arguments = self._get_calling_arguments(ev, instance_path=None)
1680
- return self._call_handler_callable(handler_callable, calling_arguments)
1887
+ try:
1888
+ if not ev.isSafe:
1889
+ raise PermissionError("Attempted executing a global event in an unsafe context.")
1890
+ if not ev.handler:
1891
+ raise ValueError("Handler not specified when attempting to execute global event.")
1892
+ handler_callable = self._get_handler_callable(ev.handler)
1893
+ if not handler_callable:
1894
+ return
1895
+ calling_arguments = self._get_calling_arguments(ev, instance_path=None)
1896
+ return self._call_handler_callable(handler_callable, calling_arguments)
1897
+ except BaseException as e:
1898
+ if not isinstance(e, BlueprintExecutionError):
1899
+ # Only create a notification and log entry
1900
+ # for non-blueprint errors, as blueprint errors
1901
+ # have their own logging mechanism
1902
+ self.session_state.add_notification(
1903
+ "error",
1904
+ "Runtime Error",
1905
+ f"An error occurred when processing event '{ ev.type }'.",
1906
+ )
1907
+ self.session_state.add_log_entry(
1908
+ "error",
1909
+ "Runtime Exception",
1910
+ f"A runtime exception was raised when processing event '{ ev.type }'.",
1911
+ traceback.format_exc(),
1912
+ )
1913
+ raise e
1681
1914
 
1682
1915
  def _handle_component_event(self, ev: WriterEvent):
1683
1916
  instance_path = ev.instancePath
@@ -1687,6 +1920,14 @@ class EventHandler:
1687
1920
  target_id = instance_path[-1]["componentId"]
1688
1921
  target_component = cast(Component, self.session_component_tree.get_component(target_id))
1689
1922
  self._handle_binding(ev.type, target_component, instance_path, ev.payload)
1923
+ calling_arguments = self._get_calling_arguments(ev, instance_path)
1924
+ execution_environment = self._get_blueprint_execution_environment(
1925
+ calling_arguments.get("payload"),
1926
+ calling_arguments.get("context"),
1927
+ calling_arguments.get("session"),
1928
+ calling_arguments.get("vault"),
1929
+ )
1930
+ self.blueprint_runner.execute_ui_trigger(target_id, ev.type, execution_environment)
1690
1931
  if not target_component.handlers:
1691
1932
  return None
1692
1933
  handler = target_component.handlers.get(ev.type)
@@ -1695,13 +1936,19 @@ class EventHandler:
1695
1936
  handler_callable = self._get_handler_callable(handler)
1696
1937
  if not handler_callable:
1697
1938
  return
1698
- calling_arguments = self._get_calling_arguments(ev, instance_path)
1699
1939
  return self._call_handler_callable(handler_callable, calling_arguments)
1700
1940
  except BaseException as e:
1701
- self.session_state.add_notification("error", "Runtime Error", f"An error occurred when processing event '{ ev.type }'.",
1702
- )
1703
- self.session_state.add_log_entry("error", "Runtime Exception",
1704
- f"A runtime exception was raised when processing event '{ ev.type }'.", traceback.format_exc())
1941
+ self.session_state.add_notification(
1942
+ "error",
1943
+ "Runtime Error",
1944
+ f"An error occurred when processing event '{ ev.type }'.",
1945
+ )
1946
+ self.session_state.add_log_entry(
1947
+ "error",
1948
+ "Runtime Exception",
1949
+ f"A runtime exception was raised when processing event '{ ev.type }'.",
1950
+ traceback.format_exc(),
1951
+ )
1705
1952
  raise e
1706
1953
 
1707
1954
  def handle(self, ev: WriterEvent) -> WriterEventResult:
@@ -1718,7 +1965,6 @@ class EventHandler:
1718
1965
 
1719
1966
 
1720
1967
  class EventHandlerExecutor:
1721
-
1722
1968
  @staticmethod
1723
1969
  def build_arguments(func: Callable, writer_args: dict) -> List[Any]:
1724
1970
  """
@@ -1756,7 +2002,7 @@ class EventHandlerExecutor:
1756
2002
  >>> EventHandlerExecutor.invoke(my_handler, {'state': {'a': 1}, 'payload': None, 'context': None, 'session': None, 'ui': None})
1757
2003
  """
1758
2004
  is_async_handler = inspect.iscoroutinefunction(callable_handler)
1759
- if (not callable(callable_handler) and not is_async_handler):
2005
+ if not callable(callable_handler) and not is_async_handler:
1760
2006
  raise ValueError("Invalid handler. The handler isn't a callable object.")
1761
2007
 
1762
2008
  handler_args = EventHandlerExecutor.build_arguments(callable_handler, writer_args)
@@ -1770,7 +2016,11 @@ class EventHandlerExecutor:
1770
2016
  return result
1771
2017
 
1772
2018
  @staticmethod
1773
- def invoke_with_middlewares(middlewares_executors: List[MiddlewareExecutor], callable_handler: Callable, writer_args: dict) -> Any:
2019
+ def invoke_with_middlewares(
2020
+ middlewares_executors: List[MiddlewareExecutor],
2021
+ callable_handler: Callable,
2022
+ writer_args: dict,
2023
+ ) -> Any:
1774
2024
  """
1775
2025
  Runs the middlewares then the handler. This function allows you to manage exceptions that are triggered in middleware
1776
2026
 
@@ -1789,7 +2039,9 @@ class EventHandlerExecutor:
1789
2039
  else:
1790
2040
  executor = middlewares_executors[0]
1791
2041
  with executor.execute(writer_args):
1792
- return EventHandlerExecutor.invoke_with_middlewares(middlewares_executors[1:], callable_handler, writer_args)
2042
+ return EventHandlerExecutor.invoke_with_middlewares(
2043
+ middlewares_executors[1:], callable_handler, writer_args
2044
+ )
1793
2045
 
1794
2046
 
1795
2047
  class DictPropertyProxy:
@@ -1840,6 +2092,7 @@ class DictPropertyProxy:
1840
2092
 
1841
2093
  S = TypeVar("S", bound=WriterState)
1842
2094
 
2095
+
1843
2096
  def new_initial_state(klass: Type[S], raw_state: dict) -> S:
1844
2097
  """
1845
2098
  Initializes the initial state of the application and makes it globally accessible.
@@ -1859,6 +2112,7 @@ def new_initial_state(klass: Type[S], raw_state: dict) -> S:
1859
2112
 
1860
2113
  return initial_state
1861
2114
 
2115
+
1862
2116
  """
1863
2117
  This variable contains the list of properties calculated for each class
1864
2118
  that inherits from State.
@@ -1868,6 +2122,7 @@ these properties when loading an application.
1868
2122
  """
1869
2123
  calculated_properties_per_state_type: Dict[Type[State], List[str]] = {}
1870
2124
 
2125
+
1871
2126
  def writerproperty(path: Union[str, List[str]]):
1872
2127
  """
1873
2128
  Mechanism for declaring a calculated property whenever an attribute changes
@@ -1891,8 +2146,7 @@ def writerproperty(path: Union[str, List[str]]):
1891
2146
  >>> return self.counterA + self.counterB
1892
2147
  """
1893
2148
 
1894
- class Property():
1895
-
2149
+ class Property:
1896
2150
  def __init__(self, func):
1897
2151
  self.func = func
1898
2152
  self.initialized = False
@@ -1917,17 +2171,22 @@ def writerproperty(path: Union[str, List[str]]):
1917
2171
  """
1918
2172
  args = inspect.getfullargspec(self.func)
1919
2173
  if len(args.args) > 1:
1920
- logging.warning(f"Wrong signature for calculated property '{instance.__class__.__name__}:{self.property_name}'. It must declare only self argument.")
2174
+ logging.warning(
2175
+ f"Wrong signature for calculated property '{instance.__class__.__name__}:{self.property_name}'. It must declare only self argument."
2176
+ )
1921
2177
  return None
1922
2178
 
1923
2179
  if self.initialized is False:
1924
- instance.calculated_property(property_name=self.property_name, path=path, handler=self.func)
2180
+ instance.calculated_property(
2181
+ property_name=self.property_name, path=path, handler=self.func
2182
+ )
1925
2183
  self.initialized = True
1926
2184
 
1927
2185
  return self.func(instance)
1928
2186
 
1929
2187
  return Property
1930
2188
 
2189
+
1931
2190
  def session_verifier(func: Callable) -> Callable:
1932
2191
  """
1933
2192
  Decorator for marking session verifiers.
@@ -1967,7 +2226,10 @@ def reset_base_component_tree() -> None:
1967
2226
  global base_component_tree
1968
2227
  base_component_tree = core_ui.build_base_component_tree()
1969
2228
 
1970
- def _clone_mutation_subscriptions(session_state: State, app_state: State, root_state: Optional['State'] = None) -> None:
2229
+
2230
+ def _clone_mutation_subscriptions(
2231
+ session_state: State, app_state: State, root_state: Optional["State"] = None
2232
+ ) -> None:
1971
2233
  """
1972
2234
  clone subscriptions on mutations between the initial state of the application and the state created for the session
1973
2235
 
@@ -1988,7 +2250,9 @@ def _clone_mutation_subscriptions(session_state: State, app_state: State, root_s
1988
2250
  _root_state = root_state if root_state is not None else session_state
1989
2251
  for mutation_subscription in state_proxy_app.local_mutation_subscriptions:
1990
2252
  new_mutation_subscription = copy.copy(mutation_subscription)
1991
- new_mutation_subscription.state = _root_state if new_mutation_subscription.type == "subscription" else session_state
2253
+ new_mutation_subscription.state = (
2254
+ _root_state if new_mutation_subscription.type == "subscription" else session_state
2255
+ )
1992
2256
  session_state._state_proxy.local_mutation_subscriptions.append(new_mutation_subscription)
1993
2257
 
1994
2258
 
@@ -2006,19 +2270,19 @@ def parse_state_variable_expression(p: str):
2006
2270
  it = 0
2007
2271
  last_split = 0
2008
2272
  while it < len(p):
2009
- if p[it] == '\\':
2273
+ if p[it] == "\\":
2010
2274
  it += 2
2011
- elif p[it] == '.':
2012
- new_part = p[last_split: it]
2013
- parts.append(new_part.replace('\\.', '.'))
2275
+ elif p[it] == ".":
2276
+ new_part = p[last_split:it]
2277
+ parts.append(new_part.replace("\\.", "."))
2014
2278
 
2015
2279
  last_split = it + 1
2016
2280
  it += 1
2017
2281
  else:
2018
2282
  it += 1
2019
2283
 
2020
- new_part = p[last_split: len(p)]
2021
- parts.append(new_part.replace('\\.', '.'))
2284
+ new_part = p[last_split : len(p)]
2285
+ parts.append(new_part.replace("\\.", "."))
2022
2286
  return parts
2023
2287
 
2024
2288
 
@@ -2026,31 +2290,36 @@ async def _async_wrapper_internal(callable_handler: Callable, arg_values: List[A
2026
2290
  result = await callable_handler(*arg_values)
2027
2291
  return result
2028
2292
 
2293
+
2029
2294
  def _event_handler_session_info() -> Dict[str, Any]:
2030
2295
  """
2031
2296
  Returns the session information for the current event handler.
2032
2297
 
2033
2298
  This information is exposed in the session parameter of a handler
2034
-
2299
+
2035
2300
  """
2036
2301
  current_session = get_session()
2037
2302
  session_info: Dict[str, Any] = {}
2038
2303
  if current_session is not None:
2039
- session_info['id'] = current_session.session_id
2040
- session_info['cookies'] = current_session.cookies
2041
- session_info['headers'] = current_session.headers
2042
- session_info['userinfo'] = current_session.userinfo or {}
2304
+ session_info["id"] = current_session.session_id
2305
+ session_info["cookies"] = current_session.cookies
2306
+ session_info["headers"] = current_session.headers
2307
+ session_info["userinfo"] = current_session.userinfo or {}
2043
2308
 
2044
2309
  return session_info
2045
2310
 
2311
+
2046
2312
  def _event_handler_ui_manager():
2047
2313
  from writer import PROPER_UI_INIT, _get_ui_runtime_error_message
2314
+
2048
2315
  if PROPER_UI_INIT:
2049
2316
  from writer.ui import WriterUIManager
2317
+
2050
2318
  return WriterUIManager()
2051
2319
  else:
2052
2320
  raise RuntimeError(_get_ui_runtime_error_message())
2053
2321
 
2322
+
2054
2323
  def _deserialize_bigint_format(payload: Optional[Union[dict, list]]):
2055
2324
  """
2056
2325
  Decodes the payload of a big int serialization
@@ -2117,9 +2386,11 @@ def _type_match_dict(expected_type: Type):
2117
2386
  >>>
2118
2387
  >>> _type_match_dict(SpecifcDict) # True
2119
2388
  """
2120
- if expected_type is not None and \
2121
- inspect.isclass(expected_type) and \
2122
- issubclass(expected_type, dict):
2389
+ if (
2390
+ expected_type is not None
2391
+ and inspect.isclass(expected_type)
2392
+ and issubclass(expected_type, dict)
2393
+ ):
2123
2394
  return True
2124
2395
 
2125
2396
  if typing.get_origin(expected_type) == dict:
@@ -2151,6 +2422,7 @@ def unescape_bigint_matching_string(string: str) -> str:
2151
2422
 
2152
2423
  return result
2153
2424
 
2425
+
2154
2426
  state_serialiser = StateSerialiser()
2155
2427
  initial_state = WriterState()
2156
2428
  base_component_tree = core_ui.build_base_component_tree()