pyegeria 5.4.3.4__py3-none-any.whl → 5.4.4.1__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 (215) hide show
  1. commands/cat/dr_egeria_command_help.py +11 -17
  2. commands/cat/list_format_set.py +3 -1
  3. md_processing/__init__.py +9 -1
  4. md_processing/data/commands.json +6951 -541
  5. md_processing/data/generated_format_sets.json +4137 -0
  6. md_processing/data/generated_format_sets.py +51 -0
  7. md_processing/dr_egeria.py +14 -2
  8. md_processing/md_commands/ext_ref_commands.py +543 -0
  9. md_processing/md_commands/old_solution_architect_commands.py +1139 -0
  10. md_processing/md_commands/solution_architect_commands.py +26 -59
  11. md_processing/md_processing_utils/debug_log +186 -3
  12. md_processing/md_processing_utils/debug_log.2025-09-09_11-10-03_528597.zip +0 -0
  13. md_processing/md_processing_utils/dr-egeria-help-2025-09-09T11:10:03.md +3305 -0
  14. md_processing/md_processing_utils/dr-egeria-help-2025-09-10T14:49:49.md +3460 -0
  15. md_processing/md_processing_utils/dr-egeria-help-2025-09-10T14:57:46.md +472 -0
  16. md_processing/md_processing_utils/gen_format_sets.py +422 -0
  17. md_processing/md_processing_utils/generate_md_cmd_templates.py +4 -4
  18. md_processing/md_processing_utils/md_processing_constants.py +19 -1
  19. pyegeria/_output_formats.py +240 -86
  20. pyegeria/collection_manager.py +3 -3
  21. pyegeria/egeria_tech_client.py +3 -0
  22. pyegeria/external_references.py +1794 -0
  23. pyegeria/glossary_manager.py +2 -2
  24. pyegeria/solution_architect.py +290 -485
  25. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.1.dist-info}/METADATA +1 -1
  26. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.1.dist-info}/RECORD +29 -205
  27. commands/.DS_Store +0 -0
  28. commands/cat/.DS_Store +0 -0
  29. commands/cat/.env +0 -8
  30. commands/cat/debug_log.2025-08-29_07-07-27_848189.log.zip +0 -0
  31. commands/cat/debug_log.2025-08-30_21-15-48_528443.log.zip +0 -0
  32. commands/cat/debug_log.2025-09-01_07-02-58_818650.log.zip +0 -0
  33. commands/cat/debug_log.2025-09-02_07-44-39_567276.log.zip +0 -0
  34. commands/cat/debug_log.2025-09-03_07-45-21_986388.log.zip +0 -0
  35. commands/cat/debug_log.log +0 -7276
  36. commands/cat/logs/pyegeria.log +0 -90
  37. commands/cli/debug_log.log +0 -0
  38. commands/doc/.DS_Store +0 -0
  39. commands/ops/logs/pyegeria.log +0 -0
  40. md_processing/.DS_Store +0 -0
  41. md_processing/.idea/.gitignore +0 -8
  42. md_processing/.idea/inspectionProfiles/Project_Default.xml +0 -59
  43. md_processing/.idea/md_processing.iml +0 -15
  44. md_processing/.idea/modules.xml +0 -8
  45. md_processing/.idea/sonarlint/issuestore/index.pb +0 -0
  46. md_processing/.idea/sonarlint/securityhotspotstore/index.pb +0 -0
  47. md_processing/.idea/vcs.xml +0 -6
  48. md_processing/.idea/workspace.xml +0 -107
  49. md_processing/dr_egeria_inbox/Derive-Dr-Gov-Defs.md +0 -8
  50. md_processing/dr_egeria_inbox/Dr.Egeria Templates.md +0 -873
  51. md_processing/dr_egeria_inbox/arch_test.md +0 -57
  52. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +0 -254
  53. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +0 -696
  54. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +0 -254
  55. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +0 -298
  56. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +0 -608
  57. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +0 -94
  58. md_processing/dr_egeria_inbox/archive/freddie_intro.md +0 -284
  59. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +0 -275
  60. md_processing/dr_egeria_inbox/archive/test-term.md +0 -110
  61. md_processing/dr_egeria_inbox/cat_test.md +0 -100
  62. md_processing/dr_egeria_inbox/collections.md +0 -39
  63. md_processing/dr_egeria_inbox/data_designer_debug.log +0 -6
  64. md_processing/dr_egeria_inbox/data_designer_out.md +0 -60
  65. md_processing/dr_egeria_inbox/data_designer_search_test.md +0 -11
  66. md_processing/dr_egeria_inbox/data_field.md +0 -54
  67. md_processing/dr_egeria_inbox/data_spec.md +0 -77
  68. md_processing/dr_egeria_inbox/data_spec_test.md +0 -2014
  69. md_processing/dr_egeria_inbox/data_test.md +0 -179
  70. md_processing/dr_egeria_inbox/data_test2.md +0 -429
  71. md_processing/dr_egeria_inbox/data_test3.md +0 -462
  72. md_processing/dr_egeria_inbox/dr_egeria_data_designer_1.md +0 -124
  73. md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +0 -168
  74. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +0 -280
  75. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +0 -318
  76. md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +0 -1073
  77. md_processing/dr_egeria_inbox/dr_egeria_isc1.md +0 -44
  78. md_processing/dr_egeria_inbox/generated_help_report.md +0 -9
  79. md_processing/dr_egeria_inbox/generated_help_terms.md +0 -842
  80. md_processing/dr_egeria_inbox/glossary_list.md +0 -5
  81. md_processing/dr_egeria_inbox/glossary_search_test.md +0 -40
  82. md_processing/dr_egeria_inbox/glossary_test1.md +0 -378
  83. md_processing/dr_egeria_inbox/gov_def.md +0 -718
  84. md_processing/dr_egeria_inbox/gov_def2.md +0 -447
  85. md_processing/dr_egeria_inbox/img.png +0 -0
  86. md_processing/dr_egeria_inbox/output_tests.md +0 -114
  87. md_processing/dr_egeria_inbox/product.md +0 -219
  88. md_processing/dr_egeria_inbox/rel.md +0 -8
  89. md_processing/dr_egeria_inbox/sb.md +0 -119
  90. md_processing/dr_egeria_inbox/solution-components.md +0 -136
  91. md_processing/dr_egeria_inbox/solution_blueprints.md +0 -118
  92. md_processing/dr_egeria_inbox/synonym_test.md +0 -42
  93. md_processing/dr_egeria_inbox/t2.md +0 -268
  94. md_processing/dr_egeria_outbox/.DS_Store +0 -0
  95. md_processing/dr_egeria_outbox/.obsidian/app.json +0 -1
  96. md_processing/dr_egeria_outbox/.obsidian/appearance.json +0 -1
  97. md_processing/dr_egeria_outbox/.obsidian/community-plugins.json +0 -7
  98. md_processing/dr_egeria_outbox/.obsidian/core-plugins.json +0 -33
  99. md_processing/dr_egeria_outbox/.obsidian/plugins/buttons/main.js +0 -5164
  100. md_processing/dr_egeria_outbox/.obsidian/plugins/buttons/manifest.json +0 -10
  101. md_processing/dr_egeria_outbox/.obsidian/plugins/buttons/styles.css +0 -624
  102. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/data.json +0 -10
  103. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/main.js +0 -4459
  104. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/manifest.json +0 -10
  105. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/data.json +0 -3
  106. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/main.js +0 -153
  107. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/manifest.json +0 -11
  108. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/styles.css +0 -1
  109. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/main.js +0 -500
  110. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +0 -12
  111. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/styles.css +0 -1
  112. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/data.json +0 -38
  113. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/main.js +0 -37
  114. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/manifest.json +0 -11
  115. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/styles.css +0 -220
  116. md_processing/dr_egeria_outbox/.obsidian/types.json +0 -28
  117. md_processing/dr_egeria_outbox/.obsidian/workspace.json +0 -270
  118. md_processing/dr_egeria_outbox/Button Test.md +0 -11
  119. md_processing/dr_egeria_outbox/Scripts/.DS_Store +0 -0
  120. md_processing/dr_egeria_outbox/Scripts/sendRest.js +0 -24
  121. md_processing/dr_egeria_outbox/Templates/sendToApi.md.md +0 -17
  122. md_processing/dr_egeria_outbox/Untitled.canvas +0 -1
  123. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 09:26-product.md +0 -210
  124. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 14:03-product.md +0 -209
  125. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 14:24-product.md +0 -263
  126. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 16:03-data_spec_test.md +0 -2374
  127. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 16:05-data_spec_test.md +0 -2374
  128. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 08:28-data_spec_test.md +0 -2321
  129. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 08:37-data_spec_test.md +0 -2304
  130. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 08:56-data_spec_test.md +0 -2324
  131. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 09:00-data_spec_test.md +0 -2324
  132. md_processing/dr_egeria_outbox/processed-2025-08-30 16:56-generated_help_terms.md +0 -795
  133. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +0 -719
  134. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +0 -41
  135. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +0 -33
  136. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +0 -192
  137. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:08-gov_def2.md +0 -486
  138. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:10-gov_def2.md +0 -486
  139. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:53-gov_def2.md +0 -486
  140. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:54-gov_def2.md +0 -486
  141. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:03-gov_def2.md +0 -486
  142. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:06-gov_def2.md +0 -486
  143. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:10-gov_def2.md +0 -486
  144. md_processing/family_docs/Data Designer/Create_Data_Class.md +0 -164
  145. md_processing/family_docs/Data Designer/Create_Data_Dictionary.md +0 -30
  146. md_processing/family_docs/Data Designer/Create_Data_Field.md +0 -162
  147. md_processing/family_docs/Data Designer/Create_Data_Specification.md +0 -36
  148. md_processing/family_docs/Data Designer/Create_Data_Structure.md +0 -38
  149. md_processing/family_docs/Data Designer/View_Data_Classes.md +0 -78
  150. md_processing/family_docs/Data Designer/View_Data_Dictionaries.md +0 -78
  151. md_processing/family_docs/Data Designer/View_Data_Fields.md +0 -78
  152. md_processing/family_docs/Data Designer/View_Data_Specifications.md +0 -78
  153. md_processing/family_docs/Data Designer/View_Data_Structures.md +0 -78
  154. md_processing/family_docs/Data Designer.md +0 -842
  155. md_processing/family_docs/Digital Product Manager/Add_Member->Collection.md +0 -42
  156. md_processing/family_docs/Digital Product Manager/Attach_Collection->Resource.md +0 -36
  157. md_processing/family_docs/Digital Product Manager/Create_Agreement.md +0 -96
  158. md_processing/family_docs/Digital Product Manager/Create_Data_Sharing_Agreement.md +0 -72
  159. md_processing/family_docs/Digital Product Manager/Create_DigitalSubscription.md +0 -102
  160. md_processing/family_docs/Digital Product Manager/Create_Digital_Product.md +0 -134
  161. md_processing/family_docs/Digital Product Manager/Link_Agreement_Items.md +0 -60
  162. md_processing/family_docs/Digital Product Manager/Link_Contracts.md +0 -26
  163. md_processing/family_docs/Digital Product Manager/Link_Digital_Product_-_Digital_Product.md +0 -30
  164. md_processing/family_docs/Digital Product Manager/Link_Subscribers.md +0 -48
  165. md_processing/family_docs/Digital Product Manager.md +0 -668
  166. md_processing/family_docs/Glossary/Attach_Category_Parent.md +0 -18
  167. md_processing/family_docs/Glossary/Attach_Term-Term_Relationship.md +0 -26
  168. md_processing/family_docs/Glossary/Create_Category.md +0 -38
  169. md_processing/family_docs/Glossary/Create_Glossary.md +0 -42
  170. md_processing/family_docs/Glossary/Create_Term.md +0 -70
  171. md_processing/family_docs/Glossary.md +0 -206
  172. md_processing/family_docs/Governance Officer/Create_Business_Imperative.md +0 -106
  173. md_processing/family_docs/Governance Officer/Create_Certification_Type.md +0 -112
  174. md_processing/family_docs/Governance Officer/Create_Governance_Approach.md +0 -114
  175. md_processing/family_docs/Governance Officer/Create_Governance_Obligation.md +0 -114
  176. md_processing/family_docs/Governance Officer/Create_Governance_Principle.md +0 -114
  177. md_processing/family_docs/Governance Officer/Create_Governance_Procedure.md +0 -128
  178. md_processing/family_docs/Governance Officer/Create_Governance_Process.md +0 -122
  179. md_processing/family_docs/Governance Officer/Create_Governance_Processing_Purpose.md +0 -106
  180. md_processing/family_docs/Governance Officer/Create_Governance_Responsibility.md +0 -122
  181. md_processing/family_docs/Governance Officer/Create_Governance_Rule.md +0 -122
  182. md_processing/family_docs/Governance Officer/Create_Governance_Strategy.md +0 -106
  183. md_processing/family_docs/Governance Officer/Create_License_Type.md +0 -112
  184. md_processing/family_docs/Governance Officer/Create_Naming_Standard_Rule.md +0 -122
  185. md_processing/family_docs/Governance Officer/Create_Regulation_Article.md +0 -106
  186. md_processing/family_docs/Governance Officer/Create_Regulation_Definition.md +0 -118
  187. md_processing/family_docs/Governance Officer/Create_Security_Access_Control.md +0 -114
  188. md_processing/family_docs/Governance Officer/Create_Security_Group.md +0 -120
  189. md_processing/family_docs/Governance Officer/Create_Service_Level_Objectives.md +0 -122
  190. md_processing/family_docs/Governance Officer/Create_Threat_Definition.md +0 -106
  191. md_processing/family_docs/Governance Officer/Link_Governance_Controls.md +0 -32
  192. md_processing/family_docs/Governance Officer/Link_Governance_Drivers.md +0 -32
  193. md_processing/family_docs/Governance Officer/Link_Governance_Policies.md +0 -32
  194. md_processing/family_docs/Governance Officer/View_Governance_Definitions.md +0 -82
  195. md_processing/family_docs/Governance Officer.md +0 -2412
  196. md_processing/family_docs/Solution Architect/Create_Information_Supply_Chain.md +0 -70
  197. md_processing/family_docs/Solution Architect/Create_Solution_Blueprint.md +0 -44
  198. md_processing/family_docs/Solution Architect/Create_Solution_Component.md +0 -96
  199. md_processing/family_docs/Solution Architect/Create_Solution_Role.md +0 -66
  200. md_processing/family_docs/Solution Architect/Link_Information_Supply_Chain_Peers.md +0 -32
  201. md_processing/family_docs/Solution Architect/Link_Solution_Component_Peers.md +0 -32
  202. md_processing/family_docs/Solution Architect/View_Information_Supply_Chains.md +0 -32
  203. md_processing/family_docs/Solution Architect/View_Solution_Blueprints.md +0 -32
  204. md_processing/family_docs/Solution Architect/View_Solution_Components.md +0 -32
  205. md_processing/family_docs/Solution Architect/View_Solution_Roles.md +0 -32
  206. md_processing/family_docs/Solution Architect.md +0 -490
  207. md_processing/md_commands/old_project_commands.py +0 -164
  208. md_processing/md_processing_utils/debug_log.log +0 -5580
  209. md_processing/md_processing_utils/generated_help_terms.md +0 -842
  210. md_processing/md_processing_utils/logs/pyegeria.log +0 -56
  211. pyegeria/.DS_Store +0 -0
  212. pyegeria/___external_references.py +0 -3255
  213. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.1.dist-info}/LICENSE +0 -0
  214. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.1.dist-info}/WHEEL +0 -0
  215. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,422 @@
