systemlink-cli 1.3.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.
- slcli/__init__.py +1 -0
- slcli/__main__.py +23 -0
- slcli/_version.py +4 -0
- slcli/asset_click.py +1289 -0
- slcli/cli_formatters.py +218 -0
- slcli/cli_utils.py +504 -0
- slcli/comment_click.py +602 -0
- slcli/completion_click.py +418 -0
- slcli/config.py +81 -0
- slcli/config_click.py +498 -0
- slcli/dff_click.py +979 -0
- slcli/dff_decorators.py +24 -0
- slcli/example_click.py +404 -0
- slcli/example_loader.py +274 -0
- slcli/example_provisioner.py +2777 -0
- slcli/examples/README.md +134 -0
- slcli/examples/_schema/schema-v1.0.json +169 -0
- slcli/examples/demo-complete-workflow/README.md +323 -0
- slcli/examples/demo-complete-workflow/config.yaml +638 -0
- slcli/examples/demo-test-plans/README.md +132 -0
- slcli/examples/demo-test-plans/config.yaml +154 -0
- slcli/examples/exercise-5-1-parametric-insights/README.md +101 -0
- slcli/examples/exercise-5-1-parametric-insights/config.yaml +1589 -0
- slcli/examples/exercise-7-1-test-plans/README.md +93 -0
- slcli/examples/exercise-7-1-test-plans/config.yaml +323 -0
- slcli/examples/spec-compliance-notebooks/README.md +140 -0
- slcli/examples/spec-compliance-notebooks/config.yaml +112 -0
- slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +1553 -0
- slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +1577 -0
- slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +912 -0
- slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- slcli/feed_click.py +892 -0
- slcli/file_click.py +932 -0
- slcli/function_click.py +1400 -0
- slcli/function_templates.py +85 -0
- slcli/main.py +406 -0
- slcli/mcp_click.py +269 -0
- slcli/mcp_server.py +748 -0
- slcli/notebook_click.py +1770 -0
- slcli/platform.py +345 -0
- slcli/policy_click.py +679 -0
- slcli/policy_utils.py +411 -0
- slcli/profiles.py +411 -0
- slcli/response_handlers.py +359 -0
- slcli/routine_click.py +763 -0
- slcli/skill_click.py +253 -0
- slcli/skills/slcli/SKILL.md +713 -0
- slcli/skills/slcli/references/analysis-recipes.md +474 -0
- slcli/skills/slcli/references/filtering.md +236 -0
- slcli/skills/systemlink-webapp/SKILL.md +744 -0
- slcli/skills/systemlink-webapp/references/deployment.md +123 -0
- slcli/skills/systemlink-webapp/references/nimble-angular.md +380 -0
- slcli/skills/systemlink-webapp/references/systemlink-services.md +192 -0
- slcli/ssl_trust.py +93 -0
- slcli/system_click.py +2216 -0
- slcli/table_utils.py +124 -0
- slcli/tag_click.py +794 -0
- slcli/templates_click.py +599 -0
- slcli/testmonitor_click.py +1667 -0
- slcli/universal_handlers.py +305 -0
- slcli/user_click.py +1218 -0
- slcli/utils.py +832 -0
- slcli/web_editor.py +295 -0
- slcli/webapp_click.py +981 -0
- slcli/workflow_preview.py +287 -0
- slcli/workflows_click.py +988 -0
- slcli/workitem_click.py +2258 -0
- slcli/workspace_click.py +576 -0
- slcli/workspace_utils.py +206 -0
- systemlink_cli-1.3.1.dist-info/METADATA +20 -0
- systemlink_cli-1.3.1.dist-info/RECORD +74 -0
- systemlink_cli-1.3.1.dist-info/WHEEL +4 -0
- systemlink_cli-1.3.1.dist-info/entry_points.txt +7 -0
- systemlink_cli-1.3.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Enhanced response handlers for all CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Dict, List, Any, Optional, Union, Callable
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from .utils import ExitCodes, handle_api_error, format_success
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FilteredResponse:
|
|
14
|
+
"""Mock response class for use with UniversalResponseHandler.
|
|
15
|
+
|
|
16
|
+
This class mimics a requests.Response object to allow filtered data
|
|
17
|
+
to be passed to UniversalResponseHandler without making additional API calls.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, data: Dict[str, Any], status_code: int = 200):
|
|
21
|
+
"""Initialize with data and optional status code.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
data: Dictionary containing the response data
|
|
25
|
+
status_code: HTTP status code to return (default: 200)
|
|
26
|
+
"""
|
|
27
|
+
self._data = data
|
|
28
|
+
self._status_code = status_code
|
|
29
|
+
|
|
30
|
+
def json(self) -> Dict[str, Any]:
|
|
31
|
+
"""Return the response data as JSON."""
|
|
32
|
+
return self._data
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def status_code(self) -> int:
|
|
36
|
+
"""Return the HTTP status code."""
|
|
37
|
+
return self._status_code
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class UniversalResponseHandler:
|
|
41
|
+
"""Universal response handler for all CLI commands."""
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def handle_list_response(
|
|
45
|
+
resp: Union[requests.Response, "FilteredResponse"],
|
|
46
|
+
data_key: str,
|
|
47
|
+
item_name: str,
|
|
48
|
+
format_output: str,
|
|
49
|
+
formatter_func: Optional[Callable[[Dict[str, Any]], List[str]]] = None,
|
|
50
|
+
headers: Optional[List[str]] = None,
|
|
51
|
+
column_widths: Optional[List[int]] = None,
|
|
52
|
+
empty_message: Optional[str] = None,
|
|
53
|
+
enable_pagination: bool = True,
|
|
54
|
+
page_size: int = 25,
|
|
55
|
+
total_count: Optional[int] = None,
|
|
56
|
+
shown_count: Optional[int] = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Handle list response with standardized formatting and optional pagination.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
resp: API response
|
|
62
|
+
data_key: Key to extract items from response
|
|
63
|
+
item_name: Name of item type (for messages)
|
|
64
|
+
format_output: 'table' or 'json'
|
|
65
|
+
formatter_func: Function to format table rows
|
|
66
|
+
headers: Table headers
|
|
67
|
+
column_widths: Table column widths
|
|
68
|
+
empty_message: Message when no items found
|
|
69
|
+
enable_pagination: Whether to enable pagination for table output
|
|
70
|
+
page_size: Number of items per page
|
|
71
|
+
total_count: Optional server-provided total count for all matching items.
|
|
72
|
+
shown_count: Optional count of items shown so far (useful for paged responses).
|
|
73
|
+
"""
|
|
74
|
+
from .cli_utils import paginate_list_output
|
|
75
|
+
from .table_utils import output_formatted_list
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
data = resp.json()
|
|
79
|
+
# Support both dict with data_key and direct array responses
|
|
80
|
+
if isinstance(data, list):
|
|
81
|
+
items = data
|
|
82
|
+
elif isinstance(data, dict):
|
|
83
|
+
items = data.get(data_key, [])
|
|
84
|
+
else:
|
|
85
|
+
items = []
|
|
86
|
+
|
|
87
|
+
if not empty_message:
|
|
88
|
+
empty_message = f"No {item_name}s found."
|
|
89
|
+
|
|
90
|
+
if enable_pagination and format_output.lower() == "table":
|
|
91
|
+
# Use pagination for table output
|
|
92
|
+
paginate_list_output(
|
|
93
|
+
items,
|
|
94
|
+
page_size=page_size,
|
|
95
|
+
format_output=format_output,
|
|
96
|
+
formatter_func=formatter_func,
|
|
97
|
+
headers=headers,
|
|
98
|
+
column_widths=column_widths,
|
|
99
|
+
empty_message=empty_message,
|
|
100
|
+
total_label=f"{item_name}(s)",
|
|
101
|
+
)
|
|
102
|
+
elif format_output.lower() == "json":
|
|
103
|
+
# For JSON format, always output all items (no display pagination)
|
|
104
|
+
click.echo(json.dumps(items, indent=2))
|
|
105
|
+
elif formatter_func and headers and column_widths:
|
|
106
|
+
# Use traditional output (no pagination)
|
|
107
|
+
output_formatted_list(
|
|
108
|
+
items,
|
|
109
|
+
format_output,
|
|
110
|
+
headers,
|
|
111
|
+
column_widths,
|
|
112
|
+
formatter_func,
|
|
113
|
+
empty_message,
|
|
114
|
+
f"{item_name}(s)",
|
|
115
|
+
)
|
|
116
|
+
# If the caller provided a server-side total, display a
|
|
117
|
+
# concise summary like the notebook listing after rendering
|
|
118
|
+
# the formatted table for the current page.
|
|
119
|
+
if total_count is not None:
|
|
120
|
+
shown = int(shown_count) if shown_count is not None else len(items)
|
|
121
|
+
remaining = max(int(total_count) - shown, 0)
|
|
122
|
+
click.echo(
|
|
123
|
+
f"\nShowing {shown} of {total_count} {item_name}(s). {remaining} more available."
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
# Fallback to simple JSON/basic formatting
|
|
127
|
+
if format_output.lower() == "json":
|
|
128
|
+
click.echo(json.dumps(items, indent=2))
|
|
129
|
+
else:
|
|
130
|
+
if not items:
|
|
131
|
+
click.echo(empty_message)
|
|
132
|
+
else:
|
|
133
|
+
for item in items:
|
|
134
|
+
click.echo(f"• {item.get('name', item.get('id', 'Unknown'))}")
|
|
135
|
+
|
|
136
|
+
# If the caller provided a server-side total, display a
|
|
137
|
+
# concise summary like the notebook listing:
|
|
138
|
+
# "Showing 25 of 556 webapp(s). 531 more available."
|
|
139
|
+
if total_count is not None:
|
|
140
|
+
# If shown_count wasn't provided, assume current page length
|
|
141
|
+
shown = int(shown_count) if shown_count is not None else len(items)
|
|
142
|
+
remaining = max(int(total_count) - shown, 0)
|
|
143
|
+
click.echo(
|
|
144
|
+
f"\nShowing {shown} of {total_count} {item_name}(s). {remaining} more available."
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
# Fallback to the old behaviour
|
|
148
|
+
click.echo(f"\nTotal: {len(items)} {item_name}(s)")
|
|
149
|
+
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
handle_api_error(exc)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def handle_get_response(
|
|
155
|
+
resp: requests.Response,
|
|
156
|
+
item_name: str,
|
|
157
|
+
format_output: str,
|
|
158
|
+
table_formatter_func: Optional[Callable[[Dict[str, Any]], List[str]]] = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Handle get response with standardized formatting."""
|
|
161
|
+
try:
|
|
162
|
+
data = resp.json()
|
|
163
|
+
|
|
164
|
+
if format_output.lower() == "json":
|
|
165
|
+
click.echo(json.dumps(data, indent=2))
|
|
166
|
+
else:
|
|
167
|
+
if table_formatter_func:
|
|
168
|
+
table_formatter_func(data)
|
|
169
|
+
else:
|
|
170
|
+
# Fallback to basic info display
|
|
171
|
+
click.echo(f"{item_name.title()} Details")
|
|
172
|
+
click.echo("=" * 50)
|
|
173
|
+
for key, value in data.items():
|
|
174
|
+
if isinstance(value, (str, int, bool, float)):
|
|
175
|
+
click.echo(f"{key.title()}: {value}")
|
|
176
|
+
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
handle_api_error(exc)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def handle_create_response(
|
|
182
|
+
resp: requests.Response,
|
|
183
|
+
item_name: str,
|
|
184
|
+
success_message_template: str = "✓ {item_name} created successfully.",
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Handle create response with standardized success messaging."""
|
|
187
|
+
try:
|
|
188
|
+
if resp.status_code in (200, 201):
|
|
189
|
+
data = resp.json() if resp.text.strip() else {}
|
|
190
|
+
message = success_message_template.format(item_name=item_name.title())
|
|
191
|
+
|
|
192
|
+
format_success(message, data)
|
|
193
|
+
else:
|
|
194
|
+
click.echo(f"✗ Failed to create {item_name}", err=True)
|
|
195
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
196
|
+
|
|
197
|
+
except Exception as exc:
|
|
198
|
+
handle_api_error(exc)
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def handle_update_response(
|
|
202
|
+
resp: requests.Response,
|
|
203
|
+
item_name: str,
|
|
204
|
+
success_message_template: str = "✓ {item_name} updated successfully.",
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Handle update response with standardized success messaging."""
|
|
207
|
+
try:
|
|
208
|
+
if resp.status_code in (200, 204):
|
|
209
|
+
data = resp.json() if resp.text.strip() else {}
|
|
210
|
+
message = success_message_template.format(item_name=item_name.title())
|
|
211
|
+
|
|
212
|
+
format_success(message, data)
|
|
213
|
+
else:
|
|
214
|
+
click.echo(f"✗ Failed to update {item_name}", err=True)
|
|
215
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
216
|
+
|
|
217
|
+
except Exception as exc:
|
|
218
|
+
handle_api_error(exc)
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def handle_delete_response(
|
|
222
|
+
resp: requests.Response,
|
|
223
|
+
item_name: str,
|
|
224
|
+
item_count: int = 1,
|
|
225
|
+
success_message_template: str = "✓ {count} {item_name}(s) deleted successfully.",
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Handle delete response with standardized success messaging."""
|
|
228
|
+
try:
|
|
229
|
+
if resp.status_code in (200, 204):
|
|
230
|
+
message = success_message_template.format(count=item_count, item_name=item_name)
|
|
231
|
+
click.echo(message)
|
|
232
|
+
else:
|
|
233
|
+
click.echo(f"✗ Failed to delete {item_name}(s)", err=True)
|
|
234
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
235
|
+
|
|
236
|
+
except Exception as exc:
|
|
237
|
+
handle_api_error(exc)
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def handle_export_response(
|
|
241
|
+
resp: requests.Response,
|
|
242
|
+
item_name: str,
|
|
243
|
+
output_file: str,
|
|
244
|
+
success_message_template: str = "✓ {item_name} exported to {output_file}",
|
|
245
|
+
) -> None:
|
|
246
|
+
"""Handle export response with file saving."""
|
|
247
|
+
from .utils import save_json_file
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
data = resp.json()
|
|
251
|
+
save_json_file(data, output_file)
|
|
252
|
+
|
|
253
|
+
message = success_message_template.format(
|
|
254
|
+
item_name=item_name.title(), output_file=output_file
|
|
255
|
+
)
|
|
256
|
+
click.echo(message)
|
|
257
|
+
|
|
258
|
+
except Exception as exc:
|
|
259
|
+
handle_api_error(exc)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class BatchResponseHandler:
|
|
263
|
+
"""Handler for batch operations with progress tracking."""
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
def handle_batch_operation(
|
|
267
|
+
items: List[Dict[str, Any]],
|
|
268
|
+
operation_func: Callable[[Dict[str, Any]], None],
|
|
269
|
+
operation_name: str,
|
|
270
|
+
item_name: str,
|
|
271
|
+
show_progress: bool = True,
|
|
272
|
+
) -> None:
|
|
273
|
+
"""Handle batch operations with progress tracking."""
|
|
274
|
+
total = len(items)
|
|
275
|
+
success_count = 0
|
|
276
|
+
failed_items = []
|
|
277
|
+
|
|
278
|
+
for i, item in enumerate(items):
|
|
279
|
+
if show_progress and total > 1:
|
|
280
|
+
click.echo(f"Processing {operation_name} {i + 1}/{total}...", nl=False)
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
operation_func(item)
|
|
284
|
+
success_count += 1
|
|
285
|
+
if show_progress and total > 1:
|
|
286
|
+
click.echo(" ✓")
|
|
287
|
+
except Exception as exc:
|
|
288
|
+
failed_items.append({"item": item, "error": str(exc)})
|
|
289
|
+
if show_progress and total > 1:
|
|
290
|
+
click.echo(f" ✗ ({exc})")
|
|
291
|
+
|
|
292
|
+
# Summary
|
|
293
|
+
if success_count > 0:
|
|
294
|
+
click.echo(f"✓ {success_count} {item_name}(s) {operation_name}d successfully.")
|
|
295
|
+
|
|
296
|
+
if failed_items:
|
|
297
|
+
click.echo(f"✗ {len(failed_items)} {item_name}(s) failed:", err=True)
|
|
298
|
+
for failed in failed_items:
|
|
299
|
+
item_id = failed["item"].get("id", failed["item"].get("name", "Unknown")) # type: ignore
|
|
300
|
+
click.echo(f" - {item_id}: {failed['error']}", err=True)
|
|
301
|
+
|
|
302
|
+
if failed_items and success_count == 0:
|
|
303
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
304
|
+
elif failed_items:
|
|
305
|
+
sys.exit(ExitCodes.INVALID_INPUT)
|