catocli 3.0.18__py3-none-any.whl → 3.0.24__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.

Potentially problematic release.


This version of catocli might be problematic. Click here for more details.

Files changed (43) hide show
  1. catocli/Utils/clidriver.py +16 -8
  2. catocli/Utils/formatter_account_metrics.py +544 -0
  3. catocli/Utils/formatter_app_stats.py +184 -0
  4. catocli/Utils/formatter_app_stats_timeseries.py +377 -0
  5. catocli/Utils/formatter_events_timeseries.py +459 -0
  6. catocli/Utils/formatter_socket_port_metrics.py +189 -0
  7. catocli/Utils/formatter_socket_port_metrics_timeseries.py +339 -0
  8. catocli/Utils/formatter_utils.py +251 -0
  9. catocli/__init__.py +1 -1
  10. catocli/clisettings.json +37 -5
  11. catocli/parsers/customParserApiClient.py +211 -66
  12. catocli/parsers/mutation_policy/__init__.py +405 -405
  13. catocli/parsers/mutation_site/__init__.py +15 -15
  14. catocli/parsers/mutation_sites/__init__.py +15 -15
  15. catocli/parsers/query_accountMetrics/README.md +90 -0
  16. catocli/parsers/query_accountMetrics/__init__.py +6 -0
  17. catocli/parsers/query_appStats/README.md +2 -2
  18. catocli/parsers/query_appStats/__init__.py +4 -2
  19. catocli/parsers/query_appStatsTimeSeries/__init__.py +4 -2
  20. catocli/parsers/query_eventsTimeSeries/README.md +280 -0
  21. catocli/parsers/query_eventsTimeSeries/__init__.py +6 -0
  22. catocli/parsers/query_policy/__init__.py +42 -42
  23. catocli/parsers/query_socketPortMetrics/README.md +44 -0
  24. catocli/parsers/query_socketPortMetrics/__init__.py +6 -0
  25. catocli/parsers/query_socketPortMetricsTimeSeries/README.md +83 -0
  26. catocli/parsers/query_socketPortMetricsTimeSeries/__init__.py +4 -2
  27. catocli/parsers/utils/export_utils.py +6 -2
  28. catocli-3.0.24.dist-info/METADATA +184 -0
  29. {catocli-3.0.18.dist-info → catocli-3.0.24.dist-info}/RECORD +37 -35
  30. {catocli-3.0.18.dist-info → catocli-3.0.24.dist-info}/top_level.txt +0 -1
  31. models/mutation.xdr.analystFeedback.json +822 -87
  32. models/query.xdr.stories.json +822 -87
  33. models/query.xdr.story.json +822 -87
  34. schema/catolib.py +89 -64
  35. catocli/Utils/csv_formatter.py +0 -663
  36. catocli-3.0.18.dist-info/METADATA +0 -124
  37. scripts/catolib.py +0 -62
  38. scripts/export_if_rules_to_json.py +0 -188
  39. scripts/export_wf_rules_to_json.py +0 -111
  40. scripts/import_wf_rules_to_tfstate.py +0 -331
  41. {catocli-3.0.18.dist-info → catocli-3.0.24.dist-info}/WHEEL +0 -0
  42. {catocli-3.0.18.dist-info → catocli-3.0.24.dist-info}/entry_points.txt +0 -0
  43. {catocli-3.0.18.dist-info → catocli-3.0.24.dist-info}/licenses/LICENSE +0 -0
@@ -129,22 +129,50 @@ def createRequest(args, configuration):
129
129
  print(f"ERROR: Failed to load operation model for {operation_name}: {e}")
130
130
  return None
131
131
 
132
- # Load CSV configuration for this operation
132
+ # Load configuration for output formatting
133
133
  csv_function = None
134
- output_format = getattr(args, 'format', 'json') # Default to json if -f not provided
135
-
136
- if output_format == 'csv':
137
- try:
138
- settings = loadJSON("clisettings.json")
139
- csv_supported_operations = settings.get("queryOperationCsvOutput", {})
134
+ output_format = getattr(args, 'format', None)
135
+ raw_output = getattr(args, 'raw_output', False)
136
+ default_override = None
137
+
138
+ try:
139
+ settings = loadJSON("clisettings.json")
140
+ csv_supported_operations = settings.get("queryOperationCsvOutput", {})
141
+ default_overrides = settings.get("queryOperationDefaultFormatOverrides", {})
142
+ default_override = default_overrides.get(operation_name)
143
+ except Exception as e:
144
+ csv_supported_operations = {}
145
+ default_override = None
146
+
147
+ # Determine effective format
148
+ # Priority: -raw flag -> raw/original; -f -> explicit; config override -> default; fallback -> json
149
+ effective_format = 'json'
150
+ if raw_output:
151
+ effective_format = 'raw'
152
+ elif output_format in ['json', 'csv']:
153
+ effective_format = output_format
154
+ elif default_override and default_override.get('enabled'):
155
+ effective_format = default_override.get('default_format', 'json')
156
+
157
+ # If CSV requested, validate support - check both new and legacy systems
158
+ if effective_format == 'csv':
159
+ # First check new format override system
160
+ if default_override and default_override.get('format_function'):
161
+ csv_function = default_override.get('format_function')
162
+ else:
163
+ # Fallback to legacy CSV system for backward compatibility
140
164
  csv_function = csv_supported_operations.get(operation_name)
