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,198 @@
1
+ import tomllib
2
+ import os
3
+ import shutil
4
+ import subprocess
5
+ import tempfile
6
+ import uuid
7
+ from pathlib import Path
8
+ from typing import Dict, Any, Optional, List, Union
9
+
10
+ from odmantic import ObjectId
11
+
12
+ import logging
13
+ logger = logging.getLogger("ResourceConfig")
14
+
15
+ class ResourceConfig:
16
+ """
17
+ ResourceConfig is responsible for managing configuration settings, setup,
18
+ execution, and post-processing parameters for specified resources.
19
+
20
+ This class is designed to read configuration data from a TOML file, provide
21
+ an interface to access resource-specific parameters, and execute resource-related
22
+ operations such as setup and running commands. It encapsulates functionality for
23
+ handling temporary directories, file manipulation, and subprocess execution.
24
+
25
+ Attributes:
26
+ _config (Dict[str, Any]): The loaded configuration dictionary.
27
+ _resource (str): The name of the current resource.
28
+ """
29
+ def __init__(self, config_path: Path, resource: str):
30
+ self._config: Dict[str, Any] = {}
31
+ self._resource = resource
32
+
33
+ actual_path = Path(config_path)
34
+ if actual_path.is_dir():
35
+ actual_path = actual_path / "config.toml"
36
+
37
+ if actual_path.exists():
38
+ with open(actual_path, "rb") as f:
39
+ self._config = tomllib.load(f)
40
+ else:
41
+ # If it doesn't exist, we just have an empty config
42
+ pass
43
+
44
+ @property
45
+ def os(self) -> str:
46
+ """
47
+ Returns the OS of the current resource, defaults to 'linux'.
48
+ """
49
+ try:
50
+ return self._config[self._resource].get("os", "linux")
51
+ except KeyError:
52
+ return "linux"
53
+
54
+ def setup(self, node_runner: Optional[Any] = None):
55
+ """
56
+ Executes the setup scripts for the current resource.
57
+ """
58
+ setup_params = self.get_setup_params()
59
+ scripts = setup_params.get("scripts", [])
60
+ for i, script in enumerate(scripts):
61
+ # Note: shell=True is needed to execute shell commands/scripts
62
+ # and might be OS dependent for the actual commands.
63
+ if node_runner and hasattr(node_runner, "subprocess"):
64
+ node_runner.subprocess(f"setup_{i}", script)
65
+ else:
66
+ subprocess.run(script, shell=True, check=True)
67
+
68
+ def tmp_dir(self, task_id: ObjectId | str) -> Path:
69
+ if isinstance(task_id, ObjectId):
70
+ task_id = str(task_id)
71
+ tmp_dir = self.tmp_base_dir / str(task_id)
72
+ Path(tmp_dir).mkdir(parents=True, exist_ok=True)
73
+ return tmp_dir
74
+
75
+ @property
76
+ def tmp_base_dir(self) -> Path:
77
+ tmp_base_dir_str = self.get_setup_params().get("tmp_base_dir", "")
78
+
79
+ if tmp_base_dir_str:
80
+ expanded_path_str = os.path.expandvars(os.path.expanduser(tmp_base_dir_str))
81
+ path = Path(expanded_path_str)
82
+ path.mkdir(parents=True, exist_ok=True)
83
+ return path
84
+
85
+ # Default to system temp directory if no command/path specified
86
+ return Path(tempfile.gettempdir())
87
+
88
+ def run(self,
89
+ program_name: str,
90
+ input_files: Optional[List[Union[str, "FileStack"]]] = None,
91
+ output_files: Optional[List[Union[str, "FileStack"]]] = None,
92
+ node_runner: Optional[Any] = None,
93
+ ):
94
+ """
95
+ Executes the run command with optional temporary directory usage and file handling.
96
+ Retrieves parameters from the configuration for the specified program.
97
+
98
+ Args:
99
+ program_name: Name of the program to run.
100
+ input_files: List of input files (str or FileStack). Overrides TOML input_files if provided.
101
+ output_files: List of output files (str or FileStack). Overrides TOML output_files if provided.
102
+ node_runner: Optional NodeRunner instance for execution.
103
+ """
104
+
105
+ params = self.get_program(program_name)
106
+ run_command = params.get("run_command", "")
107
+
108
+ if input_files is None:
109
+ input_files = params.get("input_files", [])
110
+
111
+ if output_files is None:
112
+ output_files = params.get("output_files", [])
113
+
114
+ use_temp = params.get("use_temp", False)
115
+
116
+ # tmp_base_dir can come from setup or the program itself, but usually it's in setup for the resource
117
+
118
+ # scratch_cleanup from postprocessing
119
+ post_params = self.get_postprocessing_params()
120
+ scratch_cleanup = params.get("scratch_cleanup", post_params.get("scratch_cleanup", False))
121
+
122
+ tmp_dir = None
123
+ try:
124
+ exec_dir = Path.cwd()
125
+ if use_temp:
126
+ tmp_id = node_runner.task_id if node_runner else uuid.uuid4()
127
+ tmp_dir = self.tmp_dir(tmp_id)
128
+ exec_dir = tmp_dir
129
+
130
+ # Copy input files to exec_dir
131
+ for f in input_files:
132
+ if hasattr(f, "get"): # It's a FileStack
133
+ f.get(local_dir=exec_dir)
134
+ else: # It's a string (filename)
135
+ src = Path.cwd() / f
136
+ if src.exists() and src != exec_dir / f:
137
+ shutil.copy(src, exec_dir / f)
138
+
139
+ # Execute run_command
140
+ if node_runner and hasattr(node_runner, "subprocess"):
141
+ node_runner.subprocess("run", run_command, cwd=str(exec_dir))
142
+ else:
143
+ subprocess.run(run_command, shell=True, check=True, cwd=exec_dir)
144
+
145
+
146
+ # Copy output files back to cwd
147
+ if use_temp and tmp_dir:
148
+ for f in output_files:
149
+ # output_files can also be FileStack in the new pattern?
150
+ # "output_files: List[str | FileStack] = [...]"
151
+ # If it's a FileStack, we might need to update its content from the local file
152
+ # but the standard behavior for output_files here is copying back to cwd.
153
+ filename = f.name if hasattr(f, "name") else f
154
+ src = tmp_dir / filename
155
+ if src.exists():
156
+ shutil.copy(src, Path.cwd() / filename)
157
+
158
+ finally:
159
+ if scratch_cleanup and tmp_dir and tmp_dir.exists():
160
+ shutil.rmtree(tmp_dir)
161
+
162
+ def get_program(self, program_name: str) -> Dict[str, Any]:
163
+ """
164
+ Returns the dict from resource.program.name for program with name and the current resource.
165
+ Expected structure in TOML: [resource_name.program.program_name]
166
+ """
167
+ try:
168
+ return self._config[self._resource]["program"][program_name]
169
+ except (KeyError, TypeError):
170
+ return {}
171
+
172
+ def get_setup_params(self) -> Dict[str, Any]:
173
+ """
174
+ Returns the setup dict for the specified resource.
175
+ Expected structure in TOML: [resource_name.setup]
176
+ """
177
+ try:
178
+ return self._config[self._resource]["setup"]
179
+ except KeyError:
180
+ return {}
181
+
182
+ def get_postprocessing_params(self) -> Dict[str, Any]:
183
+ """
184
+ Returns the post-processing dict for the specified resource.
185
+ Expected structure in TOML: [resource_name.post-processing] or [resource_name.postprocessing]
186
+ """
187
+ try:
188
+ resource_cfg = self._config[self._resource]
189
+ if "post-processing" in resource_cfg:
190
+ return resource_cfg["post-processing"]
191
+ if "postprocessing" in resource_cfg:
192
+ return resource_cfg["postprocessing"]
193
+ except KeyError:
194
+ pass
195
+ return {}
196
+
197
+ def __str__(self):
198
+ return f"ResourceConfig(resource={self._resource}, config={self._config})"
@@ -0,0 +1,74 @@
1
+ # TODO rmeove duplicate route finder this is a test implementation
2
+ routes_table = [
3
+ {"target": "local", "source": "int-nano", "host": "local"},
4
+ {"target": "int-nano", "source": "local", "host": "local"},
5
+ {
6
+ "target": "horeka",
7
+ "source": "local",
8
+ "host": "horeka",
9
+ },
10
+ {
11
+ "target": "local",
12
+ "source": "horeka",
13
+ "host": "horeka",
14
+ },
15
+ ]
16
+
17
+
18
+ def find_route(target, source):
19
+ """
20
+ Find the minimal route from source to target using available routes.
21
+
22
+ Args:
23
+ target (str): The target system
24
+ source (str): The source system
25
+
26
+ Returns:
27
+ list: A list of systems to visit in order, or None if no route exists
28
+ """
29
+ # If source and target are the same, no transfer needed
30
+ if source == target:
31
+ return [source]
32
+
33
+ # Build a graph of available routes
34
+ graph = {}
35
+ for route in routes_table:
36
+ if route["source"] not in graph:
37
+ graph[route["source"]] = []
38
+ graph[route["source"]].append(route["target"])
39
+
40
+ # If source isn't in our graph, we can't start a route
41
+ if source not in graph:
42
+ return None
43
+
44
+ # Use BFS to find the shortest path
45
+ visited = {source}
46
+ queue = [(source, [source])] # (current_node, path_so_far)
47
+
48
+ while queue:
49
+ current, path = queue.pop(0)
50
+
51
+ # Check neighbors (available transfer targets)
52
+ if current in graph:
53
+ for neighbor in graph[current]:
54
+ if neighbor == target:
55
+ # Found a path to target
56
+ return path + [neighbor]
57
+
58
+ if neighbor not in visited:
59
+ visited.add(neighbor)
60
+ queue.append((neighbor, path + [neighbor]))
61
+
62
+ # If we've exhausted all possibilities without finding a path
63
+ return None
64
+
65
+
66
+ if __name__ == "__main__":
67
+ # Example usage
68
+ target = "int-nano"
69
+ source = "horeka"
70
+ route = find_route(target, source)
71
+ if route:
72
+ print(f"Route from {source} to {target}: {' -> '.join(route)}")
73
+ else:
74
+ print(f"No route found from {source} to {target}")
@@ -0,0 +1,166 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import re
5
+ import socket
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from simstack.core.context import context
10
+ from simstack.models.runner_model import RunnerEvent, RunnerType, RunnerEventEnum
11
+
12
+ logger = logging.getLogger("RunnerUtil")
13
+
14
+
15
+ def ensure_crontab_entry(command, schedule="*/10 * * * *"):
16
+ """
17
+ Checks if a crontab entry exists and adds it if it doesn't.
18
+
19
+ Args:
20
+ command (str): The command to be executed by cron
21
+ schedule (str): The cron schedule expression (default: every 10 minutes)
22
+
23
+ Returns:
24
+ bool: True if entry was added, False if it already existed
25
+ """
26
+ # The full crontab entry to look for
27
+ cron_entry = f"{schedule} {command}"
28
+
29
+ try:
30
+ # Get current crontab
31
+ result = subprocess.run(["crontab", "-l"], capture_output=True, text=True)
32
+
33
+ # Check if command succeeded
34
+ if result.returncode == 0:
35
+ current_crontab = result.stdout
36
+ else:
37
+ # If crontab is empty or doesn't exist
38
+ current_crontab = ""
39
+
40
+ # Check if our entry already exists (using regex to handle whitespace variations)
41
+ entry_pattern = (
42
+ re.escape(schedule.strip()) + r"\s+" + re.escape(command.strip())
43
+ )
44
+ if re.search(entry_pattern, current_crontab):
45
+ logger.info(f"Crontab entry for '{command}' already exists.")
46
+
47
+ return False
48
+
49
+ # Add our entry to crontab
50
+ new_crontab = current_crontab
51
+ if new_crontab and not new_crontab.endswith("\n"):
52
+ new_crontab += "\n"
53
+ new_crontab += cron_entry + "\n"
54
+
55
+ # Write back to crontab
56
+ process = subprocess.Popen(["crontab", "-"], stdin=subprocess.PIPE, text=True)
57
+ process.communicate(input=new_crontab)
58
+
59
+ if process.returncode == 0:
60
+ logger.info(f"Successfully added crontab entry: '{cron_entry}'")
61
+ return True
62
+ else:
63
+ logger.error(f"Failed to update crontab, return code: {process.returncode}")
64
+ return False
65
+
66
+ except Exception as e:
67
+ logger.error(f"Error managing crontab: {str(e)}")
68
+ return False
69
+
70
+
71
+ async def graceful_shutdown(resource_name):
72
+ # Log shutdown
73
+
74
+ pid = os.getpid()
75
+ user = os.environ.get("USER", os.environ.get("USERNAME", "unknown"))
76
+ hostname = socket.gethostname()
77
+
78
+ # Capture current user info for the subprocess
79
+ current_user = os.environ.get("USER", os.environ.get("USERNAME"))
80
+
81
+ runner_event = RunnerEvent(
82
+ resource=resource_name,
83
+ runner_type=RunnerType.RESOURCE_RUNNER,
84
+ event=RunnerEventEnum.SHUTDOWN,
85
+ pid=pid,
86
+ user=user,
87
+ hostname=hostname,
88
+ message=f"Graceful shutdown of {pid} by {current_user}",
89
+ )
90
+ await context.db.save(runner_event)
91
+
92
+ # Cancel all remaining tasks
93
+ for task in asyncio.all_tasks():
94
+ if task is not asyncio.current_task():
95
+ task.cancel()
96
+
97
+ # Give tasks time to cancel
98
+ await asyncio.sleep(0.5)
99
+
100
+ # Signal to the main function to exit
101
+ raise SystemExit(0)
102
+
103
+
104
+ async def restart(resource_name):
105
+ logger.info(
106
+ f"Restarting node runner for resource: {resource_name} {context.config.python_paths}"
107
+ )
108
+
109
+ cmd = Path(context.config.project_root) / "scripts" / "old" / "check_runner.sh"
110
+ if not cmd.is_file():
111
+ raise FileNotFoundError(f"Runner restart script not found: {cmd}")
112
+
113
+ # Get current user information
114
+ current_uid = os.getuid()
115
+ current_gid = os.getgid()
116
+ current_pid = os.getpid()
117
+ logger.info(f"Current user ID: {current_uid}, group ID: {current_gid}")
118
+
119
+ # Create environment with preserved user information
120
+ env = os.environ.copy()
121
+
122
+ # Start a new process with the same arguments
123
+
124
+ logger.info(f"Restarting node runner for resource: {resource_name} {str(cmd)}")
125
+
126
+ result = subprocess.Popen(
127
+ ["bash", str(cmd)], start_new_session=True, env=env
128
+ )
129
+ # Log the restart
130
+ message = f"user: {current_uid} group: {current_gid} pid: {current_pid} result: {result.pid}"
131
+ runner_event = RunnerEvent(
132
+ resource=resource_name,
133
+ runner_type=RunnerType.RESOURCE_RUNNER,
134
+ event=RunnerEventEnum.RESTART,
135
+ message=message,
136
+ )
137
+ await context.db.save(runner_event)
138
+ # Exit current process
139
+ logger.info(f"Restarted node runner for resource: {resource_name}")
140
+ # await graceful_shutdown(resource_name)
141
+
142
+
143
+ async def schedule_restart(resource_name, restart_minutes):
144
+ """Schedule a restart after the specified minutes"""
145
+ try:
146
+ logger.info(
147
+ f"Runner will restart in {restart_minutes} minutes on resource {resource_name} "
148
+ )
149
+ await asyncio.sleep(restart_minutes * 60) # Convert minutes to seconds
150
+ # try:
151
+ # if context.config.python_path:
152
+ # path = Path(context.config.python_path[0])
153
+ # command = str(path / "scripts" / "old" / "check_runner.sh") + " >> " + str(path / "cron_log.log") + " 2>&1"
154
+ # logger.info(f"Path: {command} ")
155
+ # if not ensure_crontab_entry(command):
156
+ # runner_entry = RunnerEvent(resource=resource_name, runner_type=RunnerType.RESOURCE_RUNNER,
157
+ # event=RunnerEventEnum.CRONTAB_GONE,
158
+ # message="Crontab entry for resource runner was not found")
159
+ # await context.db.save(runner_entry)
160
+ # except Exception as e:
161
+ # logger.error("Something went wrong for crontab")
162
+ await restart(resource_name)
163
+ await graceful_shutdown(resource_name)
164
+ except asyncio.CancelledError:
165
+ logger.info("Restart task was cancelled")
166
+ raise
@@ -0,0 +1,143 @@
1
+ import re
2
+ import subprocess
3
+ import uuid
4
+ from datetime import datetime
5
+ from typing import List
6
+
7
+ from odmantic import ObjectId
8
+ from simstack.core.context import context
9
+ from simstack.models.parameters import Resource
10
+ from simstack.models.slurm_info import SlurmInfo
11
+ from simstack.util.git_repository_status import get_git_status
12
+ from simstack.util.submit_to_watchdog import submit_to_watchdog
13
+
14
+ import logging
15
+
16
+ logger = logging.getLogger("runner_utils")
17
+
18
+
19
+ def make_git_status_list() -> List[str]:
20
+ git_status_list = []
21
+ git_path_list = [context.config.project_root]
22
+ for path in git_path_list:
23
+ result = get_git_status(path)
24
+ if result["branch"]:
25
+ value = result["branch"] + "[" + result["short_hash"] + "]"
26
+ if result["up_to_date"]:
27
+ value += " (up-to-date)"
28
+ else:
29
+ value += " (behind " + str(result["behind"]) + " commits)"
30
+ git_status_list.append(value)
31
+ else:
32
+ git_status_list.append("No branch found")
33
+ return git_status_list
34
+
35
+
36
+ def run_squeue_for_job(job_id: str) -> str:
37
+ result = subprocess.run(
38
+ f"squeue -j {job_id}",
39
+ shell=True,
40
+ capture_output=True,
41
+ text=True,
42
+ timeout=30,
43
+ )
44
+ return result.stdout
45
+
46
+
47
+ def get_job_info(
48
+ job_id: str, task_id: ObjectId, resource: Resource
49
+ ) -> SlurmInfo | None:
50
+ """Get job information from SLURM queue using squeue"""
51
+ try:
52
+ stdout = run_squeue_for_job(job_id)
53
+ # logger.info(f"task_id: {task_id} running squeue for job {job_id}: result: {stdout}")
54
+
55
+ if not stdout or stdout.strip() == "":
56
+ # after a while slurm will stop returning info for jobs that are no longer running
57
+ return None
58
+
59
+ lines = stdout.splitlines()
60
+ # logger.info(f"task_id: {task_id} slurm info for job {job_id}: {lines}")
61
+ if len(lines) < 2:
62
+ return None
63
+ # The first line is the header; the second line is the single info line
64
+
65
+ info_line = lines[1].strip()
66
+ if not info_line:
67
+ return None
68
+ # Split the single line into parts separated by whitespace
69
+ parts = re.split(r"\s+", info_line)
70
+ # logger.info(f"task_id: {task_id} slurm info for job {job_id}: {parts}")
71
+ # Expected default squeue columns:
72
+ # JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
73
+ name = parts[2] if len(parts) > 2 else ""
74
+ user = parts[3] if len(parts) > 3 else ""
75
+ code = parts[4] if len(parts) > 4 else ""
76
+ time_str = parts[5] if len(parts) > 5 else ""
77
+ nodelist_raw = parts[7] if len(parts) > 7 else ""
78
+ # Split nodelist on commas or whitespace, filter empties
79
+ nodes = [n for n in re.split(r"[,\s]+", nodelist_raw) if n]
80
+
81
+ slurm_info = SlurmInfo(
82
+ node_registry=task_id,
83
+ resource=resource,
84
+ job_id=job_id,
85
+ updated=datetime.now(),
86
+ name=name,
87
+ user=user,
88
+ code=code,
89
+ time=time_str,
90
+ nodes=nodes,
91
+ )
92
+
93
+ return slurm_info
94
+ except Exception as e:
95
+ logger.exception(f"Error getting job info for {job_id}: {str(e)}")
96
+ return None
97
+
98
+
99
+ async def clean_slurm_info(resource: Resource, user: str | None = None) -> None:
100
+ """Clean up old slurm info entries"""
101
+ try:
102
+ squeue_cmd = "squeue"
103
+ if user:
104
+ squeue_cmd += f" -u {user}"
105
+
106
+ if context.config.docker:
107
+ watchdog_id = f"slurm_{uuid.uuid4()}"
108
+ queue_dir = context.config.workdir / "queue"
109
+ result = submit_to_watchdog(squeue_cmd, watchdog_id, queue_dir=queue_dir)
110
+ else:
111
+ result = subprocess.run(
112
+ squeue_cmd,
113
+ shell=True,
114
+ capture_output=True,
115
+ text=True,
116
+ timeout=30,
117
+ )
118
+
119
+ if result.returncode == 0:
120
+ active_job_ids = set()
121
+ for line in result.stdout.splitlines():
122
+ parts = line.split()
123
+ if not parts or parts[0] == "JOBID":
124
+ continue
125
+ active_job_ids.add(parts[0])
126
+
127
+ # Find all SLURM info entries for this resource
128
+ # the user id is truncated on saving
129
+ running_jobs = await context.db.find(
130
+ SlurmInfo, SlurmInfo.resource.value == resource.value
131
+ )
132
+ # logger.info(f"Found {running_jobs} slurm info entries for {resource}")
133
+ # logger.info(f"Active job IDs: {active_job_ids} Slurm info IDs: {[job.job_id for job in running_jobs]}")
134
+ # logger.info(f"User: {user} resource: {resource} ")
135
+
136
+ # Delete entries for jobs that are no longer running
137
+ for job in running_jobs:
138
+ if job.job_id not in active_job_ids:
139
+ await context.db.delete(job)
140
+ logger.info(f"Deleted SLURM info for completed job {job.job_id}")
141
+
142
+ except Exception as e:
143
+ logger.exception(f"Error cleaning slurm info for {resource}: {str(e)}")