pyegeria 5.4.0.28__py3-none-any.whl → 5.5.3.3__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.

Potentially problematic release.


This version of pyegeria might be problematic. Click here for more details.

Files changed (433) hide show
  1. commands/__init__.py +24 -0
  2. commands/cat/Dr-Egeria_md-orig.py +2 -2
  3. commands/cat/collection_actions.py +197 -0
  4. commands/cat/dr_egeria_command_help.py +137 -38
  5. commands/cat/dr_egeria_jupyter.py +7 -7
  6. commands/cat/dr_egeria_md.py +10 -267
  7. commands/cat/exp_list_glossaries.py +11 -14
  8. commands/cat/get_asset_graph.py +37 -267
  9. commands/cat/{get_collection.py → get_collection_tree.py} +10 -18
  10. commands/cat/get_project_dependencies.py +14 -14
  11. commands/cat/get_project_structure.py +15 -14
  12. commands/cat/get_tech_type_elements.py +16 -116
  13. commands/cat/glossary_actions.py +145 -298
  14. commands/cat/list_assets.py +3 -11
  15. commands/cat/list_cert_types.py +17 -63
  16. commands/cat/list_collections.py +17 -139
  17. commands/cat/list_deployed_catalogs.py +15 -27
  18. commands/cat/list_deployed_database_schemas.py +27 -43
  19. commands/cat/list_deployed_databases.py +16 -31
  20. commands/cat/list_deployed_servers.py +35 -54
  21. commands/cat/list_glossaries.py +18 -17
  22. commands/cat/list_projects.py +10 -12
  23. commands/cat/list_tech_type_elements.py +21 -37
  24. commands/cat/list_tech_types.py +13 -25
  25. commands/cat/list_terms.py +38 -79
  26. commands/cat/list_todos.py +4 -11
  27. commands/cat/list_user_ids.py +3 -10
  28. commands/cat/my_reports.py +559 -0
  29. commands/cat/run_report.py +394 -0
  30. commands/cat/{list_format_set.py → run_report_orig.py} +136 -44
  31. commands/cli/egeria.py +182 -219
  32. commands/cli/egeria_cat.py +32 -59
  33. commands/cli/egeria_my.py +13 -0
  34. commands/cli/egeria_ops.py +69 -74
  35. commands/cli/egeria_tech.py +17 -93
  36. commands/{cat → deprecated}/list_data_designer.py +2 -4
  37. commands/{cat → deprecated}/list_data_structures_full.py +3 -6
  38. commands/deprecated/old_get_asset_graph.py +315 -0
  39. commands/my/__init__.py +0 -2
  40. commands/my/list_my_profile.py +27 -34
  41. commands/my/list_my_roles.py +1 -7
  42. commands/my/monitor_my_todos.py +1 -7
  43. commands/my/monitor_open_todos.py +6 -7
  44. commands/my/todo_actions.py +4 -5
  45. commands/ops/__init__.py +0 -2
  46. commands/ops/gov_server_actions.py +17 -21
  47. commands/ops/list_archives.py +17 -38
  48. commands/ops/list_catalog_targets.py +33 -40
  49. commands/ops/load_archive.py +14 -11
  50. commands/ops/{monitor_engine_activity_c.py → monitor_active_engine_activity.py} +51 -82
  51. commands/ops/{monitor_integ_daemon_status.py → monitor_daemon_status.py} +35 -55
  52. commands/ops/monitor_engine_activity.py +79 -77
  53. commands/ops/{monitor_gov_eng_status.py → monitor_engine_status.py} +10 -7
  54. commands/ops/monitor_platform_status.py +38 -50
  55. commands/ops/monitor_server_startup.py +6 -11
  56. commands/ops/monitor_server_status.py +7 -11
  57. commands/ops/orig_monitor_server_list.py +8 -8
  58. commands/ops/orig_monitor_server_status.py +1 -5
  59. commands/ops/refresh_integration_daemon.py +5 -5
  60. commands/ops/restart_integration_daemon.py +5 -5
  61. commands/ops/table_integ_daemon_status.py +6 -6
  62. commands/ops/x_engine_actions.py +7 -7
  63. commands/tech/__init__.py +0 -2
  64. commands/tech/{generic_actions.py → element_actions.py} +6 -11
  65. commands/tech/get_element_info.py +20 -29
  66. commands/tech/get_guid_info.py +23 -42
  67. commands/tech/get_tech_details.py +20 -35
  68. commands/tech/get_tech_type_template.py +28 -39
  69. commands/tech/list_all_om_type_elements.py +24 -30
  70. commands/tech/list_all_om_type_elements_x.py +22 -28
  71. commands/tech/list_all_related_elements.py +19 -28
  72. commands/tech/list_anchored_elements.py +22 -30
  73. commands/tech/list_asset_types.py +19 -24
  74. commands/tech/list_elements_by_classification_by_property_value.py +26 -32
  75. commands/tech/list_elements_by_property_value.py +19 -25
  76. commands/tech/list_elements_by_property_value_x.py +20 -28
  77. commands/tech/list_elements_for_classification.py +28 -41
  78. commands/tech/list_gov_action_processes.py +16 -27
  79. commands/tech/list_information_supply_chains.py +22 -30
  80. commands/tech/list_registered_services.py +14 -26
  81. commands/tech/list_related_elements_with_prop_value.py +15 -25
  82. commands/tech/list_related_specification.py +1 -4
  83. commands/tech/list_relationship_types.py +15 -25
  84. commands/tech/list_relationships.py +20 -36
  85. commands/tech/list_solution_blueprints.py +28 -33
  86. commands/tech/list_solution_components.py +23 -29
  87. commands/tech/list_solution_roles.py +21 -32
  88. commands/tech/list_tech_templates.py +51 -54
  89. commands/tech/list_valid_metadata_values.py +5 -9
  90. commands/tech/table_tech_templates.py +2 -6
  91. commands/tech/x_list_related_elements.py +1 -4
  92. examples/GeoSpatial Products Example.py +524 -0
  93. examples/Jupyter Notebooks/P-egeria-server-config.ipynb +2137 -0
  94. examples/Jupyter Notebooks/README.md +2 -0
  95. examples/Jupyter Notebooks/common/P-environment-check.ipynb +115 -0
  96. examples/Jupyter Notebooks/common/__init__.py +14 -0
  97. examples/Jupyter Notebooks/common/common-functions.ipynb +4694 -0
  98. examples/Jupyter Notebooks/common/environment-check.ipynb +52 -0
  99. examples/Jupyter Notebooks/common/globals.ipynb +184 -0
  100. examples/Jupyter Notebooks/common/globals.py +154 -0
  101. examples/Jupyter Notebooks/common/orig_globals.py +152 -0
  102. examples/format_sets/all_format_sets.json +910 -0
  103. examples/format_sets/custom_format_sets.json +268 -0
  104. examples/format_sets/subset_format_sets.json +187 -0
  105. examples/format_sets_save_load_example.py +291 -0
  106. examples/jacquard_data_sets.py +129 -0
  107. examples/output_formats_example.py +193 -0
  108. examples/test_jacquard_data_sets.py +54 -0
  109. examples/test_jacquard_data_sets_scenarios.py +94 -0
  110. md_processing/__init__.py +33 -24
  111. md_processing/command_dispatcher.py +33 -0
  112. md_processing/command_mapping.py +221 -0
  113. md_processing/data/commands/commands_data_designer.json +537 -0
  114. md_processing/data/commands/commands_external_reference.json +733 -0
  115. md_processing/data/commands/commands_feedback.json +155 -0
  116. md_processing/data/commands/commands_general.json +204 -0
  117. md_processing/data/commands/commands_glossary.json +218 -0
  118. md_processing/data/commands/commands_governance.json +3678 -0
  119. md_processing/data/commands/commands_product_manager.json +865 -0
  120. md_processing/data/commands/commands_project.json +642 -0
  121. md_processing/data/commands/commands_solution_architect.json +366 -0
  122. md_processing/data/commands.json +6489 -30060
  123. md_processing/data/{commands-working.json → commands_working.json} +9304 -13513
  124. md_processing/data/gened_report_specs.py +6584 -0
  125. md_processing/data/generated_format_sets.json +6533 -0
  126. md_processing/data/generated_format_sets_old.json +4137 -0
  127. md_processing/data/generated_format_sets_old.py +45 -0
  128. md_processing/dr_egeria.py +182 -0
  129. md_processing/md_commands/data_designer_commands.py +195 -583
  130. md_processing/md_commands/ext_ref_commands.py +530 -0
  131. md_processing/md_commands/feedback_commands.py +726 -0
  132. md_processing/md_commands/glossary_commands.py +106 -490
  133. md_processing/md_commands/governance_officer_commands.py +129 -18
  134. md_processing/md_commands/product_manager_commands.py +362 -115
  135. md_processing/md_commands/project_commands.py +351 -134
  136. md_processing/md_commands/solution_architect_commands.py +276 -232
  137. md_processing/md_commands/view_commands.py +295 -0
  138. md_processing/md_processing_utils/common_md_proc_utils.py +258 -166
  139. md_processing/md_processing_utils/common_md_utils.py +138 -43
  140. md_processing/md_processing_utils/determine_width.py +103 -0
  141. md_processing/md_processing_utils/extraction_utils.py +100 -39
  142. md_processing/md_processing_utils/gen_report_specs.py +643 -0
  143. md_processing/md_processing_utils/generate_dr_help.py +61 -33
  144. md_processing/md_processing_utils/generate_md_cmd_templates.py +20 -19
  145. md_processing/md_processing_utils/generate_md_templates.py +3 -12
  146. md_processing/md_processing_utils/md_processing_constants.py +1053 -72
  147. pyegeria/__init__.py +203 -158
  148. pyegeria/core/__init__.py +40 -0
  149. pyegeria/core/_base_platform_client.py +574 -0
  150. pyegeria/core/_base_server_client.py +573 -0
  151. pyegeria/{_exceptions_new.py → core/_exceptions.py} +62 -30
  152. pyegeria/{_globals.py → core/_globals.py} +14 -3
  153. pyegeria/core/_server_client.py +6073 -0
  154. pyegeria/{_validators.py → core/_validators.py} +7 -8
  155. pyegeria/core/config.py +654 -0
  156. pyegeria/{create_tech_guid_lists.py → core/create_tech_guid_lists.py} +0 -1
  157. pyegeria/core/load_config.py +37 -0
  158. pyegeria/{logging_configuration.py → core/logging_configuration.py} +1 -1
  159. pyegeria/core/mcp_adapter.py +144 -0
  160. pyegeria/core/mcp_server.py +212 -0
  161. pyegeria/core/utils.py +405 -0
  162. pyegeria/{_client.py → deprecated/_client.py} +24 -25
  163. pyegeria/{_deprecated_gov_engine.py → deprecated/_deprecated_gov_engine.py} +16 -16
  164. pyegeria/{classification_manager_omvs.py → deprecated/classification_manager_omvs.py} +1987 -1877
  165. pyegeria/{output_formatter.py → deprecated/output_formatter_with_machine_keys.py} +298 -45
  166. pyegeria/{runtime_manager_omvs.py → deprecated/runtime_manager_omvs.py} +155 -171
  167. pyegeria/{valid_metadata_omvs.py → deprecated/valid_metadata_omvs.py} +93 -93
  168. pyegeria/{x_action_author_omvs.py → deprecated/x_action_author_omvs.py} +2 -3
  169. pyegeria/egeria_cat_client.py +26 -70
  170. pyegeria/egeria_client.py +130 -93
  171. pyegeria/egeria_config_client.py +40 -46
  172. pyegeria/egeria_tech_client.py +141 -54
  173. pyegeria/models/__init__.py +150 -0
  174. pyegeria/{models.py → models/models.py} +156 -20
  175. pyegeria/omvs/__init__.py +84 -0
  176. pyegeria/omvs/action_author.py +342 -0
  177. pyegeria/omvs/actor_manager.py +5980 -0
  178. pyegeria/omvs/asset_catalog.py +842 -0
  179. pyegeria/omvs/asset_maker.py +2736 -0
  180. pyegeria/omvs/automated_curation.py +4403 -0
  181. pyegeria/omvs/classification_manager.py +11213 -0
  182. pyegeria/{collection_manager.py → omvs/collection_manager.py} +1334 -1160
  183. pyegeria/omvs/community_matters_omvs.py +468 -0
  184. pyegeria/{core_omag_server_config.py → omvs/core_omag_server_config.py} +157 -157
  185. pyegeria/{data_designer.py → omvs/data_designer.py} +1115 -660
  186. pyegeria/omvs/data_discovery.py +869 -0
  187. pyegeria/omvs/data_engineer.py +372 -0
  188. pyegeria/omvs/digital_business.py +1133 -0
  189. pyegeria/omvs/external_links.py +1752 -0
  190. pyegeria/omvs/feedback_manager.py +834 -0
  191. pyegeria/{full_omag_server_config.py → omvs/full_omag_server_config.py} +73 -69
  192. pyegeria/{glossary_manager.py → omvs/glossary_manager.py} +857 -519
  193. pyegeria/{governance_officer.py → omvs/governance_officer.py} +964 -468
  194. pyegeria/omvs/lineage_linker.py +314 -0
  195. pyegeria/omvs/location_arena.py +1525 -0
  196. pyegeria/omvs/metadata_expert.py +668 -0
  197. pyegeria/omvs/metadata_explorer_omvs.py +2943 -0
  198. pyegeria/omvs/my_profile.py +1042 -0
  199. pyegeria/omvs/notification_manager.py +358 -0
  200. pyegeria/omvs/people_organizer.py +394 -0
  201. pyegeria/{platform_services.py → omvs/platform_services.py} +113 -193
  202. pyegeria/omvs/product_manager.py +1825 -0
  203. pyegeria/omvs/project_manager.py +1907 -0
  204. pyegeria/omvs/reference_data.py +1140 -0
  205. pyegeria/omvs/registered_info.py +334 -0
  206. pyegeria/omvs/runtime_manager.py +2817 -0
  207. pyegeria/omvs/schema_maker.py +446 -0
  208. pyegeria/{server_operations.py → omvs/server_operations.py} +27 -26
  209. pyegeria/{solution_architect_omvs.py → omvs/solution_architect.py} +1886 -1505
  210. pyegeria/omvs/specification_properties.py +37 -0
  211. pyegeria/omvs/subject_area.py +1042 -0
  212. pyegeria/omvs/template_manager_omvs.py +236 -0
  213. pyegeria/omvs/time_keeper.py +1761 -0
  214. pyegeria/omvs/valid_metadata.py +3221 -0
  215. pyegeria/omvs/valid_metadata_lists.py +37 -0
  216. pyegeria/omvs/valid_type_lists.py +37 -0
  217. pyegeria/view/__init__.py +28 -0
  218. pyegeria/{_output_format_models.py → view/_output_format_models.py} +160 -24
  219. pyegeria/view/_output_formats.py +14 -0
  220. pyegeria/view/base_report_formats.py +2719 -0
  221. pyegeria/view/dr_egeria_reports.py +56 -0
  222. pyegeria/view/format_set_executor.py +397 -0
  223. pyegeria/{md_processing_utils.py → view/md_processing_utils.py} +5 -5
  224. pyegeria/{mermaid_utilities.py → view/mermaid_utilities.py} +2 -154
  225. pyegeria/view/output_formatter.py +1297 -0
  226. pyegeria-5.5.3.3.dist-info/METADATA +218 -0
  227. pyegeria-5.5.3.3.dist-info/RECORD +241 -0
  228. {pyegeria-5.4.0.28.dist-info → pyegeria-5.5.3.3.dist-info}/WHEEL +2 -1
  229. pyegeria-5.5.3.3.dist-info/entry_points.txt +103 -0
  230. pyegeria-5.5.3.3.dist-info/top_level.txt +4 -0
  231. commands/cat/.DS_Store +0 -0
  232. commands/cat/.env +0 -8
  233. commands/cat/README.md +0 -16
  234. commands/cat/debug_log +0 -1126
  235. commands/cat/debug_log.2025-08-18_11-34-38_088636.zip +0 -0
  236. commands/cat/list_categories.py +0 -192
  237. commands/cat/logs/pyegeria.log +0 -4
  238. commands/cli/debug_log +0 -0
  239. commands/cli/debug_log.log +0 -0
  240. commands/cli/txt_custom_v2.tcss +0 -19
  241. commands/my/README.md +0 -17
  242. commands/ops/README.md +0 -24
  243. commands/ops/logs/pyegeria.log +0 -0
  244. commands/ops/monitor_asset_events.py +0 -108
  245. commands/tech/README.md +0 -24
  246. md_processing/.DS_Store +0 -0
  247. md_processing/dr-egeria-outbox/Collections-2025-08-12-13-30-37.md +0 -163
  248. md_processing/dr-egeria-outbox/Collections-2025-08-12-13-35-58.md +0 -474
  249. md_processing/dr_egeria_inbox/Derive-Dr-Gov-Defs.md +0 -8
  250. md_processing/dr_egeria_inbox/Dr.Egeria Templates.md +0 -873
  251. md_processing/dr_egeria_inbox/arch_test.md +0 -57
  252. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +0 -254
  253. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +0 -696
  254. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +0 -254
  255. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +0 -298
  256. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +0 -608
  257. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +0 -94
  258. md_processing/dr_egeria_inbox/archive/freddie_intro.md +0 -284
  259. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +0 -275
  260. md_processing/dr_egeria_inbox/archive/test-term.md +0 -110
  261. md_processing/dr_egeria_inbox/cat_test.md +0 -100
  262. md_processing/dr_egeria_inbox/collections.md +0 -39
  263. md_processing/dr_egeria_inbox/data_designer_debug.log +0 -6
  264. md_processing/dr_egeria_inbox/data_designer_out.md +0 -60
  265. md_processing/dr_egeria_inbox/data_designer_search_test.md +0 -11
  266. md_processing/dr_egeria_inbox/data_field.md +0 -54
  267. md_processing/dr_egeria_inbox/data_spec.md +0 -77
  268. md_processing/dr_egeria_inbox/data_spec_test.md +0 -2406
  269. md_processing/dr_egeria_inbox/data_test.md +0 -179
  270. md_processing/dr_egeria_inbox/data_test2.md +0 -429
  271. md_processing/dr_egeria_inbox/data_test3.md +0 -462
  272. md_processing/dr_egeria_inbox/dr_egeria_data_designer_1.md +0 -124
  273. md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +0 -168
  274. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +0 -280
  275. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +0 -318
  276. md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +0 -1073
  277. md_processing/dr_egeria_inbox/dr_egeria_isc1.md +0 -44
  278. md_processing/dr_egeria_inbox/generated_help_report.md +0 -9
  279. md_processing/dr_egeria_inbox/glossary_creation_experiment.ipynb +0 -341
  280. md_processing/dr_egeria_inbox/glossary_list.md +0 -5
  281. md_processing/dr_egeria_inbox/glossary_search_test.md +0 -40
  282. md_processing/dr_egeria_inbox/glossary_test1.md +0 -324
  283. md_processing/dr_egeria_inbox/gov_def.md +0 -482
  284. md_processing/dr_egeria_inbox/gov_def2.md +0 -447
  285. md_processing/dr_egeria_inbox/img.png +0 -0
  286. md_processing/dr_egeria_inbox/product.md +0 -211
  287. md_processing/dr_egeria_inbox/rel.md +0 -8
  288. md_processing/dr_egeria_inbox/sb.md +0 -119
  289. md_processing/dr_egeria_inbox/solution-components.md +0 -136
  290. md_processing/dr_egeria_inbox/solution_blueprints.md +0 -118
  291. md_processing/dr_egeria_inbox/synonym_test.md +0 -42
  292. md_processing/dr_egeria_inbox/t2.md +0 -268
  293. md_processing/dr_egeria_outbox/.obsidian/app.json +0 -1
  294. md_processing/dr_egeria_outbox/.obsidian/appearance.json +0 -1
  295. md_processing/dr_egeria_outbox/.obsidian/community-plugins.json +0 -6
  296. md_processing/dr_egeria_outbox/.obsidian/core-plugins.json +0 -31
  297. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/data.json +0 -10
  298. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/main.js +0 -4459
  299. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/manifest.json +0 -10
  300. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/data.json +0 -3
  301. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/main.js +0 -153
  302. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/manifest.json +0 -11
  303. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/styles.css +0 -1
  304. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/main.js +0 -500
  305. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +0 -12
  306. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/styles.css +0 -1
  307. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/main.js +0 -37
  308. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/manifest.json +0 -11
  309. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/styles.css +0 -220
  310. md_processing/dr_egeria_outbox/.obsidian/types.json +0 -28
  311. md_processing/dr_egeria_outbox/.obsidian/workspace.json +0 -220
  312. md_processing/dr_egeria_outbox/Untitled.canvas +0 -1
  313. md_processing/dr_egeria_outbox/friday/processed-2025-08-22 21:22-dr_egeria_intro_part1.md +0 -312
  314. md_processing/dr_egeria_outbox/friday/processed-2025-08-22 21:23-dr_egeria_intro_part1.md +0 -265
  315. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 15:06-dr_egeria_intro_part1.md +0 -230
  316. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 15:30-dr_egeria_intro_part1.md +0 -296
  317. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 15:31-dr_egeria_intro_part1.md +0 -253
  318. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 16:08-dr_egeria_intro_part2.md +0 -343
  319. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 16:12-dr_egeria_intro_part2.md +0 -343
  320. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:05-product.md +0 -426
  321. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:56-product.md +0 -212
  322. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 09:43-product.md +0 -201
  323. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 14:55-product.md +0 -77
  324. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 15:05-product.md +0 -75
  325. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 15:11-product.md +0 -74
  326. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 20:40-collections.md +0 -49
  327. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +0 -719
  328. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +0 -41
  329. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +0 -33
  330. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +0 -192
  331. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:08-gov_def2.md +0 -486
  332. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:10-gov_def2.md +0 -486
  333. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:53-gov_def2.md +0 -486
  334. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:54-gov_def2.md +0 -486
  335. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:03-gov_def2.md +0 -486
  336. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:06-gov_def2.md +0 -486
  337. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:10-gov_def2.md +0 -486
  338. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-16 19:15-gov_def2.md +0 -527
  339. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-17 12:08-gov_def2.md +0 -527
  340. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-17 14:27-gov_def2.md +0 -485
  341. md_processing/dr_egeria_outbox/tuesday/processed-2025-08-19 10:55-product.md +0 -209
  342. md_processing/family_docs/Data Designer/Create_Data_Class.md +0 -164
  343. md_processing/family_docs/Data Designer/Create_Data_Dictionary.md +0 -30
  344. md_processing/family_docs/Data Designer/Create_Data_Field.md +0 -162
  345. md_processing/family_docs/Data Designer/Create_Data_Specification.md +0 -36
  346. md_processing/family_docs/Data Designer/Create_Data_Structure.md +0 -38
  347. md_processing/family_docs/Data Designer/View_Data_Classes.md +0 -78
  348. md_processing/family_docs/Data Designer/View_Data_Dictionaries.md +0 -78
  349. md_processing/family_docs/Data Designer/View_Data_Fields.md +0 -78
  350. md_processing/family_docs/Data Designer/View_Data_Specifications.md +0 -78
  351. md_processing/family_docs/Data Designer/View_Data_Structures.md +0 -78
  352. md_processing/family_docs/Data Designer.md +0 -842
  353. md_processing/family_docs/Digital Product Manager/Add_Member->Collection.md +0 -42
  354. md_processing/family_docs/Digital Product Manager/Attach_Collection->Resource.md +0 -36
  355. md_processing/family_docs/Digital Product Manager/Create_Agreement.md +0 -96
  356. md_processing/family_docs/Digital Product Manager/Create_Data_Sharing_Agreement.md +0 -72
  357. md_processing/family_docs/Digital Product Manager/Create_DigitalSubscription.md +0 -102
  358. md_processing/family_docs/Digital Product Manager/Create_Digital_Product.md +0 -134
  359. md_processing/family_docs/Digital Product Manager/Link_Agreement_Items.md +0 -60
  360. md_processing/family_docs/Digital Product Manager/Link_Contracts.md +0 -26
  361. md_processing/family_docs/Digital Product Manager/Link_Digital_Product_-_Digital_Product.md +0 -30
  362. md_processing/family_docs/Digital Product Manager/Link_Subscribers.md +0 -48
  363. md_processing/family_docs/Digital Product Manager.md +0 -668
  364. md_processing/family_docs/Glossary/Attach_Category_Parent.md +0 -18
  365. md_processing/family_docs/Glossary/Attach_Term-Term_Relationship.md +0 -26
  366. md_processing/family_docs/Glossary/Create_Category.md +0 -38
  367. md_processing/family_docs/Glossary/Create_Glossary.md +0 -42
  368. md_processing/family_docs/Glossary/Create_Term.md +0 -70
  369. md_processing/family_docs/Glossary.md +0 -206
  370. md_processing/family_docs/Governance Officer/Create_Business_Imperative.md +0 -106
  371. md_processing/family_docs/Governance Officer/Create_Certification_Type.md +0 -112
  372. md_processing/family_docs/Governance Officer/Create_Governance_Approach.md +0 -114
  373. md_processing/family_docs/Governance Officer/Create_Governance_Obligation.md +0 -114
  374. md_processing/family_docs/Governance Officer/Create_Governance_Principle.md +0 -114
  375. md_processing/family_docs/Governance Officer/Create_Governance_Procedure.md +0 -128
  376. md_processing/family_docs/Governance Officer/Create_Governance_Process.md +0 -122
  377. md_processing/family_docs/Governance Officer/Create_Governance_Processing_Purpose.md +0 -106
  378. md_processing/family_docs/Governance Officer/Create_Governance_Responsibility.md +0 -122
  379. md_processing/family_docs/Governance Officer/Create_Governance_Rule.md +0 -122
  380. md_processing/family_docs/Governance Officer/Create_Governance_Strategy.md +0 -106
  381. md_processing/family_docs/Governance Officer/Create_License_Type.md +0 -112
  382. md_processing/family_docs/Governance Officer/Create_Naming_Standard_Rule.md +0 -122
  383. md_processing/family_docs/Governance Officer/Create_Regulation_Article.md +0 -106
  384. md_processing/family_docs/Governance Officer/Create_Regulation_Definition.md +0 -118
  385. md_processing/family_docs/Governance Officer/Create_Security_Access_Control.md +0 -114
  386. md_processing/family_docs/Governance Officer/Create_Security_Group.md +0 -120
  387. md_processing/family_docs/Governance Officer/Create_Service_Level_Objectives.md +0 -122
  388. md_processing/family_docs/Governance Officer/Create_Threat_Definition.md +0 -106
  389. md_processing/family_docs/Governance Officer/Link_Governance_Controls.md +0 -32
  390. md_processing/family_docs/Governance Officer/Link_Governance_Drivers.md +0 -32
  391. md_processing/family_docs/Governance Officer/Link_Governance_Policies.md +0 -32
  392. md_processing/family_docs/Governance Officer/View_Governance_Definitions.md +0 -82
  393. md_processing/family_docs/Governance Officer.md +0 -2412
  394. md_processing/family_docs/Solution Architect/Create_Information_Supply_Chain.md +0 -70
  395. md_processing/family_docs/Solution Architect/Create_Solution_Blueprint.md +0 -44
  396. md_processing/family_docs/Solution Architect/Create_Solution_Component.md +0 -96
  397. md_processing/family_docs/Solution Architect/Create_Solution_Role.md +0 -66
  398. md_processing/family_docs/Solution Architect/Link_Information_Supply_Chain_Peers.md +0 -32
  399. md_processing/family_docs/Solution Architect/Link_Solution_Component_Peers.md +0 -32
  400. md_processing/family_docs/Solution Architect/View_Information_Supply_Chains.md +0 -32
  401. md_processing/family_docs/Solution Architect/View_Solution_Blueprints.md +0 -32
  402. md_processing/family_docs/Solution Architect/View_Solution_Components.md +0 -32
  403. md_processing/family_docs/Solution Architect/View_Solution_Roles.md +0 -32
  404. md_processing/family_docs/Solution Architect.md +0 -490
  405. md_processing/md_processing_utils/debug_log +0 -574
  406. md_processing/md_processing_utils/debug_log.log +0 -0
  407. md_processing/md_processing_utils/dr-egeria-help-2025-07-17T17:22:09.md +0 -2065
  408. md_processing/md_processing_utils/generated_help_terms.md +0 -842
  409. pyegeria/.DS_Store +0 -0
  410. pyegeria/README.md +0 -35
  411. pyegeria/_client_new.py +0 -1102
  412. pyegeria/_output_formats.py +0 -730
  413. pyegeria/asset_catalog_omvs.py +0 -864
  414. pyegeria/automated_curation_omvs.py +0 -3765
  415. pyegeria/config.py +0 -523
  416. pyegeria/egeria_my_client.py +0 -91
  417. pyegeria/feedback_manager_omvs.py +0 -4573
  418. pyegeria/load_config_orig.py +0 -218
  419. pyegeria/md_processing_helpers.py +0 -58
  420. pyegeria/md_processing_utils_orig.py +0 -1103
  421. pyegeria/metadata_explorer_omvs.py +0 -2326
  422. pyegeria/my_profile_omvs.py +0 -1022
  423. pyegeria/project_manager.py +0 -1591
  424. pyegeria/registered_info.py +0 -167
  425. pyegeria/template_manager_omvs.py +0 -1414
  426. pyegeria/utils.py +0 -256
  427. pyegeria-5.4.0.28.dist-info/METADATA +0 -77
  428. pyegeria-5.4.0.28.dist-info/RECORD +0 -343
  429. pyegeria-5.4.0.28.dist-info/entry_points.txt +0 -105
  430. /commands/cat/debug_log.log → /pyegeria/deprecated/__init__.py +0 -0
  431. /pyegeria/{_exceptions.py → deprecated/_exceptions.py} +0 -0
  432. /pyegeria/{collection_models.py → models/collection_models.py} +0 -0
  433. {pyegeria-5.4.0.28.dist-info → pyegeria-5.5.3.3.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,1297 @@
1
+ """
2
+ SPDX-License-Identifier: Apache-2.0
3
+ Copyright Contributors to the ODPi Egeria project.
4
+
5
+ Utilities for rendering Egeria elements into various output formats
6
+ such as DICT, LIST, Markdown (including Mermaid), and basic HTML.
7
+
8
+ Exceptions
9
+ ----------
10
+ Functions in this module may raise the following exceptions:
11
+ - ValueError: Unsupported or inconsistent output format requests (e.g., LIST/TABLE
12
+ without the required columns); invalid option combinations.
13
+ - KeyError: Missing expected keys from provided elements or report specifications.
14
+ - TypeError: Incorrect input types (e.g., non-list where a list of dicts is required).
15
+ - RuntimeError: Failures in Markdown/HTML conversion or Mermaid rendering helpers.
16
+ """
17
+
18
+ import copy
19
+ from datetime import datetime
20
+ import re
21
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
22
+
23
+ from pyegeria.core._globals import MERMAID_GRAPH_TITLES, MERMAID_GRAPHS
24
+ from pyegeria.core.utils import (camel_to_title_case)
25
+ from markdown_it import MarkdownIt
26
+ from rich.console import Console
27
+ from loguru import logger
28
+
29
+ from pyegeria.view.mermaid_utilities import construct_mermaid_web
30
+ from pyegeria.view.base_report_formats import select_report_format, MD_SEPARATOR, get_report_spec_match
31
+ from pyegeria.models import to_camel_case
32
+
33
+ """
34
+ Note on select_report_spec function:
35
+
36
+ This function and related data structures have been moved back to _output_formats.py.
37
+ Please import select_report_spec from pyegeria._output_formats instead of from this module.
38
+ """
39
+ # Todo - put this back after testing
40
+ # console = Console(width=settings.Environment.console_width)
41
+ console = Console(width=300)
42
+
43
+
44
+ def _extract_referenceable_properties(element: dict[str, Any]) -> dict[str, Any]:
45
+ # Get general header attributes
46
+ guid = element.get('elementHeader', {}).get("guid", None)
47
+ if guid is None:
48
+ return {}
49
+ metadata_collection_id = element['elementHeader']['origin'].get("homeMetadataCollectionId", None)
50
+ metadata_collection_name = element['elementHeader']['origin'].get("homeMetadataCollectionName", None)
51
+ origin_category = element['elementHeader'].get("origin_category", None)
52
+ created_by = element['elementHeader']["versions"].get("createdBy", None)
53
+ create_time = element['elementHeader']["versions"].get("createTime", None)
54
+ updated_by = element['elementHeader']["versions"].get("updatedBy", None)
55
+ version = element['elementHeader']["versions"].get("version", None)
56
+ type_name = element['elementHeader']["type"].get("typeName", None)
57
+ classifications = element['elementHeader'].get("classifications", [])
58
+ effective_from = element['elementHeader'].get("effectiveFrom", None)
59
+ effective_to = element['elementHeader'].get("effectiveTo", None)
60
+ status = element['elementHeader'].get("status", None)
61
+ # Get attributes from properties
62
+ properties = element['properties']
63
+ url = properties.get('url', None)
64
+ display_name = properties.get("displayName","")
65
+ description = properties.get("description", "") or ""
66
+ qualified_name = properties.get("qualifiedName", "") or ""
67
+ category = properties.get("category", "") or ""
68
+ version_identifier = properties.get("versionIdentifier", "") or ""
69
+ additional_properties = properties.get("additionalProperties", {}) or {}
70
+ extended_properties = properties.get("extendedProperties", {}) or {}
71
+ contentStatus = properties.get("contentStatus", "")
72
+ activityStatus = properties.get("activityStatus", "")
73
+ deploymentStatus = properties.get("deploymentStatus", "")
74
+
75
+ return {
76
+ "GUID": guid,
77
+ "metadata_collection_id": metadata_collection_id,
78
+ "metadata_collection_name": metadata_collection_name,
79
+ "origin_category": origin_category,
80
+ "created_by": created_by,
81
+ "create_time": create_time,
82
+ "updated_by": updated_by,
83
+ "version": version,
84
+ "type_name": type_name,
85
+ "classifications": classifications,
86
+ "element_status": status,
87
+ "url": url,
88
+ "display_name": display_name,
89
+ "description": description,
90
+ "qualified_name": qualified_name,
91
+ "category": category,
92
+ "version_identifier": version_identifier,
93
+ "additional_properties": additional_properties,
94
+ "extended_properties": extended_properties,
95
+ "effective_from": effective_from,
96
+ "effective_to": effective_to,
97
+ "content_status": contentStatus,
98
+ "activity_status": activityStatus,
99
+ "deployment_status": deploymentStatus,
100
+ }
101
+
102
+
103
+
104
+
105
+
106
+
107
+ def markdown_to_html(markdown_text: str) -> str:
108
+ """
109
+ Convert markdown text to HTML, with special handling for mermaid code blocks.
110
+
111
+ Args:
112
+ markdown_text: The markdown text to convert
113
+
114
+ Returns:
115
+ HTML string
116
+ """
117
+ # Initialize markdown-it
118
+ md = MarkdownIt()
119
+
120
+ # Find all mermaid code blocks
121
+ mermaid_blocks = re.findall(r'```mermaid\n(.*?)\n```', markdown_text, re.DOTALL)
122
+
123
+ # Replace each mermaid block with a placeholder
124
+ placeholders = []
125
+ for i, block in enumerate(mermaid_blocks):
126
+ placeholder = f"MERMAID_PLACEHOLDER_{i}"
127
+ markdown_text = markdown_text.replace(f"```mermaid\n{block}\n```", placeholder)
128
+ placeholders.append((placeholder, block))
129
+
130
+ # Convert markdown to HTML
131
+ html_text = md.render(markdown_text)
132
+
133
+ # Replace placeholders with rendered mermaid HTML
134
+ for placeholder, mermaid_block in placeholders:
135
+ mermaid_html = construct_mermaid_web(mermaid_block)
136
+ html_text = html_text.replace(placeholder, mermaid_html)
137
+
138
+ # Add basic HTML structure
139
+ html_text = f"""
140
+ <!DOCTYPE html>
141
+ <html>
142
+ <head>
143
+ <meta charset="UTF-8">
144
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
145
+ <title>Egeria Report</title>
146
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
147
+ <script>
148
+ mermaid.initialize({{ startOnLoad: true }});
149
+ </script>
150
+ <style>
151
+ body {{ font-family: Arial, sans-serif; line-height: 1.6; padding: 20px; }}
152
+ h1 {{ color: #2c3e50; }}
153
+ h2 {{ color: #3498db; }}
154
+ pre {{ background-color: #f8f8f8; padding: 10px; border-radius: 5px; overflow-x: auto; }}
155
+ table {{ border-collapse: collapse; width: 100%; margin-bottom: 20px; }}
156
+ th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
157
+ th {{ background-color: #f2f2f2; }}
158
+ tr:nth-child(even) {{ background-color: #f9f9f9; }}
159
+ </style>
160
+ </head>
161
+ <body>
162
+ {html_text}
163
+ </body>
164
+ </html>
165
+ """
166
+
167
+ return html_text
168
+
169
+ def make_preamble(obj_type: str, search_string: str, output_format: str = 'MD') -> Tuple[str, Optional[str]]:
170
+ """
171
+ Creates a preamble string and an elements action based on the given object type, search string,
172
+ and output format.
173
+
174
+ Args:
175
+ obj_type: The type of object being updated or reported on (e.g., "Product", "Category").
176
+ search_string: The search string used to filter objects. Defaults to "All Elements" if None.
177
+ output_format: A format identifier determining the output structure.
178
+ JSON - output standard json
179
+ MD - output standard markdown with no preamble
180
+ FORM - output markdown with a preamble for a form
181
+ REPORT - output markdown with a preamble for a report
182
+
183
+ Returns:
184
+ tuple: A tuple containing:
185
+ - A string representing the formatted update or report preamble.
186
+ - A string or None indicating the action description for the elements,
187
+ depending on the output format.
188
+ """
189
+ # search_string = search_string if search_string else "All Elements"
190
+ elements_md = ""
191
+ elements_action = "Update " + obj_type
192
+ if output_format == "FORM":
193
+ preamble = f"\n# Update {obj_type} Form - created at {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
194
+ if search_string:
195
+ preamble += f"\t {obj_type} found from the search string: `{search_string}`\n\n"
196
+ return preamble, elements_action
197
+ elif output_format == "REPORT":
198
+ elements_md += (f"# {obj_type} Report - created at {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
199
+ f"\t{obj_type} found from the search string: `{search_string}`\n\n")
200
+ elements_action = None
201
+ return elements_md, elements_action
202
+ else:
203
+ return "\n", elements_action
204
+
205
+ def make_md_attribute(attribute_name: str, attribute_value: str, output_type: str) -> Optional[str]:
206
+ """
207
+ Create a markdown attribute line for a given attribute name and value.
208
+
209
+ Args:
210
+ attribute_name: The name of the attribute
211
+ attribute_value: The value of the attribute
212
+ output_type: The output format (FORM, MD, REPORT)
213
+
214
+ Returns:
215
+ str: Formatted markdown for the attribute
216
+ """
217
+ output = ""
218
+ if isinstance(attribute_value,str):
219
+ attribute_value = attribute_value.strip() if attribute_value else ""
220
+ elif isinstance(attribute_value,list) and len(attribute_value) > 0:
221
+ attribute_value = ",\n".join(attribute_value)
222
+ if attribute_name:
223
+ if attribute_name.upper() == "GUID":
224
+ attribute_title = attribute_name.upper()
225
+ else:
226
+ # attribute_title = attribute_name.title()
227
+ attribute_title = camel_to_title_case(attribute_name)
228
+ else:
229
+ attribute_title = ""
230
+
231
+ if output_type in ["FORM", "MD"]:
232
+ if attribute_name.lower() in [ "mermaid", "solutionBlueprintMermaidGraph", "links", "implemented by", "sub_components"]:
233
+ return '\n'
234
+
235
+ output = f"## {attribute_title}\n{attribute_value}\n\n"
236
+ elif output_type in ["REPORT", "MERMAID"]:
237
+ if attribute_title in MERMAID_GRAPH_TITLES + ['Mermaid Graph', 'Mermaid']:
238
+ output = f"## {attribute_title}\n\n```mermaid\n{attribute_value}\n```\n"
239
+ elif attribute_value:
240
+ output = f"## {attribute_title}\n{attribute_value}\n\n"
241
+ return output
242
+
243
+ def format_for_markdown_table(text: str, guid: str = None) -> str:
244
+ """
245
+ Format text for markdown tables by replacing newlines with spaces and escaping pipe characters.
246
+ No truncation is applied to allow full-length text display regardless of console width.
247
+
248
+ Args:
249
+ text (str): The text to format
250
+
251
+ Returns:
252
+ str: Formatted text safe for markdown tables
253
+ """
254
+ if not text:
255
+ return ""
256
+ # Replace newlines with spaces and escape pipe characters
257
+ if isinstance(text, list):
258
+ text = "\n".join(text)
259
+ t = text.replace("\n", " ").replace("|", "\\|")
260
+ if '::' in t and guid:
261
+ t = f" [{t}](#{guid}) "
262
+ return t
263
+
264
+
265
+ def populate_columns_from_properties(element: dict, columns_struct: dict) -> dict:
266
+ """
267
+ Populate a columns_struct with values from the element's properties.
268
+
269
+ The element can be either:
270
+ - a full element dict that contains a nested 'properties' dict (preferred), or
271
+ - a dict that itself represents the set of properties.
272
+
273
+ In both cases, the effective properties are expected to use camelCase keys.
274
+ The columns_struct is expected to follow the format returned by select_report_spec, where
275
+ columns are located at columns_struct['formats']['columns'] and each column is a dict containing
276
+ at least a 'key' field expressed in snake_case. For each column whose snake_case key corresponds
277
+ to a key in the element properties (after converting to camelCase), this function adds a 'value'
278
+ entry to the column with the matching property's value.
279
+
280
+ Args:
281
+ element: Either a dict with a nested 'properties' dict using camelCase keys, or
282
+ a dict that itself is the set of properties.
283
+ columns_struct: The columns structure whose columns have snake_case 'key' fields.
284
+
285
+ Returns:
286
+ The updated columns_struct (the input structure is modified in place and also returned).
287
+ """
288
+ if not isinstance(columns_struct, dict):
289
+ return columns_struct
290
+
291
+ # Determine the effective properties:
292
+ # 1) Prefer element['properties'] if present and a dict
293
+ # 2) Otherwise, if element itself is a dict, treat it as the properties dict
294
+ # 3) Otherwise, nothing to do
295
+ props: dict | None = None
296
+ if isinstance(element, dict):
297
+ maybe_props = element.get('properties')
298
+ props = maybe_props if isinstance(maybe_props, dict) else element
299
+ if not isinstance(props, dict):
300
+ return columns_struct
301
+
302
+ # Get the attributes list if present
303
+ formats = columns_struct.get('formats') or {}
304
+ columns = formats.get('attributes') if isinstance(formats, dict) else None
305
+ if not isinstance(columns, list):
306
+ return columns_struct
307
+
308
+ # Pre-compute snake_case to camelCase mapping for all columns (cache for performance)
309
+ key_mapping = {}
310
+ for col in columns:
311
+ if isinstance(col, dict):
312
+ key_snake = col.get('key')
313
+ if key_snake:
314
+ key_mapping[key_snake] = to_camel_case(key_snake)
315
+
316
+ # Single pass to populate values
317
+ for col in columns:
318
+ try:
319
+ key_snake = col.get('key') if isinstance(col, dict) else None
320
+ if not key_snake:
321
+ continue
322
+ # Use pre-computed camelCase key
323
+ key_camel = key_mapping.get(key_snake)
324
+ if key_camel and key_camel in props:
325
+ col['value'] = props.get(key_camel)
326
+ except Exception as e:
327
+ # Be resilient; log and continue
328
+ logger.debug(f"populate_columns_from_properties: skipping column due to error: {e}")
329
+ continue
330
+
331
+ return columns_struct
332
+
333
+ def populate_feedback(element: dict, columns_struct: dict) -> dict:
334
+ """
335
+ Populate a columns_struct with values from the element's feedback.
336
+
337
+ The element dict may have feedback relationships. If these are present in the column request, extract them
338
+ and add the values to the columns_struct.
339
+ The columns_struct is expected to follow the format returned by select_report_spec, where
340
+ columns are located at columns_struct['formats']['columns'] and each column is a dict containing
341
+ at least a 'key' field expressed in snake_case. For each column whose snake_case key corresponds
342
+ to a key in the element properties (after converting to camelCase), this function adds a 'value'
343
+ entry to the column with the matching property's value.
344
+
345
+ Args:
346
+ element: The element containing a 'properties' dict with camelCase keys.
347
+ columns_struct: The columns structure whose columns have snake_case 'key' fields.
348
+
349
+ Returns:
350
+ The updated columns_struct (the input structure is modified in place and also returned).
351
+ """
352
+ if not isinstance(columns_struct, dict):
353
+ return columns_struct
354
+
355
+ # Need to think more about what I want to produce
356
+ return columns_struct
357
+ props = {}
358
+
359
+ # Get the attributes list if present
360
+ formats = columns_struct.get('formats') or {}
361
+ columns = formats.get('attributes') if isinstance(formats, dict) else None
362
+ if not isinstance(columns, list):
363
+ return columns_struct
364
+
365
+ for col in columns:
366
+ try:
367
+ key_snake = col.get('key') if isinstance(col, dict) else None
368
+ if not key_snake:
369
+ continue
370
+ # Convert the snake_case key to camelCase to look up in properties
371
+ key_camel = to_camel_case(key_snake)
372
+ if key_camel in props:
373
+ col['value'] = props.get(key_camel)
374
+ except Exception as e:
375
+ # Be resilient; log and continue
376
+ logger.debug(f"populate_columns_from_properties: skipping column due to error: {e}")
377
+ continue
378
+
379
+ return columns_struct
380
+
381
+ def get_required_relationships(element: dict, columns_struct: dict) -> dict:
382
+ """
383
+ Populate relationship-derived column values in columns_struct based on top-level keys in the element.
384
+
385
+ NOTE: This function is now a lightweight wrapper around the optimized logic in populate_common_columns.
386
+ For best performance, use populate_common_columns directly with include_relationships=True.
387
+
388
+ This function inspects the requested columns in columns_struct, converts each column key from
389
+ snake_case to camelCase, and if a matching top-level key exists in the element, parses that value
390
+ (typically lists of relationship beans) into a human-readable value (e.g., a comma-separated list
391
+ of qualified names) and stores it under the column's 'value'. Columns not specified in the
392
+ columns_struct are ignored. Existing non-empty 'value's are left as-is.
393
+
394
+ Example: if a column with key 'member_of_collections' is present, this function will look for the
395
+ top-level key 'memberOfCollections' in the element and derive a value if found.
396
+
397
+ Args:
398
+ element: The element dictionary containing top-level relationship lists (e.g., associatedGlossaries,
399
+ memberOfCollections, collectionMembers).
400
+ columns_struct: The columns structure to augment with derived 'value's.
401
+
402
+ Returns:
403
+ The updated columns_struct (modified in place and returned).
404
+ """
405
+ if not isinstance(columns_struct, dict):
406
+ return columns_struct
407
+
408
+ formats = columns_struct.get('formats') or {}
409
+ columns = formats.get('attributes') if isinstance(formats, dict) else None
410
+ if not isinstance(columns, list):
411
+ return columns_struct
412
+
413
+ # Pre-compute relationship values for efficiency
414
+ relationship_values = {}
415
+ for col in columns:
416
+ if not isinstance(col, dict):
417
+ continue
418
+ key_snake = col.get('key')
419
+ if not key_snake:
420
+ continue
421
+ # If already has a non-empty value, don't overwrite
422
+ if col.get('value') not in (None, ""):
423
+ continue
424
+
425
+ key_camel = to_camel_case(key_snake)
426
+ if key_camel not in element:
427
+ continue
428
+
429
+ top_val = element.get(key_camel)
430
+ derived_value = ""
431
+ if isinstance(top_val, list):
432
+ names = []
433
+ for item in top_val:
434
+ nm = _extract_name_from_relationship_item(item)
435
+ if nm:
436
+ names.append(nm)
437
+ derived_value = ", ".join(names)
438
+ elif isinstance(top_val, dict):
439
+ nm = _extract_name_from_relationship_item(top_val)
440
+ derived_value = nm or ""
441
+ else:
442
+ derived_value = str(top_val) if top_val is not None else ""
443
+
444
+ if derived_value:
445
+ relationship_values[key_snake] = derived_value
446
+
447
+ # Apply the values
448
+ for col in columns:
449
+ if isinstance(col, dict):
450
+ key = col.get('key')
451
+ if key and key in relationship_values and col.get('value') in (None, ""):
452
+ col['value'] = relationship_values[key]
453
+
454
+ return columns_struct
455
+
456
+
457
+
458
+
459
+ def generate_entity_md(elements: List[Dict],
460
+ elements_action: str,
461
+ output_format: str,
462
+ entity_type: str,
463
+ extract_properties_func: Callable,
464
+ get_additional_props_func: Optional[Callable] = None,
465
+ columns_struct: [dict] = None) -> str:
466
+ """
467
+ Generic method to generate markdown for entities.
468
+
469
+ Args:
470
+ elements (list): List of entity elements
471
+ elements_action (str): Action description for elements
472
+ output_format (str): Output format
473
+ entity_type (str): Type of entity (Glossary, Term, Category, etc.)
474
+ extract_properties_func: Function to extract properties from an element
475
+ get_additional_props_func: Optional function to get additional properties
476
+ columns (list): List of column name structures
477
+
478
+ Returns:
479
+ str: Markdown representation
480
+ """
481
+ heading = columns_struct.get("heading")
482
+ if heading == "Default Base Attributes":
483
+ elements_md = "## Reporting on Default Base Attributes - Perhaps couldn't find a valid combination of report_spec and output_format?\n\n"
484
+ else:
485
+ elements_md = ""
486
+ base_columns = columns_struct['formats'].get('attributes') if columns_struct else None
487
+
488
+ for element in elements:
489
+ if element is None:
490
+ continue
491
+ guid = element.get('elementHeader', {}).get('guid')
492
+
493
+ # Prefer new behavior: extractor returns an updated columns_struct with values
494
+ returned_struct = None
495
+ if columns_struct is not None:
496
+ try:
497
+ returned_struct = extract_properties_func(element, columns_struct)
498
+ except TypeError:
499
+ # Fallback for legacy extractors without columns_struct parameter
500
+ returned_struct = None
501
+
502
+ # Legacy fallback: get props dict if no columns_struct provided/returned
503
+ props = {}
504
+ if returned_struct is None:
505
+ props = extract_properties_func(element) if callable(extract_properties_func) else {}
506
+
507
+ # Get additional properties if function is provided
508
+ additional_props = {}
509
+ if get_additional_props_func:
510
+ # Use guid if available, else try to get from props
511
+ guid_for_fmt = guid or props.get('GUID')
512
+ additional_props = get_additional_props_func(element, guid_for_fmt, output_format)
513
+
514
+ # Determine display name
515
+ display_name = None
516
+ if returned_struct is not None:
517
+ cols = returned_struct.get('formats', {}).get('attributes', [])
518
+ # Find value from 'display_name' or 'title'
519
+ for col in cols:
520
+ if col.get('key') in ('display_name', 'title', 'keyword'):
521
+ display_name = col.get('value')
522
+ if display_name:
523
+ break
524
+ else:
525
+ display_name = props.get('display_name') or props.get('title') or props.get('keyword')
526
+
527
+ if display_name is None and (keyword:= element.get('properties',{}).get('keyword',None)) is not None:
528
+ display_name = keyword
529
+ elif display_name is None:
530
+ display_name = "NO DISPLAY NAME"
531
+
532
+ # Format header based on output format
533
+ if output_format in ['FORM', 'MD']:
534
+ elements_md += f"# {elements_action}\n\n"
535
+ elements_md += f"## {entity_type} Name \n\n{display_name}\n\n"
536
+ elif output_format == 'REPORT':
537
+ elements_md += f'<a id="{(guid or props.get("GUID") or "No GUID" )}"></a>\n# {entity_type} Name: {display_name}\n\n'
538
+ else:
539
+ elements_md += f"## {entity_type} Name \n\n{display_name}\n\n"
540
+
541
+ # Add attributes based on column spec if available, otherwise, add all (legacy)
542
+ if returned_struct is not None:
543
+ cols = returned_struct.get('formats', {}).get('attributes', [])
544
+ for column in cols:
545
+ name = column.get('name')
546
+ key = column.get('key')
547
+ value = column.get('value')
548
+ if value in (None, "") and key in additional_props:
549
+ value = additional_props[key]
550
+ if column.get('format'):
551
+ value = format_for_markdown_table(value, guid)
552
+ elements_md += make_md_attribute(name, value, output_format)
553
+ if wk := returned_struct.get("annotations", {}).get("wikilinks", None):
554
+ elements_md += ", ".join(wk)
555
+ elif base_columns:
556
+ # If we have columns but extractor didn't return struct, use legacy props lookup
557
+ for column in base_columns:
558
+ key = column['key']
559
+ name = column['name']
560
+ value = ""
561
+ if key in props:
562
+ value = props[key]
563
+ elif key in additional_props:
564
+ value = additional_props[key]
565
+ if column.get('format'):
566
+ value = format_for_markdown_table(value, guid or props.get('GUID'))
567
+ elements_md += make_md_attribute(name, value, output_format)
568
+ if wk := columns_struct.get("annotations", {}).get("wikilinks", None):
569
+ elements_md += ", ".join(wk)
570
+ else:
571
+ # Legacy path without columns: dump all props
572
+ for key, value in props.items():
573
+ if output_format in ['FORM', 'MD', 'DICT'] and key == 'mermaid':
574
+ continue
575
+ if key not in ['properties', 'display_name']:
576
+ if key == 'mermaid' and value == '':
577
+ continue
578
+ elements_md += make_md_attribute(key.replace('_', ' '), value, output_format)
579
+ for key, value in additional_props.items():
580
+ elements_md += make_md_attribute(key.replace('_', ' '), value, output_format)
581
+
582
+ if element != elements[-1]:
583
+ elements_md += MD_SEPARATOR
584
+
585
+ return elements_md
586
+
587
+ def generate_entity_md_table(elements: List[Dict],
588
+ search_string: str,
589
+ entity_type: str,
590
+ extract_properties_func: Callable,
591
+ columns_struct: dict,
592
+ get_additional_props_func: Optional[Callable] = None,
593
+ output_format: str = 'LIST') -> str:
594
+ """
595
+ Generic method to generate a markdown table for entities.
596
+
597
+ Args:
598
+ elements (list): List of entity elements
599
+ search_string (str): The search string used
600
+ entity_type (str): Type of entity (Glossary, Term, Category, etc.)
601
+ extract_properties_func: Function to extract properties from an element
602
+ columns: List of column definitions, each containing 'name', 'key', and 'format' (optional)
603
+ get_additional_props_func: Optional function to get additional properties
604
+ output_format (str): Output format (FORM, REPORT, LIST, etc.)
605
+
606
+ Returns:
607
+ str: Markdown table
608
+ """
609
+ # Handle pluralization - if entity_type ends with 'y', use 'ies' instead of 's'
610
+ target_type = columns_struct.get('target_type', entity_type)
611
+ # if target_type.endswith('y'):
612
+ # target_type = target_type.replace('y', 'ies')
613
+ # else:
614
+ # target_type = target_type.replace('s', 's')
615
+
616
+ entity_type_plural = f"{target_type[:-1]}ies" if target_type.endswith('y') else f"{target_type}s"
617
+ # entity_type_plural = target_type
618
+ columns = columns_struct['formats'].get('attributes', [])
619
+ heading = columns_struct.get("heading")
620
+ if heading == "Default Base Attributes":
621
+ elements_md = "## Reporting on Default Base Attributes - Perhaps couldn't find a valid combination of report_spec and output_format?\n\n"
622
+ else:
623
+ elements_md = ""
624
+
625
+ if output_format == "LIST":
626
+ elements_md = f"# {entity_type_plural} Table\n\n"
627
+ elements_md += f"{entity_type_plural} found from the search string: `{search_string}`\n\n"
628
+
629
+ # Add column headers
630
+ header_row = "| "
631
+ separator_row = "|"
632
+ for column in columns:
633
+ header_row += f"{column['name']} | "
634
+ separator_row += "-------------|"
635
+
636
+ elements_md += header_row + "\n"
637
+ elements_md += separator_row + "\n"
638
+
639
+ for element in elements:
640
+ guid = element.get('elementHeader', {}).get('guid', None)
641
+
642
+ # Extractor returns columns_struct with values when possible
643
+ # Use shallow copy and reset only column values for performance
644
+ local_columns_struct = columns_struct.copy()
645
+ if 'formats' in local_columns_struct and 'attributes' in local_columns_struct['formats']:
646
+ # Create new list of columns with reset values
647
+ local_columns_struct['formats'] = local_columns_struct['formats'].copy()
648
+ local_columns_struct['formats']['attributes'] = [
649
+ {**col, 'value': None} if isinstance(col, dict) else col
650
+ for col in local_columns_struct['formats']['attributes']
651
+ ]
652
+ try:
653
+ returned_struct = extract_properties_func(element, local_columns_struct)
654
+ except TypeError:
655
+ returned_struct = None
656
+
657
+ # For help mode, bypass extraction
658
+ if output_format == "help":
659
+ returned_struct = {"formats": {"attributes": columns}}
660
+
661
+ # Additional props (if any)
662
+ additional_props = {}
663
+ if get_additional_props_func:
664
+ additional_props = get_additional_props_func(element, guid, output_format)
665
+
666
+ # Build row
667
+ row = "| "
668
+ if returned_struct is not None:
669
+ for column in returned_struct.get('formats', {}).get('attributes', []):
670
+ key = column.get('key')
671
+ value = column.get('value')
672
+ if (value in (None, "")) and key in additional_props:
673
+ value = additional_props[key]
674
+
675
+ if value in (None, ""):
676
+ value = " --- "
677
+
678
+ if column.get('format'):
679
+ value = format_for_markdown_table(value, guid)
680
+ elif isinstance(value, str):
681
+ value = value.replace("\n", " ").replace("|", "\\|")
682
+
683
+ row += f"{value} | "
684
+ else:
685
+ # Legacy fallback: read from props dict
686
+ props = extract_properties_func(element)
687
+ for column in columns:
688
+ key = column['key']
689
+ value = " "
690
+ if key in props:
691
+ value = props[key]
692
+ elif key in additional_props:
693
+ value = additional_props[key]
694
+
695
+ if value in (None, "", " "):
696
+ value = " --- "
697
+
698
+ if column.get('format'):
699
+ value = format_for_markdown_table(value, guid or props.get('GUID'))
700
+ elif isinstance(value, str):
701
+ value = value.replace("\n", " ").replace("|", "\\|")
702
+
703
+ row += f"{value} | "
704
+
705
+ elements_md += row + "\n"
706
+ # if wk := columns_struct.get("annotations",{}).get("wikilinks", None):
707
+ # elements_md += ", ".join(wk)
708
+ return elements_md
709
+
710
+
711
+ def generate_entity_dict(elements: List[Dict],
712
+ extract_properties_func: Callable,
713
+ get_additional_props_func: Optional[Callable] = None,
714
+ include_keys: Optional[List[str]] = None,
715
+ exclude_keys: Optional[List[str]] = None,
716
+ columns_struct: dict = None,
717
+ output_format: str = 'DICT') -> List[Dict]:
718
+ """
719
+ Generic method to generate a dictionary representation of entities.
720
+
721
+ Args:
722
+ elements (list): List of entity elements
723
+ extract_properties_func: Function to extract properties from an element
724
+ get_additional_props_func: Optional function to get additional properties
725
+ include_keys: Optional list of keys to include in the result (if None, include all)
726
+ exclude_keys: Optional list of keys to exclude from the result (if None, exclude none)
727
+ columns_struct: Optional dict of columns to include (if None, include all)
728
+ output_format (str): Output format (FORM, REPORT, DICT, etc.)
729
+
730
+ Returns:
731
+ list: List of entity dictionaries
732
+ """
733
+ result = []
734
+
735
+ #####
736
+ # Add attributes based on column spec if available, otherwise, add all
737
+ import copy
738
+ for element in elements:
739
+ if not isinstance(element, dict):
740
+ continue
741
+
742
+ guid = element.get('elementHeader', {}).get('guid')
743
+
744
+ # Use shallow copy and reset only column values for performance
745
+ local_columns_struct = None
746
+ if columns_struct is not None:
747
+ local_columns_struct = columns_struct.copy()
748
+ if 'formats' in local_columns_struct and 'attributes' in local_columns_struct['formats']:
749
+ # Create new list of columns with reset values
750
+ local_columns_struct['formats'] = local_columns_struct['formats'].copy()
751
+ local_columns_struct['formats']['attributes'] = [
752
+ {**col, 'value': None} if isinstance(col, dict) else col
753
+ for col in local_columns_struct['formats']['attributes']
754
+ ]
755
+
756
+ returned_struct = None
757
+ if local_columns_struct is not None:
758
+ try:
759
+ returned_struct = extract_properties_func(element, local_columns_struct)
760
+ except TypeError as e:
761
+ logger.info(f"Error - didn't find extractor?: {e}")
762
+ returned_struct = None
763
+
764
+ # Get additional properties if function is provided
765
+ additional_props = {}
766
+ if get_additional_props_func:
767
+ additional_props = get_additional_props_func(element, guid, output_format)
768
+
769
+ # Create entity dictionary
770
+ entity_dict = {}
771
+
772
+ cols = local_columns_struct['formats'].get('attributes', None) if local_columns_struct else None
773
+ if returned_struct is not None:
774
+ for column in returned_struct.get('formats', {}).get('attributes', []):
775
+ key = column.get('key')
776
+ name = column.get('name')
777
+ value = column.get('value')
778
+ if (value in (None, "")) and key in additional_props:
779
+ value = additional_props[key]
780
+ if column.get('format'):
781
+ value = format_for_markdown_table(value, guid)
782
+ # Avoid overwriting when multiple columns share the same display name in a spec
783
+ dict_key = name
784
+ if dict_key in entity_dict:
785
+ logger.warning(f"DICT key collision for display name '{dict_key}'. Suffixing duplicate to preserve all values.")
786
+ suffix_idx = 1
787
+ tmp_key = f"{dict_key}_{suffix_idx}"
788
+ while tmp_key in entity_dict:
789
+ suffix_idx += 1
790
+ tmp_key = f"{dict_key}_{suffix_idx}"
791
+ dict_key = tmp_key
792
+ entity_dict[dict_key] = value
793
+ elif cols:
794
+ for column in cols:
795
+ key = column['key']
796
+ name = column['name']
797
+ value = ""
798
+ props = extract_properties_func(element, columns_struct)
799
+ if key in props:
800
+ value = props[key]
801
+ elif key in additional_props:
802
+ value = additional_props[key]
803
+ if column.get('format', None):
804
+ value = format_for_markdown_table(value, guid or props.get('GUID'))
805
+ dict_key = name
806
+ if dict_key in entity_dict:
807
+ logger.warning(f"DICT key collision for display name '{dict_key}'. Suffixing duplicate to preserve all values.")
808
+ suffix_idx = 1
809
+ tmp_key = f"{dict_key}_{suffix_idx}"
810
+ while tmp_key in entity_dict:
811
+ suffix_idx += 1
812
+ tmp_key = f"{dict_key}_{suffix_idx}"
813
+ dict_key = tmp_key
814
+ entity_dict[dict_key] = value
815
+ else:
816
+ props = extract_properties_func(element, local_columns_struct)
817
+ # Add properties based on include/exclude lists
818
+ for key, value in props.items():
819
+ if key not in ['properties', 'mermaid']: # Skip the raw properties object
820
+ if (include_keys is None or key in include_keys) and (
821
+ exclude_keys is None or key not in exclude_keys):
822
+ entity_dict[key] = value
823
+
824
+ # Add additional properties
825
+ for key, value in additional_props.items():
826
+ if (include_keys is None or key in include_keys) and (exclude_keys is None or key not in exclude_keys):
827
+ entity_dict[key] = value
828
+
829
+ result.append(entity_dict)
830
+ #####
831
+ # for element in elements:
832
+ # if element is None:
833
+ # continue
834
+ # props = extract_properties_func(element)
835
+ #
836
+ # # Get additional properties if function is provided
837
+ # additional_props = {}
838
+ # if get_additional_props_func:
839
+ # additional_props = get_additional_props_func(element,props['GUID'], output_format)
840
+ #
841
+ # # Create entity dictionary
842
+ # entity_dict = {}
843
+ #
844
+ # # Add properties based on include/exclude lists
845
+ # for key, value in props.items():
846
+ # if key not in [ 'properties', 'mermaid']: # Skip the raw properties object
847
+ # if (include_keys is None or key in include_keys) and (
848
+ # exclude_keys is None or key not in exclude_keys):
849
+ # entity_dict[key] = value
850
+ #
851
+ # # Add additional properties
852
+ # for key, value in additional_props.items():
853
+ # if (include_keys is None or key in include_keys) and (exclude_keys is None or key not in exclude_keys):
854
+ # entity_dict[key] = value
855
+ #
856
+ # result.append(entity_dict)
857
+
858
+ return result
859
+
860
+ def resolve_output_formats(entity_type: str,
861
+ output_format: str,
862
+ report_spec: Optional[Union[str, dict]] = None,
863
+ default_label: Optional[str] = None,
864
+ **kwargs) -> Optional[dict]:
865
+ """
866
+ Resolve a report format structure given an entity type, the desired output format
867
+ (e.g., DICT, LIST, MD, REPORT, FORM), and either a label (str) or a dict of format sets.
868
+
869
+ Notes:
870
+ - Preferred naming is report_spec (formerly output_format); compatibility for the
871
+ old 'output_format_spec' terminology has been removed.
872
+
873
+ Selection order:
874
+ - If report_spec is a str: select by label.
875
+ - If report_spec is a dict: use get_report_spec_match to pick a matching format.
876
+ - Else: try selecting by entity_type or default_label.
877
+ - Fallback: select "Default".
878
+ """
879
+ from pyegeria.view.base_report_formats import get_report_spec_match
880
+
881
+ if report_spec is None and isinstance(kwargs, dict):
882
+ if 'report_spec' in kwargs:
883
+ report_spec = kwargs.get('report_spec')
884
+ elif 'report_format' in kwargs:
885
+ # Still accept 'report_format' as a synonym for report_spec
886
+ report_spec = kwargs.get('report_format')
887
+
888
+ if isinstance(report_spec, str):
889
+ return select_report_format(report_spec, output_format)
890
+ if isinstance(report_spec, dict):
891
+ return get_report_spec_match(report_spec, output_format)
892
+
893
+ label = default_label or entity_type
894
+ fmt = select_report_format(label, output_format)
895
+ if fmt is None:
896
+ fmt = select_report_format("Default", output_format)
897
+ return fmt
898
+
899
+
900
+ def overlay_additional_values(columns_struct: dict, extra: Optional[dict]) -> dict:
901
+ """
902
+ Overlay extra values into columns_struct only where the column's value is empty or missing.
903
+ Returns the modified columns_struct.
904
+ """
905
+ if not isinstance(columns_struct, dict) or not extra:
906
+ return columns_struct
907
+ columns = columns_struct.get('formats', {}).get('attributes')
908
+ if not isinstance(columns, list):
909
+ return columns_struct
910
+ for col in columns:
911
+ if not isinstance(col, dict):
912
+ continue
913
+ key = col.get('key')
914
+ if not key:
915
+ continue
916
+ if col.get('value') in (None, "") and key in extra:
917
+ col['value'] = extra[key]
918
+ return columns_struct
919
+
920
+
921
+ def populate_common_columns(
922
+ element: dict,
923
+ columns_struct: dict,
924
+ *,
925
+ include_header: bool = True,
926
+ include_relationships: bool = True,
927
+ include_subject_area: bool = True,
928
+ mermaid_source_key: str = 'mermaidGraph',
929
+ mermaid_dest_key: str = 'mermaid'
930
+ ) -> dict:
931
+ """
932
+ Populate the common columns in columns_struct based on a standard Egeria element shape.
933
+
934
+ Steps:
935
+ - Populate from element.properties (camelCase mapped from snake_case keys)
936
+ - Optionally overlay header-derived values (GUID, type_name, times, etc.)
937
+ - Optionally populate relationship-based columns via get_required_relationships
938
+ - Optionally populate subject_area from element.elementHeader.subjectArea.classificationProperties.subjectAreaName
939
+ - If a column with key == mermaid_dest_key is present, set it from mermaid_source_key
940
+ - Do not overwrite non-empty values already set
941
+ """
942
+ # Pre-compute values that will be reused
943
+ props = element.get('properties', {}) if isinstance(element, dict) else {}
944
+ header_props = _extract_referenceable_properties(element) if include_header else {}
945
+ guid = header_props.get('GUID') if include_header else None
946
+
947
+ # Pre-compute subject area value
948
+ subject_area_val = ""
949
+ if include_subject_area:
950
+ try:
951
+ subject_area = element.get('elementHeader', {}).get('subjectArea') or ""
952
+ if isinstance(subject_area, dict):
953
+ subject_area_val = subject_area.get('classificationProperties', {}).get('subjectAreaName', '')
954
+ except Exception as e:
955
+ logger.debug(f"populate_common_columns: subject_area handling error: {e}")
956
+
957
+ # Pre-compute mermaid value
958
+ mermaid_val = element.get(mermaid_source_key, '') or ''
959
+
960
+ # Pre-compute relationship values for efficiency
961
+ relationship_values = {}
962
+ if include_relationships:
963
+ formats = columns_struct.get('formats') or {}
964
+ columns = formats.get('attributes') if isinstance(formats, dict) else None
965
+ if isinstance(columns, list):
966
+ for col in columns:
967
+ if not isinstance(col, dict):
968
+ continue
969
+ key_snake = col.get('key')
970
+ if not key_snake:
971
+ continue
972
+ key_camel = to_camel_case(key_snake)
973
+ if key_camel in element:
974
+ top_val = element.get(key_camel)
975
+ derived_value = ""
976
+ if isinstance(top_val, list):
977
+ names = []
978
+ for item in top_val:
979
+ nm = _extract_name_from_relationship_item(item)
980
+ if nm:
981
+ names.append(nm)
982
+ derived_value = ", ".join(names)
983
+ elif isinstance(top_val, dict):
984
+ nm = _extract_name_from_relationship_item(top_val)
985
+ derived_value = nm or ""
986
+ else:
987
+ derived_value = str(top_val) if top_val is not None else ""
988
+ if derived_value:
989
+ relationship_values[key_snake] = derived_value
990
+
991
+ # Single pass through columns - consolidate all operations
992
+ col_data = columns_struct
993
+ columns_list = col_data.get('formats', {}).get('attributes', [])
994
+
995
+ for column in columns_list:
996
+ if not isinstance(column, dict):
997
+ continue
998
+
999
+ key = column.get('key')
1000
+ if not key:
1001
+ continue
1002
+
1003
+ # Skip if already has a value
1004
+ if column.get('value') not in (None, ""):
1005
+ continue
1006
+
1007
+ # 1) Try properties (camelCase conversion)
1008
+ key_camel = to_camel_case(key)
1009
+ if key_camel in props:
1010
+ column['value'] = props.get(key_camel)
1011
+ continue
1012
+
1013
+ # 2) Try header properties
1014
+ if include_header:
1015
+ if key in header_props:
1016
+ column['value'] = header_props.get(key)
1017
+ continue
1018
+ elif isinstance(key, str) and key.lower() == 'guid':
1019
+ column['value'] = guid
1020
+ continue
1021
+
1022
+ # 3) Try relationship values
1023
+ if include_relationships and key in relationship_values:
1024
+ column['value'] = relationship_values[key]
1025
+ continue
1026
+
1027
+ # 4) Try subject area
1028
+ if include_subject_area and key == 'subject_area':
1029
+ column['value'] = subject_area_val
1030
+ continue
1031
+
1032
+ # 5) Try mermaid
1033
+ if key == mermaid_dest_key:
1034
+ column['value'] = mermaid_val
1035
+ continue
1036
+ if key in MERMAID_GRAPHS and mermaid_dest_key not in MERMAID_GRAPHS:
1037
+ column['value'] = element.get(key, '')
1038
+ continue
1039
+
1040
+ # Get any Feedback Values that have been requested (kept separate as it may have side effects)
1041
+ col_data = populate_feedback(element, col_data)
1042
+
1043
+ return col_data
1044
+
1045
+
1046
+ def _extract_name_from_relationship_item(item: Any) -> Optional[str]:
1047
+ """Best-effort extraction of a display/qualified name from a relationship item."""
1048
+ try:
1049
+ if isinstance(item, dict):
1050
+ # Common pattern: item['relatedElement']['properties']['qualifiedName']
1051
+ related = item.get('relatedElement') or item.get('related_element')
1052
+ if isinstance(related, dict):
1053
+ props = related.get('properties') or {}
1054
+ name = (
1055
+ props.get('qualifiedName')
1056
+ or props.get('displayName')
1057
+ or props.get('name')
1058
+ )
1059
+ if name:
1060
+ return name
1061
+ # Sometimes the properties may be at the top level of the item
1062
+ name = (
1063
+ item.get('qualifiedName')
1064
+ or item.get('displayName')
1065
+ or item.get('name')
1066
+ )
1067
+ if name:
1068
+ return name
1069
+ elif isinstance(item, str):
1070
+ return item
1071
+ except Exception as e:
1072
+ logger.debug(f"_extract_name_from_relationship_item: error extracting name: {e}")
1073
+ return None
1074
+
1075
+
1076
+ def extract_mermaid_only(elements: Union[Dict, List[Dict]]) -> Union[str, List[str]]:
1077
+ """
1078
+ Extract mermaid graph data from elements.
1079
+
1080
+ Args:
1081
+ elements: Dictionary or list of dictionaries containing element data
1082
+
1083
+ Returns:
1084
+ String or list of strings containing mermaid graph data
1085
+ """
1086
+ if isinstance(elements, dict):
1087
+ mer = elements.get('mermaidGraph', None)
1088
+ if mer:
1089
+ return f"\n```mermaid\n{mer}\n```"
1090
+ else:
1091
+ return "---"
1092
+
1093
+
1094
+ result = []
1095
+ for element in elements:
1096
+ mer = element.get('mermaidGraph', "---")
1097
+ mer_out = f"\n\n```mermaid\n{mer}\n```" if mer else "---"
1098
+ result.append(mer_out)
1099
+ return result
1100
+
1101
+ def extract_basic_dict(elements: Union[Dict, List[Dict]]) -> Union[Dict, List[Dict]]:
1102
+ """
1103
+ Extract basic dictionary data from elements.
1104
+
1105
+ Args:
1106
+ elements: Dictionary or list of dictionaries containing element data
1107
+
1108
+ Returns:
1109
+ Dictionary or list of dictionaries with extracted data
1110
+ """
1111
+ if isinstance(elements, dict):
1112
+ body = {'guid': elements['elementHeader']['guid']}
1113
+ for key in elements['properties']:
1114
+ body[key] = elements['properties'][key]
1115
+
1116
+ # Add classifications if present
1117
+ classifications = elements['elementHeader'].get('classifications', [])
1118
+ if classifications:
1119
+ classification_names = "["
1120
+ for classification in classifications:
1121
+ if len(classification_names) > 1:
1122
+ classification_names += ", "
1123
+ classification_names += f"{classification['classificationName']}"
1124
+ body['classification_names'] = classification_names + ']'
1125
+
1126
+ return body
1127
+
1128
+ result = []
1129
+ for element in elements:
1130
+ if element is None:
1131
+ continue
1132
+ body = {'guid': element['elementHeader']['guid']}
1133
+ for key in element['properties']:
1134
+ body[key] = element['properties'][key]
1135
+
1136
+ # Add classifications if present
1137
+ classifications = element['elementHeader'].get('classifications', [])
1138
+ if classifications:
1139
+ classification_names = "["
1140
+ for classification in classifications:
1141
+ if len(classification_names) > 1:
1142
+ classification_names += ", "
1143
+ classification_names += f"{classification['classificationName']}"
1144
+ body['classifications'] = classification_names + ']'
1145
+
1146
+ result.append(body)
1147
+ return result
1148
+
1149
+ def _extract_default_properties(self, element: dict, columns_struct: dict) -> dict:
1150
+ props = element.get('properties', {}) or {}
1151
+ normalized = {
1152
+ 'properties': props,
1153
+ 'elementHeader': element.get('elementHeader', {}),
1154
+ }
1155
+ # Common population pipeline
1156
+ col_data = populate_common_columns(element, columns_struct)
1157
+ columns_list = col_data.get('formats', {}).get('attributes', [])
1158
+
1159
+ return col_data
1160
+
1161
+
1162
+ def _generate_default_output(self, elements: dict | list[dict], search_string: str,
1163
+ element_type_name: str | None,
1164
+ output_format: str = 'DICT',
1165
+ report_format: dict | str | None = None,
1166
+ **kwargs) -> str | list[dict]:
1167
+ entity_type = 'Referenceable' if element_type_name is None else element_type_name
1168
+ # Backward compatibility: accept legacy kwarg
1169
+ if report_format is None and isinstance(kwargs, dict) and 'report_spec' in kwargs:
1170
+ report_format = kwargs.get('report_spec')
1171
+ if report_format:
1172
+ if isinstance(report_format, str):
1173
+ output_formats = select_report_format(report_format, output_format)
1174
+ elif isinstance(report_format, dict):
1175
+ output_formats = get_report_spec_match(report_format, output_format)
1176
+ else:
1177
+ output_formats = None
1178
+ else:
1179
+ output_formats = select_report_format(entity_type, output_format)
1180
+ if output_formats is None:
1181
+ output_formats = select_report_format('Default', output_format)
1182
+ return generate_output(
1183
+ elements=elements,
1184
+ search_string=search_string,
1185
+ entity_type=entity_type,
1186
+ output_format=output_format,
1187
+ extract_properties_func=_extract_default_properties,
1188
+ get_additional_props_func=None,
1189
+ columns_struct=output_formats,
1190
+ )
1191
+
1192
+
1193
+ def generate_output(elements: Union[Dict, List[Dict]],
1194
+ search_string: str,
1195
+ entity_type: str,
1196
+ output_format: str,
1197
+ extract_properties_func: Callable,
1198
+ get_additional_props_func: Optional[Callable] = None,
1199
+ columns_struct: dict = None) -> Union[str, list[dict]]:
1200
+ """
1201
+ Generate output in the specified format for the given elements.
1202
+
1203
+ Args:
1204
+ elements: Dictionary or list of dictionaries containing element data
1205
+ search_string: The search string used to find the elements
1206
+ entity_type: The type of entity (e.g., "Glossary", "Term", "Category")
1207
+ output_format: The desired output format (MD, FORM, REPORT, LIST, DICT, MERMAID, HTML)
1208
+ extract_properties_func: Function to extract properties from an element
1209
+ get_additional_props_func: Optional function to get additional properties
1210
+ columns: Optional list of column definitions for table output
1211
+
1212
+ Returns:
1213
+ Formatted output as string or list of dictionaries
1214
+ """
1215
+ columns = columns_struct['formats'].get('attributes',None) if columns_struct else None
1216
+ if not columns:
1217
+ columns_struct = select_report_format("Default",output_format)
1218
+ if columns_struct:
1219
+ columns = columns_struct.get('formats', {}).get('attributes', None)
1220
+
1221
+ target_type = columns_struct.get('target_type', entity_type) if columns_struct else entity_type
1222
+ if target_type is None:
1223
+ target_type = entity_type
1224
+
1225
+ # Ensure elements is a list
1226
+ if isinstance(elements, dict):
1227
+ elements = [elements]
1228
+
1229
+ # Handle empty search string
1230
+ if search_string is None or search_string == '':
1231
+ search_string = "All"
1232
+
1233
+ # Set the output format to DICT to return values to table display
1234
+ # if output_format == "TABLE":
1235
+ # output_format = "DICT"
1236
+
1237
+ # Generate output based on format
1238
+ if output_format == 'MERMAID':
1239
+ return extract_mermaid_only(elements)
1240
+
1241
+ elif output_format == 'HTML':
1242
+ # First generate the REPORT format output
1243
+ report_output = generate_output(
1244
+ elements=elements,
1245
+ search_string=search_string,
1246
+ entity_type=entity_type,
1247
+ output_format="REPORT",
1248
+ extract_properties_func=extract_properties_func,
1249
+ get_additional_props_func=get_additional_props_func,
1250
+ columns_struct=columns_struct
1251
+ )
1252
+
1253
+ # Convert the markdown to HTML
1254
+ return markdown_to_html(report_output)
1255
+
1256
+ elif output_format in ['DICT','TABLE']:
1257
+ return generate_entity_dict(
1258
+ elements=elements,
1259
+ extract_properties_func=extract_properties_func,
1260
+ get_additional_props_func=get_additional_props_func,
1261
+ exclude_keys=['properties'],
1262
+ columns_struct=columns_struct,
1263
+ output_format=output_format
1264
+ )
1265
+
1266
+ elif output_format == 'LIST':
1267
+ if columns is None:
1268
+ raise ValueError("Columns must be provided for LIST output format")
1269
+
1270
+ return generate_entity_md_table(
1271
+ elements=elements,
1272
+ search_string=search_string,
1273
+ entity_type=entity_type,
1274
+ extract_properties_func=extract_properties_func,
1275
+ columns_struct=columns_struct,
1276
+ get_additional_props_func=get_additional_props_func,
1277
+ output_format=output_format
1278
+ )
1279
+
1280
+ else: # MD, FORM, REPORT
1281
+ elements_md, elements_action = make_preamble(
1282
+ obj_type=target_type,
1283
+ search_string=search_string,
1284
+ output_format=output_format
1285
+ )
1286
+
1287
+ elements_md += generate_entity_md(
1288
+ elements=elements,
1289
+ elements_action=elements_action,
1290
+ output_format=output_format,
1291
+ entity_type=target_type,
1292
+ extract_properties_func=extract_properties_func,
1293
+ get_additional_props_func=get_additional_props_func,
1294
+ columns_struct = columns_struct
1295
+ )
1296
+
1297
+ return elements_md