141
- except Exception as e:
142
- print(f"WARNING: Could not load CSV settings: {e}")
143
- csv_function = None
144
165
 
145
166
  if not csv_function:
167
+ # List all operations that support CSV from both systems
168
+ supported_ops = list(csv_supported_operations.keys())
169
+ if default_overrides:
170
+ for op_name, config in default_overrides.items():
171
+ if config.get('enabled') and config.get('format_function'):
172
+ supported_ops.append(op_name)
173
+
146
174
  print(f"ERROR: CSV output not supported for operation '{operation_name}'")
147
- print(f"Supported CSV operations: {list(csv_supported_operations.keys()) if 'csv_supported_operations' in locals() else 'none'}")
175
+ print(f"Supported CSV operations: {sorted(list(set(supported_ops))) if supported_ops else 'none'}")
148
176
  return None
149
177
 
150
178
  variables_obj = {}
@@ -235,79 +263,194 @@ def createRequest(args, configuration):
235
263
  )
236
264
  print(f"Sentinel API response code: {result_code}")
237
265
 
238
- # Apply CSV formatting if requested
239
- if output_format == 'csv' and csv_function and response:
266
+ # Apply formatting based on effective format
267
+ if effective_format == 'raw' or not default_override:
268
+ # Raw format - return only the data portion, not HTTP headers/status
269
+ if isinstance(response, (list, tuple)) and len(response) > 0:
270
+ return response[0] # Extract just the data portion
271
+ else:
272
+ return response
273
+ elif effective_format == 'csv' and csv_function and response:
274
+ # CSV formatting requested
240
275
  try:
241
- # Import the CSV formatter dynamically
242
276
  # Get the response data (handle both list and tuple responses)
243
277
  if isinstance(response, (list, tuple)) and len(response) > 0:
244
278
  response_data = response[0]
245
279
  else:
246
280
  response_data = response
247
281
 
248
- # Add Utils directory to path and import csv_formatter
282
+ # Add Utils directory to path and import formatter_utils
249
283
  current_dir = os.path.dirname(os.path.abspath(__file__))
250
284
  utils_dir = os.path.join(os.path.dirname(current_dir), 'Utils')
251
285
  if utils_dir not in sys.path:
252
286
  sys.path.insert(0, utils_dir)
253
287
 
254
- # Import the csv_formatter module
255
- import csv_formatter
288
+ # Import the formatter_utils module
289
+ import formatter_utils
256
290
 
257
- # Call the appropriate CSV formatter function
258
- if hasattr(csv_formatter, csv_function):
259
- csv_formatter_func = getattr(csv_formatter, csv_function)
260
- csv_output = csv_formatter_func(response_data)
291
+ # Use the centralized format_to_csv function
292
+ csv_output = None
293
+ if hasattr(formatter_utils, 'format_to_csv'):
294
+ # Use format_to_csv with the operation name
295
+ csv_output = formatter_utils.format_to_csv(response_data, operation_name)
296
+ elif hasattr(formatter_utils, csv_function):
297
+ # Fallback to direct function call for legacy support
298
+ csv_formatter_func = getattr(formatter_utils, csv_function)
261
299
 
