pyegeria 5.4.0.24__py3-none-any.whl → 5.4.0.25__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 (42) hide show
  1. commands/cat/debug_log +7373 -1452
  2. commands/cat/debug_log.2025-08-17_11-34-27_981852.zip +0 -0
  3. commands/cat/dr_egeria_md.py +21 -4
  4. commands/cat/logs/pyegeria.log +4 -0
  5. md_processing/.DS_Store +0 -0
  6. md_processing/__init__.py +7 -3
  7. md_processing/data/commands.json +1683 -2801
  8. md_processing/dr_egeria_inbox/product.md +69 -20
  9. md_processing/dr_egeria_outbox/.obsidian/workspace.json +5 -5
  10. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:05-product.md +426 -0
  11. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:56-product.md +212 -0
  12. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 09:43-product.md +201 -0
  13. md_processing/dr_egeria_outbox/tuesday/processed-2025-08-19 10:55-product.md +209 -0
  14. md_processing/md_commands/governance_officer_commands.py +1 -73
  15. md_processing/md_commands/product_manager_commands.py +453 -211
  16. md_processing/md_processing_utils/common_md_proc_utils.py +60 -5
  17. md_processing/md_processing_utils/common_md_utils.py +21 -9
  18. md_processing/md_processing_utils/extraction_utils.py +2 -2
  19. md_processing/md_processing_utils/md_processing_constants.py +8 -7
  20. pyegeria/.DS_Store +0 -0
  21. pyegeria/_client_new.py +58 -10
  22. pyegeria/_output_formats.py +25 -0
  23. pyegeria/collection_manager.py +79 -14
  24. pyegeria/{data_designer_omvs.py → data_designer.py} +1171 -1675
  25. pyegeria/glossary_browser.py +1259 -0
  26. pyegeria/{glossary_manager_omvs.py → glossary_manager.py} +1181 -1099
  27. pyegeria/models.py +9 -3
  28. pyegeria/output_formatter.py +2 -1
  29. pyegeria/project_manager.py +1952 -0
  30. pyegeria/utils.py +4 -1
  31. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.25.dist-info}/METADATA +1 -1
  32. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.25.dist-info}/RECORD +35 -34
  33. md_processing/dr_egeria_outbox/monday/processed-2025-07-14 12:38-data_designer_out.md +0 -663
  34. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 10:52-generated_help_report.md +0 -2744
  35. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 18:38-collections.md +0 -62
  36. md_processing/dr_egeria_outbox/monday/processed-2025-08-01 11:34-gov_def.md +0 -444
  37. md_processing/dr_egeria_outbox/monday/processed-2025-08-17 21:04-product.md +0 -97
  38. pyegeria/glossary_browser_omvs.py +0 -3840
  39. pyegeria/governance_officer_omvs.py +0 -2367
  40. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.25.dist-info}/LICENSE +0 -0
  41. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.25.dist-info}/WHEEL +0 -0
  42. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.25.dist-info}/entry_points.txt +0 -0