1
+ import argparse
2
+ import json
3
+ import re
4
+ import argparse
5
+ from pathlib import Path
6
+ from typing import Iterable, List, Optional
7
+
8
+ from loguru import logger
9
+ from rich.prompt import Prompt
10
+
11
+ from pyegeria._output_format_models import Column, Format, ActionParameter, FormatSet, FormatSetDict
12
+ from pyegeria._output_formats import OPTIONAL_PARAMS # ["page_size","start_from","starts_with","ends_with","ignore_case"]
13
+
14
+ def _is_create_command(cmd_key: str, cmd_obj: dict) -> bool:
15
+ name = str(cmd_obj.get("display_name", "")).strip()
16
+ return (isinstance(cmd_key, str) and cmd_key.strip().lower().startswith("create")) or \
17
+ (name.lower().startswith("create"))
18
+
19
+ def _derive_set_name_from_display_name(display_name: str) -> str:
20
+ """From 'Create Governance Strategy Definition' -> 'GovernanceStrategyDefinition'."""
21
+ if not display_name:
22
+ return ""
23
+ parts = display_name.strip().split()
24
+ if len(parts) <= 1:
25
+ return re.sub(r"\s+", "", display_name.strip())+"-DrE"
26
+ rest = " ".join(parts[1:]).strip()
27
+ rest = rest+"-DrE"
28
+ return re.sub(r"\s+", "", rest)
29
+
30
+ def _safe_parse_constraints(s) -> dict:
31
+ """Parse find_constraints (often an escaped JSON string) into a dict."""
32
+ if not s:
33
+ return {}
34
+ if isinstance(s, dict):
35
+ return s
36
+ txt = str(s).strip()
37
+ # Attempt 1: direct JSON
38
+ try:
39
+ return json.loads(txt)
40
+ except Exception:
41
+ logger.error(f"Error parsing constraints: {s!r}")
42
+ pass
43
+ # Attempt 2: unwrap quotes
44
+ try:
45
+ txt2 = txt.strip('"').strip("'")
46
+ return json.loads(txt2)
47
+ except Exception:
48
+ pass
49
+ # Attempt 3: heuristic single->double quotes for dict-like strings
50
+ if ("{" in txt and "}" in txt) and ("'" in txt and '"' not in txt):
51
+ try:
52
+ return json.loads(txt.replace("'", '"'))
53
+ except Exception:
54
+ pass
55
+ logger.debug(f"Could not parse find_constraints: {s!r} -> {{}}")
56
+ return {}
57
+
58
+ def _extract_basic_columns_from_attributes(attributes: Iterable[dict]) -> List[Column]:
59
+ """
60
+ attributes: list of single-key dicts, e.g.
61
+ {"Display Name": {"variable_name": "display_name", "level": "Basic", ...}}
62
+ """
63
+
64
+ cols: List[Column] = []
65
+ seen: set[str] = set()
66
+ for entry in attributes or []:
67
+ if not isinstance(entry, dict) or not entry:
68
+ continue
69
+ # Some entries may contain more than one key; scan all items
70
+ for label, details in entry.items():
71
+ if not isinstance(details, dict):
72
+ continue
73
+ if details.get("level") != "Basic":
74
+ continue
75
+ key = details.get("variable_name")
76
+ if not key or key in seen:
77
+ continue
78
+ seen.add(key)
79
+ cols.append(Column(name=str(label), key=str(key)))
80
+ return cols
81
+
82
+ def build_format_sets_from_commands(
83
+ commands_json_path: str | Path,
84
+ *,
85
+ include_only_create: bool = True,
86
+ default_types: Optional[List[str]] = None
87
+ ) -> FormatSetDict:
88
+ """
89
+ Convert commands.json into a FormatSetDict following your mappings.
90
+ """
91
+ path = Path(commands_json_path)
92
+ data = json.loads(path.read_text(encoding="utf-8"))
93
+
94
+ if isinstance(data, dict):
95
+ items_iter = data.items()
96
+ elif isinstance(data, list):
97
+ # If it’s a list of command objects, synthesize keys from display_name or index
98
+ def _key_for(idx, obj):
99
+ dn = str(obj.get("display_name", "")).strip()
100
+ return dn or f"cmd_{idx}"
101
+
102
+ items_iter = ((_key_for(i, obj), obj) for i, obj in enumerate(data))
103
+ else:
104
+ raise ValueError("commands.json root must be an object/dict or an array of command objects")
105
+ data = dict(items_iter).get("Command Specifications", {})
106
+ logger.info(
107
+ f"Loaded commands.json from {path} with {len(data)} commands"
108
+ )
109
+
110
+
111
+ results = FormatSetDict()
112
+ types = default_types or ["ALL"]
113
+
114
+ for cmd_key, cmd_obj in data.items():
115
+ if not isinstance(cmd_obj, dict):
116
+ continue
117
+ if include_only_create and not _is_create_command(cmd_key, cmd_obj):
118
+ continue
119
+
120
+ display_name = str(cmd_obj.get("display_name", "")).strip()
121
+ if not display_name:
122
+ logger.debug(f"Skip {cmd_key!r}: missing display_name")
123
+ continue
124
+ set_name = re.sub(r"\s+", "-", display_name)
125
+ set_name = set_name.strip() + "-DrE"
126
+ # set_name = _derive_set_name_from_display_name(display_name)
127
+ if not set_name:
128
+ logger.debug(f"Skip {cmd_key!r}: could not derive set name from display_name={display_name!r}")
129
+ continue
130
+
131
+ columns = _extract_basic_columns_from_attributes(cmd_obj.get("Attributes", []))
132
+ if not columns:
133
+ logger.debug(f"Skip {set_name}: no Basic attributes")
134
+ continue
135
+
136
+ find_method = cmd_obj.get("find_method") or ""
137
+ constraints = _safe_parse_constraints(cmd_obj.get("find_constraints"))
138
+ action = None
139
+ if find_method:
140
+ action = ActionParameter(
141
+ function=find_method,
142
+ required_params=["search_string"],
143
+ optional_params=OPTIONAL_PARAMS,
144
+ spec_params=constraints or {},
145
+ )
146
+
147
+ fs = FormatSet(
148
+ target_type=set_name,
149
+ heading=f"{set_name} Attributes",
150
+ description=f"Auto-generated format for {display_name} (Create).",
151
+ formats=[Format(types=types, columns=columns)],
152
+ action=action
153
+ )
154
+ results[set_name] = fs
155
+
156
+ return results
157
+
158
+ def save_generated_format_sets(
159
+ commands_json_path: str | Path,
160
+ out_file: str | Path,
161
+ *,
162
+ include_only_create: bool = True,
163
+ default_types: Optional[List[str]] = None
164
+ ) -> Path:
165
+ """Build and save to a JSON file loadable by _output_formats.load_output_format_sets."""
166
+ sets = build_format_sets_from_commands(
167
+ commands_json_path,
168
+ include_only_create=include_only_create,
169
+ default_types=default_types,
170
+ )
171
+ out = Path(out_file)
172
+ out.parent.mkdir(parents=True, exist_ok=True)
173
+ sets.save_to_json(str(out))
174
+ logger.info(f"Saved {len(sets)} generated format sets to {out}")
175
+ return out
176
+
177
+ # New: expose a simple alias that returns a FormatSetDict directly
178
+ # for callers that want the in-memory object rather than JSON.
179
+ def generate_format_sets(
180
+ commands_json_path: str | Path,
181
+ *,
182
+ include_only_create: bool = True,
183
+ default_types: Optional[List[str]] = None,
184
+ ):
185
+ """Return a FormatSetDict built from commands.json (no file I/O)."""
186
+ return build_format_sets_from_commands(
187
+ commands_json_path,
188
+ include_only_create=include_only_create,
189
+ default_types=default_types,
190
+ )
191
+
192
+ # New: merge generated sets into the builtin registry
193
+
194
+ def merge_generated_format_sets(
195
+ commands_json_path: str | Path,
196
+ *,
197
+ include_only_create: bool = True,
198
+ default_types: Optional[List[str]] = None,
199
+ ) -> int:
200
+ """Build a FormatSetDict and merge into pyegeria._output_formats.output_format_sets.
201
+ Returns the number of sets merged/added.
202
+ """
203
+ from pyegeria._output_formats import output_format_sets # local import to avoid cycles on tooling
204
+
205
+ gen = build_format_sets_from_commands(
206
+ commands_json_path,
207
+ include_only_create=include_only_create,
208
+ default_types=default_types,
209
+ )
210
+ count = 0
211
+ for name, fs in gen.items():
212
+ output_format_sets[name] = fs
213
+ count += 1
214
+ logger.info(f"Merged {count} generated format sets into built-ins")
215
+ return count
216
+
217
+
218
+ def _py_literal(value):
219
+ """Render a Python literal safely for strings, lists, dicts, bools, None, numbers."""
220
+ if isinstance(value, str):
221
+ return repr(value)
222
+ if isinstance(value, (int, float)):
223
+ return str(value)
224
+ if value is True:
225
+ return "True"
226
+ if value is False:
227
+ return "False"
228
+ if value is None:
229
+ return "None"
230
+ if isinstance(value, list):
231
+ return "[" + ", ".join(_py_literal(v) for v in value) + "]"
232
+ if isinstance(value, dict):
233
+ # Render dict with stable key ordering
234
+ items = ", ".join(f"{_py_literal(k)}: {_py_literal(v)}" for k, v in sorted(value.items(), key=lambda kv: str(kv[0]).lower()))
235
+ return "{" + items + "}"
236
+ # Fallback
237
+ return repr(value)
238
+
239
+
240
+ def _format_column(col: Column) -> str:
241
+ parts = [f"name={_py_literal(getattr(col, 'name', ''))}", f"key={_py_literal(getattr(col, 'key', ''))}"]
242
+ fmt = getattr(col, 'format', None)
243
+ if fmt not in (None, False):
244
+ parts.append(f"format={_py_literal(bool(fmt))}")
245
+ return "Column(" + ", ".join(parts) + ")"
246
+
247
+
248
+ def _format_action(ap: ActionParameter | None) -> str:
249
+ if not ap:
250
+ return "None"
251
+ parts = [
252
+ f"function={_py_literal(getattr(ap, 'function', ''))}",
253
+ ]
254
+ req = getattr(ap, 'required_params', None)
255
+ if req:
256
+ parts.append(f"required_params={_py_literal(list(req))}")
257
+ opt = getattr(ap, 'optional_params', None)
258
+ if opt:
259
+ parts.append(f"optional_params={_py_literal(list(opt))}")
260
+ spec = getattr(ap, 'spec_params', None)
261
+ if spec:
262
+ parts.append(f"spec_params={_py_literal(dict(spec))}")
263
+ return "ActionParameter(" + ", ".join(parts) + ")"
264
+
265
+
266
+ def _format_format(fmt: Format) -> str:
267
+ cols = getattr(fmt, 'columns', []) or []
268
+ types = getattr(fmt, 'types', []) or []
269
+ col_code = ", ".join(_format_column(c) for c in cols)
270
+ parts = [f"types={_py_literal(list(types))}", f"columns=[{col_code}]"]
271
+ return "Format(" + ", ".join(parts) + ")"
272
+
273
+
274
+ def _format_formatset(fs: FormatSet) -> str:
275
+ parts = []
276
+ if getattr(fs, 'target_type', None):
277
+ parts.append(f"target_type={_py_literal(fs.target_type)}")
278
+ if getattr(fs, 'heading', None):
279
+ parts.append(f"heading={_py_literal(fs.heading)}")
280
+ if getattr(fs, 'description', None):
281
+ parts.append(f"description={_py_literal(fs.description)}")
282
+ aliases = getattr(fs, 'aliases', None)
283
+ if aliases:
284
+ parts.append(f"aliases={_py_literal(list(aliases))}")
285
+ ann = getattr(fs, 'annotations', None)
286
+ if ann:
287
+ parts.append(f"annotations={_py_literal(dict(ann))}")
288
+ # formats
289
+ fmts = getattr(fs, 'formats', []) or []
290
+ fmt_code = ", ".join(_format_format(f) for f in fmts)
291
+ parts.append(f"formats=[{fmt_code}]")
292
+ # actions
293
+ action = getattr(fs, 'action', None)
294
+ if action:
295
+ parts.append(f"action={_format_action(action)}")
296
+ get_add = getattr(fs, 'get_additional_props', None)
297
+ if get_add:
298
+ parts.append(f"get_additional_props={_format_action(get_add)}")
299
+ return "FormatSet(" + ", ".join(parts) + ")"
300
+
301
+
302
+ def format_sets_to_python_code(sets: FormatSetDict, var_name: str = "generated_format_sets") -> str:
303
+ """Render a FormatSetDict into a Python module string defining `var_name`."""
304
+ header = (
305
+ "# Auto-generated by gen_format_sets.py\n"
306
+ "from pyegeria._output_format_models import Column, Format, ActionParameter, FormatSet, FormatSetDict\n\n"
307
+ )
308
+ # Stable ordering by key
309
+ entries = []
310
+ for name in sorted(sets.keys(), key=lambda s: s.lower()):
311
+ fs = sets[name]
312
+ entries.append(f" {_py_literal(name)}: {_format_formatset(fs)}")
313
+ body = f"{var_name} = FormatSetDict({{\n" + ",\n".join(entries) + "\n})\n"
314
+ return header + body
315
+
316
+
317
+ def save_generated_format_sets_code(
318
+ commands_json_path: str | Path,
319
+ out_file: str | Path,
320
+ *,
321
+ include_only_create: bool = True,
322
+ default_types: Optional[List[str]] = None,
323
+ var_name: str = "generated_format_sets",
324
+ ) -> Path:
325
+ """Build FormatSetDict and save as Python code to out_file."""
326
+ sets = build_format_sets_from_commands(
327
+ commands_json_path,
328
+ include_only_create=include_only_create,
329
+ default_types=default_types,
330
+ )
331
+ code = format_sets_to_python_code(sets, var_name=var_name)
332
+ out = Path(out_file)
333
+ out.parent.mkdir(parents=True, exist_ok=True)
334
+ out.write_text(code, encoding="utf-8")
335
+ logger.info(f"Saved Python code for {len(sets)} generated format sets to {out}")
336
+ return out
337
+
338
+
339
+ def main():
340
+ parser = argparse.ArgumentParser(description="Generate FormatSets from commands.json")
341
+ parser.add_argument("commands_json", nargs="?", help="Path to commands.json")
342
+ parser.add_argument("output_json", nargs="?", help="Path to output JSON file (when --emit json)")
343
+ parser.add_argument("--emit", choices=["json", "dict", "code"], default="json",
344
+ help="Choose output mode: save JSON file, write Python code, or work with in-memory FormatSetDict")
345
+ parser.add_argument("--merge", action="store_true",
346
+ help="Merge generated sets into built-in registry (output_format_sets)")
347
+ parser.add_argument("--list", action="store_true",
348
+ help="When emitting dict (and/or after merge), list set names to stdout")
349
+ args = parser.parse_args()
350
+
351
+ try:
352
+ input_file = args.commands_json or Prompt.ask(
353
+ "Enter commands.json:", default="md_processing/data/commands.json"
354
+ )
355
+
356
+ if args.emit == "json":
357
+ # JSON output path
358
+ output_file = args.output_json or Prompt.ask(
359
+ "Output File:", default="md_processing/data/generated_format_sets.json"
360
+ )
361
+ save_generated_format_sets(input_file, output_file)
362
+ if args.merge:
363
+ # Also load and merge saved JSON into registry for convenience
364
+ from pyegeria._output_formats import load_output_format_sets
365
+ load_output_format_sets(output_file, merge=True)
366
+ logger.info("Merged saved JSON into built-in registry")
367
+ elif args.emit == "code":
368
+ # Python code output path
369
+ output_file = args.output_json or Prompt.ask(
370
+ "Output Python File:", default="md_processing/data/generated_format_sets.py"
371
+ )
372
+ save_generated_format_sets_code(input_file, output_file)
373
+ if args.merge:
374
+ # Dynamically import and merge the generated variable
375
+ try:
376
+ import importlib.util
377
+ import sys as _sys
378
+ spec = importlib.util.spec_from_file_location("_gen_fs_module", output_file)
379
+ mod = importlib.util.module_from_spec(spec)
380
+ assert spec and spec.loader
381
+ spec.loader.exec_module(mod) # type: ignore
382
+ generated = getattr(mod, "generated_format_sets", None)
383
+ if generated:
384
+ from pyegeria._output_formats import output_format_sets
385
+ merged = 0
386
+ for name, fs in generated.items():
387
+ output_format_sets[name] = fs
388
+ merged += 1
389
+ logger.info(f"Merged {merged} generated format sets from code into built-ins")
390
+ else:
391
+ logger.warning("No 'generated_format_sets' found in generated code module")
392
+ except Exception as e:
393
+ logger.error(f"Failed to import and merge generated code: {e}")
394
+ else:
395
+ # Emit dict path: build in memory
396
+ sets = generate_format_sets(input_file)
397
+ logger.info(f"Generated {len(sets)} format sets (in-memory FormatSetDict)")
398
+ if args.merge:
399
+ from pyegeria._output_formats import output_format_sets
400
+ merged = 0
401
+ for name, fs in sets.items():
402
+ output_format_sets[name] = fs
403
+ merged += 1
404
+ logger.info(f"Merged {merged} generated format sets into built-ins")
405
+ if args.list:
406
+ # If merged, show names from the global registry that match the generated ones
407
+ names = sorted(list(sets.keys()))
408
+ print("Merged set names:")
409
+ for n in names:
410
+ print(f"- {n}")
411
+ else:
412
+ if args.list:
413
+ names = sorted(list(sets.keys()))
414
+ print(f"Generated set names ({len(names)}):")
415
+ for n in names:
416
+ print(f"- {n}")
417
+ except (KeyboardInterrupt, EOFError):
418
+ # Graceful exit when user cancels or stdin is not interactive
419
+ pass
420
+
421
+ if __name__ == "__main__":
422
+ main()
@@ -88,8 +88,8 @@ def main():
88
88
  command_output = io.StringIO()
