pyegeria 5.4.0.22__py3-none-any.whl → 5.4.0.23__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 (120) hide show
  1. commands/cat/.DS_Store +0 -0
  2. commands/cat/.env +8 -0
  3. commands/cat/debug_log.log +0 -0
  4. commands/cat/list_collections.py +15 -6
  5. commands/cat/list_format_set.py +90 -85
  6. commands/cat/logs/pyegeria.log +136 -0
  7. commands/cli/debug_log.log +0 -0
  8. commands/ops/logs/pyegeria.log +0 -0
  9. md_processing/.DS_Store +0 -0
  10. md_processing/dr-egeria-outbox/Collections-2025-08-12-13-30-37.md +163 -0
  11. md_processing/dr-egeria-outbox/Collections-2025-08-12-13-35-58.md +474 -0
  12. md_processing/dr_egeria_inbox/Derive-Dr-Gov-Defs.md +8 -0
  13. md_processing/dr_egeria_inbox/Dr.Egeria Templates.md +873 -0
  14. md_processing/dr_egeria_inbox/arch_test.md +57 -0
  15. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +254 -0
  16. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +696 -0
  17. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +254 -0
  18. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +298 -0
  19. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +608 -0
  20. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +94 -0
  21. md_processing/dr_egeria_inbox/archive/freddie_intro.md +284 -0
  22. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +275 -0
  23. md_processing/dr_egeria_inbox/archive/test-term.md +110 -0
  24. md_processing/dr_egeria_inbox/cat_test.md +100 -0
  25. md_processing/dr_egeria_inbox/collections.md +39 -0
  26. md_processing/dr_egeria_inbox/data_designer_debug.log +6 -0
  27. md_processing/dr_egeria_inbox/data_designer_out.md +60 -0
  28. md_processing/dr_egeria_inbox/data_designer_search_test.md +11 -0
  29. md_processing/dr_egeria_inbox/data_field.md +54 -0
  30. md_processing/dr_egeria_inbox/data_spec.md +77 -0
  31. md_processing/dr_egeria_inbox/data_spec_test.md +2406 -0
  32. md_processing/dr_egeria_inbox/data_test.md +179 -0
  33. md_processing/dr_egeria_inbox/data_test2.md +429 -0
  34. md_processing/dr_egeria_inbox/data_test3.md +462 -0
  35. md_processing/dr_egeria_inbox/dr_egeria_data_designer_1.md +124 -0
  36. md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +168 -0
  37. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +280 -0
  38. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +313 -0
  39. md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +1073 -0
  40. md_processing/dr_egeria_inbox/dr_egeria_isc1.md +44 -0
  41. md_processing/dr_egeria_inbox/generated_help_report.md +9 -0
  42. md_processing/dr_egeria_inbox/glossary_list.md +5 -0
  43. md_processing/dr_egeria_inbox/glossary_search_test.md +40 -0
  44. md_processing/dr_egeria_inbox/glossary_test1.md +324 -0
  45. md_processing/dr_egeria_inbox/gov_def.md +424 -0
  46. md_processing/dr_egeria_inbox/gov_def2.md +447 -0
  47. md_processing/dr_egeria_inbox/product.md +50 -0
  48. md_processing/dr_egeria_inbox/rel.md +8 -0
  49. md_processing/dr_egeria_inbox/sb.md +119 -0
  50. md_processing/dr_egeria_inbox/solution-components.md +136 -0
  51. md_processing/dr_egeria_inbox/solution_blueprints.md +118 -0
  52. md_processing/dr_egeria_inbox/synonym_test.md +42 -0
  53. md_processing/dr_egeria_inbox/t2.md +268 -0
  54. md_processing/dr_egeria_outbox/.obsidian/app.json +1 -0
  55. md_processing/dr_egeria_outbox/.obsidian/appearance.json +1 -0
  56. md_processing/dr_egeria_outbox/.obsidian/community-plugins.json +6 -0
  57. md_processing/dr_egeria_outbox/.obsidian/core-plugins.json +31 -0
  58. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/data.json +10 -0
  59. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/main.js +4459 -0
  60. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/manifest.json +10 -0
  61. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/data.json +3 -0
  62. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/main.js +153 -0
  63. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
  64. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
  65. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/main.js +500 -0
  66. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
  67. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
  68. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/main.js +37 -0
  69. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
  70. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/styles.css +220 -0
  71. md_processing/dr_egeria_outbox/.obsidian/types.json +28 -0
  72. md_processing/dr_egeria_outbox/.obsidian/workspace.json +220 -0
  73. md_processing/dr_egeria_outbox/Untitled.canvas +1 -0
  74. md_processing/dr_egeria_outbox/friday/processed-2025-07-18 15:00-product.md +62 -0
  75. md_processing/dr_egeria_outbox/friday/processed-2025-07-18 15:13-product.md +62 -0
  76. md_processing/dr_egeria_outbox/friday/processed-2025-07-20 13:23-product.md +47 -0
  77. md_processing/dr_egeria_outbox/friday/processed-2025-08-01 11:55-data_test3.md +503 -0
  78. md_processing/dr_egeria_outbox/monday/processed-2025-07-14 12:38-data_designer_out.md +663 -0
  79. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 10:52-generated_help_report.md +2744 -0
  80. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 18:38-collections.md +62 -0
  81. md_processing/dr_egeria_outbox/monday/processed-2025-08-01 11:34-gov_def.md +444 -0
  82. md_processing/dr_egeria_outbox/processed-2025-08-03 16:05-glossary_list.md +37 -0
  83. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 14:55-product.md +77 -0
  84. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 15:05-product.md +75 -0
  85. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 15:11-product.md +74 -0
  86. md_processing/dr_egeria_outbox/sunday/processed-2025-07-20 20:40-collections.md +49 -0
  87. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +719 -0
  88. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +41 -0
  89. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +33 -0
  90. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +192 -0
  91. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:08-gov_def2.md +486 -0
  92. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:10-gov_def2.md +486 -0
  93. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:53-gov_def2.md +486 -0
  94. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:54-gov_def2.md +486 -0
  95. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:03-gov_def2.md +486 -0
  96. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:06-gov_def2.md +486 -0
  97. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:10-gov_def2.md +486 -0
  98. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-16 19:15-gov_def2.md +527 -0
  99. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-17 12:08-gov_def2.md +527 -0
  100. md_processing/dr_egeria_outbox/tuesday/processed-2025-07-17 14:27-gov_def2.md +485 -0
  101. md_processing/md_processing_utils/debug_log.log +0 -0
  102. md_processing/md_processing_utils/solution_architect_log.log +0 -0
  103. pyegeria/.DS_Store +0 -0
  104. pyegeria/__init__.py +2 -2
  105. pyegeria/_client_new.py +45 -49
  106. pyegeria/_output_format_models.py +22 -17
  107. pyegeria/_output_formats.py +105 -32
  108. pyegeria/collection_manager.py +154 -50
  109. pyegeria/collection_manager_omvs.py +47 -18
  110. pyegeria/egeria_cat_client.py +1 -1
  111. pyegeria/egeria_client.py +6 -0
  112. pyegeria/egeria_tech_client.py +6 -1
  113. pyegeria/governance_officer.py +2515 -0
  114. pyegeria/models.py +22 -6
  115. pyegeria/output_formatter.py +298 -79
  116. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.23.dist-info}/METADATA +1 -1
  117. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.23.dist-info}/RECORD +120 -18
  118. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.23.dist-info}/LICENSE +0 -0
  119. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.23.dist-info}/WHEEL +0 -0
  120. {pyegeria-5.4.0.22.dist-info → pyegeria-5.4.0.23.dist-info}/entry_points.txt +0 -0
