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,427 @@
1
+ from __future__ import annotations
2
+
3
+ import http.client
4
+ import base64
5
+ import json
6
+ import logging
7
+ import os
8
+ import tempfile
9
+ import time
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Iterable, cast
13
+ from urllib.parse import urlencode, urlparse
14
+
15
+ from simstack.util.file_hashing import hash_file
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class FileTransferError(RuntimeError):
21
+ """Raised when the SimStack file transfer API cannot satisfy a request."""
22
+
23
+
24
+ @dataclass
25
+ class DownloadResult:
26
+ path: Path
27
+ size_bytes: int
28
+ checksum_sha256: str
29
+
30
+
31
+ class FileTransferClient:
32
+ """
33
+ Small standard-library HTTP client for runner-to-server file transfer.
34
+
35
+ It intentionally avoids adding a mandatory requests/httpx dependency to the
36
+ runner environment while still streaming upload and download bodies.
37
+ """
38
+
39
+ chunk_size = 1024 * 1024
40
+
41
+ def __init__(
42
+ self,
43
+ *,
44
+ server_url: str,
45
+ runner_token: str,
46
+ timeout_seconds: int = 60,
47
+ ) -> None:
48
+ self.server_url = server_url.rstrip("/")
49
+ self.runner_token = runner_token
50
+ self.timeout_seconds = timeout_seconds
51
+ parsed = urlparse(self.server_url)
52
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
53
+ raise ValueError(f"Invalid SIMSTACK_SERVER_URL: {server_url!r}")
54
+ self._parsed = parsed
55
+
56
+ @classmethod
57
+ def from_context(cls, *, required: bool = True) -> "FileTransferClient | None":
58
+ """
59
+ Build a client from environment variables or context config attributes.
60
+
61
+ Environment variables are preferred because runner tokens are machine
62
+ credentials and should not be stored in FileStack/FileInstance objects.
63
+ """
64
+ try:
65
+ from simstack.core.context import context
66
+
67
+ config = getattr(context, "config", None)
68
+ except Exception:
69
+ config = None
70
+
71
+ server_url = (
72
+ os.environ.get("SIMSTACK_SERVER_URL")
73
+ or getattr(config, "server_url", None)
74
+ or getattr(config, "simstack_server_url", None)
75
+ )
76
+ runner_token = (
77
+ os.environ.get("SIMSTACK_RUNNER_TOKEN")
78
+ or getattr(config, "server_token", None)
79
+ or getattr(config, "simstack_runner_token", None)
80
+ )
81
+ timeout_seconds = int(
82
+ os.environ.get("SIMSTACK_FILE_TRANSFER_REQUEST_TIMEOUT_SECONDS", "60")
83
+ )
84
+
85
+ if not server_url or not runner_token:
86
+ if required:
87
+ raise FileTransferError(
88
+ "Remote FileStack transfer requires SIMSTACK_SERVER_URL and "
89
+ "SIMSTACK_RUNNER_TOKEN in the runner environment."
90
+ )
91
+ return None
92
+
93
+ return cls(
94
+ server_url=str(server_url),
95
+ runner_token=str(runner_token),
96
+ timeout_seconds=timeout_seconds,
97
+ )
98
+
99
+ def create_transfer(
100
+ self,
101
+ *,
102
+ file_stack_id: str,
103
+ source_file_instance_id: str | None,
104
+ source_resource_name: str | None,
105
+ target_resource_name: str,
106
+ request_type: str = "runner_to_runner",
107
+ ) -> Dict[str, Any]:
108
+ return self._json_request(
109
+ "POST",
110
+ "/api/file-transfers",
111
+ {
112
+ "file_stack_id": file_stack_id,
113
+ "source_file_instance_id": source_file_instance_id,
114
+ "source_resource_name": source_resource_name,
115
+ "target_resource_name": target_resource_name,
116
+ "request_type": request_type,
117
+ },
118
+ )
119
+
120
+ def get_transfer(self, transfer_id: str) -> Dict[str, Any]:
121
+ return self._json_request("GET", f"/api/file-transfers/{transfer_id}")
122
+
123
+ def list_transfers(
124
+ self,
125
+ *,
126
+ role: str = "source",
127
+ status: str | None = "created",
128
+ limit: int = 20,
129
+ ) -> list[Dict[str, Any]]:
130
+ query = {"role": role, "limit": str(limit)}
131
+ if status:
132
+ query["status"] = status
133
+ response = self._json_request("GET", f"/api/file-transfers?{urlencode(query)}")
134
+ transfers = response.get("transfers", [])
135
+ return transfers if isinstance(transfers, list) else []
136
+
137
+ def fail_transfer(
138
+ self, transfer_id: str, *, error_message: str, error_code: str | None = None
139
+ ) -> Dict[str, Any]:
140
+ return self._json_request(
141
+ "POST",
142
+ f"/api/file-transfers/{transfer_id}/fail",
143
+ {"error_message": error_message, "error_code": error_code},
144
+ )
145
+
146
+ def wait_until_uploaded(
147
+ self,
148
+ transfer_id: str,
149
+ *,
150
+ timeout_seconds: int | None = None,
151
+ poll_interval_seconds: float | None = None,
152
+ ) -> Dict[str, Any]:
153
+ deadline = time.monotonic() + float(
154
+ timeout_seconds
155
+ if timeout_seconds is not None
156
+ else os.environ.get("SIMSTACK_FILE_TRANSFER_TIMEOUT_SECONDS", "3600")
157
+ )
158
+ interval = float(
159
+ poll_interval_seconds
160
+ if poll_interval_seconds is not None
161
+ else os.environ.get("SIMSTACK_FILE_TRANSFER_POLL_SECONDS", "5")
162
+ )
163
+
164
+ last_status = "unknown"
165
+ while time.monotonic() < deadline:
166
+ transfer = self.get_transfer(transfer_id)
167
+ last_status = str(transfer.get("status", "unknown"))
168
+ if last_status in {"uploaded", "target_downloading", "completed"}:
169
+ return transfer
170
+ if last_status in {"failed", "expired"}:
171
+ raise FileTransferError(
172
+ f"Transfer {transfer_id} failed with status {last_status}: "
173
+ f"{transfer.get('error_message')}"
174
+ )
175
+ time.sleep(max(interval, 0.1))
176
+
177
+ raise FileTransferError(
178
+ f"Timed out waiting for transfer {transfer_id} to upload; last status was {last_status}."
179
+ )
180
+
181
+ def upload_file(self, transfer_id: str, path: Path) -> Dict[str, Any]:
182
+ path = Path(path)
183
+ if not path.is_file():
184
+ raise FileTransferError(f"Transfer source file does not exist: {path}")
185
+
186
+ size_bytes = path.stat().st_size
187
+ checksum = hash_file(path)
188
+ conn = self._connection()
189
+ try:
190
+ conn.putrequest(
191
+ "PUT", self._path(f"/api/file-transfers/{transfer_id}/upload")
192
+ )
193
+ for key, value in self._auth_headers().items():
194
+ conn.putheader(key, value)
195
+ conn.putheader("Content-Type", "application/octet-stream")
196
+ conn.putheader("Content-Length", str(size_bytes))
197
+ conn.putheader("X-File-Size", str(size_bytes))
198
+ conn.putheader("X-Checksum-SHA256", checksum)
199
+ conn.endheaders()
200
+
201
+ with path.open("rb") as handle:
202
+ for chunk in iter(lambda: handle.read(self.chunk_size), b""):
203
+ conn.send(chunk)
204
+
205
+ return self._parse_response(conn.getresponse())
206
+ finally:
207
+ conn.close()
208
+
209
+ def download_file(self, transfer_id: str, target_path: Path) -> DownloadResult:
210
+ target_path = Path(target_path)
211
+ target_path.parent.mkdir(parents=True, exist_ok=True)
212
+
213
+ conn = self._connection()
214
+ temp_name = None
215
+ try:
216
+ conn.request(
217
+ "GET",
218
+ self._path(f"/api/file-transfers/{transfer_id}/download"),
219
+ headers=self._auth_headers(),
220
+ )
221
+ response = conn.getresponse()
222
+ if response.status >= 400:
223
+ self._raise_response(response)
224
+
225
+ import hashlib
226
+
227
+ sha256 = hashlib.sha256()
228
+ size_bytes = 0
229
+ with tempfile.NamedTemporaryFile(
230
+ mode="wb",
231
+ dir=str(target_path.parent),
232
+ prefix=f".{target_path.name}.",
233
+ delete=False,
234
+ ) as temp_file:
235
+ temp_name = temp_file.name
236
+ while True:
237
+ chunk = response.read(self.chunk_size)
238
+ if not chunk:
239
+ break
240
+ temp_file.write(chunk)
241
+ sha256.update(chunk)
242
+ size_bytes += len(chunk)
243
+
244
+ temp_path = Path(temp_name)
245
+ temp_path.replace(target_path)
246
+ return DownloadResult(
247
+ path=target_path,
248
+ size_bytes=size_bytes,
249
+ checksum_sha256=sha256.hexdigest(),
250
+ )
251
+ except Exception:
252
+ if temp_name:
253
+ try:
254
+ Path(temp_name).unlink(missing_ok=True)
255
+ except Exception:
256
+ logger.warning("Failed to clean temporary download %s", temp_name)
257
+ raise
258
+ finally:
259
+ conn.close()
260
+
261
+ def complete_transfer(
262
+ self,
263
+ *,
264
+ transfer_id: str,
265
+ target_resource_name: str,
266
+ target_path: str,
267
+ size_bytes: int,
268
+ checksum_sha256: str,
269
+ ) -> Dict[str, Any]:
270
+ return self._json_request(
271
+ "POST",
272
+ f"/api/file-transfers/{transfer_id}/complete",
273
+ {
274
+ "target_resource_name": target_resource_name,
275
+ "target_path": target_path,
276
+ "size_bytes": size_bytes,
277
+ "checksum_sha256": checksum_sha256,
278
+ },
279
+ )
280
+
281
+ def _json_request(
282
+ self, method: str, path: str, payload: Dict[str, Any] | None = None
283
+ ) -> Dict[str, Any]:
284
+ body = None if payload is None else json.dumps(payload).encode("utf-8")
285
+ headers = self._auth_headers()
286
+ if body is not None:
287
+ headers["Content-Type"] = "application/json"
288
+ conn = self._connection()
289
+ try:
290
+ conn.request(method, self._path(path), body=body, headers=headers)
291
+ return self._parse_response(conn.getresponse())
292
+ finally:
293
+ conn.close()
294
+
295
+ def _connection(self) -> http.client.HTTPConnection:
296
+ connection_cls = (
297
+ http.client.HTTPSConnection
298
+ if self._parsed.scheme == "https"
299
+ else http.client.HTTPConnection
300
+ )
301
+ return connection_cls(self._parsed.netloc, timeout=self.timeout_seconds)
302
+
303
+ def _path(self, path: str) -> str:
304
+ base_path = self._parsed.path.rstrip("/")
305
+ if path.startswith("http://") or path.startswith("https://"):
306
+ parsed = urlparse(path)
307
+ return parsed.path + (f"?{parsed.query}" if parsed.query else "")
308
+ if not path.startswith("/"):
309
+ path = f"/{path}"
310
+ return f"{base_path}{path}" if base_path else path
311
+
312
+ def _auth_headers(self) -> Dict[str, str]:
313
+ return {"Authorization": f"Bearer {self.runner_token}"}
314
+
315
+ def _parse_response(self, response: http.client.HTTPResponse) -> Dict[str, Any]:
316
+ if response.status >= 400:
317
+ self._raise_response(response)
318
+ payload = response.read()
319
+ if not payload:
320
+ return {}
321
+ content_type = response.getheader("Content-Type") or ""
322
+ if "application/json" not in content_type:
323
+ return {"raw": payload.decode("utf-8", errors="replace")}
324
+ parsed = json.loads(payload.decode("utf-8"))
325
+ return cast(Dict[str, Any], parsed)
326
+
327
+ def _raise_response(self, response: http.client.HTTPResponse) -> None:
328
+ payload = response.read()
329
+ detail = (
330
+ payload.decode("utf-8", errors="replace") if payload else response.reason
331
+ )
332
+ raise FileTransferError(
333
+ f"File transfer API returned HTTP {response.status}: {detail}"
334
+ )
335
+
336
+
337
+ def path_for_file_instance(path: Path, workdir: Path) -> str:
338
+ resolved_path = Path(path).resolve()
339
+ resolved_workdir = Path(workdir).resolve()
340
+ try:
341
+ return str(resolved_path.relative_to(resolved_workdir))
342
+ except ValueError:
343
+ return str(resolved_path)
344
+
345
+
346
+ def resolve_instance_path(raw_path: str, workdir: Path) -> Path:
347
+ path = Path(raw_path)
348
+ if path.is_absolute():
349
+ return path
350
+ return Path(workdir) / path
351
+
352
+
353
+ def resource_name(value: Any) -> str:
354
+ raw = getattr(value, "__dict__", {}).get("value")
355
+ if raw is not None:
356
+ return str(raw)
357
+ return str(value)
358
+
359
+
360
+ def _configured_runner_token() -> str | None:
361
+ token = os.environ.get("SIMSTACK_RUNNER_TOKEN")
362
+ if token:
363
+ return token
364
+ try:
365
+ from simstack.core.context import context
366
+
367
+ config = getattr(context, "config", None)
368
+ except Exception:
369
+ return None
370
+ return cast(
371
+ str | None,
372
+ getattr(config, "server_token", None)
373
+ or getattr(config, "simstack_runner_token", None),
374
+ )
375
+
376
+
377
+ def _resource_from_runner_token(token: str | None) -> str | None:
378
+ if not token:
379
+ return None
380
+ parts = token.split(".")
381
+ if len(parts) < 2:
382
+ return None
383
+ payload = parts[1]
384
+ payload += "=" * (-len(payload) % 4)
385
+ try:
386
+ decoded = json.loads(base64.urlsafe_b64decode(payload.encode("ascii")))
387
+ except Exception:
388
+ return None
389
+ resource = decoded.get("res")
390
+ return str(resource) if resource else None
391
+
392
+
393
+ def transfer_resource_name(value: Any) -> str:
394
+ """
395
+ Return the concrete resource name used by file transfer endpoints.
396
+
397
+ SimStack uses the symbolic resource "self" for nodes that run on the
398
+ current resource. The server transfer API cannot authorize "self" because
399
+ runner tokens are scoped to concrete resources such as "local" or
400
+ "int-nano". When possible, resolve "self" through the configured runner
401
+ token resource before sending transfer requests or storing file instances.
402
+ """
403
+ name = resource_name(value)
404
+ if name != "self":
405
+ return name
406
+
407
+ token_resource = _resource_from_runner_token(_configured_runner_token())
408
+ if token_resource:
409
+ return token_resource
410
+ # TODO: Artem we get here only when resource is self and no runner token is configured. We should probably raise an error here.
411
+ return name
412
+
413
+
414
+ def first_available_remote_location(
415
+ locations: Iterable[Any], local_resource: Any
416
+ ) -> Any | None:
417
+ local_resource_str = transfer_resource_name(local_resource)
418
+ for location in locations:
419
+ if (
420
+ transfer_resource_name(getattr(location, "resource", ""))
421
+ == local_resource_str
422
+ ):
423
+ continue
424
+ if getattr(location, "status", "available") != "available":
425
+ continue
426
+ return location
427
+ return None