pyegeria 5.4.0.23__py3-none-any.whl → 5.4.0.24__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 (34) hide show
  1. commands/cat/debug_log +2046 -465
  2. commands/cat/debug_log.2025-08-15_09-14-07_444802.zip +0 -0
  3. commands/cat/debug_log.2025-08-16_10-21-59_388912.zip +0 -0
  4. commands/cat/dr_egeria_md.py +16 -3
  5. md_processing/__init__.py +5 -3
  6. md_processing/data/commands.json +8310 -903
  7. md_processing/dr_egeria_inbox/gov_def.md +76 -18
  8. md_processing/dr_egeria_inbox/img.png +0 -0
  9. md_processing/dr_egeria_inbox/product.md +132 -20
  10. md_processing/dr_egeria_outbox/monday/processed-2025-08-17 21:04-product.md +97 -0
  11. md_processing/md_commands/governance_officer_commands.py +291 -150
  12. md_processing/md_commands/product_manager_commands.py +309 -401
  13. md_processing/md_processing_utils/common_md_proc_utils.py +110 -7
  14. md_processing/md_processing_utils/common_md_utils.py +112 -26
  15. md_processing/md_processing_utils/md_processing_constants.py +8 -5
  16. pyegeria/__init__.py +1 -1
  17. pyegeria/_client_new.py +3 -2
  18. pyegeria/_exceptions_new.py +6 -0
  19. pyegeria/_output_formats.py +17 -2
  20. pyegeria/governance_officer.py +1 -3
  21. pyegeria/load_config.py +1 -1
  22. pyegeria/models.py +28 -1
  23. pyegeria/utils.py +1 -1
  24. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.24.dist-info}/METADATA +1 -1
  25. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.24.dist-info}/RECORD +28 -30
  26. commands/cat/logs/pyegeria.log +0 -136
  27. md_processing/dr_egeria_outbox/friday/processed-2025-07-18 15:00-product.md +0 -62
  28. md_processing/dr_egeria_outbox/friday/processed-2025-07-18 15:13-product.md +0 -62
  29. md_processing/dr_egeria_outbox/friday/processed-2025-07-20 13:23-product.md +0 -47
  30. md_processing/dr_egeria_outbox/friday/processed-2025-08-01 11:55-data_test3.md +0 -503
  31. md_processing/dr_egeria_outbox/processed-2025-08-03 16:05-glossary_list.md +0 -37
  32. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.24.dist-info}/LICENSE +0 -0
  33. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.24.dist-info}/WHEEL +0 -0
  34. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.24.dist-info}/entry_points.txt +0 -0
@@ -4,10 +4,11 @@ This file contains general utility functions for processing Egeria Markdown
4
4
  import json
5
5
  import os
6
6
  import sys
7
- from typing import List
7
+ from typing import List, Optional
8
8
 
9
9
  from loguru import logger
10
10
  from rich import print
11
+ from rich.markdown import Markdown
11
12
  from rich.console import Console
12
13
 