pyegeria/models.py CHANGED
@@ -215,7 +215,7 @@ class NewRelationshipRequestBody(RequestBody):
215
215
  class_: Annotated[Literal["NewRelationshipRequestBody"], Field(alias="class")]
216
216
  make_anchor: bool | None = False
217
217
  anchor_scope_guid: str | None = None
218
- properties: RelationshipBeanProperties
218
+ properties: dict | RelationshipBeanProperties | None = None
219
219
 
220
220
 
221
221
  class DeleteRequestBody(RequestBody):
@@ -250,11 +250,11 @@ class NewElementRequestBody(RequestBody):
250
250
  is_own_anchor: bool | None = True
251
251
  anchor_scope_guid: str | None = None
252
252
  initial_classifications: Dict[str, InitialClassifications] | None = None
253
- initial_status: ValidStatusValues = ValidStatusValues.ACTIVE
253
+ initial_status: ValidStatusValues | str= ValidStatusValues.ACTIVE
254
254
  parent_guid: str | None = None
255
255
  parent_relationship_type_name: str | None = None
256
256
  parent_at_end_1: bool | None = True
257
- properties: ReferenceableProperties
257
+ properties: dict | None = None
258
258
 
259
259
 
260
260
  class NewClassificationRequestBody(RequestBody):
