pyegeria 5.4.0.22__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 (130) hide show
  1. commands/cat/.DS_Store +0 -0
  2. commands/cat/.env +8 -0
  3. commands/cat/debug_log +2046 -465
  4. commands/cat/debug_log.2025-08-15_09-14-07_444802.zip +0 -0
  5. commands/cat/debug_log.2025-08-16_10-21-59_388912.zip +0 -0
  6. commands/cat/debug_log.log +0 -0
  7. commands/cat/dr_egeria_md.py +16 -3
  8. commands/cat/list_collections.py +15 -6
  9. commands/cat/list_format_set.py +90 -85
  10. commands/cli/debug_log.log +0 -0
  11. commands/ops/logs/pyegeria.log +0 -0
  12. md_processing/.DS_Store +0 -0
  13. md_processing/__init__.py +5 -3
  14. md_processing/data/commands.json +8310 -903
  15. md_processing/dr-egeria-outbox/Collections-2025-08-12-13-30-37.md +163 -0
  16. md_processing/dr-egeria-outbox/Collections-2025-08-12-13-35-58.md +474 -0
  17. md_processing/dr_egeria_inbox/Derive-Dr-Gov-Defs.md +8 -0
  18. md_processing/dr_egeria_inbox/Dr.Egeria Templates.md +873 -0
  19. md_processing/dr_egeria_inbox/arch_test.md +57 -0
  20. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +254 -0
  21. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +696 -0
  22. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +254 -0
  23. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +298 -0
  24. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +608 -0
  25. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +94 -0
  26. md_processing/dr_egeria_inbox/archive/freddie_intro.md +284 -0
  27. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +275 -0
  28. md_processing/dr_egeria_inbox/archive/test-term.md +110 -0
  29. md_processing/dr_egeria_inbox/cat_test.md +100 -0
  30. md_processing/dr_egeria_inbox/collections.md +39 -0
  31. md_processing/dr_egeria_inbox/data_designer_debug.log +6 -0
  32. md_processing/dr_egeria_inbox/data_designer_out.md +60 -0
  33. md_processing/dr_egeria_inbox/data_designer_search_test.md +11 -0
  34. md_processing/dr_egeria_inbox/data_field.md +54 -0
  35. md_processing/dr_egeria_inbox/data_spec.md +77 -0
  36. md_processing/dr_egeria_inbox/data_spec_test.md +2406 -0
  37. md_processing/dr_egeria_inbox/data_test.md +179 -0
  38. md_processing/dr_egeria_inbox/data_test2.md +429 -0
  39. md_processing/dr_egeria_inbox/data_test3.md +462 -0
  40. md_processing/dr_egeria_inbox/dr_egeria_data_designer_1.md +124 -0
  41. md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +168 -0
  42. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +280 -0
  43. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +313 -0
  44. md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +1073 -0
  45. md_processing/dr_egeria_inbox/dr_egeria_isc1.md +44 -0
  46. md_processing/dr_egeria_inbox/generated_help_report.md +9 -0
  47. md_processing/dr_egeria_inbox/glossary_list.md +5 -0
  48. md_processing/dr_egeria_inbox/glossary_search_test.md +40 -0
  49. md_processing/dr_egeria_inbox/glossary_test1.md +324 -0
  50. md_processing/dr_egeria_inbox/gov_def.md +482 -0
  51. md_processing/dr_egeria_inbox/gov_def2.md +447 -0
  52. md_processing/dr_egeria_inbox/img.png +0 -0
  53. md_processing/dr_egeria_inbox/product.md +162 -0
  54. md_processing/dr_egeria_inbox/rel.md +8 -0
  55. md_processing/dr_egeria_inbox/sb.md +119 -0
  56. md_processing/dr_egeria_inbox/solution-components.md +136 -0
  57. md_processing/dr_egeria_inbox/solution_blueprints.md +118 -0
  58. md_processing/dr_egeria_inbox/synonym_test.md +42 -0
  59. md_processing/dr_egeria_inbox/t2.md +268 -0
  60. md_processing/dr_egeria_outbox/.obsidian/app.json +1 -0
  61. md_processing/dr_egeria_outbox/.obsidian/appearance.json +1 -0
  62. md_processing/dr_egeria_outbox/.obsidian/community-plugins.json +6 -0
  63. md_processing/dr_egeria_outbox/.obsidian/core-plugins.json +31 -0
  64. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/data.json +10 -0
  65. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/main.js +4459 -0
  66. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/manifest.json +10 -0
  67. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/data.json +3 -0
  68. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/main.js +153 -0
  69. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
  70. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
  71. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/main.js +500 -0
  72. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
  73. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
  74. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/main.js +37 -0
  75. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
  76. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/styles.css +220 -0
  77. md_processing/dr_egeria_outbox/.obsidian/types.json +28 -0
  78. md_processing/dr_egeria_outbox/.obsidian/workspace.json +220 -0
  79. md_processing/dr_egeria_outbox/Untitled.canvas +1 -0
  80. md_processing/dr_egeria_outbox/monday/processed-2025-07-14 12:38-data_designer_out.md +663 -0
  81. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 10:52-generated_help_report.md +2744 -0
  82. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 18:38-collections.md +62 -0
  83. md_processing/dr_egeria_outbox/monday/processed-2025-08-01 11:34-gov_def.md +444 -0
  84. md_processing/dr_egeria_outbox/monday/processed-2025-08-17 21:04-product.md +97 -0
  85. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 14:55-product.md +77 -0
  86. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 15:05-product.md +75 -0
  87. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 15:11-product.md +74 -0
  88. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 20:40-collections.md +49 -0
  89. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +719 -0
  90. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +41 -0
  91. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +33 -0
  92. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +192 -0
  93. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:08-gov_def2.md +486 -0
  94. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:10-gov_def2.md +486 -0
  95. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:53-gov_def2.md +486 -0
  96. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:54-gov_def2.md +486 -0
  97. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:03-gov_def2.md +486 -0
  98. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:06-gov_def2.md +486 -0
  99. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:10-gov_def2.md +486 -0
  100. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-16 19:15-gov_def2.md +527 -0
  101. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-17 12:08-gov_def2.md +527 -0
  102. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-17 14:27-gov_def2.md +485 -0
  103. md_processing/md_commands/governance_officer_commands.py +291 -150
  104. md_processing/md_commands/product_manager_commands.py +309 -401
  105. md_processing/md_processing_utils/common_md_proc_utils.py +110 -7
  106. md_processing/md_processing_utils/common_md_utils.py +112 -26
  107. md_processing/md_processing_utils/debug_log.log +0 -0
  108. md_processing/md_processing_utils/md_processing_constants.py +8 -5
  109. md_processing/md_processing_utils/solution_architect_log.log +0 -0
  110. pyegeria/.DS_Store +0 -0
  111. pyegeria/__init__.py +3 -3
  112. pyegeria/_client_new.py +48 -51
  113. pyegeria/_exceptions_new.py +6 -0
  114. pyegeria/_output_format_models.py +22 -17
  115. pyegeria/_output_formats.py +122 -34
  116. pyegeria/collection_manager.py +154 -50
  117. pyegeria/collection_manager_omvs.py +47 -18
  118. pyegeria/egeria_cat_client.py +1 -1
  119. pyegeria/egeria_client.py +6 -0
  120. pyegeria/egeria_tech_client.py +6 -1
  121. pyegeria/governance_officer.py +2513 -0
  122. pyegeria/load_config.py +1 -1
  123. pyegeria/models.py +48 -5
  124. pyegeria/output_formatter.py +298 -79
  125. pyegeria/utils.py +1 -1
  126. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.24.dist-info}/METADATA +1 -1
  127. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.24.dist-info}/RECORD +130 -30
  128. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.24.dist-info}/LICENSE +0 -0
  129. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.24.dist-info}/WHEEL +0 -0
  130. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.24.dist-info}/entry_points.txt +0 -0
