pyegeria 5.4.0.26__py3-none-any.whl → 5.4.0.27__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 (46) hide show
  1. commands/cat/debug_log +868 -7794
  2. commands/cat/debug_log.2025-08-18_11-34-38_088636.zip +0 -0
  3. commands/cat/list_collections.py +1 -1
  4. commands/cat/list_format_set.py +6 -8
  5. commands/cli/egeria.py +2 -2
  6. commands/cli/egeria_cat.py +3 -2
  7. commands/ops/load_archive.py +2 -2
  8. md_processing/data/commands.json +7 -7
  9. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +7 -7
  10. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +36 -31
  11. md_processing/dr_egeria_outbox/friday/processed-2025-08-22 21:22-dr_egeria_intro_part1.md +312 -0
  12. md_processing/dr_egeria_outbox/friday/processed-2025-08-22 21:23-dr_egeria_intro_part1.md +265 -0
  13. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 15:06-dr_egeria_intro_part1.md +230 -0
  14. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 15:30-dr_egeria_intro_part1.md +296 -0
  15. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 15:31-dr_egeria_intro_part1.md +253 -0
  16. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 16:08-dr_egeria_intro_part2.md +343 -0
  17. md_processing/dr_egeria_outbox/friday/processed-2025-08-23 16:12-dr_egeria_intro_part2.md +343 -0
  18. md_processing/md_commands/glossary_commands.py +888 -951
  19. md_processing/md_commands/product_manager_commands.py +8 -270
  20. md_processing/md_commands/project_commands.py +1 -1
  21. md_processing/md_processing_utils/common_md_proc_utils.py +138 -64
  22. md_processing/md_processing_utils/common_md_utils.py +2 -1
  23. pyegeria/__init__.py +2 -3
  24. pyegeria/_client_new.py +4 -3
  25. pyegeria/_output_formats.py +5 -3
  26. pyegeria/collection_manager.py +31 -28
  27. pyegeria/{load_config.py → config.py} +7 -2
  28. pyegeria/data_designer.py +154 -194
  29. pyegeria/egeria_cat_client.py +46 -28
  30. pyegeria/egeria_client.py +71 -72
  31. pyegeria/egeria_config_client.py +37 -7
  32. pyegeria/egeria_my_client.py +45 -10
  33. pyegeria/egeria_tech_client.py +68 -57
  34. pyegeria/glossary_manager.py +494 -122
  35. pyegeria/governance_officer.py +2 -2
  36. pyegeria/logging_configuration.py +1 -4
  37. pyegeria/models.py +1 -1
  38. pyegeria/project_manager.py +358 -509
  39. pyegeria/utils.py +1 -3
  40. {pyegeria-5.4.0.26.dist-info → pyegeria-5.4.0.27.dist-info}/METADATA +1 -1
  41. {pyegeria-5.4.0.26.dist-info → pyegeria-5.4.0.27.dist-info}/RECORD +44 -38
  42. md_processing/md_processing_utils/solution_architect_log.log +0 -0
  43. pyegeria/glossary_browser.py +0 -1259
  44. {pyegeria-5.4.0.26.dist-info → pyegeria-5.4.0.27.dist-info}/LICENSE +0 -0
  45. {pyegeria-5.4.0.26.dist-info → pyegeria-5.4.0.27.dist-info}/WHEEL +0 -0
  46. {pyegeria-5.4.0.26.dist-info → pyegeria-5.4.0.27.dist-info}/entry_points.txt +0 -0