@@ -292,8 +292,8 @@ class UpdateElementRequestBody(PyegeriaModel):
292
292
  class_: Annotated[Literal["UpdateElementRequestBody"], Field(alias="class")]
293
293
  properties: dict[str, Any] = {}
294
294
  merge_update: bool | None = True
295
- external_source_guid: str = None
296
- external_source_name: str = None
295
+ external_source_guid: str | None = None
296
+ external_source_name: str | None = None
297
297
  effective_time: datetime | None = None
298
298
  for_lineage: bool | None = False
299
299
  for_duplicate_processing: bool | None = False
@@ -311,7 +311,7 @@ class UpdateStatusRequestBody(PyegeriaModel):
311
311
 
312
312
  class GetRequestBody(PyegeriaModel):
313
313
  class_: Annotated[Literal["GetRequestBody"], Field(alias="class")]
314
- metadata_element_type_name: str | None = None
314
+ # metadata_element_type_name: list[str] | None = None
315
315
  metadata_element_subtype_names: list[str] | None = None
316
316
  skip_relationships: list[str] | None = None
317
317
  include_only_relationships: list[str] | None = None
@@ -349,8 +349,24 @@ class SearchStringRequestBody(ResultsRequestBody):
349
349
 
350
350
 
351
351
  #######
352
+ # This gets only the fields in the most specific model
353
+ def get_defined_fields(model):
354
+ return {
355
+ field_name: field
356
+ for field_name, field in model.__fields__.items()
357
+ if field_name in model.__annotations__ # Only fields defined in the current model
358
+ }
359
+
360
+ def get_defined_field_values(model_instance):
361
+ # Extract the subset of the model's fields
362
+ defined_fields = get_defined_fields(model_instance.__class__).keys()
363
+ # Return only the defined fields with their values
364
+ return {field: getattr(model_instance, field) for field in defined_fields}
352
365
 
353
366
 
367
+
368
+ #######
369
+
354
370
  # --- Custom Base Model for JSON Key Conversion ---
355
371
 
356
372
 
@@ -8,6 +8,7 @@ from loguru import logger
8
8
 
9
9
  from pyegeria.mermaid_utilities import construct_mermaid_web
10
10
  from pyegeria._output_formats import select_output_format_set, MD_SEPARATOR
11
+ from pyegeria.models import to_camel_case
11
12
 
