pyegeria 5.3.9.9.3__py3-none-any.whl → 5.3.9.9.5__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 (58) hide show
  1. md_processing/__init__.py +49 -0
  2. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +254 -0
  3. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +696 -0
  4. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +254 -0
  5. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +298 -0
  6. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +608 -0
  7. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +94 -0
  8. md_processing/dr_egeria_inbox/archive/freddie_intro.md +284 -0
  9. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +275 -0
  10. md_processing/dr_egeria_inbox/archive/test-term.md +110 -0
  11. md_processing/dr_egeria_inbox/cat_test.md +100 -0
  12. md_processing/dr_egeria_inbox/commands.json +3252 -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/dr_egeria_outbox/processed-2025-05-19 09:26-data_test.md +91 -0
  41. md_processing/dr_egeria_outbox/processed-2025-05-19 10:27-data_test.md +91 -0
  42. md_processing/md_commands/__init__.py +3 -0
  43. md_processing/md_commands/blueprint_commands.py +303 -0
  44. md_processing/md_commands/data_designer_commands.py +1183 -0
  45. md_processing/md_commands/glossary_commands.py +1144 -0
  46. md_processing/md_commands/project_commands.py +163 -0
  47. md_processing/md_processing_utils/__init__.py +4 -0
  48. md_processing/md_processing_utils/common_md_proc_utils.py +724 -0
  49. md_processing/md_processing_utils/common_md_utils.py +172 -0
  50. md_processing/md_processing_utils/extraction_utils.py +486 -0
  51. md_processing/md_processing_utils/md_processing_constants.py +122 -0
  52. md_processing/md_processing_utils/message_constants.py +19 -0
  53. pyegeria/data_designer_omvs.py +4 -3
  54. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.5.dist-info}/METADATA +1 -2
  55. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.5.dist-info}/RECORD +58 -6
  56. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.5.dist-info}/LICENSE +0 -0
  57. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.5.dist-info}/WHEEL +0 -0
  58. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,724 @@