89
89
 
90
90
  # Write command header
91
- command_output.write(f"# **{command}**\n>\t{command_description}\n")
92
- print(f"\n## {command_verb} **{command}**\n>\t{command_description}")
91
+ command_output.write(f"# {command}\n>\t{command_description}\n")
92
+ print(f"\n## {command_verb} {command}\n>\t{command_description}")
93
93
 
94
94
  # Process command attributes
95
95
  attributes = values['Attributes']
@@ -99,8 +99,8 @@ def main():
99
99
  continue
100
100
  user_specified = value.get('user_specified', 'true') in ["true", "True"]
101
101
 
102
- command_output.write(f"\n## **{key}**\n")
103
- print(f"\n### **{key}**")
102
+ command_output.write(f"\n## {key}\n")
103
+ print(f"\n### {key}")
104
104
 
105
105
  command_output.write(f">\t**Input Required**: {value.get('input_required', 'false')}\n\n")
106
106
  print(f">\tInput Required: {value.get('input_required', 'false')}")
@@ -123,13 +123,30 @@ COLLECTIONS_LIST = ["List Collections", "View Collections", "List Digital Produc
123
123
  "List Context Event Collections", "View Context Event Collections",
124
124
  "List Name Space Collections", "View Name Space Collections",
125
125
  "List Event Set Collections", "View Event Set Collections",
126
- "List Naming Standard Rulesets", "View Naming Standard Rulesets",
126
+ "List Naming Standard Rulesets", "View Naming Standard Rulesets", "List External Reference",
127
+ "List Related Media", "List Cited Document", "List External Data Source", "List External Model Source",
127
128
  ]
