pyegeria 5.3.9.9.3__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 (272) hide show
  1. commands/__init__.py +24 -0
  2. commands/cat/Dr-Egeria_md-orig.py +2 -2
  3. commands/cat/__init__.py +1 -17
  4. commands/cat/collection_actions.py +197 -0
  5. commands/cat/dr_egeria_command_help.py +372 -0
  6. commands/cat/dr_egeria_jupyter.py +7 -7
  7. commands/cat/dr_egeria_md.py +27 -182
  8. commands/cat/exp_list_glossaries.py +11 -14
  9. commands/cat/get_asset_graph.py +37 -267
  10. commands/cat/{get_collection.py → get_collection_tree.py} +10 -18
  11. commands/cat/get_project_dependencies.py +14 -14
  12. commands/cat/get_project_structure.py +15 -14
  13. commands/cat/get_tech_type_elements.py +16 -116
  14. commands/cat/glossary_actions.py +145 -298
  15. commands/cat/list_assets.py +3 -11
  16. commands/cat/list_cert_types.py +17 -63
  17. commands/cat/list_collections.py +46 -138
  18. commands/cat/list_deployed_catalogs.py +15 -27
  19. commands/cat/list_deployed_database_schemas.py +27 -43
  20. commands/cat/list_deployed_databases.py +16 -31
  21. commands/cat/list_deployed_servers.py +35 -54
  22. commands/cat/list_glossaries.py +18 -17
  23. commands/cat/list_projects.py +10 -12
  24. commands/cat/list_tech_type_elements.py +21 -37
  25. commands/cat/list_tech_types.py +13 -25
  26. commands/cat/list_terms.py +38 -79
  27. commands/cat/list_todos.py +4 -11
  28. commands/cat/list_user_ids.py +3 -10
  29. commands/cat/my_reports.py +559 -0
  30. commands/cat/run_report.py +394 -0
  31. commands/cat/run_report_orig.py +528 -0
  32. commands/cli/egeria.py +222 -247
  33. commands/cli/egeria_cat.py +68 -81
  34. commands/cli/egeria_my.py +13 -0
  35. commands/cli/egeria_ops.py +69 -74
  36. commands/cli/egeria_tech.py +17 -93
  37. commands/cli/ops_config.py +3 -6
  38. commands/{cat/list_categories.py → deprecated/list_data_designer.py} +53 -64
  39. commands/{cat/list_data_structures.py → deprecated/list_data_structures_full.py} +3 -6
  40. commands/deprecated/old_get_asset_graph.py +315 -0
  41. commands/my/__init__.py +0 -2
  42. commands/my/list_my_profile.py +27 -34
  43. commands/my/list_my_roles.py +1 -7
  44. commands/my/monitor_my_todos.py +1 -7
  45. commands/my/monitor_open_todos.py +6 -7
  46. commands/my/todo_actions.py +4 -5
  47. commands/ops/__init__.py +0 -2
  48. commands/ops/gov_server_actions.py +17 -21
  49. commands/ops/list_archives.py +17 -38
  50. commands/ops/list_catalog_targets.py +33 -40
  51. commands/ops/load_archive.py +35 -26
  52. commands/ops/{monitor_engine_activity_c.py → monitor_active_engine_activity.py} +51 -82
  53. commands/ops/{monitor_integ_daemon_status.py → monitor_daemon_status.py} +35 -55
  54. commands/ops/monitor_engine_activity.py +79 -77
  55. commands/ops/{monitor_gov_eng_status.py → monitor_engine_status.py} +10 -7
  56. commands/ops/monitor_platform_status.py +38 -50
  57. commands/ops/monitor_server_startup.py +6 -11
  58. commands/ops/monitor_server_status.py +7 -11
  59. commands/ops/orig_monitor_server_list.py +8 -8
  60. commands/ops/orig_monitor_server_status.py +1 -5
  61. commands/ops/refresh_integration_daemon.py +5 -5
  62. commands/ops/restart_integration_daemon.py +5 -5
  63. commands/ops/table_integ_daemon_status.py +6 -6
  64. commands/ops/x_engine_actions.py +7 -7
  65. commands/tech/__init__.py +0 -2
  66. commands/tech/{generic_actions.py → element_actions.py} +6 -11
  67. commands/tech/get_element_info.py +20 -29
  68. commands/tech/get_guid_info.py +23 -42
  69. commands/tech/get_tech_details.py +20 -35
  70. commands/tech/get_tech_type_template.py +28 -39
  71. commands/tech/list_all_om_type_elements.py +24 -30
  72. commands/tech/list_all_om_type_elements_x.py +22 -28
  73. commands/tech/list_all_related_elements.py +19 -28
  74. commands/tech/list_anchored_elements.py +22 -30
  75. commands/tech/list_asset_types.py +19 -24
  76. commands/tech/list_elements_by_classification_by_property_value.py +26 -32
  77. commands/tech/list_elements_by_property_value.py +19 -25
  78. commands/tech/list_elements_by_property_value_x.py +20 -28
  79. commands/tech/list_elements_for_classification.py +28 -41
  80. commands/tech/list_gov_action_processes.py +16 -27
  81. commands/tech/list_information_supply_chains.py +22 -30
  82. commands/tech/list_registered_services.py +14 -26
  83. commands/tech/list_related_elements_with_prop_value.py +15 -25
  84. commands/tech/list_related_specification.py +1 -4
  85. commands/tech/list_relationship_types.py +15 -25
  86. commands/tech/list_relationships.py +20 -36
  87. commands/tech/list_solution_blueprints.py +28 -33
  88. commands/tech/list_solution_components.py +23 -29
  89. commands/tech/list_solution_roles.py +21 -32
  90. commands/tech/list_tech_templates.py +51 -54
  91. commands/tech/list_valid_metadata_values.py +5 -9
  92. commands/tech/table_tech_templates.py +2 -6
  93. commands/tech/x_list_related_elements.py +1 -4
  94. examples/GeoSpatial Products Example.py +524 -0
  95. examples/Jupyter Notebooks/P-egeria-server-config.ipynb +2137 -0
  96. examples/Jupyter Notebooks/README.md +2 -0
  97. examples/Jupyter Notebooks/common/P-environment-check.ipynb +115 -0
  98. examples/Jupyter Notebooks/common/__init__.py +14 -0
  99. examples/Jupyter Notebooks/common/common-functions.ipynb +4694 -0
  100. examples/Jupyter Notebooks/common/environment-check.ipynb +52 -0
  101. examples/Jupyter Notebooks/common/globals.ipynb +184 -0
  102. examples/Jupyter Notebooks/common/globals.py +154 -0
  103. examples/Jupyter Notebooks/common/orig_globals.py +152 -0
  104. examples/format_sets/all_format_sets.json +910 -0
  105. examples/format_sets/custom_format_sets.json +268 -0
  106. examples/format_sets/subset_format_sets.json +187 -0
  107. examples/format_sets_save_load_example.py +291 -0
  108. examples/jacquard_data_sets.py +129 -0
  109. examples/output_formats_example.py +193 -0
  110. examples/test_jacquard_data_sets.py +54 -0
  111. examples/test_jacquard_data_sets_scenarios.py +94 -0
  112. md_processing/__init__.py +90 -0
  113. md_processing/command_dispatcher.py +33 -0
  114. md_processing/command_mapping.py +221 -0
  115. md_processing/data/commands/commands_data_designer.json +537 -0
  116. md_processing/data/commands/commands_external_reference.json +733 -0
  117. md_processing/data/commands/commands_feedback.json +155 -0
  118. md_processing/data/commands/commands_general.json +204 -0
  119. md_processing/data/commands/commands_glossary.json +218 -0
  120. md_processing/data/commands/commands_governance.json +3678 -0
  121. md_processing/data/commands/commands_product_manager.json +865 -0
  122. md_processing/data/commands/commands_project.json +642 -0
  123. md_processing/data/commands/commands_solution_architect.json +366 -0
  124. md_processing/data/commands.json +17568 -0
  125. md_processing/data/commands_working.json +30641 -0
  126. md_processing/data/gened_report_specs.py +6584 -0
  127. md_processing/data/generated_format_sets.json +6533 -0
  128. md_processing/data/generated_format_sets_old.json +4137 -0
  129. md_processing/data/generated_format_sets_old.py +45 -0
  130. md_processing/dr_egeria.py +182 -0
  131. md_processing/md_commands/__init__.py +3 -0
  132. md_processing/md_commands/data_designer_commands.py +1276 -0
  133. md_processing/md_commands/ext_ref_commands.py +530 -0
  134. md_processing/md_commands/feedback_commands.py +726 -0
  135. md_processing/md_commands/glossary_commands.py +684 -0
  136. md_processing/md_commands/governance_officer_commands.py +600 -0
  137. md_processing/md_commands/product_manager_commands.py +1266 -0
  138. md_processing/md_commands/project_commands.py +383 -0
  139. md_processing/md_commands/solution_architect_commands.py +1184 -0
  140. md_processing/md_commands/view_commands.py +295 -0
  141. md_processing/md_processing_utils/__init__.py +4 -0
  142. md_processing/md_processing_utils/common_md_proc_utils.py +1249 -0
  143. md_processing/md_processing_utils/common_md_utils.py +578 -0
  144. md_processing/md_processing_utils/determine_width.py +103 -0
  145. md_processing/md_processing_utils/extraction_utils.py +547 -0
  146. md_processing/md_processing_utils/gen_report_specs.py +643 -0
  147. md_processing/md_processing_utils/generate_dr_help.py +193 -0
  148. md_processing/md_processing_utils/generate_md_cmd_templates.py +144 -0
  149. md_processing/md_processing_utils/generate_md_templates.py +83 -0
  150. md_processing/md_processing_utils/md_processing_constants.py +1228 -0
  151. md_processing/md_processing_utils/message_constants.py +19 -0
  152. pyegeria/__init__.py +201 -443
  153. pyegeria/core/__init__.py +40 -0
  154. pyegeria/core/_base_platform_client.py +574 -0
  155. pyegeria/core/_base_server_client.py +573 -0
  156. pyegeria/core/_exceptions.py +457 -0
  157. pyegeria/core/_globals.py +60 -0
  158. pyegeria/core/_server_client.py +6073 -0
  159. pyegeria/core/_validators.py +257 -0
  160. pyegeria/core/config.py +654 -0
  161. pyegeria/{create_tech_guid_lists.py → core/create_tech_guid_lists.py} +0 -1
  162. pyegeria/core/load_config.py +37 -0
  163. pyegeria/core/logging_configuration.py +207 -0
  164. pyegeria/core/mcp_adapter.py +144 -0
  165. pyegeria/core/mcp_server.py +212 -0
  166. pyegeria/core/utils.py +405 -0
  167. pyegeria/deprecated/__init__.py +0 -0
  168. pyegeria/{_client.py → deprecated/_client.py} +62 -24
  169. pyegeria/{_deprecated_gov_engine.py → deprecated/_deprecated_gov_engine.py} +16 -16
  170. pyegeria/{classification_manager_omvs.py → deprecated/classification_manager_omvs.py} +1988 -1878
  171. pyegeria/deprecated/output_formatter_with_machine_keys.py +1127 -0
  172. pyegeria/{runtime_manager_omvs.py → deprecated/runtime_manager_omvs.py} +216 -229
  173. pyegeria/{valid_metadata_omvs.py → deprecated/valid_metadata_omvs.py} +93 -93
  174. pyegeria/{x_action_author_omvs.py → deprecated/x_action_author_omvs.py} +2 -3
  175. pyegeria/egeria_cat_client.py +25 -51
  176. pyegeria/egeria_client.py +140 -98
  177. pyegeria/egeria_config_client.py +48 -24
  178. pyegeria/egeria_tech_client.py +170 -83
  179. pyegeria/models/__init__.py +150 -0
  180. pyegeria/models/collection_models.py +168 -0
  181. pyegeria/models/models.py +654 -0
  182. pyegeria/omvs/__init__.py +84 -0
  183. pyegeria/omvs/action_author.py +342 -0
  184. pyegeria/omvs/actor_manager.py +5980 -0
  185. pyegeria/omvs/asset_catalog.py +842 -0
  186. pyegeria/omvs/asset_maker.py +2736 -0
  187. pyegeria/omvs/automated_curation.py +4403 -0
  188. pyegeria/omvs/classification_manager.py +11213 -0
  189. pyegeria/omvs/collection_manager.py +5780 -0
  190. pyegeria/omvs/community_matters_omvs.py +468 -0
  191. pyegeria/{core_omag_server_config.py → omvs/core_omag_server_config.py} +157 -157
  192. pyegeria/{data_designer_omvs.py → omvs/data_designer.py} +1991 -1691
  193. pyegeria/omvs/data_discovery.py +869 -0
  194. pyegeria/omvs/data_engineer.py +372 -0
  195. pyegeria/omvs/digital_business.py +1133 -0
  196. pyegeria/omvs/external_links.py +1752 -0
  197. pyegeria/omvs/feedback_manager.py +834 -0
  198. pyegeria/{full_omag_server_config.py → omvs/full_omag_server_config.py} +73 -69
  199. pyegeria/omvs/glossary_manager.py +3231 -0
  200. pyegeria/omvs/governance_officer.py +3009 -0
  201. pyegeria/omvs/lineage_linker.py +314 -0
  202. pyegeria/omvs/location_arena.py +1525 -0
  203. pyegeria/omvs/metadata_expert.py +668 -0
  204. pyegeria/omvs/metadata_explorer_omvs.py +2943 -0
  205. pyegeria/omvs/my_profile.py +1042 -0
  206. pyegeria/omvs/notification_manager.py +358 -0
  207. pyegeria/omvs/people_organizer.py +394 -0
  208. pyegeria/{platform_services.py → omvs/platform_services.py} +113 -193
  209. pyegeria/omvs/product_manager.py +1825 -0
  210. pyegeria/omvs/project_manager.py +1907 -0
  211. pyegeria/omvs/reference_data.py +1140 -0
  212. pyegeria/omvs/registered_info.py +334 -0
  213. pyegeria/omvs/runtime_manager.py +2817 -0
  214. pyegeria/omvs/schema_maker.py +446 -0
  215. pyegeria/{server_operations.py → omvs/server_operations.py} +27 -26
  216. pyegeria/omvs/solution_architect.py +6490 -0
  217. pyegeria/omvs/specification_properties.py +37 -0
  218. pyegeria/omvs/subject_area.py +1042 -0
  219. pyegeria/omvs/template_manager_omvs.py +236 -0
  220. pyegeria/omvs/time_keeper.py +1761 -0
  221. pyegeria/omvs/valid_metadata.py +3221 -0
  222. pyegeria/omvs/valid_metadata_lists.py +37 -0
  223. pyegeria/omvs/valid_type_lists.py +37 -0
  224. pyegeria/view/__init__.py +28 -0
  225. pyegeria/view/_output_format_models.py +514 -0
  226. pyegeria/view/_output_formats.py +14 -0
  227. pyegeria/view/base_report_formats.py +2719 -0
  228. pyegeria/view/dr_egeria_reports.py +56 -0
  229. pyegeria/view/format_set_executor.py +397 -0
  230. pyegeria/{md_processing_utils.py → view/md_processing_utils.py} +5 -5
  231. pyegeria/{mermaid_utilities.py → view/mermaid_utilities.py} +2 -154
  232. pyegeria/view/output_formatter.py +1297 -0
  233. pyegeria-5.5.3.3.dist-info/METADATA +218 -0
  234. pyegeria-5.5.3.3.dist-info/RECORD +241 -0
  235. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.5.3.3.dist-info}/WHEEL +2 -1
  236. pyegeria-5.5.3.3.dist-info/entry_points.txt +103 -0
  237. pyegeria-5.5.3.3.dist-info/top_level.txt +4 -0
  238. commands/cat/.DS_Store +0 -0
  239. commands/cat/README.md +0 -16
  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/monitor_asset_events.py +0 -108
  244. commands/tech/README.md +0 -24
  245. pyegeria/.DS_Store +0 -0
  246. pyegeria/README.md +0 -35
  247. pyegeria/_globals.py +0 -47
  248. pyegeria/_validators.py +0 -385
  249. pyegeria/asset_catalog_omvs.py +0 -864
  250. pyegeria/automated_curation_omvs.py +0 -3765
  251. pyegeria/collection_manager_omvs.py +0 -2744
  252. pyegeria/dr.egeria spec.md +0 -9
  253. pyegeria/egeria_my_client.py +0 -56
  254. pyegeria/feedback_manager_omvs.py +0 -4573
  255. pyegeria/glossary_browser_omvs.py +0 -3728
  256. pyegeria/glossary_manager_omvs.py +0 -2440
  257. pyegeria/m_test.py +0 -118
  258. pyegeria/md_processing_helpers.py +0 -58
  259. pyegeria/md_processing_utils_orig.py +0 -1103
  260. pyegeria/metadata_explorer_omvs.py +0 -2326
  261. pyegeria/my_profile_omvs.py +0 -1022
  262. pyegeria/output_formatter.py +0 -389
  263. pyegeria/project_manager_omvs.py +0 -1933
  264. pyegeria/registered_info.py +0 -167
  265. pyegeria/solution_architect_omvs.py +0 -2156
  266. pyegeria/template_manager_omvs.py +0 -1414
  267. pyegeria/utils.py +0 -197
  268. pyegeria-5.3.9.9.3.dist-info/METADATA +0 -72
  269. pyegeria-5.3.9.9.3.dist-info/RECORD +0 -143
  270. pyegeria-5.3.9.9.3.dist-info/entry_points.txt +0 -99
  271. /pyegeria/{_exceptions.py → deprecated/_exceptions.py} +0 -0
  272. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.5.3.3.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,103 @@