@@ -21,7 +21,7 @@ from md_processing.md_processing_utils.md_processing_constants import (load_comm
21
21
  from pyegeria import DEBUG_LEVEL, body_slimmer, to_pascal_case, PyegeriaException, print_basic_exception, print_exception_table
22
22
  from pyegeria.egeria_tech_client import EgeriaTech
23
23
 
24
- GERIA_METADATA_STORE = os.environ.get("EGERIA_METADATA_STORE", "active-metadata-store")
24
+ EGERIA_METADATA_STORE = os.environ.get("EGERIA_METADATA_STORE", "active-metadata-store")
25
25
  EGERIA_KAFKA_ENDPOINT = os.environ.get("KAFKA_ENDPOINT", "localhost:9092")
26
26
  EGERIA_PLATFORM_URL = os.environ.get("EGERIA_PLATFORM_URL", "https://localhost:9443")
27
27
  EGERIA_VIEW_SERVER = os.environ.get("EGERIA_VIEW_SERVER", "view-server")
@@ -142,270 +142,6 @@ def update_data_collection_memberships(egeria_client: EgeriaTech, entity_type: s
142
142
  add_member_to_collections(egeria_client, guid_list, display_name, guid)
143
143
 
144
144
 
145
- # @logger.catch
146
-
147
-
148
- # @logger.catch
149
- # def add_field_to_data_structures(egeria_client: EgeriaTech, display_name: str, struct_list: list, guid) -> None:
150
- # """
151
- # Add data field to data structures.
152
- # """
153
- #
154
- # try:
155
- # for structure_guid in struct_list:
156
- # egeria_client.link_member_data_field(structure_guid, guid, None)
157
- # msg = f"Added `{display_name}` to structure `{structure_guid}`"
158
- # logger.info(msg)
159
- # return
160
- #
161
- # except Exception as e:
162
- # console.print_exception()
163
- #
164
- #
165
- # @logger.catch
166
- # def remove_field_from_data_structures(egeria_client: EgeriaTech, display_name: str, struct_list: list,
167
- # guid: str) -> None:
168
- # """Remove a data field from a list of data structures."""
169
- # try:
170
- # for structure_guid in struct_list:
171
- # egeria_client.detach_member_data_field(structure_guid, guid, None)
172
- # msg = f"Removed `{display_name}` from structure `{structure_guid}`"
173
- # logger.info(msg)
174
- # return
175
- #
176
- # except Exception as e:
177
- # console.print_exception()
178
- #
179
-
180
- # @logger.catch
181
- # def sync_data_field_rel_elements(egeria_client: EgeriaTech, structure_list: list, parent_field_list: list, terms: list,
182
- # data_class_guid: str, guid: str, display_name: str,
183
- # replace_all_props: bool = True) -> None:
184
- # """Sync a field's related elements.
185
- #
186
- # TODO: Need to add data class support when ready and may need to revisit bodies.
187
- #
188
- # """
189
- # if terms:
190
- # terms = [terms]
191
- #
192
- # if replace_all_props:
193
- # rel_el_list = egeria_client.get_data_field_rel_elements(guid)
194
- # # should I throw an exception if empty?
195
- # if rel_el_list is None:
196
- # logger.warning("Unexpected -> the list was None - assigning empty list")
197
- # rel_el_list = {}
198
- #
199
- # as_is_data_structs = set(rel_el_list.get("data_structure_guids", []))
200
- # as_is_parent_fields = set(rel_el_list.get("parent_guids", []))
201
- # as_is_assigned_meanings = set(rel_el_list.get("assigned_meanings_guids", []))
202
- # as_is_data_classes = set(rel_el_list.get("data_class_guids", []))
203
- #
204
- # to_be_data_structs = set(structure_list) if structure_list is not None else set()
205
- # to_be_parent_fields = set(parent_field_list) if parent_field_list is not None else set()
206
- # to_be_assigned_meanings = set(terms) if terms is not None else set()
207
- # to_be_data_classes = set([data_class_guid]) if data_class_guid is not None else set()
208
- #
209
- # logger.trace(f"as_is_data_structs: {list(as_is_data_structs)} to_be_data_struct: {list(to_be_data_structs)}")
210
- # logger.trace(
211
- # f"as_is_parent_fields: {list(as_is_parent_fields)} to_be_parent_fields: {list(to_be_parent_fields)}")
212
- # logger.trace(f"as_is_assigned_meanings: {list(as_is_assigned_meanings)} to_be_assigned_meanings: "
213
- # f"{list(to_be_assigned_meanings)}")
214
- # logger.trace(f"as_is_data_classes: {list(as_is_data_classes)} to_be_assigned_data_classes: "
215
- # f"{list(to_be_data_classes)}")
216
- #
217
- # data_struct_to_remove = as_is_data_structs - to_be_data_structs
218
- # logger.trace(f"data_struct_to_remove: {list(data_struct_to_remove)}")
219
- # if len(data_struct_to_remove) > 0:
220
- # for ds in data_struct_to_remove:
221
- # egeria_client.detach_member_data_field(ds, guid, None)
222
- # msg = f"Removed `{display_name}` from structure `{ds}`"
223
- # logger.trace(msg)
224
- # data_struct_to_add = to_be_data_structs - as_is_data_structs
225
- # logger.trace(f"data_struct_to_add: {list(data_struct_to_add)}")
226
- # if len(data_struct_to_add) > 0:
227
- # for ds in data_struct_to_add:
228
- # egeria_client.link_member_data_field(ds, guid, None)
229
- # msg = f"Added `{display_name}` to structure `{ds}`"
230
- # logger.trace(msg)
231
- #
232
- # parent_field_to_remove = to_be_parent_fields - as_is_parent_fields
233
- # logger.trace(f"parent_field_to_remove: {list(parent_field_to_remove)}")
234
- # if len(parent_field_to_remove) > 0:
235
- # for field in parent_field_to_remove:
236
- # egeria_client.detach_nested_data_field(field, guid, None)
237
- # msg = f"Removed `{display_name}` from field `{field}`"
238
- # logger.trace(msg)
239
- # parent_field_to_add = to_be_parent_fields - as_is_parent_fields
240
- # logger.trace(f"parent_field_to_add: {list(parent_field_to_add)}")
241
- # if len(parent_field_to_add) > 0:
242
- # for field in parent_field_to_add:
243
- # egeria_client.link_nested_data_field(field, guid, None)
244
- # msg = f"Added `{display_name}` to field `{field}`"
245
- # logger.trace(msg)
246
- #
247
- # terms_to_remove = as_is_assigned_meanings - to_be_assigned_meanings
248
- # logger.trace(f"terms_to_remove: {list(terms_to_remove)}")
249
- # if terms:
250
- # for term in terms_to_remove:
251
- # egeria_client.detach_semantic_definition(guid, term, None)
252
- # msg = f"Removed `{term}` from `{display_name}`"
253
- # logger.trace(msg)
254
- # terms_to_add = to_be_assigned_meanings - as_is_assigned_meanings
255
- # logger.trace(f"terms_to_add: {list(terms_to_add)}")
256
- # if len(terms_to_add) > 0:
257
- # for term in terms_to_add:
258
- # egeria_client.link_semantic_definition(guid, term, None)
259
- # msg = f"Added `{term}` to`{display_name}`"
260
- # logger.trace(msg)
261
- #
262
- # classes_to_remove = as_is_data_classes - to_be_data_classes
263
- # logger.trace(f"classes_to_remove: {list(classes_to_remove)}")
264
- # if len(terms_to_remove) > 0:
265
- # for dc in classes_to_remove:
266
- # body = {
267
- # "class": "MetadataSourceRequestBody", "forLineage": False, "forDuplicateProcessing": False
268
- # }
269
- # egeria_client.detach_data_class_definition(guid, dc, body)
270
- # msg = f"Removed `{dc}` from `{display_name}`"
271
- # logger.trace(msg)
272
- # classes_to_add = to_be_data_classes - as_is_data_classes
273
- # logger.trace(f"classes_to_add: {list(classes_to_add)}")
274
- # if len(terms_to_add) > 0:
275
- # for dc in classes_to_add:
276
- # body = {
277
- # "class": "RelationshipRequestBody", "forLineage": False, "forDuplicateProcessing": False
278
- # }
279
- # egeria_client.link_data_class_definition(guid, dc, body)
280
- # msg = f"Added `{dc}` to`{display_name}`"
281
- # logger.trace(msg)
282
- #
283
- #
284
- # else: # merge - add field to related elements
285
- # if structure_list:
286
- # add_field_to_data_structures(egeria_client, display_name, structure_list, guid)
287
- # msg = f"Added `{display_name}` to `{structure_list}`"
288
- # logger.trace(msg)
289
- #
290
- # if parent_field_list:
291
- # for field in parent_field_list:
292
- # egeria_client.link_nested_data_field(field, guid, None)
293
- # msg = f"Added `{display_name}` to `{field}`"
294
- # logger.trace(msg)
295
- # if terms:
296
- # for term in terms:
297
- # egeria_client.link_semantic_definition(guid, term, None)
298
- # msg = f"Added `{term}` to `{display_name}`"
299
- # logger.trace(msg)
300
- #
301
- # if data_class_guid:
302
- # egeria_client.link_data_class_definition(guid, data_class_guid)
303
- # msg = f"Added `{data_class_guid}` to `{display_name}`"
304
- # logger.trace(msg)
305
-
306
- #
307
- # @logger.catch
308
- # def sync_data_class_rel_elements(egeria_client: EgeriaTech, containing_data_class_guids: list, terms: list,
309
- # specializes_data_classes: list, guid: str, display_name: str,
310
- # replace_all_props: bool = True) -> None:
311
- # """Sync a data class' related elements.
312
- #
313
- # """
314
- # if terms:
315
- # terms = [terms]
316
- #
317
- # if replace_all_props:
318
- # rel_el_list = egeria_client.get_data_class_rel_elements(guid)
319
- # if rel_el_list is None:
320
- # logger.warning("Unexpected -> the list was None - assigning empty list")
321
- # rel_el_list = {}
322
- # if terms:
323
- # terms = [terms]
324
- #
325
- # as_is_nested_classes = set(rel_el_list.get("nested_data_class_guids", []))
326
- # as_is_assigned_meanings = set(rel_el_list.get("assigned_meanings_guids", []))
327
- # as_is_specialized_classes = set(rel_el_list.get("specialized_data_class_guids", []))
328
- #
329
- # to_be_nested_classes = set(containing_data_class_guids) if containing_data_class_guids is not None else set()
330
- # to_be_assigned_meanings = set(terms) if terms is not None else set()
331
- # to_be_specialized_classes = set([specializes_data_classes]) if specializes_data_classes is not None else set()
332
- #
333
- # logger.trace(
334
- # f"as_is_nested_classes: {list(as_is_nested_classes)} to_be_nested_classes: {list(to_be_nested_classes)}")
335
- # logger.trace(f"as_is_assigned_meanings: {list(as_is_assigned_meanings)} to_be_assigned_meanings: "
336
- # f"{list(to_be_assigned_meanings)}")
337
- # logger.trace(f"as_is_specialized_classes: {list(as_is_specialized_classes)} to_be_specizialized_data_classes: "
338
- # f"{list(to_be_specialized_classes)}")
339
- #
340
- # nested_classes_to_remove = to_be_nested_classes - as_is_nested_classes
341
- # logger.trace(f"nested_classes_to_remove: {list(nested_classes_to_remove)}")
342
- # if len(nested_classes_to_remove) > 0:
343
- # for field in nested_classes_to_remove:
344
- # egeria_client.detach_nested_data_class(field, guid, None)
345
- # msg = f"Removed `{display_name}` from field `{field}`"
346
- # logger.trace(msg)
347
- # nested_classes_to_add = to_be_nested_classes - as_is_nested_classes
348
- # logger.trace(f"nested_classes_to_add: {list(nested_classes_to_add)}")
349
- # if len(nested_classes_to_add) > 0:
350
- # for field in nested_classes_to_add:
351
- # egeria_client.link_nested_data_class(field, guid, None)
352
- # msg = f"Added `{display_name}` to field `{field}`"
353
- # logger.trace(msg)
354
- #
355
- # terms_to_remove = as_is_assigned_meanings - to_be_assigned_meanings
356
- # logger.trace(f"terms_to_remove: {list(terms_to_remove)}")
357
- # if len(terms_to_remove) > 0:
358
- # for term in terms_to_remove:
359
- # egeria_client.detach_semantic_definition(guid, term, None)
360
- # msg = f"Removed `{term}` from `{display_name}`"
361
- # logger.trace(msg)
362
- # terms_to_add = to_be_assigned_meanings - as_is_assigned_meanings
363
- # logger.trace(f"terms_to_add: {list(terms_to_add)}")
364
- # if len(terms_to_add) > 0:
365
- # for term in terms_to_add:
366
- # egeria_client.link_semantic_definition(guid, term, None)
367
- # msg = f"Added `{term}` to`{display_name}`"
368
- # logger.trace(msg)
369
- #
370
- # specialized_classes_to_remove = as_is_specialized_classes - to_be_specialized_classes
371
- # logger.trace(f"classes_to_remove: {list(specialized_classes_to_remove)}")
372
- # if len(terms_to_remove) > 0:
373
- # for dc in specialized_classes_to_remove:
374
- # body = {
375
- # "class": "MetadataSourceRequestBody", "forLineage": False, "forDuplicateProcessing": False
376
- # }
377
- # egeria_client.detach_specialist_data_class(guid, dc, body)
378
- # msg = f"Removed `{dc}` from `{display_name}`"
379
- # logger.trace(msg)
380
- # specialized_classes_to_add = to_be_specialized_classes - as_is_specialized_classes
381
- # logger.trace(f"classes_to_add: {list(specialized_classes_to_add)}")
382
- # if len(specialized_classes_to_add) > 0:
383
- # for dc in specialized_classes_to_add:
384
- # body = {
385
- # "class": "RelationshipRequestBody", "forLineage": False, "forDuplicateProcessing": False
386
- # }
387
- # egeria_client.link_specialist_data_class(guid, dc, body)
388
- # msg = f"Added `{dc}` to`{display_name}`"
389
- # logger.trace(msg)
390
- #
391
- #
392
- # else: # merge - add field to related elements
393
- # if containing_data_class_guids:
394
- # for field in containing_data_class_guids:
395
- # egeria_client.link_nested_data_class(field, guid, None)
396
- # msg = f"Added `{display_name}` to `{field}`"
397
- # logger.trace(msg)
398
- #
399
- # if terms:
400
- # for term in terms:
401
- # egeria_client.link_semantic_definition(guid, term, None)
402
- # msg = f"Added `{term}` to `{display_name}`"
403
- # logger.trace(msg)
404
- # if specializes_data_classes:
405
- # for el in specializes_data_classes:
406
- # egeria_client.link_specialist_data_class(guid, el)
407
- # msg = f"Linked `{el}` to `{display_name}`"
408
- # logger.trace(msg)
409
145
  #
410
146
  # Product Manager Commands
411
147
  #
@@ -465,14 +201,14 @@ def process_collection_upsert_command(egeria_client: EgeriaTech, txt: str, direc
465
201
  if object_action == "Update":
466
202
  if not exists:
467
203
  msg = (f" Element `{display_name}` does not exist! Updating result document with Create "
468
- f"object_action\n")
204
+ f"`{object_action}`\n")
469
205
  logger.error(msg)
470
206
  return update_a_command(txt, object_action, object_type, qualified_name, guid)
471
207
  elif not valid:
472
208
  return None
473
209
  else:
474
210
  print(Markdown(
475
- f"==> Validation of {command} completed successfully! Proceeding to apply the changes.\n"))
211
+ f"==> Validation of `{command}` completed successfully! Proceeding to apply the changes.\n"))
476
212
  prop_body = set_prop_body(obj,qualified_name,attributes)
