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,517 @@
1
+ import inspect
2
+ import logging
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Callable, List, get_type_hints, Dict, Any, Type
6
+
7
+
8
+ from simstack.core.simstack_result import SimstackResult
9
+ from simstack.core.context import context
10
+ from simstack.tables.node_children import update_node_children
11
+ from simstack.models import Parameters
12
+ from simstack.models.models import NodeModel, ModelMapping, DataMapping
13
+ from simstack.tables.table_builder import TableBuilderBase
14
+ from simstack.util.db import Database
15
+ from simstack.util.docstring_parser import DocstringParser
16
+ from simstack.util.importer import import_class_by_name
17
+
18
+ logger = logging.getLogger("NodeTable")
19
+
20
+
21
+ def is_node_function(func: Callable[..., Any]) -> bool:
22
+ """Check if a function is marked as a node using the @node decorator."""
23
+ return hasattr(func, "_is_node") and getattr(func, "_is_node", False) is True
24
+
25
+
26
+ class CreateNodeTable(TableBuilderBase):
27
+ """
28
+ Helper class to build the node table without passing around many parameters.
29
+
30
+ Usage:
31
+ creator = CreateNodeTable(database)
32
+ await creator.make_node_table()
33
+ """
34
+
35
+ @property
36
+ def logger(self) -> logging.Logger:
37
+ return logger
38
+
39
+ async def build(self, *args, **kwargs) -> None:
40
+ if not context.initialized:
41
+ await context.initialize()
42
+ await context.refresh_mappings(models=True, nodes=False)
43
+ await super().build(*args, **kwargs)
44
+ await context.refresh_mappings(models=False, nodes=True)
45
+
46
+ async def _process_module(self, module: Any, drops: str) -> None:
47
+ await self._register_nodes_from_module(module, drops)
48
+
49
+ def _discover_module_functions(
50
+ self, module: Any
51
+ ) -> List[tuple[str, Callable[..., Any]]]:
52
+ """
53
+ Return (name, func) for functions that are defined in `module` (not imported).
54
+ """
55
+ functions: List[tuple[str, Callable[..., Any]]] = inspect.getmembers(
56
+ module, inspect.isfunction
57
+ )
58
+ module_name = module.__name__
59
+ return [
60
+ (func_name, func)
61
+ for func_name, func in functions
62
+ if func.__module__ == module_name
63
+ ]
64
+
65
+ def _build_inputs(
66
+ self,
67
+ sig: inspect.Signature,
68
+ type_hints: Dict[str, Any],
69
+ doc_params: Dict[str, Any] | None,
70
+ ) -> List[Dict[str, Any]]:
71
+ inputs: List[Dict[str, Any]] = []
72
+ for param_name, param in sig.parameters.items():
73
+ if param_name == "self": # Skip self parameter for methods
74
+ continue
75
+
76
+ try:
77
+ param_info: Dict[str, Any] = {
78
+ "name": param_name,
79
+ "type": type_hints.get(param_name, param.annotation.__name__),
80
+ "type_str": str(
81
+ type_hints.get(
82
+ param_name,
83
+ param.annotation.__name__
84
+ if param.annotation != inspect.Parameter.empty
85
+ else "Any",
86
+ )
87
+ ),
88
+ }
89
+ except AttributeError as e:
90
+ logger.error(f"Could not parse type for {param_name}: {e}")
91
+ param_info = {}
92
+
93
+ if doc_params and param_name in doc_params:
94
+ param_info["description"] = doc_params[param_name].get("description")
95
+
96
+ if param.default != inspect.Parameter.empty:
97
+ param_info["default"] = param.default
98
+
99
+ inputs.append(param_info)
100
+
101
+ return inputs
102
+
103
+ def _parse_generic_type(self, type_str: str) -> tuple[str | None, str | None]:
104
+ """
105
+ Parse generic types like List[X] or Dict[str,X] and extract the inner type.
106
+ Returns (wrapper, inner_type) where wrapper is 'List' or 'Dict' and inner_type is the model type.
107
+ """
108
+ list_match = re.match(r"List\[(.*?)\]", type_str)
109
+ if list_match:
110
+ return "List", list_match.group(1)
111
+
112
+ dict_match = re.match(r"Dict\[str,\s*(.*?)\]", type_str)
113
+ if dict_match:
114
+ return "Dict", dict_match.group(1)
115
+
116
+ return None, None
117
+
118
+ @staticmethod
119
+ def _normalize_docstring_type(type_str: str) -> str:
120
+ """Strip top-level docstring qualifiers like ', optional' from a parsed type string."""
121
+ normalized = (type_str or "").strip()
122
+ bracket_depth = 0
123
+
124
+ for index, char in enumerate(normalized):
125
+ if char == "[":
126
+ bracket_depth += 1
127
+ elif char == "]" and bracket_depth > 0:
128
+ bracket_depth -= 1
129
+ elif char == "," and bracket_depth == 0:
130
+ return normalized[:index].strip()
131
+
132
+ return normalized
133
+
134
+ async def _build_outputs(
135
+ self,
136
+ func_name: str,
137
+ type_hints: Dict[str, Any],
138
+ parser: DocstringParser,
139
+ drops: str,
140
+ ) -> List[DataMapping]:
141
+ outputs: List[Dict[str, Any]] = []
142
+ return_type = type_hints.get("return", None)
143
+ doc_returns = parser.returns()
144
+ if return_type and return_type != type(None): # Check for actual return type
145
+ output_info: Dict[str, Any] = {
146
+ "name": "result",
147
+ "type_str": str(return_type),
148
+ "type": return_type,
149
+ }
150
+ if doc_returns:
151
+ output_info["description"] = doc_returns.get("description")
152
+ outputs.append(output_info)
153
+
154
+ returns_simstack_result = any(
155
+ output["type"] == SimstackResult for output in outputs
156
+ )
157
+ result_mappings = []
158
+ db = context.db
159
+ if returns_simstack_result:
160
+ if len(outputs) > 1:
161
+ logger.warning(
162
+ f"Node {func_name} returns more than one output, one of which is SimstackResult"
163
+ )
164
+ else:
165
+ doc_simstack_result = parser.simstack_results()
166
+ if doc_simstack_result is None:
167
+ logger.warning(f"The docstring of {func_name} does not defines its SimstackResult outputs")
168
+ else:
169
+ for name, data in doc_simstack_result.items():
170
+ output_mapping = None
171
+ output_type = self._normalize_docstring_type(data["type"])
172
+ try:
173
+ wrapper, inner_type_str = self._parse_generic_type(
174
+ output_type
175
+ )
176
+ if wrapper:
177
+ if inner_type_str is None:
178
+ raise ValueError(
179
+ f"Could not parse inner type for '{output_type}'"
180
+ )
181
+ # Handle List[type] or Dict[str,type]
182
+ inner_mapping = None
183
+ if "." in inner_type_str:
184
+ inner_mapping = inner_type_str
185
+ else:
186
+ try:
187
+ inner_model = await import_class_by_name(
188
+ inner_type_str, db
189
+ )
190
+ except (ValueError, LookupError):
191
+ inner_model = None
192
+ if inner_model is not None:
193
+ inner_mapping = self.get_class_mapping(
194
+ inner_model, drops
195
+ )
196
+ if inner_mapping is not None:
197
+ if wrapper == "List":
198
+ output_mapping = f"List[{inner_mapping}]"
199
+ else: # Dict
200
+ output_mapping = f"Dict[str,{inner_mapping}]"
201
+ elif "." in output_type:
202
+ # It's a full mapping path
203
+ output_mapping = output_type
204
+ else:
205
+ # It's a single class name
206
+ try:
207
+ output_model = await import_class_by_name(
208
+ output_type, db
209
+ )
210
+ output_mapping = self.get_class_mapping(
211
+ output_model, drops
212
+ )
213
+ except (ValueError, LookupError) as e:
214
+ logger.error(
215
+ f"Could not parse '{data['type']}' to mapping: {e}"
216
+ )
217
+ output_mapping = None
218
+ if output_mapping is not None:
219
+ result_mappings.append(
220
+ DataMapping(
221
+ name=name,
222
+ mapping=output_mapping,
223
+ description=data.get("description"),
224
+ )
225
+ )
226
+ except (ValueError, LookupError) as e:
227
+ logger.error(
228
+ f"Could not parse '{data['type']}' to mapping: {e}"
229
+ )
230
+ else: # not a SimstackResult
231
+ for output in outputs:
232
+ try:
233
+ output_type = output["type"]
234
+ if isinstance(output_type, str):
235
+ output_mapping = output_type
236
+ if " | None" in output_mapping:
237
+ output_mapping = output_mapping.replace(" | None", "")
238
+ else:
239
+ # Handle Optional[T] / T | None
240
+ from typing import get_args, get_origin, Union
241
+ import types
242
+ origin = get_origin(output_type)
243
+ if origin is types.UnionType or origin is Union:
244
+ args = get_args(output_type)
245
+ if type(None) in args:
246
+ # It's an Optional, take the first non-None argument
247
+ output_type = next(arg for arg in args if arg is not type(None))
248
+ output_mapping = self.get_class_mapping(output_type, drops)
249
+ result_mappings.append(
250
+ DataMapping(
251
+ name=output["name"],
252
+ mapping=output_mapping,
253
+ description=output.get("description"),
254
+ )
255
+ )
256
+ except ValueError:
257
+ logger.error(f"Could not parse '{output['type']}' to mapping in {func_name}")
258
+ return result_mappings
259
+
260
+ def _extract_default_parameters(self, func: Callable[..., Any]) -> Parameters:
261
+ """
262
+ Best-effort extraction of node Parameters from either a direct attribute
263
+ or from closure variables. Always returns a non-None Parameters().
264
+ """
265
+ parameters = Parameters()
266
+
267
+ if hasattr(func, "_node_parameters"):
268
+ return func._node_parameters
269
+
270
+ closures = inspect.getclosurevars(func)
271
+ for name, values in closures._asdict().items():
272
+ if name == "nonlocals":
273
+ continue
274
+ if isinstance(values, dict):
275
+ kwargs_node = values.get("kwargs_node", None)
276
+ if kwargs_node and "parameters" in kwargs_node:
277
+ return kwargs_node["parameters"]
278
+
279
+ return parameters
280
+
281
+ async def _delete_existing_node_model_if_needed(
282
+ self,
283
+ node_name: str,
284
+ function_mapping: str,
285
+ ) -> tuple[bool, bool]:
286
+ """
287
+ If a NodeModel with the same name exists, delete it (and its pickle if present),
288
+ but preserve the 'favorite' flag for the new entry.
289
+
290
+ Returns:
291
+ (should_skip, existing_favorite)
292
+ """
293
+ try:
294
+ existing_model = await self.db.find_one(
295
+ NodeModel, NodeModel.name == node_name
296
+ )
297
+ except Exception as e:
298
+ logger.error(f"Error finding existing NodeModel {node_name}: {e}")
299
+ return False, False
300
+
301
+ if not existing_model:
302
+ return False, False
303
+
304
+ if function_mapping != existing_model.function_mapping:
305
+ logger.error(
306
+ f"NodeModel '{node_name}' already exists in the database\n"
307
+ f" DB Mapping: {existing_model.function_mapping}\n"
308
+ f" New Mapping: {function_mapping}\n"
309
+ f"New Mapping will overwrite DB Mapping."
310
+ )
311
+
312
+ existing_favorite = getattr(existing_model, "favorite", False)
313
+
314
+ if getattr(existing_model, "pickle_function", None):
315
+ try:
316
+ await self.db.delete(existing_model.pickle_function)
317
+ except Exception as e:
318
+ logger.error(f"Error deleting FunctionPickle for {node_name}: {e}")
319
+
320
+ try:
321
+ await self.db.delete(existing_model)
322
+ except Exception as e:
323
+ logger.error(f"Error deleting existing NodeModel {node_name}: {e}")
324
+
325
+ return False, existing_favorite
326
+
327
+ async def _resolve_input_mappings(
328
+ self,
329
+ node_name: str,
330
+ inputs: List[Dict[str, Any]],
331
+ drops: str,
332
+ ) -> List[str]:
333
+ """
334
+ Convert input python types to ModelMapping.mapping strings and validate their existence.
335
+ """
336
+ input_mappings: List[str] = []
337
+ if not inputs:
338
+ return input_mappings
339
+
340
+ try:
341
+ for specific_input in inputs:
342
+ if (
343
+ specific_input.get("type")
344
+ and hasattr(specific_input["type"], "__module__")
345
+ and hasattr(specific_input["type"], "__name__")
346
+ ):
347
+ input_mapping = (
348
+ specific_input["type"].__module__
349
+ + "."
350
+ + specific_input["type"].__name__
351
+ )
352
+
353
+ if drops and input_mapping.startswith(drops + "."):
354
+ input_mapping = input_mapping[len(drops) + 1 :]
355
+
356
+ input_mapping_found = await self.db.find_one(
357
+ ModelMapping, ModelMapping.mapping == input_mapping
358
+ )
359
+ if not input_mapping_found and input_mapping:
360
+ logger.error(
361
+ f"Processing node: {node_name} model {input_mapping} not found in db!"
362
+ )
363
+
364
+ input_mappings.append(input_mapping)
365
+ except Exception as e:
366
+ logger.error(f"Error getting input mapping: {e}")
367
+
368
+ return input_mappings
369
+
370
+ def get_class_mapping(self, type: Type, drops: str = "") -> str:
371
+ """Return the class mapping for a given type, optionally dropping a prefix."""
372
+ if hasattr(type, "__module__") and hasattr(type, "__name__"):
373
+ mapping = type.__module__ + "." + type.__name__
374
+ if drops and mapping.startswith(drops + "."):
375
+ mapping = mapping[len(drops) + 1 :]
376
+ return mapping
377
+ else:
378
+ raise ValueError(f"Could not parse '{type}' to mapping")
379
+
380
+ async def _register_nodes_from_module(self, module: Any, drops: str) -> None:
381
+ """
382
+ Core logic to discover node functions in a module and (re)create NodeModel entries.
383
+
384
+ Heuristic:
385
+ - All top-level callables (functions) whose names do not start with '_'
386
+ - Only functions actually defined in this module.
387
+ """
388
+ functions = self._discover_module_functions(module)
389
+
390
+ for func_name, func in functions:
391
+ if not is_node_function(func):
392
+ continue
393
+
394
+ sig = inspect.signature(func)
395
+
396
+ parser = DocstringParser(inspect.getdoc(func))
397
+ doc_description = parser.description()
398
+ doc_params = parser.params()
399
+
400
+ type_hints = get_type_hints(func)
401
+
402
+ inputs = self._build_inputs(sig, type_hints, doc_params)
403
+
404
+ parameters = self._extract_default_parameters(func)
405
+ node_name = getattr(func, "_node_name", func_name)
406
+ node_description = getattr(func, "_node_description", doc_description or "")
407
+
408
+ if not inputs:
409
+ logger.warning(
410
+ f"{node_name} has no inputs -- this means the node will be executed only once."
411
+ )
412
+
413
+ input_mappings = await self._resolve_input_mappings(
414
+ node_name, inputs, drops
415
+ )
416
+ function_mapping = module.__name__ + "." + func_name
417
+ try:
418
+ (
419
+ should_skip,
420
+ existing_favorite,
421
+ ) = await self._delete_existing_node_model_if_needed(
422
+ node_name, function_mapping
423
+ )
424
+ if should_skip:
425
+ continue
426
+
427
+ data_mappings = []
428
+ for data_input, input_mapping in zip(inputs, input_mappings):
429
+ if data_input.get("type") and hasattr(
430
+ data_input["type"], "__name__"
431
+ ):
432
+ data_input_mapping = self.get_class_mapping(
433
+ data_input["type"], drops
434
+ )
435
+ if data_input_mapping != input_mapping:
436
+ self.logger.error(
437
+ f"Type mismatch for input '{data_input['name']}': expected '{data_input['type'].__name__}', got '{input_mapping}'"
438
+ )
439
+ else:
440
+ self.logger.error(
441
+ f"No type specified for input '{data_input['name']}'"
442
+ )
443
+ data_mappings.append(
444
+ DataMapping(
445
+ name=data_input["name"],
446
+ mapping=input_mapping,
447
+ description=data_input.get("description"),
448
+ )
449
+ )
450
+
451
+ result_mappings = await self._build_outputs(
452
+ func_name, type_hints, parser, drops
453
+ )
454
+
455
+ if node_name is None:
456
+ raise ValueError(f"Node {func_name} has no name")
457
+
458
+ node_model = NodeModel(
459
+ name=node_name,
460
+ function_mapping=function_mapping,
461
+ description=node_description,
462
+ input_mappings=data_mappings,
463
+ result_mappings=result_mappings,
464
+ called_nodes=[], # we need to first build the full list, will be filled in second_stage
465
+ default_parameters=parameters,
466
+ pickle_function=None,
467
+ favorite=existing_favorite,
468
+ )
469
+
470
+ logger.debug(
471
+ f"NodeModel: {node_model.name}, {node_model.function_mapping}, {node_model.input_mappings}"
472
+ )
473
+ await self.db.save(node_model)
474
+
475
+ except Exception as e:
476
+ logger.error(f"Error creating/saving NodeModel {node_name}: {e}")
477
+ import traceback
478
+
479
+ traceback.print_exc()
480
+
481
+ async def second_stage(self, drops: str) -> None:
482
+ await update_node_children(self.db, drops)
483
+
484
+ async def clear_table(self) -> None:
485
+ self.logger.info("Clearing NodeModel collection")
486
+ await self.db.get_collection(NodeModel).drop()
487
+
488
+
489
+ async def make_node_table(
490
+ db: Database,
491
+ dirs: list[str] | None = None,
492
+ drops: str | None = None,
493
+ write_schema: bool = False,
494
+ clear: bool = False,
495
+ project_root: Path | None = None,
496
+ ignore_entrypoints: bool = False,
497
+ ) -> None:
498
+ """
499
+ Rebuild the node table using the given databse.
500
+
501
+ This is a thin wrapper around CreateNodeTable for backward compatibility.
502
+ """
503
+ creator = CreateNodeTable(db, write_schema=write_schema, project_root=project_root)
504
+ await creator.build(dirs=dirs, drops=drops, clear=clear, ignore_entrypoints=ignore_entrypoints)
505
+
506
+
507
+ def create_node_table_main() -> None:
508
+ """
509
+ CLI-style entry point to (re)build the node table.
510
+
511
+ Uses a dedicated event loop, matching the pattern used for model table creation.
512
+ """
513
+ TableBuilderBase.cli_main(CreateNodeTable)
514
+
515
+
516
+ if __name__ == "__main__":
517
+ create_node_table_main()