pyegeria 5.3.5.1__py3-none-any.whl → 5.3.5.2__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.
@@ -0,0 +1,530 @@
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
+ import os
12
+ import re
13
+ from pyegeria import EgeriaTech
14
+ from rich import box, print
15
+ from rich.console import Console
16
+ from rich.markdown import Markdown
17
+ from rich.prompt import Prompt
18
+ from rich.table import Table
19
+ import click
20
+ from pyegeria import EgeriaTech, body_slimmer, NO_GLOSSARIES_FOUND, NO_TERMS_FOUND, NO_ELEMENTS_FOUND, NO_PROJECTS_FOUND
21
+ from pyegeria._exceptions import (
22
+ InvalidParameterException,
23
+ PropertyServerException,
24
+ print_exception_response,
25
+ )
26
+ import datetime
27
+ commands = ["Create Glossary", "Update Glossary",
28
+ "Create Term", "Update Term",
29
+ "Create Personal Project", "Update Personal Project"]
30
+ ERROR = "ERROR-> "
31
+ INFO = "INFO- "
32
+ WARNING = "WARNING-> "
33
+
34
+ def is_valid_iso_date(date_text) -> bool:
35
+ """Checks if the given string is a valid ISO date."""
36
+ try:
37
+ datetime.datetime.strptime(date_text, '%Y-%m-%d')
38
+ return True
39
+ except ValueError:
40
+ return False
41
+
42
+ def get_current_datetime_string():
43
+ """Returns the current date and time as a human-readable string."""
44
+ now = datetime.datetime.now()
45
+ return now.strftime("%Y%m%d%H%M%S")
46
+
47
+
48
+ def extract_command(block: str) -> str | None:
49
+ match = re.search(r"#(.*?)(?:##|\n|$)", block) # Using a non capturing group
50
+ if match:
51
+ return match.group(1).strip()
52
+ return None
53
+
54
+
55
+ def extract_attribute (text: str, label: str) -> str | None:
56
+ """
57
+ Extracts the glossary name from a string.
58
+
59
+ Args:
60
+ text: The input string.
61
+ label: The label to search for.
62
+
63
+ Returns:
64
+ The glossary name, or None if not found.
65
+ """
66
+ pattern = r"## " + re.escape(label) + r"\n(.*?)(?:##|$)" # Construct pattern
67
+ match = re.search(pattern, text, re.DOTALL)
68
+ if match:
69
+ return match.group(1).strip()
70
+ return None
71
+
72
+ def update_a_command(txt: str, command: str, obj_type: str, q_name: str, u_guid: str)->str:
73
+ u_guid = u_guid if u_guid else " "
74
+ txt = txt.replace(f"{command}", f'**Update {obj_type}**\n') # update the command
75
+ txt = txt.replace('<GUID>', f'**GUID**\n{u_guid}') # update with GUID
76
+ txt = txt.replace('<Qualified Name>', f"**Qualified Name**\n{q_name}")
77
+ # if command == "Update Term":
78
+ # txt = txt.replace('Update Description', f"**Update Description**\n")
79
+ return txt
80
+
81
+
82
+
83
+ def process_glossary_upsert_command(egeria_client: EgeriaTech, txt: str, directive: str = "display" ) -> str | None:
84
+ """
85
+ Processes a glossary create or update command by extracting key attributes such as
86
+ glossary name, language, description, and usage from the given cell.
87
+
88
+ :param txt: A string representing the input cell to be processed for
89
+ extracting glossary-related attributes.
90
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
91
+ :return: A string summarizing the outcome of the processing.
92
+ """
93
+ command = extract_command(txt)
94
+ object_type = command.split(' ')[1].strip()
95
+ object_action = command.split(' ')[0].strip()
96
+
97
+ glossary_name = extract_attribute(txt, 'Glossary Name')
98
+ print(f"\n==> Processing command: {command} for glossary: {glossary_name} with directive: {directive} ")
99
+ language = extract_attribute(txt, 'Language')
100
+ description = extract_attribute(txt, 'Description')
101
+ usage = extract_attribute(txt, 'Usage')
102
+ glossary_display = (f"\n* Command: {command}\n\t* Glossary: {glossary_name}\n\t"
103
+ f"* Language: {language}\n\t* Description: {description}\n\t"
104
+ f"* Usage: {usage}")
105
+
106
+ def validate_glossary(obj_action: str) -> tuple[bool, bool, str]:
107
+ valid = True
108
+ msg = ""
109
+ known_glossary_guid = None
110
+ known_q_name = None
111
+
112
+ glossary_details = egeria_client.get_glossaries_by_name(glossary_name)
113
+ if glossary_details == NO_GLOSSARIES_FOUND:
114
+ glossary_exists = False
115
+ else:
116
+ glossary_exists = True
117
+
118
+ if glossary_name is None:
119
+ msg = f"* {ERROR}Glossary name is missing\n"
120
+ valid = False
121
+ if language is None:
122
+ msg += f"* {ERROR}Language is missing\n"
123
+ valid = False
124
+ if description is None:
125
+ msg += f"* {INFO}Description is missing\n"
126
+
127
+ if obj_action == "Update":
128
+ q_name = extract_attribute(txt, 'Qualified Name')
129
+
130
+ if not glossary_exists:
131
+ msg += f"* {ERROR}Glossary {glossary_name} does not exist\n"
132
+ valid = False
133
+ if len(glossary_details) > 1 and glossary_exists:
134
+ msg += f"* {ERROR}More than one glossary with name {glossary_name} found\n"
135
+ valid = False
136
+ if len(glossary_details) == 1:
137
+ known_glossary_guid = glossary_details[0]['elementHeader'].get('guid', None)
138
+ known_q_name = glossary_details[0]['glossaryProperties'].get('qualifiedName',None)
139
+ if q_name is None:
140
+ msg += f"* {INFO}Qualified Name is missing => can use known qualified name of {known_q_name}\n"
141
+ valid = True
142
+ elif q_name != known_q_name:
143
+ msg += (f"* {ERROR}Glossary {glossary_name} qualifiedName mismatch between {q_name} and {known_q_name}\n")
144
+ valid = False
145
+ if valid:
146
+ msg += glossary_display
147
+ msg += f"* -->Glossary {glossary_name} exists and can be updated\n"
148
+ else:
149
+ msg += f"* --> validation failed\n"
150
+ msg+='---'
151
+ print(Markdown(msg))
152
+ return valid, glossary_exists, known_glossary_guid, known_q_name
153
+
154
+ elif obj_action == "Create":
155
+ if glossary_exists:
156
+ msg += f"{ERROR}Glossary {glossary_name} already exists"
157
+ valid = False
158
+ else:
159
+ msg += f"-->It is valid to create Glossary \'{glossary_name}\' with:\n"
160
+ print(Markdown(msg))
161
+ return valid, glossary_exists, known_glossary_guid, known_q_name
162
+
163
+ if directive == "display":
164
+ print(Markdown(glossary_display))
165
+ return None
166
+
167
+ elif directive == "validate":
168
+ is_valid, exists, known_guid, known_q_name = validate_glossary(object_action)
169
+ valid = is_valid if is_valid else None
170
+ return valid
171
+
172
+ elif directive == "process":
173
+ is_valid, exists, known_guid, known_q_name = validate_glossary(object_action)
174
+ if not is_valid:
175
+ return None
176
+ if object_action == "Update":
177
+ if not exists:
178
+ print(f"\n-->Glossary {glossary_name} does not exist")
179
+ return None
180
+ body = {
181
+ "class": "ReferenceableRequestBody",
182
+ "elementProperties":
183
+ {
184
+ "class": "GlossaryProperties",
185
+ "qualifiedName": known_q_name,
186
+ "description": description,
187
+ "language": language,
188
+ "usage": usage
189
+ }
190
+ }
191
+ egeria_client.update_glossary(known_guid, body)
192
+ print(f"\n-->Updated Glossary {glossary_name} with GUID {known_guid}")
193
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
194
+ elif object_action == "Create" :
195
+ guid = None
196
+ q_name = f"Glossary:{glossary_name}:{get_current_datetime_string()}"
197
+
198
+ if exists:
199
+ print(f"Glossary {glossary_name} already exists")
200
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
201
+ else:
202
+ guid = egeria_client.create_glossary(glossary_name, description,
203
+ language, usage)
204
+ glossary = egeria_client.get_glossary_by_guid(guid)
205
+ if glossary == NO_GLOSSARIES_FOUND:
206
+ print(f"Just created with GUID {guid} but Glossary not found")
207
+ return None
208
+ q_name = glossary['glossaryProperties']["qualifiedName"]
209
+ return update_a_command(txt, command, object_type, q_name, guid)
210
+
211
+
212
+ def process_term_upsert_command(egeria_client: EgeriaTech, txt: str, directive: str = "display" ) -> str | None:
213
+ """
214
+ Processes a term create or update command by extracting key attributes such as
215
+ term name, summary, description, abbreviation, examples, usage, version, and status from the given cell.
216
+
217
+ :param txt: A string representing the input cell to be processed for
218
+ extracting glossary-related attributes.
219
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
220
+ :return: A string summarizing the outcome of the processing.
221
+ """
222
+
223
+ command = extract_command(txt)
224
+ object_type = command.split(' ')[1].strip()
225
+ object_action = command.split(' ')[0].strip()
226
+
227
+ term_name = extract_attribute(txt, 'Term Name')
228
+ summary = extract_attribute(txt, 'Summary')
229
+ description = extract_attribute(txt, 'Description')
230
+ abbreviation = extract_attribute(txt, 'Abbreviation')
231
+ examples = extract_attribute(txt, 'Examples')
232
+ usage = extract_attribute(txt, 'Usage')
233
+ version = extract_attribute(txt, 'Version')
234
+ status = extract_attribute(txt, 'Status')
235
+ glossary_name = extract_attribute(txt, 'Glossary Name')
236
+
237
+ print(f"\n==> Processing command: {command} for Term {term_name} with directive: {directive}")
238
+
239
+ def validate_term(obj_action: str) -> tuple[bool, bool, str]:
240
+ valid = True
241
+ msg = ""
242
+ known_term_guid = None
243
+ known_q_name = None
244
+
245
+ term_details = egeria_client.get_terms_by_name(term_name)
246
+ if term_details == NO_TERMS_FOUND:
247
+ term_exists = False
248
+ else:
249
+ term_exists = True
250
+
251
+ if term_name is None:
252
+ msg = f"* {ERROR}Term name is missing\n"
253
+ valid = False
254
+ if glossary_name is None:
255
+ msg += f"* {ERROR}Glossary name is missing\n"
256
+ valid = False
257
+ if status is None:
258
+ msg += f"* {INFO}Term status is missing - will default to DRAFT"
259
+ if obj_action == "Update": # check to see if provided information exists and is consistent with existing info
260
+
261
+ if not term_exists:
262
+ msg += f"* {ERROR}Term {term_name} does not exist\n"
263
+ valid = False
264
+
265
+ if len(term_details) > 1 and term_exists:
266
+ msg += f"* {ERROR}More than one term with name {term_name} found\n"
267
+ valid = False
268
+ elif len(term_details) == 1:
269
+ known_term_guid = term_details[0]['elementHeader'].get('guid', None)
270
+ known_q_name = term_details[0]['glossaryTermProperties'].get('qualifiedName', None)
271
+ if q_name != known_q_name:
272
+ msg += (f"* {ERROR}Term {term_name} qualifiedName mismatch between {q_name} and {known_q_name}\n")
273
+ valid = False
274
+ else:
275
+ msg += f"--> * Term {term_name} exists and can be updated\n"
276
+ msg += term_display
277
+ msg += '\n---\n'
278
+ print(Markdown(msg))
279
+ return valid, term_exists, known_term_guid
280
+
281
+ elif obj_action == 'Create': # if the command is create, check that it doesn't already exist
282
+ if term_exists:
283
+ msg += f"{WARNING}Term \'{term_name}\' already exists\n"
284
+
285
+ else:
286
+ msg += f"-->It is valid to create Term \'{term_name}\' with: {term_display}\n"
287
+ msg += '\n---\n'
288
+ print(Markdown(msg))
289
+ return valid, term_exists, known_term_guid
290
+
291
+ if object_action == "Update":
292
+ term_guid = extract_attribute(txt, 'GUID')
293
+ term_guid = term_guid if term_guid else " "
294
+ q_name = extract_attribute(txt, 'Qualified Name')
295
+ q_name = q_name if q_name else " "
296
+ update_description = extract_attribute(txt, 'Update Description')
297
+ update_description = update_description if update_description else " "
298
+ term_display = (f"\n* Command: {command}\n\t* Glossary: {glossary_name}\n\t"
299
+ f"* Term Name: {term_name}\n\t* Summary: {summary}\n\t* Description: {description}\n\t"
300
+ f"* Abbreviation: {abbreviation}\n\t* Examples: {examples}\n\t* Usage: {usage}\n\t"
301
+ f"* Version: {version}\n\t* Status: {status}\n\t* GUID: {term_guid}\n\t* Qualified Name: {q_name}"
302
+ f"\n\t* Update Description: {update_description}\n")
303
+ else:
304
+ term_display = (f"\n* Command: {command}\n\t* Glossary: {glossary_name}\n\t"
305
+ f"* Term Name: {term_name}\n\t* Summary: {summary}\n\t* Description: {description}\n\t"
306
+ f"* Abbreviation: {abbreviation}\n\t* Examples: {examples}\n\t* Usage: {usage}\n\t"
307
+ f"* Version: {version}\n\t* Status: {status}\n")
308
+
309
+
310
+ if directive == "display":
311
+ print(Markdown(term_display))
312
+ return None
313
+ elif directive == "validate":
314
+ is_valid, exists, known_guid = validate_term(object_action)
315
+ valid = is_valid if is_valid else None
316
+ return valid
317
+ elif directive == "process":
318
+ is_valid, exists, known_guid = validate_term(object_action)
319
+ if not is_valid: # First validate the term before we process it
320
+ return None
321
+
322
+ if object_action == "Update":
323
+ if not exists:
324
+ print(f"\n-->Term {term_name} does not exist")
325
+ return None
326
+ body = {
327
+ "class": "ReferenceableRequestBody",
328
+ "elementProperties":
329
+ {
330
+ "class": "GlossaryTermProperties",
331
+ "qualifiedName": q_name,
332
+ "summary": summary,
333
+ "description": description,
334
+ "abbreviation": abbreviation,
335
+ "examples": examples,
336
+ "usage": usage,
337
+ "publishVersionIdentifier": version,
338
+ "status": status
339
+ },
340
+ "updateDescription": update_description
341
+ }
342
+ egeria_client.update_term(known_guid, body)
343
+ print(f"\n-->Updated Term {term_name} with GUID {known_guid}")
344
+ return update_a_command(txt, command, object_type, q_name, known_guid)
345
+
346
+ elif object_action == "Create" :
347
+ guid = None
348
+ q_name = f"GlossaryTerm:{term_name}:{get_current_datetime_string()}"
349
+ if exists:
350
+ print(f"\n-->Term {term_name} exists and document updated")
351
+ return update_a_command(txt, command, object_type, q_name, guid)
352
+ else:
353
+ ## get the guid for the glossary from the name
354
+ glossary_guid = egeria_client.__get_guid__(property_name="displayName", display_name=glossary_name)
355
+ if glossary_guid == NO_ELEMENTS_FOUND:
356
+ print(f"Glossary {glossary_name} not found")
357
+ return None
358
+ term_body = {
359
+ "class": "ReferenceableRequestBody",
360
+ "elementProperties":
361
+ {
362
+ "class": "GlossaryTermProperties",
363
+ "qualifiedName":q_name,
364
+ "displayName": term_name,
365
+ "summary": summary,
366
+ "description": description,
367
+ "abbreviation": abbreviation,
368
+ "examples": examples,
369
+ "usage": usage,
370
+ "publishVersionIdentifier": version
371
+ # "additionalProperties":
372
+ # {
373
+ # "propertyName1": "xxxx",
374
+ # "propertyName2": "xxxx"
375
+ # }
376
+ },
377
+ "initialStatus": status
378
+ }
379
+ term_guid = egeria_client.create_controlled_glossary_term(glossary_guid, term_body)
380
+ print(f"\n-->Created Term {term_name} with GUID {term_guid}")
381
+ return update_a_command(txt, command, object_type, q_name, term_guid)
382
+
383
+ def process_per_proj_upsert_command(egeria_client: EgeriaTech, txt: str, directive: str = "display" ) -> str | None:
384
+ """
385
+ Processes a personal project create or update command by extracting key attributes such as
386
+ glossary name, language, description, and usage from the given cell.
387
+
388
+ :param txt: A string representing the input cell to be processed for
389
+ extracting glossary-related attributes.
390
+ :param directive: an optional string indicating the directive to be used - display, validate or execute
391
+ :return: A string summarizing the outcome of the processing.
392
+ """
393
+ command = extract_command(txt)
394
+ object = command.split()
395
+ object_type = f"{object[1]} {object[2]}"
396
+ object_action = object[0]
397
+
398
+ project_name = extract_attribute(txt, 'Project Name')
399
+ description = extract_attribute(txt, 'Description')
400
+ project_identifier = extract_attribute(txt, 'Project Identifier')
401
+ project_status = extract_attribute(txt, 'Project Status')
402
+ project_phase = extract_attribute(txt, 'Project Phase')
403
+ project_health = extract_attribute(txt, 'Project Health')
404
+ start_date = extract_attribute(txt, 'Start Date')
405
+ planned_end_date = extract_attribute(txt, 'Planned End Date')
406
+ print(f"\n==> Processing command: {command} for project: {project_name} with directive: {directive} ")
407
+
408
+ project_display = (f"\n* Command: {command}\n\t* Project: {project_name}\n\t"
409
+ f"* Status: {project_status}\n\t* Description: {description}\n\t"
410
+ f"* Phase: {project_phase}\n\t* Health: {project_health}\n\t"
411
+ f"* Start Date: {start_date}\n\t* Planned End Date: {planned_end_date}\n")
412
+
413
+ def validate_project(obj_action: str) -> tuple[bool, bool, str, str]:
414
+ valid = True
415
+ msg = ""
416
+ known_guid = None
417
+ known_q_name = None
418
+
419
+ project_details = egeria_client.get_projects_by_name(project_name)
420
+ if project_details == NO_PROJECTS_FOUND:
421
+ project_exists = False
422
+ else:
423
+ project_exists = True
424
+
425
+ if project_name is None:
426
+ msg = f"* {ERROR}Project name is missing\n"
427
+ valid = False
428
+ if project_status is None:
429
+ msg += f"* {INFO}No Project status found\n"
430
+
431
+ if description is None:
432
+ msg += f"* {INFO}No Description found\n"
433
+
434
+ if project_identifier is None:
435
+ msg += f"* {INFO}No Project Identifier found\n"
436
+
437
+ if project_phase is None:
438
+ msg += f"* {INFO}No Project Phase found\n"
439
+
440
+ if project_health is None:
441
+ msg += f"* {INFO}No Project Health found\n"
442
+
443
+ if start_date is None:
444
+ msg += f"* {INFO}No Start Date found\n"
445
+ elif not is_valid_iso_date(start_date):
446
+ msg += f"* {ERROR}Start Date is not a valid ISO date of form YYYY-MM-DD\n"
447
+ valid = False
448
+
449
+ if planned_end_date is None:
450
+ msg += f"* {INFO} No Planned End Date found\n"
451
+ elif not is_valid_iso_date(planned_end_date):
452
+ msg += f"* {ERROR}Planned End Date is not a valid ISO date of form YYYY-MM-DD\n"
453
+ valid = False
454
+
455
+ if obj_action == "Update":
456
+ q_name = extract_attribute(txt, 'Qualified Name')
457
+
458
+ if not project_exists:
459
+ msg += f"* {ERROR}Project {project_name} does not exist\n"
460
+ valid = False
461
+ if len(project_details) > 1 and project_exists:
462
+ msg += f"* {ERROR}More than one projecty with name {project_name} found\n"
463
+ valid = False
464
+ if len(project_details) == 1:
465
+ known_guid = project_details[0]['elementHeader'].get('guid', None)
466
+ known_q_name = project_details[0]['glossaryProperties'].get('qualifiedName',None)
467
+ if q_name is None:
468
+ msg += f"* {INFO}Qualified Name is missing => can use known qualified name of {known_q_name}\n"
469
+ valid = True
470
+ elif q_name != known_q_name:
471
+ msg += (f"* {ERROR}Project {project_name} qualifiedName mismatch between {q_name} and {known_q_name}\n")
472
+ valid = False
473
+ if valid:
474
+ msg += project_display
475
+ msg += f"* -->Project {project_name} exists and can be updated\n"
476
+ else:
477
+ msg += f"* --> validation failed\n"
478
+ msg+='---'
479
+ print(Markdown(msg))
480
+ return valid, project_exists, known_guid, known_q_name
481
+
482
+ elif obj_action == "Create":
483
+ if project_exists:
484
+ msg += f"\n{ERROR}Project {project_name} already exists"
485
+ else:
486
+ msg += f"\n-->It is valid to create Project \'{project_name}\' with:\n"
487
+ print(Markdown(msg))
488
+ return valid, project_exists, known_guid, known_q_name
489
+
490
+ if directive == "display":
491
+ print(Markdown(project_display))
492
+ return None
493
+
494
+ elif directive == "validate":
495
+ is_valid, exists, known_guid, known_q_name = validate_project(object_action)
496
+ valid = is_valid if is_valid else None
497
+ return valid
498
+
499
+ elif directive == "process":
500
+ is_valid, exists, known_guid, known_q_name = validate_project(object_action)
501
+ if not is_valid:
502
+ return None
503
+ if object_action == "Update":
504
+ if not exists:
505
+ print(f"\n\n-->Project {project_name} does not exist")
506
+ return None
507
+
508
+ egeria_client.update_project(known_guid, known_q_name, project_identifier, project_name,
509
+ description,project_status,project_phase,project_health,
510
+ start_date, planned_end_date,False)
511
+ print(f"\n-->Updated Project {project_name} with GUID {known_guid}")
512
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
513
+ elif object_action == "Create" :
514
+ guid = None
515
+
516
+ if exists:
517
+ print(f"Project {project_name} already exists and")
518
+ return update_a_command(txt, command, object_type, known_q_name, known_guid)
519
+ else:
520
+ guid = egeria_client.create_project(None,None, None, False,
521
+ project_name, description,
522
+ "PersonalProject",project_identifier, True,
523
+ project_status, project_phase, project_health,
524
+ start_date, planned_end_date)
525
+ project_g = egeria_client.get_project(guid)
526
+ if project_g == NO_GLOSSARIES_FOUND:
527
+ print(f"Just created with GUID {guid} but Project not found")
528
+ return None
529
+ q_name = project_g['prjectProperties']["qualifiedName"]
530
+ return update_a_command(txt, command, object_type, q_name, guid)