pyegeria 5.3.6.2.5__py3-none-any.whl → 5.3.6.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 (33) hide show
  1. pyegeria/__init__.py +3 -0
  2. pyegeria/_client.py +10 -0
  3. pyegeria/commands/cat/{freddie_jupyter.py → dr_egeria_jupyter.py} +8 -16
  4. pyegeria/commands/cat/{freddie_md.py → dr_egeria_md.py} +39 -33
  5. pyegeria/commands/cat/glossary_actions.py +59 -1
  6. pyegeria/commands/cat/list_categories.py +185 -0
  7. pyegeria/commands/cat/list_glossaries.py +26 -33
  8. pyegeria/commands/cat/list_terms.py +47 -47
  9. pyegeria/commands/cli/egeria.py +51 -29
  10. pyegeria/commands/cli/egeria_cat.py +53 -31
  11. pyegeria/create_tech_guid_lists.py +2 -5
  12. pyegeria/glossary_browser_omvs.py +283 -113
  13. pyegeria/glossary_manager_omvs.py +150 -1594
  14. pyegeria/md_processing_utils.py +853 -0
  15. {pyegeria-5.3.6.2.5.dist-info → pyegeria-5.3.6.4.dist-info}/METADATA +1 -1
  16. {pyegeria-5.3.6.2.5.dist-info → pyegeria-5.3.6.4.dist-info}/RECORD +21 -31
  17. {pyegeria-5.3.6.2.5.dist-info → pyegeria-5.3.6.4.dist-info}/entry_points.txt +5 -0
  18. pyegeria/commands/cat/freddie_jup.py +0 -124
  19. pyegeria/commands/cat/freddie_utils.py +0 -590
  20. pyegeria/commands/cat/freddies-inbox/freddie_intro.md +0 -221
  21. pyegeria/commands/cat/freddies-outbox/Terms-2025-03-06-13-19-29-Report.md +0 -69
  22. pyegeria/commands/cat/freddies-outbox/Terms-2025-03-06-13-20-30-Update-Form.md +0 -78
  23. pyegeria/commands/cat/freddies-outbox/Terms-2025-03-06-19-39-25-Update-Form.md +0 -78
  24. pyegeria/commands/cat/glossary_creation_experiment.ipynb +0 -235
  25. pyegeria/test_j.html +0 -0
  26. pyegeria/test_m.html +0 -213
  27. pyegeria/test_m1.html +0 -273
  28. pyegeria/test_mer.ipynb +0 -596
  29. pyegeria/test_w.html +0 -213
  30. /pyegeria/commands/cat/{freddies-inbox → dr_egeria_inbox}/glossary_creation_experiment.ipynb +0 -0
  31. /pyegeria/commands/cat/{freddies-inbox → dr_egeria_inbox}/glossary_exp.md +0 -0
  32. {pyegeria-5.3.6.2.5.dist-info → pyegeria-5.3.6.4.dist-info}/LICENSE +0 -0
  33. {pyegeria-5.3.6.2.5.dist-info → pyegeria-5.3.6.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,853 @@
1
+ """
2
+
3
+ This file contains functions to parse and process Egeria Markdown (Freddie)
4
+
5
+
6
+ """
7
+
8
+ import json
9
+ from jupyter_notebook_parser import JupyterNotebookParser
10
+ import nbformat
11
+ from typing import List, Optional
12
+
13
+ import os
14
+ import re
15
+ from pyegeria import EgeriaTech, NO_CATEGORIES_FOUND
16
+ from rich import box, print
17
+ from rich.console import Console
18
+ from rich.markdown import Markdown
19
+ from rich.prompt import Prompt
20
+ from rich.table import Table
21
+ import click
22
+ from pyegeria import EgeriaTech, body_slimmer, NO_GLOSSARIES_FOUND, NO_TERMS_FOUND, NO_ELEMENTS_FOUND, NO_PROJECTS_FOUND
23
+ from pyegeria._exceptions import (InvalidParameterException, PropertyServerException, print_exception_response, )
24
+ import datetime
25
+
26
+ console = Console(width=120)
27
+
28
+ commands = ["Provenance", "Create Glossary", "Update Glossary", "Create Term", "Update Term", "Create Personal Project",
29
+ "Update Personal Project", "Create Category", "Update Category"]
30
+ ERROR = "ERROR-> "
31
+ INFO = "INFO- "
32
+ WARNING = "WARNING-> "
33
+ pre_command = "\n---\n==> Processing command:"
34
+ EGERIA_LOCAL_QUALIFIER = os.environ.get("EGERIA_LOCAL_QUALIFIER", "PDR")
35
+ element_dictionary = {}
36
+
37
+
38
+ def is_valid_iso_date(date_text) -> bool:
39
+ """Checks if the given string is a valid ISO date."""
40
+ try:
41
+ datetime.datetime.strptime(date_text, '%Y-%m-%d')
42
+ return True
43
+ except ValueError:
44
+ return False
45
+
46
+
47
+ def get_current_datetime_string():
48
+ """Returns the current date and time as a human-readable string."""
49
+ now = datetime.datetime.now()
50
+ return now.strftime("%Y-%m-%d-%H-%M")
51
+
52
+ def add_term_to_categories(egeria_client: EgeriaTech, term_guid: str, categories_exist: bool, categories_list: [str], element_dictionary: dict)-> None:
53
+ if categories_exist is True and categories_list is not None:
54
+ for category in categories_list:
55
+ cat_guid = None
56
+ cat_el = category.strip()
57
+ if cat_el in element_dictionary:
58
+ cat= element_dictionary.get(cat_el, None)
59
+ cat_guid = cat.get('guid', None) if cat else None
60
+ if cat_guid is None:
61
+ cat_guid = egeria_client.__get_guid__(qualified_name=category)
62
+ egeria_client.add_term_to_category(term_guid, cat_guid)
63
+
64
+
65
+
66
+ def extract_command(block: str) -> str | None:
67
+ match = re.search(r"#(.*?)(?:##|\n|$)", block) # Using a non capturing group
68
+ if match:
69
+ return match.group(1).strip()
70
+ return None
71
+
72
+
73
+ def extract_attribute(text: str, labels: List[str]) -> str | None:
74
+ """
75
+ Extracts the glossary name from a string.
76
+
77
+ Args:
78
+ text: The input string.
79
+ label: List of equivalent labels to search for
80
+
81
+ Returns:
82
+ The glossary name, or None if not found.
83
+ """
84
+ # Iterate over the list of labels
85
+ for label in labels:
86
+ # Construct pattern for the current label
87
+ pattern = rf"## {re.escape(label)}\n(.*?)(?:##|$)"
88
+ match = re.search(pattern, text, re.DOTALL)
89
+ if match and not match.group(1).isspace(): # Ensure extracted text is not blank
90
+ return match.group(1).strip() # Return the matched text
91
+
92
+ return None # Return None if no match is found
93
+
94
+
95
+ def update_a_command(txt: str, command: str, obj_type: str, q_name: str, u_guid: str) -> str:
96
+ u_guid = u_guid if u_guid else " "
97
+ verb = command.split(' ')[0].strip()
98
+ action = "Update" if (verb == "Create" and u_guid is not None) else "Create"
99
+ txt = txt.replace(f"{command}", f'{action} {obj_type}\n') # update the command
100
+ txt = txt.replace('<GUID>', f'GUID\n{u_guid}') # update with GUID
101
+ txt = txt.replace('<Qualified Name>', f"Qualified Name\n{q_name}")
102
+ if "Qualified Name" not in txt:
103
+ txt += f"\n## Qualified Name\n{q_name}\n"
104
+ if "GUID" not in txt:
105
+ txt += f"\n## GUID\n{u_guid}\n"
106
+
107
+ # if (command in {"Update Term", "Update Category", 'Update Glossary'}) and ("Update Description" not in txt):
108
+ # txt += '\n** Update Description\n\n\n'
109
+ # elif "Update Description" in txt:
110
+ # pattern = r"(## Update Description\n).*?(#)"
111
+ # replacement = r"\1\n\n\2"
112
+ # txt += re.sub(pattern, replacement, txt)
113
+
114
+ status = extract_attribute(txt, "Status")
115
+ if command in ["Create Term", "Update Term"] and status is None:
116
+ pattern = r"(## Status\s*\n)(.*?)(#)"
117
+ replacement = r"\1\n DRAFT\n\n\3"
118
+ txt = re.sub(pattern, replacement, txt)
119
+ return txt
120
+
121
+ def process_provenance_command(file_path: str, txt: str) -> str:
122
+ """This commands processes a provenence command by pre-pending the current file name and time to the provenance output"""
123
+ output = (f"* Derived from processing file {file_path} on "
124
+ f"{datetime.now().strftime("%Y-%m-%d %H:%M")}\n")
125
+ existing_prov = extract_attribute(txt,'Provenance')
126
+ return f"# Provenance:\n{existing_prov}\n{output}\n"
127
+
128
+
129
+
130
+ def process_glossary_upsert_command(egeria_client: EgeriaTech, element_dictionary: dict, txt: str,
131
+ directive: str = "display") -> str | None:
132
+ """
133
+ Processes a glossary create or update command by extracting key attributes such as
134
+ glossary name, language, description, and usage from the given text.
135
+
136
+ :param txt: A string representing the input cell to be processed for
137
+ extracting glossary-related attributes.
138
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
139
+ :return: A string summarizing the outcome of the processing.
140
+ """
141
+ command = extract_command(txt)
142
+ object_type = command.split(' ')[1].strip()
143
+ object_action = command.split(' ')[0].strip()
144
+
145
+ glossary_name = extract_attribute(txt, ['Glossary Name'])
146
+ print(Markdown(f"{pre_command} `{command}` for glossary: `\'{glossary_name}\'` with directive: `{directive}` "))
147
+ language = extract_attribute(txt, ['Language'])
148
+ description = extract_attribute(txt, ['Description'])
149
+ usage = extract_attribute(txt, ['Usage'])
150
+
151
+ glossary_display = (f"\n* Command: {command}\n\t* Glossary Name: {glossary_name}\n\t"
152
+ f"* Language: {language}\n\t* Description:\n{description}\n"
153
+ f"* Usage: {usage}\n")
154
+
155
+ if object_action == 'Update':
156
+ q_name = extract_attribute(txt, ['Qualified Name'])
157
+ guid = extract_attribute(txt, ['GUID', 'guid', 'Guid'])
158
+ glossary_display += f"* Qualified Name: {q_name}\n\t* GUID: {guid}\n\n"
159
+
160
+ def validate_glossary(obj_action: str) -> tuple[bool, bool, str | None, str | None]:
161
+ valid = True
162
+ msg = ""
163
+ known_glossary_guid = None
164
+ known_q_name = None
165
+
166
+ glossary_details = egeria_client.get_glossaries_by_name(glossary_name)
167
+ if glossary_details == NO_GLOSSARIES_FOUND:
168
+ glossary_exists = False
169
+ else:
170
+ glossary_exists = True
171
+
172
+ if glossary_name is None:
173
+ msg = f"* {ERROR}Glossary name is missing\n"
174
+ valid = False
175
+ if language is None:
176
+ msg += f"* {ERROR}Language is missing\n"
177
+ valid = False
178
+ if description is None:
179
+ msg += f"* {INFO}Description is missing\n"
180
+
181
+ if len(glossary_details) > 1 and glossary_exists:
182
+ msg += f"* {ERROR}More than one glossary with name {glossary_name} found\n"
183
+ valid = False
184
+ if len(glossary_details) == 1:
185
+ known_glossary_guid = glossary_details[0]['elementHeader'].get('guid', None)
186
+ known_q_name = glossary_details[0]['glossaryProperties'].get('qualifiedName', None)
187
+
188
+ if obj_action == "Update":
189
+
190
+ if not glossary_exists:
191
+ msg += f"* {ERROR}Glossary {glossary_name} does not exist\n"
192
+ valid = False
193
+ # if len(glossary_details) > 1 and glossary_exists:
194
+ # msg += f"* {ERROR}More than one glossary with name {glossary_name} found\n"
195
+ # valid = False
196
+ # if len(glossary_details) == 1:
197
+ # known_glossary_guid = glossary_details[0]['elementHeader'].get('guid', None)
198
+ # known_q_name = glossary_details[0]['glossaryProperties'].get('qualifiedName',None)
199
+ if q_name is None:
200
+ msg += f"* {INFO}Qualified Name is missing => can use known qualified name of {known_q_name}\n"
201
+ valid = True
202
+ elif q_name != known_q_name:
203
+ msg += (
204
+ f"* {ERROR}Glossary `{glossary_name}` qualifiedName mismatch between {q_name} and {known_q_name}\n")
205
+ valid = False
206
+ if valid:
207
+ msg += glossary_display
208
+ msg += f"* -->Glossary `{glossary_name}` exists and can be updated\n"
209
+ else:
210
+ msg += f"* --> validation failed\n"
211
+
212
+ print(Markdown(msg))
213
+ return valid, glossary_exists, known_glossary_guid, known_q_name
214
+
215
+ elif obj_action == "Create":
216
+ if glossary_exists:
217
+ msg += f"{ERROR}Glossary {glossary_name} already exists\n"
218
+
219
+ elif valid:
220
+ msg += f"-->It is valid to create Glossary \'{glossary_name}\' with:\n"
221
+ msg += glossary_display
222
+
223
+ print(Markdown(msg))
224
+ return valid, glossary_exists, known_glossary_guid, known_q_name
225
+
226
+ if directive == "display":
227
+ print(Markdown(glossary_display))
228
+ return None
229
+
230
+ elif directive == "validate":
231
+ is_valid, exists, known_guid, known_q_name = validate_glossary(object_action)
232
+ valid = is_valid if is_valid else None
233
+ return valid
234
+
235
+ elif directive == "process":
236
+ is_valid, exists, known_guid, known_q_name = validate_glossary(object_action)
237
+ if not is_valid:
238
+ return None
239
+ if object_action == "Update":
240
+ if not exists:
241
+ print(
242
+ f"\n{ERROR}Glossary {glossary_name} does not exist! Updating result document with Create command\n")
243
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
244
+
245
+ body = {
246
+ "class": "ReferenceableRequestBody", "elementProperties": {
247
+ "class": "GlossaryProperties", "qualifiedName": known_q_name, "description": description,
248
+ "language": language, "usage": usage
249
+ }
250
+ }
251
+ egeria_client.update_glossary(known_guid, body)
252
+ print(f"\n-->Updated Glossary {glossary_name} with GUID {known_guid}")
253
+ element_dictionary[known_q_name] = {
254
+ 'guid': known_guid, 'display_name': glossary_name
255
+ }
256
+ # return update_a_command(txt, command, object_type, known_q_name, known_guid)
257
+ return egeria_client.get_glossary_by_guid(known_guid, output_format='MD')
258
+ elif object_action == "Create":
259
+ glossary_guid = None
260
+
261
+ if exists:
262
+ print(f"\nGlossary {glossary_name} already exists and result document updated\n")
263
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
264
+ else:
265
+ glossary_guid = egeria_client.create_glossary(glossary_name, description, language, usage)
266
+ glossary = egeria_client.get_glossary_by_guid(glossary_guid)
267
+ if glossary == NO_GLOSSARIES_FOUND:
268
+ print(f"{ERROR}Just created with GUID {glossary_guid} but Glossary not found\n")
269
+ return None
270
+ qualified_name = glossary['glossaryProperties']["qualifiedName"]
271
+ element_dictionary[qualified_name] = {
272
+ 'guid': glossary_guid, 'display_name': glossary_name
273
+ }
274
+ # return update_a_command(txt, command, object_type, qualified_name, glossary_guid)
275
+ return egeria_client.get_glossary_by_guid(glossary_guid, output_format = 'MD')
276
+
277
+
278
+ def process_categories_upsert_command(egeria_client: EgeriaTech, element_dictionary: dict, txt: str,
279
+ directive: str = "display") -> str | None:
280
+ """
281
+ Processes a glossary category create or update command by extracting key attributes such as
282
+ category name, qualified, description, and anchor glossary from the given txt..
283
+
284
+ :param txt: A string representing the input cell to be processed for
285
+ extracting category-related attributes.
286
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
287
+ :return: A string summarizing the outcome of the processing.
288
+ """
289
+ command = extract_command(txt)
290
+ object_type = command.split(' ')[1].strip()
291
+ object_action = command.split(' ')[0].strip()
292
+
293
+ category_name = extract_attribute(txt, ['Category Name', 'category_name', 'Cat'])
294
+ print(Markdown(f"{pre_command} `{command}` for category: `\'{category_name}\'` with directive: `{directive}` "))
295
+ owning_glossary_qn = extract_attribute(txt, ['Owning Glossary', '[In Glossary'])
296
+ description = extract_attribute(txt, ['Description'])
297
+ q_name = extract_attribute(txt, ['Qualified Name'])
298
+
299
+ category_display = (f"\n* Command: {command}\n\t* Category: {category_name}\n\t* In Glossary: {owning_glossary_qn}\n\t"
300
+ f"* Description:\n{description}\n\t* Qualified Name: {q_name}\n\t")
301
+ update_description = None
302
+
303
+ if object_action == 'Update':
304
+ guid = extract_attribute(txt, ['GUID','guid','Guid'])
305
+ update_description = extract_attribute(txt, 'Update Description')
306
+ category_display += (f"* GUID: {guid}\n\n"
307
+ f"* Update Description: \n {update_description}\n\t")
308
+
309
+ def validate_category(obj_action: str) -> tuple[bool, bool, str | None, str | None, str | None]:
310
+ valid = True
311
+ msg = ""
312
+ known_category_guid = None
313
+ known_q_name = None
314
+ glossary_guid = None
315
+
316
+ category_details = egeria_client.get_categories_by_name(category_name)
317
+ if category_details == NO_CATEGORIES_FOUND:
318
+ category_exists = False
319
+ else:
320
+ category_exists = True
321
+
322
+ if owning_glossary_qn is None:
323
+ msg += f"* {ERROR}Owning Glossary Qualified Name is missing\n"
324
+ valid = False
325
+
326
+ elif owning_glossary_qn in element_dictionary: # Check to see if we already know about this glossary
327
+ glossary_name = element_dictionary[owning_glossary_qn].get('display_name', None)
328
+ glossary_guid = element_dictionary[owning_glossary_qn].get('guid', None)
329
+
330
+ else:
331
+ # need to ask Egeria if it knows the Glossary Name
332
+ glossary = egeria_client.get_glossaries_by_name(owning_glossary_qn)
333
+ if glossary == NO_GLOSSARIES_FOUND:
334
+ msg += f"* {ERROR}Glossary `{owning_glossary_qn}` does not exist\n\n"
335
+ valid = False
336
+ else:
337
+ msg += f"* {INFO}Glossary `{owning_glossary_qn}` exists\n\n"
338
+ glossary_guid = glossary[0]['elementHeader'].get('guid', None)
339
+ glossary_name = glossary[0]['glossaryProperties'].get('displayName', None)
340
+ glossary_qn = glossary[0]['glossaryProperties'].get('qualifiedName', None)
341
+ if glossary_qn != owning_glossary_qn: # we were given the right qualified name - maybe a display_name
342
+ msg += f"* {ERROR}Glossary `{owning_glossary_qn}` is known by qualifiedName `{glossary_qn}`\n\n"
343
+ valid = False
344
+ else:
345
+ element_dictionary[owning_glossary_qn] = {
346
+ 'guid': glossary_guid, 'display_name': glossary_name
347
+ }
348
+
349
+ if category_name is None:
350
+ msg = f"* {ERROR}Category name is missing\n"
351
+ valid = False
352
+
353
+ if description is None:
354
+ msg += f"* {INFO}Description is missing\n"
355
+
356
+ if len(category_details) > 1 and category_exists:
357
+ msg += f"* {ERROR}More than one category with name `{category_name}` found\n"
358
+ valid = False
359
+ if len(category_details) == 1:
360
+ known_category_guid = category_details[0]['elementHeader'].get('guid', None)
361
+ known_q_name = category_details[0]['glossaryCategoryProperties'].get('qualifiedName', None)
362
+
363
+ if obj_action == "Update":
364
+ if not category_exists:
365
+ msg += f"* {ERROR}category `{category_name}` does not exist\n"
366
+ valid = False
367
+ if q_name is None:
368
+ msg += f"* {INFO}Qualified Name is missing => can use known qualified name of {known_q_name}\n"
369
+ valid = True
370
+ elif q_name != known_q_name:
371
+ msg += (
372
+ f"* {ERROR}category `{category_name}` qualifiedName mismatch between {q_name} and {known_q_name}\n")
373
+ valid = False
374
+ if valid:
375
+ msg += category_display
376
+ msg += f"* -->category `{category_name}` exists and can be updated\n"
377
+ else:
378
+ msg += f"* --> validation failed\n"
379
+
380
+ print(Markdown(msg))
381
+ return valid, category_exists, known_category_guid, known_q_name, glossary_guid
382
+
383
+ elif obj_action == "Create":
384
+ if category_exists:
385
+ msg += f"{ERROR}category `{category_name}` already exists\n"
386
+
387
+ elif valid:
388
+ msg += f"-->It is valid to create category `{category_name}` with:\n"
389
+ msg += category_display
390
+
391
+ print(Markdown(msg))
392
+ return valid, category_exists, known_category_guid, known_q_name, glossary_guid
393
+
394
+ if directive == "display":
395
+ print(Markdown(category_display))
396
+ return None
397
+
398
+ elif directive == "validate":
399
+ is_valid, exists, known_guid, known_q_name, glossary_guid = validate_category(object_action)
400
+ valid = is_valid if is_valid else None
401
+ return valid
402
+
403
+ elif directive == "process":
404
+ is_valid, exists, known_guid, known_q_name, glossary_guid = validate_category(object_action)
405
+ if not is_valid:
406
+ print(f"{ERROR}Validation checks failed in creating category `{category_name}`")
407
+ return None
408
+
409
+ if object_action == "Update":
410
+ if not exists:
411
+ print(
412
+ f"\n{ERROR}category `{category_name}` does not exist! Updating result document with Create "
413
+ f"command\n")
414
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
415
+
416
+ egeria_client.update_category(glossary_guid, category_name, description, known_q_name, None,
417
+ update_description)
418
+ print(f"\n-->Updated category `{category_name}`with GUID {known_guid}")
419
+ element_dictionary[known_q_name] = {
420
+ 'guid': known_guid, 'display_name': category_name
421
+ }
422
+ # return update_a_command(txt, command, object_type, known_q_name, known_guid)
423
+ return egeria_client.get_categories_by_guid(known_guid, output_format='FORM')
424
+
425
+ elif object_action == "Create":
426
+ is_root = False
427
+
428
+ if exists:
429
+ print(f"\ncategory `{category_name}` already exists and result document updated\n")
430
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
431
+ else:
432
+ category_guid = egeria_client.create_category(glossary_guid, category_name, description, is_root)
433
+ category = egeria_client.get_categories_by_guid(category_guid)
434
+
435
+ if category == NO_CATEGORIES_FOUND:
436
+ print(f"{ERROR}Just created with GUID {category_guid} but category not found\n")
437
+ return None
438
+ qualified_name = category['glossaryCategoryProperties']["qualifiedName"]
439
+ element_dictionary[qualified_name] = {
440
+ 'guid': category_guid, 'display_name': category_name
441
+ }
442
+ # return update_a_command(txt, command, object_type, qualified_name, category_guid)
443
+ return egeria_client.get_categories_by_guid(category_guid, output_format='MD')
444
+
445
+ def process_term_upsert_command(egeria_client: EgeriaTech, element_dictionary: dict, txt: str,
446
+ directive: str = "display") -> str | None:
447
+ """
448
+ Processes a term create or update command by extracting key attributes such as
449
+ term name, summary, description, abbreviation, examples, usage, version, and status from the given cell.
450
+
451
+ :param txt: A string representing the input cell to be processed for
452
+ extracting glossary-related attributes.
453
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
454
+ :return: A string summarizing the outcome of the processing.
455
+ """
456
+
457
+ command = extract_command(txt)
458
+ object_type = command.split(' ')[1].strip()
459
+ object_action = command.split(' ')[0].strip()
460
+
461
+ term_name = extract_attribute(txt, ['Term Name'])
462
+ summary = extract_attribute(txt, ['Summary'])
463
+ description = extract_attribute(txt, ['Description'])
464
+ abbreviation = extract_attribute(txt, ['Abbreviation'])
465
+ examples = extract_attribute(txt, ['Examples'])
466
+ usage = extract_attribute(txt, ['Usage'])
467
+ status = extract_attribute(txt, ['Status'])
468
+ version = extract_attribute(txt, ['Version', "Version Identifier"])
469
+ categories = extract_attribute(txt, ['Categories'])
470
+ q_name = extract_attribute(txt, ['Qualified Name'])
471
+ # q_name = q_name if q_name else " "
472
+
473
+ categories_list = None
474
+ cats_exist = True
475
+
476
+ glossary_qn = extract_attribute(txt, ['In Glossary','Owning Glossary'])
477
+
478
+ print(Markdown(f"{pre_command} `{command}` for term: `\'{term_name}\'` with directive: `{directive}`"))
479
+
480
+ def validate_term(obj_action: str) -> tuple[bool, bool, str | None, str | None]:
481
+ nonlocal version, status, categories, categories_list, cats_exist, q_name
482
+ valid = True
483
+ msg = ""
484
+ known_term_guid = None
485
+ known_q_name = None
486
+
487
+ # If the user has specified a qualified_name then use it to look for matching terms.
488
+ # If not, use the display_name.
489
+ if q_name:
490
+ term_details = egeria_client.get_terms_by_name(q_name)
491
+ else:
492
+ term_details = egeria_client.get_terms_by_name(term_name)
493
+
494
+ if term_details == NO_TERMS_FOUND:
495
+ term_exists = False
496
+ else:
497
+ term_exists = True
498
+
499
+ if status is None:
500
+ msg += f"* {INFO}Term status is missing - will default to DRAFT\n"
501
+ status = 'DRAFT'
502
+
503
+
504
+ if term_name is None:
505
+ msg = f"* {ERROR}Term name is missing\n"
506
+ valid = False
507
+ if glossary_qn is None:
508
+ msg += f"* {ERROR}Glossary qualified name is missing\n"
509
+ valid = False
510
+ else:
511
+ if glossary_qn not in element_dictionary:
512
+ glossary = egeria_client.get_glossaries_by_name(glossary_qn) #assuming q_name?
513
+ if isinstance(glossary,str):
514
+ msg += f"* {ERROR}Glossary `{glossary_qn}` is unknown\n "
515
+ valid = False
516
+ else:
517
+ element_dictionary[glossary_qn] = {
518
+ 'guid': glossary[0]['elementHeader'].get('guid', None),
519
+ 'display_name': glossary[0]['glossaryProperties'].get('displayName', None)
520
+ }
521
+
522
+
523
+ if categories is None:
524
+ msg += f"* {INFO} No categories found\n"
525
+ else:
526
+ categories_list = re.split(r'[,\n]+', categories)
527
+ categories = ""
528
+ for category in categories_list:
529
+ category_el = category.strip()
530
+ if category_el not in element_dictionary:
531
+ cat = egeria_client.get_categories_by_name(category) # assuming qualified name?
532
+ if isinstance(cat,str):
533
+ msg += (f"* {WARNING}Category `{category}` not found -> "
534
+ f"categories for this term won't be processed!\n")
535
+ cats_exist = False
536
+ categories = f"{category}, {categories}"
537
+ if cats_exist:
538
+ categories +='\n'
539
+ else:
540
+ categories = None
541
+
542
+
543
+ if summary is None:
544
+ msg += f"* {INFO}Term summary is missing\n"
545
+
546
+ if description is None:
547
+ msg += f"* {INFO}Term description is missing\n"
548
+
549
+ if abbreviation is None:
550
+ msg += f"* {INFO}Term abbreviation is missing\n"
551
+ if examples is None:
552
+ msg += f"* {INFO}Term examples is missing\n"
553
+ if usage is None:
554
+ msg += f"* {INFO}Term usage is missing\n"
555
+ if version is None:
556
+ msg += f"* {INFO}Term version is missing\n"
557
+ # version = "0.0.1"
558
+
559
+
560
+
561
+ if obj_action == "Update": # check to see if provided information exists and is consistent with existing info
562
+ if not term_exists:
563
+ msg += f"* {ERROR}Term {term_name} does not exist\n"
564
+ valid = False
565
+
566
+ if len(term_details) > 1 and term_exists:
567
+ msg += (f"* {ERROR}More than one term with name {term_name} found, please specify a "
568
+ f"**Qualified Name**\n")
569
+ valid = False
570
+ elif len(term_details) == 1:
571
+ known_term_guid = term_details[0]['elementHeader'].get('guid', None)
572
+ known_q_name = term_details[0]['glossaryTermProperties'].get('qualifiedName', None)
573
+ if q_name != known_q_name:
574
+ msg += (f"* {ERROR}Term {term_name} qualifiedName mismatch between {q_name} and {known_q_name}\n")
575
+ valid = False
576
+ else:
577
+ msg += f"\n--> * Term {term_name} exists and can be updated\n"
578
+ msg += term_display
579
+
580
+ print(Markdown(msg))
581
+ return valid, term_exists, known_term_guid, known_q_name
582
+
583
+ elif obj_action == 'Create': # if the command is create, check that it doesn't already exist
584
+ if term_exists:
585
+ msg += f"\n{WARNING}Term \'{term_name}\' already exists.\n"
586
+ elif not valid:
587
+ msg += f"\n-->Validation checks failed in creating Term \'{term_name}\' with: {term_display}\n"
588
+ else:
589
+ msg += f"\n-->It is valid to create Term \'{term_name}\' with: {term_display}\n"
590
+
591
+ print(Markdown(msg))
592
+ return valid, term_exists, known_term_guid, known_q_name
593
+
594
+
595
+ if object_action == "Update":
596
+ term_guid = extract_attribute(txt, 'GUID')
597
+ term_guid = term_guid if term_guid else " "
598
+
599
+
600
+ update_description = extract_attribute(txt, 'Update Description')
601
+ update_description = update_description if update_description else " "
602
+ term_display = (f"\n* Command: {command}\n\t* Glossary: {glossary_qn}\n\t"
603
+ f"* Term Name: {term_name}\n\t* Categories: {categories}\n\t* Summary: {summary}"
604
+ f"\n\t* Description: {description}\n\t"
605
+ f"* Abbreviation: {abbreviation}\n\t* Examples: {examples}\n\t* Usage: {usage}\n\t"
606
+ f"* Version: {version}\n\t* Status: {status}\n\t* GUID: {term_guid}\n\t* Qualified Name: "
607
+ f"{q_name}"
608
+ f"\n\t* Update Description: {update_description}\n")
609
+ else:
610
+ term_display = (f"\n* Command: {command}\n\t* Glossary: {glossary_qn}\n\t"
611
+ f"* Term Name: {term_name}\n\t* Categories: {categories}\n\t* Summary: {summary}\n\t"
612
+ f"* Description: {description}\n\t"
613
+ f"* Abbreviation: {abbreviation}\n\t* Examples: {examples}\n\t* Usage: {usage}\n\t"
614
+ f"* Version: {version}\n\t* Status: {status}\n")
615
+
616
+ if directive == "display":
617
+ print(Markdown(term_display))
618
+ return None
619
+ elif directive == "validate":
620
+ is_valid, exists, known_guid, known_q_name = validate_term(object_action)
621
+ valid = is_valid if is_valid else None
622
+ return valid
623
+ elif directive == "process":
624
+ try:
625
+ is_valid, exists, known_guid, known_q_name = validate_term(object_action)
626
+ if not is_valid: # First validate the term before we process it
627
+ return None
628
+
629
+ if object_action == "Update" and directive == "process":
630
+ if not exists:
631
+ print(f"\n-->Term {term_name} does not exist")
632
+ return None
633
+ body = {
634
+ "class": "ReferenceableRequestBody", "elementProperties": {
635
+ "class": "GlossaryTermProperties", "qualifiedName": known_q_name, "summary": summary,
636
+ "description": description, "abbreviation": abbreviation, "examples": examples, "usage": usage,
637
+ "publishVersionIdentifier": version, "status": status
638
+ }, "updateDescription": update_description
639
+ }
640
+ egeria_client.update_term(known_guid, body)
641
+ # if cats_exist is True and categories_list is not None:
642
+ # for category in categories_list:
643
+ # cat_guid = element_dictionary.get(f"category.{category}", None)
644
+ # if cat_guid is None:
645
+ # cat_guid = egeria_client.__get_guid__(display_name=category)
646
+ # egeria_client.add_term_to_category(known_guid, cat_guid)
647
+ add_term_to_categories(
648
+ egeria_client, known_guid, cats_exist , categories_list,
649
+ element_dictionary)
650
+ print(f"\n-->Updated Term {term_name} with GUID {known_guid} and categories {categories_list}")
651
+ return egeria_client.get_terms_by_guid(known_guid, 'md')
652
+ # return update_a_command(txt, command, object_type, known_q_name, known_guid)
653
+ elif object_action == "Update" and directive == "validate":
654
+ return egeria_client.get_terms_by_guid(known_guid, 'md')
655
+
656
+ elif object_action == "Create":
657
+ guid = None
658
+
659
+ q_name = egeria_client.__create_qualified_name__("Term",term_name, EGERIA_LOCAL_QUALIFIER)
660
+ if exists:
661
+ print(f"\n{WARNING}Term {term_name} exists and result document updated")
662
+ return update_a_command(txt, command, object_type, q_name, known_guid)
663
+ else:
664
+ ## get the guid for the glossary from the name - first look locally
665
+ glossary = element_dictionary.get(glossary_qn, None)
666
+
667
+ if glossary is not None:
668
+ glossary_guid = glossary.get('guid', None)
669
+ if glossary_guid is None:
670
+ print(f"{ERROR}Glossary reference {glossary_qn} not found")
671
+ return None
672
+ else:
673
+ glossary_guid = egeria_client.__get_guid__(qualified_name=glossary_qn)
674
+ if glossary_guid == NO_ELEMENTS_FOUND:
675
+ print(f"{ERROR}Glossary {glossary_qn} not found")
676
+ return None
677
+ term_body = {
678
+ "class": "ReferenceableRequestBody", "elementProperties": {
679
+ "class": "GlossaryTermProperties", "qualifiedName": q_name, "displayName": term_name,
680
+ "summary": summary, "description": description, "abbreviation": abbreviation,
681
+ "examples": examples, "usage": usage, "publishVersionIdentifier": version
682
+ # "additionalProperties":
683
+ # {
684
+ # "propertyName1": "xxxx",
685
+ # "propertyName2": "xxxx"
686
+ # }
687
+ }, "initialStatus": status
688
+ }
689
+ term_guid = egeria_client.create_controlled_glossary_term(glossary_guid, term_body)
690
+ if term_guid == NO_ELEMENTS_FOUND:
691
+ print(f"{ERROR}Term {term_name} not created")
692
+ return None
693
+ if cats_exist and categories is not None:
694
+ add_term_to_categories(
695
+ egeria_client, term_guid, cats_exist, categories_list,
696
+ element_dictionary)
697
+ print(f"\n-->Created Term {term_name} with GUID {term_guid}")
698
+ element_dictionary[q_name] = {'guid': term_guid, 'display_name': term_name}
699
+ return egeria_client.get_terms_by_guid(term_guid, 'MD')
700
+ # return update_a_command(txt, command, object_type, q_name, term_guid)
701
+ except Exception as e:
702
+ print(f"{ERROR}Error creating term {term_name}: {e}")
703
+ console.print_exception(show_locals=True)
704
+ return None
705
+
706
+ def process_per_proj_upsert_command(egeria_client: EgeriaTech, element_dictionary: dict, txt: str,
707
+ directive: str = "display") -> str | None:
708
+ """
709
+ Processes a personal project create or update command by extracting key attributes such as
710
+ glossary name, language, description, and usage from the given cell.
711
+
712
+ :param txt: A string representing the input cell to be processed for
713
+ extracting glossary-related attributes.
714
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
715
+ :return: A string summarizing the outcome of the processing.
716
+ """
717
+ command = extract_command(txt)
718
+ object = command.split()
719
+ object_type = f"{object[1]} {object[2]}"
720
+ object_action = object[0]
721
+
722
+ project_name = extract_attribute(txt, ['Project Name'])
723
+ description = extract_attribute(txt, ['Description'])
724
+ project_identifier = extract_attribute(txt, ['Project Identifier'])
725
+ project_status = extract_attribute(txt, ['Project Status'])
726
+ project_phase = extract_attribute(txt, ['Project Phase'])
727
+ project_health = extract_attribute(txt, ['Project Health'])
728
+ start_date = extract_attribute(txt, ['Start Date'])
729
+ planned_end_date = extract_attribute(txt, ['Planned End Date'])
730
+ print(Markdown(f"{pre_command} `\'{command}\'` for project: `{project_name}` with directive: `{directive}` "))
731
+
732
+ project_display = (f"\n* Command: {command}\n\t* Project: {project_name}\n\t"
733
+ f"* Status: {project_status}\n\t* Description: {description}\n\t"
734
+ f"* Phase: {project_phase}\n\t* Health: {project_health}\n\t"
735
+ f"* Start Date: {start_date}\n\t* Planned End Date: {planned_end_date}\n")
736
+
737
+ def validate_project(obj_action: str) -> tuple[bool, bool, str, str]:
738
+ valid = True
739
+ msg = ""
740
+ known_guid = None
741
+ known_q_name = None
742
+
743
+ project_details = egeria_client.get_projects_by_name(project_name)
744
+ if project_details == NO_PROJECTS_FOUND:
745
+ project_exists = False
746
+ else:
747
+ project_exists = True
748
+
749
+ if project_name is None:
750
+ msg = f"* {ERROR}Project name is missing\n"
751
+ valid = False
752
+ if project_status is None:
753
+ msg += f"* {INFO}No Project status found\n"
754
+
755
+ if description is None:
756
+ msg += f"* {INFO}No Description found\n"
757
+
758
+ if project_identifier is None:
759
+ msg += f"* {INFO}No Project Identifier found\n"
760
+
761
+ if project_phase is None:
762
+ msg += f"* {INFO}No Project Phase found\n"
763
+
764
+ if project_health is None:
765
+ msg += f"* {INFO}No Project Health found\n"
766
+
767
+ if start_date is None:
768
+ msg += f"* {INFO}No Start Date found\n"
769
+ elif not is_valid_iso_date(start_date):
770
+ msg += f"* {ERROR}Start Date is not a valid ISO date of form YYYY-MM-DD\n"
771
+ valid = False
772
+
773
+ if planned_end_date is None:
774
+ msg += f"* {INFO} No Planned End Date found\n"
775
+ elif not is_valid_iso_date(planned_end_date):
776
+ msg += f"* {ERROR}Planned End Date is not a valid ISO date of form YYYY-MM-DD\n"
777
+ valid = False
778
+
779
+ if obj_action == "Update":
780
+ q_name = extract_attribute(txt, 'Qualified Name')
781
+
782
+ if not project_exists:
783
+ msg += f"* {ERROR}Project {project_name} does not exist\n"
784
+ valid = False
785
+ if len(project_details) > 1 and project_exists:
786
+ msg += f"* {ERROR}More than one project with name {project_name} found\n"
787
+ valid = False
788
+ if len(project_details) == 1:
789
+ known_guid = project_details[0]['elementHeader'].get('guid', None)
790
+ known_q_name = project_details[0]['glossaryProperties'].get('qualifiedName', None)
791
+ if q_name is None:
792
+ msg += f"* {INFO}Qualified Name is missing => can use known qualified name of {known_q_name}\n"
793
+ valid = True
794
+ elif q_name != known_q_name:
795
+ msg += (f"* {ERROR}Project {project_name} qualifiedName mismatch between {q_name} and {known_q_name}\n")
796
+ valid = False
797
+ if valid:
798
+ msg += project_display
799
+ msg += f"* -->Project {project_name} exists and can be updated\n"
800
+ else:
801
+ msg += f"* --> validation failed\n"
802
+ msg += '---'
803
+ print(Markdown(msg))
804
+ return valid, project_exists, known_guid, known_q_name
805
+
806
+ elif obj_action == "Create":
807
+ if project_exists:
808
+ msg += f"\n{ERROR}Project {project_name} already exists"
809
+ else:
810
+ msg += f"\n-->It is valid to create Project \'{project_name}\' with:\n"
811
+ print(Markdown(msg))
812
+ return valid, project_exists, known_guid, known_q_name
813
+
814
+ if directive == "display":
815
+ print(Markdown(project_display))
816
+ return None
817
+
818
+ elif directive == "validate":
819
+ is_valid, exists, known_guid, known_q_name = validate_project(object_action)
820
+ valid = is_valid if is_valid else None
821
+ return valid
822
+
823
+ elif directive == "process":
824
+ is_valid, exists, known_guid, known_q_name = validate_project(object_action)
825
+ if not is_valid:
826
+ return None
827
+ if object_action == "Update":
828
+ if not exists:
829
+ print(f"\n\n-->Project {project_name} does not exist")
830
+ return None
831
+
832
+ egeria_client.update_project(known_guid, known_q_name, project_identifier, project_name, description,
833
+ project_status, project_phase, project_health, start_date, planned_end_date,
834
+ False)
835
+ print(f"\n-->Updated Project {project_name} with GUID {known_guid}")
836
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
837
+ elif object_action == "Create":
838
+ guid = None
839
+ if exists:
840
+ print(f"Project {project_name} already exists and update document created")
841
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
842
+ else:
843
+ guid = egeria_client.create_project(None, None, None, False, project_name, description,
844
+ "PersonalProject", project_identifier, True, project_status,
845
+ project_phase, project_health, start_date, planned_end_date)
846
+ project_g = egeria_client.get_project(guid)
847
+ if project_g == NO_GLOSSARIES_FOUND:
848
+ print(f"Just created with GUID {guid} but Project not found")
849
+ return None
850
+
851
+ q_name = project_g['projectProperties']["qualifiedName"]
852
+ element_dictionary[q_name] = {'guid': guid, 'display_name': project_name}
853
+ return update_a_command(txt, command, object_type, q_name, guid)