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,402 @@
1
+ from enum import Enum
2
+ from typing import Optional, List, ClassVar, Dict, Any
3
+ from odmantic import Field, EmbeddedModel
4
+ from pydantic import field_validator, model_validator
5
+
6
+ from simstack.core.resources import allowed_resources
7
+
8
+
9
+ def _validate_value_on_read(v: str) -> str:
10
+ """
11
+ Validate the value against allowed resources when it is read.
12
+ """
13
+ if not allowed_resources.has_resource(v):
14
+ allowed_str = ", ".join(repr(val) for val in allowed_resources.get_resources())
15
+ raise ValueError(f"Invalid resource value: {v!r}. Allowed values are: {allowed_str}")
16
+ return v
17
+
18
+
19
+ class Resource(EmbeddedModel):
20
+ """
21
+ Resource whose value is validated against the allowed resources
22
+ only when the value is *read*, not when it is *set* or constructed.
23
+ """
24
+
25
+ # Regular model field, no leading underscore
26
+ value: str
27
+
28
+ def __getattribute__(self, name: str):
29
+ """
30
+ Intercept reads of `value` and validate at access time.
31
+ Setting/constructing does not perform validation.
32
+ """
33
+ if name == "value":
34
+ # Get raw stored value without triggering this override again
35
+ raw_value = object.__getattribute__(self, "__dict__").get("value")
36
+ return _validate_value_on_read(raw_value)
37
+ # Avoid triggering Pydantic deprecation warnings on model_fields
38
+ if name == "model_fields":
39
+ return self.__class__.model_fields
40
+ return object.__getattribute__(self, name)
41
+
42
+ def __str__(self) -> str:
43
+ """String representation of the resource (validated on access)."""
44
+ return self.value
45
+
46
+ def __repr__(self) -> str:
47
+ """Debug representation of the resource (validated on access)."""
48
+ return f"{self.__class__.__name__}(value={self.value!r})"
49
+
50
+ def __eq__(self, other) -> bool:
51
+ """Equal comparison operator (uses validated value)."""
52
+ if isinstance(other, Resource):
53
+ return self.value == other.value
54
+ elif isinstance(other, str):
55
+ return self.value == other
56
+ return False
57
+
58
+ def __ne__(self, other) -> bool:
59
+ """Not equal comparison operator."""
60
+ return not self.__eq__(other)
61
+
62
+
63
+ class Queue(str, Enum):
64
+ DEFAULT = "default"
65
+ SLURM_QUEUE = "slurm-queue"
66
+ DOCKER = "docker"
67
+ SLURM_DOCKER = "slurm-docker"
68
+
69
+
70
+ # TODO Fix Slurm Parameters
71
+ class SlurmParameters(EmbeddedModel):
72
+ # Essential Resource Allocation Parameters
73
+ nodes: Optional[int] = Field(default=1, ge=1, description="Number of compute nodes")
74
+ tasks: Optional[int] = Field(
75
+ default=1, ge=1, description="Total number of tasks/processes"
76
+ )
77
+ tasks_per_node: Optional[int] = Field(
78
+ default=1, ge=1, description="Number of tasks per node"
79
+ )
80
+ cpus_per_task: Optional[int] = Field(
81
+ default=1, ge=1, description="Number of CPU cores per task"
82
+ )
83
+
84
+ # Memory Parameters
85
+ mem: Optional[str] = Field(
86
+ default="1G", description="Memory per node (e.g., '32G', '1024M')"
87
+ )
88
+ mem_per_cpu: Optional[str] = Field(
89
+ default=None, description="Memory per CPU core (e.g., '4G')"
90
+ )
91
+
92
+ # Time Parameters
93
+ time: Optional[str] = Field(
94
+ default="1:00:00",
95
+ description="Maximum runtime (e.g., '24:00:00', '1-12:00:00')",
96
+ )
97
+ begin: Optional[str] = Field(
98
+ default=None, description="Defer job start until specified time"
99
+ )
100
+
101
+ # Queue/Partition Parameters
102
+ partition: Optional[str] = Field(
103
+ default=None, description="Queue/partition to submit to"
104
+ )
105
+ qos: Optional[str] = Field(default=None, description="Quality of Service level")
106
+
107
+ # Job Information Parameters
108
+ job_name: Optional[str] = Field(default="simstack", description="Job name")
109
+ output: Optional[str] = Field(default=None, description="Standard output file path")
110
+ error: Optional[str] = Field(default=None, description="Standard error file path")
111
+
112
+ # Notification Parameters
113
+ mail_type: Optional[str] = Field(
114
+ default=None, description="Email notification triggers (BEGIN,END,FAIL,ALL)"
115
+ )
116
+ mail_user: Optional[str] = Field(
117
+ default=None, description="Email address for notifications"
118
+ )
119
+
120
+ # GPU Parameters
121
+ gres: Optional[str] = Field(
122
+ default=None, description="Generic resources (e.g., 'gpu:2', 'gpu:v100:1')"
123
+ )
124
+
125
+ # Advanced Scheduling Parameters
126
+ account: Optional[str] = Field(default=None, description="Billing account")
127
+ priority: Optional[int] = Field(default=None, description="Job priority")
128
+ reservation: Optional[str] = Field(
129
+ default=None, description="Use specific reservation"
130
+ )
131
+ constraint: Optional[str] = Field(
132
+ default=None, description="Node feature constraints"
133
+ )
134
+ exclusive: Optional[bool] = Field(default=None, description="Exclusive node access")
135
+ nice: Optional[int] = Field(default=None, description="Adjust scheduling priority")
136
+
137
+ # Dependency and Array Parameters
138
+ dependency: Optional[str] = Field(
139
+ default=None, description="Job dependencies (e.g., 'afterok:jobid')"
140
+ )
141
+ array: Optional[str] = Field(
142
+ default=None, description="Job arrays (e.g., '1-100', '1-10:2')"
143
+ )
144
+
145
+ # Additional Commands
146
+ startup_commands: List[str] = Field(
147
+ default_factory=list, description="Commands to run before main job"
148
+ )
149
+
150
+ # Working Directory
151
+ chdir: Optional[str] = Field(
152
+ default=None, description="Working directory for the job"
153
+ )
154
+
155
+ # Export Environment
156
+ export: Optional[str] = Field(
157
+ default=None, description="Environment variables to export"
158
+ )
159
+
160
+ # Signal handling
161
+ signal: Optional[str] = Field(
162
+ default=None, description="Signal to send when time limit is reached"
163
+ )
164
+
165
+ # Requeue options
166
+ requeue: Optional[bool] = Field(
167
+ default=None, description="Allow job to be requeued"
168
+ )
169
+ no_requeue: Optional[bool] = Field(
170
+ default=None, description="Prevent job from being requeued"
171
+ )
172
+
173
+ model_config: ClassVar[Dict[str, Any]] = {
174
+ "extra": "forbid",
175
+ "json_schema_extra": {
176
+ "title": "SlurmParameters",
177
+ "description": "Comprehensive parameters for Slurm job submission",
178
+ "examples": [
179
+ {
180
+ "nodes": 2,
181
+ "ntasks_per_node": 8,
182
+ "cpus_per_task": 4,
183
+ "mem": "64G",
184
+ "time": "12:00:00",
185
+ "partition": "compute",
186
+ "gres": "gpu:2",
187
+ "job_name": "my_simulation",
188
+ "output": "job_%j.out",
189
+ "error": "job_%j.err",
190
+ "mail_type": "END,FAIL",
191
+ "mail_user": "user@institution.edu",
192
+ }
193
+ ],
194
+ },
195
+ }
196
+
197
+ def to_sbatch_args(self) -> List[str]:
198
+ """Convert parameters to SBATCH arguments list."""
199
+ args = []
200
+
201
+ # Validate memory parameters first
202
+ memory_options = []
203
+
204
+ if self.mem_per_cpu is not None:
205
+ memory_options.append("mem_per_cpu")
206
+ elif self.mem is not None:
207
+ memory_options.append("mem")
208
+ if hasattr(self, "mem_per_gpu") and getattr(self, "mem_per_gpu") is not None:
209
+ memory_options.append("mem_per_gpu")
210
+
211
+ if len(memory_options) > 1:
212
+ raise ValueError(
213
+ f"SLURM memory parameters are mutually exclusive. "
214
+ f"Found: {', '.join(memory_options)}. "
215
+ f"Please specify only one of: --mem, --mem-per-cpu, or --mem-per-gpu"
216
+ )
217
+
218
+ # Ensure task count options are mutually exclusive
219
+ # If both total tasks (--ntasks) and tasks per node (--ntasks-per-node)
220
+ # are provided, prefer --ntasks-per-node and omit --ntasks.
221
+ tasks_value = self.tasks
222
+ tasks_per_node_value = self.tasks_per_node
223
+
224
+ if tasks_value is not None and tasks_per_node_value is not None:
225
+ tasks_value = None
226
+
227
+ # Map field names to SBATCH parameters
228
+ field_mapping = {
229
+ "nodes": "--nodes",
230
+ # NOTE: tasks and tasks_per_node are handled specially below to enforce
231
+ # mutual exclusivity when converting to sbatch arguments.
232
+ "tasks": "--ntasks",
233
+ "tasks_per_node": "--ntasks-per-node",
234
+ "cpus_per_task": "--cpus-per-task",
235
+ "mem": "--mem",
236
+ "mem_per_cpu": "--mem-per-cpu",
237
+ "time": "--time",
238
+ "begin": "--begin",
239
+ "partition": "--partition",
240
+ "qos": "--qos",
241
+ "job_name": "--job-name",
242
+ "output": "--output",
243
+ "error": "--error",
244
+ "mail_type": "--mail-type",
245
+ "mail_user": "--mail-user",
246
+ "gres": "--gres",
247
+ "account": "--account",
248
+ "priority": "--priority",
249
+ "reservation": "--reservation",
250
+ "constraint": "--constraint",
251
+ "dependency": "--dependency",
252
+ "array": "--array",
253
+ "chdir": "--chdir",
254
+ "export": "--export",
255
+ "signal": "--signal",
256
+ }
257
+
258
+ # Add mem_per_gpu to field mapping if it exists
259
+ if hasattr(self, "mem_per_gpu"):
260
+ field_mapping["mem_per_gpu"] = "--mem-per-gpu"
261
+
262
+ # Add parameters with values
263
+ for field_name, sbatch_param in field_mapping.items():
264
+ # Handle mutually exclusive task options
265
+ if field_name == "tasks":
266
+ value = tasks_value
267
+ elif field_name == "tasks_per_node":
268
+ value = tasks_per_node_value
269
+ else:
270
+ value = getattr(self, field_name, None)
271
+
272
+ if value is not None:
273
+ args.append(f"{sbatch_param}={value}")
274
+
275
+ # Add boolean flags
276
+ if self.exclusive:
277
+ args.append("--exclusive")
278
+ if self.requeue:
279
+ args.append("--requeue")
280
+ if self.no_requeue:
281
+ args.append("--no-requeue")
282
+ if self.nice is not None:
283
+ args.append(f"--nice={self.nice}")
284
+
285
+ return args
286
+
287
+ def to_sbatch_header(self) -> str:
288
+ """Convert parameters to SBATCH header string for script files."""
289
+ lines = ["#!/bin/bash"]
290
+
291
+ for arg in self.to_sbatch_args():
292
+ lines.append(f"#SBATCH {arg}")
293
+
294
+ if self.startup_commands:
295
+ lines.append("")
296
+ lines.extend(self.startup_commands)
297
+
298
+ return "\n".join(lines)
299
+
300
+
301
+ class Parameters(EmbeddedModel):
302
+ force_rerun: bool = False
303
+ resource: Resource = Field(default_factory=lambda: Resource(value="self"))
304
+ queue: str = Field(default="default")
305
+ recompute_artifacts: Optional[bool] = Field(
306
+ default=False, description="Recompute artifacts for this node"
307
+ )
308
+ docker_image: Optional[str] = Field(default=None, description="Docker image to use")
309
+ other_value: str = Field(default="other")
310
+ test_dict: Dict[str, Any] = Field(default_factory=lambda: {"test": "value"})
311
+
312
+ slurm_parameters: SlurmParameters = Field(default=None)
313
+ # slurm_parameters_data: Dict[str, Any] = Field(default_factory=dict)
314
+
315
+ model_config = {
316
+ "extra": "forbid",
317
+ "json_schema_extra": {
318
+ "title": "Parameters",
319
+ "description": "Parameters for running a simulation",
320
+ "examples": [
321
+ {
322
+ "resource": "self",
323
+ "queue": "default",
324
+ "slurm_parameters": {
325
+ "nodes": 2,
326
+ },
327
+ }
328
+ ],
329
+ },
330
+ }
331
+
332
+ @model_validator(mode="before")
333
+ @classmethod
334
+ def migrate_slurm_parameters(cls, data):
335
+ if "slurm_parameters_data" in data and "slurm_parameters" not in data:
336
+ data["slurm_parameters"] = SlurmParameters(**data["slurm_parameters_data"])
337
+ del data["slurm_parameters_data"]
338
+ if "slurm_parameters" not in data or data["slurm_parameters"] is None:
339
+ data["slurm_parameters"] = SlurmParameters()
340
+ return data
341
+
342
+ @field_validator("resource", mode="before")
343
+ @classmethod
344
+ def validate_resource(cls, v):
345
+ """
346
+ Validate and convert resource input to a Resource object.
347
+
348
+ Accepts string, Resource objects, and dictionary representations.
349
+ If a string is provided, converts it to a Resource object.
350
+ If a dictionary is provided (e.g., during deserialization),
351
+ extracts the value and creates a Resource object.
352
+
353
+ Args:
354
+ v: The value to validate (str, Resource, or dict)
355
+
356
+ Returns:
357
+ Resource: The validated Resource object
358
+ """
359
+ if isinstance(v, str):
360
+ return Resource(value=v)
361
+ elif isinstance(v, Resource):
362
+ return v
363
+ elif isinstance(v, dict):
364
+ # Handle deserialization from the dictionary
365
+ if "value" in v:
366
+ return Resource(value=v["value"])
367
+ else:
368
+ raise ValueError(
369
+ f"Dictionary representation of resource must contain 'value' key, got {v}"
370
+ )
371
+ else:
372
+ raise ValueError(
373
+ f"resource must be a string, Resource object, or dictionary, got {type(v)}"
374
+ )
375
+
376
+ # # property getters and setters to handle the slurm_parameters
377
+ # @property
378
+ # def slurm_parameters(self) -> Optional[SlurmParameters]:
379
+ # if not self.slurm_parameters_data:
380
+ # default_slurm = SlurmParameters()
381
+ # self.slurm_parameters_data = default_slurm.model_dump()
382
+ # return SlurmParameters(**self.slurm_parameters_data)
383
+ #
384
+ # @slurm_parameters.setter
385
+ # def slurm_parameters(self, value: Optional[SlurmParameters]) -> None:
386
+ # if value is None:
387
+ # self.slurm_parameters_data = {}
388
+ # else:
389
+ # self.slurm_parameters_data = value.model_dump()
390
+ #
391
+ # # Add any convenience methods to work with slurm parameters
392
+ # def set_slurm_config(self, **kwargs):
393
+ # """Update slurm parameters with the given keyword arguments."""
394
+ # current = self.slurm_parameters
395
+ # if current is None:
396
+ # current = SlurmParameters()
397
+ #
398
+ # for key, value in kwargs.items():
399
+ # if hasattr(current, key):
400
+ # setattr(current, key, value)
401
+ #
402
+ # self.slurm_parameters = current
@@ -0,0 +1,300 @@
1
+ import logging
2
+ import sys
3
+ import types
4
+ from typing import Optional, Type, Any, Callable
5
+
6
+ import cloudpickle
7
+ from odmantic import Model
8
+ from simstack.util.b64mixin import BytesB64Mixin
9
+
10
+ logger = logging.getLogger("PickleModels")
11
+
12
+
13
+ class ClassPickle(BytesB64Mixin, Model):
14
+ """
15
+ Persist an *arbitrary Python class* in MongoDB.
16
+
17
+ Fields
18
+ ------
19
+ name – class __name__ (for reference / debugging)
20
+ module_path – original module path (dotted)
21
+ pickle_data – base64-encoded pickled bytes of the class
22
+
23
+ Methods
24
+ -------
25
+ store_class(cls)
26
+ Serialise and save the given class into `pickle_data`.
27
+ load_class()
28
+ Reconstruct the class object from `pickle_data`.
29
+ """
30
+
31
+ name: str
32
+ module_path: str
33
+ pickle_data: Optional[bytes] = None
34
+
35
+ def store_class(self, cls: Type[Any]) -> None:
36
+ self.name = cls.__name__
37
+ self.module_path = cls.__module__
38
+ self.pickle_data = cloudpickle.dumps(cls)
39
+ logger.debug("Stored class %s (%s)", self.name, self.module_path)
40
+
41
+ def load_class(self) -> Type[Any]:
42
+ if self.pickle_data is None:
43
+ raise ValueError("pickle_data is empty")
44
+ return cloudpickle.loads(self.pickle_data)
45
+
46
+
47
+ class FunctionPickle(BytesB64Mixin, Model):
48
+ """
49
+ Persist an *arbitrary Python function* in MongoDB.
50
+
51
+ Fields
52
+ ------
53
+ name – function __name__
54
+ module_path – original module path
55
+ pickle_data – base64 pickled bytes of the function
56
+ """
57
+
58
+ name: str
59
+ module_path: str
60
+ pickle_data: Optional[bytes] = None
61
+
62
+ def _is_problematic_object(self, obj) -> bool:
63
+ """Check if an object is problematic for pickling."""
64
+ try:
65
+ # Quick type checks
66
+ # if isinstance(obj, (asyncio.Future, concurrent.futures.Future, asyncio.AbstractEventLoop)):
67
+ # return True
68
+ #
69
+ # Check type name for Windows-specific objects
70
+ type_name = type(obj).__name__
71
+ problematic_types = {
72
+ "_OverlappedFuture",
73
+ "ProactorEventLoop",
74
+ "WindowsProactorEventLoopPolicy",
75
+ "_ProactorBasePipeTransport",
76
+ "_ProactorSocketTransport",
77
+ "Handle",
78
+ "_WindowsSelectorEventLoop",
79
+ "_ProactorReadPipeTransport",
80
+ }
81
+
82
+ if type_name in problematic_types:
83
+ return True
84
+
85
+ # Check module origin
86
+ if hasattr(obj, "__class__") and hasattr(obj.__class__, "__module__"):
87
+ module_name = obj.__class__.__module__ or ""
88
+ if any(x in module_name for x in ["_overlapped", "_winapi"]):
89
+ return True
90
+
91
+ return False
92
+
93
+ except Exception:
94
+ return True # If we can't inspect it safely, consider it problematic
95
+
96
+ def _clean_object_recursively(self, obj, seen=None):
97
+ """Recursively clean an object, removing problematic references."""
98
+ if seen is None:
99
+ seen = set()
100
+
101
+ # Avoid infinite recursion
102
+ obj_id = id(obj)
103
+ if obj_id in seen:
104
+ return obj
105
+ seen.add(obj_id)
106
+
107
+ try:
108
+ if self._is_problematic_object(obj):
109
+ return None
110
+
111
+ # Handle different types
112
+ if isinstance(obj, dict):
113
+ cleaned = {}
114
+ for key, value in obj.items():
115
+ if not self._is_problematic_object(value):
116
+ cleaned_value = self._clean_object_recursively(value, seen)
117
+ if cleaned_value is not None:
118
+ cleaned[key] = cleaned_value
119
+ return cleaned
120
+
121
+ elif isinstance(obj, (list, tuple)):
122
+ cleaned_items = []
123
+ for item in obj:
124
+ if not self._is_problematic_object(item):
125
+ cleaned_item = self._clean_object_recursively(item, seen)
126
+ if cleaned_item is not None:
127
+ cleaned_items.append(cleaned_item)
128
+ return type(obj)(cleaned_items)
129
+
130
+ # For other objects, try to pickle test
131
+ try:
132
+ cloudpickle.dumps(obj)
133
+ return obj
134
+ except Exception:
135
+ return None
136
+
137
+ except Exception:
138
+ return None
139
+
140
+ def _clean_globals(self, func_globals: dict) -> dict:
141
+ """Remove unpickleable objects from function globals."""
142
+ cleaned_globals = {}
143
+
144
+ # Essential builtins that we need to keep
145
+ essential_builtins = {
146
+ "__builtins__",
147
+ "__name__",
148
+ "__doc__",
149
+ "__package__",
150
+ "print",
151
+ "len",
152
+ "str",
153
+ "int",
154
+ "float",
155
+ "bool",
156
+ "dict",
157
+ "list",
158
+ "tuple",
159
+ "Exception",
160
+ "ValueError",
161
+ "TypeError",
162
+ "AttributeError",
163
+ }
164
+
165
+ for key, value in func_globals.items():
166
+ try:
167
+ # Always skip certain names
168
+ skip_names = {"_OverlappedFuture", "_overlapped", "_winapi"}
169
+
170
+ if any(skip_name in key.lower() for skip_name in skip_names):
171
+ continue
172
+
173
+ # Handle builtins specially
174
+ if key == "__builtins__":
175
+ if isinstance(value, dict):
176
+ # Keep only essential builtins
177
+ cleaned_builtins = {
178
+ k: v
179
+ for k, v in value.items()
180
+ if k in essential_builtins
181
+ and not self._is_problematic_object(v)
182
+ }
183
+ cleaned_globals[key] = cleaned_builtins
184
+ continue
185
+
186
+ # Skip modules entirely to avoid complex dependencies
187
+ if isinstance(value, types.ModuleType):
188
+ continue
189
+
190
+ # Deep clean the object
191
+ cleaned_value = self._clean_object_recursively(value)
192
+ if cleaned_value is not None:
193
+ # Final pickle test
194
+ try:
195
+ cloudpickle.dumps(cleaned_value)
196
+ cleaned_globals[key] = cleaned_value
197
+ except Exception as e:
198
+ logger.debug(
199
+ f"Skipping '{key}' after cleaning failed pickle test: {e}"
200
+ )
201
+ else:
202
+ logger.debug(f"Skipping '{key}': cleaned to None")
203
+
204
+ except Exception as e:
205
+ logger.debug(f"Error processing global '{key}': {e}")
206
+ continue
207
+
208
+ return cleaned_globals
209
+
210
+ def _clean_closure(self, closure):
211
+ """Clean closure variables of problematic objects."""
212
+ if not closure:
213
+ return closure
214
+
215
+ cleaned_closure = []
216
+ for cell in closure:
217
+ try:
218
+ cell_contents = cell.cell_contents
219
+ if not self._is_problematic_object(cell_contents):
220
+ cleaned_contents = self._clean_object_recursively(cell_contents)
221
+ if cleaned_contents is not None:
222
+ # Create new cell with cleaned contents
223
+ new_cell = types.CellType(cleaned_contents)
224
+ cleaned_closure.append(new_cell)
225
+ else:
226
+ # Create cell with None if we had to remove the contents
227
+ cleaned_closure.append(types.CellType(None))
228
+ else:
229
+ # Replace problematic closure variable with None
230
+ cleaned_closure.append(types.CellType(None))
231
+ except (ValueError, AttributeError):
232
+ # Cell is empty or has issues, create empty cell
233
+ cleaned_closure.append(types.CellType(None))
234
+ except Exception as e:
235
+ logger.debug(f"Error cleaning closure cell: {e}")
236
+ cleaned_closure.append(types.CellType(None))
237
+
238
+ return tuple(cleaned_closure) if cleaned_closure else None
239
+
240
+ def store_function(self, func: Callable) -> None:
241
+ self.name = func.__name__
242
+ self.module_path = func.__module__
243
+
244
+ try:
245
+ # Clean the function's globals and closure
246
+ cleaned_globals = self._clean_globals(func.__globals__)
247
+ cleaned_closure = self._clean_closure(func.__closure__)
248
+
249
+ # Create a copy of the function with cleaned components
250
+ func_copy = types.FunctionType(
251
+ func.__code__,
252
+ cleaned_globals,
253
+ func.__name__,
254
+ func.__defaults__,
255
+ cleaned_closure,
256
+ )
257
+ func_copy.__module__ = "__pickled_function__"
258
+
259
+ # Final test - try to pickle the cleaned function
260
+ test_pickle = cloudpickle.dumps(func_copy)
261
+
262
+ self.pickle_data = test_pickle
263
+ logger.debug("Stored function %s (%s)", self.name, self.module_path)
264
+
265
+ except Exception as e:
266
+ logger.error(f"Failed to pickle function {self.name}: {e}")
267
+ self.pickle_data = None
268
+ logger.warning(
269
+ f"Function {self.name} will use regular import instead of pickle"
270
+ )
271
+
272
+ def load_function(self) -> Callable:
273
+ if self.pickle_data is None:
274
+ raise ValueError("pickle_data is empty")
275
+
276
+ # Create a dummy module to satisfy cloudpickle's import requirements
277
+ if "__pickled_function__" not in sys.modules:
278
+ dummy_module = types.ModuleType("__pickled_function__")
279
+ sys.modules["__pickled_function__"] = dummy_module
280
+
281
+ func = cloudpickle.loads(self.pickle_data)
282
+
283
+ # Ensure the function has the correct __name__ attribute
284
+ if not hasattr(func, "__name__") or func.__name__ is None:
285
+ func.__name__ = self.name
286
+ logger.debug(f"Restored missing __name__ attribute: {self.name}")
287
+
288
+ # Ensure other essential attributes are present
289
+ if not hasattr(func, "__module__") or func.__module__ is None:
290
+ func.__module__ = self.module_path
291
+ logger.debug(f"Restored missing __module__ attribute: {self.module_path}")
292
+
293
+ # Ensure __qualname__ exists (needed for some introspection)
294
+ if not hasattr(func, "__qualname__"):
295
+ func.__qualname__ = self.name
296
+
297
+ logger.debug(
298
+ "Loaded function %s (%s)", self.name, getattr(func, "__name__", "unnamed")
299
+ )
300
+ return func