simstack 2.0.1__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 (354) hide show
  1. simstack/__init__.py +0 -0
  2. simstack/_version.py +24 -0
  3. simstack/core/__init__.py +0 -0
  4. simstack/core/artifacts.py +406 -0
  5. simstack/core/asnyc_helper.py +14 -0
  6. simstack/core/config_file.py +60 -0
  7. simstack/core/context.py +283 -0
  8. simstack/core/definitions.py +22 -0
  9. simstack/core/engine.py +115 -0
  10. simstack/core/find_simstack_modules.py +41 -0
  11. simstack/core/hash.py +179 -0
  12. simstack/core/node.py +1137 -0
  13. simstack/core/node_claim.py +31 -0
  14. simstack/core/node_runner.py +362 -0
  15. simstack/core/process_results.py +102 -0
  16. simstack/core/recompute_artifacts.py +117 -0
  17. simstack/core/reset_db.py +12 -0
  18. simstack/core/resource_assignment.py +222 -0
  19. simstack/core/resources.py +90 -0
  20. simstack/core/route_table.py +24 -0
  21. simstack/core/run_docker.py +109 -0
  22. simstack/core/run_node.py +71 -0
  23. simstack/core/runner.py +169 -0
  24. simstack/core/services/__init__.py +11 -0
  25. simstack/core/services/base_service.py +191 -0
  26. simstack/core/services/file_transfer_service.py +117 -0
  27. simstack/core/services/git_restart_service.py +12 -0
  28. simstack/core/services/git_uv_update_service.py +105 -0
  29. simstack/core/services/node_execution_service.py +182 -0
  30. simstack/core/services/resource_branch_monitor_service.py +71 -0
  31. simstack/core/services/runner_cleanup_service.py +149 -0
  32. simstack/core/services/runner_manager.py +163 -0
  33. simstack/core/services/runner_status_service.py +10 -0
  34. simstack/core/services/slurm_status_service.py +67 -0
  35. simstack/core/services/stop_check_service.py +27 -0
  36. simstack/core/services/timeout_restart_service.py +26 -0
  37. simstack/core/simstack_result.py +39 -0
  38. simstack/core/submit_node.py +217 -0
  39. simstack/core/task_id.py +22 -0
  40. simstack/methods/__init__.py +0 -0
  41. simstack/methods/archive_files.py +292 -0
  42. simstack/methods/fire_and_forget_runner.py +113 -0
  43. simstack/methods/generate_test.py +125 -0
  44. simstack/methods/get_file.py +26 -0
  45. simstack/methods/mass_runner.py +154 -0
  46. simstack/methods/multiple_executor.py +307 -0
  47. simstack/methods/switch_git.py +16 -0
  48. simstack/methods/upload_helpers.py +81 -0
  49. simstack/models/__init__.py +74 -0
  50. simstack/models/array_list.py +21 -0
  51. simstack/models/array_storage.py +83 -0
  52. simstack/models/artifact_models.py +127 -0
  53. simstack/models/base_lists.py +51 -0
  54. simstack/models/base_types.py +209 -0
  55. simstack/models/charts_artifact.py +690 -0
  56. simstack/models/dataset.py +451 -0
  57. simstack/models/dataset_metadata.py +390 -0
  58. simstack/models/datasettuple.py +587 -0
  59. simstack/models/file_instance.py +150 -0
  60. simstack/models/file_list.py +45 -0
  61. simstack/models/files.py +424 -0
  62. simstack/models/fire_and_forget_result.py +19 -0
  63. simstack/models/images2d.py +113 -0
  64. simstack/models/log_entry_model.py +37 -0
  65. simstack/models/models.py +52 -0
  66. simstack/models/named_data_reference.py +36 -0
  67. simstack/models/node_registry.py +96 -0
  68. simstack/models/pandas_model.py +239 -0
  69. simstack/models/parameters.py +402 -0
  70. simstack/models/pickle_models.py +300 -0
  71. simstack/models/project.py +13 -0
  72. simstack/models/resource_assignment.py +170 -0
  73. simstack/models/resource_definition.py +125 -0
  74. simstack/models/runner_model.py +39 -0
  75. simstack/models/simple_table.py +79 -0
  76. simstack/models/simstack_model.py +97 -0
  77. simstack/models/slurm_info.py +17 -0
  78. simstack/models/table_artifact.py +87 -0
  79. simstack/models/tag.py +12 -0
  80. simstack/models/unused_test_file.py +5 -0
  81. simstack/simstack_marker.com +1 -0
  82. simstack/static/docs/.buildinfo +4 -0
  83. simstack/static/docs/_images/GUI Concept.webp +0 -0
  84. simstack/static/docs/_modules/index.html +434 -0
  85. simstack/static/docs/_modules/simstack/core/artifacts.html +743 -0
  86. simstack/static/docs/_modules/simstack/core/asnyc_helper.html +330 -0
  87. simstack/static/docs/_modules/simstack/core/config_file.html +379 -0
  88. simstack/static/docs/_modules/simstack/core/definitions.html +341 -0
  89. simstack/static/docs/_modules/simstack/core/engine.html +437 -0
  90. simstack/static/docs/_modules/simstack/core/find_simstack_modules.html +360 -0
  91. simstack/static/docs/_modules/simstack/core/hash.html +528 -0
  92. simstack/static/docs/_modules/simstack/core/node.html +1392 -0
  93. simstack/static/docs/_modules/simstack/core/node_claim.html +347 -0
  94. simstack/static/docs/_modules/simstack/core/node_runner.html +711 -0
  95. simstack/static/docs/_modules/simstack/core/process_results.html +418 -0
  96. simstack/static/docs/_modules/simstack/core/recompute_artifacts.html +433 -0
  97. simstack/static/docs/_modules/simstack/core/reset_db.html +328 -0
  98. simstack/static/docs/_modules/simstack/core/resource_assignment.html +553 -0
  99. simstack/static/docs/_modules/simstack/core/resources.html +424 -0
  100. simstack/static/docs/_modules/simstack/core/route_table.html +349 -0
  101. simstack/static/docs/_modules/simstack/core/run_docker.html +425 -0
  102. simstack/static/docs/_modules/simstack/core/run_node.html +390 -0
  103. simstack/static/docs/_modules/simstack/core/runner.html +491 -0
  104. simstack/static/docs/_modules/simstack/core/services/base_service.html +522 -0
  105. simstack/static/docs/_modules/simstack/core/services/file_transfer_service.html +436 -0
  106. simstack/static/docs/_modules/simstack/core/services/git_restart_service.html +331 -0
  107. simstack/static/docs/_modules/simstack/core/services/git_uv_update_service.html +427 -0
  108. simstack/static/docs/_modules/simstack/core/services/node_execution_service.html +507 -0
  109. simstack/static/docs/_modules/simstack/core/services/resource_branch_monitor_service.html +390 -0
  110. simstack/static/docs/_modules/simstack/core/services/runner_cleanup_service.html +468 -0
  111. simstack/static/docs/_modules/simstack/core/services/runner_manager.html +485 -0
  112. simstack/static/docs/_modules/simstack/core/services/runner_status_service.html +329 -0
  113. simstack/static/docs/_modules/simstack/core/services/slurm_status_service.html +386 -0
  114. simstack/static/docs/_modules/simstack/core/services/stop_check_service.html +346 -0
  115. simstack/static/docs/_modules/simstack/core/services/timeout_restart_service.html +345 -0
  116. simstack/static/docs/_modules/simstack/core/simstack_result.html +355 -0
  117. simstack/static/docs/_modules/simstack/core/submit_node.html +536 -0
  118. simstack/static/docs/_modules/simstack/core/task_id.html +344 -0
  119. simstack/static/docs/_modules/simstack/methods/archive_files.html +598 -0
  120. simstack/static/docs/_modules/simstack/methods/fire_and_forget_runner.html +432 -0
  121. simstack/static/docs/_modules/simstack/methods/generate_test.html +443 -0
  122. simstack/static/docs/_modules/simstack/methods/get_file.html +342 -0
  123. simstack/static/docs/_modules/simstack/methods/mass_runner.html +476 -0
  124. simstack/static/docs/_modules/simstack/methods/multiple_executor.html +674 -0
  125. simstack/static/docs/_modules/simstack/methods/switch_git.html +332 -0
  126. simstack/static/docs/_modules/simstack/methods/upload_helpers.html +403 -0
  127. simstack/static/docs/_modules/simstack/models/array_list.html +337 -0
  128. simstack/static/docs/_modules/simstack/models/array_storage.html +417 -0
  129. simstack/static/docs/_modules/simstack/models/base_lists.html +374 -0
  130. simstack/static/docs/_modules/simstack/models/base_types.html +588 -0
  131. simstack/static/docs/_modules/simstack/models/charts_artifact.html +1035 -0
  132. simstack/static/docs/_modules/simstack/models/dataset.html +866 -0
  133. simstack/static/docs/_modules/simstack/models/dataset_metadata.html +711 -0
  134. simstack/static/docs/_modules/simstack/models/datasettuple.html +1005 -0
  135. simstack/static/docs/_modules/simstack/models/file_instance.html +472 -0
  136. simstack/static/docs/_modules/simstack/models/file_list.html +367 -0
  137. simstack/static/docs/_modules/simstack/models/files.html +773 -0
  138. simstack/static/docs/_modules/simstack/models/fire_and_forget_result.html +335 -0
  139. simstack/static/docs/_modules/simstack/models/images2d.html +447 -0
  140. simstack/static/docs/_modules/simstack/models/log_entry_model.html +356 -0
  141. simstack/static/docs/_modules/simstack/models/models.html +374 -0
  142. simstack/static/docs/_modules/simstack/models/named_data_reference.html +355 -0
  143. simstack/static/docs/_modules/simstack/models/node_registry.html +415 -0
  144. simstack/static/docs/_modules/simstack/models/pandas_model.html +570 -0
  145. simstack/static/docs/_modules/simstack/models/parameters.html +739 -0
  146. simstack/static/docs/_modules/simstack/models/pickle_models.html +631 -0
  147. simstack/static/docs/_modules/simstack/models/project.html +329 -0
  148. simstack/static/docs/_modules/simstack/models/resource_assignment.html +501 -0
  149. simstack/static/docs/_modules/simstack/models/resource_definition.html +477 -0
  150. simstack/static/docs/_modules/simstack/models/runner_model.html +361 -0
  151. simstack/static/docs/_modules/simstack/models/simple_table.html +413 -0
  152. simstack/static/docs/_modules/simstack/models/simstack_model.html +416 -0
  153. simstack/static/docs/_modules/simstack/models/slurm_info.html +333 -0
  154. simstack/static/docs/_modules/simstack/models/table_artifact.html +406 -0
  155. simstack/static/docs/_modules/simstack/models/tag.html +328 -0
  156. simstack/static/docs/_modules/simstack/tables/model_table.html +519 -0
  157. simstack/static/docs/_modules/simstack/tables/node_children.html +538 -0
  158. simstack/static/docs/_modules/simstack/tables/node_table.html +854 -0
  159. simstack/static/docs/_modules/simstack/tables/table_builder.html +642 -0
  160. simstack/static/docs/_modules/simstack/util/async_zip_utils.html +337 -0
  161. simstack/static/docs/_modules/simstack/util/b64mixin.html +339 -0
  162. simstack/static/docs/_modules/simstack/util/cleaned_json_schema.html +336 -0
  163. simstack/static/docs/_modules/simstack/util/config_reader.html +498 -0
  164. simstack/static/docs/_modules/simstack/util/custom_model_dump.html +391 -0
  165. simstack/static/docs/_modules/simstack/util/database_information.html +488 -0
  166. simstack/static/docs/_modules/simstack/util/db.html +1392 -0
  167. simstack/static/docs/_modules/simstack/util/db_logger.html +448 -0
  168. simstack/static/docs/_modules/simstack/util/default_from_dict.html +376 -0
  169. simstack/static/docs/_modules/simstack/util/directory_iterator.html +553 -0
  170. simstack/static/docs/_modules/simstack/util/docstring_parser.html +526 -0
  171. simstack/static/docs/_modules/simstack/util/file_hashing.html +394 -0
  172. simstack/static/docs/_modules/simstack/util/file_transfer_client.html +791 -0
  173. simstack/static/docs/_modules/simstack/util/files.html +733 -0
  174. simstack/static/docs/_modules/simstack/util/find_python_files.html +349 -0
  175. simstack/static/docs/_modules/simstack/util/generate_ui_schema.html +396 -0
  176. simstack/static/docs/_modules/simstack/util/generic_list_mixin.html +487 -0
  177. simstack/static/docs/_modules/simstack/util/get_module_path.html +336 -0
  178. simstack/static/docs/_modules/simstack/util/git_pull.html +381 -0
  179. simstack/static/docs/_modules/simstack/util/git_repository_status.html +411 -0
  180. simstack/static/docs/_modules/simstack/util/import_module.html +377 -0
  181. simstack/static/docs/_modules/simstack/util/importer.html +637 -0
  182. simstack/static/docs/_modules/simstack/util/init_data_source.html +357 -0
  183. simstack/static/docs/_modules/simstack/util/make_graph.html +423 -0
  184. simstack/static/docs/_modules/simstack/util/make_table.html +1171 -0
  185. simstack/static/docs/_modules/simstack/util/mappings.html +378 -0
  186. simstack/static/docs/_modules/simstack/util/minimal_route_finder.html +388 -0
  187. simstack/static/docs/_modules/simstack/util/mock_db.html +472 -0
  188. simstack/static/docs/_modules/simstack/util/module_path_checker.html +354 -0
  189. simstack/static/docs/_modules/simstack/util/object_list_mixin.html +649 -0
  190. simstack/static/docs/_modules/simstack/util/path_manager.html +566 -0
  191. simstack/static/docs/_modules/simstack/util/project_root_finder.html +357 -0
  192. simstack/static/docs/_modules/simstack/util/queue_watcher.html +593 -0
  193. simstack/static/docs/_modules/simstack/util/resource_config.html +532 -0
  194. simstack/static/docs/_modules/simstack/util/route_finder.html +390 -0
  195. simstack/static/docs/_modules/simstack/util/runner_util.html +487 -0
  196. simstack/static/docs/_modules/simstack/util/runner_utils.html +468 -0
  197. simstack/static/docs/_modules/simstack/util/safe_code_executor.html +510 -0
  198. simstack/static/docs/_modules/simstack/util/sample_config.html +414 -0
  199. simstack/static/docs/_modules/simstack/util/sensitive_content_filter.html +364 -0
  200. simstack/static/docs/_modules/simstack/util/setup_logging.html +365 -0
  201. simstack/static/docs/_modules/simstack/util/submit_to_watchdog.html +425 -0
  202. simstack/static/docs/_modules/simstack/util/toml_reader.html +482 -0
  203. simstack/static/docs/_modules/simstack/util/transform_file_name.html +357 -0
  204. simstack/static/docs/_modules/simstack/util/ui_tools.html +541 -0
  205. simstack/static/docs/_sources/architecture.rst.txt +25 -0
  206. simstack/static/docs/_sources/array_lists.rst.txt +60 -0
  207. simstack/static/docs/_sources/ci_pipeline.rst.txt +34 -0
  208. simstack/static/docs/_sources/data.rst.txt +175 -0
  209. simstack/static/docs/_sources/database_migrations.rst.txt +310 -0
  210. simstack/static/docs/_sources/datasets.rst.txt +93 -0
  211. simstack/static/docs/_sources/developer_guide.rst.txt +25 -0
  212. simstack/static/docs/_sources/docker_nodes.rst.txt +149 -0
  213. simstack/static/docs/_sources/files.rst.txt +159 -0
  214. simstack/static/docs/_sources/fire_and_forget_runner.rst.txt +67 -0
  215. simstack/static/docs/_sources/index.rst.txt +25 -0
  216. simstack/static/docs/_sources/installation.rst.txt +213 -0
  217. simstack/static/docs/_sources/lists.rst.txt +111 -0
  218. simstack/static/docs/_sources/mass_runner.rst.txt +91 -0
  219. simstack/static/docs/_sources/misc.rst.txt +106 -0
  220. simstack/static/docs/_sources/overview.rst.txt +91 -0
  221. simstack/static/docs/_sources/recovery.rst.txt +38 -0
  222. simstack/static/docs/_sources/resource_config.rst.txt +141 -0
  223. simstack/static/docs/_sources/simstack/modules/modules.rst.txt +7 -0
  224. simstack/static/docs/_sources/simstack/modules/simstack.core.rst.txt +205 -0
  225. simstack/static/docs/_sources/simstack/modules/simstack.core.services.rst.txt +109 -0
  226. simstack/static/docs/_sources/simstack/modules/simstack.methods.rst.txt +77 -0
  227. simstack/static/docs/_sources/simstack/modules/simstack.models.rst.txt +261 -0
  228. simstack/static/docs/_sources/simstack/modules/simstack.rst.txt +22 -0
  229. simstack/static/docs/_sources/simstack/modules/simstack.tables.rst.txt +45 -0
  230. simstack/static/docs/_sources/simstack/modules/simstack.util.rst.txt +381 -0
  231. simstack/static/docs/_sources/submit.rst.txt +141 -0
  232. simstack/static/docs/_sources/tables.rst.txt +50 -0
  233. simstack/static/docs/_sources/testing_complex_nodes.rst.txt +92 -0
  234. simstack/static/docs/_sources/ui_documentation.rst.txt +272 -0
  235. simstack/static/docs/_sources/workflows.rst.txt +446 -0
  236. simstack/static/docs/_static/base-stemmer.js +476 -0
  237. simstack/static/docs/_static/basic.css +906 -0
  238. simstack/static/docs/_static/cobalt2-theme.css +1 -0
  239. simstack/static/docs/_static/debug.css +69 -0
  240. simstack/static/docs/_static/docs-build-badge.css +51 -0
  241. simstack/static/docs/_static/docs-build-badge.js +51 -0
  242. simstack/static/docs/_static/doctools.js +150 -0
  243. simstack/static/docs/_static/documentation_options.js +13 -0
  244. simstack/static/docs/_static/english-stemmer.js +1066 -0
  245. simstack/static/docs/_static/file.png +0 -0
  246. simstack/static/docs/_static/language_data.js +13 -0
  247. simstack/static/docs/_static/minus.png +0 -0
  248. simstack/static/docs/_static/plus.png +0 -0
  249. simstack/static/docs/_static/pygments.css +250 -0
  250. simstack/static/docs/_static/scripts/furo-extensions.js +0 -0
  251. simstack/static/docs/_static/scripts/furo.js +3 -0
  252. simstack/static/docs/_static/scripts/furo.js.LICENSE.txt +7 -0
  253. simstack/static/docs/_static/scripts/furo.js.map +1 -0
  254. simstack/static/docs/_static/searchtools.js +693 -0
  255. simstack/static/docs/_static/skeleton.css +296 -0
  256. simstack/static/docs/_static/sphinx_highlight.js +159 -0
  257. simstack/static/docs/_static/styles/furo-extensions.css +2 -0
  258. simstack/static/docs/_static/styles/furo-extensions.css.map +1 -0
  259. simstack/static/docs/_static/styles/furo.css +2 -0
  260. simstack/static/docs/_static/styles/furo.css.map +1 -0
  261. simstack/static/docs/architecture.html +340 -0
  262. simstack/static/docs/array_lists.html +678 -0
  263. simstack/static/docs/ci_pipeline.html +385 -0
  264. simstack/static/docs/data.html +545 -0
  265. simstack/static/docs/database_migrations.html +652 -0
  266. simstack/static/docs/datasets.html +1268 -0
  267. simstack/static/docs/developer_guide.html +359 -0
  268. simstack/static/docs/docker_nodes.html +503 -0
  269. simstack/static/docs/files.html +718 -0
  270. simstack/static/docs/fire_and_forget_runner.html +641 -0
  271. simstack/static/docs/genindex.html +6390 -0
  272. simstack/static/docs/index.html +516 -0
  273. simstack/static/docs/installation.html +553 -0
  274. simstack/static/docs/lists.html +478 -0
  275. simstack/static/docs/mass_runner.html +481 -0
  276. simstack/static/docs/misc.html +454 -0
  277. simstack/static/docs/objects.inv +0 -0
  278. simstack/static/docs/overview.html +469 -0
  279. simstack/static/docs/py-modindex.html +1111 -0
  280. simstack/static/docs/recovery.html +371 -0
  281. simstack/static/docs/resource_config.html +590 -0
  282. simstack/static/docs/search.html +327 -0
  283. simstack/static/docs/searchindex.js +1 -0
  284. simstack/static/docs/simstack/modules/modules.html +489 -0
  285. simstack/static/docs/simstack/modules/simstack.core.html +1883 -0
  286. simstack/static/docs/simstack/modules/simstack.core.services.html +698 -0
  287. simstack/static/docs/simstack/modules/simstack.html +3060 -0
  288. simstack/static/docs/simstack/modules/simstack.methods.html +1150 -0
  289. simstack/static/docs/simstack/modules/simstack.models.html +13113 -0
  290. simstack/static/docs/simstack/modules/simstack.tables.html +635 -0
  291. simstack/static/docs/simstack/modules/simstack.util.html +3320 -0
  292. simstack/static/docs/submit.html +487 -0
  293. simstack/static/docs/tables.html +539 -0
  294. simstack/static/docs/testing_complex_nodes.html +458 -0
  295. simstack/static/docs/ui_documentation.html +788 -0
  296. simstack/static/docs/workflows.html +766 -0
  297. simstack/tables/__init__.py +0 -0
  298. simstack/tables/model_table.py +191 -0
  299. simstack/tables/node_children.py +219 -0
  300. simstack/tables/node_table.py +517 -0
  301. simstack/tables/table_builder.py +314 -0
  302. simstack/util/__init__.py +0 -0
  303. simstack/util/async_zip_utils.py +18 -0
  304. simstack/util/b64mixin.py +23 -0
  305. simstack/util/cleaned_json_schema.py +20 -0
  306. simstack/util/config_reader.py +179 -0
  307. simstack/util/custom_model_dump.py +75 -0
  308. simstack/util/database_information.py +160 -0
  309. simstack/util/db.py +1013 -0
  310. simstack/util/db_logger.py +117 -0
  311. simstack/util/default_from_dict.py +57 -0
  312. simstack/util/directory_iterator.py +216 -0
  313. simstack/util/docstring_parser.py +186 -0
  314. simstack/util/file_hashing.py +72 -0
  315. simstack/util/file_transfer_client.py +427 -0
  316. simstack/util/files.py +396 -0
  317. simstack/util/find_python_files.py +33 -0
  318. simstack/util/generate_ui_schema.py +80 -0
  319. simstack/util/generic_list_mixin.py +120 -0
  320. simstack/util/geometry_utils.py +118 -0
  321. simstack/util/get_module_path.py +20 -0
  322. simstack/util/git_pull.py +65 -0
  323. simstack/util/git_repository_status.py +95 -0
  324. simstack/util/import_module.py +63 -0
  325. simstack/util/importer.py +312 -0
  326. simstack/util/init_data_source.py +41 -0
  327. simstack/util/make_graph.py +107 -0
  328. simstack/util/make_table.py +837 -0
  329. simstack/util/mappings.py +41 -0
  330. simstack/util/minimal_route_finder.py +72 -0
  331. simstack/util/mock_db.py +144 -0
  332. simstack/util/module_path_checker.py +38 -0
  333. simstack/util/object_list_mixin.py +282 -0
  334. simstack/util/path_manager.py +223 -0
  335. simstack/util/project_root_finder.py +41 -0
  336. simstack/util/queue_watcher.py +262 -0
  337. simstack/util/resource_config.py +198 -0
  338. simstack/util/route_finder.py +74 -0
  339. simstack/util/runner_util.py +166 -0
  340. simstack/util/runner_utils.py +143 -0
  341. simstack/util/safe_code_executor.py +194 -0
  342. simstack/util/sample_config.py +98 -0
  343. simstack/util/sensitive_content_filter.py +48 -0
  344. simstack/util/setup_logging.py +49 -0
  345. simstack/util/submit_to_watchdog.py +106 -0
  346. simstack/util/toml_reader.py +142 -0
  347. simstack/util/transform_file_name.py +41 -0
  348. simstack/util/ui_tools.py +213 -0
  349. simstack-2.0.1.dist-info/METADATA +454 -0
  350. simstack-2.0.1.dist-info/RECORD +354 -0
  351. simstack-2.0.1.dist-info/WHEEL +5 -0
  352. simstack-2.0.1.dist-info/entry_points.txt +12 -0
  353. simstack-2.0.1.dist-info/licenses/LICENSE +21 -0
  354. simstack-2.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import shutil
