pyegeria 5.4.0.1__py3-none-any.whl → 5.4.0.3__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 (43) hide show
  1. commands/cat/__init__.py +1 -1
  2. commands/cat/dr_egeria_md.py +6 -4
  3. commands/cat/list_collections.py +47 -36
  4. md_processing/__init__.py +5 -2
  5. md_processing/data/commands-working.json +34850 -0
  6. md_processing/data/commands.json +1750 -530
  7. md_processing/md_commands/product_manager_commands.py +171 -220
  8. md_processing/md_processing_utils/common_md_proc_utils.py +9 -0
  9. md_processing/md_processing_utils/common_md_utils.py +15 -2
  10. md_processing/md_processing_utils/md_processing_constants.py +44 -6
  11. pyegeria/__init__.py +8 -4
  12. pyegeria/_client.py +2 -1
  13. pyegeria/_client_new.py +688 -0
  14. pyegeria/_exceptions_new.py +362 -0
  15. pyegeria/_globals.py +3 -1
  16. pyegeria/_output_formats.py +196 -0
  17. pyegeria/_validators.py +72 -199
  18. pyegeria/collection_manager_omvs.py +602 -324
  19. pyegeria/data_designer_omvs.py +251 -203
  20. pyegeria/load_config.py +217 -0
  21. pyegeria/logging_configuration.py +204 -0
  22. pyegeria/output_formatter.py +162 -31
  23. pyegeria/utils.py +99 -61
  24. {pyegeria-5.4.0.1.dist-info → pyegeria-5.4.0.3.dist-info}/METADATA +3 -1
  25. {pyegeria-5.4.0.1.dist-info → pyegeria-5.4.0.3.dist-info}/RECORD +28 -37
  26. commands/cat/debug_log +0 -2806
  27. commands/cat/debug_log.2025-07-15_14-28-38_087378.zip +0 -0
  28. commands/cat/debug_log.2025-07-16_15-48-50_037087.zip +0 -0
  29. md_processing/dr_egeria_outbox-pycharm/.obsidian/app.json +0 -1
  30. md_processing/dr_egeria_outbox-pycharm/.obsidian/appearance.json +0 -1
  31. md_processing/dr_egeria_outbox-pycharm/.obsidian/core-plugins.json +0 -31
  32. md_processing/dr_egeria_outbox-pycharm/.obsidian/workspace.json +0 -177
  33. md_processing/dr_egeria_outbox-pycharm/monday/processed-2025-07-14 12:38-data_designer_out.md +0 -663
  34. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +0 -719
  35. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +0 -41
  36. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +0 -33
  37. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +0 -192
  38. md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-16 19:15-gov_def2.md +0 -527
  39. md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-17 12:08-gov_def2.md +0 -527
  40. md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-17 14:27-gov_def2.md +0 -474
  41. {pyegeria-5.4.0.1.dist-info → pyegeria-5.4.0.3.dist-info}/LICENSE +0 -0
  42. {pyegeria-5.4.0.1.dist-info → pyegeria-5.4.0.3.dist-info}/WHEEL +0 -0
  43. {pyegeria-5.4.0.1.dist-info → pyegeria-5.4.0.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,362 @@
1
+ """
2
+ SPDX-License-Identifier: Apache-2.0
3
+ Copyright Contributors to the ODPi Egeria project.
4
+
5
+ Definitions, utilities and exceptions in support of the Egeria Python Client package.
6
+
7
+ """
8
+ import os
9
+ import json
10
+ from enum import Enum
11
+
12
+ from httpx import Response
13
+ from loguru import logger
14
+ from rich.markdown import Markdown
15
+ from rich.table import Table
16
+ from rich.text import Text
17
+ from rich import print, box
18
+ from rich.console import Console
19
+
20
+
21
+ EGERIA_WIDTH = os.getenv("PYEGERIA_CONSOLE_WIDTH", 200)
22
+ console = Console(width=EGERIA_WIDTH)
23
+
24
+ """
25
+
26
+ The following definitions are used in creating Exception messages.
27
+ They mirror similar definitions in the Egeria core.
28
+ Note that not all of the definitions are currently used - they merely serve as placeholders for future extensions.
29
+
30
+ """
31
+ class PyegeriaErrorCode(Enum):
32
+ """Egeria error codes"""
33
+ CLIENT_ERROR = {
34
+ "http_code": 400,
35
+ "egeria_code": "From Egeria",
36
+ "message_id": "CLIENT_ERROR_400",
37
+ "message_template": "Client error occurred accessing `{0}` with status code `{1}`.",
38
+ "system_action": "The client is unable to connect to the Egeria platform.",
39
+ "user_action": "Check the URL to ensure the valid platform url, server, user id are correct.",
40
+ }
41
+ VALIDATION_ERROR = {
42
+ "http_code": 0,
43
+ "egeria_code": "From Egeria",
44
+ "message_id": "VALIDATION_ERROR_1",
45
+ "message_template": "Invalid parameters were provided -> `{0}`.",
46
+ "system_action": "The parameters provided were invalid.",
47
+ "user_action": "Check that your parameters are correct - please see documentation, if unsure.",
48
+ }
49
+ AUTHORIZATION_ERROR = {
50
+ "http_code": 401,
51
+ "pyegeria_code": "From Egeria",
52
+ "message_id": "AUTHORIZATION_ERROR_401",
53
+ "message_template": "User not authorized received for user - `{0}`.",
54
+ "system_action": "The user credentials provided were not authorized.",
55
+ "user_action": "Check that your user credentials are correct - please see documentation, if unsure.",
56
+ }
57
+ AUTHENTICATION_ERROR = {
58
+ "http_code": 403,
59
+ "egeria_code": "From Egeria",
60
+ "message_id": "AUTHENTICATION_ERROR_403",
61
+ "message_template": "User not authenticated received for user - `{0}`.",
62
+ "system_action": "The user credentials provided were not authenticated.",
63
+ "user_action": "Check that your user credentials and token are valid - please see documentation, if unsure.",
64
+ }
65
+ CONNECTION_ERROR = {
66
+ "http_code": 404,
67
+ "egeria_code": "Connection error",
68
+ "message_id": "CONNECTION_ERROR_1",
69
+ "message_template": "Client failed to connect to the Egeria platform using URL `{0}`.",
70
+ "system_action": "The client is unable to connect to the Egeria platform.",
71
+ "user_action": "Check the URL to ensure the valid platform url, server, user id are correct.",
72
+ }
73
+ EGERIA_ERROR = {
74
+ "http_code": 500,
75
+ "egeria_code": "From Egeria",
76
+ "message_id": "SERVER_ERROR_500",
77
+ "message_template": "Egeria detected error: `{0}`.",
78
+ "system_action": "Server-side issue requires attention.",
79
+ "user_action": "Contact server support."
80
+ }
81
+
82
+ def __str__(self):
83
+ return (
84
+ "\nhttp_code= "
85
+ + str(self.value["http_code"])
86
+ + "\n\t* messageId= "
87
+ + self.value["message_id"]
88
+ + ",\n\t message= "
89
+ + self.value["message_template"]
90
+ + ",\n\t systemAction= "
91
+ + self.value["system_action"]
92
+ + ",\n\t userAction= "
93
+ + self.value["user_action"]
94
+ )
95
+ colors = ["blue", "red", "green"]
96
+
97
+ def print_bullet_list_colored(items, colors)->Text:
98
+ for i, item in enumerate(items):
99
+ bullet_text = Text(f"\t• ", style="bold yellow")
100
+ wrapped_text = Text(item, style=colors[i % len(colors)]) # Rotate colors
101
+ bullet_text.append(wrapped_text)
102
+ return bullet_text
103
+
104
+ def print_bullet_list(items)->Text:
105
+ for key, value in items:
106
+ bullet_text = Text(f"\t• {key}", style="bold yellow")
107
+ wrapped_text = Text(value, style= "blue")
108
+ bullet_text.append(wrapped_text)
109
+ console.print(bullet_text)
110
+ return bullet_text
111
+
112
+
113
+ def flatten_dict_to_string(d: dict) -> str:
114
+ """Flatten a dictionary into a string and replace quotes with backticks."""
115
+ try:
116
+ flat_string = "\n\t".join(
117
+ # Change replace(\"'\", '`') to replace("'", '`')
118
+ f"\t* {key}=`{str(value).replace('\"', '`').replace("'", '`')}`"
119
+ for key, value in d.items()
120
+ )
121
+ return flat_string
122
+ except Exception as e:
123
+ # Corrected syntax for exception chaining
124
+ raise Exception("Error flattening dictionary") from e
125
+
126
+ def format_dict_to_string(d: dict) -> str:
127
+ """
128
+ Converts a dictionary into a printable string of name-value pairs.
129
+ Replaces quotes with backticks and removes braces.
130
+
131
+ Args:
132
+ d (dict): The input dictionary.
133
+
134
+ Returns:
135
+ str: A formatted printable string.
136
+ """
137
+ if isinstance(d, dict):
138
+ name_value_set = {
139
+ f"`{str(value).replace('\"', '`').replace("'", '`')}`"
140
+ for key, value in d.items()
141
+ }
142
+ # Join the set elements into a single printable string
143
+ return ", ".join(name_value_set)
144
+ else:
145
+ return str(d)
146
+
147
+
148
+ class PyegeriaException(Exception):
149
+ """Base exception for My REST Library errors."""
150
+
151
+ def __init__(self, response:Response, error_code: PyegeriaErrorCode,
152
+ context: dict = None, additional_info:dict = None, e:Exception = None) -> None:
153
+ self.response = response
154
+ self.error_code = error_code
155
+ self.error_details = error_code.value
156
+ self.pyegeria_code = self.error_details.get("message_id", "UNKNOWN_ERROR")
157
+ self.response_url = getattr(response, "url", "unknown URL") if response else additional_info.get("endpoint","")
158
+ self.response_code = getattr(response, "status_code", "unknown status code") if response else ""
159
+ self.message = self.error_details["message_template"].format(self.response_url, self.response_code)
160
+ self.system_action = self.error_details.get("system_action", "")
161
+ self.user_action = self.error_details.get("user_action", "")
162
+ # self.original_exception = context.get("exception", {}) if context else None
163
+ self.context = context
164
+ self.additional_info = additional_info or {}
165
+ self.e = e
166
+
167
+
168
+ def __str__(self):
169
+ msg = "\n"
170
+ # ctx_str = flatten_dict_to_string(self.context)
171
+ ctx_str = flatten_dict_to_string(self.context)
172
+
173
+ msg += f"\n=> \t{self.pyegeria_code}"
174
+ msg += f"\n=>\t{self.message}"
175
+ msg += f"\n\t* Context: \n\t{ctx_str}\n"
176
+
177
+ related_http_code = self.additional_info.get('relatedHTTPCode', None)
178
+ if related_http_code:
179
+ if related_http_code != 200:
180
+ msg += f"\t* Egeria error information: \n"
181
+ for key, value in self.additional_info.items():
182
+ msg += f"\t* {key}= `{str(value).replace('\"', '`').replace("'", '`')}`\n"
183
+
184
+ else:
185
+ msg += f"\t* System Action: {self.system_action}\n"
186
+ msg += f"\t* User Action: {self.user_action}\n"
187
+ if self.response:
188
+ msg += f"\t* HTTP Code: {self.response_code}\n"
189
+ return msg
190
+
191
+
192
+ class PyegeriaConnectionException(PyegeriaException):
193
+ """Raised when there's an issue connecting to an Egeria Platform."""
194
+ def __init__(self, context: dict = None, additional_info:dict = None, e: Exception = None) -> None:
195
+ super().__init__(None, PyegeriaErrorCode.CONNECTION_ERROR,
196
+ context, additional_info, e)
197
+ self.message = self.error_details["message_template"].format(self.response_url)
198
+ logger.error(self.__str__())
199
+
200
+ class PyegeriaInvalidParameterException(PyegeriaException):
201
+ """Raised for invalid parameters - parameters that might be missing or incorrect."""
202
+ def __init__(self, response: Response,
203
+ context: dict = None, additional_info: dict = None, e: Exception = None) -> None:
204
+ super().__init__(response, PyegeriaErrorCode.VALIDATION_ERROR,
205
+ context, additional_info, e)
206
+ self.message = self.error_details["message_template"].format(self.additional_info.get('reason', ''))
207
+ logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
208
+
209
+ class PyegeriaClientException(PyegeriaException):
210
+ """Raised for invalid parameters - parameters that might be missing or incorrect."""
211
+ def __init__(self, response: Response,
212
+ context: dict = None, additional_info: dict = None, e: Exception = None) -> None:
213
+ # base_exception = context.get('caught_exception', None)
214
+ super().__init__(response, PyegeriaErrorCode.CLIENT_ERROR,
215
+ context, additional_info, e)
216
+ logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
217
+
218
+
219
+ class PyegeriaAPIException(PyegeriaException):
220
+ """Raised for errors reported by Egeria"""
221
+ def __init__(self, response: Response,
222
+ context: dict = None, additional_info: dict = None, e: Exception = None) -> None:
223
+ super().__init__(response, PyegeriaErrorCode.EGERIA_ERROR,
224
+ context, additional_info, e)
225
+ related_response = response.json()
226
+ self.related_http_code = related_response.get("relatedHTTPCode", None)
227
+ msg = self.__str__()
228
+ if self.related_http_code:
229
+ exception_msg_id = related_response.get("exceptionErrorMessageId", "UNKNOWN_ERROR")
230
+ msg += f"\n\t{self.error_details['message_template'].format(exception_msg_id)}\n"
231
+
232
+ for key, value in related_response.items():
233
+ msg += f"\t\t* {key} = {format_dict_to_string(value)}\n"
234
+
235
+ logger.error(msg, ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
236
+
237
+
238
+
239
+
240
+ # class PyegeriaAuthenticationException(PyegeriaException):
241
+ # """Raised for 401 authentication errors."""
242
+ # def __init__(self, response: Response,
243
+ # context: dict = None, additional_info: dict = None) -> None:
244
+ # super().__init__(response, PyegeriaErrorCode.AUTHENTICATION_ERROR,
245
+ # context, additional_info)
246
+ # self.message = self.error_details["message_template"].format(additional_info.get("userid",""))
247
+ # logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
248
+
249
+ class PyegeriaUnauthorizedException(PyegeriaException):
250
+ """Raised for 403 authorization errors."""
251
+ def __init__(self, response: Response,
252
+ context: dict = None, additional_info: dict = None, e: Exception = None) -> None:
253
+ super().__init__(response, PyegeriaErrorCode.AUTHORIZATION_ERROR,
254
+ context, additional_info, e)
255
+ self.message = self.error_details["message_template"].format(additional_info.get("userid", ""))
256
+ logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
257
+
258
+
259
+
260
+ class PyegeriaNotFoundException(PyegeriaException):
261
+ """Raised for 404 Not Found errors."""
262
+ def __init__(self, response: Response,
263
+ context: dict = None, additional_info: dict = None, e: Exception = None) -> None:
264
+ super().__init__(response, PyegeriaErrorCode.CLIENT_ERROR,
265
+ context, additional_info, e)
266
+ logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
267
+
268
+
269
+ # class PyegeriaInvalidResponseException(PyegeriaException):
270
+ # """Raised when the API returns an unparseable or unexpected response."""
271
+ # def __init__(self, response: Response,
272
+ # context: dict = None, additional_info: dict = None) -> None:
273
+ # super().__init__(response, PyegeriaErrorCode.CLIENT_ERROR,
274
+ # context, additional_info)
275
+ # logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
276
+ #
277
+ #
278
+ # class PyegeriaValidationException(PyegeriaException):
279
+ # """Raised when data sent to the API fails validation, or received data fails Pydantic validation."""
280
+ #
281
+ # def __init__(self, response: Response = None,
282
+ # context: dict = None, additional_info: dict = None) -> None:
283
+ # super().__init__(response, PyegeriaErrorCode.VALIDATION_ERROR,
284
+ # context, additional_info)
285
+ # reason = additional_info.get("reason","")
286
+ # input_parameters = additional_info.get("input_parameters","")
287
+ # self.message = self.error_details["message_template"].format(reason, input_parameters)
288
+ # logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
289
+ #
290
+ # class PyegeriaRequestException(PyegeriaException):
291
+ # """Raised when data sent to the API fails validation, or received data fails Pydantic validation."""
292
+ # def __init__(self, response: Response,
293
+ # context: dict = None, additional_info: dict = None) -> None:
294
+ # super().__init__(response, PyegeriaErrorCode.CLIENT_ERROR,
295
+ # context, additional_info)
296
+ # logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
297
+
298
+
299
+ class PyegeriaUnknownException(PyegeriaException):
300
+ """Raised when data sent to the API fails validation, or received data fails Pydantic validation."""
301
+ def __init__(self, response: Response,
302
+ context: dict = None, additional_info: dict = None, e: Exception = None) -> None:
303
+ super().__init__(response, PyegeriaErrorCode.CLIENT_ERROR,
304
+ context, additional_info, e)
305
+ logger.error(self.__str__(), ip=self.response_url, http_code=self.response_code, pyegeria_code=self.pyegeria_code)
306
+
307
+
308
+
309
+ def print_exception_response(e: PyegeriaException):
310
+ """Prints the exception response"""
311
+ if isinstance(e, PyegeriaException):
312
+ console.print(Markdown(f"\n---\n# Exception: {e.__class__.__name__}"))
313
+ msg: Text = Text(e.__str__(), overflow="fold")
314
+ if hasattr(e, 'related_http_code') and e.related_http_code:
315
+ related_response = e.response.json()
316
+ exception_msg_id = related_response.get("exceptionErrorMessageId", None)
317
+ if exception_msg_id:
318
+ msg.append( f"\n\t{e.error_details['message_template'].format(exception_msg_id)}\n")
319
+
320
+ # for key, value in related_response.items():
321
+ # msg += f"\t\t* {key} = {print(value)}\n"
322
+ for key, value in related_response.items():
323
+ msg.append( Text(f"\t* {key} =", style = "bold yellow"))
324
+ msg.append(Text( f"\n\t\t{format_dict_to_string(value)}\n", overflow="fold", style = "green"))
325
+ console.print(msg)
326
+ else:
327
+ print(f"\n\n\t Not an Pyegeria exception {e}")
328
+
329
+ def print_exception_table(e: PyegeriaException):
330
+ """Prints the exception response"""
331
+ related_code = e.related_http_code if hasattr(e, "related_http_code") else ""
332
+ related_response = e.response.json()
333
+ table = Table(title=f"Exception: {e.__class__.__name__}", show_lines=True, header_style="bold", box=box.HEAVY_HEAD)
334
+ table.caption = e.pyegeria_code
335
+ table.add_column("Facet", justify="center")
336
+ table.add_column("Item", justify="center")
337
+
338
+ if isinstance(e, PyegeriaException):
339
+ table.add_row("HTTP Code", str(e.response_code))
340
+ table.add_row("Egeria Code", str(related_code))
341
+ table.add_row("Caller Method", e.context.get("caller method", "---"))
342
+ table.add_row("Request URL", str(e.response_url))
343
+ if e.related_http_code:
344
+ item_table = Table(show_lines = True, header_style="bold")
345
+ item_table.add_column("Item", justify="center")
346
+ item_table.add_column("Detail", justify="left")
347
+ for key, value in related_response.items():
348
+ item_table.add_row(key, format_dict_to_string(value))
349
+ table.add_row("Egeria Details", item_table)
350
+
351
+
352
+
353
+
354
+ exception_msg_id = related_response.get("exceptionErrorMessageId", None)
355
+ table.add_row("Pyegeria Exception", exception_msg_id)
356
+ table.add_row("Pyegeria Message",
357
+ f"\n\t{e.error_details['message_template'].format(exception_msg_id)}\n")
358
+
359
+
360
+ console.print(table)
361
+ else:
362
+ print(f"\n\n\t Not an Pyegeria exception {e}")
pyegeria/_globals.py CHANGED
@@ -44,4 +44,6 @@ NO_TERMS_FOUND = "No terms found"
44
44
  NO_CATEGORIES_FOUND = "No categories found"
45
45
  NO_ELEMENT_FOUND = "No element found"
46
46
  NO_PROJECTS_FOUND = "No projects found"
47
- NO_COLLECTION_FOUND = "No collection found"
47
+ NO_COLLECTION_FOUND = "No collection found"
48
+ NO_GUID_RETURNED = "No guid returned"
49
+ NO_MEMBERS_FOUND = "No members found"
@@ -0,0 +1,196 @@
1
+ """
2
+ SPDX-License-Identifier: Apache-2.0
3
+ Copyright Contributors to the ODPi Egeria project.
4
+ """
5
+
6
+ """
7
+ # Purpose
8
+ pyegeria allows find and get requests to generate output in different output formats -
9
+ including DICT, MD, FORM, REPORT, LIST, MERMAID, TABLE, and perhaps additional ones in the future.
10
+
11
+ Some of the formats, such as REPORT and LIST, allow the user to filter which attributes to
12
+ display, and the order in which they appear. However, many, if not most users will likely not want to customize
13
+ the column list and so we need a sensible set of defaults for each type of output. These defaults are used
14
+ by the find and get methods if the user doesn't provide a value for the columns parameter.
15
+
16
+ This file contains these defaults and a function to return the the right default dict for a given type.
17
+
18
+ """
19
+
20
+ # Define shared elements
21
+ COMMON_COLUMNS = [
22
+ {'name': 'Display Name', 'key': 'display_name'},
23
+ {'name': 'Qualified Name', 'key': 'qualified_name', 'format': True},
24
+ {'name': 'Category', 'key': 'category'},
25
+ {'name': 'Description', 'key': 'description', 'format': True},
26
+ ]
27
+
28
+ COMMON_METADATA_COLUMNS = [
29
+ {'name': 'GUID', 'key': 'guid', 'format': True},
30
+ {'name': 'Metadata Collection ID', 'key': 'metadata_collection_id', 'format': True},
31
+ {'name': 'Metadata Collection Name', 'key': 'metadata_collection_name', 'format': True},
32
+ ]
33
+
34
+ COMMON_FORMATS_ALL = {
35
+ "types": ["ALL"],
36
+ "columns": COMMON_COLUMNS,
37
+ }
38
+
39
+
40
+ COLLECTIONS_COLUMNS = COMMON_COLUMNS + [
41
+ {'name': "Created By", 'key': 'created_by'},
42
+ {'name': "Create Time", 'key': 'create_time'},
43
+ {'name': "Updated By", 'key': 'updated_by'},
44
+ {'name': "Update Time", 'key': 'update_time'},
45
+ ]
46
+
47
+ COLLECTION_DICT = {
48
+ "types": ["DICT"],
49
+ "columns": COLLECTIONS_COLUMNS,
50
+ }
51
+
52
+ COMMON_ANNOTATIONS = {
53
+ "wikilinks": ["[[Commons]]"]
54
+ }
55
+
56
+ # Modularized output_format_sets
57
+ output_format_sets = {
58
+ "Referenceable": {
59
+ "heading": "Common Attributes",
60
+ "description": "Attributes that apply to all Referenceables.",
61
+ "annotations": {}, # No specific annotations
62
+ "formats": [
63
+ {
64
+ "types": ["ALL"],
65
+ "columns": COMMON_COLUMNS + COMMON_METADATA_COLUMNS + [
66
+ {'name': 'Version Identifier', 'key': 'version_identifier'},
67
+ {'name': "Classifications", 'key': 'classifications'},
68
+ {'name': "Additional Properties", 'key': 'additional_properties'},
69
+ {'name': "Created By", 'key': 'created_by'},
70
+ {'name': "Create Time", 'key': 'create_time'},
71
+ {'name': "Updated By", 'key': 'updated_by'},
72
+ {'name': "Update Time", 'key': 'update_time'},
73
+ {'name': "Effective From", 'key': 'effective_from'},
74
+ {'name': "Effective To", 'key': 'effective_to'},
75
+ {'name': "Version", 'key': 'version'},
76
+ {'name': "Open Metadata Type Name", 'key': 'type_name'},
77
+ ],
78
+ }
79
+ ],
80
+ },
81
+
82
+ "Collections": {
83
+ "heading": "Common Collection Information",
84
+ "description": "Attributes generic to all Collections.",
85
+ "aliases": ["Collection", "RootCollection", "Folder", "ReferenceList", "HomeCollection",
86
+ "ResultSet", "RecentAccess", "WorkItemList", "Namespace"],
87
+ "annotations": COMMON_ANNOTATIONS,
88
+ "formats": [COMMON_FORMATS_ALL, COLLECTION_DICT] # Reusing common formats
89
+ },
90
+
91
+ "DigitalProducts": {
92
+ "heading": "Digital Product Information",
93
+ "description": "Attributes useful to Digital Products.",
94
+ "aliases": ["DigitalProduct", "DataProducts"],
95
+ "annotations": {},
96
+ "formats": [
97
+ {
98
+ "types": ["REPORT"],
99
+ "columns": COMMON_COLUMNS + [
100
+ {'name': "Status", 'key': 'status'},
101
+ {'name': 'Product Name', 'key': 'product_name'},
102
+ {'name': 'Identifier', 'key': 'identifier'},
103
+ {'name': 'Maturity', 'key': 'maturity'},
104
+ {'name': 'Service Life', 'key': 'service_life'},
105
+ {'name': 'Next Version', 'key': 'next_version'},
106
+ {'name': 'Withdraw Date', 'key': 'withdraw_date'},
107
+ {'name': 'Members', 'key': 'members', 'format': True},
108
+ ],
109
+ }
110
+ ]
111
+ },
112
+
113
+ "Agreements": {
114
+ "heading": "General Agreement Information",
115
+ "description": "Attributes generic to all Agreements.",
116
+ "aliases": ["DataSharingAgreement"],
117
+ "annotations": {"wikilinks": ["[[Agreements]]", "[[Egeria]]"]},
118
+ "formats": COMMON_FORMATS_ALL # Reusing common formats and columns
119
+ },
120
+
121
+ "Data Dictionary": {
122
+ "heading": "Data Dictionary Information",
123
+ "description": "Attributes useful to Data Dictionary.",
124
+ "aliases": ["Data Dict"],
125
+ "annotations": {"wikilinks": ["[[Data Dictionary]]"]},
126
+ "formats": COMMON_FORMATS_ALL # Reusing common formats and columns
127
+ },
128
+
129
+ "Data Specification": {
130
+ "heading": "Data Specification Information",
131
+ "description": "Attributes useful to Data Specification.",
132
+ "aliases": ["Data Spec"],
133
+ "annotations": {"wikilinks": ["[[Data Specification]]"]},
134
+ "formats": COMMON_FORMATS_ALL # Reusing common formats and columns
135
+ },
136
+ }
137
+
138
+
139
+ def select_output_format_set(kind: str, output_type: str) -> dict | None:
140
+ """
141
+ This function retrieves the appropriate output set configuration dictionary based on the `kind` and `output_type`.
142
+
143
+ :param kind: The kind of output set (e.g., "Referenceable", "Collections").
144
+ :param output_type: The desired output format type (e.g., "DICT", "LIST", "REPORT").
145
+ :return: The matched output set dictionary or None if no match is found.
146
+
147
+ Returns:
148
+ dict | None:
149
+ """
150
+ from loguru import logger
151
+ # from pyegeria.logging_configuration import config_logging
152
+
153
+ # config_logging()
154
+
155
+ # Normalize the output type to uppercase for consistency
156
+ output_type = output_type.upper()
157
+ output_struct:dict = {}
158
+
159
+ # Step 1: Check if `kind` exists in the `output_format_sets` dictionary
160
+ element = output_format_sets.get(kind)
161
+
162
+ # Step 2: If not found, attempt to match `kind` in aliases
163
+ if element is None:
164
+ for value in output_format_sets.values():
165
+ aliases = value.get("aliases", [])
166
+ if kind in aliases:
167
+ element = value
168
+ break
169
+
170
+
171
+ # Step 3: If still not found, return None
172
+ if element is None:
173
+ msg = "No matching column set found for kind='{kind}' and output type='{output_type}'."
174
+ logger.error(msg)
175
+ return None
176
+ else:
177
+ output_struct["aliases"] = element.get("aliases", [])
178
+ output_struct["heading"] = element.get("heading", [])
179
+ output_struct["description"] = element.get("description", [])
180
+ output_struct["annotations"] = element.get("annotations", {})
181
+
182
+ # Step 4: Search for a matching format in the `formats` list
183
+ for format in element.get("formats", []):
184
+ if output_type in format.get("types", []):
185
+ output_struct["formats"] = format
186
+ return output_struct
187
+
188
+ # Step 5: Handle the fallback case of "ALL"
189
+ for format in element.get("formats", []):
190
+ if "ALL" in format.get("types", []):
191
+ output_struct["formats"] = format
192
+ return output_struct
193
+
194
+ # Step 6: If no match is found, return None
195
+ logger.error(f"No matching format found for kind='{kind}' with output type='{output_type}'.")
196
+ return None