128
129
 
129
130
  PROJECT_COMMANDS = ["Create Project", "Update Project", "Create Campaign", "Update Campaign",
130
131
  "Create Task", "Update Task", "Create Study Project", "Update Study Project",
131
132
  "Create Personal Project", "Update Personal Project"]
132
133
 
134
+ LINK_EXT_REF = ["Link External Reference", "Link Referenceable->External Reference", "Attach External Reference",
135
+ "Detach External Reference", "Detach External Reference Link", "Link External Data Source",
136
+ "Link External Model Source", "Detach External Data Source", "Detach External Model Source",]
137
+
138
+ LINK_MEDIA = ["Link Related Media", "Link Referenceable->Related Media", "Attach Related Media", "Attach Media Reference Link",
139
+ "Detach Related Media", "Detach Related Media Link", "Detach Media Reference Link"]
140
+
141
+ LINK_CITED_DOC = ["Link Cited Document", "Link Referenceable->Cited Document", "Attach Cited Document", "Attach Cited Document Link",
142
+ "Detach Cited Document", "Detach Cited Document Link",]
143
+
144
+ EXT_REF_UPSERT = ["Create External Reference", "Update External Reference",
145
+ "Create Related Media", "Update Related Media",
146
+ "Create Cited Document", "Update Cited Document",
147
+ "Create External Data Source", "Update External Data Source", "Create External Model Source",
148
+ "Update External Model Source",]
149
+ EXT_REF_COMMANDS = EXT_REF_UPSERT + LINK_EXT_REF + LINK_MEDIA + LINK_CITED_DOC
133
150
  command_list = ["Provenance", "Create Glossary", "Update Glossary", "Create Term", "Update Term", "List Terms",
134
151
  "List Term Details", "List Glossary Terms", "List Term History", "List Term Revision History",
135
152
  "List Term Update History", "List Glossary Structure", "List Glossaries", "List Categories",
@@ -198,6 +215,7 @@ command_list.extend(GOV_LINK_LIST)
198
215
  command_list.extend(COLLECTIONS_LIST)
199
216
  command_list.extend(SIMPLE_COLLECTIONS)
200
217
  command_list.extend(PROJECT_COMMANDS)
218
+ command_list.extend(EXT_REF_COMMANDS)
201
219
  command_list.extend(["Link Governance Response", "Detach Governance Response",
202
220
  "Link Governance Mechanism", "Detach Governance Mechanism"])
203
221