12
13
  """
13
14
  Note on select_output_format_set function:
@@ -31,21 +32,21 @@ def _extract_referenceable_properties(element: dict[str, Any]) -> dict[str, Any]
31
32
  version = element['elementHeader']["versions"].get("version", None)
32
33
  type_name = element['elementHeader']["type"].get("typeName", None)
33
34
  classifications = element['elementHeader'].get("classifications", [])
34
-
35
- # Get attributes from properties
36
- properties = element['properties']
37
- display_name = properties.get("name", "") or ""
38
- if display_name == "":
39
- display_name = properties.get("displayName","")
40
- description = properties.get("description", "") or ""
41
- qualified_name = properties.get("qualifiedName", "") or ""
42
- category = properties.get("category", "") or ""
43
- version_identifier = properties.get("versionIdentifier", "") or ""
44
- additional_properties = properties.get("additionalProperties", {}) or {}
45
- extended_properties = properties.get("extendedProperties", {}) or {}
46
35
  effective_from = element['elementHeader'].get("effectiveFrom", None)
47
36
  effective_to = element['elementHeader'].get("effectiveTo", None)
48
37
 
38
+ # Get attributes from properties
39
+ # properties = element['properties']
40
+ # display_name = properties.get("name", "") or ""
41
+ # if display_name == "":
42
+ # display_name = properties.get("displayName","")
43
+ # description = properties.get("description", "") or ""
44
+ # qualified_name = properties.get("qualifiedName", "") or ""
45
+ # category = properties.get("category", "") or ""
46
+ # version_identifier = properties.get("versionIdentifier", "") or ""
47
+ # additional_properties = properties.get("additionalProperties", {}) or {}
48
+ # extended_properties = properties.get("extendedProperties", {}) or {}
49
+ #
49
50
  return {
50
51
  "GUID": guid,
51
52
  "metadata_collection_id": metadata_collection_id,
@@ -58,13 +59,13 @@ def _extract_referenceable_properties(element: dict[str, Any]) -> dict[str, Any]
58
59
  "type_name": type_name,
59
60
  "classifications": classifications,
60
61
 
61
- "display_name": display_name,
62
- "description": description,
63
- "qualified_name": qualified_name,
64
- "category": category,
65
- "version_identifier": version_identifier,
66
- "additional_properties": additional_properties,
67
- "extended_properties": extended_properties,
62
+ # "display_name": display_name,
63
+ # "description": description,
64
+ # "qualified_name": qualified_name,
65
+ # "category": category,
66
+ # "version_identifier": version_identifier,
67
+ # "additional_properties": additional_properties,
68
+ # "extended_properties": extended_properties,
68
69
  "effective_from": effective_from,
69
70
  "effective_to": effective_to,
70
71
  }
@@ -228,7 +229,154 @@ def format_for_markdown_table(text: str, guid: str = None) -> str:
228
229
  return t
229
230
 
230
231
 
231
- def generate_entity_md(elements: List[Dict],
232
+ def populate_columns_from_properties(element: dict, columns_struct: dict) -> dict:
233
+ """
234
+ Populate a columns_struct with values from the element's properties.
235
+
236
+ The element dict is expected to have a nested 'properties' dict whose keys are in camelCase.
237
+ The columns_struct is expected to follow the format returned by select_output_format_set, where
238
+ columns are located at columns_struct['formats']['columns'] and each column is a dict containing
239
+ at least a 'key' field expressed in snake_case. For each column whose snake_case key corresponds
240
+ to a key in the element properties (after converting to camelCase), this function adds a 'value'
241
+ entry to the column with the matching property's value.
242
+
243
+ Args:
244
+ element: The element containing a 'properties' dict with camelCase keys.
245
+ columns_struct: The columns structure whose columns have snake_case 'key' fields.
246
+
247
+ Returns:
248
+ The updated columns_struct (the input structure is modified in place and also returned).
249
+ """
250
+ if not isinstance(columns_struct, dict):
251
+ return columns_struct
252
+
253
+ props = (element or {}).get('properties') or {}
254
+ # If properties is not a dict, do nothing
255
+ if not isinstance(props, dict):
256
+ return columns_struct
257
+
258
+ # Get the columns list if present
259
+ formats = columns_struct.get('formats') or {}
260
+ columns = formats.get('columns') if isinstance(formats, dict) else None
261
+ if not isinstance(columns, list):
262
+ return columns_struct
263
+
264
+ for col in columns:
265
+ try:
266
+ key_snake = col.get('key') if isinstance(col, dict) else None
267
+ if not key_snake:
268
+ continue
269
+ # Convert the snake_case key to camelCase to look up in properties
270
+ key_camel = to_camel_case(key_snake)
271
+ if key_camel in props:
272
+ col['value'] = props.get(key_camel)
273
+ except Exception as e:
274
+ # Be resilient; log and continue
275
+ logger.debug(f"populate_columns_from_properties: skipping column due to error: {e}")
276
+ continue
277
+
278
+ return columns_struct
279
+
280
+
281
+ def get_required_relationships(element: dict, columns_struct: dict) -> dict:
282
+ """
283
+ Populate relationship-derived column values in columns_struct based on top-level keys in the element.
284
+
285
+ This function inspects the requested columns in columns_struct, converts each column key from
286
+ snake_case to camelCase, and if a matching top-level key exists in the element, parses that value
287
+ (typically lists of relationship beans) into a human-readable value (e.g., a comma-separated list
288
+ of qualified names) and stores it under the column's 'value'. Columns not specified in the
289
+ columns_struct are ignored. Existing non-empty 'value's are left as-is.
290
+
291
+ Example: if a column with key 'member_of_collections' is present, this function will look for the
292
+ top-level key 'memberOfCollections' in the element and derive a value if found.
293
+
294
+ Args:
295
+ element: The element dictionary containing top-level relationship lists (e.g., associatedGlossaries,
296
+ memberOfCollections, collectionMembers).
297
+ columns_struct: The columns structure to augment with derived 'value's.
298
+
299
+ Returns:
300
+ The updated columns_struct (modified in place and returned).
301
+ """
302
+ if not isinstance(columns_struct, dict):
303
+ return columns_struct
304
+
305
+ formats = columns_struct.get('formats') or {}
306
+ columns = formats.get('columns') if isinstance(formats, dict) else None
307
+ if not isinstance(columns, list):
308
+ return columns_struct
309
+
310
+ def _extract_name_from_item(item: Any) -> Optional[str]:
311
+ """Best-effort extraction of a display/qualified name from a relationship item."""
312
+ try:
313
+ if isinstance(item, dict):
314
+ # Common pattern: item['relatedElement']['properties']['qualifiedName']
315
+ related = item.get('relatedElement') or item.get('related_element')
316
+ if isinstance(related, dict):
317
+ props = related.get('properties') or {}
318
+ name = (
319
+ props.get('qualifiedName')
320
+ or props.get('displayName')
321
+ or props.get('name')
322
+ )
323
+ if name:
324
+ return name
325
+ # Sometimes the properties may be at the top level of the item
326
+ name = (
327
+ item.get('qualifiedName')
328
+ or item.get('displayName')
329
+ or item.get('name')
330
+ )
331
+ if name:
332
+ return name
333
+ elif isinstance(item, str):
334
+ return item
335
+ except Exception as e:
336
+ logger.debug(f"get_required_relationships: error extracting name from item: {e}")
337
+ return None
338
+
339
+ for col in columns:
340
+ try:
341
+ if not isinstance(col, dict):
342
+ continue
343
+ key_snake = col.get('key')
344
+ if not key_snake:
345
+ continue
346
+ # If already has a non-empty value, don't overwrite
347
+ if col.get('value') not in (None, ""):
348
+ continue
349
+
350
+ # Convert the snake_case key to camelCase to look up in top-level element
351
+ key_camel = to_camel_case(key_snake)
352
+ if key_camel not in element:
353
+ continue
354
+
355
+ top_val = element.get(key_camel)
356
+ derived_value: str = ""
357
+ if isinstance(top_val, list):
358
+ names: List[str] = []
359
+ for item in top_val:
360
+ nm = _extract_name_from_item(item)
361
+ if nm:
362
+ names.append(nm)
363
+ derived_value = ", ".join(names)
364
+ elif isinstance(top_val, dict):
365
+ nm = _extract_name_from_item(top_val)
366
+ derived_value = nm or ""
367
+ else:
368
+ # Primitive or unexpected type; coerce to string if not None
369
+ derived_value = str(top_val) if top_val is not None else ""
370
+
371
+ col['value'] = derived_value
372
+ except Exception as e:
373
+ logger.debug(f"get_required_relationships: skipping column due to error: {e}")
374
+ continue
375
+
376
+ return columns_struct
377
+
378
+
379
+ def generate_entity_md(elements: List[Dict],
232
380
  elements_action: str,
233
381
  output_format: str,
234
382
  entity_type: str,
@@ -250,70 +398,105 @@ def generate_entity_md(elements: List[Dict],
250
398
  Returns:
251
399
  str: Markdown representation
252
400
  """
