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,359 @@
|
|
|
1
|
+
"""Standardized API response handling utilities for DFF commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from requests import Response
|
|
10
|
+
|
|
11
|
+
from .utils import ExitCodes
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DFFResponseHandler:
|
|
15
|
+
"""Standardized response handling for DFF API operations."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def handle_list_response(
|
|
19
|
+
response: Response,
|
|
20
|
+
item_type: str,
|
|
21
|
+
format_output: str = "table",
|
|
22
|
+
workspace_filter: Optional[str] = None,
|
|
23
|
+
output_formatter: Optional[Any] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Handle API response for list operations.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
response: HTTP response object
|
|
29
|
+
item_type: Type of items being listed (e.g., "configurations", "groups")
|
|
30
|
+
format_output: Output format ("table", "json", "csv")
|
|
31
|
+
workspace_filter: Optional workspace filter
|
|
32
|
+
output_formatter: Formatter function for the output
|
|
33
|
+
"""
|
|
34
|
+
if response.status_code == 200:
|
|
35
|
+
data = response.json()
|
|
36
|
+
items = data.get(item_type, [])
|
|
37
|
+
|
|
38
|
+
# Apply workspace filtering if specified
|
|
39
|
+
if workspace_filter:
|
|
40
|
+
items = [item for item in items if item.get("workspace") == workspace_filter]
|
|
41
|
+
|
|
42
|
+
if not items:
|
|
43
|
+
filter_msg = f" in workspace '{workspace_filter}'" if workspace_filter else ""
|
|
44
|
+
click.echo(f"No {item_type} found{filter_msg}")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Use provided formatter or default JSON output
|
|
48
|
+
if output_formatter:
|
|
49
|
+
output_formatter(items, format_output)
|
|
50
|
+
else:
|
|
51
|
+
DFFResponseHandler._default_output(items, format_output)
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
DFFResponseHandler._handle_error_response(response, f"list {item_type}")
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def handle_export_response(
|
|
58
|
+
response: Response,
|
|
59
|
+
output_file: str,
|
|
60
|
+
item_type: str,
|
|
61
|
+
workspace_filter: Optional[str] = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Handle API response for export operations.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
response: HTTP response object
|
|
67
|
+
output_file: Output file path
|
|
68
|
+
item_type: Type of items being exported
|
|
69
|
+
workspace_filter: Optional workspace filter
|
|
70
|
+
"""
|
|
71
|
+
if response.status_code == 200:
|
|
72
|
+
data = response.json()
|
|
73
|
+
items = data.get(item_type, [])
|
|
74
|
+
|
|
75
|
+
# Apply workspace filtering if specified
|
|
76
|
+
if workspace_filter:
|
|
77
|
+
items = [item for item in items if item.get("workspace") == workspace_filter]
|
|
78
|
+
data[item_type] = items
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
Path(output_file).write_text(json.dumps(data, indent=2))
|
|
82
|
+
filter_msg = f" (workspace: {workspace_filter})" if workspace_filter else ""
|
|
83
|
+
click.echo(f"✓ Exported {len(items)} {item_type} to {output_file}{filter_msg}")
|
|
84
|
+
except IOError as e:
|
|
85
|
+
click.echo(f"✗ Failed to write to {output_file}: {e}", err=True)
|
|
86
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
87
|
+
else:
|
|
88
|
+
DFFResponseHandler._handle_error_response(response, f"export {item_type}")
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def handle_import_response(
|
|
92
|
+
response: Response,
|
|
93
|
+
item_count: int,
|
|
94
|
+
item_type: str,
|
|
95
|
+
operation: str = "imported",
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Handle API response for import operations.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
response: HTTP response object
|
|
101
|
+
item_count: Number of items being imported
|
|
102
|
+
item_type: Type of items being imported
|
|
103
|
+
operation: Operation description (e.g., "imported", "created")
|
|
104
|
+
"""
|
|
105
|
+
if response.status_code in [200, 201]:
|
|
106
|
+
click.echo(f"✓ Successfully {operation} {item_count} {item_type}")
|
|
107
|
+
else:
|
|
108
|
+
DFFResponseHandler._handle_error_response(response, f"import {item_type}")
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def handle_delete_response(
|
|
112
|
+
response: Response,
|
|
113
|
+
item_identifier: str,
|
|
114
|
+
item_type: str,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Handle API response for delete operations.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
response: HTTP response object
|
|
120
|
+
item_identifier: Identifier of the deleted item
|
|
121
|
+
item_type: Type of item being deleted
|
|
122
|
+
"""
|
|
123
|
+
if response.status_code == 204:
|
|
124
|
+
click.echo(f"✓ Successfully deleted {item_type}: {item_identifier}")
|
|
125
|
+
elif response.status_code == 404:
|
|
126
|
+
click.echo(f"✗ {item_type.capitalize()} not found: {item_identifier}", err=True)
|
|
127
|
+
sys.exit(ExitCodes.NOT_FOUND)
|
|
128
|
+
else:
|
|
129
|
+
DFFResponseHandler._handle_error_response(response, f"delete {item_type}")
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def handle_create_response(
|
|
133
|
+
response: Response,
|
|
134
|
+
item_data: Dict[str, Any],
|
|
135
|
+
item_type: str,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Handle API response for create operations.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
response: HTTP response object
|
|
141
|
+
item_data: Data of the created item
|
|
142
|
+
item_type: Type of item being created
|
|
143
|
+
"""
|
|
144
|
+
if response.status_code == 201:
|
|
145
|
+
item_id = item_data.get("key") or item_data.get("name", "unknown")
|
|
146
|
+
click.echo(f"✓ Successfully created {item_type}: {item_id}")
|
|
147
|
+
elif response.status_code == 409:
|
|
148
|
+
item_id = item_data.get("key") or item_data.get("name", "unknown")
|
|
149
|
+
click.echo(f"✗ {item_type.capitalize()} already exists: {item_id}", err=True)
|
|
150
|
+
sys.exit(ExitCodes.INVALID_INPUT)
|
|
151
|
+
else:
|
|
152
|
+
DFFResponseHandler._handle_error_response(response, f"create {item_type}")
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def handle_update_response(
|
|
156
|
+
response: Response,
|
|
157
|
+
item_identifier: str,
|
|
158
|
+
item_type: str,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Handle API response for update operations.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
response: HTTP response object
|
|
164
|
+
item_identifier: Identifier of the updated item
|
|
165
|
+
item_type: Type of item being updated
|
|
166
|
+
"""
|
|
167
|
+
if response.status_code == 200:
|
|
168
|
+
click.echo(f"✓ Successfully updated {item_type}: {item_identifier}")
|
|
169
|
+
elif response.status_code == 404:
|
|
170
|
+
click.echo(f"✗ {item_type.capitalize()} not found: {item_identifier}", err=True)
|
|
171
|
+
sys.exit(ExitCodes.NOT_FOUND)
|
|
172
|
+
else:
|
|
173
|
+
DFFResponseHandler._handle_error_response(response, f"update {item_type}")
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def handle_get_response(
|
|
177
|
+
response: Response,
|
|
178
|
+
item_identifier: str,
|
|
179
|
+
item_type: str,
|
|
180
|
+
format_output: str = "json",
|
|
181
|
+
output_formatter: Optional[Any] = None,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Handle API response for get operations.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
response: HTTP response object
|
|
187
|
+
item_identifier: Identifier of the item being retrieved
|
|
188
|
+
item_type: Type of item being retrieved
|
|
189
|
+
format_output: Output format
|
|
190
|
+
output_formatter: Optional custom formatter
|
|
191
|
+
"""
|
|
192
|
+
if response.status_code == 200:
|
|
193
|
+
data = response.json()
|
|
194
|
+
|
|
195
|
+
if output_formatter:
|
|
196
|
+
output_formatter([data], format_output)
|
|
197
|
+
else:
|
|
198
|
+
DFFResponseHandler._default_output(data, format_output)
|
|
199
|
+
|
|
200
|
+
elif response.status_code == 404:
|
|
201
|
+
click.echo(f"✗ {item_type.capitalize()} not found: {item_identifier}", err=True)
|
|
202
|
+
sys.exit(ExitCodes.NOT_FOUND)
|
|
203
|
+
else:
|
|
204
|
+
DFFResponseHandler._handle_error_response(response, f"get {item_type}")
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _handle_error_response(response: Response, operation: str) -> None:
|
|
208
|
+
"""Handle error responses with standardized messaging.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
response: HTTP response object
|
|
212
|
+
operation: Description of the operation that failed
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
error_data = response.json()
|
|
216
|
+
error_message = error_data.get("message", "Unknown error")
|
|
217
|
+
click.echo(f"✗ Failed to {operation}: {error_message}", err=True)
|
|
218
|
+
except (ValueError, KeyError):
|
|
219
|
+
click.echo(
|
|
220
|
+
f"✗ Failed to {operation}: HTTP {response.status_code}",
|
|
221
|
+
err=True,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Map HTTP status codes to appropriate exit codes
|
|
225
|
+
if response.status_code == 400:
|
|
226
|
+
sys.exit(ExitCodes.INVALID_INPUT)
|
|
227
|
+
elif response.status_code == 401:
|
|
228
|
+
sys.exit(ExitCodes.PERMISSION_DENIED)
|
|
229
|
+
elif response.status_code == 403:
|
|
230
|
+
sys.exit(ExitCodes.PERMISSION_DENIED)
|
|
231
|
+
elif response.status_code == 404:
|
|
232
|
+
sys.exit(ExitCodes.NOT_FOUND)
|
|
233
|
+
elif response.status_code == 409:
|
|
234
|
+
sys.exit(ExitCodes.INVALID_INPUT)
|
|
235
|
+
else:
|
|
236
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def _default_output(data: Any, format_output: str) -> None:
|
|
240
|
+
"""Default output formatting for response data.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
data: Data to output
|
|
244
|
+
format_output: Format for output
|
|
245
|
+
"""
|
|
246
|
+
if format_output == "json":
|
|
247
|
+
click.echo(json.dumps(data, indent=2))
|
|
248
|
+
else:
|
|
249
|
+
# For non-JSON formats, convert to JSON as fallback
|
|
250
|
+
click.echo(json.dumps(data, indent=2))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class DFFBatchResponseHandler:
|
|
254
|
+
"""Handler for batch operations with multiple API calls."""
|
|
255
|
+
|
|
256
|
+
def __init__(self, operation_name: str):
|
|
257
|
+
"""Initialize batch response handler.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
operation_name: Name of the batch operation
|
|
261
|
+
"""
|
|
262
|
+
self.operation_name = operation_name
|
|
263
|
+
self.successes: List[str] = []
|
|
264
|
+
self.failures: List[tuple[str, str]] = []
|
|
265
|
+
|
|
266
|
+
def add_success(self, item_identifier: str) -> None:
|
|
267
|
+
"""Record a successful operation.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
item_identifier: Identifier of the successfully processed item
|
|
271
|
+
"""
|
|
272
|
+
self.successes.append(item_identifier)
|
|
273
|
+
|
|
274
|
+
def add_failure(self, item_identifier: str, error_message: str) -> None:
|
|
275
|
+
"""Record a failed operation.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
item_identifier: Identifier of the item that failed
|
|
279
|
+
error_message: Error message for the failure
|
|
280
|
+
"""
|
|
281
|
+
self.failures.append((item_identifier, error_message))
|
|
282
|
+
|
|
283
|
+
def process_response(
|
|
284
|
+
self,
|
|
285
|
+
response: Response,
|
|
286
|
+
item_identifier: str,
|
|
287
|
+
expected_status: int = 200,
|
|
288
|
+
) -> bool:
|
|
289
|
+
"""Process a single response in the batch operation.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
response: HTTP response object
|
|
293
|
+
item_identifier: Identifier of the item being processed
|
|
294
|
+
expected_status: Expected HTTP status code for success
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
True if successful, False if failed
|
|
298
|
+
"""
|
|
299
|
+
if response.status_code == expected_status:
|
|
300
|
+
self.add_success(item_identifier)
|
|
301
|
+
return True
|
|
302
|
+
else:
|
|
303
|
+
try:
|
|
304
|
+
error_data = response.json()
|
|
305
|
+
error_message = error_data.get("message", f"HTTP {response.status_code}")
|
|
306
|
+
except (ValueError, KeyError):
|
|
307
|
+
error_message = f"HTTP {response.status_code}"
|
|
308
|
+
|
|
309
|
+
self.add_failure(item_identifier, error_message)
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
def report_results(self, exit_on_failure: bool = True) -> None:
|
|
313
|
+
"""Report the final results of the batch operation.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
exit_on_failure: Whether to exit with error code if any failures occurred
|
|
317
|
+
"""
|
|
318
|
+
total = len(self.successes) + len(self.failures)
|
|
319
|
+
|
|
320
|
+
if self.successes:
|
|
321
|
+
click.echo(f"✓ Successfully {self.operation_name} {len(self.successes)}/{total} items")
|
|
322
|
+
|
|
323
|
+
if self.failures:
|
|
324
|
+
click.echo(
|
|
325
|
+
f"✗ Failed to {self.operation_name} {len(self.failures)}/{total} items:", err=True
|
|
326
|
+
)
|
|
327
|
+
for item_id, error in self.failures:
|
|
328
|
+
click.echo(f" - {item_id}: {error}", err=True)
|
|
329
|
+
|
|
330
|
+
if exit_on_failure:
|
|
331
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def handle_file_validation(file_path: str, operation: str) -> Dict[str, Any]:
|
|
335
|
+
"""Validate and load JSON file for DFF operations.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
file_path: Path to the JSON file
|
|
339
|
+
operation: Operation being performed (for error messages)
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Loaded JSON data
|
|
343
|
+
|
|
344
|
+
Raises:
|
|
345
|
+
SystemExit: If file validation fails
|
|
346
|
+
"""
|
|
347
|
+
try:
|
|
348
|
+
from .utils import load_json_file
|
|
349
|
+
|
|
350
|
+
return load_json_file(file_path)
|
|
351
|
+
except FileNotFoundError:
|
|
352
|
+
click.echo(f"✗ File not found: {file_path}", err=True)
|
|
353
|
+
sys.exit(ExitCodes.NOT_FOUND)
|
|
354
|
+
except json.JSONDecodeError as e:
|
|
355
|
+
click.echo(f"✗ Invalid JSON in {file_path}: {e}", err=True)
|
|
356
|
+
sys.exit(ExitCodes.INVALID_INPUT)
|
|
357
|
+
except Exception as e:
|
|
358
|
+
click.echo(f"✗ Failed to read {file_path} for {operation}: {e}", err=True)
|
|
359
|
+
sys.exit(ExitCodes.GENERAL_ERROR)
|