262
- if csv_output:
263
- # Determine output directory (reports) in current folder
264
- reports_dir = os.path.join(os.getcwd(), 'reports')
265
- if not os.path.exists(reports_dir):
266
- os.makedirs(reports_dir)
267
-
268
- # Default filename is the operation name (second segment) lowercased
269
- op_base = operation_name.split('.')[-1].lower()
270
- default_filename = f"{op_base}.csv"
271
- filename = default_filename
272
-
273
- # Override filename if provided
274
- if hasattr(args, 'csv_filename') and getattr(args, 'csv_filename'):
275
- filename = getattr(args, 'csv_filename')
276
- # Ensure .csv extension
277
- if not filename.lower().endswith('.csv'):
278
- filename += '.csv'
279
-
280
- # Append timestamp if requested
281
- if hasattr(args, 'append_timestamp') and getattr(args, 'append_timestamp'):
282
- ts = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
283
- name, ext = os.path.splitext(filename)
284
- filename = f"{name}_{ts}{ext}"
285
-
286
- output_path = os.path.join(reports_dir, filename)
287
-
288
- # Write CSV to file
289
- try:
290
- with open(output_path, 'w', encoding='utf-8', newline='') as f:
291
- f.write(csv_output)
292
- except Exception as write_err:
293
- print(f"ERROR: Failed to write CSV to file {output_path}: {write_err}")
294
- # Fallback: return CSV to stdout behavior
295
- return [{"__csv_output__": csv_output}]
296
-
297
- if params.get('v'):
298
- print(f"Saved CSV report to: {output_path}")
299
-
300
- # Return structured response similar to export functions
301
- return [{"success": True, "output_file": output_path, "operation": operation_name}]
300
+ # Check if this is a new format function that takes output_format parameter
301
+ if default_override and default_override.get('format_function') == csv_function:
302
+ # New format functions that support output_format parameter
303
+ csv_output = csv_formatter_func(response_data, output_format='csv')
302
304
  else:
303
- print("WARNING: CSV formatter returned empty result")
304
- return response
305
+ # Legacy CSV functions that only take response_data
306
+ csv_output = csv_formatter_func(response_data)
305
307
  else:
308
+ # CSV formatter function not found
306
309
  print(f"ERROR: CSV formatter function '{csv_function}' not found")
307
- return response
310
+ # Return clean error response instead of raw response with HTTP headers
311
+ return [{"error": f"CSV formatter function '{csv_function}' not found", "operation": operation_name, "success": False}]
312
+
313
+ # Handle CSV output regardless of which formatter path was used
314
+ if csv_output:
315
+ # Determine output directory (reports) in current folder
316
+ reports_dir = os.path.join(os.getcwd(), 'reports')
317
+ if not os.path.exists(reports_dir):
318
+ os.makedirs(reports_dir)
319
+
320
+ # Default filename is the operation name (second segment) lowercased
321
+ op_base = operation_name.split('.')[-1].lower()
322
+ default_filename = f"{op_base}.csv"
323
+ filename = default_filename
324
+
325
+ # Override filename if provided
326
+ if hasattr(args, 'csv_filename') and getattr(args, 'csv_filename'):
327
+ filename = getattr(args, 'csv_filename')
328
+ # Ensure .csv extension
329
+ if not filename.lower().endswith('.csv'):
330
+ filename += '.csv'
331
+
332
+ # Append timestamp if requested
333
+ if hasattr(args, 'append_timestamp') and getattr(args, 'append_timestamp'):
334
+ ts = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
335
+ name, ext = os.path.splitext(filename)
336
+ filename = f"{name}_{ts}{ext}"
337
+
338
+ output_path = os.path.join(reports_dir, filename)
339
+
340
+ # Write CSV to file
341
+ try:
342
+ with open(output_path, 'w', encoding='utf-8', newline='') as f:
343
+ f.write(csv_output)
344
+ except Exception as write_err:
345
+ print(f"ERROR: Failed to write CSV to file {output_path}: {write_err}")
346
+ # Fallback: return CSV to stdout behavior
347
+ return [{"__csv_output__": csv_output}]
348
+
349
+ if params.get('v'):
350
+ print(f"Saved CSV report to: {output_path}")
351
+
352
+ # Return structured response similar to export functions
353
+ # Normalize path separators for better cross-platform display
354
+ display_path = output_path.replace(os.sep, '/')
355
+ return [{"success": True, "output_file": display_path, "operation": operation_name}]
356
+ elif csv_output is None:
357
+ # Formatter returned None, indicating we should fall back to raw response
358
+ print("INFO: No processable data found, returning raw API response")
359
+ # Extract just the data portion to avoid HTTP headers
360
+ if isinstance(response, (list, tuple)) and len(response) > 0:
361
+ return response[0]
362
+ else:
363
+ return response
364
+ else:
365
+ print("WARNING: CSV formatter returned empty result - no data available for the specified criteria")
366
+ # Return clean error response instead of raw response with HTTP headers
367
+ return [{"error": "No data available for CSV export", "operation": operation_name, "success": False}]
308
368
  except Exception as e:
309
369
  print(f"ERROR: Failed to format CSV output: {e}")