253
- elements_md = ""
254
- columns = columns_struct['formats'].get('columns') if columns_struct else None
401
+ heading = columns_struct.get("heading")
402
+ if heading == "Default Base Attributes":
403
+ elements_md = "## Reporting on Default Base Attributes - Perhaps couldn't find a valid combination of output_format_set and output_format?\n\n"
404
+ else:
405
+ elements_md = ""
406
+ base_columns = columns_struct['formats'].get('columns') if columns_struct else None
255
407
 
256
408
  for element in elements:
257
409
  if element is None:
258
- continue
259
- props = extract_properties_func(element)
410
+ continue
411
+ guid = element.get('elementHeader', {}).get('guid')
412
+
413
+ # Prefer new behavior: extractor returns an updated columns_struct with values
414
+ returned_struct = None
415
+ if columns_struct is not None:
416
+ try:
417
+ returned_struct = extract_properties_func(element, columns_struct)
418
+ except TypeError:
419
+ # Fallback for legacy extractors without columns_struct parameter
420
+ returned_struct = None
421
+
422
+ # Legacy fallback: get props dict if no columns_struct provided/returned
423
+ props = {}
424
+ if returned_struct is None:
425
+ props = extract_properties_func(element) if callable(extract_properties_func) else {}
260
426
 
261
427
  # Get additional properties if function is provided