13
14
  from md_processing.md_processing_utils.common_md_utils import (get_current_datetime_string, get_element_dictionary,
@@ -15,16 +16,20 @@ from md_processing.md_processing_utils.common_md_utils import (get_current_datet
15
16
  split_tb_string, str_to_bool, )
16
17
  from md_processing.md_processing_utils.extraction_utils import (process_simple_attribute, extract_attribute,
17
18
  get_element_by_name)
19
+ from md_processing.md_processing_utils.common_md_utils import (update_element_dictionary, set_gov_prop_body, \
20
+ set_update_body, set_create_body,
21
+ set_peer_gov_def_request_body, set_rel_request_body,
22
+ set_delete_request_body,set_rel_request_body,
23
+ set_filter_request_body, setup_log,
24
+ ALL_GOVERNANCE_DEFINITIONS, set_find_body)
25
+ from md_processing.md_processing_utils.extraction_utils import (extract_command_plus, update_a_command)
18
26
  from md_processing.md_processing_utils.md_processing_constants import (get_command_spec)
19
27
  from md_processing.md_processing_utils.message_constants import (ERROR, INFO, WARNING, ALWAYS, EXISTS_REQUIRED)
20
- from pyegeria import EgeriaTech
28
+ from pyegeria import EgeriaTech, select_output_format_set
29
+
21
30
  from pyegeria._globals import DEBUG_LEVEL
22
31
 
23
- log_format = "P {time} | {level} | {function} | {line} | {message} | {extra}"
24
- logger.remove()
25
- logger.add(sys.stderr, level="INFO", format=log_format, colorize=True)
26
- logger.add("debug_log.log", rotation="1 day", retention="1 week", compression="zip", level="TRACE", format=log_format,
27
- colorize=True)
32
+
28
33
  # Constants
29
34
  EGERIA_WIDTH = int(os.environ.get("EGERIA_WIDTH", "200"))
30
35
  EGERIA_USAGE_LEVEL = os.environ.get("EGERIA_USAGE_LEVEL", "Basic")
@@ -923,3 +928,101 @@ def update_term_categories(egeria_client: EgeriaTech, term_guid: str, categories
923
928
 
924
929
 
925
930
 
931
+
932
+ @logger.catch
933
+ def process_output_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
934
+ """
935
+ Processes a generic output request by extracting attributes (including Output Format and
936
+ Output Format Set) and dynamically invoking the find function specified by the
937
+ output_format_set, following the approach used in commands/cat/list_format_set.
938
+
939
+ This is modeled on process_gov_definition_list_command but uses the dynamic
940
+ dispatch via the output format set rather than directly calling a specific
941
+ egeria_client method.
942
+
943
+ :param egeria_client: EgeriaTech composite client instance
944
+ :param txt: The command text (e.g., parsed from a markdown cell)
945
+ :param directive: display | validate | process
946
+ :return: Markdown string for processed output or None
947
+ """
948
+ command, object_type, object_action = extract_command_plus(txt)
949
+ print(Markdown(f"# {command}\n"))
950
+
951
+ parsed_output = parse_view_command(egeria_client, object_type, object_action, txt, directive)
952
+
953
+ valid = parsed_output['valid']
954
+ print(Markdown(f"Performing {command}"))
955
+ print(Markdown(parsed_output['display']))
956
+
957
+ attr = parsed_output.get('attributes', {})
958
+
959
+ search_string = attr.get('Search String', {}).get('value', '*')
960
+ output_format = attr.get('Output Format', {}).get('value', 'LIST')
961
+ output_format_set = attr.get('Output Format Set', {}).get('value', object_type)
962
+
963
+ if directive == "display":
964
+ return None
965
+ elif directive == "validate":
966
+ # Validate that the format set exists and has an action
967
+ fmt = select_output_format_set(output_format_set, "ANY") if valid else None
968
+ if valid and fmt and fmt.get("action"):
969
+ print(Markdown(f"==> Validation of {command} completed successfully!\n"))
970
+ return True
971
+ else:
972
+ msg = f"Validation failed for object_action `{command}`"
973
+ logger.error(msg)
974
+ return False
975
+
976
+ elif directive == "process":
977
+ try:
978
+ if not valid:
979
+ msg = f"Validation failed for {object_action} `{object_type}`"
980
+ logger.error(msg)
981
+ return None
982
+
983
+ # Resolve the find function from the output format set
984
+ fmt = select_output_format_set(output_format_set, output_format)
985
+ if not fmt:
986
+ logger.error(f"Output format set '{output_format_set}' not found or not compatible with '{output_format}'.")
987
+ return None
988
+ action = fmt.get("action", {})
989
+ func_spec = action.get("function")
990
+ if not func_spec or "." not in func_spec:
991
+ func_spec = f"EgeriaTech.find_{object_type.replace(' ', '_').lower()}"
992
+
993
+
994
+ # Extract method name and get it from the composite client
995
+ _, method_name = func_spec.split(".", 1)
996
+ if not hasattr(egeria_client, method_name):
997
+ logger.error(f"Method '{method_name}' not available on EgeriaTech client.")
998
+ return None
999
+ method = getattr(egeria_client, method_name)
1000
+
1001
+ # Build body and params
1002
+ list_md = f"\n# `{object_type}` with filter: `{search_string}`\n\n"
1003
+ body = set_find_body(object_type, attr)
1004
+
1005
+ params = {
1006
+ 'search_string': search_string,
1007
+ 'body': body,
1008
+ 'output_format': output_format,
1009
+ 'output_format_set': output_format_set,
1010
+ }
1011
+
1012
+ # Call the resolved method
1013
+ struct = method(**params)
1014
+
1015
+ if output_format.upper() == "DICT":
1016
+ list_md += f"```\n{json.dumps(struct, indent=4)}\n```\n"
1017
+ else:
1018
+ list_md += struct
1019
+ logger.info(f"Wrote `{object_type}` for search string: `{search_string}` using format set '{output_format_set}'")
1020
+
1021
+ return list_md
1022
+
1023
+ except Exception as e:
1024
+ logger.error(f"Error performing {command}: {e}")
1025
+ console.print_exception(show_locals=True)
1026
+ return None
1027
+ else:
1028
+ return None
@@ -11,7 +11,7 @@ from loguru import logger
11
11
  from rich import print
12
12
  from rich.console import Console
13
13
  from rich.markdown import Markdown
14
- from pyegeria.utils import (camel_to_title_case)
14
+ from pyegeria.utils import (camel_to_title_case, body_slimmer)
15
15
  from pyegeria._globals import DEBUG_LEVEL
16
16
  from md_processing.md_processing_utils.message_constants import message_types
17
17
 
@@ -38,11 +38,22 @@ LOG_FORMAT = "D <green> {time} </green> | {level} | {function} | {line} | {messa
38
38
  CONSOLE_LOG_FORMAT = "<green>{time}</green> | {message}"
39
39
 
40
40
  console = Console(width=EGERIA_WIDTH)
41
- GENERAL_GOVERNANCE_DEFINITIONS = ["Business Imperative", "Regulation Article", "Threat", "Governance Principle",
42
- "Governance Obligation", "Governance Approach", "Governance Processing Purpose"]
41
+ GENERAL_GOVERNANCE_DEFINITIONS = ["Governance Definition", "Business Imperative", "Regulation Article", "Threat",
42
+ "Governance Policy", "Governance Principle", "Governance Obligation",
43
+ "Governance Approach",
44
+ "Governance Processing Purpose"]
45
+ GOVERNANCE_DRIVERS = ["Governance Driver", "Governance Strategy", "Governance Imperative", "Regulation",
46
+ "Regulation Article", "Threat"]
47
+ GOVERNANCE_POLICIES = ["Governance Policy", "Governance Principle", "Governance Obligation", "Governance Approach"]
43
48
 
44
- GOVERNANCE_CONTROLS = ["Governance Rule", "Service Level Objective", "Governance Process",
45
- "Governance Responsibility", "Governance Procedure", "Security Access Control"]
49
+
50
+ GOVERNANCE_CONTROLS = ["Governance Control", "Governance Rule", "Service Level Objective", "Governance Action",
51
+ "Security Access Control", "Governance Procedure","Governance Responsibility",
52
+ "Subject Area Definition", "Data Processing Purposes", "Methodology"]
53
+
54
+ ALL_GOVERNANCE_DEFINITIONS = GENERAL_GOVERNANCE_DEFINITIONS + GOVERNANCE_CONTROLS + ["Governance Strategy", "Regulation",
55
+ "Security Group", "GovernanceMetric",
56
+ "Naming Standard Rule", "TermsAndConditions", "Certification Type", "License Type"]
46
57
 
47
58
  debug_level = DEBUG_LEVEL
48
59
  global COMMAND_DEFINITIONS
@@ -216,13 +227,46 @@ def find_key_with_value(value: str) -> str | None:
216
227
  return None # If value not found
217
228
 
218
229
 
230
+ def set_find_body(object_type: str, attributes: dict)->dict:
231
+ prop_name = object_type.replace(" ", "")
232
+
233
+ start = attributes.get('Start From', {}).get('value', 0)
234
+ start_from = int(start) if start else 0
235
+ page = attributes.get('Page Size', {}).get('value', 0)
236
+ page_size = int(page) if page else 0
237
+ depth = attributes.get('Graph Query Depth', {}).get('value', 0)
238
+ depth = int(depth) if depth else 0
239
+
240
+
241
+
242
+ body = {
243
+ "class": "SearchStringRequestBody",
244
+ "searchString": attributes.get('Search String', {}).get('value', None),
245
+ "startsWith": attributes.get('Start With', {}).get('value', True),
246
+ "endWith": attributes.get('End With', {}).get('value', False),
247
+ "ignoreCase": attributes.get('Ignore Case', {}).get('value', False),
248
+ "limitResultsByStatus": attributes.get('Limit Results By Status', {}).get('value', False),
249
+ "startFrom": int(attributes.get('Start From', {}).get('value', 0)),
250
+ "pageSize": int(attributes.get('Page Size', {}).get('value', 0)),
251
+ # "metadataElementSubtypeNames": attributes.get('Metadata Element Subtype Name', {}).get('value', None),
252
+ "metadataElementTypeName": attributes.get('Metadata Element Type Name', {}).get('value', None),
253
+ "effectiveTime": attributes.get('Effective Time', {}).get('value', None),
254
+ "governanceZoneFilter" : attributes.get('Governance Zone Filter', {}).get('value', None),
255
+ "graphQueryDepth": int(attributes.get('Graph Query Depth', {}).get('value', 0)),
256
+ "initialStatus": attributes.get('Status', {}).get('value', "ACTIVE"),
257
+ "initialClassifications": {}}
258
+
259
+ return body
260
+
261
+
219
262
  def set_create_body(object_type: str, attributes: dict)->dict:
220
263
  prop_name = object_type.replace(" ", "")
221
264
  body = {
222
- "class": "NewGovernanceDefinitionRequestBody",
265
+ "class": "NewElementRequestBody",
223
266
  "externalSourceGUID": attributes.get('External Source GUID', {}).get('guid', None),
224
267
  "externalSourceName": attributes.get('External Source Name', {}).get('value', None),
225
268
  "effectiveTime": attributes.get('Effective Time', {}).get('value', None),
269
+ "governanceZoneFilter" : attributes.get('Governance Zone Filter', {}).get('value', None),
226
270
  "forLineage": attributes.get('For Lineage', {}).get('value', False),
227
271
  "forDuplicateProcessing": attributes.get('For Duplicate Processing', {}).get('value', False),
228
272
  "anchorGUID": attributes.get('Anchor ID', {}).get('guid', None),
@@ -245,6 +289,7 @@ def set_update_body(object_type: str, attributes: dict)->dict:
245
289
  "class" : "UpdateElementRequestBody",
246
290
  "externalSourceGUID": attributes.get('External Source GUID', {}).get('guid', None),
247
291
  "externalSourceName": attributes.get('External Source Name', {}).get('value', None),
292
+ "governanceZoneFilter": attributes.get('Governance Zone Filter', {}).get('value', None),
248
293
  "effectiveTime": attributes.get('Effective Time', {}).get('value', None),
249
294
  "forLineage": attributes.get('For Lineage', {}).get('value', False),
250
295
  "forDuplicateProcessing": attributes.get('For Duplicate Processing', {}).get('value', False),
@@ -261,7 +306,7 @@ def set_prop_body(object_type: str, qualified_name: str, attributes: dict)->dict
261
306
  "displayName": attributes['Display Name'].get('value', None),
262
307
  "qualifiedName" : qualified_name,
263
308
  "description": attributes['Description'].get('value', None),
264
- "status": attributes.get('Status', {}).get('value', "ACTIVE"),
309
+ "category": attributes.get('Category', {}).get('value', None),
265
310
  "userDefinedStatus": attributes.get('User Defined Status', {}).get('value', None),
266
311
  "versionIdentifier": attributes.get('Version Identifier', {}).get('value', None),
267
312
  "effectiveFrom": attributes.get('Effective From', {}).get('value', None),
@@ -270,26 +315,51 @@ def set_prop_body(object_type: str, qualified_name: str, attributes: dict)->dict
270
315
  "extendedProperties": attributes.get('Extended Properties', {}).get('value', None)
271
316
  }
272
317
 
318
+ def set_product_body(object_type: str, qualified_name: str, attributes: dict)->dict:
319
+ prop_bod = set_prop_body(object_type, qualified_name, attributes)
320
+ prop_bod["identifier"] = attributes.get('Identifier', {}).get('value', None)
321
+ prop_bod["productName"] = attributes.get('Product Name', {}).get('value', None)
322
+ prop_bod["maturity"] = attributes.get('Maturity', {}).get('value', None)
323
+ prop_bod["serviceLife"] = attributes.get('Service Life', {}).get('value', None)
324
+ prop_bod["introductionDate"] = attributes.get('Introduction Date', {}).get('value', [])
325
+ prop_bod["withdrawalDate"] = attributes.get('Withdrawal Date', {}).get('value', [])
326
+ prop_bod["nextVersion"] = attributes.get('Next Version Date', {}).get('value', [])
327
+
328
+ def set_update_status_body(object_type: str, attributes: dict)->dict:
329
+ return {
330
+ "class" : "UpdateStatusRequestBody",
331
+ "effectiveTime": attributes.get('Effective Time', {}).get('value', None),
332
+ "forLineage": attributes.get('For Lineage', {}).get('value', False),
333
+ "forDuplicateProcessing": attributes.get('For Duplicate Processing', {}).get('value', False),
334
+ "mergeUpdate": attributes.get('Merge Update', {}).get('value', True),
335
+ }
336
+
273
337
  def set_gov_prop_body(object_type: str, qualified_name: str, attributes: dict)->dict:
274
338
  prop_name = object_type.replace(" ", "")
275
339
  prop_bod = set_prop_body(object_type, qualified_name, attributes)
276
340
  prop_bod["domainIdentifier"] = attributes.get('Domain Identifier', {}).get('value', None)
277
- # prop_bod["documentIdentifier"] = attributes.get('Document Identifier', {}).get('value', None)
278
- prop_bod["title"]= attributes.get('Display Name', {}).get('value', None)
279
- prop_bod['documentIdentifier'] = qualified_name
341
+ prop_bod["displayName"]= attributes.get('Display Name', {}).get('value', None)
342
+ prop_bod['qualifiedName'] = qualified_name
280
343
  prop_bod["versionIdentifier"] = attributes.get('Version Identifier', {}).get('value', None)
281
344
  prop_bod["summary"] = attributes.get('Summary', {}).get('value', None)
345
+ prop_bod["description"] = attributes.get('Description', {}).get('value', None)
346
+
282
347
  prop_bod["scope"] = attributes.get('Scope', {}).get('value', None)
283
348
  prop_bod["importance"] = attributes.get('Importance', {}).get('value', None)
284
349
  prop_bod["implications"] = attributes.get('Implication', {}).get('value', [])
285
350
  prop_bod["outcomes"] = attributes.get('Outcomes', {}).get('value', [])
286
351
  prop_bod["results"] = attributes.get('Results', {}).get('value', [])
352
+ prop_bod["effectiveFrom"] = attributes.get('Effective From', {}).get('value', None),
353
+ prop_bod["effectiveTo"] = attributes.get('Effective To', {}).get('value', None),
354
+ prop_bod["additionalProperties"] = attributes.get('Additional Properties', {}).get('value', None),
355
+ prop_bod["extendedProperties"] = attributes.get('Extended Properties', {}).get('value', None)
287
356
 
288
- body = update_body_for_type(object_type, prop_bod, attributes)
357
+
358
+ body = update_gov_body_for_type(object_type, prop_bod, attributes)
289
359
  return body
290
360
 
291
361
 
292
- def update_body_for_type(object_type: str, body: dict, attributes: dict) -> dict:
362
+ def update_gov_body_for_type(object_type: str, body: dict, attributes: dict) -> dict:
293
363
  gov_def_name = object_type.replace(" ", "")
294
364
  if object_type in GENERAL_GOVERNANCE_DEFINITIONS:
295
365
  return body
@@ -298,7 +368,7 @@ def update_body_for_type(object_type: str, body: dict, attributes: dict) -> dict
298
368
  return body
299
369
 
300
370
  elif object_type == "Regulation":
301
- body['source'] = attributes.get('Source', {}).get('value', None)
371
+ body['regulationSource'] = attributes.get('Regulation Source', {}).get('value', None)
302
372
  body['regulators'] = attributes.get('Regulators', {}).get('value', [])
303
373
  return body
304
374
 
@@ -308,17 +378,24 @@ def update_body_for_type(object_type: str, body: dict, attributes: dict) -> dict
308
378
  elif object_type == "Security Group":
309
379
  body['distinguishedName'] = attributes.get('Distinguished Name', {}).get('value', None)
310
380
  return body
381
+ elif object_type == "GovernanceMetric":
382
+ body['measurement'] = attributes.get('Measurement', {}).get('value', None)
383
+ body['target'] = attributes.get('Target', {}).get('value', None)
384
+ return body
311
385
  elif object_type == "Naming Standard Rule":
312
386
  body['namePatterns'] = attributes.get('Name Patterns', {}).get('value', [])
313
387
  return body
314
- elif object_type in ["Certification Type", "License Type"]:
315
- body['details'] = attributes.get('Details', {}).get('value', None)
388
+ elif object_type in ["TermsAndConditions", "Certification Type", "License Type"]:
389
+ body['entitlements'] = attributes.get('Entitlementss', {}).get('value', {})
390
+ body['restrictions'] = attributes.get('Restrictions', {}).get('value', {})
391
+ body['obligations'] = attributes.get('Obligations', {}).get('value', {})
392
+
316
393
  return body
317
394
 
318
395
 
319
396
  def set_rel_request_body(object_type: str, attributes: dict)->dict:
320
397
  return {
321
- "class" : "RelationshipRequestBody",
398
+ "class" : "NewRelationshipRequestBody",
322
399
  "externalSourceGUID": attributes.get('External Source GUID', {}).get('guid', None),
323
400
  "externalSourceName": attributes.get('External Source Name', {}).get('value', None),
324
401
  "effectiveTime": attributes.get('Effective Time', {}).get('value', None),
@@ -338,9 +415,9 @@ def set_peer_gov_def_request_body(object_type: str, attributes: dict)->dict:
338
415
  }
339
416
  return rel_body
340
417
 
341
- def set_metadata_source_request_body(object_type: str, attributes: dict)->dict:
418
+ def set_delete_request_body(object_type: str, attributes: dict)->dict:
342
419
  return {
343
- "class": "MetadataSourceRequestBody",
420
+ "class": "DeleteRequestBody",
344
421
  "externalSourceGUID": attributes.get('External Source GUID', {}).get('guid', None),
345
422
  "externalSourceName": attributes.get('External Source Name', {}).get('value', None),
346
423
  "effectiveTime": attributes.get('Effective Time', {}).get('value', None),
@@ -371,14 +448,23 @@ def set_element_status_request_body(object_type: str, attributes: dict)->dict:
371
448
  "forLineage": attributes.get('For Lineage', {}).get('value', False),
372
449
  "forDuplicateProcessing": attributes.get('For Duplicate Processing', {}).get('value', False)
373
450
  }
374
- def set_collection_property_body(object_type: str, qualified_name:str, attributes: dict)->dict:
375
- body = set_prop_body("Collection", qualified_name,attributes)
376
- body["category"] = attributes.get('Category', {}).get('value', None)
451
+
452
+
453
+ def set_classifications(object_type: str, attributes: dict)->dict:
454
+ classifications = attributes.get('Classifications', {}).get('name_list', None)
455
+ body = None
456
+ if classifications:
457
+ body = {classification: {} for classification in classifications} if cclassifications else {}
377
458
  return body
378
459
 
379
460
  def set_collection_classifications(object_type: str, attributes: dict)->dict:
380
- collection_classifications = attributes.get('Collection Classifications', {}).get('name_list', None)
381
- if collection_classifications is None:
382
- collection_classifications = object_type
383
- body = {classification: {} for classification in collection_classifications} if collection_classifications else {}
384
- return body
461
+ classifications = attributes.get('Classifications', {}).get('name_list', None)
462
+
463
+ if object_type in ["Folder", "Root"]:
464
+ if object_type not in classifications:
465
+ classifications.append(object_type)
466
+
467
+ body = None
468
+ if classifications:
469
+ body = {classification: {} for classification in classifications} if cclassifications else {}
470
+ return body
@@ -51,9 +51,10 @@ TERM_RELATIONSHPS = ["Synonym", "Translation", "PreferredTerm", "TermISATYPEOFRe
51
51
  "ISARelationship"]
52
52
 
53
53
  # List of supported md_commands
54
- GOV_LINK_LIST = [ "Link Governance Drivers", "Detach Governance Drivers",
55
- "Link Governance Policies", "Detach Governance Policies",
56
- "Link Governance Controls", "Detach Governance Controls",]
54
+ GOV_LINK_LIST = [ "Link Governance Drivers", "Link Drivers", "Detach Governance Drivers", "Detach Drivers",
55
+ "Link Governance Policies", "Link Policies", "Detach Governance Policies", "Detach Policies",
56
+ "Link Governance Controls", "Link Controls", "Detach Governance Controls", "Detach Controls",
57
+ ]
57
58
 
58
59
  GOV_COM_LIST = [ "Create Business Imperative", "Update Business Imperative",
59
60
  "Create Regulation Article Definition", "Update Regulation Article Definition",
@@ -131,8 +132,8 @@ command_list = ["Provenance", "Create Glossary", "Update Glossary", "Create Term
131
132
  "Create Digital Product", "Create Data Product", "Update Digital Product", "Update Data Product",
132
133
  "Create Agreement", "Update Agreement",
133
134
  "Link Digital Products", "Link Data Products", "Detach Digital Products", "Detach Data Products",
134
- # "Create Data Sharing Agreement", "Update Data Sharing Agreement",
135
- "Create Digital Subscription", "Create Product Subscription", "Update Digital Subscription", "Update Product Subscription",
135
+ "Create Data Sharing Agreement", "Update Data Sharing Agreement",
136
+ # "Create Digital Subscription", "Create Product Subscription", "Update Digital Subscription", "Update Product Subscription",
136
137
  "Attach Agreement Items", "Detach Agreement Items",
137
138
  "Attach Contract", "Detach Contract",
138
139
  "Attach Subscriber", "Detach Subscriber",
@@ -173,6 +174,8 @@ command_list.extend(GOV_COM_LIST)
173
174
  command_list.extend(GOV_LINK_LIST)
174
175
  command_list.extend(COLLECTIONS_LIST)
175
176
  command_list.extend(SIMPLE_COLLECTIONS)
177
+ command_list.extend(["Link Governance Response", "Detach Governance Response",
178
+ "Link Governance Mechanism", "Detach Governance Mechanism"])
176
179
 
177
180
  pre_command = "\n---\n==> Processing object_action:"
178
181
  command_seperator = Markdown("\n---\n")
pyegeria/__init__.py CHANGED
@@ -30,7 +30,7 @@ from ._client_new import Client2
30
30
  from ._exceptions_new import (PyegeriaInvalidParameterException,PyegeriaAPIException, PyegeriaException,
31
31
  PyegeriaUnauthorizedException, PyegeriaClientException, PyegeriaUnknownException,
32
32
  PyegeriaConnectionException, PyegeriaNotFoundException,
33
- print_exception_table, print_basic_exception)
33
+ print_exception_table, print_basic_exception, print_validation_error)
34
34
  from .load_config import load_app_config, get_app_config
35
35
  from .logging_configuration import config_logging, console_log_filter, init_logging
36
36
  from ._exceptions import (InvalidParameterException, PropertyServerException, UserNotAuthorizedException,
pyegeria/_client_new.py CHANGED
@@ -476,7 +476,8 @@ class Client2:
476
476
  except (HTTPStatusError, httpx.HTTPStatusError, httpx.RequestError) as e:
477
477
  # context["caught_exception"] = e
478
478
  # context['HTTPStatusCode'] = e.response.status_code
479
- additional_info = {"userid": self.user_id}
479
+ additional_info = {"userid": self.user_id, "reason": response.text}
480
+
480
481
  raise PyegeriaClientException(response, context, additional_info, e)
481
482
  #
482
483
  # except json.JSONDecodeError as e:
@@ -820,7 +821,7 @@ class Client2:
820
821
  "class": "UpdateStatusRequestBody",
821
822
  "status": status
822
823
  }
823
- validated_body = UpdateStatusRequestBody.validate_python(body)
824
+ validated_body = UpdateStatusRequestBody.model_validate(body)
824
825
  else:
825
826
  raise PyegeriaInvalidParameterException(additional_info={"reason": "invalid parameters"})
826
827
 
@@ -374,6 +374,11 @@ def print_exception_table(e: PyegeriaException):
374
374
  def print_basic_exception(e: PyegeriaException):
375
375
  """Prints the exception response"""
376
376
  related_code = e.related_http_code if hasattr(e, "related_http_code") else ""
377
+ http_reason = e.response.text if e.response else ""
378
+ table = Table(title=f"Exception: {e.__class__.__name__}", show_lines=True, header_style="bold", box=box.HEAVY_HEAD)
379
+ table.caption = e.pyegeria_code
380
+ table.add_column("Facet", justify="center")
381
+ table.add_column("Item", justify="left", width=80)
377
382
  related_response = e.response.json() if e.response else ""
378
383
  table = Table(title=f"Exception: {e.__class__.__name__}", show_lines=True, header_style="bold", box=box.HEAVY_HEAD)
379
384
  table.caption = e.pyegeria_code
@@ -382,6 +387,7 @@ def print_basic_exception(e: PyegeriaException):
382
387
 
383
388
  if isinstance(e, PyegeriaException):
384
389
  table.add_row("HTTP Code", str(e.response_code))
390
+ table.add_row("HTTP Reason", str(http_reason))
385
391
  table.add_row("Egeria Code", str(related_code))
386
392
  table.add_row("Caller Method", e.context.get("caller method", "---")) if e.context else ""
387
393
  table.add_row("Request URL", str(e.response_url))
@@ -163,7 +163,11 @@ GOVERNANCE_DEFINITIONS_COLUMNS = COMMON_COLUMNS + [
163
163
  Column(name="Scope", key='scope'),
164
164
  Column(name="Type", key='type_name'),
165
165
  ]
166
-
166
+ GOVERNANCE_DEFINITIONS_BASIC = [
167
+ Column(name="Type", key='type_name'),
168
+ Column(name='Qualified Name', key='qualified_name', format=True),
169
+ Column(name="GUID", key='guid', format=True),
170
+ ]
167
171
  COMMON_ANNOTATIONS = {
168
172
  "wikilinks": ["[[Commons]]"]
169
173
  }
@@ -406,7 +410,18 @@ output_format_sets = FormatSetDict({
406
410
  spec_params={"output_format":"DICT"},
407
411
  )
408
412
  ),
409
-
413
+ "Governance Basics": FormatSet(
414
+ heading="Basic Governance-Definitions Information",
415
+ description="Core Attributes useful to Governance-Definitions.",
416
+ aliases=["BasicGovernance"],
417
+ annotations={"wikilinks": ["[[Governance]]"]},
418
+ formats=[Format(types=["ALL"], columns=GOVERNANCE_DEFINITIONS_BASIC)],
419
+ action=ActionParameter(
420
+ function="GovernanceOfficer.find_governance_definitions",
421
+ user_params=["search_string"],
422
+ spec_params={},
423
+ )
424
+ ),
410
425
  "Governance Definitions": FormatSet(
411
426
  heading="Governance-Definitions Information",
412
427
  description="Attributes useful to Governance-Definitions.",
@@ -726,8 +726,6 @@ class GovernanceOfficer(Client2):
726
726
 
727
727
  @dynamic_catch
728
728
  async def _async_update_governance_definition(self, guid: str, body: dict | UpdateElementRequestBody) -> None:
729
- """ Update a governance definition."""
730
-
731
729
  """ Update the properties of a governance definition. Async Version.
732
730
 
733
731
  Parameters
@@ -793,7 +791,7 @@ class GovernanceOfficer(Client2):
793
791
  await self._async_update_element_body_request(url, GOV_DEF_PROPERTIES_LIST, body)
794
792
 
795
793
  @dynamic_catch
796
- def _async_update_governance_definition(self, guid: str, body: dict | UpdateElementRequestBody) -> None:
794
+ async def _async_update_governance_definition(self, guid: str, body: dict | UpdateElementRequestBody) -> None:
797
795
  """ Update a governance definition. Async Version."""
798
796
 
799
797
  def update_governance_definition(self, guid: str, body: dict | UpdateStatusRequestBody) -> None:
pyegeria/load_config.py CHANGED
@@ -381,7 +381,7 @@ def load_app_config(env_file: str = None):
381
381
 
382
382
  # Logging section
383
383
  log = config_dict["Logging"]
384
- log["console_filter_levels"] = _parse_list_env("PYEGERIA_CONSOLE_FILTER_LEVELS", log.get("console_filter_levels", ["ERROR"]))
384
+ log["console_filter_levels"] = _parse_list_env("PYEGERIA_CONSOLE_FILTER_LEVELS", log.get("console_filter_levels", ["ERROR","WARNING","INFO","SUCCESS"]))
385
385
  log["console_logging_enabled"] = _parse_list_env("PYEGERIA_CONSOLE_LOGGING_ENABLED", log.get("console_logging_enabled", ["pyegeria"]))
386
386
  log["console_logging_level"] = os.getenv("PYEGERIA_CONSOLE_LOG_LVL", log.get("console_logging_level", "INFO"))
387
387
  log["enable_logging"] = _parse_bool_env("PYEGERIA_ENABLE_LOGGING", log.get("enable_logging", False))
pyegeria/models.py CHANGED
@@ -177,6 +177,33 @@ class OpenMetadataRootProperties(PyegeriaModel):
177
177
  type_name: str | None = None
178
178
  extended_properties: dict | None = None
179
179
 
180
+ @model_validator(mode='before')
181
+ @classmethod
182
+ def preprocess_data(cls, values):
183
+ """
184
+ This model validator performs pre-processing on the entire
185
+ input dictionary before field validation.
186
+ It converts any empty tuples to None and ensures a metadata key exists.
187
+
188
+ This is the modern equivalent of @root_validator(pre=True).
189
+ """
190
+ # Ensure the 'data' key exists before trying to access it.
191
+ if 'data' in values and isinstance(values['data'], dict):
192
+ # Convert empty tuples to None
193
+ processed_data = {}
194
+ for key, value in values['data'].items():
195
+ if isinstance(value, tuple) and not value:
196
+ processed_data[key] = None
197
+ else:
198
+ processed_data[key] = value
199
+ values['data'] = processed_data
200
+
201
+ # Ensure a 'metadata' key is always a dictionary.
202
+ if 'metadata' not in values:
203
+ values['metadata'] = {}
204
+
205
+ return values
206
+
180
207
 
181
208
  class RelationshipBeanProperties(PyegeriaModel):
182
209
  effective_from: datetime | None = None
@@ -311,7 +338,7 @@ class UpdateStatusRequestBody(PyegeriaModel):
311
338
 
312
339
  class GetRequestBody(PyegeriaModel):
313
340
  class_: Annotated[Literal["GetRequestBody"], Field(alias="class")]
314
- # metadata_element_type_name: list[str] | None = None
341
+ metadata_element_type_name: str | None = None
315
342
  metadata_element_subtype_names: list[str] | None = None
316
343
  skip_relationships: list[str] | None = None
317
344
  include_only_relationships: list[str] | None = None
pyegeria/utils.py CHANGED
@@ -140,7 +140,7 @@ def body_slimmer(body: dict) -> dict:
140
140
 
141
141
  slimmed = {}
142
142
  for key, value in body.items():
143
- if value:
143
+ if value and not isinstance(value, tuple):
144
144
  if isinstance(value, dict):
145
145
  # Recursively slim embedded dictionaries
146
146
  slimmed_value = body_slimmer(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyegeria
3
- Version: 5.4.0.23
3
+ Version: 5.4.0.24
4
4
  Summary: A python client for Egeria
5
5
  License: Apache 2.0
6
6
  Keywords: egeria,metadata,governance