310
- return response
370
+ # Return clean error response instead of raw response with HTTP headers
371
+ return [{"error": f"Failed to format CSV output: {str(e)}", "operation": operation_name, "success": False}]
372
+ elif effective_format == 'json' and default_override and default_override.get('format_function') and response:
373
+ # Enhanced JSON formatting requested
374
+ try:
375
+ # Get the response data (handle both list and tuple responses)
376
+ if isinstance(response, (list, tuple)) and len(response) > 0:
377
+ response_data = response[0]
378
+ else:
379
+ response_data = response
380
+
381
+ # Add Utils directory to path and import formatter_utils module for format functions
382
+ current_dir = os.path.dirname(os.path.abspath(__file__))
383
+ utils_dir = os.path.join(os.path.dirname(current_dir), 'Utils')
384
+ if utils_dir not in sys.path:
385
+ sys.path.insert(0, utils_dir)
386
+
387
+ # Import the formatter_utils module (which contains format functions)
388
+ import formatter_utils
389
+
390
+ # Use the centralized format_to_csv function which can handle JSON output too
391
+ format_function = default_override.get('format_function')
392
+ if hasattr(formatter_utils, 'format_to_csv'):
393
+ # The individual formatters handle JSON output internally when called from format_to_csv
394
+ # We need to directly call the specific formatter for JSON output
395
+ try:
396
+ # Dynamic import of the specific formatter function
397
+ if format_function == 'format_app_stats':
398
+ from formatter_app_stats import format_app_stats
399
+ formatted_output = format_app_stats(response_data, output_format='json')
400
+ elif format_function == 'format_app_stats_timeseries':
401
+ from formatter_app_stats_timeseries import format_app_stats_timeseries
402
+ formatted_output = format_app_stats_timeseries(response_data, output_format='json')
403
+ elif format_function == 'format_account_metrics':
404
+ from formatter_account_metrics import format_account_metrics
405
+ formatted_output = format_account_metrics(response_data, output_format='json')
406
+ elif format_function == 'format_socket_port_metrics_timeseries':
407
+ from formatter_socket_port_metrics_timeseries import format_socket_port_metrics_timeseries
408
+ formatted_output = format_socket_port_metrics_timeseries(response_data, output_format='json')
409
+ elif format_function == 'format_events_timeseries':
410
+ from formatter_events_timeseries import format_events_timeseries
411
+ formatted_output = format_events_timeseries(response_data, output_format='json')
412
+ else:
413
+ formatted_output = None
414
+ except ImportError:
415
+ formatted_output = None
416
+
417
+ # Handle the formatted output from individual formatters
418
+ if formatted_output is None:
419
+ # Formatter returned None, indicating we should fall back to raw response
420
+ print("INFO: No processable data found, returning raw API response")
421
+ return response_data
422
+ else:
423
+ # Pretty print the formatted JSON directly to stdout
424
+ print(formatted_output)
425
+ return None # Return None to prevent further processing/output
426
+ elif hasattr(formatter_utils, format_function):
427
+ formatter_func = getattr(formatter_utils, format_function)
428
+ formatted_output = formatter_func(response_data, output_format='json')
429
+
430
+ if formatted_output is None:
431
+ # Formatter returned None, indicating we should fall back to raw response
432
+ print("INFO: No processable data found, returning raw API response")
433
+ return response_data
434
+ else:
435
+ # Pretty print the formatted JSON directly to stdout
436
+ print(formatted_output)
437
+ return None # Return None to prevent further processing/output
438
+ else:
439
+ print(f"WARNING: Formatter function '{format_function}' not found, using original response")
440
+ return response
441
+ except Exception as e:
442
+ print(f"ERROR: Failed to apply enhanced JSON formatting: {e}")
443
+ # Instead of returning the raw response which may contain non-serializable objects,
444
+ # extract just the JSON data portion to avoid HTTPHeaderDict serialization errors
445
+ try:
446
+ if isinstance(response, (list, tuple)) and len(response) > 0:
447
+ response_data = response[0]
448
+ else:
449
+ response_data = response
450
+ return response_data
451
+ except Exception:
452
+ # If we can't extract data safely, return a minimal error response
453
+ return [{"error": "Failed to format response and fallback failed", "original_error": str(e)}]
311
454
 
312
455
  return response
313
456
 
@@ -1703,7 +1846,9 @@ def createPrivateRequest(args, configuration):
1703
1846
  print(f"Saved CSV report to: {output_path}")
1704
1847
 
1705
1848
  # Return structured response similar to export functions
1706
- return [{"success": True, "output_file": output_path, "operation": csv_operation, "private_command": private_command}]
1849
+ # Normalize path separators for better cross-platform display
1850
+ display_path = output_path.replace(os.sep, '/')
1851
+ return [{"success": True, "output_file": display_path, "operation": csv_operation, "private_command": private_command}]
1707
1852
  else:
1708
1853
  print("WARNING: CSV formatter returned empty result")
1709
1854
  return response