262
428
  additional_props = {}
263
429
  if get_additional_props_func:
264
- additional_props = get_additional_props_func(element,props['GUID'], output_format)
430
+ # Use guid if available, else try to get from props
431
+ guid_for_fmt = guid or props.get('GUID')
432
+ additional_props = get_additional_props_func(element, guid_for_fmt, output_format)
433
+
434
+ # Determine display name
435
+ display_name = None
436
+ if returned_struct is not None:
437
+ cols = returned_struct.get('formats', {}).get('columns', [])
438
+ # Find value from 'display_name' or 'title'
439
+ for col in cols:
440
+ if col.get('key') in ('display_name', 'title'):
441
+ display_name = col.get('value')
442
+ if display_name:
443
+ break
444
+ else:
445
+ display_name = props.get('display_name') or props.get('title')
265
446
 
266
- display_name = props.get('display_name', None)
267
447
  if display_name is None:
268
- display_name = props.get('title', None)
269
- if display_name is None:
270
- display_name = "NO DISPLAY NAME"
448
+ display_name = "NO DISPLAY NAME"
271
449
 
272
450
  # Format header based on output format
273
451
  if output_format in ['FORM', 'MD']:
274
452
  elements_md += f"# {elements_action}\n\n"
275
453
  elements_md += f"## {entity_type} Name \n\n{display_name}\n\n"
276
454
  elif output_format == 'REPORT':
277
- elements_md += f'<a id="{props.get("GUID","No GUID")}"></a>\n# {entity_type} Name: {display_name}\n\n'
455
+ elements_md += f'<a id="{(guid or props.get("GUID") or "No GUID" )}"></a>\n# {entity_type} Name: {display_name}\n\n'
278
456
  else:
279
457
  elements_md += f"## {entity_type} Name \n\n{display_name}\n\n"
280
458
 
281
- # Add attributes based on column spec if available, otherwise, add all
282
- if columns:
283
- for column in columns:
459
+ # Add attributes based on column spec if available, otherwise, add all (legacy)
460
+ if returned_struct is not None:
461
+ cols = returned_struct.get('formats', {}).get('columns', [])
462
+ for column in cols:
463
+ name = column.get('name')
464
+ key = column.get('key')
465
+ value = column.get('value')
466
+ if value in (None, "") and key in additional_props:
467
+ value = additional_props[key]
468
+ if column.get('format'):
469
+ value = format_for_markdown_table(value, guid)
470
+ elements_md += make_md_attribute(name, value, output_format)
471
+ if wk := returned_struct.get("annotations", {}).get("wikilinks"):
472
+ elements_md += ", ".join(wk)
473
+ elif base_columns:
474
+ # If we have columns but extractor didn't return struct, use legacy props lookup
475
+ for column in base_columns:
284
476
  key = column['key']
285
477
  name = column['name']
286
478
  value = ""