1
+ """
2
+ Determines the width of a markdown table
3
+ """
4
+
5
+ import re
6
+ import html
7
+ from wcwidth import wcswidth
8
+
9
+ def split_row(line: str) -> list[str]:
10
+ s = line.strip()
11
+ if s.startswith("|"):
12
+ s = s[1:]
13
+ if s.endswith("|"):
14
+ s = s[:-1]
15
+
16
+ parts = []
17
+ cur = []
18
+ escape = False
19
+ for ch in s:
20
+ if escape:
21
+ cur.append(ch)
22
+ escape = False
23
+ elif ch == "\\":
24
+ escape = True
25
+ elif ch == "|":
26
+ parts.append("".join(cur))
27
+ cur = []
28
+ else:
29
+ cur.append(ch)
30
+ parts.append("".join(cur))
31
+ return parts
32
+
33
+ IMG_RE = re.compile(r'!\[([^\]]*)\]\([^)]+\)')
34
+ LINK_RE = re.compile(r'\[([^\]]*)\]\([^)]+\)')
35
+ CODE_TICKS_RE = re.compile(r'`([^`]*)`')
36
+ EMPH_RE = re.compile(r'(\*\*|\*|__|_)')
37
+
38
+ def visible_text(md: str) -> str:
39
+ s = md
40
+ s = IMG_RE.sub(lambda m: m.group(1), s) # images → alt
41
+ s = LINK_RE.sub(lambda m: m.group(1), s) # links → text
42
+ s = CODE_TICKS_RE.sub(lambda m: m.group(1), s) # remove backticks
43
+ s = EMPH_RE.sub("", s) # remove emphasis markers
44
+
45
+ # unescape common backslash-escapes
46
+ s = (s
47
+ .replace("\\|", "|")
48
+ .replace("\\*", "*")
49
+ .replace("\\_", "_")
50
+ .replace("\\`", "`")
51
+ .replace("\\\\", "\\"))
52
+
53
+ s = html.unescape(s) # & → &
54
+ return s.strip()
55
+
56
+ def is_alignment_row(line: str) -> bool:
57
+ parts = split_row(line)
58
+ if not parts:
59
+ return False
60
+ def is_align_cell(c: str) -> bool:
61
+ c = c.strip()
62
+ return c != "" and all(ch in ":-" for ch in c)
63
+ return all(is_align_cell(p) for p in parts)
64
+
65
+ def column_widths(md_table: str) -> list[int]:
66
+ lines = [ln for ln in md_table.splitlines() if ln.strip()]
67
+ if not lines:
68
+ return []
69
+
70
+ lines_wo_align = [ln for ln in lines if not is_alignment_row(ln)]
71
+
72
+ rows = [split_row(ln) for ln in lines_wo_align]
73
+ if not rows:
74
+ return []
75
+
76
+ max_cols = max(len(r) for r in rows)
77
+ for r in rows:
78
+ if len(r) < max_cols:
79
+ r.extend([""] * (max_cols - len(r)))
80
+
81
+ widths = [0] * max_cols
82
+ for r in rows:
83
+ for i, cell in enumerate(r):
84
+ text = visible_text(cell)
85
+ w = wcswidth(text)
86
+ if w < 0: # non-printables fallback
87
+ w = len(text)
88
+ widths[i] = max(widths[i], w)
89
+ print(widths)
90
+ return widths
91
+
92
+ # Example usage
93
+ if __name__ == "__main__":
94
+ table = """
95
+ | Attribute Name | Input Required | Read Only | Generated | Default Value | Notes | Unique Values | Valid Values |
96
+ | ------------------- | -------------- | --------- | --------- | ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------- |
97
+ | Display Name | True | True | False | None | Name of the definition. | False | |
98
+ | Summary | False | True | False | None | Summary of the definition. | False | |
99
+ | Description | False | True | False | None | Description of the contents of the definition. | False | |
100
+ | Category | False | True | False | None | A user specified category name that can be used for example, to define product types or agreement types. | False | |
101
+ """
102
+
103
+ print(column_widths(table)) # e.g., [9, 28, 36] depending on characters
@@ -0,0 +1,547 @@
1
+ """
2
+ This file contains functions for extracting data from text for Egeria Markdown processing
3
+ """
4
+ import re
5
+ from typing import Any
6
+
7
+ from md_processing.md_processing_utils.common_md_utils import (print_msg, find_key_with_value, get_element_dictionary,
8
+ update_element_dictionary)
9
+ from md_processing.md_processing_utils.message_constants import INFO, EXISTS_REQUIRED
10
+ from md_processing.md_processing_utils.md_processing_constants import debug_level
11
+ from pyegeria.core._globals import NO_ELEMENTS_FOUND
12
+ from pyegeria.egeria_tech_client import EgeriaTech
13
+
14
+
15
+ def extract_command_plus(block: str) -> tuple[str, str, str] | None:
16
+ """
17
+ Extracts a multi-word object and its associated action from the given block of text.
18
+
19
+ This function searches for a pattern in the format of `#...##` or `#...\n`
20
+ inside the provided string `block`. The matched pattern is split into
21
+ two parts: the action and the object type. The action is expected to
22
+ be the first part, while the rest is treated as the object type. If
23
+ no match is found, the function returns None.
24
+
25
+ Lines beginning with '>' are ignored.
26
+
27
+ Args:
28
+ block: A string containing the block of text to search for the
29
+ object_action and action.
30
+
31
+ Returns:
32
+ A tuple containing the object_action, the object type, and the object action if a
33
+ match is found. Otherwise, returns None.
34
+ """
35
+ # Filter out lines beginning with '>'
36
+ filtered_lines = [line for line in block.split('\n') if not line.strip().startswith('>')]
37
+ filtered_block = '\n'.join(filtered_lines)
38
+
39
+ match = re.search(r"#(.*?)(?:##|\n|$)", filtered_block) # Using a non capturing group
40
+ if match:
41
+ clean_match = match.group(1).strip()
42
+ if ' ' in clean_match:
43
+ parts = clean_match.split(' ')
44
+ object_action = parts[0].strip()
45
+ # Join the rest of the parts to allow object_type to be one or two words
46
+ object_type = ' '.join(parts[1:]).strip()
47
+ else:
48
+ object_type = clean_match.split(' ')[1].strip()
49
+ object_action = clean_match.split(' ')[0].strip()
50
+
51
+ return clean_match, object_type, object_action
52
+ return None
53
+
54
+
55
+ def extract_command(block: str) -> str | None:
56
+ """
57
+ Extracts a object_action from a block of text that is contained between a single hash ('#') and
58
+ either a double hash ('##'), a newline character, or the end of the string.
59
+
60
+ The function searches for a specific pattern within the block of text and extracts the
61
+ content that appears immediately after a single hash ('#'). Ensures that the extracted
62
+ content is appropriately trimmed of leading or trailing whitespace, if present.
63
+
64
+ Args:
65
+ block: A string representing the block of text to process. Contains the content
66
+ in which the object_action and delimiters are expected to be present.
67
+
68
+ Returns:
69
+ The extracted object_action as a string if a match is found, otherwise None.
70
+ """
71
+ match = re.search(r"#(.*?)(?:##|\n|$)", block) # Using a non-capturing group
72
+ if match:
73
+ return match.group(1).strip()
74
+ return None
75
+
76
+
77
+ # def extract_attribute(text: str, labels: set) -> str | None:
78
+ # """
79
+ # Extracts the attribute value from a string.
80
+ #
81
+ # Args:
82
+ # text: The input string.
83
+ # labels: List of equivalent labels to search for
84
+ #
85
+ # Returns:
86
+ # The value of the attribute, or None if not found.
87
+ #
88
+ # Note:
89
+ # Lines beginning with '>' are ignored.
90
+ # """
91
+ # # Iterate over the list of labels
92
+ # for label in labels:
93
+ # # Construct pattern for the current label
94
+ # # text = re.sub(r'\s+', ' ', text).strip() # just added
95
+ # # text = re.sub(r'\n\n+', '\n\n', text).strip()
96
+ #
97
+ # # Replace multiple spaces or tabs with a single space
98
+ # normalized = re.sub(r'\s+', ' ', text)
99
+ # # Collapse multiple blank lines into a single one
100
+ # normalized = re.sub(r'\n\s*\n', '\n', normalized).strip()
101
+ #
102
+ # # label = label.strip()
103
+ # # pattern = rf"##\s*{re.escape(label)}\s*\n(?:\s*\n)*?(.*?)(?:#|___|$)"
104
+ # # Normalize the label
105
+ # normalized_label = re.sub(r'\s+', ' ', label.strip())
106
+ #
107
+ # # Construct the regex pattern
108
+ # pattern = rf"##\s*{re.escape(normalized_label)}\s*\n(?:\s*\n)*?(.*?)(?:#|___|$)"
109
+ # # pattern = rf"##\s+{re.escape(label)}\n(.*?)(?:#|___|$)" # modified from --- to enable embedded tables
110
+ # match = re.search(pattern, text, re.DOTALL)
111
+ # if match:
112
+ # # Extract matched text
113
+ # matched_text = match.group(1)
114
+ #
115
+ # # Filter out lines beginning with '>'
116
+ # filtered_lines = [line for line in matched_text.split('\n') if not line.strip().startswith('>')]
117
+ # filtered_text = '\n'.join(filtered_lines)
118
+ #
119
+ # # Replace consecutive \n with a single \n
120
+ # extracted_text = re.sub(r'\n+', '\n', filtered_text)
121
+ # if not extracted_text.isspace() and extracted_text:
122
+ # return extracted_text.strip() # Return the cleaned text - I removed the title casing
123
+ #
124
+ # return None
125
+
126
+
127
+ from typing import Optional, List
128
+
129
+ def extract_attribute(text: str, labels: List[str]) -> Optional[str]:
130
+ """
131
+ def extract_attribute(text: str, labels: List[str]) -> Optional[str]:
132
+
133
+ Extracts the attribute value from a string while:
134
+ - Preserving single newlines within the matched text.
135
+ - Removing lines starting with '>'.
136
+
137
+ Args:
138
+ text: The input string containing labeled sections.
139
+ labels: List of equivalent labels to search for.
140
+
141
+ Returns:
142
+ The cleaned value of the attribute, or None if not found.
143
+ """
144
+
145
+
146
+ for label in labels:
147
+ # Construct pattern for the current label - stops at next ##, ___ separator, or end of text
148
+ pattern = rf"## {re.escape(label)}\n(.*?)(?=^##|^_{3,}|\Z)"
149
+ match = re.search(pattern, text, re.DOTALL | re.MULTILINE)
150
+ if match:
151
+ # Extract matched text
152
+ extracted_text = match.group(1)
153
+
154
+ # Remove lines starting with '>' and lines that are only underscores/whitespace
155
+ filtered_lines = [
156
+ line for line in extracted_text.splitlines()
157
+ if not line.lstrip().startswith(">") and not re.match(r'^\s*_+\s*$', line)
158
+ ]
159
+ # Join the lines back, preserving single newlines
160
+ cleaned_text = "\n".join(filtered_lines).strip()
161
+
162
+ if cleaned_text:
163
+ return cleaned_text # Return the cleaned and formatted text
164
+ return None
165
+
166
+
167
+
168
+
169
+ def process_simple_attribute(txt: str, labels: set, if_missing: str = INFO) -> str | None:
170
+ """Process a simple attribute based on the provided labels and if_missing value.
171
+ Extract the attribute value from the text and return it if it exists.
172
+ If it doesn`t exist, return None and print an error message with severity of if_missing.
173
+
174
+ Parameters:
175
+ ----------
176
+ txt: str
177
+ The block of object_action text to extract attributes from.
178
+ labels: list
179
+ The possible attribute labels to search for. The first label will be used in messages.
180
+ if_missing: str, default is INFO
181
+ Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
182
+ """
183
+ if if_missing not in ["WARNING", "ERROR", "INFO"]:
184
+ print_msg("ERROR", "Invalid severity for missing attribute", debug_level)
185
+ return None
186
+
187
+ attribute = extract_attribute(txt, labels)
188
+
189
+ if attribute is None:
190
+ if if_missing == INFO:
191
+ msg = f"Optional attribute with labels `{labels}` missing"
192
+ else:
193
+ msg = f"Missing attribute with labels `{labels}` "
194
+ print_msg(if_missing, msg, debug_level)
195
+ return None
196
+ return attribute.strip()
197
+
198
+
199
+ # def process_simple_attribute(txt: str, labels: list[str], if_missing: str = INFO) -> str | None:
200
+ # """
201
+ # Processes a simple attribute from a string.
202
+ #
203
+ # Args:
204
+ # txt: The input string.
205
+ # labels: List of equivalent labels to search for
206
+ # if_missing: The message level to use if the attribute is missing.
207
+ #
208
+ # Returns:
209
+ # The value of the attribute, or None if not found.
210
+ # """
211
+ # from md_processing.md_processing_utils.common_utils import debug_level, print_msg
212
+ #
213
+ # attribute = extract_attribute(txt, labels)
214
+ # if attribute is None and if_missing:
215
+ # msg = f"No {labels[0]} found"
216
+ # print_msg(if_missing, msg, debug_level)
217
+ # return attribute
218
+
219
+
220
+ def process_name_list(egeria_client: EgeriaTech, element_type: str, txt: str, element_labels: set) -> tuple[str,
221
+ list[Any], bool | Any, bool | None | Any] | None:
222
+ """
223
+ Processes a list of names specified in the given text, retrieves details for each
224
+ element based on the provided type, and generates a list of valid qualified names.
225
+
226
+ The function reads a text block, extracts a list of element names according to the specified
227
+ element type, looks them up using the provided Egeria client, and classifies them as valid or
228
+ invalid. It returns the processed names, a list of qualified names, and validity and existence
229
+ flags.
230
+
231
+ Args:
232
+
233
+ egeria_client (EgeriaTech): The client instance to connect and query elements from an
234
+ external system.
235
+ Element_type (str): The type of element, such as schema or attribute, to process.
236
+ Txt (str): The raw input text containing element names to be processed.
237
+ element_labels: a list of equivalent label names to use in processing the element.
238
+
239
+ Returns:
240
+ tuple[str | None, list | None, bool, bool]: A tuple containing:
241
+ - Concatenated valid input names as a single string (or None if empty).
242
+ - A list of known qualified names extracted from the processed elements.
243
+ - A boolean indicating whether all elements are valid.
244
+ - A boolean indicating whether all elements exist.
245
+ """
246
+ valid = True
247
+ exists = True
248
+ elements = ""
249
+ new_element_list = []
250
+
251
+ elements_txt = extract_attribute(txt, element_labels)
252
+
253
+ if elements_txt is None:
254
+ msg = f"No {element_type} found"
255
+ print_msg("DEBUG-INFO", msg, debug_level)
256
+
257
+ else:
258
+ element_list = re.split(r'[,\n]+', elements_txt)
259
+
260
+ for element in element_list:
261
+ element_el = element.strip()
262
+
263
+ # Get the element using the generalized function
264
+ known_q_name, known_guid, el_valid, el_exists = get_element_by_name(egeria_client, element_type, element_el)
265
+ # print_msg("DEBUG-INFO", status_msg, debug_level)
266
+
267
+ if el_exists and el_valid:
268
+ elements = f"{element_el} {elements}" # list of the input names
269
+ new_element_list.append(known_q_name) # list of qualified names
270
+ elif not el_exists:
271
+ msg = f"No {element_type} `{element_el}` found"
272
+ print_msg("DEBUG-INFO", msg, debug_level)
273
+ valid = False
274
+ valid = valid if el_valid is None else (valid and el_valid)
275
+ exists = exists and el_exists
276
+
277
+ if elements:
278
+ # elements += "\n"
279
+ msg = f"Found {element_type}: {elements}"
280
+ print_msg("DEBUG-INFO", msg, debug_level)
281
+ else:
282
+ msg = " Name list contains one or more invalid qualified names."
283
+ print_msg("DEBUG-INFO", msg, debug_level)
284
+ return elements, new_element_list, valid, exists
285
+
286
+
287
+ # def process_name_list(egeria_client, element_type: str, txt: str, element_labels: list[str]) -> tuple[list, list,
288
+ # bool, bool]:
289
+ # """
290
+ # Processes a list of names from a string.
291
+ #
292
+ # Args:
293
+ # egeria_client: The Egeria client to use for validation.
294
+ # element_type: The type of element to process.
295
+ # txt: The input string.
296
+ # element_labels: List of equivalent labels to search for
297
+ #
298
+ # Returns:
299
+ # A tuple containing:
300
+ # - A list of element names
301
+ # - A list of element qualified names
302
+ # - A boolean indicating if all elements are valid
303
+ # - A boolean indicating if any elements exist
304
+ # """
305
+ # from md_processing.md_processing_utils.common_utils import debug_level, print_msg
306
+ #
307
+ # element_names = []
308
+ # element_q_names = []
309
+ # all_valid = True
310
+ # any_exist = False
311
+ #
312
+ # # Get the list of element names
313
+ # element_list = process_simple_attribute(txt, element_labels)
314
+ # if element_list:
315
+ # # Split the list by commas or newlines
316
+ # element_names = list(filter(None, re.split(r'[,\n]+', element_list.strip())))
317
+ #
318
+ # # Validate each element
319
+ # for element_name in element_names:
320
+ # element_name = element_name.strip()
321
+ # if element_name:
322
+ # element = get_element_by_name(egeria_client, element_type, element_name)
323
+ # if element:
324
+ # any_exist = True
325
+ # element_q_name = element.get('qualifiedName', None)
326
+ # if element_q_name:
327
+ # element_q_names.append(element_q_name)
328
+ # else:
329
+ # all_valid = False
330
+ # msg = f"Element {element_name} has no qualified name"
331
+ # print_msg("ERROR", msg, debug_level)
332
+ # else:
333
+ # all_valid = False
334
+ # msg = f"Element {element_name} not found"
335
+ # print_msg("ERROR", msg, debug_level)
336
+ #
337
+ # return element_names, element_q_names, all_valid, any_exist
338
+
339
+ def process_element_identifiers(egeria_client: EgeriaTech, element_type: str, element_labels: set, txt: str,
340
+ action: str, version: str = None) -> tuple[str, str, bool, bool]:
341
+ """
342
+ Processes element identifiers by extracting display name and qualified name from the input text,
343
+ checking if the element exists in Egeria and validating the information.
344
+
345
+ Parameters
346
+ ----------
347
+ egeria_client: EgeriaTech
348
+ Client object for interacting with Egeria.
349
+ element_type: str
350
+ type of element to process (e.g., 'blueprint', 'category', 'term')
351
+ element_labels: a list of equivalent label names to use in processing the element.
352
+ txt: str
353
+ A string representing the input text to be processed for extracting element identifiers.
354
+ action: str
355
+ The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
356
+ version: str, optional = None
357
+ An optional version identifier used if we need to construct the qualified name
358
+
359
+ Returns: tuple[str, str, str, bool, bool]
360
+ A tuple containing:
361
+ - qualified_name: Empty string or element identifier
362
+ - guid: Empty string or additional element information
363
+ - Valid: Boolean indicating if the element information is valid
364
+ - Exists: Boolean indicating if the element exists in Egeria
365
+ """
366
+ valid = True
367
+
368
+ element_name = extract_attribute(txt, element_labels)
369
+ qualified_name = extract_attribute(txt, ["Qualified Name"])
370
+
371
+ if qualified_name:
372
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type,
373
+ qualified_name) # Qualified name could be different if it
374
+ # is being updated
375
+ else:
376
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
377
+ if unique is False:
378
+ msg = f"Multiple elements named {element_name} found"
379
+ print_msg("DEBUG-ERROR", msg, debug_level)
380
+ valid = False
381
+
382
+ if action == "Update" and not exists:
383
+ msg = f"Element {element_name} does not exist"
384
+ print_msg("DEBUG-ERROR", msg, debug_level)
385
+ valid = False
386
+
387
+ elif action == "Update" and exists:
388
+ msg = f"Element {element_name} exists"
389
+ print_msg("DEBUG-INFO", msg, debug_level)
390
+
391
+ elif action == "Create" and exists:
392
+ msg = f"Element {element_name} already exists"
393
+ print_msg("DEBUG-ERROR", msg, debug_level)
394
+ valid = False
395
+
396
+ elif action == "Create" and not exists:
397
+ msg = f"{element_type} `{element_name}` does not exist"
398
+ print_msg("DEBUG-INFO", msg, debug_level)
399
+
400
+ if q_name is None and qualified_name is None:
401
+ q_name = egeria_client.__create_qualified_name__(element_type, element_name, version_identifier=version)
402
+ update_element_dictionary(q_name, {'display_name': element_name})
403
+ elif qualified_name:
404
+ update_element_dictionary(qualified_name, {'display_name': element_name})
405
+ elif action == EXISTS_REQUIRED:
406
+ if not exists:
407
+ msg = f"Required {element_type} `{element_name}` does not exist"
408
+ print_msg("DEBUG-ERROR", msg, debug_level)
409
+ valid = False
410
+ else:
411
+ msg = f"Required {element_type} `{element_name}` exists"
412
+ print_msg("DEBUG-INFO", msg, debug_level)
413
+ valid = True
414
+
415
+ return q_name, guid, valid, exists
416
+
417
+
418
+ def get_element_by_name(egeria_client, element_type: str, element_name: str) -> tuple[
419
+ str | None, str | None, bool | None, bool | None]:
420
+ """
421
+ Generalized function to retrieve an element by name based on its type.
422
+
423
+ Parameters:
424
+ egeria_client: Client
425
+ Client object for interacting with Egeria.
426
+ element_type: str
427
+ The type of element to retrieve (e.g., 'blueprint', 'category', 'term').
428
+ element_name: str
429
+ The name of the element to retrieve.
430
+
431
+ Returns:
432
+ tuple of qualified_name, guid, uniqye, exists
433
+ """
434
+ unique = None
435
+
436
+ element_dict = get_element_dictionary()
437
+ q_name = find_key_with_value(element_name)
438
+ if q_name: # use information from element_dictionary
439
+ guid = element_dict[q_name].get('guid', None)
440
+ unique = True
441
+
442
+ if guid is not None: # Found complete entry in element_dictionary
443
+ msg = f'Found {element_type} qualified name and guid in element_dictionary for `{element_name}`'
444
+ print_msg("DEBUG-INFO", msg, debug_level)
445
+ exists = True
446
+ return q_name, guid, unique, exists
447
+
448
+ else: # Missing guid from element_dictionary
449
+ guid = egeria_client.__get_guid__(qualified_name=q_name)
450
+ if guid == NO_ELEMENTS_FOUND:
451
+ guid = None
452
+ exists = False
453
+ msg = f"No {element_type} guid found with name {element_name} in Egeria"
454
+ print_msg("DEBUG-INFO", msg, debug_level)
455
+
456
+ return q_name, guid, unique, exists
457
+ else:
458
+ exists = True
459
+ update_element_dictionary(q_name, {'guid': guid})
460
+ msg = f"Found guid value of {guid} for {element_name} in Egeria"
461
+ print_msg("DEBUG-INFO", msg, debug_level)
462
+
463
+ return q_name, guid, unique, exists
464
+
465
+ # Haven't seen this element before
466
+ property_names = ['qualifiedName', 'displayName', 'title']
467
+ if element_type == "InformalTag":
468
+ open_metadata_type_name = element_type
469
+ else:
470
+ open_metadata_type_name = None
471
+ details = egeria_client.get_elements_by_property_value(element_name, property_names, open_metadata_type_name)
472
+ if isinstance(details, str):
473
+ msg = f"{element_type} `{element_name}` not found in Egeria"
474
+ print_msg("DEBUG-INFO", msg, debug_level)
475
+ exists = False
476
+ return None, None, unique, exists
477
+ if len(details) > 1:
478
+ if q_name is None:
479
+ q_name = egeria_client.__create_qualified_name__(element_type, element_name)
480
+ guid = egeria_client.__get_guid__(qualified_name=q_name)
481
+ update_element_dictionary(q_name, {'guid': guid})
482
+ exists = True if guid != "No elements found" else False
483
+ return q_name, guid, unique, exists
484
+ else:
485
+ msg = (f"More than one element with name {element_name} found, please specify a "
486
+ f"**Qualified Name**")
487
+ print_msg("DEBUG-ERROR", msg, debug_level)
488
+ unique = False
489
+ exists = None
490
+ return element_name, None, unique, exists
491
+
492
+ el_qname = details[0]["properties"].get('qualifiedName', None)
493
+ el_guid = details[0]['elementHeader']['guid']
494
+ el_display_name = details[0]["properties"].get('displayName', None)
495
+ if el_display_name is None:
496
+ el_display_name = details[0]["properties"].get('name', None)
497
+ update_element_dictionary(el_qname, {
498
+ 'guid': el_guid, 'displayName': el_display_name
499
+ })
500
+ msg = f"Found {element_type} `{el_display_name}` with qualified name `{el_qname}`"
501
+ print_msg("DEBUG-INFO", msg, debug_level)
502
+ exists = True
503
+ unique = True
504
+ return el_qname, el_guid, unique, exists
505
+
506
+
507
+ def update_a_command(txt: str, object_action: str, obj_type: str, q_name: str, u_guid: str) -> str:
508
+ """
509
+ Updates a command in a string.
510
+
511
+ Args:
512
+ txt: The input string.
513
+ object_action: The command to update.
514
+ obj_type: The type of object to update.
515
+ q_name: The qualified name of the object.
516
+ u_guid: The GUID of the object.
517
+
518
+ Returns:
519
+ The updated string.
520
+ """
521
+
522
+ # Determine the new action
523
+ new_action = "Update" if object_action == "Create" else "Create"
524
+
525
+ # Replace the object_action
526
+ new_command = f"{new_action} {obj_type}"
527
+ pattern = rf"#\s*{object_action}\s+{obj_type}"
528
+ replacement = f"# {new_command}"
529
+ updated_txt = re.sub(pattern, replacement, txt)
530
+
531
+ # Add qualified name and GUID if updating
532
+ if new_action == "Update" and q_name and u_guid:
533
+ # Check if Qualified Name section exists
534
+ if "## Qualified Name" not in updated_txt:
535
+ # Add Qualified Name section before the first ## that's not part of the object_action
536
+ pattern = r"(##\s+[^#\n]+)"
537
+ replacement = f"## Qualified Name\n{q_name}\n\n\\1"
538
+ updated_txt = re.sub(pattern, replacement, updated_txt, count=1)
539
+
540
+ # Check if GUID section exists
541
+ if "## GUID" not in updated_txt and "## guid" not in updated_txt:
542
+ # Add GUID section before the first ## that's not part of the object_action or Qualified Name
543
+ pattern = r"(##\s+(?!Qualified Name)[^#\n]+)"
544
+ replacement = f"## GUID\n{u_guid}\n\n\\1"
545
+ updated_txt = re.sub(pattern, replacement, updated_txt, count=1)
546
+
547
+ return updated_txt