@@ -129,8 +129,12 @@ def parse_upsert_command(egeria_client: EgeriaTech, object_type: str, object_act
129
129
  default_value = attr[key].get('default_value', None)
130
130
 
131
131
  style = attr[key]['style']
132
- if style in ['Simple', 'Dictionary', 'Comment']:
132
+ if style in ['Simple', 'Comment']:
133
133
  parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
134
+ elif style == 'Dictionary':
135
+ parsed_attributes[key] = proc_dictionary_attribute(txt, object_action, labels, if_missing, default_value)
136
+ parsed_attributes[key]['name_list'] = json.dumps(parsed_attributes[key]['value'], indent=2)
137
+
134
138
  elif style == 'Valid Value':
135
139
  parsed_attributes[key] = proc_valid_value(txt, object_action, labels,
136
140
  attr[key].get('valid_values', None), if_missing,
@@ -330,8 +334,12 @@ def parse_view_command(egeria_client: EgeriaTech, object_type: str, object_actio
330
334
  default_value = attr[key].get('default_value', None)
331
335
 
332
336
  style = attr[key]['style']
333
- if style in ['Simple', 'Dictionary', 'Comment']:
337
+ if style in ['Simple', 'Comment']:
334
338
  parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
339
+ elif style == 'Dictionary':
340
+ parsed_attributes[key] = proc_dictionary_attribute(txt, object_action, labels, if_missing,
341
+ default_value)
342
+ parsed_attributes[key]['name_list'] = json.dumps(parsed_attributes[key]['value'], indent=2)
335
343
  elif style == 'Valid Value':
336
344
  parsed_attributes[key] = proc_valid_value(txt, object_action, labels,
337
345
  attr[key].get('valid_values', None), if_missing,
@@ -406,7 +414,7 @@ def parse_view_command(egeria_client: EgeriaTech, object_type: str, object_actio
406
414
  value = parsed_attributes[key].get('value', None)
407
415
 
408
416
  if value is not None:
409
- # if the value is a dict, get the flattened name list
417
+ # if the value is a dict or list, get the stringifiedt
410
418
  value = parsed_attributes[key].get('name_list', None) if isinstance(value, (dict, list)) else value
411
419
  parsed_output['display'] += f"\n\t* {key}: `{value}`\n\t"
412
420
 
@@ -469,6 +477,50 @@ def proc_simple_attribute(txt: str, action: str, labels: set, if_missing: str =
469
477
 
470
478
  return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
471
479
 
480
+ @logger.catch
481
+ def proc_dictionary_attribute(txt: str, action: str, labels: set, if_missing: str = INFO, default_value=None,
482
+ simp_type: str = None) -> dict:
483
+ """Process a dictionary attribute based on the provided labels and if_missing value.
484
+ Extract the attribute value from the text and store it in a dictionary along with valid.
485
+ If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
486
+
487
+ Parameters:
488
+ ----------
489
+ txt: str
490
+ The block of object_action text to extract attributes from.
491
+ labels: list
492
+ The possible attribute labels to search for. The first label will be used in messages.
493
+ if_missing: str, default is INFO
494
+ Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
495
+ default_value: default is None
496
+ The default value to return if the attribute is missing.
497
+ """
498
+ valid = True
499
+
500
+ if if_missing not in ["WARNING", "ERROR", "INFO"]:
501
+ msg = "Invalid severity for missing attribute"
502
+ logger.error(msg)
503
+ return {"status": ERROR, "reason": msg, "value": None, "valid": False}
504
+
505
+ if default_value == "":
506
+ default_value = None
507
+
508
+ attr = extract_attribute(txt, labels)
509
+ attribute = json.loads(attr) if attr is not None else default_value
510
+
511
+
512
+ if attribute is None:
513
+ if if_missing == INFO or if_missing == WARNING:
514
+ msg = f"Optional attribute with labels: `{labels}` missing"
515
+ valid = True
516
+ logger.info(msg)
517
+ else:
518
+ msg = f"Missing attribute with labels `{labels}` "
519
+ valid = False
520
+ logger.error(msg)
521
+ return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
522
+
523
+ return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
472
524
 
473
525
  @logger.catch
474
526
  def proc_valid_value(txt: str, action: str, labels: set, valid_values: [], if_missing: str = INFO,
@@ -529,8 +581,11 @@ def proc_valid_value(txt: str, action: str, labels: set, valid_values: [], if_mi
529
581
  logger.error(msg)
530
582
  return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
531
583
  else:
584
+ # Todo: look at moving validation into pydantic or another style...
585
+ if "Status" in labels:
586
+ attribute = attribute.upper()
532
587
  if attribute not in v_values:
533
- msg = f"Invalid value for attribute `{labels}`"
588
+ msg = f"Invalid value for attribute `{labels}` attribute is `{attribute}`"
534
589
  logger.warning(msg)
535
590
  return {"status": WARNING, "reason": msg, "value": attribute, "valid": False, "exists": True}
536
591
 
@@ -656,7 +711,7 @@ def proc_el_id(egeria_client: EgeriaTech, element_type: str, qn_prefix: str, ele
656
711
  logger.error(msg)
657
712
  identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": False, }
658
713
 
659
- elif action in ["Update", "View"] and exists:
714
+ elif action in ["Update", "View", "Link", "Detach"] and exists:
660
715
  msg = f"Element {element_name} exists"
661
716
  logger.info(msg)
662
717
  identifier_output = {
@@ -307,12 +307,15 @@ def set_prop_body(object_type: str, qualified_name: str, attributes: dict)->dict
307
307
  "qualifiedName" : qualified_name,
308
308
  "description": attributes['Description'].get('value', None),
309
309
  "category": attributes.get('Category', {}).get('value', None),
310
+ "identifier": attributes.get('Identifier', {}).get('value', None),
310
311
  "userDefinedStatus": attributes.get('User Defined Status', {}).get('value', None),
311
312
  "versionIdentifier": attributes.get('Version Identifier', {}).get('value', None),
312
313
  "effectiveFrom": attributes.get('Effective From', {}).get('value', None),
313
314
  "effectiveTo": attributes.get('Effective To', {}).get('value', None),
314
315
  "additionalProperties": attributes.get('Additional Properties', {}).get('value', None),
315
- "extendedProperties": attributes.get('Extended Properties', {}).get('value', None)
316
+ "extendedProperties": attributes.get('Extended Properties', {}).get('value', None),
317
+ "supportLevel": attributes.get('Support Level', {}).get('value', None),
318
+ "serviceLevels": attributes.get('Service Levels', {}).get('value', None),
316
319
  }
317
320
 
318
321
  def set_product_body(object_type: str, qualified_name: str, attributes: dict)->dict:
@@ -324,6 +327,9 @@ def set_product_body(object_type: str, qualified_name: str, attributes: dict)->d
324
327
  prop_bod["introductionDate"] = attributes.get('Introduction Date', {}).get('value', [])
325
328
  prop_bod["withdrawalDate"] = attributes.get('Withdrawal Date', {}).get('value', [])
326
329
  prop_bod["nextVersion"] = attributes.get('Next Version Date', {}).get('value', [])
330
+ return prop_bod
331
+
332
+
327
333
 
328
334
  def set_update_status_body(object_type: str, attributes: dict)->dict:
329
335
  return {
@@ -425,6 +431,8 @@ def set_delete_request_body(object_type: str, attributes: dict)->dict:
425
431
  "forDuplicateProcessing": attributes.get('For Duplicate Processing', {}).get('value', False)
426
432
  }
427
433
 
434
+
435
+
428
436
  def set_filter_request_body(object_type: str, attributes: dict)->dict:
429
437
  return {
430
438
  "class": "FilterRequestBody",
@@ -457,14 +465,18 @@ def set_classifications(object_type: str, attributes: dict)->dict:
457
465
  body = {classification: {} for classification in classifications} if cclassifications else {}
458
466
  return body
459
467
 
460
- def set_collection_classifications(object_type: str, attributes: dict)->dict:
468
+ def set_collection_classifications(object_type: str, attributes: dict, obj_types: list[str])->dict:
461
469
  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
470
+ obj = object_type.replace(" ", "")
471
+ if object_type in obj_types:
472
+ if classifications:
473
+ if object_type not in classifications:
474
+ classifications.append(obj)
475
+ else:
476
+ classifications = [obj]
477
+
478
+ body = {}
468
479
  if classifications:
469
- body = {classification: {} for classification in classifications} if cclassifications else {}
480
+ for classification in classifications:
481
+ body[classification] = {"class" : f"{classification}Properties"}
470
482
  return body
@@ -91,7 +91,7 @@ def extract_attribute(text: str, labels: set) -> str | None:
91
91
  # Iterate over the list of labels
92
92
  for label in labels:
93
93
  # Construct pattern for the current label
94
- # text = re.sub(r'\s+', ' ', text).strip()
94
+ # text = re.sub(r'\s+', ' ', text).strip() # just added
95
95
  text = re.sub(r'\n\n+', '\n\n', text).strip()
96
96
 
97
97
  label = label.strip()
@@ -395,7 +395,7 @@ def get_element_by_name(egeria_client, element_type: str, element_name: str) ->
395
395
  return q_name, guid, unique, exists
396
396
 
397
397
  else: # Missing guid from element_dictionary
398
- guid = egeria_client.get_element_guid_by_unique_name(element_name)
398
+ guid = egeria_client.__get_guid__(qualified_name=q_name)
399
399
  if guid == NO_ELEMENTS_FOUND:
400
400
  guid = None
401
401
  exists = False
@@ -131,20 +131,21 @@ command_list = ["Provenance", "Create Glossary", "Update Glossary", "Create Term
131
131
  "View Dataa Classes", "View Data Class", "Create Data Class", "Update Data Class",
132
132
  "Create Digital Product", "Create Data Product", "Update Digital Product", "Update Data Product",
133
133
  "Create Agreement", "Update Agreement",
134
- "Link Digital Products", "Link Data Products", "Detach Digital Products", "Detach Data Products",
134
+ "Link Digital Products", "Link Product-Product", "Detach Digital Products", "Detach Product-Product",
135
135
  "Create Data Sharing Agreement", "Update Data Sharing Agreement",
136
- # "Create Digital Subscription", "Create Product Subscription", "Update Digital Subscription", "Update Product Subscription",
137
- "Attach Agreement Items", "Detach Agreement Items",
136
+ "Create Digital Subscription", "Create Product Subscription", "Update Digital Subscription", "Update Product Subscription",
137
+ "Link Agreement->Item", "Detach Agreement->Item",
138
138
  "Attach Contract", "Detach Contract",
139
- "Attach Subscriber", "Detach Subscriber",
140
- "Link Collection to Resource", "Attach Collection to Resource",
141
- "Unlink Collection From Resource", "Detach Collection From Resource",
139
+ "Link Subscriber->Subscription", "Detach Subscriber->Subscription",
140
+ "Link Collection->Resource", "Attach Collection->Resource",
141
+ "Unlink Collection->Resource", "Detach Collection->Resource",
142
142
  "Add Member to Collection", "Add Member", "Member->Collection",
143
- "Remove Member from Collection","Remove Member from Collection",
143
+ "Remove Member from Collection","Remove Member->Collection",
144
144
  "View Governance Definitions", "View Gov Definitions",
145
145
  "List Governance Definitions", "List Gov Definitions",
146
146
  "View Governance Definition Context","List Governance Definition Context",
147
147
  "View Governance Def Context", "List Governance Def Context",
148
+ "View Report",
148
149
  # "Create Business Imperative", "Update Business Imperative",
149
150
  # "Create Regulation Article Definition", "Update Regulation Article Definition",
150
151
  # "Create Threat Definition", "Update Threat Definition",
pyegeria/.DS_Store CHANGED
Binary file
pyegeria/_client_new.py CHANGED
@@ -35,7 +35,7 @@ from pyegeria._validators import (
35
35
  )
36
36
  from pyegeria.models import SearchStringRequestBody, FilterRequestBody, GetRequestBody, NewElementRequestBody, \
37
37
  TemplateRequestBody, UpdateStatusRequestBody, UpdateElementRequestBody, NewRelationshipRequestBody, \
38
- DeleteRequestBody, UpdateRelationshipRequestBody, ResultsRequestBody
38
+ DeleteRequestBody, UpdateRelationshipRequestBody, ResultsRequestBody, NewClassificationRequestBody
39
39
  from pyegeria.utils import body_slimmer
40
40
 
41
41
  ...
@@ -146,6 +146,7 @@ class Client2:
146
146
  self._update_element_request_adapter = TypeAdapter(UpdateElementRequestBody)
147
147
  self._update_status_request_adapter = TypeAdapter(UpdateStatusRequestBody)
148
148
  self._new_relationship_request_adapter = TypeAdapter(NewRelationshipRequestBody)
149
+ self._new_classification_request_adapter = TypeAdapter(NewClassificationRequestBody)
149
150
  self._delete_request_adapter = TypeAdapter(DeleteRequestBody)
150
151
  self._template_request_adapter = TypeAdapter(TemplateRequestBody)
151
152
  self._update_relationship_request_adapter = TypeAdapter(UpdateRelationshipRequestBody)
@@ -755,9 +756,21 @@ class Client2:
755
756
  return None
756
757
  return validated_body
757
758
 
759
+ def validate_new_element_from_template_request(self, body: dict | TemplateRequestBody
760
+ ) -> NewElementRequestBody | None:
761
+ if isinstance(body, TemplateRequestBody):
762
+ validated_body = body
763
+
764
+ elif isinstance(body, dict):
765
+ # if body.get("properties", {}).get("class", "") == prop:
766
+ validated_body = self._template_request_adapter.validate_python(body)
767
+ else:
768
+ return None
769
+ return validated_body
770
+
758
771
  def validate_new_relationship_request(self, body: dict | NewRelationshipRequestBody,
759
772
  prop: str = None) -> NewRelationshipRequestBody | None:
760
- if isinstance(body, NewElementRequestBody):
773
+ if isinstance(body, NewRelationshipRequestBody):
761
774
  if (prop and body.properties.class_ == prop) or (prop is None):
762
775
  validated_body = body
763
776
  else:
@@ -775,6 +788,26 @@ class Client2:
775
788
 
776
789
  return validated_body
777
790
 
791
+ def validate_new_classification_request(self, body: dict | NewClassificationRequestBody,
792
+ prop: str = None) -> NewClassificationRequestBody | None:
793
+ if isinstance(body, NewClassificationRequestBody):
794
+ if (prop and body.properties.class_ == prop) or (prop is None):
795
+ validated_body = body
796
+ else:
797
+ raise PyegeriaInvalidParameterException(additional_info=
798
+ {"reason": "unexpected property class name"})
799
+
800
+ elif isinstance(body, dict):
801
+ if body.get("properties", {}).get("class", "") == prop:
802
+ validated_body = self._new_classification_request_adapter.validate_python(body)
803
+ else:
804
+ raise PyegeriaInvalidParameterException(additional_info=
805
+ {"reason": "unexpected property class name"})
806
+ else:
807
+ return None
808
+
809
+ return validated_body
810
+
778
811
  def validate_delete_request(self, body: dict | DeleteRequestBody,
779
812
  cascade_delete: bool = False) -> DeleteRequestBody | None:
780
813
  if isinstance(body, DeleteRequestBody):
@@ -819,7 +852,7 @@ class Client2:
819
852
  elif status:
820
853
  body = {
821
854
  "class": "UpdateStatusRequestBody",
822
- "status": status
855
+ "newStatus": status
823
856
  }
824
857
  validated_body = UpdateStatusRequestBody.model_validate(body)
825
858
  else:
@@ -939,11 +972,11 @@ class Client2:
939
972
  else:
940
973
  body = {
941
974
  "class": "GetRequestBody",
942
-
975
+ "metadataElementTypeName": _type
943
976
  }
944
977
  validated_body = GetRequestBody.model_validate(body)
945
978
 
946
- json_body = validated_body.model_dump_json(indent=2)
979
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
947
980
 
948
981
  response = await self._async_make_request("POST", url, json_body)
949
982
  elements = response.json().get("element", NO_ELEMENTS_FOUND)
@@ -972,7 +1005,7 @@ class Client2:
972
1005
  }
973
1006
  validated_body = ResultsRequestBody.model_validate(body)
974
1007
 
975
- json_body = validated_body.model_dump_json(indent=2)
1008
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
976
1009
 
977
1010
  response = await self._async_make_request("POST", url, json_body)
978
1011
  elements = response.json().get("elements", None)
@@ -996,7 +1029,15 @@ class Client2:
996
1029
  logger.info(json_body)
997
1030
  response = await self._async_make_request("POST", url, json_body)
998
1031
  logger.info(response.json())
999
- return response.json().get("guid")
1032
+ return response.json().get("guid", "NO_GUID_RETURNED")
1033
+
1034
+ async def _async_create_element_from_template(self, url: str, body: dict | TemplateRequestBody = None) -> str:
1035
+ validated_body = self.validate_new_element_from_template_request(body)
1036
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
1037
+ logger.info(json_body)
1038
+ response = await self._async_make_request("POST", url, json_body, is_json=True)
1039
+ logger.info(response.json())
1040
+ return response.json().get("guid", "NO_GUID_RETURNED")
1000
1041
 
1001
1042
  async def _async_update_element_body_request(self, url: str, prop: list[str],
1002
1043
  body: dict | UpdateElementRequestBody = None) -> None:
@@ -1024,6 +1065,16 @@ class Client2:
1024
1065
  else:
1025
1066
  await self._async_make_request("POST", url)
1026
1067
 
1068
+ async def _async_new_classification_request(self, url: str, prop: str,
1069
+ body: dict | NewRelationshipRequestBody = None) -> None:
1070
+ validated_body = self.validate_new_classification_request(body, prop)
1071
+ if validated_body:
1072
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
1073
+ logger.info(json_body)
1074
+ await self._async_make_request("POST", url, json_body)
1075
+ else:
1076
+ await self._async_make_request("POST", url)
1077
+
1027
1078
  async def _async_delete_request(self, url: str, body: dict | DeleteRequestBody = None,
1028
1079
  cascade_delete: bool = False) -> None:
1029
1080
  validated_body = self.validate_delete_request(body, cascade_delete)
@@ -1035,9 +1086,6 @@ class Client2:
1035
1086
  await self._async_make_request("POST", url)
1036
1087
 
1037
1088
 
1038
-
1039
-
1040
-
1041
1089
  async def _async_update_relationship_request(self, url: str, prop: str,
1042
1090
  body: dict | UpdateRelationshipRequestBody = None) -> None:
1043
1091
  validated_body = self.validate_update_relationship_request(body, prop)
@@ -143,6 +143,14 @@ COLLECTION_DICT = Format(
143
143
  ],
144
144
  )
145
145
 
146
+ BASIC_COLLECTIONS_COLUMNS = [
147
+ Column(name='Qualified Name', key='qualified_name', format=True),
148
+ Column(name='GUID', key='guid', format=True),
149
+ Column(name='Type Name', key='type_name'),
150
+ Column(name="Containing Members", key='collection_members'),
151
+ Column(name="Member Of", key='member_of_collections')
152
+ ]
153
+
146
154
  COLLECTION_REPORT = Format(
147
155
  types=["REPORT"],
148
156
  columns=COLLECTIONS_MEMBERS_COLUMNS + [
@@ -239,6 +247,8 @@ output_format_sets = FormatSetDict({
239
247
  Column(name="Effective To", key='effective_to'),
240
248
  Column(name="GUID", key='guid'),
241
249
  Column(name="Open Metadata Type Name", key='type_name'),
250
+ Column(name="Glossary", key='parent_glossary'),
251
+ Column(name="Subject Aream", key='subject_area'),
242
252
  ],
243
253
  )
244
254
  ]
@@ -257,6 +267,21 @@ output_format_sets = FormatSetDict({
257
267
  spec_params={},
258
268
  )
259
269
  ),
270
+ "BasicCollections": FormatSet(
271
+ heading="Common Collection Information",
272
+ description="Attributes generic to all Collections.",
273
+ aliases=[],
274
+ annotations=COMMON_ANNOTATIONS,
275
+ formats=[Format(
276
+ types=["ALL"],
277
+ columns=BASIC_COLLECTIONS_COLUMNS,
278
+ )], # Reusing common formats
279
+ action=ActionParameter(
280
+ function="CollectionManager.find_collections",
281
+ user_params=["search_string"],
282
+ spec_params={},
283
+ )
284
+ ),
260
285
 
261
286
  "CollectionMembers": FormatSet(
262
287
  heading="Collection Membership Information",
@@ -672,7 +672,7 @@ class CollectionManager(Client2):
672
672
  ----
673
673
  Body sample:
674
674
  {
675
- "class": "AnyTimeRequestBody",
675
+ "class": "GetRequestBody",
676
676
  "asOfTime": "{{$isoTimestamp}}",
677
677
  "effectiveTime": "{{$isoTimestamp}}",
678
678
  "forLineage": false,
@@ -680,7 +680,7 @@ class CollectionManager(Client2):
680
680
  }
681
681
  """
682
682
 
683
- url = str(HttpUrl(f"{self.collection_command_root}/{collection_guid}"))
683
+ url = str(HttpUrl(f"{self.collection_command_root}/{collection_guid}/retrieve"))
684
684
  type = element_type if element_type else "Collection"
685
685
 
686
686
  response = await self._async_get_guid_request(url, _type=type,
@@ -1414,6 +1414,26 @@ class CollectionManager(Client2):
1414
1414
  self._async_create_collection(display_name, description, category,
1415
1415
  ["ContextEvent"], body))
1416
1416
 
1417
+ @dynamic_catch
1418
+ def create_glossary_category(self, display_name: str, parent_guid: str, description: str = None ) -> str:
1419
+ """Create a new glossary category."""
1420
+ body = {
1421
+ "class": "NewRelationshipRequestBody",
1422
+ "parentGUID": parent_guid,
1423
+ "parentRelationshipTypeName": "CategoryHierarchy",
1424
+ "parentAtEnd1": True,
1425
+ "is_own_anchor": False,
1426
+ "anchor_guid": parent_guid,
1427
+ "properties": {
1428
+ "class": "GlossaryCategoryProperties",
1429
+ "displayName": display_name,
1430
+ "description": description,
1431
+ "parentCategory": parent_guid,
1432
+ },
1433
+ }
1434
+ response = self.create_collection(body=body)
1435
+ return response
1436
+
1417
1437
  @dynamic_catch
1418
1438
  async def _async_create_data_spec_collection(self, display_name: str = None, description: str = None,
1419
1439
  category: str = None, classification_name: str = None,
@@ -1520,15 +1540,16 @@ class CollectionManager(Client2):
1520
1540
  }
1521
1541
 
1522
1542
  """
1523
- validated_body = self.validate_new_element_request(body,"DataSpecProperties")
1524
-
1525
- if validated_body is None and display_name is not None:
1543
+ if body:
1544
+ validated_body = self.validate_new_element_request(body,"DataSpecProperties")
1545
+ elif display_name is not None:
1526
1546
  qualified_name = self.__create_qualified_name__("DataSpec", display_name, EGERIA_LOCAL_QUALIFIER)
1527
- print(f"\n\tDisplayName was {display_name}, classification {classification_name}\n")
1528
- initial_classifications_data = {"class" : "ClassificationProperties"}
1547
+ logger.info(f"\n\tDisplayName was {display_name}, classification {classification_name}\n")
1529
1548
  if classification_name:
1530
- initial_classification_dict = {
1531
- classification_name: InitialClassifications.model_validate(initial_classifications_data)
1549
+ initial_classification_data = {
1550
+ classification_name: {
1551
+ "class" : "ClassificationProperties"
1552
+ }
1532
1553
  }
1533
1554
  else:
1534
1555
  initial_classification_dict = None
@@ -2398,12 +2419,11 @@ class CollectionManager(Client2):
2398
2419
  """
2399
2420
 
2400
2421
  url = (f"{self.collection_command_root}/{collection_guid}/update")
2401
- await self._async_update_element_request_body(url, "DigitalProductProperties", body )
2422
+ await self._async_update_element_body_request(url, "DigitalProductProperties", body )
2402
2423
 
2403
2424
 
2404
2425
  @dynamic_catch
2405
- def update_digital_product(self, collection_guid: str, body: dict | UpdateElementRequestBody,
2406
- merge_update: bool = True) -> None:
2426
+ def update_digital_product(self, collection_guid: str, body: dict | UpdateElementRequestBody,) -> None:
2407
2427
  """ Update the properties of a digital product..
2408
2428
  Collections: https://egeria-project.org/concepts/collection
2409
2429
 
@@ -2467,7 +2487,7 @@ class CollectionManager(Client2):
2467
2487
  """
2468
2488
 
2469
2489
  return asyncio.get_event_loop().run_until_complete(
2470
- self._async_update_digital_product(collection_guid, body, merge_update))
2490
+ self._async_update_digital_product(collection_guid, body))
2471
2491
 
2472
2492
 
2473
2493
  @dynamic_catch
@@ -2512,7 +2532,7 @@ class CollectionManager(Client2):
2512
2532
  }
2513
2533
  """
2514
2534
 
2515
- url = f"{self.collection_command_root}/{collection_guid}/update-status"
2535
+ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/collection-manager/metadata-elements/{collection_guid}/update-status"
2516
2536
  await self._async_update_status_request(url, status, body)
2517
2537
 
2518
2538
  @dynamic_catch
@@ -5071,6 +5091,12 @@ class CollectionManager(Client2):
5071
5091
  loop = asyncio.get_event_loop()
5072
5092
  loop.run_until_complete(self._async_add_to_collection(collection_guid, element_guid, body))
5073
5093
 
5094
+ def add_term_to_category(self, category_guid: str, term_guid: str,
5095
+ body: dict | NewRelationshipRequestBody = None) -> None:
5096
+ """Add a term to a category. The request body is optional."""
5097
+ loop = asyncio.get_event_loop()
5098
+ loop.run_until_complete(self._async_add_to_collection(category_guid, term_guid, body))
5099
+
5074
5100
 
5075
5101
  @dynamic_catch
5076
5102
  async def _async_update_collection_membership_prop(self, collection_guid: str, element_guid: str, body: dict = None,
@@ -5296,6 +5322,45 @@ class CollectionManager(Client2):
5296
5322
  #
5297
5323
 
5298
5324
 
5325
+ def remove_term_from_category(self, category_guid: str, term_guid: str,
5326
+ body: dict | DeleteRequestBody= None) -> None:
5327
+ """Remove a term from a category.
5328
+
5329
+ Parameters
5330
+ ----------
5331
+ category_guid: str
5332
+ identity of the collection to return members for.
5333
+ term_guid: str
5334
+ Effective time of the query. If not specified will default to any time.
5335
+ body: dict, optional, defaults to None
5336
+ The body of the request to add to the collection. See notes.
5337
+
5338
+ Returns
5339
+ -------
5340
+ None
5341
+
5342
+ Raises
5343
+ ------
5344
+
5345
+ InvalidParameterException
5346
+ If the client passes incorrect parameters on the request - such as bad URLs or invalid values
5347
+ PropertyServerException
5348
+ Raised by the server when an issue arises in processing a valid request
5349
+ NotAuthorizedException
5350
+ The principle specified by the user_id does not have authorization for the requested action
5351
+
5352
+ Notes
5353
+ -----
5354
+
5355
+ """
5356
+ loop = asyncio.get_event_loop()
5357
+ loop.run_until_complete(self._async_remove_from_collection(category_guid, term_guid, body))
5358
+
5359
+ #
5360
+ #
5361
+ #
5362
+
5363
+
5299
5364
  @dynamic_catch
5300
5365
  async def _async_get_member_list(self, collection_guid: str = None, collection_name: str = None,
5301
5366
  collection_qname: str = None, ) -> list | str: