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,837 @@
1
+ from datetime import datetime
2
+ from typing import Union, Dict, Any, List, Optional, Set
3
+
4
+ from odmantic import Model
5
+
6
+
7
+ def make_table_entries_helper(
8
+ model_instance,
9
+ table_name=None,
10
+ max_recursion_level=1,
11
+ drop_id=True,
12
+ current_level=0,
13
+ visited=None,
14
+ field_prefix="",
15
+ ):
16
+ """
17
+ Create table data for AG Grid from a model instance.
18
+ Handles datetime objects properly and can optionally drop ID fields.
19
+
20
+ Args:
21
+ model_instance: The model instance to process
22
+ table_name: Optional name for the table (default: None, uses class name)
23
+ max_recursion_level: Maximum depth for processing nested models (default: 1)
24
+ drop_id: Whether to drop the ID field (default: True)
25
+ current_level: Current recursion level (default: 0)
26
+ visited: Set of objects already visited to prevent infinite recursion (default: None)
27
+ field_prefix: Prefix for field paths in nested structures (default: "")
28
+
29
+ Returns:
30
+ dict: Dictionary with 'tableName' and 'tableData' (rows data for AG Grid)
31
+ """
32
+ # Initialize the visited set to track objects for preventing infinite recursion
33
+ if visited is None:
34
+ visited = set()
35
+
36
+ # If the instance has already been visited, stop recursion
37
+ if id(model_instance) in visited:
38
+ return {"tableData": {}}
39
+
40
+ # Mark this instance as visited
41
+ visited.add(id(model_instance))
42
+
43
+ # Get model class and fields
44
+ model_class = type(model_instance)
45
+
46
+ if hasattr(model_instance, "make_table_entries"):
47
+ field_prefix = f"{field_prefix}." if field_prefix else ""
48
+ field_prefix = (
49
+ f"{field_prefix}{model_instance.__class__.__name__}"
50
+ if field_prefix
51
+ else model_instance.__class__.__name__
52
+ )
53
+ return model_instance.make_table_entries(
54
+ max_recursion_level=1,
55
+ drop_id=True,
56
+ current_level=0,
57
+ visited=None,
58
+ field_prefix="",
59
+ )
60
+
61
+ model_fields = getattr(model_instance, "model_fields", {})
62
+
63
+ # Use class name if table_name not provided
64
+ if table_name is None:
65
+ table_name = model_class.__name__
66
+
67
+ # Initialize empty summary dictionary
68
+ summary = {}
69
+
70
+ # Process each field
71
+ for field_name, field_type in model_fields.items():
72
+ # Skip ID fields if drop_id is True
73
+ if drop_id and (field_name == "id" or field_name == "_id"):
74
+ continue
75
+
76
+ # Calculate the full field path for AG Grid
77
+ field_path = f"{field_prefix}.{field_name}" if field_prefix else field_name
78
+
79
+ # Get field value
80
+ field_value = getattr(model_instance, field_name, None)
81
+
82
+ # If the field value is None, add it to summary
83
+ if field_value is None:
84
+ summary[field_name] = None
85
+ continue
86
+
87
+ # Handle datetime objects for AG Grid
88
+ if isinstance(field_value, datetime):
89
+ summary[field_name] = field_value.isoformat()
90
+ continue
91
+
92
+ # Check if the field is a nested model
93
+ is_nested_model = hasattr(field_value, "model_fields")
94
+
95
+ # If the recursion level is 0, exclude all nested objects
96
+ if max_recursion_level == 0 and is_nested_model:
97
+ continue
98
+
99
+ # Handle nested model if it exists and we haven't reached max recursion
100
+ if is_nested_model and current_level < max_recursion_level:
101
+ # Check if the object has its own make_table method
102
+ if hasattr(field_value, "make_table_entries"):
103
+ nested_result = field_value.make_table_entries(
104
+ max_recursion_level=max_recursion_level,
105
+ current_level=current_level + 1,
106
+ visited=visited,
107
+ field_prefix=f"{field_path}.",
108
+ )
109
+
110
+ if isinstance(nested_result, dict) and "tableData" in nested_result:
111
+ nested_summary = nested_result["tableData"]
112
+ else:
113
+ # If make_table doesn't return expected format
114
+ nested_summary = nested_result
115
+ else:
116
+ # Process the nested model recursively
117
+ nested_result = make_table_entries_helper(
118
+ field_value,
119
+ f"{table_name}_{field_name}",
120
+ max_recursion_level,
121
+ drop_id,
122
+ current_level + 1,
123
+ visited,
124
+ f"{field_path}.",
125
+ )
126
+ nested_summary = nested_result
127
+
128
+ # Add the nested object as a sub-dictionary
129
+ summary[field_name] = nested_summary
130
+
131
+ elif field_type == "self" and current_level < max_recursion_level:
132
+ # Handle self-referential field, but only go one level deep to avoid cycles
133
+ raise ValueError(
134
+ f"Self-referential field '{field_name}' is not supported in make_table_entries."
135
+ )
136
+ if field_value is not None and id(field_value) not in visited:
137
+ # Process only the direct fields of the self-reference
138
+ nested_result = make_table_entries_helper(
139
+ field_value,
140
+ f"{table_name}_{field_name}",
141
+ 0, # Only process direct fields, no further recursion
142
+ drop_id,
143
+ current_level + 1,
144
+ visited,
145
+ f"{field_path}.",
146
+ )
147
+ nested_summary = nested_result["tableData"]
148
+ summary[field_name] = nested_summary
149
+ else:
150
+ # Handle lists/arrays
151
+ if isinstance(field_value, list):
152
+ processed_list = []
153
+ for item in field_value:
154
+ if isinstance(item, datetime):
155
+ processed_list.append(item.isoformat())
156
+ elif (
157
+ hasattr(item, "model_fields")
158
+ and current_level < max_recursion_level
159
+ ):
160
+ # Handle nested models in lists
161
+ if hasattr(item, "make_table"):
162
+ nested_result = item.make_table_entries(
163
+ max_recursion_level=0,
164
+ current_level=current_level + 1,
165
+ visited=visited.copy(),
166
+ )
167
+
168
+ if (
169
+ isinstance(nested_result, dict)
170
+ and "tableData" in nested_result
171
+ ):
172
+ processed_list.append(nested_result["tableData"])
173
+ else:
174
+ processed_list.append(nested_result)
175
+ else:
176
+ nested_result = make_table_entries_helper(
177
+ item,
178
+ f"{table_name}_{field_name}_item",
179
+ 0,
180
+ drop_id,
181
+ current_level + 1,
182
+ visited.copy(),
183
+ "",
184
+ )
185
+
186
+ if (
187
+ isinstance(nested_result, dict)
188
+ and "tableData" in nested_result
189
+ ):
190
+ processed_list.append(nested_result["tableData"])
191
+ else:
192
+ processed_list.append(nested_result)
193
+
194
+ else:
195
+ processed_list.append(item)
196
+ summary[field_name] = processed_list
197
+ else:
198
+ # This is a simple field, add it directly
199
+ summary[field_name] = field_value
200
+
201
+ return summary
202
+
203
+
204
+ def is_pydantic_model(obj):
205
+ """
206
+ Check if an object is a Pydantic model class.
207
+
208
+ Args:
209
+ obj: The object to check
210
+
211
+ Returns:
212
+ bool: True if the object is a Pydantic model class, False otherwise
213
+ """
214
+ # For Pydantic V1
215
+ if hasattr(obj, "__fields__"):
216
+ return True
217
+
218
+ # For Pydantic V2
219
+ if hasattr(obj, "model_fields"):
220
+ return True
221
+
222
+ # Check for BaseModel inheritance (works for both V1 and V2)
223
+ if isinstance(obj, type):
224
+ from inspect import getmro
225
+
226
+ for base in getmro(obj):
227
+ if base.__name__ == "BaseModel":
228
+ return True
229
+
230
+ # Handle typing objects like Optional[Model]
231
+ if hasattr(obj, "__origin__") and hasattr(obj, "__args__"):
232
+ # Check if it's a Union type (which includes Optional)
233
+ from typing import _SpecialGenericAlias, _GenericAlias
234
+
235
+ if (
236
+ isinstance(obj, (_SpecialGenericAlias, _GenericAlias))
237
+ and obj.__origin__ is Union
238
+ ):
239
+ return any(is_pydantic_model(arg) for arg in obj.__args__)
240
+
241
+ return False
242
+
243
+
244
+ def make_column_defs_helper(
245
+ model_class,
246
+ table_name=None,
247
+ max_recursion_level=1,
248
+ drop_id=True,
249
+ current_level=0,
250
+ visited=None,
251
+ field_prefix="",
252
+ ):
253
+ """
254
+ Create column definitions for AG Grid based on a model class.
255
+ Uses the same logic as table data generation but for model class.
256
+ This is a helper function mapped to make_column_defs in simstack_model.
257
+ The idea is that most classes can call the helper function but some classes might
258
+ want to override it to return specific column definitions.
259
+
260
+ Args:
261
+ model_class: The model class to process
262
+ table_name: Optional name for the table (default: None, uses class name)
263
+ max_recursion_level: Maximum depth for processing nested models (default: 1)
264
+ drop_id: Whether to drop the ID field (default: True)
265
+ current_level: Current recursion level (default: 0)
266
+ visited: Set of objects already visited to prevent infinite recursion (default: None)
267
+ field_prefix: Prefix for field paths in nested structures (default: "")
268
+
269
+ Returns:
270
+ list: Column definitions for AG Grid
271
+ """
272
+ from typing import get_origin, get_args, List, Union
273
+
274
+ # Initialize a visited set to track classes for preventing infinite recursion
275
+ if visited is None:
276
+ visited = set()
277
+
278
+ # If the class has already been visited, stop recursion
279
+ if id(model_class) in visited:
280
+ return []
281
+
282
+ # Mark this class as visited
283
+ visited.add(id(model_class))
284
+
285
+ if hasattr(model_class, "make_column_defs"):
286
+ return model_class.make_column_defs(
287
+ model_class,
288
+ table_name=None,
289
+ max_recursion_level=1,
290
+ drop_id=True,
291
+ current_level=0,
292
+ visited=None,
293
+ field_prefix="",
294
+ )
295
+ # Get model fields
296
+ model_fields = getattr(
297
+ model_class, "model_fields", getattr(model_class, "__fields__", {})
298
+ )
299
+
300
+ # Use class name if table_name not provided
301
+ if table_name is None:
302
+ table_name = model_class.__name__
303
+
304
+ # Initialize column definitions
305
+ column_defs = []
306
+
307
+ # Process each field
308
+ for field_name, field_info in model_fields.items():
309
+ # Skip ID fields if drop_id is True
310
+ if drop_id and (field_name == "id" or field_name == "_id"):
311
+ continue
312
+
313
+ # Calculate the full field path for AG Grid
314
+ field_path = f"{field_prefix}{field_name}" if field_prefix else field_name
315
+
316
+ # Extract field type (different in V1 and V2)
317
+ if hasattr(field_info, "annotation"):
318
+ # Pydantic V2
319
+ field_type = field_info.annotation
320
+ elif hasattr(field_info, "type_"):
321
+ # Pydantic V1
322
+ field_type = field_info.type_
323
+ else:
324
+ # Fallback
325
+ field_type = None
326
+
327
+ # Check if the field type is a Pydantic model
328
+ is_nested_model = is_pydantic_model(field_type)
329
+
330
+ # Extract actual model class for nested types (like Optional[Model])
331
+ if is_nested_model and hasattr(field_type, "__args__"):
332
+ args = get_args(field_type)
333
+ for arg in args:
334
+ if is_pydantic_model(arg):
335
+ field_type = arg
336
+ break
337
+
338
+ # Create column definition based on the field type
339
+ column_def = create_column_def(field_name, field_type, None, field_path)
340
+
341
+ # Set object type for nested models
342
+ if is_nested_model:
343
+ column_def["type"] = "objectColumn"
344
+
345
+ # If recursion level is 0, exclude all nested objects but include column def for the field itself
346
+ if max_recursion_level == 0 and is_nested_model:
347
+ column_defs.append(column_def)
348
+ continue
349
+
350
+ # Handle nested model class if it exists and we haven't reached max recursion
351
+ if is_nested_model and current_level < max_recursion_level:
352
+ # Check if the class has its own make_columns method
353
+ if hasattr(field_type, "make_column_defs"):
354
+ nested_columns = field_type.make_column_defs(
355
+ max_recursion_level=max_recursion_level,
356
+ current_level=current_level + 1,
357
+ visited=visited.copy(),
358
+ )
359
+ else:
360
+ # Process the nested model class recursively
361
+ nested_columns = make_column_defs_helper(
362
+ field_type,
363
+ f"{table_name}_{field_name}",
364
+ max_recursion_level,
365
+ drop_id,
366
+ current_level + 1,
367
+ visited.copy(),
368
+ f"{field_path}.",
369
+ )
370
+
371
+ # Create a column group for nested fields if we have columns
372
+ if nested_columns:
373
+ group_column = {
374
+ "headerName": field_name.capitalize(),
375
+ "marryChildren": True,
376
+ "children": nested_columns,
377
+ }
378
+ column_defs.append(group_column)
379
+ else:
380
+ column_defs.append(column_def)
381
+ elif field_type == "self" and current_level < max_recursion_level:
382
+ # Handle self-referential field type
383
+ # Create a new visited set for this branch to avoid affecting other branches
384
+ self_visited = visited.copy()
385
+ if model_class not in self_visited:
386
+ # Process only the direct fields of the self-reference
387
+ nested_columns = make_column_defs_helper(
388
+ model_class,
389
+ f"{table_name}_{field_name}",
390
+ 0, # Only process direct fields, no further recursion
391
+ drop_id,
392
+ current_level + 1,
393
+ self_visited,
394
+ f"{field_path}.",
395
+ )
396
+
397
+ # Create a column group for self-referential fields
398
+ if nested_columns:
399
+ group_column = {
400
+ "headerName": field_name.capitalize(),
401
+ "marryChildren": True,
402
+ "children": nested_columns,
403
+ }
404
+ column_defs.append(group_column)
405
+ else:
406
+ column_defs.append(column_def)
407
+ else:
408
+ column_defs.append(column_def)
409
+ else:
410
+ # Handle lists/arrays
411
+ is_list_type = False
412
+ inner_type = None
413
+
414
+ # Check for various list types using get_origin and get_args
415
+ origin = get_origin(field_type)
416
+ if origin is list or origin is List:
417
+ is_list_type = True
418
+ args = get_args(field_type)
419
+ if args:
420
+ inner_type = args[0]
421
+ # Handle Optional[List[...]]
422
+ elif origin is Union:
423
+ args = get_args(field_type)
424
+ for arg in args:
425
+ arg_origin = get_origin(arg)
426
+ if arg_origin is list or arg_origin is List:
427
+ is_list_type = True
428
+ arg_args = get_args(arg)
429
+ if arg_args:
430
+ inner_type = arg_args[0]
431
+ break
432
+
433
+ if is_list_type:
434
+ # For lists, we add a special column definition
435
+ list_column_def = {
436
+ "headerName": field_name.capitalize(),
437
+ "field": field_path,
438
+ "cellRenderer": "agArrayCellRenderer",
439
+ }
440
+
441
+ # If the list contains models, we might want to indicate that
442
+ if inner_type and is_pydantic_model(inner_type):
443
+ list_column_def["type"] = "modelArrayColumn"
444
+
445
+ column_defs.append(list_column_def)
446
+ else:
447
+ # This is a simple field, add column definition directly
448
+ column_defs.append(column_def)
449
+
450
+ return column_defs
451
+
452
+
453
+ def make_column_defs_instance(
454
+ model_instance: Model,
455
+ table_name: Optional[str] = None,
456
+ max_recursion_level: int = 1,
457
+ drop_id: bool = True,
458
+ current_level: int = 0,
459
+ visited: Optional[Set[int]] = None,
460
+ field_prefix: str = "",
461
+ ) -> List[Dict[str, Any]]:
462
+ """
463
+ Create column definitions for AG Grid based on a model instance.
464
+ Uses the same logic as table data generation but works on an actual instance.
465
+ This allows for dynamic column generation based on actual data values.
466
+
467
+ Args:
468
+ model_instance: The model instance to process
469
+ table_name: Optional name for the table (default: None, uses class name)
470
+ max_recursion_level: Maximum depth for processing nested models (default: 1)
471
+ drop_id: Whether to drop the ID field (default: True)
472
+ current_level: Current recursion level (default: 0)
473
+ visited: Set of objects already visited to prevent infinite recursion (default: None)
474
+ field_prefix: Prefix for field paths in nested structures (default: "")
475
+
476
+ Returns:
477
+ list: Column definitions for AG Grid
478
+ """
479
+ from typing import get_origin, get_args, List, Union
480
+
481
+ # Initialize a visited set to track instances for preventing infinite recursion
482
+ if visited is None:
483
+ visited = set()
484
+
485
+ # If the instance has already been visited, stop recursion
486
+ instance_id = id(model_instance)
487
+ if instance_id in visited:
488
+ return []
489
+
490
+ # Mark this instance as visited
491
+ visited.add(instance_id)
492
+
493
+ # Check if the instance has a custom make_column_defs method
494
+ if hasattr(model_instance, "make_column_defs_instance") and callable(
495
+ getattr(model_instance, "make_column_defs_instance")
496
+ ):
497
+ return model_instance.make_column_defs_instance(
498
+ table_name=table_name,
499
+ max_recursion_level=max_recursion_level,
500
+ drop_id=drop_id,
501
+ current_level=current_level,
502
+ visited=visited.copy(),
503
+ field_prefix=field_prefix,
504
+ )
505
+
506
+ # Get model class and fields
507
+ model_class = type(model_instance)
508
+ model_fields = getattr(
509
+ model_class, "model_fields", getattr(model_class, "__fields__", {})
510
+ )
511
+
512
+ # Use class name if table_name not provided
513
+ if table_name is None:
514
+ table_name = model_class.__name__
515
+
516
+ # Initialize column definitions
517
+ column_defs = []
518
+
519
+ # Process each field
520
+ for field_name, field_info in model_fields.items():
521
+ # Skip ID fields if drop_id is True
522
+ if drop_id and (field_name == "id" or field_name == "_id"):
523
+ continue
524
+
525
+ # Calculate the full field path for AG Grid
526
+ field_path = f"{field_prefix}.{field_name}" if field_prefix else field_name
527
+
528
+ # Get the actual field value from the instance
529
+ try:
530
+ field_value = getattr(model_instance, field_name, None)
531
+ except AttributeError:
532
+ field_value = None
533
+
534
+ # Extract field type (different in V1 and V2)
535
+ if hasattr(field_info, "annotation"):
536
+ # Pydantic V2
537
+ field_type = field_info.annotation
538
+ elif hasattr(field_info, "type_"):
539
+ # Pydantic V1
540
+ field_type = field_info.type_
541
+ else:
542
+ # Fallback - infer from actual value
543
+ field_type = type(field_value) if field_value is not None else str
544
+
545
+ # Check if the field type is a Pydantic model
546
+ is_nested_model = is_pydantic_model(field_type)
547
+
548
+ # Extract actual model class for nested types (like Optional[Model])
549
+ if is_nested_model and hasattr(field_type, "__args__"):
550
+ args = get_args(field_type)
551
+ for arg in args:
552
+ if is_pydantic_model(arg):
553
+ field_type = arg
554
+ break
555
+
556
+ # For instances, we can also infer type from actual value
557
+ if field_value is not None and is_pydantic_model(type(field_value)):
558
+ field_type = type(field_value)
559
+ is_nested_model = True
560
+
561
+ # Create column definition based on the field type and actual value
562
+ column_def = create_column_def(field_name, field_type, field_value, field_path)
563
+
564
+ # Set object type for nested models
565
+ if is_nested_model:
566
+ column_def["type"] = "objectColumn"
567
+
568
+ # If recursion level is 0, exclude all nested objects but include column def for the field itself
569
+ if max_recursion_level == 0 and is_nested_model:
570
+ column_defs.append(column_def)
571
+ continue
572
+
573
+ # Handle nested model instance if it exists and we haven't reached max recursion
574
+ if (
575
+ is_nested_model
576
+ and field_value is not None
577
+ and current_level < max_recursion_level
578
+ ):
579
+ # Check if the instance has its own make_column_defs method
580
+ if hasattr(field_value, "make_column_defs_instance") and callable(
581
+ getattr(field_value, "make_column_defs_instance")
582
+ ):
583
+ nested_columns = field_value.make_column_defs_instance(
584
+ max_recursion_level=max_recursion_level,
585
+ current_level=current_level + 1,
586
+ visited=visited.copy(),
587
+ field_prefix=f"{field_path}." if field_path else "",
588
+ )
589
+ else:
590
+ # Process the nested model instance recursively
591
+ nested_columns = make_column_defs_instance(
592
+ field_value,
593
+ f"{table_name}_{field_name}",
594
+ max_recursion_level,
595
+ drop_id,
596
+ current_level + 1,
597
+ visited.copy(),
598
+ f"{field_path}.",
599
+ )
600
+
601
+ # Create a column group for nested fields if we have columns
602
+ if nested_columns:
603
+ group_column = {
604
+ "headerName": field_name.capitalize(),
605
+ "marryChildren": True,
606
+ "children": nested_columns,
607
+ }
608
+ column_defs.append(group_column)
609
+ else:
610
+ column_defs.append(column_def)
611
+ elif field_type == "self" and current_level < max_recursion_level:
612
+ # Handle self-referential field type
613
+ if field_value is not None:
614
+ # Create a new visited set for this branch to avoid affecting other branches
615
+ self_visited = visited.copy()
616
+ if instance_id not in self_visited:
617
+ # Process only the direct fields of the self-reference
618
+ nested_columns = make_column_defs_instance(
619
+ field_value,
620
+ f"{table_name}_{field_name}",
621
+ 0, # Only process direct fields, no further recursion
622
+ drop_id,
623
+ current_level + 1,
624
+ self_visited,
625
+ f"{field_path}.",
626
+ )
627
+
628
+ # Create a column group for self-referential fields
629
+ if nested_columns:
630
+ group_column = {
631
+ "headerName": field_name.capitalize(),
632
+ "marryChildren": True,
633
+ "children": nested_columns,
634
+ }
635
+ column_defs.append(group_column)
636
+ else:
637
+ column_defs.append(column_def)
638
+ else:
639
+ column_defs.append(column_def)
640
+ else:
641
+ column_defs.append(column_def)
642
+ else:
643
+ # Handle lists/arrays
644
+ is_list_type = False
645
+ inner_type = None
646
+
647
+ # Check for various list types using get_origin and get_args
648
+ origin = get_origin(field_type)
649
+ if origin is list or origin is List:
650
+ is_list_type = True
651
+ args = get_args(field_type)
652
+ if args:
653
+ inner_type = args[0]
654
+ # Handle Optional[List[...]]
655
+ elif origin is Union:
656
+ args = get_args(field_type)
657
+ for arg in args:
658
+ arg_origin = get_origin(arg)
659
+ if arg_origin is list or arg_origin is List:
660
+ is_list_type = True
661
+ arg_args = get_args(arg)
662
+ if arg_args:
663
+ inner_type = arg_args[0]
664
+ break
665
+
666
+ # Also check if the actual field value is a list
667
+ if field_value is not None and isinstance(field_value, list):
668
+ is_list_type = True
669
+ # Try to infer inner type from first non-None element
670
+ if field_value and inner_type is None:
671
+ for item in field_value:
672
+ if item is not None:
673
+ inner_type = type(item)
674
+ break
675
+
676
+ if is_list_type:
677
+ # For lists, we add a special column definition
678
+ list_column_def = {
679
+ "headerName": field_name.capitalize(),
680
+ "field": field_path,
681
+ "cellRenderer": "agArrayCellRenderer",
682
+ }
683
+
684
+ # If the list contains models, we might want to indicate that
685
+ if inner_type and is_pydantic_model(inner_type):
686
+ list_column_def["type"] = "modelArrayColumn"
687
+
688
+ # If we have actual list data, we can provide more specific info
689
+ if field_value is not None and isinstance(field_value, list):
690
+ list_column_def["listLength"] = len(field_value)
691
+ if field_value:
692
+ # Add info about the first item for reference
693
+ first_item = field_value[0]
694
+ if is_pydantic_model(type(first_item)):
695
+ list_column_def["itemType"] = type(first_item).__name__
696
+
697
+ column_defs.append(list_column_def)
698
+ else:
699
+ # This is a simple field, add column definition directly
700
+ column_defs.append(column_def)
701
+
702
+ return column_defs
703
+
704
+
705
+ def make_table_entries(
706
+ model_instances, table_name=None, max_recursion_level=1, drop_id=True
707
+ ):
708
+ """
709
+ Create AG Grid table configuration for a list of model instances.
710
+ This is the main function to make tables that are visible in the ui.
711
+
712
+ To generate a table in the ui the ui_schema of the class should initialize
713
+ the grid_options for the table.
714
+
715
+ It is basically impossible to predict what a table should be for a class but
716
+ tables can be created only from lists. The make_table function of the class should
717
+ therefore call this function to generate the table for appropriate list fields and
718
+ merge these manually where it makes sense.
719
+
720
+ This function calls the helper functions make_table_entries_helper and make_column_defs_helper
721
+ which recursively process the model instances and their fields to generate the table data and column definitions.
722
+
723
+ for model fields these functions check if the nested model has functions make_columns or make_column_defs
724
+ which are called to generate the column data and definitions, respectively
725
+
726
+ These functions do not exist by default but can be used by models to modify the default definitions
727
+ where they dont work. This is typically the case if the models contain huge data sets that
728
+ we do not want to display in the table. An example of this is in the Molecule class.
729
+
730
+
731
+ Args:
732
+ model_instances: List of model instances to process
733
+ table_name: Name for the table (default: None, uses class name)
734
+ max_recursion_level: Maximum depth for processing nested models (default: 1)
735
+ drop_id: Whether to drop the ID field (default: True)
736
+
737
+ Returns:
738
+ dict: Dictionary with 'tableName', 'rowData' (for AG Grid), and 'columnDefs'
739
+ """
740
+ if not model_instances:
741
+ return {
742
+ "tableName": table_name or "EmptyTable",
743
+ "rowData": [],
744
+ "columnDefs": [],
745
+ }
746
+
747
+ # Get the first instance and its class
748
+
749
+ first_instance = model_instances[0]
750
+ model_class = type(first_instance)
751
+
752
+ # Use the new helper functions
753
+ # table_data_result = make_table_entries_helper(
754
+ # first_instance, table_name, max_recursion_level, drop_id
755
+ # )
756
+
757
+ # Get column definitions using the model class
758
+ column_defs = make_column_defs_helper(
759
+ model_class, table_name, max_recursion_level, drop_id
760
+ )
761
+
762
+ # Process all instances to get row data
763
+ row_data = []
764
+ for instance in model_instances:
765
+ instance_result = make_table_entries_helper(
766
+ instance, table_name, max_recursion_level, drop_id
767
+ )
768
+ row_data.append(instance_result)
769
+
770
+ return {"tableName": "Table", "rowData": row_data, "columnDefs": column_defs}
771
+
772
+
773
+ def create_column_def(field_name, field_type, field_value, field_path):
774
+ """
775
+ Create a column definition for AG Grid based on field type and value.
776
+
777
+ Args:
778
+ field_name: Name of the field
779
+ field_type: Type of the field from model definition
780
+ field_value: Actual value of the field (can be None for class-based definitions)
781
+ field_path: Full path to the field (for nested structures)
782
+
783
+ Returns:
784
+ dict: Column definition for AG Grid
785
+ """
786
+ column_def = {
787
+ "headerName": field_name.capitalize(),
788
+ "field": field_path,
789
+ }
790
+
791
+ # Determine the column type based on field_value if available or field_type
792
+ if field_value is not None:
793
+ if isinstance(field_value, bool):
794
+ column_def["type"] = "booleanColumn"
795
+ elif isinstance(field_value, (int, float)):
796
+ column_def["type"] = "numericColumn"
797
+ elif isinstance(field_value, datetime):
798
+ column_def["type"] = "dateColumn"
799
+ elif isinstance(field_value, dict):
800
+ column_def["type"] = "objectColumn"
801
+ elif isinstance(field_value, list):
802
+ column_def["type"] = "arrayColumn"
803
+ else:
804
+ column_def["type"] = "textColumn"
805
+ else:
806
+ # Determine type from field_type if field_value is None
807
+ if field_type is bool or (
808
+ isinstance(field_type, type) and issubclass(field_type, bool)
809
+ ):
810
+ column_def["type"] = "booleanColumn"
811
+ elif field_type in (int, float) or (
812
+ isinstance(field_type, type) and issubclass(field_type, (int, float))
813
+ ):
814
+ column_def["type"] = "numericColumn"
815
+ elif hasattr(field_type, "__name__") and field_type.__name__ == "datetime":
816
+ column_def["type"] = "dateColumn"
817
+ else:
818
+ column_def["type"] = "textColumn"
819
+
820
+ return column_def
821
+
822
+
823
+ def ui_add_table(ui_options: Dict[str, Any]) -> Dict[str, Any]:
824
+ """
825
+ Add table options to the UI schema for AG Grid.
826
+
827
+ the class must then implement o make_table function calling make_table
828
+ :param ui_options:
829
+ :return: modified ui_options
830
+ """
831
+ ui_options["ui:table"] = { # this goes into gridOptions
832
+ "pagination": True,
833
+ "paginationPageSize": 20,
834
+ "domLayout": "autoHeight",
835
+ "defaultColDef": {"flex": 1, "minWidth": 80, "resizable": True},
836
+ }
837
+ return ui_options # for chaining