1
+ """
2
+ This file contains general utility functions for processing Egeria Markdown
3
+ """
4
+ import os
5
+ import sys
6
+ from typing import List
7
+
8
+ from rich.console import Console
9
+ from rich import print
10
+
11
+
12
+
13
+ from md_processing.md_processing_utils.common_md_utils import (get_current_datetime_string, print_msg,
14
+ get_element_dictionary, update_element_dictionary,
15
+ split_tb_string, str_to_bool, )
16
+ from md_processing.md_processing_utils.extraction_utils import (process_simple_attribute, extract_attribute,
17
+ get_element_by_name)
18
+ from md_processing.md_processing_utils.md_processing_constants import (get_command_spec)
19
+ from md_processing.md_processing_utils.message_constants import (ERROR, INFO, WARNING, ALWAYS, EXISTS_REQUIRED)
20
+ from pyegeria import EgeriaTech
21
+ from pyegeria._globals import DEBUG_LEVEL
22
+
23
+ # Constants
24
+ EGERIA_WIDTH = int(os.environ.get("EGERIA_WIDTH", "200"))
25
+ EGERIA_USAGE_LEVEL = os.environ.get("EGERIA_USAGE_LEVEL", "Basic")
26
+ console = Console(width=EGERIA_WIDTH)
27
+
28
+ debug_level = DEBUG_LEVEL
29
+ global COMMAND_DEFINITIONS
30
+
31
+
32
+ def process_provenance_command(file_path: str, txt: [str]) -> str:
33
+ """
34
+ Processes a provenance object_action by extracting the file path and current datetime.
35
+
36
+ Args:
37
+ file_path: The path to the file being processed.
38
+ txt: The text containing the provenance object_action.
39
+
40
+ Returns:
41
+ A string containing the provenance information.
42
+ """
43
+ now = get_current_datetime_string()
44
+ file_name = os.path.basename(file_path)
45
+ provenance = f"\n\n\n# Provenance:\n \n* Derived from processing file {file_name} on {now}\n"
46
+ return provenance
47
+
48
+
49
+ def parse_user_command(egeria_client: EgeriaTech, object_type: str, object_action: str, txt: str,
50
+ directive: str = "display") -> dict:
51
+ parsed_attributes, parsed_output = {}, {}
52
+
53
+ parsed_output['valid'] = True
54
+ parsed_output['exists'] = False
55
+ parsed_output['display'] = ""
56
+ display_name = ""
57
+ labels = {}
58
+
59
+ command_spec = get_command_spec(object_type)
60
+ attributes = command_spec.get('Attributes', [])
61
+ command_display_name = command_spec.get('display_name', None)
62
+ command_qn_prefix = command_spec.get('qn_prefix', None)
63
+
64
+ parsed_output['display_name'] = command_display_name
65
+ parsed_output['qn_prefix'] = command_qn_prefix
66
+
67
+ parsed_output['is_own_anchor'] = command_spec.get('isOwnAnchor', True)
68
+
69
+ parsed_output['reason'] = ""
70
+
71
+ msg = f"\tProcessing {object_action} on a {object_type} \n"
72
+ print_msg(ALWAYS, msg, debug_level)
73
+
74
+ # get the version early because we may need it to construct qualified names.
75
+ version = process_simple_attribute(txt, {'Version', "Version Identifier", "Published Version"}, INFO)
76
+ parsed_output['version'] = version
77
+
78
+ for attr in attributes:
79
+ for key in attr:
80
+ # Run some checks to see if the attribute is appropriate to the operation and usage level
81
+ for_update = attr[key].get('inUpdate', True)
82
+ level = attr[key].get('level', 'Basic')
83
+ msg = (f"___\nProcessing `{key}` in `{object_action}` on a `{object_type}` "
84
+ f"\n\twith usage level: `{EGERIA_USAGE_LEVEL}` and attribute level `{level}` and for_update `{for_update}`\n")
85
+ print_msg(INFO, msg, debug_level)
86
+ if for_update is False and object_action == "Update":
87
+ console.print(f"Attribute `{key}`is not allowed for `Update`", highlight=True)
88
+ continue
89
+ if EGERIA_USAGE_LEVEL == "Basic" and level != "Basic":
90
+ console.print(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.", highlight=True)
91
+ continue
92
+ if EGERIA_USAGE_LEVEL == "Advanced" and level in [ "Expert", "Invisible"]:
93
+ console.print(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.", highlight=True)
94
+ continue
95
+ if EGERIA_USAGE_LEVEL == "Expert" and level == "Invisible":
96
+ console.print(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.", highlight=True)
97
+ continue
98
+
99
+
100
+ if attr[key].get('input_required', False) is True:
101
+ if_missing = ERROR
102
+ else:
103
+ if_missing = INFO
104
+
105
+ # lab = [item.strip() for item in re.split(r'[;,\n]+',attr[key]['attr_labels'])]
106
+ lab =split_tb_string(attr[key]['attr_labels'] )
107
+ labels: set = set()
108
+ labels.add(key.strip())
109
+ if key == 'Display Name':
110
+ labels.add(object_type.strip())
111
+ if lab is not None and lab != [""]:
112
+ labels.update(lab)
113
+
114
+
115
+ default_value = attr[key].get('default_value', None)
116
+
117
+ style = attr[key]['style']
118
+ if style in ['Simple', 'Dictionary', 'Comment']:
119
+ parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
120
+ elif style == 'Valid Value':
121
+ parsed_attributes[key] = proc_valid_value(txt, object_action, labels,
122
+ attr[key].get('valid_values', None), if_missing,
123
+ default_value)
124
+ elif style == 'QN':
125
+ parsed_attributes[key] = proc_el_id(egeria_client, command_display_name, command_qn_prefix, labels, txt,
126
+ object_action, version, if_missing)
127
+ if key == 'Qualified Name' and parsed_attributes[key]['value'] and parsed_attributes[key]['exists'] is False:
128
+ parsed_output['exists'] = False
129
+ elif style == 'ID':
130
+ parsed_attributes[key] = proc_el_id(egeria_client, command_display_name, command_qn_prefix, labels, txt,
131
+ object_action, version, if_missing)
132
+
133
+ parsed_output['guid'] = parsed_attributes[key].get('guid', None)
134
+ parsed_output['qualified_name'] = parsed_attributes[key].get('qualified_name', None)
135
+ parsed_output['exists'] = parsed_attributes[key]['exists']
136
+ if parsed_attributes[key]['valid'] is False:
137
+ parsed_output['valid'] = False
138
+ parsed_output['reason'] += parsed_attributes[key]['reason']
139
+
140
+ elif style == 'Reference Name':
141
+ parsed_attributes[key] = proc_ids(egeria_client, key, labels, txt, object_action, if_missing)
142
+ if ((if_missing==ERROR) and parsed_attributes[key].get("value", None) and
143
+ parsed_attributes[key]['exists'] is False):
144
+ msg = f"Reference Name `{parsed_attributes[key]['value']}` is specified but does not exist"
145
+ print_msg(ERROR, msg, debug_level)
146
+ parsed_output['valid'] = False
147
+ parsed_output['reason'] += msg
148
+
149
+ elif style == 'GUID':
150
+ parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing)
151
+ elif style == 'Ordered Int':
152
+ parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing)
153
+ elif style == 'Simple Int':
154
+ parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
155
+ elif style == 'Simple List':
156
+ parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
157
+ name_list = parsed_attributes[key]['value']
158
+ # attribute = re.split(r'[;,\n]+', name_list) if name_list is not None else None
159
+ attribute = split_tb_string(name_list)
160
+ parsed_attributes[key]['value'] = attribute
161
+ parsed_attributes[key]['name_list'] = name_list
162
+ elif style == 'Parent':
163
+ parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
164
+ elif style == 'Bool':
165
+ parsed_attributes[key] = proc_bool_attribute(txt, object_action, labels, if_missing, default_value)
166
+
167
+
168
+ elif style == 'Reference Name List':
169
+ parsed_attributes[key] = proc_name_list(egeria_client, key, txt, labels, if_missing)
170
+
171
+ else:
172
+ msg = f"Unknown attribute style: {style}"
173
+ print_msg(ERROR, msg, debug_level)
174
+ sys.exit(1)
175
+ parsed_attributes[key]['valid'] = False
176
+ parsed_attributes[key]['value'] = None
177
+ if key == "Display Name":
178
+ display_name = parsed_attributes[key]['value']
179
+
180
+
181
+
182
+ value = parsed_attributes[key].get('value',None)
183
+
184
+ if value is not None:
185
+ # if the value is a dict, get the flattened name list
186
+ value = parsed_attributes[key].get('name_list', None) if isinstance(value,(dict, list)) else value
187
+
188
+ parsed_output['display'] += f"\n\t* {key}: `{value}`\n\t"
189
+
190
+
191
+ if parsed_attributes.get('Parent ID', {}).get('value', None) is not None:
192
+ if (parsed_attributes['Parent Relationship Type Name']['value'] is None) or (
193
+ parsed_attributes['Parent at End1']['value'] is None):
194
+ msg = "Parent ID was found but either Parent `Relationship Type Name` or `Parent at End1` are missing"
195
+ print_msg(ERROR, msg, debug_level)
196
+ parsed_output['valid'] = False
197
+ parsed_output['reason'] = msg
198
+ if parsed_attributes['Parent Relationship Type Name'].get('exists', False) is False:
199
+ msg = "Parent ID was found but does not exist"
200
+ print_msg(ERROR, msg, debug_level)
201
+ parsed_output['valid'] = False
202
+ parsed_output['reason'] = msg
203
+
204
+ if directive in ["validate", "process"] and object_action == "Update" and not parsed_output[
205
+ 'exists']: # check to see if provided information exists and is consistent with existing info
206
+ msg = f"Update request invalid, Term {display_name} does not exist\n"
207
+ print_msg(ERROR, msg, debug_level)
208
+ parsed_output['valid'] = False
209
+ if directive in ["validate", "process"] and not parsed_output['valid'] and object_action == "Update":
210
+ msg = f"Request is invalid, `{object_action} {object_type}` is not valid - see previous messages\n"
211
+ print_msg(ERROR, msg, debug_level)
212
+
213
+ elif directive in ["validate",
214
+ "process"] and object_action == 'Create': # if the object_action is create, check that it
215
+ # doesn't already exist
216
+ if parsed_output['exists']:
217
+ msg = f"Element `{display_name}` cannot be created since it already exists\n"
218
+ print_msg(ERROR, msg, debug_level)
219
+ parsed_output['valid'] = False
220
+ else:
221
+ msg = f"It is valid to create Element `{display_name}`"
222
+ print_msg(ALWAYS, msg, debug_level)
223
+
224
+ parsed_output['attributes'] = parsed_attributes
225
+ return parsed_output
226
+
227
+
228
+ def proc_simple_attribute(txt: str, action: str, labels: set, if_missing: str = INFO, default_value=None) -> dict:
229
+ """Process a simple attribute based on the provided labels and if_missing value.
230
+ Extract the attribute value from the text and store it in a dictionary along with valid.
231
+ If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
232
+
233
+ Parameters:
234
+ ----------
235
+ txt: str
236
+ The block of object_action text to extract attributes from.
237
+ labels: list
238
+ The possible attribute labels to search for. The first label will be used in messages.
239
+ if_missing: str, default is INFO
240
+ Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
241
+ default_value: default is None
242
+ The default value to return if the attribute is missing.
243
+ """
244
+ valid = True
245
+
246
+ if if_missing not in ["WARNING", "ERROR", "INFO"]:
247
+ msg = "Invalid severity for missing attribute"
248
+ print_msg("ERROR", msg, debug_level)
249
+ return {"status": ERROR, "reason": msg, "value": None, "valid": False}
250
+
251
+ attribute = extract_attribute(txt, labels)
252
+ if default_value == "":
253
+ default_value = None
254
+ attribute = default_value if attribute is None else attribute
255
+
256
+ if attribute is None:
257
+
258
+ if if_missing == INFO or if_missing == WARNING:
259
+ msg = f"Optional attribute with labels: `{labels}` missing"
260
+ valid = True
261
+ else:
262
+ msg = f"Missing attribute with labels `{labels}` "
263
+ valid = False
264
+ print_msg(if_missing, msg, debug_level)
265
+ return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
266
+ return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
267
+
268
+
269
+ def proc_valid_value(txt: str, action: str, labels: set, valid_values: [], if_missing: str = INFO,
270
+ default_value=None) -> dict:
271
+ """Process a string attribute to check that it is a member of the associated value values list.
272
+ Extract the attribute value from the text and store it in a dictionary along with valid.
273
+ If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
274
+
275
+ Parameters:
276
+ ----------
277
+ txt: str
278
+ The block of object_action text to extract attributes from.
279
+ labels: list
280
+ The possible attribute labels to search for. The first label will be used in messages.
281
+ if_missing: str, default is INFO
282
+ Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
283
+ default_value: default is None
284
+ The default value to return if the attribute is missing.
285
+ """
286
+ valid = True
287
+ v_values = []
288
+
289
+ if if_missing not in ["WARNING", "ERROR", "INFO"]:
290
+ msg = "Invalid severity for missing attribute"
291
+ print_msg("ERROR", msg, debug_level)
292
+ return {"status": ERROR, "reason": msg, "value": None, "valid": False}
293
+ if valid_values is None:
294
+ msg = "Missing valid values list"
295
+ print_msg("ERROR", msg, debug_level)
296
+ return {"status": WARNING, "reason": msg, "value": None, "valid": False}
297
+ if isinstance(valid_values, str):
298
+ # v_values = [item.strip() for item in re.split(r'[;,\n]+', valid_values)]
299
+ v_values = split_tb_string(valid_values)
300
+ if not isinstance(v_values, list):
301
+ msg = "Valid values list is not a list"
302
+ print_msg("ERROR", msg, debug_level)
303
+ return {"status": WARNING, "reason": msg, "value": None, "valid": False}
304
+ if len(v_values) == 0:
305
+ msg = "Valid values list is empty"
306
+ print_msg("ERROR", msg, debug_level)
307
+ return {"status": WARNING, "reason": msg, "value": None, "valid": False}
308
+
309
+ attribute = extract_attribute(txt, labels)
310
+ if default_value == "":
311
+ default_value = None
312
+ attribute = default_value if attribute is None else attribute
313
+
314
+ if attribute is None:
315
+ if if_missing == INFO or if_missing == WARNING:
316
+ msg = f"Optional attribute with labels: `{labels}` missing"
317
+ valid = True
318
+ else:
319
+ msg = f"Missing attribute with labels `{labels}` "
320
+ valid = False
321
+ print_msg(if_missing, msg, debug_level)
322
+ return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
323
+ else:
324
+ if attribute not in v_values:
325
+ msg = f"Invalid value for attribute `{labels}`"
326
+ print_msg(WARNING, msg, debug_level)
327
+ return {"status": WARNING, "reason": msg, "value": attribute, "valid": False, "exists": True}
328
+
329
+ return {"status": INFO, "OK": "OK", "value": attribute, "valid": valid, "exists": True}
330
+
331
+
332
+ def proc_bool_attribute(txt: str, action: str, labels: set, if_missing: str = INFO, default_value=None) -> dict:
333
+ """Process a boolean attribute based on the provided labels and if_missing value.
334
+ Extract the attribute value from the text and store it in a dictionary along with valid.
335
+ If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
336
+
337
+ Parameters:
338
+ ----------
339
+ txt: str
340
+ The block of object_action text to extract attributes from.
341
+ labels: list
342
+ The possible attribute labels to search for. The first label will be used in messages.
343
+ if_missing: str, default is INFO
344
+ Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
345
+ default_value: default is None
346
+ The default value to return if the attribute is missing.
347
+ """
348
+ valid = True
349
+
350
+ if if_missing not in ["WARNING", "ERROR", "INFO"]:
351
+ msg = "Invalid severity for missing attribute"
352
+ print_msg("ERROR", msg, debug_level)
353
+ return {"status": ERROR, "reason": msg, "value": None, "valid": False}
354
+
355
+ attribute = extract_attribute(txt, labels)
356
+ if default_value == "":
357
+ default = None
358
+ else:
359
+ default = str_to_bool(default_value)
360
+ attribute = default if attribute is None else attribute
361
+
362
+ if attribute is None:
363
+ if if_missing == INFO or if_missing == WARNING:
364
+ msg = f"Optional attribute with labels: `{labels}` missing"
365
+ valid = True
366
+ else:
367
+ msg = f"Missing attribute with labels `{labels}` "
368
+ valid = False
369
+ print_msg(if_missing, msg, debug_level)
370
+ return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
371
+
372
+ if isinstance(attribute, str):
373
+ attribute = attribute.strip().lower()
374
+ if attribute in ["true", "yes", "1"]:
375
+ attribute = True
376
+ elif attribute in ["false", "no", "0"]:
377
+ attribute = False
378
+ else:
379
+ msg = f"Invalid value for boolean attribute `{labels}`"
380
+ print_msg("ERROR", msg, debug_level)
381
+ return {"status": ERROR, "reason": msg, "value": attribute, "valid": False, "exists": True}
382
+
383
+ return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
384
+
385
+
386
+ def proc_el_id(egeria_client: EgeriaTech, element_type: str, qn_prefix: str, element_labels: list[str], txt: str,
387
+ action: str, version: str = None, if_missing: str = INFO) -> dict:
388
+ """
389
+ Processes display_name and qualified_name by extracting them from the input text,
390
+ checking if the element exists in Egeria, and validating the information. If a qualified
391
+ name isn't found, one will be created.
392
+
393
+ Parameters
394
+ ----------
395
+ egeria_client: EgeriaTech
396
+ Client object for interacting with Egeria.
397
+ element_type: str
398
+ type of element to process (e.g., 'blueprint', 'category', 'term')
399
+ element_labels: a list of equivalent label names to use in processing the element.
400
+ txt: str
401
+ A string representing the input text to be processed for extracting element identifiers.
402
+ action: str
403
+ The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
404
+ version: str, optional = None
405
+ An optional version identifier used if we need to construct the qualified name
406
+
407
+ Returns: dict with keys:
408
+ status
409
+ reason
410
+ value - value of display name
411
+ valid - name we parse out
412
+ exists
413
+ qualified_name - qualified name - either that we find or the one we construct
414
+ guid - guid of the element if it already exists
415
+ """
416
+ valid = True
417
+ exists = False
418
+ identifier_output = {}
419
+
420
+ element_name = extract_attribute(txt, element_labels)
421
+ qualified_name = extract_attribute(txt, ["Qualified Name"])
422
+
423
+ if element_name is None:
424
+ msg = f"Optional attribute with label`{element_type}` missing"
425
+ print_msg("INFO", msg, debug_level)
426
+ identifier_output = {"status": INFO, "reason": msg, "value": None, "valid": False, "exists": False, }
427
+ return identifier_output
428
+
429
+ if qualified_name:
430
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type,
431
+ qualified_name) # Qualified name could be different if it
432
+ # is being updated
433
+ else:
434
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
435
+ qualified_name = q_name
436
+
437
+ if unique is False:
438
+ msg = f"Multiple elements named {element_name} found"
439
+ print_msg("DEBUG-ERROR", msg, debug_level)
440
+ identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": True, }
441
+ valid = False
442
+
443
+ if action == "Update" and not exists:
444
+ msg = f"Element {element_name} does not exist"
445
+ print_msg("DEBUG-ERROR", msg, debug_level)
446
+ identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": False, }
447
+
448
+ elif action == "Update" and exists:
449
+ msg = f"Element {element_name} exists"
450
+ print_msg("DEBUG-INFO", msg, debug_level)
451
+ identifier_output = {
452
+ "status": INFO, "reason": msg, "value": element_name, "valid": True, "exists": True,
453
+ 'qualified_name': q_name, 'guid': guid
454
+ }
455
+
456
+ elif action == "Create" and exists:
457
+ msg = f"Element {element_name} already exists"
458
+ print_msg("DEBUG-ERROR", msg, debug_level)
459
+ identifier_output = {
460
+ "status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": True,
461
+ 'qualified_name': qualified_name, 'guid': guid,
462
+ }
463
+
464
+ elif action == "Create" and not exists and valid:
465
+ msg = f"{element_type} `{element_name}` does not exist"
466
+ print_msg("DEBUG-INFO", msg, debug_level)
467
+
468
+ if q_name is None and qualified_name is None:
469
+ q_name = egeria_client.__create_qualified_name__(qn_prefix, element_name, version_identifier=version)
470
+ update_element_dictionary(q_name, {'display_name': element_name})
471
+
472
+ elif qualified_name:
473
+ update_element_dictionary(qualified_name, {'display_name': element_name})
474
+ q_name = qualified_name
475
+
476
+ identifier_output = {
477
+ "status": INFO, "reason": msg, "value": element_name, "valid": True, "exists": False,
478
+ 'qualified_name': q_name
479
+ }
480
+
481
+ return identifier_output
482
+
483
+
484
+ def proc_ids(egeria_client: EgeriaTech, element_type: str, element_labels: set, txt: str, action: str,
485
+ if_missing: str = INFO, version: str = None) -> dict:
486
+ """
487
+ Processes element identifiers from the input text using the labels supplied,
488
+ checking if the element exists in Egeria, and validating the information.
489
+
490
+ Parameters
491
+ ----------
492
+ egeria_client: EgeriaTech
493
+ Client object for interacting with Egeria.
494
+ element_type: str
495
+ type of element to process (e.g., 'blueprint', 'category', 'term')
496
+ element_labels: a list of equivalent label names to use in processing the element.
497
+ txt: str
498
+ A string representing the input text to be processed for extracting element identifiers.
499
+ action: str
500
+ The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
501
+ if_missing: str, optional = None
502
+ Optional version identifier used if we need to construct the qualified name
503
+ version: str, optional = INFO
504
+ What to do if the element doesn't exist. Default is INFO. Can be one of "WARNING", "ERROR", "INFO".
505
+
506
+ Returns: dict with keys:
507
+ status
508
+ reason
509
+ value
510
+ valid - name we parse out
511
+ exists
512
+ qualified_name - what we find exists
513
+ guid
514
+ """
515
+ valid = True
516
+ exists = False
517
+ identifier_output = {}
518
+ unique = True
519
+ value = None
520
+
521
+ element_name = extract_attribute(txt, element_labels)
522
+ if element_name:
523
+ q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
524
+ value = element_name
525
+ else:
526
+ exists = False
527
+ q_name = None
528
+ unique = None
529
+
530
+ if exists is True and unique is False:
531
+ # Multiple elements found - so need to respecify with qualified name
532
+ msg = f"Multiple elements named {element_name} found"
533
+ print_msg("DEBUG-ERROR", msg, debug_level)
534
+ identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": True, }
535
+
536
+
537
+ elif action == EXISTS_REQUIRED or if_missing == ERROR and not exists:
538
+ # a required identifier doesn't exist
539
+ msg = f"Required {element_type} `{element_name}` does not exist"
540
+ print_msg("DEBUG-ERROR", msg, debug_level)
541
+ identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": False, }
542
+ elif value is None and if_missing == INFO:
543
+ # an optional identifier is empty
544
+ msg = f"Optional attribute with label`{element_type}` missing"
545
+ print_msg("INFO", msg, debug_level)
546
+ identifier_output = {"status": INFO, "reason": msg, "value": None, "valid": True, "exists": False, }
547
+ elif value and exists is False:
548
+ # optional identifier specified but doesn't exist
549
+ msg = f"Optional attribute with label`{element_type}` specified but doesn't exist"
550
+ print_msg("ERROR", msg, debug_level)
551
+ identifier_output = {"status": ERROR, "reason": msg, "value": value, "valid": False, "exists": False, }
552
+
553
+ else:
554
+ # all good.
555
+ msg = f"Element {element_type} `{element_name}` exists"
556
+ print_msg("DEBUG-INFO", msg, debug_level)
557
+ identifier_output = {
558
+ "status": INFO, "reason": msg, "value": element_name, "valid": True, "exists": True,
559
+ "qualified_name": q_name, 'guid': guid
560
+ }
561
+
562
+ return identifier_output
563
+
564
+
565
+ def proc_name_list(egeria_client: EgeriaTech, element_type: str, txt: str, element_labels: set,
566
+ if_missing: str = INFO) -> dict:
567
+ """
568
+ Processes a list of names specified in the given text, retrieves details for each
569
+ element based on the provided type, and generates a list of valid qualified names.
570
+
571
+ The function reads a text block, extracts a list of element names according to the specified
572
+ element type, looks them up using the provided Egeria client, and classifies them as valid or
573
+ invalid. It returns the processed names, a list of qualified names, and validity and existence
574
+ flags.
575
+
576
+ Args:
577
+
578
+ egeria_client (EgeriaTech): The client instance to connect and query elements from an
579
+ external system.
580
+ Element_type (str): The type of element, such as schema or attribute, to process.
581
+ Txt (str): The raw input text containing element names to be processed.
582
+ element_labels: a list of equivalent label names to use in processing the element.
583
+ required: bool, default is False
584
+ - indicates whether the list of names is required to be present in the input text.
585
+
586
+ Returns:
587
+ Dict containing:
588
+ 'names' - Concatenated valid input names as a single string (or None if empty).
589
+ 'name_list' - A list of known qualified names extracted from the processed elements.
590
+ 'valid' - A boolean indicating whether all elements are valid.
591
+ 'exists' - A boolean indicating whether all elements exist.
592
+ """
593
+ valid = True
594
+ exists = True
595
+ id_list_output = {}
596
+ elements = ""
597
+ new_element_list = []
598
+
599
+ elements_txt = extract_attribute(txt, element_labels)
600
+
601
+ if elements_txt is None:
602
+ msg = f"Attribute with labels `{{element_type}}` missing"
603
+ print_msg("DEBUG-INFO", msg, debug_level)
604
+ return {"status": if_missing, "reason": msg, "value": None, "valid": False, "exists": False, }
605
+ else:
606
+ # element_list = re.split(r'[;,\n]+', elements_txt)
607
+ element_list = split_tb_string(elements_txt)
608
+ element_details = {}
609
+ for element in element_list:
610
+ # Get the element using the generalized function
611
+ known_q_name, known_guid, el_valid, el_exists = get_element_by_name(egeria_client, element_type, element)
612
+ details = {"known_q_name": known_q_name, "known_guid": known_guid, "el_valid": el_valid}
613
+ elements = f"{element} {elements}" # list of the input names
614
+
615
+ if el_exists and el_valid:
616
+ new_element_list.append(known_q_name) # list of qualified names
617
+ elif not el_exists:
618
+ msg = f"No {element_type} `{element}` found"
619
+ print_msg("DEBUG-INFO", msg, debug_level)
620
+ valid = False
621
+ valid = valid if el_valid is None else (valid and el_valid)
622
+ exists = exists and el_exists
623
+ element_details[element] = details
624
+
625
+ if elements:
626
+ msg = f"Found {element_type}: {elements}"
627
+ print_msg("DEBUG-INFO", msg, debug_level)
628
+ id_list_output = {
629
+ "status": INFO, "reason": msg, "value": element_details, "valid": valid, "exists": exists,
630
+ "name_list": new_element_list,
631
+ }
632
+ else:
633
+ msg = f" Name list contains one or more invalid qualified names."
634
+ print_msg("DEBUG-INFO", msg, debug_level)
635
+ id_list_output = {"status": if_missing, "reason": msg, "value": elements, "valid": valid,
636
+ "exists": exists, "dict_list" : element_details}
637
+ return id_list_output
638
+
639
+
640
+ def update_term_categories(egeria_client: EgeriaTech, term_guid: str, categories_exist: bool,
641
+ categories_list: List[str]) -> None:
642
+ """
643
+
644
+ Adds or removes a term to/from specified categories in a glossary.
645
+
646
+ This function associates a term, identified by its GUID, with one or more
647
+ categories. It uses the provided EgeriaTech client to assign the term
648
+ to the categories. If the GUID of a category is not readily available, it
649
+ is retrieved either from a pre-loaded dictionary or through a client lookup.
650
+
651
+ Args:
652
+ egeria_client (EgeriaTech): The client to interact with the glossary.
653
+ term_guid (str): The GUID of the term to be associated with categories.
654
+ categories_exist (bool): Flag indicating whether the categories already
655
+ exist.
656
+ categories_list (List[str]): A list of category names to associate with
657
+ the term.
658
+
659
+ Returns:
660
+ None
661
+ """
662
+ to_be_cat_guids: list[str] = []
663
+ # find the categories a term is currently in.
664
+ existing_categories = egeria_client.get_categories_for_term(term_guid)
665
+ if type(existing_categories) is str:
666
+ current_categories = []
667
+ else:
668
+ current_categories = [cat['elementHeader']['guid'] for cat in existing_categories]
669
+
670
+ if categories_exist is True and categories_list is not None:
671
+ if type(categories_list) is str:
672
+ # categories_list = re.split(r'[;,\n]+', categories_list)
673
+ # categories_list = categories_list.split(";,").trim()
674
+ categories_list = split_tb_string(categories_list)
675
+ for category in categories_list:
676
+ cat_guid = None
677
+ cat_el = category.strip()
678
+ element_dict = get_element_dictionary()
679
+ if cat_el in element_dict:
680
+ cat = element_dict.get(cat_el, None)
681
+ cat_guid = cat.get('guid', None) if cat else None
682
+ if cat_guid is None:
683
+ cat_guid = egeria_client.__get_guid__(qualified_name=cat_el)
684
+ update_element_dictionary(cat_el, {'guid': cat_guid})
685
+ to_be_cat_guids.append(cat_guid)
686
+
687
+ for cat in to_be_cat_guids:
688
+ if cat not in current_categories:
689
+ egeria_client.add_term_to_category(term_guid, cat)
690
+ current_categories.append(cat)
691
+ msg = f"Added term {term_guid} to category {cat}"
692
+ print_msg("DEBUG-INFO", msg, debug_level)
693
+
694
+ for cat in current_categories:
695
+ if cat not in to_be_cat_guids:
696
+ egeria_client.remove_term_from_category(term_guid, cat)
697
+ msg = f"Removed term {term_guid} from category {cat}"
698
+ print_msg("DEBUG-INFO", msg, debug_level)
699
+ else: # No categories specified - so remove any categories a term is in
700
+ for cat in current_categories:
701
+ egeria_client.remove_term_from_category(term_guid, cat)
702
+ msg = f"Removed term {term_guid} from category {cat}"
703
+ print_msg("DEBUG-INFO", msg, debug_level)
704
+
705
+ def sync_data_dict_membership(egeria_client: EgeriaTech, in_data_dictionary_names: list , in_data_dictionary: dict,
706
+ guid:str, object_type:str)->dict:
707
+ pass
708
+
709
+ def sync_data_structure_membership(egeria_client: EgeriaTech, in_data_structure_names: list , in_data_structure: dict,
710
+ guid:str, object_type:str)->dict:
711
+
712
+ pass
713
+
714
+ def sync_data_spec_membership(egeria_client: EgeriaTech, in_data_spec_names: list , in_data_spec: dict,
715
+ guid:str, object_type:str)->dict:
716
+ pass
717
+
718
+ def sync_term_links(egeria_client: EgeriaTech, term_guid: str, links_exist: bool,
719
+ links_list: List[str]) -> None:
720
+ pass
721
+
722
+ def sync_parent_data_field(egeria_client: EgeriaTech, term_guid: str, links_exist: bool,
723
+ links_list: List[str]) -> None:
724
+ pass