pyegeria 5.4.3.4__py3-none-any.whl → 5.4.4__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 (208) hide show
  1. commands/cat/debug_log.2025-09-04_08-21-58_788009.log.zip +0 -0
  2. commands/cat/debug_log.2025-09-05_09-37-53_062579.log.zip +0 -0
  3. commands/cat/list_format_set.py +3 -1
  4. md_processing/__init__.py +9 -1
  5. md_processing/data/commands.json +7181 -1400
  6. md_processing/data/generated_format_sets.json +4137 -0
  7. md_processing/data/generated_format_sets.py +51 -0
  8. md_processing/dr_egeria.py +14 -2
  9. md_processing/md_commands/ext_ref_commands.py +543 -0
  10. md_processing/md_commands/old_solution_architect_commands.py +1139 -0
  11. md_processing/md_commands/solution_architect_commands.py +26 -59
  12. md_processing/md_processing_utils/debug_log +1 -3
  13. md_processing/md_processing_utils/dr-egeria-help-2025-09-09T11:10:03.md +3305 -0
  14. md_processing/md_processing_utils/gen_format_sets.py +422 -0
  15. md_processing/md_processing_utils/md_processing_constants.py +19 -1
  16. pyegeria/_output_formats.py +239 -85
  17. pyegeria/collection_manager.py +3 -3
  18. pyegeria/egeria_tech_client.py +3 -0
  19. pyegeria/external_references.py +1794 -0
  20. pyegeria/solution_architect.py +290 -485
  21. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.dist-info}/METADATA +1 -1
  22. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.dist-info}/RECORD +25 -199
  23. commands/.DS_Store +0 -0
  24. commands/cat/.DS_Store +0 -0
  25. commands/cat/.env +0 -8
  26. commands/cat/debug_log.2025-08-29_07-07-27_848189.log.zip +0 -0
  27. commands/cat/debug_log.2025-08-30_21-15-48_528443.log.zip +0 -0
  28. commands/cat/debug_log.log +0 -7276
  29. commands/cat/logs/pyegeria.log +0 -90
  30. commands/cli/debug_log.log +0 -0
  31. commands/doc/.DS_Store +0 -0
  32. commands/ops/logs/pyegeria.log +0 -0
  33. md_processing/.DS_Store +0 -0
  34. md_processing/.idea/.gitignore +0 -8
  35. md_processing/.idea/inspectionProfiles/Project_Default.xml +0 -59
  36. md_processing/.idea/md_processing.iml +0 -15
  37. md_processing/.idea/modules.xml +0 -8
  38. md_processing/.idea/sonarlint/issuestore/index.pb +0 -0
  39. md_processing/.idea/sonarlint/securityhotspotstore/index.pb +0 -0
  40. md_processing/.idea/vcs.xml +0 -6
  41. md_processing/.idea/workspace.xml +0 -107
  42. md_processing/dr_egeria_inbox/Derive-Dr-Gov-Defs.md +0 -8
  43. md_processing/dr_egeria_inbox/Dr.Egeria Templates.md +0 -873
  44. md_processing/dr_egeria_inbox/arch_test.md +0 -57
  45. md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +0 -254
  46. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +0 -696
  47. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +0 -254
  48. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +0 -298
  49. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +0 -608
  50. md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +0 -94
  51. md_processing/dr_egeria_inbox/archive/freddie_intro.md +0 -284
  52. md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +0 -275
  53. md_processing/dr_egeria_inbox/archive/test-term.md +0 -110
  54. md_processing/dr_egeria_inbox/cat_test.md +0 -100
  55. md_processing/dr_egeria_inbox/collections.md +0 -39
  56. md_processing/dr_egeria_inbox/data_designer_debug.log +0 -6
  57. md_processing/dr_egeria_inbox/data_designer_out.md +0 -60
  58. md_processing/dr_egeria_inbox/data_designer_search_test.md +0 -11
  59. md_processing/dr_egeria_inbox/data_field.md +0 -54
  60. md_processing/dr_egeria_inbox/data_spec.md +0 -77
  61. md_processing/dr_egeria_inbox/data_spec_test.md +0 -2014
  62. md_processing/dr_egeria_inbox/data_test.md +0 -179
  63. md_processing/dr_egeria_inbox/data_test2.md +0 -429
  64. md_processing/dr_egeria_inbox/data_test3.md +0 -462
  65. md_processing/dr_egeria_inbox/dr_egeria_data_designer_1.md +0 -124
  66. md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +0 -168
  67. md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +0 -280
  68. md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +0 -318
  69. md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +0 -1073
  70. md_processing/dr_egeria_inbox/dr_egeria_isc1.md +0 -44
  71. md_processing/dr_egeria_inbox/generated_help_report.md +0 -9
  72. md_processing/dr_egeria_inbox/generated_help_terms.md +0 -842
  73. md_processing/dr_egeria_inbox/glossary_list.md +0 -5
  74. md_processing/dr_egeria_inbox/glossary_search_test.md +0 -40
  75. md_processing/dr_egeria_inbox/glossary_test1.md +0 -378
  76. md_processing/dr_egeria_inbox/gov_def.md +0 -718
  77. md_processing/dr_egeria_inbox/gov_def2.md +0 -447
  78. md_processing/dr_egeria_inbox/img.png +0 -0
  79. md_processing/dr_egeria_inbox/output_tests.md +0 -114
  80. md_processing/dr_egeria_inbox/product.md +0 -219
  81. md_processing/dr_egeria_inbox/rel.md +0 -8
  82. md_processing/dr_egeria_inbox/sb.md +0 -119
  83. md_processing/dr_egeria_inbox/solution-components.md +0 -136
  84. md_processing/dr_egeria_inbox/solution_blueprints.md +0 -118
  85. md_processing/dr_egeria_inbox/synonym_test.md +0 -42
  86. md_processing/dr_egeria_inbox/t2.md +0 -268
  87. md_processing/dr_egeria_outbox/.DS_Store +0 -0
  88. md_processing/dr_egeria_outbox/.obsidian/app.json +0 -1
  89. md_processing/dr_egeria_outbox/.obsidian/appearance.json +0 -1
  90. md_processing/dr_egeria_outbox/.obsidian/community-plugins.json +0 -7
  91. md_processing/dr_egeria_outbox/.obsidian/core-plugins.json +0 -33
  92. md_processing/dr_egeria_outbox/.obsidian/plugins/buttons/main.js +0 -5164
  93. md_processing/dr_egeria_outbox/.obsidian/plugins/buttons/manifest.json +0 -10
  94. md_processing/dr_egeria_outbox/.obsidian/plugins/buttons/styles.css +0 -624
  95. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/data.json +0 -10
  96. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/main.js +0 -4459
  97. md_processing/dr_egeria_outbox/.obsidian/plugins/calendar/manifest.json +0 -10
  98. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/data.json +0 -3
  99. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/main.js +0 -153
  100. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/manifest.json +0 -11
  101. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-kanban/styles.css +0 -1
  102. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/main.js +0 -500
  103. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +0 -12
  104. md_processing/dr_egeria_outbox/.obsidian/plugins/obsidian-tasks-plugin/styles.css +0 -1
  105. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/data.json +0 -38
  106. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/main.js +0 -37
  107. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/manifest.json +0 -11
  108. md_processing/dr_egeria_outbox/.obsidian/plugins/templater-obsidian/styles.css +0 -220
  109. md_processing/dr_egeria_outbox/.obsidian/types.json +0 -28
  110. md_processing/dr_egeria_outbox/.obsidian/workspace.json +0 -270
  111. md_processing/dr_egeria_outbox/Button Test.md +0 -11
  112. md_processing/dr_egeria_outbox/Scripts/.DS_Store +0 -0
  113. md_processing/dr_egeria_outbox/Scripts/sendRest.js +0 -24
  114. md_processing/dr_egeria_outbox/Templates/sendToApi.md.md +0 -17
  115. md_processing/dr_egeria_outbox/Untitled.canvas +0 -1
  116. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 09:26-product.md +0 -210
  117. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 14:03-product.md +0 -209
  118. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 14:24-product.md +0 -263
  119. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 16:03-data_spec_test.md +0 -2374
  120. md_processing/dr_egeria_outbox/monday/processed-2025-09-01 16:05-data_spec_test.md +0 -2374
  121. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 08:28-data_spec_test.md +0 -2321
  122. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 08:37-data_spec_test.md +0 -2304
  123. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 08:56-data_spec_test.md +0 -2324
  124. md_processing/dr_egeria_outbox/monday/processed-2025-09-02 09:00-data_spec_test.md +0 -2324
  125. md_processing/dr_egeria_outbox/processed-2025-08-30 16:56-generated_help_terms.md +0 -795
  126. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +0 -719
  127. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +0 -41
  128. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +0 -33
  129. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +0 -192
  130. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:08-gov_def2.md +0 -486
  131. md_processing/dr_egeria_outbox/thursday/processed-2025-07-17 22:10-gov_def2.md +0 -486
  132. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:53-gov_def2.md +0 -486
  133. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 08:54-gov_def2.md +0 -486
  134. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:03-gov_def2.md +0 -486
  135. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:06-gov_def2.md +0 -486
  136. md_processing/dr_egeria_outbox/thursday/processed-2025-07-18 09:10-gov_def2.md +0 -486
  137. md_processing/family_docs/Data Designer/Create_Data_Class.md +0 -164
  138. md_processing/family_docs/Data Designer/Create_Data_Dictionary.md +0 -30
  139. md_processing/family_docs/Data Designer/Create_Data_Field.md +0 -162
  140. md_processing/family_docs/Data Designer/Create_Data_Specification.md +0 -36
  141. md_processing/family_docs/Data Designer/Create_Data_Structure.md +0 -38
  142. md_processing/family_docs/Data Designer/View_Data_Classes.md +0 -78
  143. md_processing/family_docs/Data Designer/View_Data_Dictionaries.md +0 -78
  144. md_processing/family_docs/Data Designer/View_Data_Fields.md +0 -78
  145. md_processing/family_docs/Data Designer/View_Data_Specifications.md +0 -78
  146. md_processing/family_docs/Data Designer/View_Data_Structures.md +0 -78
  147. md_processing/family_docs/Data Designer.md +0 -842
  148. md_processing/family_docs/Digital Product Manager/Add_Member->Collection.md +0 -42
  149. md_processing/family_docs/Digital Product Manager/Attach_Collection->Resource.md +0 -36
  150. md_processing/family_docs/Digital Product Manager/Create_Agreement.md +0 -96
  151. md_processing/family_docs/Digital Product Manager/Create_Data_Sharing_Agreement.md +0 -72
  152. md_processing/family_docs/Digital Product Manager/Create_DigitalSubscription.md +0 -102
  153. md_processing/family_docs/Digital Product Manager/Create_Digital_Product.md +0 -134
  154. md_processing/family_docs/Digital Product Manager/Link_Agreement_Items.md +0 -60
  155. md_processing/family_docs/Digital Product Manager/Link_Contracts.md +0 -26
  156. md_processing/family_docs/Digital Product Manager/Link_Digital_Product_-_Digital_Product.md +0 -30
  157. md_processing/family_docs/Digital Product Manager/Link_Subscribers.md +0 -48
  158. md_processing/family_docs/Digital Product Manager.md +0 -668
  159. md_processing/family_docs/Glossary/Attach_Category_Parent.md +0 -18
  160. md_processing/family_docs/Glossary/Attach_Term-Term_Relationship.md +0 -26
  161. md_processing/family_docs/Glossary/Create_Category.md +0 -38
  162. md_processing/family_docs/Glossary/Create_Glossary.md +0 -42
  163. md_processing/family_docs/Glossary/Create_Term.md +0 -70
  164. md_processing/family_docs/Glossary.md +0 -206
  165. md_processing/family_docs/Governance Officer/Create_Business_Imperative.md +0 -106
  166. md_processing/family_docs/Governance Officer/Create_Certification_Type.md +0 -112
  167. md_processing/family_docs/Governance Officer/Create_Governance_Approach.md +0 -114
  168. md_processing/family_docs/Governance Officer/Create_Governance_Obligation.md +0 -114
  169. md_processing/family_docs/Governance Officer/Create_Governance_Principle.md +0 -114
  170. md_processing/family_docs/Governance Officer/Create_Governance_Procedure.md +0 -128
  171. md_processing/family_docs/Governance Officer/Create_Governance_Process.md +0 -122
  172. md_processing/family_docs/Governance Officer/Create_Governance_Processing_Purpose.md +0 -106
  173. md_processing/family_docs/Governance Officer/Create_Governance_Responsibility.md +0 -122
  174. md_processing/family_docs/Governance Officer/Create_Governance_Rule.md +0 -122
  175. md_processing/family_docs/Governance Officer/Create_Governance_Strategy.md +0 -106
  176. md_processing/family_docs/Governance Officer/Create_License_Type.md +0 -112
  177. md_processing/family_docs/Governance Officer/Create_Naming_Standard_Rule.md +0 -122
  178. md_processing/family_docs/Governance Officer/Create_Regulation_Article.md +0 -106
  179. md_processing/family_docs/Governance Officer/Create_Regulation_Definition.md +0 -118
  180. md_processing/family_docs/Governance Officer/Create_Security_Access_Control.md +0 -114
  181. md_processing/family_docs/Governance Officer/Create_Security_Group.md +0 -120
  182. md_processing/family_docs/Governance Officer/Create_Service_Level_Objectives.md +0 -122
  183. md_processing/family_docs/Governance Officer/Create_Threat_Definition.md +0 -106
  184. md_processing/family_docs/Governance Officer/Link_Governance_Controls.md +0 -32
  185. md_processing/family_docs/Governance Officer/Link_Governance_Drivers.md +0 -32
  186. md_processing/family_docs/Governance Officer/Link_Governance_Policies.md +0 -32
  187. md_processing/family_docs/Governance Officer/View_Governance_Definitions.md +0 -82
  188. md_processing/family_docs/Governance Officer.md +0 -2412
  189. md_processing/family_docs/Solution Architect/Create_Information_Supply_Chain.md +0 -70
  190. md_processing/family_docs/Solution Architect/Create_Solution_Blueprint.md +0 -44
  191. md_processing/family_docs/Solution Architect/Create_Solution_Component.md +0 -96
  192. md_processing/family_docs/Solution Architect/Create_Solution_Role.md +0 -66
  193. md_processing/family_docs/Solution Architect/Link_Information_Supply_Chain_Peers.md +0 -32
  194. md_processing/family_docs/Solution Architect/Link_Solution_Component_Peers.md +0 -32
  195. md_processing/family_docs/Solution Architect/View_Information_Supply_Chains.md +0 -32
  196. md_processing/family_docs/Solution Architect/View_Solution_Blueprints.md +0 -32
  197. md_processing/family_docs/Solution Architect/View_Solution_Components.md +0 -32
  198. md_processing/family_docs/Solution Architect/View_Solution_Roles.md +0 -32
  199. md_processing/family_docs/Solution Architect.md +0 -490
  200. md_processing/md_commands/old_project_commands.py +0 -164
  201. md_processing/md_processing_utils/debug_log.log +0 -5580
  202. md_processing/md_processing_utils/generated_help_terms.md +0 -842
  203. md_processing/md_processing_utils/logs/pyegeria.log +0 -56
  204. pyegeria/.DS_Store +0 -0
  205. pyegeria/___external_references.py +0 -3255
  206. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.dist-info}/LICENSE +0 -0
  207. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.dist-info}/WHEEL +0 -0
  208. {pyegeria-5.4.3.4.dist-info → pyegeria-5.4.4.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()
@@ -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