pyegeria 5.3.9.9.2__py3-none-any.whl → 5.3.9.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. md_processing/__init__.py +49 -0
  2. md_processing/commands.json +3252 -0
  3. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +254 -0
  4. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +696 -0
  5. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +254 -0
  6. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +298 -0
  7. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +608 -0
  8. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +94 -0
  9. md_processing/dr_egeria_inbox/archive/freddie_intro.md +284 -0
  10. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +275 -0
  11. md_processing/dr_egeria_inbox/archive/test-term.md +110 -0
  12. md_processing/dr_egeria_inbox/cat_test.md +100 -0
  13. md_processing/dr_egeria_inbox/data_field.md +54 -0
  14. md_processing/dr_egeria_inbox/data_spec.md +77 -0
  15. md_processing/dr_egeria_inbox/data_spec_test.md +2406 -0
  16. md_processing/dr_egeria_inbox/data_test.md +86 -0
  17. md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +168 -0
  18. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +280 -0
  19. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +313 -0
  20. md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +1073 -0
  21. md_processing/dr_egeria_inbox/dr_egeria_isc1.md +44 -0
  22. md_processing/dr_egeria_inbox/glossary_creation_experiment.ipynb +341 -0
  23. md_processing/dr_egeria_inbox/glossary_test1.md +324 -0
  24. md_processing/dr_egeria_inbox/rel.md +8 -0
  25. md_processing/dr_egeria_inbox/sb.md +119 -0
  26. md_processing/dr_egeria_inbox/search_test.md +39 -0
  27. md_processing/dr_egeria_inbox/solution-components.md +154 -0
  28. md_processing/dr_egeria_inbox/solution_blueprints.md +118 -0
  29. md_processing/dr_egeria_inbox/synonym_test.md +42 -0
  30. md_processing/dr_egeria_inbox/t1.md +0 -0
  31. md_processing/dr_egeria_inbox/t2.md +268 -0
  32. md_processing/dr_egeria_outbox/processed-2025-05-15 19:52-data_test.md +94 -0
  33. md_processing/dr_egeria_outbox/processed-2025-05-16 07:39-data_test.md +88 -0
  34. md_processing/dr_egeria_outbox/processed-2025-05-17 16:01-data_field.md +56 -0
  35. md_processing/dr_egeria_outbox/processed-2025-05-18 15:51-data_test.md +103 -0
  36. md_processing/dr_egeria_outbox/processed-2025-05-18 16:47-data_test.md +94 -0
  37. md_processing/dr_egeria_outbox/processed-2025-05-19 07:14-data_test.md +96 -0
  38. md_processing/dr_egeria_outbox/processed-2025-05-19 07:20-data_test.md +100 -0
  39. md_processing/dr_egeria_outbox/processed-2025-05-19 07:22-data_test.md +88 -0
  40. md_processing/md_commands/__init__.py +3 -0
  41. md_processing/md_commands/blueprint_commands.py +303 -0
  42. md_processing/md_commands/data_designer_commands.py +1182 -0
  43. md_processing/md_commands/glossary_commands.py +1144 -0
  44. md_processing/md_commands/project_commands.py +163 -0
  45. md_processing/md_processing_utils/__init__.py +4 -0
  46. md_processing/md_processing_utils/common_md_proc_utils.py +724 -0
  47. md_processing/md_processing_utils/common_md_utils.py +172 -0
  48. md_processing/md_processing_utils/extraction_utils.py +486 -0
  49. md_processing/md_processing_utils/md_processing_constants.py +112 -0
  50. md_processing/md_processing_utils/message_constants.py +19 -0
  51. {pyegeria-5.3.9.9.2.dist-info → pyegeria-5.3.9.9.4.dist-info}/METADATA +1 -2
  52. {pyegeria-5.3.9.9.2.dist-info → pyegeria-5.3.9.9.4.dist-info}/RECORD +55 -5
  53. {pyegeria-5.3.9.9.2.dist-info → pyegeria-5.3.9.9.4.dist-info}/LICENSE +0 -0
  54. {pyegeria-5.3.9.9.2.dist-info → pyegeria-5.3.9.9.4.dist-info}/WHEEL +0 -0
  55. {pyegeria-5.3.9.9.2.dist-info → pyegeria-5.3.9.9.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,172 @@
1
+ """
2
+ This file contains general utility functions for processing Egeria Markdown
3
+ """
4
+ import os
5
+ import re
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+ from rich import print
10
+ from rich.console import Console
11
+ from rich.markdown import Markdown
12
+
13
+ from pyegeria._globals import DEBUG_LEVEL
14
+ from md_processing.md_processing_utils.message_constants import message_types
15
+
16
+ # Constants
17
+ EGERIA_WIDTH = int(os.environ.get("EGERIA_WIDTH", "170"))
18
+ console = Console(width=EGERIA_WIDTH)
19
+
20
+ debug_level = DEBUG_LEVEL
21
+ global COMMAND_DEFINITIONS
22
+
23
+ def split_tb_string(input: str)-> [Any]:
24
+ """Split the string and trim the items"""
25
+ l = [item.strip() for item in re.split(r'[;,\n]+',input)] if input is not None else None
26
+ return l
27
+
28
+ def str_to_bool(value: str) -> bool:
29
+ """Converts a string to a boolean value."""
30
+ return value.lower() in ("yes", "true", "t", "1")
31
+
32
+ def render_markdown(markdown_text: str) -> None:
33
+ """Renders the given markdown text in the console."""
34
+ console.print(Markdown(markdown_text))
35
+
36
+
37
+ def is_valid_iso_date(date_text) -> bool:
38
+ """Checks if the given string is a valid ISO date."""
39
+ try:
40
+ datetime.strptime(date_text, '%Y-%m-%d')
41
+ return True
42
+ except ValueError:
43
+ return False
44
+
45
+
46
+ def set_debug_level(directive: str) -> None:
47
+ """Sets the debug level for the script."""
48
+ global debug_level
49
+ if directive == "display":
50
+ debug_level = "display-only"
51
+
52
+
53
+ def get_current_datetime_string():
54
+ """Returns the current date and time as a human-readable string."""
55
+ now = datetime.now().strftime('%Y-%m-%d %H:%M')
56
+ return now
57
+
58
+
59
+ def print_msg(msg_level: str, msg: str, verbosity: str):
60
+ """
61
+ Prints a message based on its type and verbosity level.
62
+
63
+ This function handles the output of messages depending on the specified
64
+ verbosity level and message type. It uses predefined message types and
65
+ formats the output accordingly.
66
+
67
+ Args:
68
+ msg_level: The type of the message, such as 'WARNING', 'ERROR', 'INFO', or
69
+ 'ALWAYS'.
70
+ msg: The content of the message to display.
71
+ verbosity: The verbosity level, which determines how the message is
72
+ displayed ('verbose', 'quiet', or 'debug').
73
+ """
74
+ if msg_level == "ALWAYS":
75
+ print(f"{message_types.get(msg_level, '')}{msg}")
76
+ else:
77
+ print(f"{message_types.get(msg_level, '')}{msg}")
78
+ # elif verbosity == "verbose" and msg_level in ["INFO", "WARNING", "ERROR"]:
79
+ # print(f"{message_types.get(msg_level, '')}{msg}")
80
+ # elif verbosity == "quiet" and msg_level in ["WARNING", "ERROR"]:
81
+ # print(f"{message_types.get(msg_level, '')}{msg}")
82
+ # elif verbosity == "debug" and msg_level in ["INFO", "WARNING", "ERROR", "DEBUG-INFO", "DEBUG-WARNING",
83
+ # "DEBUG-ERROR"]:
84
+ # print(f"{message_types.get(msg_level, '')}{msg}")
85
+ # elif verbosity == "display-only" and msg_level in ["ALWAYS", "ERROR"]:
86
+ # print(f"{message_types.get(msg_level, '')}{msg}")
87
+
88
+
89
+ def process_provenance_command(file_path: str, txt: [str]) -> str:
90
+ """
91
+ Processes a provenance object_action by extracting the file path and current datetime.
92
+
93
+ Args:
94
+ file_path: The path to the file being processed.
95
+ txt: The text containing the provenance object_action.
96
+
97
+ Returns:
98
+ A string containing the provenance information.
99
+ """
100
+ now = get_current_datetime_string()
101
+ file_name = os.path.basename(file_path)
102
+ provenance = f"\n\n\n# Provenance:\n \n* Derived from processing file {file_name} on {now}\n"
103
+ return provenance
104
+
105
+
106
+ # Dictionary to store element information to avoid redundant API calls
107
+ element_dictionary = {}
108
+
109
+
110
+ def get_element_dictionary():
111
+ """
112
+ Get the shared element dictionary.
113
+
114
+ Returns:
115
+ dict: The shared element dictionary
116
+ """
117
+ global element_dictionary
118
+ return element_dictionary
119
+
120
+
121
+ def update_element_dictionary(key, value):
122
+ """
123
+ Update the shared element dictionary with a new key-value pair.
124
+
125
+ Args:
126
+ key (str): The key to update
127
+ value (dict): The value to associate with the key
128
+ """
129
+ global element_dictionary
130
+ if (key is None or value is None):
131
+ print(f"===>ERROR Key is {key} and value is {value}")
132
+ return
133
+ element_dictionary[key] = value
134
+
135
+
136
+ def clear_element_dictionary():
137
+ """
138
+ Clear the shared element dictionary.
139
+ """
140
+ global element_dictionary
141
+ element_dictionary.clear()
142
+
143
+
144
+ def is_present(value: str) -> bool:
145
+ global element_dictionary
146
+ present = value in element_dictionary.keys() or any(
147
+ value in inner_dict.values() for inner_dict in element_dictionary.values())
148
+ return present
149
+
150
+
151
+ def find_key_with_value(value: str) -> str | None:
152
+ """
153
+ Finds the top-level key whose nested dictionary contains the given value.
154
+
155
+ Args:
156
+ data (dict): A dictionary where keys map to nested dictionaries.
157
+ value (str): The value to search for.
158
+
159
+ Returns:
160
+ str | None: The top-level key that contains the value, or None if not found.
161
+ """
162
+ global element_dictionary
163
+ # Check if the value matches a top-level key
164
+ if value in element_dictionary.keys():
165
+ return value
166
+
167
+ # Check if the value exists in any of the nested dictionaries
168
+ for key, inner_dict in element_dictionary.items():
169
+ if value in inner_dict.values():
170
+ return key
171
+
172
+ return None # If value not found
@@ -0,0 +1,486 @@
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._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()
95
+ text = re.sub(r'\n\n+', '\n\n', text).strip()
96
+
97
+ label = label.strip()
98
+ pattern = rf"##\s*{re.escape(label)}\s*\n(?:\s*\n)*?(.*?)(?:#|___|$)"
99
+
100
+ # pattern = rf"##\s+{re.escape(label)}\n(.*?)(?:#|___|$)" # modified from --- to enable embedded tables
101
+ match = re.search(pattern, text, re.DOTALL)
102
+ if match:
103
+ # Extract matched text
104
+ matched_text = match.group(1)
105
+
106
+ # Filter out lines beginning with '>'
107
+ filtered_lines = [line for line in matched_text.split('\n') if not line.strip().startswith('>')]
108
+ filtered_text = '\n'.join(filtered_lines)
109
+
110
+ # Replace consecutive \n with a single \n
111
+ extracted_text = re.sub(r'\n+', '\n', filtered_text)
112
+ if not extracted_text.isspace() and extracted_text:
113
+ return extracted_text.strip() # Return the cleaned text - I removed the title casing
114
+
115
+ return None
116
+
117
+
118
+ def process_simple_attribute(txt: str, labels: set, if_missing: str = INFO) -> str | None:
119
+ """Process a simple attribute based on the provided labels and if_missing value.
120
+ Extract the attribute value from the text and return it if it exists.
121
+ If it doesn`t exist, return None and print an error message with severity of if_missing.
122
+
123
+ Parameters:
124
+ ----------
125
+ txt: str
126
+ The block of object_action text to extract attributes from.
127
+ labels: list
128
+ The possible attribute labels to search for. The first label will be used in messages.
129
+ if_missing: str, default is INFO
130
+ Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
131
+ """
132
+ if if_missing not in ["WARNING", "ERROR", "INFO"]:
133
+ print_msg("ERROR", "Invalid severity for missing attribute", debug_level)
134
+ return None
135
+
136
+ attribute = extract_attribute(txt, labels)
137
+
138
+ if attribute is None:
139
+ if if_missing == INFO:
140
+ msg = f"Optional attribute with labels `{labels}` missing"
141
+ else:
142
+ msg = f"Missing attribute with labels `{labels}` "
143
+ print_msg(if_missing, msg, debug_level)
144
+ return None
145
+ return attribute
146
+
147
+
148
+ # def process_simple_attribute(txt: str, labels: list[str], if_missing: str = INFO) -> str | None:
149
+ # """
150
+ # Processes a simple attribute from a string.
151
+ #
152
+ # Args:
153
+ # txt: The input string.
154
+ # labels: List of equivalent labels to search for
155
+ # if_missing: The message level to use if the attribute is missing.
156
+ #
157
+ # Returns:
158
+ # The value of the attribute, or None if not found.
159
+ # """
160
+ # from md_processing.md_processing_utils.common_utils import debug_level, print_msg
161
+ #
162
+ # attribute = extract_attribute(txt, labels)
163
+ # if attribute is None and if_missing:
164
+ # msg = f"No {labels[0]} found"
165
+ # print_msg(if_missing, msg, debug_level)
166
+ # return attribute
167
+
168
+
169
+ def process_name_list(egeria_client: EgeriaTech, element_type: str, txt: str, element_labels: set) -> tuple[str,
170
+ list[Any], bool | Any, bool | None | Any] | None:
171
+ """
172
+ Processes a list of names specified in the given text, retrieves details for each
173
+ element based on the provided type, and generates a list of valid qualified names.
174
+
175
+ The function reads a text block, extracts a list of element names according to the specified
176
+ element type, looks them up using the provided Egeria client, and classifies them as valid or
177
+ invalid. It returns the processed names, a list of qualified names, and validity and existence
178
+ flags.
179
+
180
+ Args:
181
+
182
+ egeria_client (EgeriaTech): The client instance to connect and query elements from an
183
+ external system.
184
+ Element_type (str): The type of element, such as schema or attribute, to process.
185
+ Txt (str): The raw input text containing element names to be processed.
186
+ element_labels: a list of equivalent label names to use in processing the element.
187
+
188
+ Returns:
189
+ tuple[str | None, list | None, bool, bool]: A tuple containing:
190
+ - Concatenated valid input names as a single string (or None if empty).
191
+ - A list of known qualified names extracted from the processed elements.
192
+ - A boolean indicating whether all elements are valid.
193
+ - A boolean indicating whether all elements exist.
194
+ """
195
+ valid = True
196
+ exists = True
197
+ elements = ""
198
+ new_element_list = []
199
+
200
+ elements_txt = extract_attribute(txt, element_labels)
201
+
202
+ if elements_txt is None:
203
+ msg = f"No {element_type} found"
204
+ print_msg("DEBUG-INFO", msg, debug_level)
205
+
206
+ else:
207
+ element_list = re.split(r'[,\n]+', elements_txt)
208
+
209
+ for element in element_list:
210
+ element_el = element.strip()
211
+
212
+ # Get the element using the generalized function
213
+ known_q_name, known_guid, el_valid, el_exists = get_element_by_name(egeria_client, element_type, element_el)
214
+ # print_msg("DEBUG-INFO", status_msg, debug_level)
215
+
216
+ if el_exists and el_valid:
217
+ elements = f"{element_el} {elements}" # list of the input names
218
+ new_element_list.append(known_q_name) # list of qualified names
219
+ elif not el_exists:
220
+ msg = f"No {element_type} `{element_el}` found"
221
+ print_msg("DEBUG-INFO", msg, debug_level)
222
+ valid = False
223
+ valid = valid if el_valid is None else (valid and el_valid)
224
+ exists = exists and el_exists
225
+
226
+ if elements:
227
+ # elements += "\n"
228
+ msg = f"Found {element_type}: {elements}"
229
+ print_msg("DEBUG-INFO", msg, debug_level)
230
+ else:
231
+ msg = " Name list contains one or more invalid qualified names."
232
+ print_msg("DEBUG-INFO", msg, debug_level)
233
+ return elements, new_element_list, valid, exists
234
+
235
+
236
+ # def process_name_list(egeria_client, element_type: str, txt: str, element_labels: list[str]) -> tuple[list, list,
237
+ # bool, bool]:
238
+ # """
239
+ # Processes a list of names from a string.
240
+ #
241
+ # Args:
242
+ # egeria_client: The Egeria client to use for validation.
243
+ # element_type: The type of element to process.
244
+ # txt: The input string.
245
+ # element_labels: List of equivalent labels to search for
246
+ #
247
+ # Returns:
248
+ # A tuple containing:
249
+ # - A list of element names
250
+ # - A list of element qualified names
251
+ # - A boolean indicating if all elements are valid
252
+ # - A boolean indicating if any elements exist
253
+ # """
254
+ # from md_processing.md_processing_utils.common_utils import debug_level, print_msg
255
+ #
256
+ # element_names = []
257
+ # element_q_names = []
258
+ # all_valid = True
259
+ # any_exist = False
260
+ #
261
+ # # Get the list of element names
262
+ # element_list = process_simple_attribute(txt, element_labels)
263
+ # if element_list:
264
+ # # Split the list by commas or newlines
265
+ # element_names = list(filter(None, re.split(r'[,\n]+', element_list.strip())))
266
+ #
267
+ # # Validate each element
268
+ # for element_name in element_names:
269
+ # element_name = element_name.strip()
270
+ # if element_name:
271
+ # element = get_element_by_name(egeria_client, element_type, element_name)
272
+ # if element:
273
+ # any_exist = True
274
+ # element_q_name = element.get('qualifiedName', None)
275
+ # if element_q_name:
276
+ # element_q_names.append(element_q_name)
277
+ # else:
278
+ # all_valid = False
279
+ # msg = f"Element {element_name} has no qualified name"
280
+ # print_msg("ERROR", msg, debug_level)
281
+ # else:
282
+ # all_valid = False
283
+ # msg = f"Element {element_name} not found"
284
+ # print_msg("ERROR", msg, debug_level)
285
+ #
286
+ # return element_names, element_q_names, all_valid, any_exist
287
+
288
+ def process_element_identifiers(egeria_client: EgeriaTech, element_type: str, element_labels: set, txt: str,
289
+ action: str, version: str = None) -> tuple[str, str, bool, bool]:
290
+ """
291
+ Processes element identifiers by extracting display name and qualified name from the input text,
292
+ checking if the element exists in Egeria and validating the information.
293
+
294
+ Parameters
295
+ ----------
296
+ egeria_client: EgeriaTech
297
+ Client object for interacting with Egeria.
298
+ element_type: str
299
+ type of element to process (e.g., 'blueprint', 'category', 'term')
300
+ element_labels: a list of equivalent label names to use in processing the element.
301
+ txt: str
302
+ A string representing the input text to be processed for extracting element identifiers.
303
+ action: str
304
+ The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
305
+ version: str, optional = None
306
+ An optional version identifier used if we need to construct the qualified name
307
+
308
+ Returns: tuple[str, str, str, bool, bool]
309
+ A tuple containing:
310
+ - qualified_name: Empty string or element identifier
311
+ - guid: Empty string or additional element information
312
+ - Valid: Boolean indicating if the element information is valid
313
+ - Exists: Boolean indicating if the element exists in Egeria
314
+ """
315
+ valid = True
316
+
317
+ element_name = extract_attribute(txt, element_labels)
318
+ qualified_name = extract_attribute(txt, ["Qualified Name"])
319
+
320
+ if qualified_name:
321
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type,
322
+ qualified_name) # Qualified name could be different if it
323
+ # is being updated
324
+ else:
325
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
326
+ if unique is False:
327
+ msg = f"Multiple elements named {element_name} found"
328
+ print_msg("DEBUG-ERROR", msg, debug_level)
329
+ valid = False
330
+
331
+ if action == "Update" and not exists:
332
+ msg = f"Element {element_name} does not exist"
333
+ print_msg("DEBUG-ERROR", msg, debug_level)
334
+ valid = False
335
+
336
+ elif action == "Update" and exists:
337
+ msg = f"Element {element_name} exists"
338
+ print_msg("DEBUG-INFO", msg, debug_level)
339
+
340
+ elif action == "Create" and exists:
341
+ msg = f"Element {element_name} already exists"
342
+ print_msg("DEBUG-ERROR", msg, debug_level)
343
+ valid = False
344
+
345
+ elif action == "Create" and not exists:
346
+ msg = f"{element_type} `{element_name}` does not exist"
347
+ print_msg("DEBUG-INFO", msg, debug_level)
348
+
349
+ if q_name is None and qualified_name is None:
350
+ q_name = egeria_client.__create_qualified_name__(element_type, element_name, version_identifier=version)
351
+ update_element_dictionary(q_name, {'display_name': element_name})
352
+ elif qualified_name:
353
+ update_element_dictionary(qualified_name, {'display_name': element_name})
354
+ elif action == EXISTS_REQUIRED:
355
+ if not exists:
356
+ msg = f"Required {element_type} `{element_name}` does not exist"
357
+ print_msg("DEBUG-ERROR", msg, debug_level)
358
+ valid = False
359
+ else:
360
+ msg = f"Required {element_type} `{element_name}` exists"
361
+ print_msg("DEBUG-INFO", msg, debug_level)
362
+ valid = True
363
+
364
+ return q_name, guid, valid, exists
365
+
366
+
367
+ def get_element_by_name(egeria_client, element_type: str, element_name: str) -> tuple[
368
+ str | None, str | None, bool | None, bool | None]:
369
+ """
370
+ Generalized function to retrieve an element by name based on its type.
371
+
372
+ Parameters:
373
+ egeria_client: Client
374
+ Client object for interacting with Egeria.
375
+ element_type: str
376
+ The type of element to retrieve (e.g., 'blueprint', 'category', 'term').
377
+ element_name: str
378
+ The name of the element to retrieve.
379
+
380
+ Returns:
381
+ tuple of qualified_name, guid, uniqye, exists
382
+ """
383
+ unique = None
384
+
385
+ element_dict = get_element_dictionary()
386
+ q_name = find_key_with_value(element_name)
387
+ if q_name: # use information from element_dictionary
388
+ guid = element_dict[q_name].get('guid', None)
389
+ unique = True
390
+
391
+ if guid is not None: # Found complete entry in element_dictionary
392
+ msg = f'Found {element_type} qualified name and guid in element_dictionary for `{element_name}`'
393
+ print_msg("DEBUG-INFO", msg, debug_level)
394
+ exists = True
395
+ return q_name, guid, unique, exists
396
+
397
+ else: # Missing guid from element_dictionary
398
+ guid = egeria_client.get_element_guid_by_unique_name(element_name)
399
+ if guid == NO_ELEMENTS_FOUND:
400
+ guid = None
401
+ exists = False
402
+ msg = f"No {element_type} guid found with name {element_name} in Egeria"
403
+ print_msg("DEBUG-INFO", msg, debug_level)
404
+
405
+ return q_name, guid, unique, exists
406
+ else:
407
+ exists = True
408
+ update_element_dictionary(q_name, {'guid': guid})
409
+ msg = f"Found guid value of {guid} for {element_name} in Egeria"
410
+ print_msg("DEBUG-INFO", msg, debug_level)
411
+
412
+ return q_name, guid, unique, exists
413
+
414
+ # Haven't seen this element before
415
+ property_names = ['qualifiedName', 'name', 'displayName']
416
+ open_metadata_type_name = None
417
+ details = egeria_client.get_elements_by_property_value(element_name, property_names, open_metadata_type_name)
418
+ if isinstance(details, str):
419
+ msg = f"{element_type} `{element_name}` not found in Egeria"
420
+ print_msg("DEBUG-INFO", msg, debug_level)
421
+ exists = False
422
+ return None, None, unique, exists
423
+ if len(details) > 1:
424
+ msg = (f"More than one element with name {element_name} found, please specify a "
425
+ f"**Qualified Name**")
426
+ print_msg("DEBUG-ERROR", msg, debug_level)
427
+ unique = False
428
+ exists = None
429
+ return element_name, None, unique, exists
430
+
431
+ el_qname = details[0]["properties"].get('qualifiedName', None)
432
+ el_guid = details[0]['elementHeader']['guid']
433
+ el_display_name = details[0]["properties"].get('displayName', None)
434
+ if el_display_name is None:
435
+ el_display_name = details[0]["properties"].get('name', None)
436
+ update_element_dictionary(el_qname, {
437
+ 'guid': el_guid, 'displayName': el_display_name
438
+ })
439
+ msg = f"Found {element_type} `{el_display_name}` with qualified name `{el_qname}`"
440
+ print_msg("DEBUG-INFO", msg, debug_level)
441
+ exists = True
442
+ unique = True
443
+ return el_qname, el_guid, unique, exists
444
+
445
+
446
+ def update_a_command(txt: str, object_action: str, obj_type: str, q_name: str, u_guid: str) -> str:
447
+ """
448
+ Updates a command in a string.
449
+
450
+ Args:
451
+ txt: The input string.
452
+ object_action: The command to update.
453
+ obj_type: The type of object to update.
454
+ q_name: The qualified name of the object.
455
+ u_guid: The GUID of the object.
456
+
457
+ Returns:
458
+ The updated string.
459
+ """
460
+
461
+ # Determine the new action
462
+ new_action = "Update" if object_action == "Create" else "Create"
463
+
464
+ # Replace the object_action
465
+ new_command = f"{new_action} {obj_type}"
466
+ pattern = rf"#\s*{object_action}\s+{obj_type}"
467
+ replacement = f"# {new_command}"
468
+ updated_txt = re.sub(pattern, replacement, txt)
469
+
470
+ # Add qualified name and GUID if updating
471
+ if new_action == "Update" and q_name and u_guid:
472
+ # Check if Qualified Name section exists
473
+ if "## Qualified Name" not in updated_txt:
474
+ # Add Qualified Name section before the first ## that's not part of the object_action
475
+ pattern = r"(##\s+[^#\n]+)"
476
+ replacement = f"## Qualified Name\n{q_name}\n\n\\1"
477
+ updated_txt = re.sub(pattern, replacement, updated_txt, count=1)
478
+
479
+ # Check if GUID section exists
480
+ if "## GUID" not in updated_txt and "## guid" not in updated_txt:
481
+ # Add GUID section before the first ## that's not part of the object_action or Qualified Name
482
+ pattern = r"(##\s+(?!Qualified Name)[^#\n]+)"
483
+ replacement = f"## GUID\n{u_guid}\n\n\\1"
484
+ updated_txt = re.sub(pattern, replacement, updated_txt, count=1)
485
+
486
+ return updated_txt