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,217 @@
1
+ import os
2
+ import re
3
+ import stat
4
+ import subprocess
5
+ from pathlib import Path
6
+
7
+ from simstack.core.context import context
8
+ from simstack.core.definitions import TaskStatus
9
+ from simstack.models import NodeRegistry
10
+ from simstack.util.submit_to_watchdog import submit_to_watchdog
11
+ import logging
12
+
13
+ logger = logging.getLogger("submit_node")
14
+
15
+
16
+ def make_executable(file_path: str | os.PathLike[str]) -> None:
17
+ # Get current permissions
18
+ current_permissions = os.stat(file_path).st_mode
19
+
20
+ # Add executable bit for user, group and others
21
+ executable_mode = current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
22
+
23
+ # Apply new permissions
24
+ os.chmod(file_path, executable_mode)
25
+
26
+
27
+ async def submit_node(registry_entry: NodeRegistry) -> bool:
28
+ """Submit a node to the SLURM queue"""
29
+ task_id = registry_entry.id
30
+ try:
31
+ logger.info(f"Submitting task_id: {task_id} to SLURM queue")
32
+ # Implement SLURM submission logic here
33
+ base_path = context.config.project_root
34
+
35
+ python_path = ":".join(str(path) for path in context.config.python_paths)
36
+ work_dir = context.config.workdir / registry_entry.name / str(registry_entry.id)
37
+ job_name = registry_entry.name + "." + str(registry_entry.id)
38
+
39
+ logger.info(f"task_id: {task_id} workdir {work_dir} python path {python_path}")
40
+ # write a slurm script that starts a python script "run_node.py" with the node id as an argument
41
+
42
+ original_slurm_parameters = registry_entry.parameters.slurm_parameters
43
+ if original_slurm_parameters is None:
44
+ logger.error(f"Task task_id: {task_id} has no slurm parameters -- failing")
45
+ registry_entry.status = TaskStatus.FAILED
46
+ await context.db.save(registry_entry)
47
+ return False
48
+ slurm_parameters = original_slurm_parameters.model_copy(deep=True)
49
+ slurm_parameters.output = f"{work_dir}/%j.out"
50
+ slurm_parameters.error = f"{work_dir}/%j.err"
51
+ slurm_parameters.job_name = f"{job_name}"
52
+ slurm_parameters.chdir = str(work_dir)
53
+
54
+ slurm_parameters.startup_commands.append("source ~/.bashrc")
55
+ slurm_parameters.startup_commands.append(f"{context.config.environment_start}")
56
+ slurm_parameters.startup_commands.append(
57
+ f"export PYTHONPATH={python_path}:$PYTHONPATH"
58
+ )
59
+
60
+ sync_data_start = """# 1. Define the rsync function
61
+ sync_data() {
62
+ echo "=== Time limit approaching! Starting rsync at $(date) ==="
63
+
64
+ # Run rsync safely
65
+ """
66
+ sync_data_end = """
67
+ echo "=== Rsync completed at $(date) ==="
68
+ exit 0
69
+ }
70
+ """
71
+
72
+ if context.resource_config is not None:
73
+ program_config = context.resource_config.get_program(registry_entry.name)
74
+ else:
75
+ program_config = {}
76
+
77
+ logger.info(f"task_id: {task_id} program_config {program_config}")
78
+ if program_config.get("use_tmp", False):
79
+ tmp_dir = context.resource_config.tmp_dir(registry_entry.id)
80
+
81
+ if not tmp_dir.exists() or not tmp_dir.is_dir():
82
+ logger.error(
83
+ f"Task task_id: {task_id} tmp_dir {tmp_dir} does not exist or is not a directory -- failing")
84
+ registry_entry.status = TaskStatus.FAILED
85
+ await context.db.save(registry_entry)
86
+ return False
87
+
88
+ full_sync_data = sync_data_start + f'cp -a {tmp_dir}/. {work_dir}/recovery\n' + sync_data_end
89
+
90
+ logger.info(f"task_id: {task_id} full_sync_data {full_sync_data}")
91
+ slurm_parameters.startup_commands.append(full_sync_data)
92
+ slurm_parameters.startup_commands.append("trap 'sync_data' SIGUSR1")
93
+
94
+ if context.config.docker:
95
+ external_work_dir = (
96
+ context.config.external_workdir
97
+ / registry_entry.name
98
+ / str(registry_entry.id)
99
+ )
100
+
101
+ watcher_file = (
102
+ context.config.project_root
103
+ / "src"
104
+ / "simstack"
105
+ / "util"
106
+ / "queue_watcher.py"
107
+ )
108
+ with open(watcher_file, "r") as f:
109
+ watcher_content = f.read()
110
+ slurm_parameters.startup_commands.append(
111
+ f"cat > watcher.py <<'EOF'\n{watcher_content}\nEOF"
112
+ )
113
+ slurm_parameters.startup_commands.append("python watcher.py &")
114
+ slurm_parameters.startup_commands.append("WATCHER_PID=$!")
115
+
116
+ slurm_parameters.startup_commands.append("set -euo pipefail")
117
+ slurm_parameters.startup_commands.append(
118
+ "cleanup() { "
119
+ 'if [ -n "${WATCHER_PID:-}" ] && kill -0 "$WATCHER_PID" 2>/dev/null; then '
120
+ ' kill "$WATCHER_PID" 2>/dev/null || true; '
121
+ " sleep 2; "
122
+ ' kill -9 "$WATCHER_PID" 2>/dev/null || true; '
123
+ "fi; "
124
+ "}"
125
+ )
126
+
127
+ slurm_parameters.startup_commands.append("trap cleanup EXIT INT TERM")
128
+ toml_path = context.config.project_root / "simstack_docker.toml"
129
+ if not toml_path.exists():
130
+ logger.error(
131
+ f"Task task_id: {task_id} has no simstack.toml file -- failing"
132
+ )
133
+ registry_entry.status = TaskStatus.FAILED
134
+ await context.db.save(registry_entry)
135
+ return False
136
+
137
+ docker_start = "udocker run "
138
+ docker_start += "-e GIT_TOKEN=XXX"
139
+ docker_start += f"-e NODE_ID={registry_entry.id} "
140
+ docker_start += f"-e RESOURCE={str(context.config.resource)} "
141
+ docker_start += f"-v {external_work_dir}:/home/appuser/simstack "
142
+ docker_start += (
143
+ f"-v {context.config.external_source_dir}:/app/simstack-model "
144
+ )
145
+ docker_start += f"-v {toml_path}:/app/simstack-model/simstack.toml "
146
+ docker_start += " simstack-runner"
147
+ slurm_parameters.startup_commands.append(docker_start)
148
+ else:
149
+ slurm_parameters.startup_commands.append(
150
+ f"uv run --directory {base_path} run_node --node-id {registry_entry.id} --resource {str(context.config.resource)} &"
151
+ )
152
+ slurm_parameters.startup_commands.append("wait")
153
+ slurm_parameters.signal = "B:SIGUSR1@60"
154
+
155
+
156
+ slurm_script = slurm_parameters.to_sbatch_header()
157
+
158
+ # write the script to a file in the work_dir
159
+ os.makedirs(work_dir, exist_ok=True)
160
+ logger.info(f"task_id: {task_id} workdir {work_dir} python path {python_path}")
161
+ script_path = os.path.join(work_dir, "slurm_script.sh")
162
+ with open(script_path, "w") as f:
163
+ f.write(slurm_script)
164
+
165
+ make_executable(script_path)
166
+ # submit the script to the slurm queue
167
+ if context.config.docker:
168
+ job_id = "slurm_" + str(registry_entry.id)
169
+ queue_dir = context.config.workdir / "queue"
170
+ result = submit_to_watchdog(
171
+ f"/usr/bin/sbatch {os.path.join(external_work_dir, 'slurm_script.sh')}",
172
+ job_id,
173
+ queue_dir,
174
+ )
175
+ else:
176
+ result = subprocess.run(
177
+ f"/usr/bin/sbatch {os.path.join(work_dir, 'slurm_script.sh')}",
178
+ shell=True,
179
+ capture_output=True,
180
+ text=True,
181
+ timeout=30, # Add timeout to prevent hanging
182
+ )
183
+
184
+ logger.info(
185
+ f"submitting job task_id: {task_id} returns: {result.returncode} {result.stdout} {result.stderr}"
186
+ )
187
+ if result.returncode == 0:
188
+ # Extract job ID using a regex pattern
189
+ match = re.search(r"Submitted batch job (\d+)", result.stdout)
190
+ if match:
191
+ job_id = match.group(1)
192
+ logger.info(
193
+ f"task_id: {task_id} job successfully submitted with job_id: {job_id}"
194
+ )
195
+ registry_entry.job_id = job_id
196
+ else:
197
+ logger.warning(
198
+ f"task_id: {task_id} job submitted but could not extract job_id from output: {result.stdout}"
199
+ )
200
+ else:
201
+ logger.error(
202
+ f"error submitting job for task_id: {task_id} return code: {result.returncode} stdout: {result.stdout}"
203
+ )
204
+ logger.error(
205
+ f"submitting job for task_id: {task_id} stderr: {result.stderr}"
206
+ )
207
+ registry_entry.status = TaskStatus.FAILED
208
+ await context.db.save(registry_entry)
209
+ return False
210
+ registry_entry.status = TaskStatus.SLURM_QUEUED
211
+ await context.db.save(registry_entry)
212
+ return True
213
+ except Exception as e:
214
+ logger.exception(f"fatal error in submitting task_id: {task_id} {str(e)}")
215
+ registry_entry.status = TaskStatus.FAILED
216
+ await context.db.save(registry_entry)
217
+ return False
@@ -0,0 +1,22 @@
1
+ from contextvars import ContextVar
2
+ from typing import Optional
3
+
4
+ from bson.objectid import ObjectId
5
+
6
+ # this variable is meant to give access to the task id to user functions that do not have access to it (e.g. for logging)
7
+ _task_id: ContextVar[Optional[ObjectId]] = ContextVar("task_id", default=None)
8
+
9
+
10
+ def get_task_id() -> Optional[ObjectId]:
11
+ """Get the current task ID."""
12
+ return _task_id.get()
13
+
14
+
15
+ def set_task_id(task_id: Optional[ObjectId]) -> None:
16
+ """Set the current task ID."""
17
+ _task_id.set(task_id)
18
+
19
+
20
+ def clear_task_id() -> None:
21
+ """Clear the current task ID."""
22
+ _task_id.set(None)
File without changes
@@ -0,0 +1,292 @@
1
+ """Disabled archive prototype retained for a future, separately reviewed change.
2
+
3
+ The public entrypoints intentionally fail closed. Permanent storage ownership and
4
+ safe source cleanup must be designed and covered by production-path tests before
5
+ this module can be enabled.
6
+ """
7
+
8
+ import inspect
9
+ import shutil
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+
14
+ from odmantic import Model, Field
15
+
16
+ from simstack.core.context import context
17
+ from simstack.core.node import node
18
+ from simstack.models import FileStack, FileList, simstack_model
19
+ from simstack.models.base_lists import BooleanDataList
20
+ from simstack.models.file_instance import FileInstance
21
+
22
+
23
+ ARCHIVE_DISABLED_MESSAGE = (
24
+ "File archiving is disabled for this migration until permanent-resource "
25
+ "semantics and non-destructive cleanup are implemented and tested."
26
+ )
27
+
28
+
29
+ class ArchiveFeatureDisabledError(RuntimeError):
30
+ """Raised when code tries to use the excluded archive prototype."""
31
+
32
+
33
+ def _raise_archive_disabled() -> None:
34
+ raise ArchiveFeatureDisabledError(ARCHIVE_DISABLED_MESSAGE)
35
+
36
+
37
+ def archive_one_file(file_stack: FileStack,**kwargs):
38
+ _raise_archive_disabled()
39
+ node_runner = kwargs["node_runner"]
40
+ node_runner.info(f"Archiving file: {file_stack.name}")
41
+
42
+ config = context.resource_config.get_program("archive_one_file")
43
+ if config.get("archive_path", None) is None:
44
+ raise ValueError("archive_path not set in config")
45
+ archive_path = Path(config.get("archive_path"))
46
+ full_path = archive_path / str(file_stack.id)
47
+ full_path.mkdir(parents=True, exist_ok=True)
48
+ if full_path.exists() and full_path.is_dir():
49
+ try:
50
+ archive_location = file_stack.get(local_dir=full_path)
51
+ archive_file_instance = FileInstance.from_local_file(archive_location)
52
+ node_runner.info(f"Archived file to {archive_file_instance.path}")
53
+ file_stack.locations.append(archive_file_instance)
54
+ return True
55
+ except Exception as e:
56
+ node_runner.error(f"Error archiving file: {str(e)}")
57
+ return False
58
+ else:
59
+ raise ValueError(f"Archive path {archive_path} does not exist or is not a directory")
60
+
61
+ @node
62
+ async def archive_file(file_stack: FileStack,**kwargs):
63
+ _raise_archive_disabled()
64
+ return archive_one_file(file_stack, **kwargs)
65
+
66
+ @node
67
+ def archive_files(file_list: FileList,**kwargs):
68
+ _raise_archive_disabled()
69
+ archive_results = BooleanDataList(field_name="archive_result")
70
+ for file_stack in file_list:
71
+ archive_results.append(archive_one_file(file_stack, **kwargs))
72
+ return archive_results
73
+
74
+
75
+ @simstack_model
76
+ class ArchiveConfig(Model):
77
+ call_paths: List[str] = Field(default_factory=list)
78
+ archive_resource: str = Field(default="archive_resource", description="Name of the resource where the files will be archived")
79
+ in_memory: bool = Field(default=False, description="Whether to archive files in memory")
80
+ use_time_window: bool = Field(default=False, description="Whether to archive files within a time window")
81
+ start_date: Optional[datetime] = Field(default=None, description="Start of the period to archive files for")
82
+ end_date: Optional[datetime] = Field(default=None, description="End end of the period to archive files for")
83
+ include_patterns: List[str] = Field(default_factory=list, description="List of file patterns to include")
84
+ exclude_patterns: List[str] = Field(default_factory=list, description="List of file patterns to exclude")
85
+ min_size: int = Field(default=0, description="Minimum size of files to archive")
86
+ filter_by_resource: bool = Field(default=False, description="Whether to only archive files that have a location on the current resource")
87
+
88
+ model_config = {"collection": "archive_config"}
89
+
90
+
91
+ @node
92
+ async def archive_node(archive_config: ArchiveConfig, **kwargs):
93
+ """
94
+ Archives FileStacks matching the criteria in archive_config and deletes local instances upon success.
95
+
96
+ Args:
97
+ archive_config (ArchiveConfig): Configuration for the archive process.
98
+ **kwargs: Additional arguments.
99
+ """
100
+ _raise_archive_disabled()
101
+
102
+ with open("TEST_EXECUTION.log", "a") as f:
103
+ f.write(f"archive_node CALLED at {datetime.now()}\n")
104
+
105
+ node_runner = kwargs["node_runner"]
106
+ db = context.db
107
+ my_resource = context.config.resource
108
+
109
+ # Discover FileStacks
110
+ all_file_stacks = []
111
+ import sys
112
+ sys.stderr.write(f"DEBUG: Discovery starting for resource {my_resource}\n")
113
+ sys.stderr.flush()
114
+ if archive_config.call_paths:
115
+ from simstack.models import NodeRegistry, FileListModel
116
+
117
+ # Find matching NodeRegistry entries
118
+ node_query = {
119
+ "call_path": {"$in": archive_config.call_paths},
120
+ "parameters.resource.value": my_resource
121
+ }
122
+ matching_nodes = await db.find(NodeRegistry, node_query)
123
+
124
+ # We also need mappings to know which collection to look into
125
+ # context.model_mappings has by_mapping and by_name
126
+ filestack_mapping = context.model_mappings.get_by_name("FileStack")
127
+ filelist_mapping = context.model_mappings.get_by_name("FileList")
128
+ filelistmodel_mapping = context.model_mappings.get_by_name("FileListModel")
129
+
130
+ fs_mapping_str = filestack_mapping.mapping if filestack_mapping else ""
131
+ fl_mapping_str = filelist_mapping.mapping if filelist_mapping else ""
132
+ flm_mapping_str = filelistmodel_mapping.mapping if filelistmodel_mapping else ""
133
+
134
+ found_fs_ids = set()
135
+ found_fl_ids = set()
136
+ found_flm_ids = set()
137
+
138
+ for node in matching_nodes:
139
+ # Check info_files
140
+ for fs in node.info_files:
141
+ found_fs_ids.add(fs.id)
142
+
143
+ # Check input_references and results_references
144
+ for ref in node.input_references + node.results_references:
145
+ if ref.variable_mapping == fs_mapping_str:
146
+ found_fs_ids.add(ref.reference)
147
+ elif ref.variable_mapping == fl_mapping_str:
148
+ found_fl_ids.add(ref.reference)
149
+ elif ref.variable_mapping == flm_mapping_str:
150
+ found_flm_ids.add(ref.reference)
151
+
152
+ # Retrieve FileStacks from found IDs
153
+ if found_fs_ids:
154
+ file_stacks = await db.find(FileStack, {"_id": {"$in": list(found_fs_ids)}})
155
+ all_file_stacks.extend(file_stacks)
156
+
157
+ # Retrieve FileLists and their FileStacks
158
+ if found_flm_ids:
159
+ file_list_models = await db.find(FileListModel, {"_id": {"$in": list(found_flm_ids)}})
160
+ for flm in file_list_models:
161
+ # FileListModel uses ObjectListMixin[FileStack], elements are ObjectIds
162
+ # We need to fetch the actual FileStack objects.
163
+ if flm.elements:
164
+ stacks = await db.find(FileStack, {"_id": {"$in": flm.elements}})
165
+ all_file_stacks.extend(stacks)
166
+
167
+ # Deduplicate all_file_stacks by ID
168
+ unique_stacks = {}
169
+ for fs in all_file_stacks:
170
+ unique_stacks[fs.id] = fs
171
+ all_file_stacks = list(unique_stacks.values())
172
+
173
+ else:
174
+ # Existing logic: Query FileStack collection directly
175
+ query = {}
176
+ if archive_config.min_size > 0:
177
+ query["size"] = {"$gte": archive_config.min_size}
178
+
179
+ if archive_config.filter_by_resource:
180
+ query["locations.resource.value"] = my_resource
181
+
182
+ all_file_stacks = await db.find(FileStack, query)
183
+
184
+ print(f"DEBUG: Found {len(all_file_stacks)} FileStacks")
185
+ filtered_file_stacks = []
186
+
187
+ for fs in all_file_stacks:
188
+ # Filter by date if specified
189
+ # Since FileStack doesn't have a date, we check its locations.
190
+ if archive_config.start_date or archive_config.end_date:
191
+ valid_date = False
192
+ for loc in fs.locations:
193
+ if loc.created_at:
194
+ if archive_config.start_date and loc.created_at < archive_config.start_date:
195
+ continue
196
+ if archive_config.end_date and loc.created_at > archive_config.end_date:
197
+ continue
198
+ valid_date = True
199
+ break
200
+ if not valid_date:
201
+ continue
202
+
203
+ # Filter by patterns
204
+ if archive_config.include_patterns:
205
+ import fnmatch
206
+ if not any(fnmatch.fnmatch(fs.name, pat) for pat in archive_config.include_patterns):
207
+ continue
208
+
209
+ if archive_config.exclude_patterns:
210
+ import fnmatch
211
+ if any(fnmatch.fnmatch(fs.name, pat) for pat in archive_config.exclude_patterns):
212
+ continue
213
+
214
+ filtered_file_stacks.append(fs)
215
+
216
+ if not filtered_file_stacks:
217
+ node_runner.info("No files found matching the archival criteria.")
218
+ return BooleanDataList(field_name="archive_results")
219
+
220
+ # 2. Add them to FileList
221
+ archive_file_list = FileList(field_name="archive_files")
222
+ for fs in filtered_file_stacks:
223
+ archive_file_list.append(fs)
224
+
225
+ print(f"DEBUG: archive_file_list has {len(archive_file_list)} files")
226
+ # 3. Call archive_files on the filelist
227
+ # Note: archive_files is a @node, we can call it directly.
228
+ # It returns a BooleanDataList
229
+
230
+ # To handle mocks and different return types, we ensure we get a BooleanDataList
231
+ # When calling a @node decorated function, it returns a SimstackResult or Model in sync mode,
232
+ # or the result of the function if it was run locally.
233
+ # If we are in the same process and calling it, it might be running via NodeRunner.
234
+ raw_results = await archive_files(archive_file_list, **kwargs)
235
+ print(f"DEBUG: raw_results type={type(raw_results)}")
236
+ if isinstance(raw_results, BooleanDataList):
237
+ archive_results = raw_results
238
+ else:
239
+ # If mocked or returned as list/iterable
240
+ archive_results = BooleanDataList(field_name="archive_results")
241
+ from simstack.models import BooleanData
242
+ if raw_results is not None:
243
+ # Handle possible async result if not awaited properly by decorator
244
+ if inspect.isawaitable(raw_results):
245
+ raw_results = await raw_results
246
+
247
+ for r in raw_results:
248
+ if isinstance(r, bool):
249
+ archive_results.append(BooleanData(value=r))
250
+ elif isinstance(r, BooleanData):
251
+ archive_results.append(r)
252
+ else:
253
+ # try to save it if it's a model
254
+ archive_results.append(r)
255
+
256
+ # 4. Delete local FileInstances for successfull archival
257
+ # archive_results is a BooleanDataList, elements are BooleanData (presumably)
258
+ # Wait, archive_files returns BooleanDataList. Let's check BooleanDataList implementation.
259
+
260
+ # Actually archive_files returns BooleanDataList where each element corresponds to a FileStack in archive_file_list
261
+ for i, success_data in enumerate(archive_results):
262
+ if success_data.value:
263
+ fs = archive_file_list[i]
264
+ # Delete local FileInstances
265
+ # We need to know which ones are "local".
266
+ # Usually those are on the current resource.
267
+ current_resource = context.config.resource
268
+
269
+ remaining_locations = []
270
+ for loc in fs.locations:
271
+ # If it's on the current resource and is a local path, delete it
272
+ if loc.resource.value == current_resource and loc.location_type == "local_path":
273
+ try:
274
+ local_path = Path(context.config.workdir) / loc.path
275
+ if local_path.exists():
276
+ if local_path.is_file():
277
+ local_path.unlink()
278
+ elif local_path.is_dir():
279
+ shutil.rmtree(local_path)
280
+ node_runner.info(f"Deleted local instance of {fs.name} at {local_path}")
281
+ else:
282
+ node_runner.warning(f"Local path {local_path} for {fs.name} does not exist")
283
+ except Exception as e:
284
+ node_runner.error(f"Failed to delete local instance of {fs.name}: {e}")
285
+ remaining_locations.append(loc)
286
+ else:
287
+ remaining_locations.append(loc)
288
+
289
+ fs.locations = remaining_locations
290
+ await db.save(fs)
291
+
292
+ return archive_results
@@ -0,0 +1,113 @@
1
+ import asyncio
2
+ import inspect
3
+ from typing import List, Callable, Any, Optional
4
+
5
+ from odmantic import Model
6
+
7
+ from simstack.core.context import context
8
+ from simstack.core.definitions import TaskStatus
9
+ from simstack.core.simstack_result import SimstackResult
10
+ from simstack.core.node_runner import NodeRunner
11
+ from simstack.core.process_results import process_result_helper
12
+ from simstack.models import FireAndForgetResult
13
+
14
+ class FireAndForgetRunner(NodeRunner):
15
+ def __init__(self, node: Callable[..., Any], max_concurrency: Optional[int] = None, **kwargs):
16
+ # We need to handle the case where "node_runner" might be in kwargs, just like MassRunner does.
17
+ # However, NodeRunner.__init__ expects (name, task_id, logger).
18
+ # MassRunner does: super().__init__(kwargs["node_runner"].name, kwargs["node_runner"].task_id, kwargs["node_runner"].logger )
19
+
20
+ node_runner = kwargs.get("node_runner")
21
+ if node_runner:
22
+ super().__init__(node_runner.name, node_runner.task_id, node_runner.logger)
23
+ self.call_path = getattr(node_runner, "call_path", "NA")
24
+ else:
25
+ # Fallback if not provided, though it's expected in this context
26
+ super().__init__(kwargs.get("name", "FireAndForgetRunner"), kwargs.get("task_id", "NA"), kwargs.get("logger"))
27
+ self.call_path = "NA"
28
+
29
+ self._node = node
30
+ self._max_concurrency = max_concurrency
31
+ self._semaphore = asyncio.Semaphore(max_concurrency) if max_concurrency else None
32
+ self._tasks = []
33
+ self._kwargs = kwargs.copy()
34
+
35
+ async def _run_node(self, args: List[Model]):
36
+ sig = inspect.signature(self._node)
37
+ param_names = list(sig.parameters.keys())
38
+
39
+ node_name = getattr(self._node, "__name__", "unknown_node")
40
+ full_call_path = f"{self.call_path}/{node_name}"
41
+
42
+ models_dict = {}
43
+ for i, m in enumerate(args):
44
+ if i < len(param_names):
45
+ models_dict[f"arg_{param_names[i]}"] = m
46
+ else:
47
+ models_dict[f"arg_{i}"] = m
48
+
49
+ success = False
50
+
51
+ try:
52
+ if self._semaphore:
53
+ async with self._semaphore:
54
+ if asyncio.iscoroutinefunction(self._node):
55
+ result = await self._node(*args, **self._kwargs)
56
+ else:
57
+ result = self._node(*args, **self._kwargs)
58
+ else:
59
+ if asyncio.iscoroutinefunction(self._node):
60
+ result = await self._node(*args, **self._kwargs)
61
+ else:
62
+ result = self._node(*args, **self._kwargs)
63
+
64
+ if result is None:
65
+ success = False
66
+ elif isinstance(result, bool):
67
+ success = result
68
+ elif isinstance(result, (SimstackResult, Model)):
69
+ references, models = await process_result_helper(result, self.task_id)
70
+ if isinstance(result, SimstackResult):
71
+ success = (result.status == TaskStatus.COMPLETED)
72
+ else:
73
+ success = True
74
+
75
+ if references:
76
+ for reference, model in zip(references, models):
77
+ models_dict[f"result_{reference.variable_name}"] = model
78
+ elif isinstance(result, (list, tuple)) and all(isinstance(m, Model) for m in result):
79
+ success = True
80
+ for i, m in enumerate(result):
81
+ models_dict[f"result_{i}"] = m
82
+ else:
83
+ # Other types of results
84
+ success = True
85
+
86
+ except Exception as e:
87
+ self.error(f"Error running node in FireAndForgetRunner: {e}")
88
+ success = False
89
+ models_dict["error"] = str(e)
90
+
91
+ # Immediately write to DB
92
+ ff_result = FireAndForgetResult(
93
+ call_path=full_call_path,
94
+ models=models_dict,
95
+ success=success
96
+ )
97
+
98
+ db = context.db
99
+ await db.save(ff_result)
100
+ return ff_result
101
+
102
+ def create_tasks(self, *args: Model):
103
+ task = asyncio.create_task(self._run_node(list(args)))
104
+ self._tasks.append(task)
105
+ return task
106
+
107
+ async def __aenter__(self):
108
+ return self
109
+
110
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
111
+ if self._tasks:
112
+ await asyncio.gather(*self._tasks, return_exceptions=True)
113
+ return self.succeed()