pyegeria 5.3.9.9.3__py3-none-any.whl → 5.5.3.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.

Potentially problematic release.


This version of pyegeria might be problematic. Click here for more details.

Files changed (272) hide show
  1. commands/__init__.py +24 -0
  2. commands/cat/Dr-Egeria_md-orig.py +2 -2
  3. commands/cat/__init__.py +1 -17
  4. commands/cat/collection_actions.py +197 -0
  5. commands/cat/dr_egeria_command_help.py +372 -0
  6. commands/cat/dr_egeria_jupyter.py +7 -7
  7. commands/cat/dr_egeria_md.py +27 -182
  8. commands/cat/exp_list_glossaries.py +11 -14
  9. commands/cat/get_asset_graph.py +37 -267
  10. commands/cat/{get_collection.py → get_collection_tree.py} +10 -18
  11. commands/cat/get_project_dependencies.py +14 -14
  12. commands/cat/get_project_structure.py +15 -14
  13. commands/cat/get_tech_type_elements.py +16 -116
  14. commands/cat/glossary_actions.py +145 -298
  15. commands/cat/list_assets.py +3 -11
  16. commands/cat/list_cert_types.py +17 -63
  17. commands/cat/list_collections.py +46 -138
  18. commands/cat/list_deployed_catalogs.py +15 -27
  19. commands/cat/list_deployed_database_schemas.py +27 -43
  20. commands/cat/list_deployed_databases.py +16 -31
  21. commands/cat/list_deployed_servers.py +35 -54
  22. commands/cat/list_glossaries.py +18 -17
  23. commands/cat/list_projects.py +10 -12
  24. commands/cat/list_tech_type_elements.py +21 -37
  25. commands/cat/list_tech_types.py +13 -25
  26. commands/cat/list_terms.py +38 -79
  27. commands/cat/list_todos.py +4 -11
  28. commands/cat/list_user_ids.py +3 -10
  29. commands/cat/my_reports.py +559 -0
  30. commands/cat/run_report.py +394 -0
  31. commands/cat/run_report_orig.py +528 -0
  32. commands/cli/egeria.py +222 -247
  33. commands/cli/egeria_cat.py +68 -81
  34. commands/cli/egeria_my.py +13 -0
  35. commands/cli/egeria_ops.py +69 -74
  36. commands/cli/egeria_tech.py +17 -93
  37. commands/cli/ops_config.py +3 -6
  38. commands/{cat/list_categories.py → deprecated/list_data_designer.py} +53 -64
  39. commands/{cat/list_data_structures.py → deprecated/list_data_structures_full.py} +3 -6
  40. commands/deprecated/old_get_asset_graph.py +315 -0
  41. commands/my/__init__.py +0 -2
  42. commands/my/list_my_profile.py +27 -34
  43. commands/my/list_my_roles.py +1 -7
  44. commands/my/monitor_my_todos.py +1 -7
  45. commands/my/monitor_open_todos.py +6 -7
  46. commands/my/todo_actions.py +4 -5
  47. commands/ops/__init__.py +0 -2
  48. commands/ops/gov_server_actions.py +17 -21
  49. commands/ops/list_archives.py +17 -38
  50. commands/ops/list_catalog_targets.py +33 -40
  51. commands/ops/load_archive.py +35 -26
  52. commands/ops/{monitor_engine_activity_c.py → monitor_active_engine_activity.py} +51 -82
  53. commands/ops/{monitor_integ_daemon_status.py → monitor_daemon_status.py} +35 -55
  54. commands/ops/monitor_engine_activity.py +79 -77
  55. commands/ops/{monitor_gov_eng_status.py → monitor_engine_status.py} +10 -7
  56. commands/ops/monitor_platform_status.py +38 -50
  57. commands/ops/monitor_server_startup.py +6 -11
  58. commands/ops/monitor_server_status.py +7 -11
  59. commands/ops/orig_monitor_server_list.py +8 -8
  60. commands/ops/orig_monitor_server_status.py +1 -5
  61. commands/ops/refresh_integration_daemon.py +5 -5
  62. commands/ops/restart_integration_daemon.py +5 -5
  63. commands/ops/table_integ_daemon_status.py +6 -6
  64. commands/ops/x_engine_actions.py +7 -7
  65. commands/tech/__init__.py +0 -2
  66. commands/tech/{generic_actions.py → element_actions.py} +6 -11
  67. commands/tech/get_element_info.py +20 -29
  68. commands/tech/get_guid_info.py +23 -42
  69. commands/tech/get_tech_details.py +20 -35
  70. commands/tech/get_tech_type_template.py +28 -39
  71. commands/tech/list_all_om_type_elements.py +24 -30
  72. commands/tech/list_all_om_type_elements_x.py +22 -28
  73. commands/tech/list_all_related_elements.py +19 -28
  74. commands/tech/list_anchored_elements.py +22 -30
  75. commands/tech/list_asset_types.py +19 -24
  76. commands/tech/list_elements_by_classification_by_property_value.py +26 -32
  77. commands/tech/list_elements_by_property_value.py +19 -25
  78. commands/tech/list_elements_by_property_value_x.py +20 -28
  79. commands/tech/list_elements_for_classification.py +28 -41
  80. commands/tech/list_gov_action_processes.py +16 -27
  81. commands/tech/list_information_supply_chains.py +22 -30
  82. commands/tech/list_registered_services.py +14 -26
  83. commands/tech/list_related_elements_with_prop_value.py +15 -25
  84. commands/tech/list_related_specification.py +1 -4
  85. commands/tech/list_relationship_types.py +15 -25
  86. commands/tech/list_relationships.py +20 -36
  87. commands/tech/list_solution_blueprints.py +28 -33
  88. commands/tech/list_solution_components.py +23 -29
  89. commands/tech/list_solution_roles.py +21 -32
  90. commands/tech/list_tech_templates.py +51 -54
  91. commands/tech/list_valid_metadata_values.py +5 -9
  92. commands/tech/table_tech_templates.py +2 -6
  93. commands/tech/x_list_related_elements.py +1 -4
  94. examples/GeoSpatial Products Example.py +524 -0
  95. examples/Jupyter Notebooks/P-egeria-server-config.ipynb +2137 -0
  96. examples/Jupyter Notebooks/README.md +2 -0
  97. examples/Jupyter Notebooks/common/P-environment-check.ipynb +115 -0
  98. examples/Jupyter Notebooks/common/__init__.py +14 -0
  99. examples/Jupyter Notebooks/common/common-functions.ipynb +4694 -0
  100. examples/Jupyter Notebooks/common/environment-check.ipynb +52 -0
  101. examples/Jupyter Notebooks/common/globals.ipynb +184 -0
  102. examples/Jupyter Notebooks/common/globals.py +154 -0
  103. examples/Jupyter Notebooks/common/orig_globals.py +152 -0
  104. examples/format_sets/all_format_sets.json +910 -0
  105. examples/format_sets/custom_format_sets.json +268 -0
  106. examples/format_sets/subset_format_sets.json +187 -0
  107. examples/format_sets_save_load_example.py +291 -0
  108. examples/jacquard_data_sets.py +129 -0
  109. examples/output_formats_example.py +193 -0
  110. examples/test_jacquard_data_sets.py +54 -0
  111. examples/test_jacquard_data_sets_scenarios.py +94 -0
  112. md_processing/__init__.py +90 -0
  113. md_processing/command_dispatcher.py +33 -0
  114. md_processing/command_mapping.py +221 -0
  115. md_processing/data/commands/commands_data_designer.json +537 -0
  116. md_processing/data/commands/commands_external_reference.json +733 -0
  117. md_processing/data/commands/commands_feedback.json +155 -0
  118. md_processing/data/commands/commands_general.json +204 -0
  119. md_processing/data/commands/commands_glossary.json +218 -0
  120. md_processing/data/commands/commands_governance.json +3678 -0
  121. md_processing/data/commands/commands_product_manager.json +865 -0
  122. md_processing/data/commands/commands_project.json +642 -0
  123. md_processing/data/commands/commands_solution_architect.json +366 -0
  124. md_processing/data/commands.json +17568 -0
  125. md_processing/data/commands_working.json +30641 -0
  126. md_processing/data/gened_report_specs.py +6584 -0
  127. md_processing/data/generated_format_sets.json +6533 -0
  128. md_processing/data/generated_format_sets_old.json +4137 -0
  129. md_processing/data/generated_format_sets_old.py +45 -0
  130. md_processing/dr_egeria.py +182 -0
  131. md_processing/md_commands/__init__.py +3 -0
  132. md_processing/md_commands/data_designer_commands.py +1276 -0
  133. md_processing/md_commands/ext_ref_commands.py +530 -0
  134. md_processing/md_commands/feedback_commands.py +726 -0
  135. md_processing/md_commands/glossary_commands.py +684 -0
  136. md_processing/md_commands/governance_officer_commands.py +600 -0
  137. md_processing/md_commands/product_manager_commands.py +1266 -0
  138. md_processing/md_commands/project_commands.py +383 -0
  139. md_processing/md_commands/solution_architect_commands.py +1184 -0
  140. md_processing/md_commands/view_commands.py +295 -0
  141. md_processing/md_processing_utils/__init__.py +4 -0
  142. md_processing/md_processing_utils/common_md_proc_utils.py +1249 -0
  143. md_processing/md_processing_utils/common_md_utils.py +578 -0
  144. md_processing/md_processing_utils/determine_width.py +103 -0
  145. md_processing/md_processing_utils/extraction_utils.py +547 -0
  146. md_processing/md_processing_utils/gen_report_specs.py +643 -0
  147. md_processing/md_processing_utils/generate_dr_help.py +193 -0
  148. md_processing/md_processing_utils/generate_md_cmd_templates.py +144 -0
  149. md_processing/md_processing_utils/generate_md_templates.py +83 -0
  150. md_processing/md_processing_utils/md_processing_constants.py +1228 -0
  151. md_processing/md_processing_utils/message_constants.py +19 -0
  152. pyegeria/__init__.py +201 -443
  153. pyegeria/core/__init__.py +40 -0
  154. pyegeria/core/_base_platform_client.py +574 -0
  155. pyegeria/core/_base_server_client.py +573 -0
  156. pyegeria/core/_exceptions.py +457 -0
  157. pyegeria/core/_globals.py +60 -0
  158. pyegeria/core/_server_client.py +6073 -0
  159. pyegeria/core/_validators.py +257 -0
  160. pyegeria/core/config.py +654 -0
  161. pyegeria/{create_tech_guid_lists.py → core/create_tech_guid_lists.py} +0 -1
  162. pyegeria/core/load_config.py +37 -0
  163. pyegeria/core/logging_configuration.py +207 -0
  164. pyegeria/core/mcp_adapter.py +144 -0
  165. pyegeria/core/mcp_server.py +212 -0
  166. pyegeria/core/utils.py +405 -0
  167. pyegeria/deprecated/__init__.py +0 -0
  168. pyegeria/{_client.py → deprecated/_client.py} +62 -24
  169. pyegeria/{_deprecated_gov_engine.py → deprecated/_deprecated_gov_engine.py} +16 -16
  170. pyegeria/{classification_manager_omvs.py → deprecated/classification_manager_omvs.py} +1988 -1878
  171. pyegeria/deprecated/output_formatter_with_machine_keys.py +1127 -0
  172. pyegeria/{runtime_manager_omvs.py → deprecated/runtime_manager_omvs.py} +216 -229
  173. pyegeria/{valid_metadata_omvs.py → deprecated/valid_metadata_omvs.py} +93 -93
  174. pyegeria/{x_action_author_omvs.py → deprecated/x_action_author_omvs.py} +2 -3
  175. pyegeria/egeria_cat_client.py +25 -51
  176. pyegeria/egeria_client.py +140 -98
  177. pyegeria/egeria_config_client.py +48 -24
  178. pyegeria/egeria_tech_client.py +170 -83
  179. pyegeria/models/__init__.py +150 -0
  180. pyegeria/models/collection_models.py +168 -0
  181. pyegeria/models/models.py +654 -0
  182. pyegeria/omvs/__init__.py +84 -0
  183. pyegeria/omvs/action_author.py +342 -0
  184. pyegeria/omvs/actor_manager.py +5980 -0
  185. pyegeria/omvs/asset_catalog.py +842 -0
  186. pyegeria/omvs/asset_maker.py +2736 -0
  187. pyegeria/omvs/automated_curation.py +4403 -0
  188. pyegeria/omvs/classification_manager.py +11213 -0
  189. pyegeria/omvs/collection_manager.py +5780 -0
  190. pyegeria/omvs/community_matters_omvs.py +468 -0
  191. pyegeria/{core_omag_server_config.py → omvs/core_omag_server_config.py} +157 -157
  192. pyegeria/{data_designer_omvs.py → omvs/data_designer.py} +1991 -1691
  193. pyegeria/omvs/data_discovery.py +869 -0
  194. pyegeria/omvs/data_engineer.py +372 -0
  195. pyegeria/omvs/digital_business.py +1133 -0
  196. pyegeria/omvs/external_links.py +1752 -0
  197. pyegeria/omvs/feedback_manager.py +834 -0
  198. pyegeria/{full_omag_server_config.py → omvs/full_omag_server_config.py} +73 -69
  199. pyegeria/omvs/glossary_manager.py +3231 -0
  200. pyegeria/omvs/governance_officer.py +3009 -0
  201. pyegeria/omvs/lineage_linker.py +314 -0
  202. pyegeria/omvs/location_arena.py +1525 -0
  203. pyegeria/omvs/metadata_expert.py +668 -0
  204. pyegeria/omvs/metadata_explorer_omvs.py +2943 -0
  205. pyegeria/omvs/my_profile.py +1042 -0
  206. pyegeria/omvs/notification_manager.py +358 -0
  207. pyegeria/omvs/people_organizer.py +394 -0
  208. pyegeria/{platform_services.py → omvs/platform_services.py} +113 -193
  209. pyegeria/omvs/product_manager.py +1825 -0
  210. pyegeria/omvs/project_manager.py +1907 -0
  211. pyegeria/omvs/reference_data.py +1140 -0
  212. pyegeria/omvs/registered_info.py +334 -0
  213. pyegeria/omvs/runtime_manager.py +2817 -0
  214. pyegeria/omvs/schema_maker.py +446 -0
  215. pyegeria/{server_operations.py → omvs/server_operations.py} +27 -26
  216. pyegeria/omvs/solution_architect.py +6490 -0
  217. pyegeria/omvs/specification_properties.py +37 -0
  218. pyegeria/omvs/subject_area.py +1042 -0
  219. pyegeria/omvs/template_manager_omvs.py +236 -0
  220. pyegeria/omvs/time_keeper.py +1761 -0
  221. pyegeria/omvs/valid_metadata.py +3221 -0
  222. pyegeria/omvs/valid_metadata_lists.py +37 -0
  223. pyegeria/omvs/valid_type_lists.py +37 -0
  224. pyegeria/view/__init__.py +28 -0
  225. pyegeria/view/_output_format_models.py +514 -0
  226. pyegeria/view/_output_formats.py +14 -0
  227. pyegeria/view/base_report_formats.py +2719 -0
  228. pyegeria/view/dr_egeria_reports.py +56 -0
  229. pyegeria/view/format_set_executor.py +397 -0
  230. pyegeria/{md_processing_utils.py → view/md_processing_utils.py} +5 -5
  231. pyegeria/{mermaid_utilities.py → view/mermaid_utilities.py} +2 -154
  232. pyegeria/view/output_formatter.py +1297 -0
  233. pyegeria-5.5.3.3.dist-info/METADATA +218 -0
  234. pyegeria-5.5.3.3.dist-info/RECORD +241 -0
  235. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.5.3.3.dist-info}/WHEEL +2 -1
  236. pyegeria-5.5.3.3.dist-info/entry_points.txt +103 -0
  237. pyegeria-5.5.3.3.dist-info/top_level.txt +4 -0
  238. commands/cat/.DS_Store +0 -0
  239. commands/cat/README.md +0 -16
  240. commands/cli/txt_custom_v2.tcss +0 -19
  241. commands/my/README.md +0 -17
  242. commands/ops/README.md +0 -24
  243. commands/ops/monitor_asset_events.py +0 -108
  244. commands/tech/README.md +0 -24
  245. pyegeria/.DS_Store +0 -0
  246. pyegeria/README.md +0 -35
  247. pyegeria/_globals.py +0 -47
  248. pyegeria/_validators.py +0 -385
  249. pyegeria/asset_catalog_omvs.py +0 -864
  250. pyegeria/automated_curation_omvs.py +0 -3765
  251. pyegeria/collection_manager_omvs.py +0 -2744
  252. pyegeria/dr.egeria spec.md +0 -9
  253. pyegeria/egeria_my_client.py +0 -56
  254. pyegeria/feedback_manager_omvs.py +0 -4573
  255. pyegeria/glossary_browser_omvs.py +0 -3728
  256. pyegeria/glossary_manager_omvs.py +0 -2440
  257. pyegeria/m_test.py +0 -118
  258. pyegeria/md_processing_helpers.py +0 -58
  259. pyegeria/md_processing_utils_orig.py +0 -1103
  260. pyegeria/metadata_explorer_omvs.py +0 -2326
  261. pyegeria/my_profile_omvs.py +0 -1022
  262. pyegeria/output_formatter.py +0 -389
  263. pyegeria/project_manager_omvs.py +0 -1933
  264. pyegeria/registered_info.py +0 -167
  265. pyegeria/solution_architect_omvs.py +0 -2156
  266. pyegeria/template_manager_omvs.py +0 -1414
  267. pyegeria/utils.py +0 -197
  268. pyegeria-5.3.9.9.3.dist-info/METADATA +0 -72
  269. pyegeria-5.3.9.9.3.dist-info/RECORD +0 -143
  270. pyegeria-5.3.9.9.3.dist-info/entry_points.txt +0 -99
  271. /pyegeria/{_exceptions.py → deprecated/_exceptions.py} +0 -0
  272. {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.5.3.3.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,643 @@
1
+ """
2
+ Generate report FormatSet specifications for pyegeria from a commands.json file.
3
+
4
+ Overview
5
+ - This utility reads a Dr Egeria style commands.json (aka Command Specifications)
6
+ and converts relevant entries (by default, only those with a display name
7
+ beginning with "Create") into pyegeria FormatSet definitions. These can be:
8
+ - returned in-memory as a FormatSetDict
9
+ - saved to a JSON file that can be loaded by pyegeria.base_report_formats.load_report_specs
10
+ - saved as a Python module that defines a dictionary of FormatSet objects
11
+ - merged directly into the built-in report_specs registry at runtime
12
+
13
+ Typical uses
14
+ - Quickly scaffold report formats for new content types defined in your command
15
+ specifications, without hand-writing every Column/Format block.
16
+ - Keep generated formats alongside your hand-curated ones and iterate as the
17
+ command specifications evolve.
18
+
19
+ What this script extracts
20
+ - Target type: from each command's display_name
21
+ - Columns: only attributes marked with level == "Basic" are included. The
22
+ column name comes from the attribute label; the column key from the
23
+ attribute's variable_name.
24
+ - Action parameter: if a find_method is present on the command, an ActionParameter
25
+ is created with:
26
+ - function = find_method
27
+ - required_params = ["search_string"]
28
+ - optional_params = pyegeria.base_report_formats.OPTIONAL_PARAMS
29
+ - spec_params = parsed find_constraints (see parsing notes below)
30
+
31
+ Constraints parsing
32
+ - find_constraints may be a dict or a JSON string (sometimes double-escaped).
33
+ We try, in order: direct JSON, unquoted JSON, and a simple single-to-double
34
+ quote substitution for dict-like strings. Failures yield an empty dict, and
35
+ a debug message is logged.
36
+
37
+ Naming
38
+ - Each FormatSet key and heading are derived from the command's display_name.
39
+ The key is display_name with spaces replaced by '-' and suffixed with '-DrE'.
40
+ Example: "Create Governance Strategy Definition" -> "Create-Governance-Strategy-Definition-DrE".
41
+
42
+ CLI usage examples
43
+ - Save to JSON (default mode):
44
+ poetry run python -m md_processing.md_processing_utils.gen_report_specs \
45
+ md_processing/data/commands.json md_processing/data/generated_format_sets.json --emit json
46
+
47
+ - Emit Python code to a .py file:
48
+ poetry run python -m md_processing.md_processing_utils.gen_report_specs \
49
+ md_processing/data/commands.json md_processing/data/generated_report_specs.py --emit code
50
+
51
+ - Build in-memory only and list set names:
52
+ poetry run python -m md_processing.md_processing_utils.gen_report_specs \
53
+ md_processing/data/commands.json --emit dict --list
54
+
55
+ - Merge generated sets into the built-in registry (report_specs):
56
+ poetry run python -m md_processing.md_processing_utils.gen_report_specs \
57
+ md_processing/data/commands.json --emit dict --merge --list
58
+
59
+ Programmatic usage
60
+ - Build in memory:
61
+ from md_processing.md_processing_utils.gen_report_specs import generate_format_sets
62
+ sets = generate_format_sets("md_processing/data/commands.json")
63
+
64
+ - Save JSON that can be loaded by pyegeria:
65
+ from md_processing.md_processing_utils.gen_report_specs import save_generated_format_sets
66
+ save_generated_format_sets("md_processing/data/commands.json", "md_processing/data/generated_format_sets.json")
67
+
68
+ - Save as importable Python module:
69
+ from md_processing.md_processing_utils.gen_report_specs import save_generated_format_sets_code
70
+ save_generated_format_sets_code("md_processing/data/commands.json", "md_processing/data/generated_report_specs.py")
71
+
72
+ - Merge directly into built-ins at runtime:
73
+ from md_processing.md_processing_utils.gen_report_specs import merge_generated_report_specs
74
+ merge_generated_format_sets("md_processing/data/commands.json")
75
+
76
+ Notes
77
+ - Only commands whose key or display_name begins with "Create" are included by
78
+ default. Pass include_only_create=False to include all commands.
79
+ - default_types controls the Format.types field, and defaults to ["ALL"].
80
+ - This script logs progress with loguru.
81
+ """
82
+ import json
83
+ import re
84
+ import argparse
85
+ from pathlib import Path
86
+ from typing import Iterable, List, Optional
87
+
88
+ from loguru import logger
89
+ from rich.prompt import Prompt
90
+
91
+ from pyegeria.view._output_format_models import Column, Format, ActionParameter, FormatSet, FormatSetDict
92
+ from pyegeria.view.base_report_formats import OPTIONAL_PARAMS # ["page_size","start_from","starts_with","ends_with","ignore_case"]
93
+
94
+ def _is_create_command(cmd_key: str, cmd_obj: dict) -> bool:
95
+ """Return True if a command should be treated as a "Create" command.
96
+
97
+ A command qualifies if either its dictionary key or its "display_name"
98
+ begins with the word "Create" (case-insensitive). This mirrors how the
99
+ CLI specs tend to name creation operations and is used to filter which
100
+ commands are transformed into report FormatSets by default.
101
+ """
102
+ name = str(cmd_obj.get("display_name", "")).strip()
103
+ return (isinstance(cmd_key, str) and cmd_key.strip().lower().startswith("create")) or \
104
+ (name.lower().startswith("create"))
105
+
106
+ def _derive_set_name_from_display_name(display_name: str) -> str:
107
+ """Derive a compact set name from a command display name.
108
+
109
+ Example: 'Create Governance Strategy Definition' -> 'GovernanceStrategyDefinition-DrE'
110
+ Currently not used in favor of a dash-separated variant, but kept for
111
+ reference and potential future toggles.
112
+ """
113
+ if not display_name:
114
+ return ""
115
+ parts = display_name.strip().split()
116
+ if len(parts) <= 1:
117
+ return re.sub(r"\s+", "", display_name.strip())+"-DrE"
118
+ rest = " ".join(parts[1:]).strip()
119
+ rest = rest+"-DrE"
120
+ return re.sub(r"\s+", "", rest)
121
+
122
+ def _safe_parse_constraints(s) -> dict:
123
+ """Parse a command's `find_constraints` field into a Python dict.
124
+
125
+ Accepts:
126
+ - A dict (returned as-is)
127
+ - A JSON string (possibly surrounded by quotes or single-quoted)
128
+ - A loosely dict-like string using single quotes
129
+
130
+ Returns an empty dict on failure and logs at debug level.
131
+ """
132
+ if not s:
133
+ return {}
134
+ if isinstance(s, dict):
135
+ return s
136
+ txt = str(s).strip()
137
+ # Attempt 1: direct JSON
138
+ try:
139
+ return json.loads(txt)
140
+ except Exception:
141
+ logger.error(f"Error parsing constraints: {s!r}")
142
+ pass
143
+ # Attempt 2: unwrap quotes
144
+ try:
145
+ txt2 = txt.strip('"').strip("'")
146
+ return json.loads(txt2)
147
+ except Exception:
148
+ pass
149
+ # Attempt 3: heuristic single->double quotes for dict-like strings
150
+ if ("{" in txt and "}" in txt) and ("'" in txt and '"' not in txt):
151
+ try:
152
+ return json.loads(txt.replace("'", '"'))
153
+ except Exception:
154
+ pass
155
+ logger.debug(f"Could not parse find_constraints: {s!r} -> {{}}")
156
+ return {}
157
+
158
+ def _extract_basic_columns_from_attributes(attributes: Iterable[dict]) -> List[Column]:
159
+ """Extract Basic-level Columns from a command's Attributes list.
160
+
161
+ Parameters
162
+ - attributes: iterable of dict entries. Each entry is typically a single-key
163
+ mapping of attribute label -> metadata dict with fields like
164
+ {"variable_name": ..., "level": "Basic"|"Advanced"|...}.
165
+
166
+ Behavior
167
+ - Only items with level == "Basic" are included
168
+ - Duplicate variable_name values are de-duplicated
169
+ - Column.name is the label; Column.key is the variable_name
170
+
171
+ Returns
172
+ - List[Column]
173
+ """
174
+
175
+ cols: List[Column] = []
176
+ seen: set[str] = set()
177
+ for entry in attributes or []:
178
+ if not isinstance(entry, dict) or not entry:
179
+ continue
180
+ # Some entries may contain more than one key; scan all items
181
+ for label, details in entry.items():
182
+ if not isinstance(details, dict):
183
+ continue
184
+ if details.get("level") != "Basic":
185
+ continue
186
+ key = details.get("variable_name")
187
+ if not key or key in seen:
188
+ continue
189
+ seen.add(key)
190
+ cols.append(Column(name=str(label), key=str(key)))
191
+ return cols
192
+
193
+ def build_format_sets_from_commands(
194
+ commands_json_path: str | Path,
195
+ *,
196
+ include_only_create: bool = True,
197
+ default_types: Optional[List[str]] = None
198
+ ) -> FormatSetDict:
199
+ """Build a FormatSetDict from a commands.json file.
200
+
201
+ Parameters
202
+ - commands_json_path: path to the Dr Egeria style commands.json file. The
203
+ file may be an object with a "Command Specifications" map, or a list of
204
+ command objects.
205
+ - include_only_create: when True (default), include only commands whose key
206
+ or display_name starts with "Create".
207
+ - default_types: the list used for Format.types for each produced Format
208
+ (defaults to ["ALL"]).
209
+
210
+ Returns
211
+ - FormatSetDict keyed by set names derived from display_name + "-DrE".
212
+
213
+ Notes
214
+ - Columns are derived from Attributes entries whose level == "Basic".
215
+ - If find_method is present, an ActionParameter is created.
216
+ """
217
+ path = Path(commands_json_path)
218
+ data = json.loads(path.read_text(encoding="utf-8"))
219
+
220
+ if isinstance(data, dict):
221
+ items_iter = data.items()
222
+ elif isinstance(data, list):
223
+ # If it’s a list of command objects, synthesize keys from display_name or index
224
+ def _key_for(idx, obj):
225
+ dn = str(obj.get("display_name", "")).strip()
226
+ return dn or f"cmd_{idx}"
227
+
228
+ items_iter = ((_key_for(i, obj), obj) for i, obj in enumerate(data))
229
+ else:
230
+ raise ValueError("commands.json root must be an object/dict or an array of command objects")
231
+ data = dict(items_iter).get("Command Specifications", {})
232
+ logger.info(
233
+ f"Loaded commands.json from {path} with {len(data)} commands"
234
+ )
235
+
236
+
237
+ results = FormatSetDict()
238
+ types = default_types or ["ALL"]
239
+
240
+ for cmd_key, cmd_obj in data.items():
241
+ if not isinstance(cmd_obj, dict):
242
+ continue
243
+ if include_only_create and not _is_create_command(cmd_key, cmd_obj):
244
+ continue
245
+
246
+ display_name = str(cmd_obj.get("display_name", "")).strip()
247
+ if not display_name:
248
+ logger.debug(f"Skip {cmd_key!r}: missing display_name")
249
+ continue
250
+ set_name = re.sub(r"\s+", "-", display_name)
251
+ set_name = set_name.strip() + "-DrE"
252
+ # set_name = _derive_set_name_from_display_name(display_name)
253
+ if not set_name:
254
+ logger.debug(f"Skip {cmd_key!r}: could not derive set name from display_name={display_name!r}")
255
+ continue
256
+
257
+ columns = _extract_basic_columns_from_attributes(cmd_obj.get("Attributes", []))
258
+ if not columns:
259
+ logger.debug(f"Skip {set_name}: no Basic attributes")
260
+ continue
261
+
262
+ find_method = cmd_obj.get("find_method") or ""
263
+ constraints = _safe_parse_constraints(cmd_obj.get("find_constraints"))
264
+ action = None
265
+ if find_method:
266
+ action = ActionParameter(
267
+ function=find_method,
268
+ required_params=["search_string"],
269
+ optional_params=OPTIONAL_PARAMS,
270
+ spec_params=constraints or {},
271
+ )
272
+
273
+ fs = FormatSet(
274
+ target_type=display_name,
275
+ heading=f"{set_name} Attributes",
276
+ description=f"Auto-generated format for {display_name} (Create).",
277
+ family=cmd_obj.get("family"),
278
+ formats=[Format(types=types, columns=columns)],
279
+ action=action
280
+ )
281
+ results[set_name] = fs
282
+
283
+ return results
284
+
285
+ def save_generated_format_sets(
286
+ commands_json_path: str | Path,
287
+ out_file: str | Path,
288
+ *,
289
+ include_only_create: bool = True,
290
+ default_types: Optional[List[str]] = None
291
+ ) -> Path:
292
+ """Build FormatSets from commands.json and save them as JSON.
293
+
294
+ The resulting JSON can be loaded via `pyegeria.base_report_formats.load_report_specs`.
295
+
296
+ Parameters
297
+ - commands_json_path: input commands.json path
298
+ - out_file: where to write the generated JSON
299
+ - include_only_create: filter to only "Create" commands (default True)
300
+ - default_types: list for Format.types (default ["ALL"]).
301
+
302
+ Returns
303
+ - Path to the written JSON file
304
+ """
305
+ sets = build_format_sets_from_commands(
306
+ commands_json_path,
307
+ include_only_create=include_only_create,
308
+ default_types=default_types,
309
+ )
310
+ out = Path(out_file)
311
+ out.parent.mkdir(parents=True, exist_ok=True)
312
+ sets.save_to_json(str(out))
313
+ logger.info(f"Saved {len(sets)} generated format sets to {out}")
314
+ return out
315
+
316
+ # New: expose a simple alias that returns a FormatSetDict directly
317
+ # for callers that want the in-memory object rather than JSON.
318
+ def generate_format_sets(
319
+ commands_json_path: str | Path,
320
+ *,
321
+ include_only_create: bool = True,
322
+ default_types: Optional[List[str]] = None,
323
+ ):
324
+ """Return a FormatSetDict built from commands.json (no file I/O).
325
+
326
+ This is a convenience wrapper around `build_format_sets_from_commands` for
327
+ programmatic use when you don't want to touch the filesystem.
328
+ """
329
+ return build_format_sets_from_commands(
330
+ commands_json_path,
331
+ include_only_create=include_only_create,
332
+ default_types=default_types,
333
+ )
334
+
335
+ # New: merge generated sets into the builtin registry
336
+
337
+ def merge_generated_format_sets(
338
+ commands_json_path: str | Path,
339
+ *,
340
+ include_only_create: bool = True,
341
+ default_types: Optional[List[str]] = None,
342
+ ) -> int:
343
+ """Build and merge generated FormatSets into the built-in registry.
344
+
345
+ Parameters
346
+ - commands_json_path: input commands.json path
347
+ - include_only_create: filter to only "Create" commands (default True)
348
+ - default_types: list for Format.types (default ["ALL"]).
349
+
350
+ Returns
351
+ - int: number of sets merged/added into `pyegeria.base_report_formats.report_specs`.
352
+ """
353
+ from pyegeria.view.base_report_formats import report_specs # local import to avoid cycles on tooling
354
+
355
+ gen = build_format_sets_from_commands(
356
+ commands_json_path,
357
+ include_only_create=include_only_create,
358
+ default_types=default_types,
359
+ )
360
+ count = 0
361
+ for name, fs in gen.items():
362
+ report_specs[name] = fs
363
+ count += 1
364
+ logger.info(f"Merged {count} generated format sets into built-ins")
365
+ return count
366
+
367
+
368
+ def _py_literal(value):
369
+ """Render a value as a valid Python literal string.
370
+
371
+ Supports strings, numbers, bools, None, lists, tuples, dicts. Used when
372
+ emitting Python code for generated FormatSets.
373
+ """
374
+ if isinstance(value, str):
375
+ return repr(value)
376
+ if isinstance(value, (int, float)):
377
+ return str(value)
378
+ if value is True:
379
+ return "True"
380
+ if value is False:
381
+ return "False"
382
+ if value is None:
383
+ return "None"
384
+ if isinstance(value, list):
385
+ return "[" + ", ".join(_py_literal(v) for v in value) + "]"
386
+ if isinstance(value, dict):
387
+ # Render dict with stable key ordering
388
+ items = ", ".join(f"{_py_literal(k)}: {_py_literal(v)}" for k, v in sorted(value.items(), key=lambda kv: str(kv[0]).lower()))
389
+ return "{" + items + "}"
390
+ # Fallback
391
+ return repr(value)
392
+
393
+
394
+ def _format_column(col: Column) -> str:
395
+ """Render a Column instance as Python code.
396
+
397
+ Returns a string like: Column(name='Display Name', key='display_name', ...)
398
+ Only includes optional attributes when present.
399
+ """
400
+ parts = [f"name={_py_literal(getattr(col, 'name', ''))}", f"key={_py_literal(getattr(col, 'key', ''))}"]
401
+ fmt = getattr(col, 'format', None)
402
+ if fmt not in (None, False):
403
+ parts.append(f"format={_py_literal(bool(fmt))}")
404
+ return "Column(" + ", ".join(parts) + ")"
405
+
406
+
407
+ def _format_action(ap: ActionParameter | None) -> str:
408
+ """Render an ActionParameter as Python code for emission.
409
+
410
+ Includes function, required_params, optional_params, and spec_params when present.
411
+ """
412
+ if not ap:
413
+ return "None"
414
+ parts = [
415
+ f"function={_py_literal(getattr(ap, 'function', ''))}",
416
+ ]
417
+ req = getattr(ap, 'required_params', None)
418
+ if req:
419
+ parts.append(f"required_params={_py_literal(list(req))}")
420
+ opt = getattr(ap, 'optional_params', None)
421
+ if opt:
422
+ parts.append(f"optional_params={_py_literal(list(opt))}")
423
+ spec = getattr(ap, 'spec_params', None)
424
+ if spec:
425
+ parts.append(f"spec_params={_py_literal(dict(spec))}")
426
+ return "ActionParameter(" + ", ".join(parts) + ")"
427
+
428
+
429
+ def _format_format(fmt: Format) -> str:
430
+ """Render a Format instance as Python code, including types and columns."""
431
+ cols = getattr(fmt, 'columns', []) or []
432
+ types = getattr(fmt, 'types', []) or []
433
+ col_code = ", ".join(_format_column(c) for c in cols)
434
+ parts = [f"types={_py_literal(list(types))}", f"columns=[{col_code}]"]
435
+ return "Format(" + ", ".join(parts) + ")"
436
+
437
+
438
+ def _format_formatset(fs: FormatSet) -> str:
439
+ """Render a FormatSet as Python code including nested formats and actions."""
440
+ parts = []
441
+ if getattr(fs, 'target_type', None):
442
+ parts.append(f"target_type={_py_literal(fs.target_type)}")
443
+ if getattr(fs, 'heading', None):
444
+ parts.append(f"heading={_py_literal(fs.heading)}")
445
+ if getattr(fs, 'description', None):
446
+ parts.append(f"description={_py_literal(fs.description)}")
447
+ if getattr(fs, 'family', None):
448
+ parts.append(f"family={_py_literal(fs.family)}")
449
+ aliases = getattr(fs, 'aliases', None)
450
+ if aliases:
451
+ parts.append(f"aliases={_py_literal(list(aliases))}")
452
+ ann = getattr(fs, 'annotations', None)
453
+ if ann:
454
+ parts.append(f"annotations={_py_literal(dict(ann))}")
455
+ # formats
456
+ fmts = getattr(fs, 'formats', []) or []
457
+ fmt_code = ", ".join(_format_format(f) for f in fmts)
458
+ parts.append(f"formats=[{fmt_code}]")
459
+ # actions
460
+ action = getattr(fs, 'action', None)
461
+ if action:
462
+ parts.append(f"action={_format_action(action)}")
463
+ get_add = getattr(fs, 'get_additional_props', None)
464
+ if get_add:
465
+ parts.append(f"get_additional_props={_format_action(get_add)}")
466
+ return "FormatSet(" + ", ".join(parts) + ")"
467
+
468
+
469
+ def format_sets_to_python_code(sets: FormatSetDict, var_name: str = "generated_format_sets") -> str:
470
+ """Render a FormatSetDict into the text of a small Python module.
471
+
472
+ Parameters
473
+ - sets: the FormatSetDict to render
474
+ - var_name: the variable name to assign the dict to (default 'generated_format_sets')
475
+
476
+ Returns
477
+ - str: Python source code that, when imported, defines `var_name` as a FormatSetDict
478
+ """
479
+ header = (
480
+ "# Auto-generated by gen_report_specs.py\n"
481
+ "from pyegeria._output_format_models import Column, Format, ActionParameter, FormatSet, FormatSetDict\n\n"
482
+ )
483
+ # Stable ordering by key
484
+ entries = []
485
+ for name in sorted(sets.keys(), key=lambda s: s.lower()):
486
+ fs = sets[name]
487
+ entries.append(f" {_py_literal(name)}: {_format_formatset(fs)}")
488
+ body = f"{var_name} = FormatSetDict({{\n" + ",\n".join(entries) + "\n})\n"
489
+ return header + body
490
+
491
+
492
+ def save_generated_format_sets_code(
493
+ commands_json_path: str | Path,
494
+ out_file: str | Path,
495
+ *,
496
+ include_only_create: bool = True,
497
+ default_types: Optional[List[str]] = None,
498
+ var_name: str = "generated_format_sets",
499
+ ) -> Path:
500
+ """Build FormatSets and save them as an importable Python module.
501
+
502
+ Parameters
503
+ - commands_json_path: input commands.json path
504
+ - out_file: path to write the .py file (module will define `var_name`)
505
+ - include_only_create: filter to only "Create" commands (default True)
506
+ - default_types: list for Format.types (default ["ALL"]).
507
+ - var_name: variable name to bind the resulting FormatSetDict to
508
+
509
+ Returns
510
+ - Path to the written Python file
511
+
512
+ Notes
513
+ - You can import the resulting file dynamically and access the variable
514
+ to merge into `pyegeria.base_report_formats.report_specs`.
515
+ """
516
+ sets = build_format_sets_from_commands(
517
+ commands_json_path,
518
+ include_only_create=include_only_create,
519
+ default_types=default_types,
520
+ )
521
+ code = format_sets_to_python_code(sets, var_name=var_name)
522
+ out = Path(out_file)
523
+ out.parent.mkdir(parents=True, exist_ok=True)
524
+ out.write_text(code, encoding="utf-8")
525
+ logger.info(f"Saved Python code for {len(sets)} generated format sets to {out}")
526
+ return out
527
+
528
+
529
+ def main():
530
+ """CLI entry point for generating report FormatSets from commands.json.
531
+
532
+ Notes
533
+ - If a command object includes a string field `family`, it will be propagated into the generated
534
+ `FormatSet.family`. This applies to both JSON emission and generated Python code.
535
+
536
+ Synopsis
537
+ - Default (emit JSON):
538
+ python -m md_processing.md_processing_utils.gen_report_specs \
539
+ md_processing/data/commands.json md_processing/data/generated_format_sets.json --emit json
540
+ - Emit Python code:
541
+ python -m md_processing.md_processing_utils.gen_report_specs \
542
+ md_processing/data/commands.json md_processing/data/generated_format_sets.py --emit code
543
+ - In-memory only and list:
544
+ python -m md_processing.md_processing_utils.gen_report_specs \
545
+ md_processing/data/commands.json --emit dict --list
546
+
547
+ Flags
548
+ - --emit json|dict|code: controls output mode (default json)
549
+ - --merge: also merge generated sets into built-in report_specs at runtime
550
+ - --list: when emitting dict (or after merge), list set names to stdout
551
+ """
552
+ parser = argparse.ArgumentParser(description="Generate FormatSets from commands.json")
553
+ parser.add_argument("commands_json", nargs="?", help="Path to commands.json")
554
+ parser.add_argument("output_json", nargs="?", help="Path to output JSON file (when --emit json)")
555
+ parser.add_argument("--emit", choices=["json", "dict", "code"], default="json",
556
+ help="Choose output mode: save JSON file, write Python code, or work with in-memory FormatSetDict")
557
+ parser.add_argument("--merge", action="store_true",
558
+ help="Merge generated sets into built-in registry (report_specs)")
559
+ parser.add_argument("--list", action="store_true",
560
+ help="When emitting dict (and/or after merge), list set names to stdout")
561
+ args = parser.parse_args()
562
+
563
+ try:
564
+ input_file = args.commands_json or Prompt.ask(
565
+ "Enter commands.json:", default="md_processing/data/commands.json"
566
+ )
567
+
568
+ if args.emit == "json":
569
+ # JSON output path
570
+ output_file = args.output_json or Prompt.ask(
571
+ "Output File:", default="md_processing/data/generated_format_sets.json"
572
+ )
573
+ save_generated_format_sets(input_file, output_file)
574
+ if args.merge:
575
+ # Also load and merge saved JSON into registry for convenience
576
+ from pyegeria.view.base_report_formats import load_report_specs
577
+ load_report_specs(output_file, merge=True)
578
+ logger.info("Merged saved JSON into built-in registry")
579
+ elif args.emit == "code":
580
+ # Python code output path
581
+ output_file = args.output_json or Prompt.ask(
582
+ "Output Python File:", default="pyegeria/dr_egeria_reports.py" # Changed from config/
583
+ )
584
+ saved_path = save_generated_format_sets_code(input_file, output_file)
585
+ logger.info(f"Generated Python code written to {saved_path}")
586
+
587
+ if args.merge:
588
+ # Dynamically import and merge the generated variable
589
+ try:
590
+ import importlib.util
591
+ import sys as _sys
592
+
593
+ # Convert to absolute path to ensure spec_from_file_location can find it
594
+ abs_output_file = saved_path.resolve()
595
+
596
+ spec = importlib.util.spec_from_file_location("_gen_fs_module", abs_output_file)
597
+ if spec is None or spec.loader is None:
598
+ raise ImportError(f"Could not create module spec from {abs_output_file}")
599
+
600
+ mod = importlib.util.module_from_spec(spec)
601
+ spec.loader.exec_module(mod) # type: ignore
602
+
603
+ generated = getattr(mod, "generated_format_sets", None)
604
+ if generated:
605
+ from pyegeria.view.base_report_formats import report_specs
606
+ merged = 0
607
+ for name, fs in generated.items():
608
+ report_specs[name] = fs
609
+ merged += 1
610
+ logger.info(f"Merged {merged} generated format sets from code into built-ins")
611
+ else:
612
+ logger.warning("No 'generated_format_sets' found in generated code module")
613
+ except Exception as e:
614
+ logger.error(f"Failed to import and merge generated code: {e}")
615
+ else:
616
+ # Emit dict path: build in memory
617
+ sets = generate_format_sets(input_file)
618
+ logger.info(f"Generated {len(sets)} format sets (in-memory FormatSetDict)")
619
+ if args.merge:
620
+ from pyegeria.view.base_report_formats import report_specs
621
+ merged = 0
622
+ for name, fs in sets.items():
623
+ report_specs[name] = fs
624
+ merged += 1
625
+ logger.info(f"Merged {merged} generated format sets into built-ins")
626
+ if args.list:
627
+ # If merged, show names from the global registry that match the generated ones
628
+ names = sorted(list(sets.keys()))
629
+ print("Merged set names:")
630
+ for n in names:
631
+ print(f"- {n}")
632
+ else:
633
+ if args.list:
634
+ names = sorted(list(sets.keys()))
635
+ print(f"Generated set names ({len(names)}):")
636
+ for n in names:
637
+ print(f"- {n}")
638
+ except (KeyboardInterrupt, EOFError):
639
+ # Graceful exit when user cancels or stdin is not interactive
640
+ pass
641
+
642
+ if __name__ == "__main__":
643
+ main()