287
-
288
- # Check if the key is in props or additional_props
289
479
  if key in props:
290
480
  value = props[key]
291
481
  elif key in additional_props:
292
482
  value = additional_props[key]
293
- # Format the value if needed
294
- if 'format' in column and column['format']:
295
- value = format_for_markdown_table(value, props['GUID'])
296
- # elements_md += make_md_attribute(key.replace('_', ' '), value, output_format)
483
+ if column.get('format'):
484
+ value = format_for_markdown_table(value, guid or props.get('GUID'))
297
485
  elements_md += make_md_attribute(name, value, output_format)
298
-
486
+ if wk := columns_struct.get("annotations", {}).get("wikilinks", None):
487
+ elements_md += ", ".join(wk)
299
488
  else:
489
+ # Legacy path without columns: dump all props
300
490
  for key, value in props.items():
301
491
  if output_format in ['FORM', 'MD', 'DICT'] and key == 'mermaid':
302
492
  continue
303
- if key not in [ 'properties', 'display_name']:
304
- if key == "mermaid" and value == '':
493
+ if key not in ['properties', 'display_name']:
494
+ if key == 'mermaid' and value == '':
305
495
  continue
306
496
  elements_md += make_md_attribute(key.replace('_', ' '), value, output_format)
307
- # Add additional properties
308
497
  for key, value in additional_props.items():
309
498
  elements_md += make_md_attribute(key.replace('_', ' '), value, output_format)
310
499
 
311
- # # Add GUID
312
- # elements_md += make_md_attribute("GUID",props['GUID'], output_format)
313
-
314
- if wk := columns_struct.get("annotations", {}).get("wikilinks", None):
315
- elements_md += ", ".join(wk)
316
- # Add separator if not the last element
317
500
  if element != elements[-1]:
318
501
  elements_md += MD_SEPARATOR
319
502
 
