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.
Files changed (74) hide show
  1. slcli/__init__.py +1 -0
  2. slcli/__main__.py +23 -0
  3. slcli/_version.py +4 -0
  4. slcli/asset_click.py +1289 -0
  5. slcli/cli_formatters.py +218 -0
  6. slcli/cli_utils.py +504 -0
  7. slcli/comment_click.py +602 -0
  8. slcli/completion_click.py +418 -0
  9. slcli/config.py +81 -0
  10. slcli/config_click.py +498 -0
  11. slcli/dff_click.py +979 -0
  12. slcli/dff_decorators.py +24 -0
  13. slcli/example_click.py +404 -0
  14. slcli/example_loader.py +274 -0
  15. slcli/example_provisioner.py +2777 -0
  16. slcli/examples/README.md +134 -0
  17. slcli/examples/_schema/schema-v1.0.json +169 -0
  18. slcli/examples/demo-complete-workflow/README.md +323 -0
  19. slcli/examples/demo-complete-workflow/config.yaml +638 -0
  20. slcli/examples/demo-test-plans/README.md +132 -0
  21. slcli/examples/demo-test-plans/config.yaml +154 -0
  22. slcli/examples/exercise-5-1-parametric-insights/README.md +101 -0
  23. slcli/examples/exercise-5-1-parametric-insights/config.yaml +1589 -0
  24. slcli/examples/exercise-7-1-test-plans/README.md +93 -0
  25. slcli/examples/exercise-7-1-test-plans/config.yaml +323 -0
  26. slcli/examples/spec-compliance-notebooks/README.md +140 -0
  27. slcli/examples/spec-compliance-notebooks/config.yaml +112 -0
  28. slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +1553 -0
  29. slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +1577 -0
  30. slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +912 -0
  31. slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  32. slcli/feed_click.py +892 -0
  33. slcli/file_click.py +932 -0
  34. slcli/function_click.py +1400 -0
  35. slcli/function_templates.py +85 -0
  36. slcli/main.py +406 -0
  37. slcli/mcp_click.py +269 -0
  38. slcli/mcp_server.py +748 -0
  39. slcli/notebook_click.py +1770 -0
  40. slcli/platform.py +345 -0
  41. slcli/policy_click.py +679 -0
  42. slcli/policy_utils.py +411 -0
  43. slcli/profiles.py +411 -0
  44. slcli/response_handlers.py +359 -0
  45. slcli/routine_click.py +763 -0
  46. slcli/skill_click.py +253 -0
  47. slcli/skills/slcli/SKILL.md +713 -0
  48. slcli/skills/slcli/references/analysis-recipes.md +474 -0
  49. slcli/skills/slcli/references/filtering.md +236 -0
  50. slcli/skills/systemlink-webapp/SKILL.md +744 -0
  51. slcli/skills/systemlink-webapp/references/deployment.md +123 -0
  52. slcli/skills/systemlink-webapp/references/nimble-angular.md +380 -0
  53. slcli/skills/systemlink-webapp/references/systemlink-services.md +192 -0
  54. slcli/ssl_trust.py +93 -0
  55. slcli/system_click.py +2216 -0
  56. slcli/table_utils.py +124 -0
  57. slcli/tag_click.py +794 -0
  58. slcli/templates_click.py +599 -0
  59. slcli/testmonitor_click.py +1667 -0
  60. slcli/universal_handlers.py +305 -0
  61. slcli/user_click.py +1218 -0
  62. slcli/utils.py +832 -0
  63. slcli/web_editor.py +295 -0
  64. slcli/webapp_click.py +981 -0
  65. slcli/workflow_preview.py +287 -0
  66. slcli/workflows_click.py +988 -0
  67. slcli/workitem_click.py +2258 -0
  68. slcli/workspace_click.py +576 -0
  69. slcli/workspace_utils.py +206 -0
  70. systemlink_cli-1.3.1.dist-info/METADATA +20 -0
  71. systemlink_cli-1.3.1.dist-info/RECORD +74 -0
  72. systemlink_cli-1.3.1.dist-info/WHEEL +4 -0
  73. systemlink_cli-1.3.1.dist-info/entry_points.txt +7 -0
  74. 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)