@@ -9,7 +9,7 @@ Copyright Contributors to the ODPi Egeria project.
9
9
  import asyncio
10
10
  import inspect
11
11
  from datetime import datetime
12
- from typing import Optional, Annotated, Literal
12
+ from typing import Optional, Annotated, Literal, Union
13
13
 
14
14
  from loguru import logger
15
15
  from pydantic import ValidationError, Field, HttpUrl
@@ -21,9 +21,11 @@ from pyegeria.load_config import get_app_config
21
21
  from pyegeria.models import (SearchStringRequestBody, FilterRequestBody, GetRequestBody, NewElementRequestBody,
22
22
  ReferenceableProperties, InitialClassifications, TemplateRequestBody,
23
23
  UpdateElementRequestBody, UpdateStatusRequestBody, NewRelationshipRequestBody,
24
- DeleteRequestBody, UpdateRelationshipRequestBody, ResultsRequestBody)
24
+ DeleteRequestBody, UpdateRelationshipRequestBody, ResultsRequestBody,
25
+ get_defined_field_values, PyegeriaModel)
25
26
  from pyegeria.output_formatter import (generate_output,
26
- _extract_referenceable_properties)
27
+ _extract_referenceable_properties, populate_columns_from_properties,
28
+ get_required_relationships)
27
29
  from pyegeria.utils import body_slimmer, dynamic_catch