477
213
 
478
214
  body = set_update_body(obj, attributes)
@@ -573,6 +309,8 @@ def process_digital_product_upsert_command(egeria_client: EgeriaTech, txt: str,
573
309
 
574
310
  elif directive == "process":
575
311
  try:
312
+ prop_body = set_product_body(object_type, qualified_name, attributes)
313
+
576
314
  if object_action == "Update":
577
315
  if not exists:
578
316
  msg = (f" Element `{display_name}` does not exist! Updating result document with Create "
@@ -584,9 +322,9 @@ def process_digital_product_upsert_command(egeria_client: EgeriaTech, txt: str,
584
322
  else:
585
323
  print(Markdown(
586
324
  f"==> Validation of {command} completed successfully! Proceeding to apply the changes.\n"))
587
- prop_body = set_product_body(object_type,qualified_name,attributes)
325
+
588
326
  body = set_update_body(object_type, attributes)
589
- body['properties'] = set_prop_body(object_type,qualified_name,attributes)
327
+ body['properties'] = prop_body
590
328
  # Todo: Update product manager later?
591
329
 
592
330
  egeria_client.update_digital_product(guid, body)
@@ -612,7 +350,7 @@ def process_digital_product_upsert_command(egeria_client: EgeriaTech, txt: str,
612
350
  else:
613
351
  body = set_create_body(object_type, attributes)
614
352
  body["initialClassifications"] = set_collection_classifications(object_type, attributes,[])
615
- prop_body = set_product_body(object_type, qualified_name, attributes)
353
+
616
354
  body["properties"] = prop_body
617
355
 
618
356
  guid = egeria_client.create_digital_product(body_slimmer(body))
@@ -8,7 +8,7 @@ from md_processing.md_processing_utils.common_md_utils import (debug_level, prin
8
8
  from md_processing.md_processing_utils.extraction_utils import (extract_command, process_simple_attribute)
9
9
  from md_processing.md_processing_utils.md_processing_constants import ALWAYS, ERROR, INFO, pre_command
10
10
  from pyegeria._globals import NO_PROJECTS_FOUND
11
- from pyegeria.project_manager_omvs import ProjectManager
11
+ from pyegeria.project_manager import ProjectManager
12
12
 
13
13
  setup_log()
14
14
 
@@ -474,6 +474,9 @@ def proc_simple_attribute(txt: str, action: str, labels: set, if_missing: str =
474
474
 
475
475
  if attribute and simp_type == "int" :
476
476
  attribute = int(attribute)
477
+ elif attribute and simp_type == "list":
478
+ attribute = list(attribute)
479
+
477
480
 
478
481
  return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
479
482
 
@@ -915,74 +918,145 @@ def proc_name_list(egeria_client: EgeriaTech, element_type: str, txt: str, eleme
915
918
 
916
919
 
917
920
  @logger.catch
918
- def update_term_categories(egeria_client: EgeriaTech, term_guid: str, categories_exist: bool,
919
- categories_list: List[str]) -> None:
921
+ def sync_collection_memberships(egeria_client: EgeriaTech, guid: str, get_method: callable, collection_types: list,
922
+ to_be_collection_guids:list, merge_update: bool = True)-> None:
920
923
  """
924
+ Synchronize collection memberships for an element.
921
925
 
922
- Adds or removes a term to/from specified categories in a glossary.
923
-
924
- This function associates a term, identified by its GUID, with one or more
925
- categories. It uses the provided EgeriaTech client to assign the term
926
- to the categories. If the GUID of a category is not readily available, it
927
- is retrieved either from a pre-loaded dictionary or through a client lookup.
928
-
929
- Args:
930
- egeria_client (EgeriaTech): The client to interact with the glossary.
931
- term_guid (str): The GUID of the term to be associated with categories.
932
- categories_exist (bool): Flag indicating whether the categories already
933
- exist.
934
- categories_list (List[str]): A list of category names to associate with
935
- the term.
936
-
937
- Returns:
938
- None
926
+ Parameters
927
+ - egeria_client: EgeriaTech composite client used to call add/remove operations.
928
+ - guid: the GUID of the element (e.g., GlossaryTerm) whose memberships we sync.
929
+ - get_method: callable to fetch the element details by guid; must accept (guid, output_format="JSON").
930
+ - collection_types: list of collection type identifiers to consider when syncing (e.g., ["Glossary", "Folder"]).
931
+ - to_be_collection_guids: list of lists of GUIDs corresponding positionally to collection_types; may contain None.
932
+ - merge_update: if True, only add missing memberships; if False, remove existing memberships for the
933
+ specified collection_types and then add the desired memberships.
934
+
935
+ Behavior
936
+ - When merge_update is True: determine the element's current memberships and add the missing ones only.
937
+ - When merge_update is False: remove the element from all collections of the specified types, then add the
938
+ provided target memberships for those types.
939
939
  """
940
- to_be_cat_guids: list[str] = []
941
- # find the categories a term is currently in.
942
- existing_categories = egeria_client.get_categories_for_term(term_guid)
943
- if type(existing_categories) is str:
944
- current_categories = []
945
- else:
946
- current_categories = [cat['elementHeader']['guid'] for cat in existing_categories]
947
-
948
- if categories_exist is True and categories_list is not None:
949
- if type(categories_list) is str:
950
- # categories_list = re.split(r'[;,\n]+', categories_list)
951
- # categories_list = categories_list.split(";,").trim()
952
- categories_list = split_tb_string(categories_list)
953
- for category in categories_list:
954
- cat_guid = None
955
- cat_el = category.strip()
956
- element_dict = get_element_dictionary()
957
- if cat_el in element_dict:
958
- cat = element_dict.get(cat_el, None)
959
- cat_guid = cat.get('guid', None) if cat else None
960
- if cat_guid is None:
961
- cat_guid = egeria_client.__get_guid__(qualified_name=cat_el)
962
- update_element_dictionary(cat_el, {'guid': cat_guid})
963
- to_be_cat_guids.append(cat_guid)
964
-
965
- for cat in to_be_cat_guids:
966
- if cat not in current_categories:
967
- egeria_client.add_term_to_category(term_guid, cat)
968
- current_categories.append(cat)
969
- msg = f"Added term {term_guid} to category {cat}"
970
- logger.info(msg)
971
-
972
- for cat in current_categories:
973
- if cat not in to_be_cat_guids:
974
- egeria_client.remove_term_from_category(term_guid, cat)
975
- msg = f"Removed term {term_guid} from category {cat}"
976
- logger.info(msg)
977
- else: # No categories specified - so remove any categories a term is in
978
- for cat in current_categories:
979
- egeria_client.remove_term_from_category(term_guid, cat)
980
- msg = f"Removed term {term_guid} from category {cat}"
981
- logger.info(msg)
982
-
983
-
984
-
940
+ try:
941
+ # Defensive defaults and shape normalization
942
+ collection_types = collection_types or []
943
+ to_be_collection_guids = to_be_collection_guids or []
944
+ # Ensure the lists align by index; pad to length
945
+ max_len = max(len(collection_types), len(to_be_collection_guids)) if (collection_types or to_be_collection_guids) else 0
946
+ if len(collection_types) < max_len:
947
+ collection_types = collection_types + [None] * (max_len - len(collection_types))
948
+ if len(to_be_collection_guids) < max_len:
949
+ to_be_collection_guids = to_be_collection_guids + [None] * (max_len - len(to_be_collection_guids))
950
+
951
+ # Get current element details with raw JSON to inspect relationships
952
+ element = None
953
+ try:
954
+ element = get_method(guid, output_format="JSON")
955
+ except TypeError:
956
+ # Some get methods require element_type parameter; fallback best-effort
957
+ element = get_method(guid, element_type=None, output_format="JSON")
958
+ if isinstance(element, str):
959
+ # e.g., "No elements found"; nothing to do
960
+ logger.debug(f"sync_collection_memberships: element lookup returned: {element}")
961
+ return
962
+ if not isinstance(element, dict):
963
+ logger.debug("sync_collection_memberships: element lookup did not return a dict; skipping")
964
+ return
965
+
966
+ member_rels = element.get("memberOfCollections", []) or []
967
+
968
+ # Build current membership maps
969
+ # - by GUID: set of current collection guids
970
+ # - by type name (classification names found on related collection): map type->set(guids)
971
+ current_all_guids: set[str] = set()
972
+ current_by_type: dict[str, set[str]] = {}
973
+
974
+ for rel in member_rels:
975
+ try:
976
+ related = (rel or {}).get("relatedElement", {})
977
+ rel_guid = ((related.get("elementHeader") or {}).get("guid"))
978
+ if not rel_guid:
979
+ continue
980
+ current_all_guids.add(rel_guid)
981
+
982
+ # Collect type hints from classifications and from properties.collectionType
983
+ type_names: set[str] = set()
984
+ classifications = ((related.get("elementHeader") or {}).get("classifications")) or []
985
+ for cls in classifications:
986
+ tname = (((cls or {}).get("type") or {}).get("typeName"))
987
+ if tname:
988
+ type_names.add(tname)
989
+ ctype = ((related.get("properties") or {}).get("collectionType"))
990
+ if isinstance(ctype, str) and ctype:
991
+ type_names.add(ctype)
992
+
993
+ if not type_names:
994
+ # Fallback: try elementHeader.type.typeName
995
+ tname2 = (((related.get("elementHeader") or {}).get("type") or {}).get("typeName"))
996
+ if tname2:
997
+ type_names.add(tname2)
998
+
999
+ for tn in type_names:
1000
+ s = current_by_type.setdefault(tn, set())
1001
+ s.add(rel_guid)
1002
+ except Exception as e:
1003
+ logger.debug(f"sync_collection_memberships: skipping malformed relationship: {e}")
1004
+ continue
985
1005
 
1006
+ # Helper to coerce incoming desired list entry to a set of guids
1007
+ def to_guid_set(maybe_list) -> set[str]:
1008
+ if not maybe_list:
1009
+ return set()
1010
+ if isinstance(maybe_list, list):
1011
+ return {g for g in maybe_list if isinstance(g, str) and g}
1012
+ # Sometimes a single guid may slip through
1013
+ if isinstance(maybe_list, str):
1014
+ return {maybe_list}
1015
+ return set()
1016
+
1017
+ # If merge_update is False: remove all existing memberships for the specified types
1018
+ if not merge_update:
1019
+ # Build a set of guids to remove across specified types
1020
+ to_remove: set[str] = set()
1021
+ for t in collection_types:
1022
+ if not t:
1023
+ continue
1024
+ # Match by exact type name as seen in current_by_type
1025
+ guids_for_type = current_by_type.get(t) or set()
1026
+ if not guids_for_type and t.lower() in {k.lower() for k in current_by_type.keys()}:
1027
+ # Case-insensitive fallback
1028
+ for k, v in current_by_type.items():
1029
+ if k.lower() == t.lower():
1030
+ guids_for_type = v
1031
+ break
1032
+ to_remove.update(guids_for_type)
1033
+
1034
+ for coll_guid in to_remove:
1035
+ try:
1036
+ egeria_client.remove_from_collection(coll_guid, guid)
1037
+ logger.info(f"Removed element {guid} from collection {coll_guid}")
1038
+ except Exception as e:
1039
+ logger.debug(f"Failed to remove element {guid} from collection {coll_guid}: {e}")
1040
+
1041
+ # Now add desired memberships (for both merge and replace flows)
1042
+ for idx, t in enumerate(collection_types):
1043
+ desired_set = to_guid_set(to_be_collection_guids[idx] if idx < len(to_be_collection_guids) else None)
1044
+ if not desired_set:
1045
+ continue
1046
+ for coll_guid in desired_set:
1047
+ # If merge_update True, skip if already a member; if False, we removed earlier so can re-add
1048
+ if merge_update and coll_guid in current_all_guids:
1049
+ continue
1050
+ try:
1051
+ egeria_client.add_to_collection(coll_guid, guid)
1052
+ logger.info(f"Added element {guid} to collection {coll_guid}")
1053
+ except Exception as e:
1054
+ logger.debug(f"Failed to add element {guid} to collection {coll_guid}: {e}")
1055
+
1056
+ return
1057
+ except Exception as e:
1058
+ logger.error(f"sync_collection_memberships: unexpected error: {e}")
1059
+ return
986
1060
 
987
1061
  @logger.catch
988
1062
  def process_output_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
@@ -300,10 +300,11 @@ def set_update_body(object_type: str, attributes: dict)->dict:
300
300
  def set_prop_body(object_type: str, qualified_name: str, attributes: dict)->dict:
301
301
 
302
302
  prop_name = object_type.replace(" ", "")
303
+ display_name = attributes.get('Display Name', {}).get('value', None)
303
304
 
304
305
  return {
305
306
  "class": prop_name + "Properties",
306
- "displayName": attributes['Display Name'].get('value', None),
307
+ "displayName": attributes.get('Display Name', {}).get('value', None),
307
308
  "qualifiedName" : qualified_name,
308
309
  "description": attributes['Description'].get('value', None),
309
310
  "category": attributes.get('Category', {}).get('value', None),
pyegeria/__init__.py CHANGED
@@ -31,7 +31,7 @@ from ._exceptions_new import (PyegeriaInvalidParameterException,PyegeriaAPIExcep
31
31
  PyegeriaUnauthorizedException, PyegeriaClientException, PyegeriaUnknownException,
32
32
  PyegeriaConnectionException, PyegeriaNotFoundException,
33
33
  print_exception_table, print_basic_exception, print_validation_error)
34
- from .load_config import load_app_config, get_app_config
34
+ from .config import load_app_config, get_app_config, settings
35
35
  from .logging_configuration import config_logging, console_log_filter, init_logging
36
36
  from ._exceptions import (InvalidParameterException, PropertyServerException, UserNotAuthorizedException,
37
37
  print_exception_response, )
@@ -44,13 +44,12 @@ from .collection_manager import CollectionManager
44
44
  from .core_omag_server_config import CoreServerConfig
45
45
  from .create_tech_guid_lists import build_global_guid_lists
46
46
  from .egeria_cat_client import EgeriaCat
47
- # from .egeria_client import Egeria
47
+ from .egeria_client import Egeria
48
48
  from .egeria_config_client import EgeriaConfig
49
49
  from .egeria_my_client import EgeriaMy
50
50
  from .egeria_tech_client import EgeriaTech
51
51
  from .feedback_manager_omvs import FeedbackManager
52
52
  from .full_omag_server_config import FullServerConfig
53
- from .glossary_browser import GlossaryBrowser
54
53
  from .glossary_manager import GlossaryManager
55
54
  from .governance_officer import GovernanceOfficer
56
55
  from .mermaid_utilities import (construct_mermaid_web, construct_mermaid_jup, generate_process_graph, load_mermaid,
pyegeria/_client_new.py CHANGED
@@ -380,7 +380,7 @@ class Client2:
380
380
  endpoint: str,
381
381
  payload: str | dict = None,
382
382
  time_out: int = 30,
383
- is_json: bool = True,
383
+ is_json: bool = True
384
384
  ) -> Response | str:
385
385
  """Make a request to the Egeria API."""
386
386
  try:
@@ -401,7 +401,7 @@ class Client2:
401
401
  endpoint: str,
402
402
  payload: str | dict = None,
403
403
  time_out: int = 30,
404
- is_json: bool = True,
404
+ is_json: bool = True
405
405
  ) -> Response | str:
406
406
  """Make a request to the Egeria API - Async Version
407
407
  Function to make an API call via the self.session Library. Raise an exception if the HTTP response code
@@ -935,6 +935,7 @@ class Client2:
935
935
  validated_body = self._filter_request_adapter.validate_python(body)
936
936
  else:
937
937
  filter_string = None if filter_string is "*" else filter_string
938
+ classification_names = None if classification_names == [] else classification_names
938
939
  body = {
939
940
  "class": "FilterRequestBody",
940
941
  "filter": filter_string,
@@ -947,7 +948,7 @@ class Client2:
947
948
  # classification_names = validated_body.include_only_classified_elements
948
949
  # element_type_name = classification_names[0] if classification_names else _type
949
950
 
950
- json_body = validated_body.model_dump_json(indent=2)
951
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
951
952
 
952
953
  response = await self._async_make_request("POST", url, json_body)
953
954
  elements = response.json().get("elements", NO_ELEMENTS_FOUND)