@@ -344,8 +527,12 @@ def generate_entity_md_table(elements: List[Dict],
344
527
  # Handle pluralization - if entity_type ends with 'y', use 'ies' instead of 's'
345
528
  entity_type_plural = f"{entity_type[:-1]}ies" if entity_type.endswith('y') else f"{entity_type}s"
346
529
  columns = columns_struct['formats'].get('columns', [])
530
+ heading = columns_struct.get("heading")
531
+ if heading == "Default Base Attributes":
532
+ elements_md = "## Reporting on Default Base Attributes - Perhaps couldn't find a valid combination of output_format_set and output_format?\n\n"
533
+ else:
534
+ elements_md = ""
347
535
 
348
- elements_md = ""
349
536
  if output_format == "LIST":
350
537
  elements_md = f"# {entity_type_plural} Table\n\n"
351
538
  elements_md += f"{entity_type_plural} found from the search string: `{search_string}`\n\n"
@@ -362,33 +549,47 @@ def generate_entity_md_table(elements: List[Dict],
362
549
 
363
550
  # Add rows
364
551
  for element in elements:
552
+ guid = element.get('elementHeader', {}).get('guid')
553
+
554
+ # Extractor returns columns_struct with values when possible
555
+ try:
556
+ returned_struct = extract_properties_func(element, columns_struct)
557
+ except TypeError:
558
+ returned_struct = None
559
+
560
+ # For help mode, bypass extraction
365
561
  if output_format == "help":
366
- props = element
367
- else:
368
- props = extract_properties_func(element)
562
+ returned_struct = {"formats": {"columns": columns}}
369
563
 
370
- # Get additional properties if function is provided
564
+ # Additional props (if any)
371
565
  additional_props = {}
372
566
  if get_additional_props_func:
373
- additional_props = get_additional_props_func(element,props['GUID'], output_format)
567
+ additional_props = get_additional_props_func(element, guid, output_format)
374
568
 
375
569
  # Build row
376
570
  row = "| "
377
- for column in columns:
378
- key = column['key']
379
- value = ""
380
-
381
- # Check if the key is in props or additional_props
382
- if key in props:
383
- value = props[key]
384
- elif key in additional_props:
385
- value = additional_props[key]
386
-
387
- # Format the value if needed
388
- if 'format' in column and column['format']:
389
- value = format_for_markdown_table(value, props['GUID'])
390
-
391
- row += f"{value} | "
571
+ if returned_struct is not None:
572
+ for column in returned_struct.get('formats', {}).get('columns', []):
573
+ key = column.get('key')
574
+ value = column.get('value')
575
+ if (value in (None, "")) and key in additional_props:
576
+ value = additional_props[key]
577
+ if column.get('format'):
578
+ value = format_for_markdown_table(value, guid)
579
+ row += f"{value} | "
580
+ else:
581
+ # Legacy fallback: read from props dict
582
+ props = extract_properties_func(element)
583
+ for column in columns:
584
+ key = column['key']
585
+ value = ""
586
+ if key in props:
587
+ value = props[key]
588
+ elif key in additional_props:
589
+ value = additional_props[key]
590
+ if column.get('format'):
591
+ value = format_for_markdown_table(value, guid or props.get('GUID'))
592
+ row += f"{value} | "
392
593
 
393
594
  elements_md += row + "\n"
394
595
  if wk := columns_struct.get("annotations",{}).get("wikilinks", None):
@@ -419,38 +620,55 @@ def generate_entity_dict(elements: List[Dict],
419
620
  """
420
621
  result = []
421
622
 
422
- #####
623
+ #####
423
624
  # Add attributes based on column spec if available, otherwise, add all
424
625
  for element in elements:
425
626
  if element is None:
426
627
  continue
427
- props = extract_properties_func(element)
628
+
629
+ guid = element.get('elementHeader', {}).get('guid')
630
+
631
+ returned_struct = None
632
+ if columns_struct is not None:
633
+ try:
634
+ returned_struct = extract_properties_func(element, columns_struct)
635
+ except TypeError:
636
+ returned_struct = None
637
+
428
638
  # Get additional properties if function is provided
429
639
  additional_props = {}
430
640
  if get_additional_props_func:
431
- additional_props = get_additional_props_func(element,props['GUID'], output_format)
641
+ additional_props = get_additional_props_func(element, guid, output_format)
432
642
 
433
643
  # Create entity dictionary
434
644
  entity_dict = {}
435
645
 
436
646
  columns = columns_struct['formats'].get('columns', None) if columns_struct else None
437
- if columns:
647
+ if returned_struct is not None:
648
+ for column in returned_struct.get('formats', {}).get('columns', []):
649
+ key = column.get('key')
650
+ name = column.get('name')
651
+ value = column.get('value')
652
+ if (value in (None, "")) and key in additional_props:
653
+ value = additional_props[key]
654
+ if column.get('format'):
655
+ value = format_for_markdown_table(value, guid)
656
+ entity_dict[name] = value
657
+ elif columns:
438
658
  for column in columns:
439
659
  key = column['key']
440
660
  name = column['name']
441
661
  value = ""
442
-
443
- # Check if the key is in props or additional_props
662
+ props = extract_properties_func(element)
444
663
  if key in props:
445
664
  value = props[key]
446
665
  elif key in additional_props:
447
666
  value = additional_props[key]
448
- # Format the value if needed
449
667
  if column.get('format', None):
450
- value = format_for_markdown_table(value, props['GUID'])
668
+ value = format_for_markdown_table(value, guid or props.get('GUID'))
451
669
  entity_dict[name] = value
452
-
453
670
  else:
671
+ props = extract_properties_func(element)
454
672
  # Add properties based on include/exclude lists
455
673
  for key, value in props.items():
456
674
  if key not in ['properties', 'mermaid']: # Skip the raw properties object
@@ -583,6 +801,7 @@ def generate_output(elements: Union[Dict, List[Dict]],
583
801
  Formatted output as string or list of dictionaries
584
802
  """
585
803
  columns = columns_struct['formats'].get('columns',None) if columns_struct else None
804
+
586
805
  # Ensure elements is a list
587
806
  if isinstance(elements, dict):
588
807
  elements = [elements]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyegeria
3
- Version: 5.4.0.22
3
+ Version: 5.4.0.23
4
4
  Summary: A python client for Egeria
5
5
  License: Apache 2.0
6
6
  Keywords: egeria,metadata,governance