28
30
 
29
31
 
@@ -61,28 +63,39 @@ class CollectionProperties(ReferenceableProperties):
61
63
  class_: Annotated[Literal["CollectionProperties"], Field(alias="class")]
62
64
 
63
65
 
64
- class DataSpecProperties(ReferenceableProperties):
66
+ class DataSpecProperties(CollectionProperties):
65
67
  class_: Annotated[Literal["DataSpecProperties"], Field(alias="class")]
66
68
 
67
69
 
68
- class DataDictionaryProperties(ReferenceableProperties):
70
+ class DataDictionaryProperties(CollectionProperties):
69
71
  class_: Annotated[Literal["DataDictionaryProperties"], Field(alias="class")]
70
72
 
71
73
 
72
- class AgreementProperties(ReferenceableProperties):
74
+ class AgreementProperties(CollectionProperties):
73
75
  class_: Annotated[Literal["AgreementProperties"], Field(alias="class")]
74
76
  identifier: str | None = None
75
77
  user_defined_status: str | None = None
76
78
 
79
+ class DigitalSubscriptionProperties(AgreementProperties):
80
+ class_: Annotated[Literal["DigitalSubscriptionProperties"], Field(alias="class")]
81
+ support_level: str | None = None
82
+ service_levels: dict | None = None
77
83
 
78
- class DigitalProductProperties(ReferenceableProperties):
84
+
85
+ class DigitalProductProperties(CollectionProperties):
86
+ class_: Annotated[Literal["DigitalProductProperties"], Field(alias="class")]
87
+ user_defined_status: str | None = None
79
88
  product_name: str | None = None
89
+ identifier: str | None = None
90
+ introduction_date: datetime | None = None
80
91
  maturity: str | None = None
81
92
  service_life: str | None = None
82
- introduction_date: datetime | None = None
83
93
  next_version_date: datetime | None = None
84
94
  withdrawal_date: datetime | None = None
85
- current_version: str | None = None
95
+
96
+ class Collections(PyegeriaModel):
97
+ collection: Union[CollectionProperties, DigitalSubscriptionProperties, DigitalProductProperties, AgreementProperties,
98
+ DataSpecProperties, DataDictionaryProperties] = Field(desciminator="class_")
86
99
 
87
100
 
88
101
  class CollectionManager(Client2):
@@ -127,7 +140,8 @@ class CollectionManager(Client2):
127
140
 
128
141
  @dynamic_catch
129
142
  async def _async_get_attached_collections(self, parent_guid: str, start_from: int = 0, page_size: int = 0,
130
- body: dict = None, output_format: str = "JSON",
143
+ category: str = None, classification_names: list[str]= None,
144
+ body: dict | FilterRequestBody = None, output_format: str = "JSON",
131
145
  output_format_set: str | dict = None) -> list | str:
132
146
  """Returns the list of collections that are linked off of the supplied element using the ResourceList
133
147
  relationship. Async version.
@@ -180,12 +194,10 @@ class CollectionManager(Client2):
180
194
  }
181
195
 
182
196
  """
183
- if body is None:
184
- body = {}
185
197
 
186
198
  url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/collection-manager/"
187
- f"metadata-elements/{parent_guid}/collections?startFrom={start_from}&pageSize={page_size}")
188
-
199
+ f"metadata-elements/{parent_guid}/collections")
200
+ return await self._async_get_name_request(url, body, output_format, output_format_set)
189
201
  response = await self._async_make_request("POST", url, body_slimmer(body))
190
202
  elements = response.json().get("elements", NO_ELEMENTS_FOUND)
191
203
  if type(elements) is str:
@@ -259,7 +271,8 @@ class CollectionManager(Client2):
259
271
 
260
272
 
261
273
  @dynamic_catch