5
+ import uuid
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any, Optional, Union
9
+
10
+ from odmantic import EmbeddedModel, Field, ObjectId
11
+ from pydantic import model_validator
12
+
13
+ from simstack.models import simstack_model
14
+ from simstack.models.parameters import Resource
15
+ from simstack.util.file_transfer_client import transfer_resource_name
16
+ from simstack.util.file_hashing import hash_file
17
+
18
+ logger = logging.getLogger("file_instance")
19
+
20
+
21
+ @simstack_model
22
+ class FileInstance(EmbeddedModel):
23
+ """
24
+ Represents an embedded model for a file instance.
25
+
26
+ The `FileInstance` class is used to encapsulate details about a file,
27
+ including its path, associated resource, and creation timestamp.
28
+ It provides a class method for initializing a `FileInstance` object
29
+ from a local file path, ensuring proper handling of file-related operations.
30
+
31
+ Attributes:
32
+ path (str): Path to the file relative to the host work directory.
33
+ resource (Resource): Name of the resource associated with the file.
34
+ created_at (datetime): Timestamp indicating when the file instance was created.
35
+ """
36
+
37
+ id: Optional[str] = Field(default=None, description="File instance id")
38
+ path: str = Field(
39
+ description="Path to the file relative to the host work directory"
40
+ )
41
+ resource: Resource = Field(description="Resource name")
42
+ created_at: datetime = Field(description="Creation timestamp")
43
+ runner_id: Optional[str] = Field(
44
+ default=None, description="Runner identity that created the instance"
45
+ )
46
+ location_type: str = Field(default="local_path", description="File location type")
47
+ size_bytes: Optional[int] = Field(default=None, description="File size in bytes")
48
+ checksum_sha256: Optional[str] = Field(default=None, description="SHA256 checksum")
49
+ last_accessed_at: Optional[datetime] = Field(
50
+ default=None, description="Last access timestamp"
51
+ )
52
+ expires_at: Optional[datetime] = Field(
53
+ default=None, description="Expiration timestamp"
54
+ )
55
+ is_authoritative: bool = Field(
56
+ default=True, description="Whether this instance is authoritative"
57
+ )
58
+ is_cached: bool = Field(
59
+ default=False,
60
+ description="Whether this instance is a reusable local cache copy",
61
+ )
62
+ status: str = Field(default="available", description="Instance lifecycle status")
63
+
64
+ @model_validator(mode="before")
65
+ def migration(cls, values: Any) -> Any:
66
+ if isinstance(values.get("resource"), str):
67
+ values["resource"] = Resource(value=values["resource"])
68
+ if "path" in values and isinstance(values["path"], Path):
69
+ values["path"] = str(values["path"])
70
+ if not values.get("id"):
71
+ values["id"] = str(uuid.uuid4())
72
+ if not values.get("location_type"):
73
+ values["location_type"] = "local_path"
74
+ if not values.get("status"):
75
+ values["status"] = "available"
76
+ if values.get("is_authoritative") is None:
77
+ values["is_authoritative"] = True
78
+ if values.get("is_cached") is None:
79
+ values["is_cached"] = False
80
+ return values
81
+
82
+ @classmethod
83
+ def from_local_file(
84
+ cls,
85
+ path: Union[Path, str],
86
+ file_stack_id: ObjectId,
87
+ make_copy: bool = True,
88
+ tasks_id: str = "",
89
+ ) -> FileInstance:
90
+ """
91
+ Creates a FileInstance object from a local file path.
92
+
93
+ This class method is responsible for initializing a `FileInstance` based
94
+ on a local file's path. It supports options to hash the file, make a
95
+ user-specific copy, and tracks additional metadata. The method handles
96
+ local file operations such as copying files to a secure directory when
97
+ necessary and organizes resources under a configurable working directory.
98
+
99
+ :param file_stack_id: the id of the filestack where the file is in
100
+ :param path: The file path to the local file. Can be either a string
101
+ or `Path`.
102
+ :param make_copy: Indicates whether a secure local copy of the file should
103
+ be made within the application's working directory. Defaults to True.
104
+ :return: A `FileInstance` object initialized with file details.
105
+ :rtype: FileInstance
106
+ :raises ValueError: If there are issues during the creation of the
107
+ `FileInstance` from the specified local file.
108
+ """
109
+ source_path = path if isinstance(path, Path) else Path(path)
110
+
111
+ # Prepare the content field if in_memory is True
112
+ resolved_path = Path(path).resolve()
113
+ # Find 'simstack' in the path and compute the relative path from its parent
114
+ from simstack.core.context import context
115
+
116
+ workdir = context.config.workdir
117
+ resolved_workdir = Path(workdir).resolve()
118
+ if tasks_id == "":
119
+ logger.debug(f"workdir is {resolved_workdir} path is {resolved_path}")
120
+ else:
121
+ logger.debug(
122
+ f"task_id: {tasks_id} workdir is {resolved_workdir} path is {resolved_path}"
123
+ )
124
+ try:
125
+ relative_path = resolved_path.relative_to(resolved_workdir)
126
+ except ValueError:
127
+ if tasks_id == "":
128
+ logger.error(
129
+ f"Path {resolved_path} is not under workdir {resolved_workdir}"
130
+ )
131
+ else:
132
+ logger.error(
133
+ f"Path {resolved_path} is not under workdir {resolved_workdir} for task_id: {tasks_id}"
134
+ )
135
+ import getpass
136
+
137
+ username = getpass.getuser()
138
+ relative_path = Path(username) / str(file_stack_id)
139
+ absolute_dir = Path(context.config.workdir) / relative_path
140
+ absolute_dir.mkdir(parents=True, exist_ok=True)
141
+ shutil.copy(source_path, absolute_dir)
142
+
143
+ file_instance = FileInstance(
144
+ path=str(relative_path),
145
+ resource=Resource(value=transfer_resource_name(context.config.resource)),
146
+ created_at=datetime.now(),
147
+ size_bytes=source_path.stat().st_size if source_path.exists() else None,
148
+ checksum_sha256=hash_file(source_path) if source_path.exists() else None,
149
+ )
150
+ return file_instance
@@ -0,0 +1,45 @@
1
+ import re
2
+ from typing import List, Optional, Iterator
3
+
4
+ from odmantic import EmbeddedModel, Field, Model, ObjectId
5
+
6
+ from simstack.models import simstack_model
7
+ from simstack.util.object_list_mixin import ObjectListMixin
8
+ from simstack.models.files import FileStack
9
+
10
+
11
+ @simstack_model
12
+ class FileList(EmbeddedModel, ObjectListMixin[FileStack]):
13
+ elements: List[ObjectId] = Field(default_factory=list)
14
+
15
+ def __init__(self, **data):
16
+ data, cache = self._normalize_elements_for_init(data)
17
+ EmbeddedModel.__init__(self, **data)
18
+ if cache is not None:
19
+ self._set_cache(cache)
20
+
21
+ def __iter__(self) -> Iterator[FileStack]:
22
+ return ObjectListMixin.__iter__(self)
23
+
24
+
25
+ @simstack_model
26
+ class FileListModel(Model, ObjectListMixin[FileStack]):
27
+ elements: List[ObjectId] = Field(default_factory=list)
28
+
29
+ def __init__(self, **data):
30
+ data, cache = self._normalize_elements_for_init(data)
31
+ Model.__init__(self, **data)
32
+ if cache is not None:
33
+ self._set_cache(cache)
34
+
35
+ def __iter__(self) -> Iterator[FileStack]:
36
+ return ObjectListMixin.__iter__(self)
37
+
38
+
39
+ @simstack_model
40
+ class FileListIO(Model):
41
+ file_list: FileList = Field(default_factory=FileList)
42
+ task_status: Optional[str] = None
43
+ error: Optional[str] = None
44
+ message: Optional[str] = None
45
+
@@ -0,0 +1,424 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import zlib
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import List, Optional, Union, Dict, Any
9
+
10
+ from odmantic import Model, Field, ObjectId, Reference
11
+
12
+ from simstack.models import simstack_model
13
+ from simstack.models.file_instance import FileInstance
14
+ from simstack.models.parameters import Resource
15
+ from simstack.util.file_transfer_client import (
16
+ FileTransferClient,
17
+ first_available_remote_location,
18
+ path_for_file_instance,
19
+ transfer_resource_name,
20
+ )
21
+ from simstack.util.file_hashing import hash_file, hash_string
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # MongoDB has a 16MB document size limit
26
+ MONGODB_MAX_DOCUMENT_SIZE = 16 * 1024 * 1024 # 16 MB in bytes
27
+
28
+ # TODO: its a mystery to me why we should have files with no name or unknown size, both values are actually used in __str__
29
+
30
+ @simstack_model
31
+ class FileStack(Model):
32
+ name: Optional[str] = Field(description="Name of the file", default=None)
33
+ size: Optional[int] = Field(description="Size of the file in bytes", default=None)
34
+ is_hashable: bool = Field(
35
+ default=False, description="Whether the file stack is hashable"
36
+ )
37
+ hash: Optional[str] = Field(
38
+ default=None, description="Hash of the file stack content", index=True
39
+ )
40
+ in_memory: bool = Field(
41
+ default=False, description="Whether the file stack is in memory"
42
+ )
43
+ content: Optional[bytes] = Field(
44
+ description="Compressed file content", default=None
45
+ )
46
+
47
+ locations: List[FileInstance] = Field(
48
+ default_factory=list, description="List of file locations"
49
+ )
50
+
51
+ def __str__(self) -> str:
52
+ return f"FileStack(name={self.name}, size={self.size}, is_hashable={self.is_hashable}, in_memory={self.in_memory}, locations={self.locations})"
53
+
54
+ def __repr__(self) -> str:
55
+ return f"FileStack(name={self.name}, size={self.size}, is_hashable={self.is_hashable}, in_memory={self.in_memory}, locations={self.locations})"
56
+
57
+ async def custom_model_dump(self, **kwargs: Any) -> Dict[str, Any]:
58
+ dumped_data: Dict[str, Any] = self.model_dump()
59
+ del dumped_data["content"] # Exclude content from the dumped data
60
+ return dumped_data
61
+
62
+ @classmethod
63
+ def ui_base_schema(cls, **kwargs: Any) -> Dict[str, Any]:
64
+ # TODO get the model programatically
65
+ return {
66
+ "ui:field": "FileField",
67
+ "ui:options": {
68
+ "model": "simstack.models.files.FileStack",
69
+ },
70
+ }
71
+
72
+ @classmethod
73
+ def from_string(cls, data_string: str, file_name: str) -> FileStack:
74
+ content = zlib.compress(data_string.encode("utf-8"))
75
+ file_hash = hash_string(data_string)
76
+ size = len(content)
77
+
78
+ if len(content) > MONGODB_MAX_DOCUMENT_SIZE:
79
+ logger.error(
80
+ f"Compressed content size {len(content)} bytes exceeds MongoDB limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes for file {file_name}"
81
+ )
82
+ raise ValueError(
83
+ f"Compressed content size {len(content)} bytes exceeds MongoDB document size limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes"
84
+ )
85
+
86
+ file_stack = cls(
87
+ name=file_name,
88
+ in_memory=True,
89
+ content=content,
90
+ is_hashable=True,
91
+ size=size,
92
+ hash=file_hash,
93
+ )
94
+ return file_stack
95
+
96
+ @classmethod
97
+ def from_local_file(
98
+ cls,
99
+ path: Union[Path, str],
100
+ is_hashable: bool = True,
101
+ in_memory: bool = True,
102
+ secure_source: bool = False,
103
+ task_id: str = "",
104
+ ) -> FileStack:
105
+ """
106
+ Creates a FileStack object from a local file path.
107
+
108
+ :param task_id: task_id of the task that created the file stack, used for logging and tracking
109
+ :type task_id: str
110
+ :param secure_source: specifies if the source is secure (already in a directory generated within Simstack II)
111
+ :type secure_source: bool
112
+ :param path: The path to the local file. Can be provided as a string or Path object.
113
+ :type path: Union[Path, str]
114
+ :param is_hashable: A flag indicating whether the file hash needs to be calculated.
115
+ :type is_hashable: bool
116
+ :param in_memory: Whether to store the compressed file content in memory. Defaults to True.
117
+ :type in_memory: bool
118
+
119
+ :return: A FileStack object containing FileInstances for the file.
120
+ :rtype: FileStack
121
+ """
122
+ # TODO make a second version where a unique directory is already there
123
+ source_path = path if isinstance(path, Path) else Path(path)
124
+
125
+ # Check if the source exists
126
+ if not source_path.exists():
127
+ logger.error(f"Source file not found: {source_path}")
128
+ raise FileNotFoundError(f"Source file not found: {source_path}")
129
+
130
+ # Check if it's a file (not a directory)
131
+ if not source_path.is_file():
132
+ logger.error(f"Source file {path} is not a file")
133
+ raise ValueError(f"Source file is not a file: {path}")
134
+
135
+ # Check read permission using os.access
136
+ if not os.access(path, os.R_OK):
137
+ logger.error(f"No read permission for file: {path}")
138
+ raise ValueError(f"No read permission for file: {path}")
139
+
140
+ content = None
141
+ file_hash = hash_file(source_path) if is_hashable else None
142
+ name = source_path.name
143
+ size = source_path.stat().st_size if source_path.exists() else None
144
+
145
+ if in_memory:
146
+ try:
147
+ # Read the file content
148
+ with open(source_path, "rb") as f:
149
+ file_content = f.read()
150
+ # Compress the content using zlib
151
+ content = zlib.compress(file_content)
152
+ if task_id == "":
153
+ logger.debug(
154
+ f"Compressed file {source_path} from {len(file_content)} bytes to {len(content)} bytes"
155
+ )
156
+ else:
157
+ logger.debug(
158
+ f"task_id: {task_id} Compressed file {source_path} from {len(file_content)} bytes to {len(content)} bytes"
159
+ )
160
+ # Check if compressed content exceeds MongoDB document size limit
161
+ if len(content) > 0.9 * MONGODB_MAX_DOCUMENT_SIZE:
162
+ if task_id == "":
163
+ logger.error(
164
+ f"Compressed content size {len(content)} bytes exceeds MongoDB limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes for file {source_path}"
165
+ )
166
+ logger.error(
167
+ f"Setting in_memory to False for file {source_path} and clear content"
168
+ )
169
+ else:
170
+ logger.error(
171
+ f"task_id: {task_id} Compressed content size {len(content)} bytes exceeds MongoDB limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes for file {source_path}"
172
+ )
173
+ logger.error(
174
+ f"task_id: {task_id} Setting in_memory to False for file {source_path} and clear content"
175
+ )
176
+ in_memory = False
177
+ content = None
178
+ except Exception as e:
179
+ logger.warning(f"Failed to compress file {source_path}: {e}")
180
+ raise
181
+
182
+ file_stack = cls(
183
+ name=name,
184
+ in_memory=in_memory,
185
+ content=content,
186
+ is_hashable=is_hashable,
187
+ size=size,
188
+ hash=file_hash,
189
+ )
190
+
191
+ if not in_memory:
192
+ location = FileInstance.from_local_file(
193
+ path=path,
194
+ file_stack_id=file_stack.id,
195
+ make_copy=not secure_source and not in_memory,
196
+ )
197
+ file_stack.locations.append(location)
198
+
199
+ return file_stack
200
+
201
+ def complex_hash(self) -> str:
202
+ if self.is_hashable:
203
+ if self.hash:
204
+ return self.hash
205
+ else:
206
+ raise ValueError("FileStack is hashable but hash is not set.")
207
+ # elif self.in_memory and self.content:
208
+ # # If the content is in memory, hash the compressed content
209
+ # return complex_hash_function(zlib.decompress(self.content))
210
+ # else:
211
+ # temp_dir = Path(tempfile.mkdtemp())
212
+ # local_file = self.get(None, local_dir=temp_dir)
213
+ # return complex_hash_function(local_file.read_bytes())
214
+ else:
215
+ logger.warning(
216
+ f"FileStack {self.id} is not hashable, returning unique hash."
217
+ )
218
+ return str(ObjectId())
219
+
220
+ def append(self, file_instance: FileInstance) -> None:
221
+ """
222
+ Appends a FileInstance to the file stack.
223
+
224
+ :param file_instance: The FileInstance to append.
225
+ :type file_instance: FileInstance
226
+ """
227
+ self.locations.append(file_instance)
228
+
229
+ def get(self, local_dir: Path | None = None) -> Path:
230
+ """
231
+ Copies the file stack to a local directory. This is the version to be used in applications
232
+
233
+ :param local_dir: The local directory to copy the file stack to.
234
+ :type local_dir: Path
235
+ """
236
+ from simstack.core.context import context
237
+
238
+ return self.get_raw(context.config.resource, local_dir)
239
+
240
+ def get_raw(self, local_resource: Resource, local_dir: Path | None = None) -> Path:
241
+ """
242
+ Copies the file stack to a local directory, assumes no context.
243
+
244
+ :param local_resource: the local resource to copy the file stack to. Defaults to the current resource.
245
+ :param local_dir: The local directory to copy the file stack to.
246
+ :type local_dir: Path
247
+ """
248
+
249
+ # select the best instance
250
+ # first search for an instance with "in_memory" set to True
251
+
252
+ if local_dir is None:
253
+ local_dir = Path.cwd()
254
+
255
+ if self.in_memory:
256
+ local_dir.mkdir(parents=True, exist_ok=True)
257
+ try:
258
+ if self.content is None:
259
+ raise ValueError("FileStack is in-memory but content is missing.")
260
+ file_name = self.name or "file"
261
+ # Decompress the content
262
+ decompressed_content = zlib.decompress(self.content)
263
+ # Write the decompressed content to the local directory
264
+ target_path = local_dir / file_name
265
+ if target_path.exists(): # the user must make sure that the file does not exist, otherwise the file will be overwritten
266
+ raise FileExistsError(f"File {target_path} already exists.")
267
+ with open(target_path, "wb") as f:
268
+ f.write(decompressed_content)
269
+ return target_path
270
+ except Exception as e:
271
+ logger.error(f"Failed to decompress and write file {self.name}: {e}")
272
+ raise ValueError(
273
+ f"Failed to decompress and write file {self.name}: {e}"
274
+ )
275
+
276
+ local_resource_name = transfer_resource_name(local_resource)
277
+ local_transfer_resource = Resource(value=local_resource_name)
278
+
279
+ # If in-memory instance not found or decompression failed, try finding instance on same resource
280
+ same_resource_instance = next(
281
+ (
282
+ f
283
+ for f in self.locations
284
+ if transfer_resource_name(f.resource) == local_resource_name
285
+ and getattr(f, "status", "available") == "available"
286
+ ),
287
+ None,
288
+ )
289
+
290
+ if same_resource_instance is not None:
291
+ same_resource_instance.last_accessed_at = datetime.now()
292
+ # Return the absolute path by joining with the resource's workdir if it's relative
293
+ path = Path(same_resource_instance.path)
294
+ if not path.is_absolute():
295
+ from simstack.core.context import context
296
+
297
+ return Path(context.config.workdir) / path
298
+ logger.info(f"Using existing instance {path} for {self.name}")
299
+ return path
300
+
301
+ remote_instance = first_available_remote_location(
302
+ self.locations, local_transfer_resource
303
+ )
304
+ if remote_instance is None:
305
+ logger.error("No suitable file instance found for copying.")
306
+ raise ValueError(
307
+ f"FileStack {self.id} {self.name} is unavailable: no accessible file instance for resource {local_transfer_resource}."
308
+ )
309
+
310
+ local_dir.mkdir(parents=True, exist_ok=True)
311
+ return self._get_via_server_transfer(
312
+ remote_instance, local_transfer_resource, local_dir
313
+ )
314
+
315
+ def _get_via_server_transfer(
316
+ self,
317
+ remote_instance: FileInstance,
318
+ local_resource: Resource,
319
+ local_dir: Path,
320
+ ) -> Path:
321
+ from simstack.core.context import context
322
+
323
+ client = FileTransferClient.from_context(required=True)
324
+ assert client is not None
325
+
326
+ transfer = client.create_transfer(
327
+ file_stack_id=str(self.id),
328
+ source_file_instance_id=getattr(remote_instance, "id", None),
329
+ source_resource_name=transfer_resource_name(remote_instance.resource),
330
+ target_resource_name=transfer_resource_name(local_resource),
331
+ )
332
+ transfer_id = str(transfer["transfer_id"])
333
+ logger.info(
334
+ "Created FileStack transfer %s for file_stack=%s from resource=%s to resource=%s",
335
+ transfer_id,
336
+ self.id,
337
+ transfer_resource_name(remote_instance.resource),
338
+ transfer_resource_name(local_resource),
339
+ )
340
+
341
+ try:
342
+ client.wait_until_uploaded(transfer_id)
343
+ except Exception as exc:
344
+ try:
345
+ client.fail_transfer(
346
+ transfer_id,
347
+ error_message=str(exc),
348
+ error_code="SOURCE_RUNNER_UNAVAILABLE",
349
+ )
350
+ except Exception:
351
+ logger.warning(
352
+ "Failed to report failed FileStack transfer %s", transfer_id
353
+ )
354
+ raise
355
+ target_path = local_dir / (self.name or Path(str(remote_instance.path)).name)
356
+ if target_path.exists():
357
+ raise FileExistsError(f"File {target_path} already exists.")
358
+ downloaded = client.download_file(transfer_id, target_path)
359
+ instance_path = path_for_file_instance(downloaded.path, context.config.workdir)
360
+ completed = client.complete_transfer(
361
+ transfer_id=transfer_id,
362
+ target_resource_name=transfer_resource_name(local_resource),
363
+ target_path=instance_path,
364
+ size_bytes=downloaded.size_bytes,
365
+ checksum_sha256=downloaded.checksum_sha256,
366
+ )
367
+
368
+ file_instance_id = completed.get("file_instance_id")
369
+ existing = next(
370
+ (
371
+ location
372
+ for location in self.locations
373
+ if getattr(location, "id", None) == file_instance_id
374
+ ),
375
+ None,
376
+ )
377
+ if existing is None:
378
+ self.locations.append(
379
+ FileInstance(
380
+ id=str(file_instance_id) if file_instance_id else None,
381
+ path=instance_path,
382
+ resource=local_resource,
383
+ created_at=datetime.now(),
384
+ size_bytes=downloaded.size_bytes,
385
+ checksum_sha256=downloaded.checksum_sha256,
386
+ location_type="local_path",
387
+ is_cached=True,
388
+ status="available",
389
+ )
390
+ )
391
+
392
+ return downloaded.path
393
+
394
+ def str(self) -> str:
395
+ return f"FileStack(name={self.name}, size={self.size}, is_hashable={self.is_hashable}, in_memory={self.in_memory}, locations={self.locations})"
396
+
397
+
398
+ class FileGetterArgs(Model):
399
+ file_stack: FileStack = Reference()
400
+ local_resource: Resource
401
+ local_dir: Path
402
+
403
+
404
+ async def main() -> None:
405
+ from simstack.core.context import context
406
+
407
+ await context.initialize()
408
+
409
+ # write a file test.txt
410
+ with open("test.txt", "w") as f:
411
+ f.write("Hello World")
412
+ file_stack = FileStack.from_local_file("test.txt", is_hashable=True, in_memory=True)
413
+ print(file_stack)
414
+ await context.db.save(file_stack)
415
+ local_dir = Path(context.config.workdir) / "samira" / str(file_stack.id)
416
+ retrieved = file_stack.get(local_dir=local_dir)
417
+ print("Retrieved file path:", retrieved)
418
+
419
+
420
+ if __name__ == "__main__":
421
+ import asyncio
422
+
423
+ logging.basicConfig(level=logging.DEBUG)
424
+ asyncio.run(main())
@@ -0,0 +1,19 @@
1
+ from typing import Dict, Any
2
+ from odmantic import Model, Field
3
+ from simstack.models.simstack_model import simstack_model
4
+
5
+ @simstack_model
6
+ class FireAndForgetResult(Model):
7
+ """
8
+ Model representing the result of a fire-and-forget task.
9
+
10
+ Attributes:
11
+ call_path: The full call path of the node.
12
+ models: A dictionary mapping argument/result names to their values.
13
+ success: Whether the task was successful.
14
+ next_step: Whether this result triggers a next step.
15
+ """
16
+ call_path: str = Field(description="The full call path of the node")
17
+ models: Dict[str, Any] = Field(description="A dictionary mapping argument/result names to their values")
18
+ success: bool = Field(description="Whether the task was successful")
19
+ next_step: bool = Field(default=False, description="Whether this result triggers a next step")