262
- async def _async_find_collections(self, search_string: str = '*', classification_names: list[str] = None,
274
+ async def _async_find_collections(self, search_string: str = "*", classification_names: list[str] = None,
275
+ metadata_element_types: list[str] = None,
263
276
  starts_with: bool = True, ends_with: bool = False, ignore_case: bool = False,
264
277
  start_from: int = 0, page_size: int = 0, output_format: str = 'JSON',
265
278
  output_format_set: str | dict = None,
@@ -273,9 +286,11 @@ class CollectionManager(Client2):
273
286
  search_string: str
274
287
  Search string to match against - None or '*' indicate match against all collections (may be filtered by
275
288
  classification).
276
- initial_classifications: list[str], optional, default=None
289
+ classification_names: list[str], optional, default=None
277
290
  A list of classification names to filter on - for example, ["DataSpec"], for data specifications. If none,
278
291
  then all classifications are returned.
292
+ metadata_element_types: list[str], optional, default=None
293
+ A list of metadata element types to filter on - for example, ["DataSpec"], for data specifications. If none,
279
294
  starts_with : bool, [default=False], optional
280
295
  Starts with the supplied string.
281
296
  ends_with : bool, [default=False], optional
@@ -315,6 +330,7 @@ class CollectionManager(Client2):
315
330
  response = await self._async_find_request(url, _type="Collection",
316
331
  _gen_output=self._generate_collection_output,
317
332
  search_string = search_string, classification_names = classification_names,
333
+ metadata_element_types = metadata_element_types,
318
334
  starts_with = starts_with, ends_with = ends_with, ignore_case = ignore_case,
319
335
  start_from = start_from, page_size = page_size,
320
336
  output_format=output_format, output_format_set=output_format_set,
@@ -323,7 +339,8 @@ class CollectionManager(Client2):
323
339
  return response
324
340
 
325
341
  @dynamic_catch
326
- def find_collections(self, search_string: str = '*', classification_names: str = None, starts_with: bool = True,
342
+ def find_collections(self, search_string: str = '*', classification_names: str = None,
343
+ metadata_element_types: list[str] = None, starts_with: bool = True,
327
344
  ends_with: bool = False, ignore_case: bool = False,
328
345
  start_from: int = 0, page_size: int = 0, output_format: str = 'JSON',
329
346
  output_format_set: str | dict = None,
@@ -337,9 +354,12 @@ class CollectionManager(Client2):
337
354
  search_string: str
338
355
  Search string to match against - None or '*' indicate match against all collections (may be filtered by
339
356
  classification).
340
- initial_classifications: list[str], optional, default=None
357
+ classification_names: list[str], optional, default=None
341
358
  A list of classification names to filter on - for example, ["DataSpec"], for data specifications. If none,
342
359
  then all classifications are returned.
360
+ metadata_element_types: list[str], optional, default=None
361
+ A list of metadata element types to filter on - for example, ["DataSpec"], for data specifications. If none,
362
+ then all metadata element types are returned.
343
363
  starts_with : bool, [default=False], optional
344
364
  Starts with the supplied string.
345
365
  ends_with : bool, [default=False], optional
@@ -374,10 +394,16 @@ class CollectionManager(Client2):
374
394
  NotAuthorizedException
375
395
  The principle specified by the user_id does not have authorization for the requested action
376
396
 
397
+ Args:
398
+ classification_names ():
399
+ metadata_element_types ():
400
+
377
401
  """
378
402
  return asyncio.get_event_loop().run_until_complete(
379
- self._async_find_collections(search_string, classification_names, starts_with, ends_with, ignore_case,
380
- start_from, page_size, output_format, output_format_set, body))
403
+ self._async_find_collections(search_string, classification_names, metadata_element_types,
404
+ starts_with, ends_with, ignore_case,
405
+ start_from, page_size, output_format,
406
+ output_format_set, body))
381
407
 
382
408
 
383
409
  @dynamic_catch
@@ -4843,7 +4869,7 @@ class CollectionManager(Client2):
4843
4869
  Parameters
4844
4870
  ----------
4845
4871
  collection_guid: str
4846
- The guid of the collection to update.
4872
+ The guid of the collection to delete.
4847
4873
 
4848
4874
  cascade: bool, optional, defaults to True
4849
4875
  If true, a cascade delete is performed.
@@ -4885,13 +4911,13 @@ class CollectionManager(Client2):
4885
4911
 
4886
4912
  def delete_collection(self, collection_guid: str, body: dict | DeleteRequestBody = None,
4887
4913
  cascade: bool = False) -> None:
4888
- """Delete a collection. It is detected from all parent elements. If members are anchored to the collection
4914
+ """Delete a collection. It is deleted from all parent elements. If members are anchored to the collection
4889
4915
  then they are also deleted.
4890
4916
 
4891
4917
  Parameters
4892
4918
  ----------
4893
4919
  collection_guid: str
4894
- The guid of the collection to update.
4920
+ The guid of the collection to delete.
4895
4921
 
4896
4922
  cascade: bool, optional, defaults to True
4897
4923
  If true, a cascade delete is performed.
@@ -5352,46 +5378,113 @@ class CollectionManager(Client2):
5352
5378
  self._async_get_member_list(collection_guid, collection_name, collection_qname))
5353
5379
  return resp
5354
5380
 
5355
-
5356
- def _extract_collection_properties(self, element: dict) -> dict:
5381
+ def _extract_digital_product_properties(self, element: dict, guid: str, output_format: str) -> dict:
5382
+ props = element["properties"]
5383
+ digital_prod = DigitalProductProperties.model_validate(props)
5384
+ added_props = get_defined_field_values(digital_prod)
5385
+
5386
+ used_by_digital_products = element.get("usedByDigitalProducts", "")
5387
+ if isinstance(used_by_digital_products, (list | dict)):
5388
+ used_by_digital_products_list = ""
5389
+ for prod in used_by_digital_products:
5390
+ used_by_digital_products_list += f"{prod["relatedElement"]["properties"]["qualifiedName"]}, "
5391
+ added_props["used_by_digital_products"] = used_by_digital_products_list[:-2]
5392
+
5393
+ uses_digital_products = element.get("usesDigitalProducts", "")
5394
+ if isinstance(uses_digital_products, (list | dict)):
5395
+ uses_digital_products_list = ""
5396
+ for prod in uses_digital_products:
5397
+ uses_digital_products_list += f"{prod["relatedElement"]["properties"]["qualifiedName"]}, "
5398
+ added_props["uses_digital_products"] = uses_digital_products_list[:-2]
5399
+
5400
+ return added_props
5401
+
5402
+ def _extract_agreement_properties(self, element: dict, guid: str, output_format: str) -> dict:
5403
+ props = element["properties"]
5404
+ # agreement = Collections.model_validate(props)
5405
+ # added_props = get_defined_field_values(agreement)
5406
+
5407
+ added_props = {}
5408
+ agreement_items = element.get("agreementItems", "")
5409
+ if isinstance(agreement_items, (list | dict)):
5410
+ agreement_items_list = ""
5411
+ for item in agreement_items:
5412
+ agreement_items_list += f"{item["relatedElement"]["properties"]["qualifiedName"]}, "
5413
+ added_props["agreementItems"] = agreement_items_list[:-2]
5414
+
5415
+ return added_props
5416
+
5417
+ def _extract_collection_properties(self, element: dict, columns_struct: dict) -> dict:
5357
5418
  """
5358
- Extract common properties from a collection element.
5419
+ Extract common properties from a collection element and populate into the provided columns_struct.
5359
5420
 
5360
5421
  Args:
5361
5422
  element (dict): The collection element
5423
+ columns_struct (dict): The columns structure to populate
5362
5424
 
5363
5425
  Returns:
5364
- dict: Dictionary of extracted properties
5426
+ dict: columns_struct with column 'value' fields populated
5365
5427
  """
5366
-
5367
- props = _extract_referenceable_properties(element)
5428
+ # First, populate from element.properties using the utility
5429
+ col_data = populate_columns_from_properties(element, columns_struct)
5430
+
5431
+ columns_list = col_data.get("formats", {}).get("columns", [])
5432
+
5433
+ # Populate header-derived values
5434
+ header_props = _extract_referenceable_properties(element)
5435
+ for column in columns_list:
5436
+ key = column.get('key')
5437
+ if key in header_props:
5438
+ column['value'] = header_props.get(key)
5439
+ elif isinstance(key, str) and key.lower() == 'guid':
5440
+ column['value'] = header_props.get('GUID')
5441
+
5442
+ # Derived/computed fields
5443
+ # collectionCategories are classifications
5368
5444
  classification_names = ""
5369
- # classifications = element['elementHeader'].get("classifications", [])
5370
- for classification in props['classifications']:
5445
+ classifications = element.get('elementHeader', {}).get("collectionCategories", [])
5446
+ for classification in classifications:
5371
5447
  classification_names += f"{classification['classificationName']}, "
5372
- props["classifications"] = classification_names[:-2] # why?
5448
+ if classification_names:
5449
+ for column in columns_list:
5450
+ if column.get('key') == 'classifications':
5451
+ column['value'] = classification_names[:-2]
5452
+ break
5453
+
5454
+ # Populate requested relationship-based columns generically from top-level keys
5455
+ col_data = get_required_relationships(element, col_data)
5373
5456
 
5374
- props['mermaid'] = element.get('mermaidGraph', "") or ""
5457
+ # Subject area classification
5458
+ subject_area = element.get('elementHeader', {}).get("subjectArea", "") or ""
5459
+ subj_val = ""
5460
+ if isinstance(subject_area, dict):
5461
+ subj_val = subject_area.get("classificationProperties", {}).get("subjectAreaName", "")
5462
+ for column in columns_list:
5463
+ if column.get('key') == 'subject_area':
5464
+ column['value'] = subj_val
5465
+ break
5375
5466
 
5376
- member_names = ""
5377
- members = self.get_member_list(collection_guid=props["GUID"])
5378
- if isinstance(members, list):
5379
- for member in members:
5380
- member_names += f"{member['qualifiedName']}, "
5381
- props['members'] = member_names[:-2]
5382
- logger.trace(f"Extracted properties: {props}")
5383
- return props
5467
+ # Mermaid graph
5468
+ mermaid_val = element.get('mermaidGraph', "") or ""
5469
+ for column in columns_list:
5470
+ if column.get('key') == 'mermaid':
5471
+ column['value'] = mermaid_val
5472
+ break
5473
+
5474
+ logger.trace(f"Extracted/Populated columns: {col_data}")
5475
+
5476
+ return col_data
5384
5477
 
5385
5478
 
5386
5479
  def _generate_collection_output(self, elements: dict|list[dict], filter: Optional[str],
5387
- classification_name: Optional[str], output_format: str = "DICT",
5480
+ element_type_name: Optional[str], output_format: str = "DICT",
5388
5481
  output_format_set: dict | str = None) -> str| list[dict]:
5389
5482
  """ Generate output for collections in the specified format.
5390
5483
 
5391
5484
  Args:
5392
5485
  elements (Union[Dict, List[Dict]]): Dictionary or list of dictionaries containing data field elements
5393
5486
  filter (Optional[str]): The search string used to find the elements
5394
- classification_name (Optional[str]): The type of collection
5487
+ element_type_name (Optional[str]): The type of collection
5395
5488
  output_format (str): The desired output format (MD, FORM, REPORT, LIST, DICT, MERMAID, HTML)
5396
5489
  output_format_set (Optional[dict], optional): List of dictionaries containing column data. Defaults
5397
5490
  to None.
@@ -5399,22 +5492,33 @@ class CollectionManager(Client2):
5399
5492
  Returns:
5400
5493
  Union[str, List[Dict]]: Formatted output as a string or list of dictionaries
5401
5494
  """
5402
- if classification_name is None:
5495
+ if element_type_name is None:
5403
5496
  entity_type = "Collections"
5404
5497
  else:
5405
- entity_type = classification_name
5498
+ entity_type = element_type_name
5406
5499
  # First see if the user has specified an output_format_set - either a label or a dict
5500
+ get_additional_props_func = None
5407
5501
  if output_format_set:
5408
5502
  if isinstance(output_format_set, str):
5409
5503
  output_formats = select_output_format_set(output_format_set, output_format)
5410
- if isinstance(output_format_set, dict):
5504
+ elif isinstance(output_format_set, dict):
5411
5505
  output_formats = get_output_format_type_match(output_format_set, output_format)
5412
- # If no output_format was set, then use the initial_classifications to lookup the output format
5413
- elif classification_name:
5414
- output_formats = select_output_format_set(classification_name, output_format)
5506
+
5507
+ # If no output_format was set, then use the element_type_name to lookup the output format
5508
+ elif element_type_name:
5509
+ output_formats = select_output_format_set(element_type_name, output_format)
5415
5510
  else:
5416
5511
  # fallback to collections or entity type
5417
5512
  output_formats = select_output_format_set(entity_type,output_format)
5513
+ if output_formats is None:
5514
+ output_formats = select_output_format_set("Default", output_format)
5515
+
5516
+ if output_formats:
5517
+ get_additional_props_name = output_formats.get("get_additional_props", {}).get("function", None)
5518
+ if isinstance(get_additional_props_name, str):
5519
+ class_name, method_name = get_additional_props_name.split(".")
5520
+ if hasattr(self, method_name):
5521
+ get_additional_props_func = getattr(self, method_name)
5418
5522
 
5419
5523
  logger.trace(f"Executing generate_collection_output for {entity_type}: {output_formats}")
5420
5524
  return generate_output(
@@ -5423,7 +5527,7 @@ class CollectionManager(Client2):
5423
5527
  entity_type,
5424
5528
  output_format,
5425
5529
  self._extract_collection_properties,
5426
- None,
5530
+ get_additional_props_func,
5427
5531
  output_formats,
5428
5532
  )
5429
5533
 
@@ -16,7 +16,7 @@ from pyegeria._client import Client
16
16
  from pyegeria._globals import NO_ELEMENTS_FOUND, NO_GUID_RETURNED, NO_MEMBERS_FOUND
17
17
  from pyegeria._validators import validate_guid, validate_search_string
18
18
  from pyegeria.output_formatter import (generate_output,
19
- _extract_referenceable_properties)
19
+ _extract_referenceable_properties, get_required_relationships)
20
20
  from pyegeria.utils import body_slimmer, dynamic_catch
21
21
 
22
22
 
@@ -6431,34 +6431,63 @@ class CollectionManager(Client2):
6431
6431
  return resp
6432
6432
 
6433
6433
 
6434
- def _extract_collection_properties(self, element: dict) -> dict:
6434
+ def _extract_collection_properties(self, element: dict, columns_struct: dict) -> dict:
6435
6435
  """
6436
- Extract common properties from a collection element.
6436
+ Extract common properties from a collection element and populate into the provided columns_struct.
6437
6437
 
6438
6438
  Args:
6439
6439
  element (dict): The collection element
6440
+ columns_struct (dict): The columns structure to populate
6440
6441
 
6441
6442
  Returns:
6442
- dict: Dictionary of extracted properties
6443
+ dict: columns_struct with column 'value' fields populated
6443
6444
  """
6444
-
6445
- props = _extract_referenceable_properties(element)
6446
- classification_names = ""
6447
- # classifications = element['elementHeader'].get("classifications", [])
6448
- for classification in props['classifications']:
6449
- classification_names += f"{classification['classificationName']}, "
6450
- props["classifications"] = classification_names[:-2] # why?
6451
-
6452
- props['mermaid'] = element.get('mermaidGraph', "") or ""
6453
-
6445
+ col_data = columns_struct
6446
+ # Populate display/qualified/description etc. from properties via output formatter util not available here,
6447
+ # so we do minimal direct mapping for OMVS variant.
6448
+ props = element.get('properties', {}) or {}
6449
+ columns_list = col_data.get('formats', {}).get('columns', [])
6450
+
6451
+ # Header props
6452
+ header_props = _extract_referenceable_properties(element)
6453
+ for column in columns_list:
6454
+ key = column.get('key')
6455
+ # from element.properties (camelCase); keys here are snake_case or GUID
6456
+ if isinstance(key, str):
6457
+ if key.lower() == 'guid':
6458
+ column['value'] = header_props.get('GUID')
6459
+ elif key in header_props:
6460
+ column['value'] = header_props.get(key)
6461
+ else:
6462
+ # simple camelCase mapping inline to reduce imports here
6463
+ snake_parts = key.split('_')
6464
+ camel = snake_parts[0] + ''.join([p.capitalize() if p.lower() != 'guid' else 'GUID' for p in snake_parts[1:]])
6465
+ if camel in props:
6466
+ column['value'] = props.get(camel)
6467
+
6468
+ # Mermaid graph
6469
+ for column in columns_list:
6470
+ if column.get('key') == 'mermaid':
6471
+ column['value'] = element.get('mermaidGraph', '') or ''
6472
+ break
6473
+
6474
+ # Members list (uses API call)
6454
6475
  member_names = ""
6455
- members = self.get_member_list(collection_guid=props["GUID"])
6476
+ members = self.get_member_list(collection_guid=header_props.get('GUID'))
6456
6477
  if isinstance(members, list):
6457
6478
  for member in members:
6458
6479
  member_names += f"{member['qualifiedName']}, "
6459
- props['members'] = member_names[:-2]
6460
- logger.trace(f"Extracted properties: {props}")
6461
- return props
6480
+ member_names = member_names[:-2]
6481
+ for column in columns_list:
6482
+ if column.get('key') == 'members':
6483
+ column['value'] = member_names
6484
+ break
6485
+
6486
+ # Populate any additional requested relationship-based columns generically
6487
+ col_data = get_required_relationships(element, col_data)
6488
+
6489
+ logger.trace(f"Extracted/Populated columns: {col_data}")
6490
+ return col_data
6462
6491
 
6463
6492
 
6464
6493
  def _generate_collection_output(self, elements: dict|list[dict], filter: Optional[str],
@@ -8,7 +8,7 @@ AssetCatalog, CollectionManager, GlossaryManager, and ProjectManager.
8
8
  """
9
9
  from pyegeria.egeria_my_client import EgeriaMy
10
10
  from pyegeria.asset_catalog_omvs import AssetCatalog
11
- from pyegeria.collection_manager_omvs import CollectionManager
11
+ from pyegeria.collection_manager import CollectionManager
12
12
  from pyegeria.glossary_manager_omvs import GlossaryManager
13
13
  from pyegeria.project_manager_omvs import ProjectManager
14
14
 
pyegeria/egeria_client.py CHANGED
@@ -15,6 +15,7 @@ for all use cases..using the more role based clients is often appropriate:
15
15
  from pyegeria.asset_catalog_omvs import AssetCatalog
16
16
  from pyegeria.collection_manager_omvs import CollectionManager
17
17
  from pyegeria.glossary_manager_omvs import GlossaryManager
18
+ from pyegeria.governance_officer import GovernanceOfficer
18
19
  from pyegeria.project_manager_omvs import ProjectManager
19
20
  from pyegeria.automated_curation_omvs import AutomatedCuration
20
21
  from pyegeria.classification_manager_omvs import ClassificationManager
@@ -30,6 +31,7 @@ from pyegeria.registered_info import RegisteredInfo
30
31
  from pyegeria.valid_metadata_omvs import ValidMetadataManager
31
32
  from pyegeria.egeria_config_client import EgeriaConfig
32
33
  from pyegeria.data_designer_omvs import DataDesigner
34
+ from pyegeria.governance_officer import GovernanceOfficer
33
35
  # from pyegeria.md_processing_utils import render_markdown
34
36
 
35
37
 
@@ -55,6 +57,7 @@ class Egeria(
55
57
  SolutionArchitect,
56
58
  EgeriaConfig,
57
59
  DataDesigner,
60
+ GovernanceOfficer
58
61
  ):
59
62
  """
60
63
  Client to issue Runtime status requests.
@@ -129,4 +132,7 @@ class Egeria(
129
132
  DataDesigner.__init__(
130
133
  self, view_server, platform_url, user_id, user_pwd, token
131
134
  )
135
+ GovernanceOfficer.__init__(
136
+ self, view_server, platform_url, user_id, user_pwd, token
137
+ )
132
138
  print(Egeria.mro())
@@ -15,7 +15,8 @@ from pyegeria.runtime_manager_omvs import RuntimeManager
15
15
  from pyegeria.solution_architect_omvs import SolutionArchitect
16
16
  from pyegeria.template_manager_omvs import TemplateManager
17
17
  from pyegeria.valid_metadata_omvs import ValidMetadataManager
18
- from pyegeria.governance_officer_omvs import GovernanceOfficer
18
+ from pyegeria.governance_officer import GovernanceOfficer
19
+ from pyegeria.collection_manager import CollectionManager
19
20
 
20
21
 
21
22
  class EgeriaTech(
@@ -31,6 +32,7 @@ class EgeriaTech(
31
32
  DataDesigner,
32
33
  TemplateManager,
33
34
  GovernanceOfficer,
35
+ CollectionManager
34
36
  ):
35
37
  """
36
38
  Client for technical Egeria users.
@@ -94,6 +96,9 @@ class EgeriaTech(
94
96
  GovernanceOfficer.__init__(
95
97
  self, view_server, platform_url, user_id, user_pwd, token
96
98
  )
99
+ CollectionManager.__init__(
100
+ self, view_server, platform_url, user_id, user_pwd, token
101
+ )
97
102
 
98
103
  if __name__ == "__main__":
99
104
  print("Main-Tech Client")