catocli 3.0.22__py3-none-any.whl → 3.0.25__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 (161) hide show
  1. catocli/Utils/clidriver.py +2 -2
  2. catocli/Utils/formatter_app_stats.py +29 -16
  3. catocli/Utils/formatter_app_stats_timeseries.py +15 -23
  4. catocli/Utils/formatter_utils.py +1 -0
  5. catocli/__init__.py +1 -1
  6. catocli/parsers/customParserApiClient.py +6 -2
  7. catocli/parsers/query_appStatsTimeSeries/README.md +2 -0
  8. catocli/parsers/utils/export_utils.py +6 -2
  9. catocli-3.0.25.dist-info/METADATA +181 -0
  10. {catocli-3.0.22.dist-info → catocli-3.0.25.dist-info}/RECORD +160 -160
  11. models/mutation.policy.antiMalwareFileHash.addRule.json +0 -20
  12. models/mutation.policy.antiMalwareFileHash.addSection.json +0 -103
  13. models/mutation.policy.antiMalwareFileHash.createPolicyRevision.json +0 -123
  14. models/mutation.policy.antiMalwareFileHash.discardPolicyRevision.json +0 -123
  15. models/mutation.policy.antiMalwareFileHash.moveRule.json +0 -20
  16. models/mutation.policy.antiMalwareFileHash.moveSection.json +0 -103
  17. models/mutation.policy.antiMalwareFileHash.publishPolicyRevision.json +0 -123
  18. models/mutation.policy.antiMalwareFileHash.removeRule.json +0 -20
  19. models/mutation.policy.antiMalwareFileHash.removeSection.json +0 -103
  20. models/mutation.policy.antiMalwareFileHash.updatePolicy.json +0 -123
  21. models/mutation.policy.antiMalwareFileHash.updateRule.json +0 -20
  22. models/mutation.policy.antiMalwareFileHash.updateSection.json +0 -103
  23. models/mutation.policy.appTenantRestriction.addRule.json +0 -20
  24. models/mutation.policy.appTenantRestriction.addSection.json +0 -103
  25. models/mutation.policy.appTenantRestriction.createPolicyRevision.json +0 -123
  26. models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +0 -123
  27. models/mutation.policy.appTenantRestriction.moveRule.json +0 -20
  28. models/mutation.policy.appTenantRestriction.moveSection.json +0 -103
  29. models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +0 -123
  30. models/mutation.policy.appTenantRestriction.removeRule.json +0 -20
  31. models/mutation.policy.appTenantRestriction.removeSection.json +0 -103
  32. models/mutation.policy.appTenantRestriction.updatePolicy.json +0 -123
  33. models/mutation.policy.appTenantRestriction.updateRule.json +0 -20
  34. models/mutation.policy.appTenantRestriction.updateSection.json +0 -103
  35. models/mutation.policy.applicationControl.addRule.json +0 -20
  36. models/mutation.policy.applicationControl.addSection.json +0 -103
  37. models/mutation.policy.applicationControl.createPolicyRevision.json +0 -123
  38. models/mutation.policy.applicationControl.discardPolicyRevision.json +0 -123
  39. models/mutation.policy.applicationControl.moveRule.json +0 -20
  40. models/mutation.policy.applicationControl.moveSection.json +0 -103
  41. models/mutation.policy.applicationControl.publishPolicyRevision.json +0 -123
  42. models/mutation.policy.applicationControl.removeRule.json +0 -20
  43. models/mutation.policy.applicationControl.removeSection.json +0 -103
  44. models/mutation.policy.applicationControl.updatePolicy.json +0 -123
  45. models/mutation.policy.applicationControl.updateRule.json +0 -20
  46. models/mutation.policy.applicationControl.updateSection.json +0 -103
  47. models/mutation.policy.dynamicIpAllocation.addRule.json +0 -20
  48. models/mutation.policy.dynamicIpAllocation.addSection.json +0 -103
  49. models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +0 -123
  50. models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +0 -123
  51. models/mutation.policy.dynamicIpAllocation.moveRule.json +0 -20
  52. models/mutation.policy.dynamicIpAllocation.moveSection.json +0 -103
  53. models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +0 -123
  54. models/mutation.policy.dynamicIpAllocation.removeRule.json +0 -20
  55. models/mutation.policy.dynamicIpAllocation.removeSection.json +0 -103
  56. models/mutation.policy.dynamicIpAllocation.updatePolicy.json +0 -123
  57. models/mutation.policy.dynamicIpAllocation.updateRule.json +0 -20
  58. models/mutation.policy.dynamicIpAllocation.updateSection.json +0 -103
  59. models/mutation.policy.internetFirewall.addRule.json +0 -20
  60. models/mutation.policy.internetFirewall.addSection.json +0 -103
  61. models/mutation.policy.internetFirewall.createPolicyRevision.json +0 -123
  62. models/mutation.policy.internetFirewall.discardPolicyRevision.json +0 -123
  63. models/mutation.policy.internetFirewall.moveRule.json +0 -20
  64. models/mutation.policy.internetFirewall.moveSection.json +0 -103
  65. models/mutation.policy.internetFirewall.publishPolicyRevision.json +0 -123
  66. models/mutation.policy.internetFirewall.removeRule.json +0 -20
  67. models/mutation.policy.internetFirewall.removeSection.json +0 -103
  68. models/mutation.policy.internetFirewall.updatePolicy.json +0 -123
  69. models/mutation.policy.internetFirewall.updateRule.json +0 -20
  70. models/mutation.policy.internetFirewall.updateSection.json +0 -103
  71. models/mutation.policy.remotePortFwd.addRule.json +0 -20
  72. models/mutation.policy.remotePortFwd.addSection.json +0 -103
  73. models/mutation.policy.remotePortFwd.createPolicyRevision.json +0 -123
  74. models/mutation.policy.remotePortFwd.discardPolicyRevision.json +0 -123
  75. models/mutation.policy.remotePortFwd.moveRule.json +0 -20
  76. models/mutation.policy.remotePortFwd.moveSection.json +0 -103
  77. models/mutation.policy.remotePortFwd.publishPolicyRevision.json +0 -123
  78. models/mutation.policy.remotePortFwd.removeRule.json +0 -20
  79. models/mutation.policy.remotePortFwd.removeSection.json +0 -103
  80. models/mutation.policy.remotePortFwd.updatePolicy.json +0 -123
  81. models/mutation.policy.remotePortFwd.updateRule.json +0 -20
  82. models/mutation.policy.remotePortFwd.updateSection.json +0 -103
  83. models/mutation.policy.socketLan.addRule.json +0 -40
  84. models/mutation.policy.socketLan.addSection.json +0 -103
  85. models/mutation.policy.socketLan.createPolicyRevision.json +0 -143
  86. models/mutation.policy.socketLan.discardPolicyRevision.json +0 -143
  87. models/mutation.policy.socketLan.moveRule.json +0 -40
  88. models/mutation.policy.socketLan.moveSection.json +0 -103
  89. models/mutation.policy.socketLan.publishPolicyRevision.json +0 -143
  90. models/mutation.policy.socketLan.removeRule.json +0 -40
  91. models/mutation.policy.socketLan.removeSection.json +0 -103
  92. models/mutation.policy.socketLan.updatePolicy.json +0 -143
  93. models/mutation.policy.socketLan.updateRule.json +0 -40
  94. models/mutation.policy.socketLan.updateSection.json +0 -103
  95. models/mutation.policy.terminalServer.addRule.json +0 -20
  96. models/mutation.policy.terminalServer.addSection.json +0 -103
  97. models/mutation.policy.terminalServer.createPolicyRevision.json +0 -123
  98. models/mutation.policy.terminalServer.discardPolicyRevision.json +0 -123
  99. models/mutation.policy.terminalServer.moveRule.json +0 -20
  100. models/mutation.policy.terminalServer.moveSection.json +0 -103
  101. models/mutation.policy.terminalServer.publishPolicyRevision.json +0 -123
  102. models/mutation.policy.terminalServer.removeRule.json +0 -20
  103. models/mutation.policy.terminalServer.removeSection.json +0 -103
  104. models/mutation.policy.terminalServer.updatePolicy.json +0 -123
  105. models/mutation.policy.terminalServer.updateRule.json +0 -20
  106. models/mutation.policy.terminalServer.updateSection.json +0 -103
  107. models/mutation.policy.tlsInspect.addRule.json +0 -20
  108. models/mutation.policy.tlsInspect.addSection.json +0 -103
  109. models/mutation.policy.tlsInspect.createPolicyRevision.json +0 -123
  110. models/mutation.policy.tlsInspect.discardPolicyRevision.json +0 -123
  111. models/mutation.policy.tlsInspect.moveRule.json +0 -20
  112. models/mutation.policy.tlsInspect.moveSection.json +0 -103
  113. models/mutation.policy.tlsInspect.publishPolicyRevision.json +0 -123
  114. models/mutation.policy.tlsInspect.removeRule.json +0 -20
  115. models/mutation.policy.tlsInspect.removeSection.json +0 -103
  116. models/mutation.policy.tlsInspect.updatePolicy.json +0 -123
  117. models/mutation.policy.tlsInspect.updateRule.json +0 -20
  118. models/mutation.policy.tlsInspect.updateSection.json +0 -103
  119. models/mutation.policy.wanFirewall.addRule.json +0 -20
  120. models/mutation.policy.wanFirewall.addSection.json +0 -103
  121. models/mutation.policy.wanFirewall.createPolicyRevision.json +0 -123
  122. models/mutation.policy.wanFirewall.discardPolicyRevision.json +0 -123
  123. models/mutation.policy.wanFirewall.moveRule.json +0 -20
  124. models/mutation.policy.wanFirewall.moveSection.json +0 -103
  125. models/mutation.policy.wanFirewall.publishPolicyRevision.json +0 -123
  126. models/mutation.policy.wanFirewall.removeRule.json +0 -20
  127. models/mutation.policy.wanFirewall.removeSection.json +0 -103
  128. models/mutation.policy.wanFirewall.updatePolicy.json +0 -123
  129. models/mutation.policy.wanFirewall.updateRule.json +0 -20
  130. models/mutation.policy.wanFirewall.updateSection.json +0 -103
  131. models/mutation.policy.wanNetwork.addRule.json +0 -20
  132. models/mutation.policy.wanNetwork.addSection.json +0 -103
  133. models/mutation.policy.wanNetwork.createPolicyRevision.json +0 -123
  134. models/mutation.policy.wanNetwork.discardPolicyRevision.json +0 -123
  135. models/mutation.policy.wanNetwork.moveRule.json +0 -20
  136. models/mutation.policy.wanNetwork.moveSection.json +0 -103
  137. models/mutation.policy.wanNetwork.publishPolicyRevision.json +0 -123
  138. models/mutation.policy.wanNetwork.removeRule.json +0 -20
  139. models/mutation.policy.wanNetwork.removeSection.json +0 -103
  140. models/mutation.policy.wanNetwork.updatePolicy.json +0 -123
  141. models/mutation.policy.wanNetwork.updateRule.json +0 -20
  142. models/mutation.policy.wanNetwork.updateSection.json +0 -103
  143. models/query.appStats.json +216 -0
  144. models/query.appStatsTimeSeries.json +144 -0
  145. models/query.policy.antiMalwareFileHash.policy.json +0 -123
  146. models/query.policy.appTenantRestriction.policy.json +0 -123
  147. models/query.policy.applicationControl.policy.json +0 -123
  148. models/query.policy.dynamicIpAllocation.policy.json +0 -123
  149. models/query.policy.internetFirewall.policy.json +0 -123
  150. models/query.policy.remotePortFwd.policy.json +0 -123
  151. models/query.policy.socketLan.policy.json +0 -143
  152. models/query.policy.terminalServer.policy.json +0 -123
  153. models/query.policy.tlsInspect.policy.json +0 -123
  154. models/query.policy.wanFirewall.policy.json +0 -123
  155. models/query.policy.wanNetwork.policy.json +0 -123
  156. schema/catolib.py +64 -56
  157. catocli-3.0.22.dist-info/METADATA +0 -124
  158. {catocli-3.0.22.dist-info → catocli-3.0.25.dist-info}/WHEEL +0 -0
  159. {catocli-3.0.22.dist-info → catocli-3.0.25.dist-info}/entry_points.txt +0 -0
  160. {catocli-3.0.22.dist-info → catocli-3.0.25.dist-info}/licenses/LICENSE +0 -0
  161. {catocli-3.0.22.dist-info → catocli-3.0.25.dist-info}/top_level.txt +0 -0
@@ -54,8 +54,8 @@ from ..parsers.query_policy import query_policy_parse
54
54
  from ..parsers.query_groups import query_groups_parse
55
55
  from ..parsers.mutation_xdr import mutation_xdr_parse
56
56
  from ..parsers.mutation_site import mutation_site_parse
57
- from ..parsers.mutation_sites import mutation_sites_parse
58
57
  from ..parsers.mutation_policy import mutation_policy_parse
58
+ from ..parsers.mutation_sites import mutation_sites_parse
59
59
  from ..parsers.mutation_container import mutation_container_parse
60
60
  from ..parsers.mutation_admin import mutation_admin_parse
61
61
  from ..parsers.mutation_accountManagement import mutation_accountManagement_parse
@@ -189,8 +189,8 @@ query_policy_parser = query_policy_parse(query_subparsers)
189
189
  query_groups_parser = query_groups_parse(query_subparsers)
190
190
  mutation_xdr_parser = mutation_xdr_parse(mutation_subparsers)
191
191
  mutation_site_parser = mutation_site_parse(mutation_subparsers)
192
- mutation_sites_parser = mutation_sites_parse(mutation_subparsers)
193
192
  mutation_policy_parser = mutation_policy_parse(mutation_subparsers)
193
+ mutation_sites_parser = mutation_sites_parse(mutation_subparsers)
194
194
  mutation_container_parser = mutation_container_parse(mutation_subparsers)
195
195
  mutation_admin_parser = mutation_admin_parse(mutation_subparsers)
196
196
  mutation_accountManagement_parser = mutation_accountManagement_parse(mutation_subparsers)
@@ -14,12 +14,12 @@ from typing import Dict, List, Any
14
14
 
15
15
  # Import shared utility functions
16
16
  try:
17
- from .formatter_utils import convert_bytes_to_mb
17
+ from .formatter_utils import convert_bytes_to_mb, is_bytes_measure
18
18
  except ImportError:
19
19
  try:
20
- from catocli.Utils.formatter_utils import convert_bytes_to_mb
20
+ from catocli.Utils.formatter_utils import convert_bytes_to_mb, is_bytes_measure
21
21
  except ImportError:
22
- from formatter_utils import convert_bytes_to_mb
22
+ from formatter_utils import convert_bytes_to_mb, is_bytes_measure
23
23
 
24
24
 
25
25
  def format_app_stats(response_data: Dict[str, Any], output_format: str = 'json') -> str:
@@ -89,24 +89,27 @@ def _format_app_stats_to_json(response_data: Dict[str, Any]) -> str:
89
89
  record_data = {}
90
90
 
91
91
  for i, (field, value) in enumerate(fields_map.items()):
92
- # Add unit type information for bytes fields
93
- if (i < len(record_unit_types) and record_unit_types[i] == 'bytes'):
94
- formatted_mb = convert_bytes_to_mb(value)
95
- if formatted_mb and formatted_mb != str(value):
92
+ # Check if this is a bytes field using both unit type and field name
93
+ unit_type = record_unit_types[i] if i < len(record_unit_types) else "unknown"
94
+ is_bytes_field = (unit_type == 'bytes') or is_bytes_measure(field, unit_type)
95
+
96
+ if is_bytes_field:
97
+ try:
98
+ formatted_mb = convert_bytes_to_mb(value)
96
99
  record_data[field] = {
97
100
  "value": value,
98
101
  "formatted_mb": formatted_mb,
99
102
  "unit_type": "bytes"
100
103
  }
101
- else:
104
+ except (ValueError, ZeroDivisionError):
102
105
  record_data[field] = {
103
106
  "value": value,
104
- "unit_type": "bytes"
107
+ "unit_type": "bytes_err"
105
108
  }
106
109
  else:
107
110
  record_data[field] = {
108
111
  "value": value,
109
- "unit_type": record_unit_types[i] if i < len(record_unit_types) else "unknown"
112
+ "unit_type": unit_type
110
113
  }
111
114
 
112
115
  organized_data["appStats"]["records"].append(record_data)
@@ -155,7 +158,10 @@ def _format_app_stats_to_csv(response_data: Dict[str, Any]) -> str:
155
158
  # Create headers with _mb suffix for bytes fields
156
159
  headers = []
157
160
  for i, field_name in enumerate(field_names):
158
- if i < len(field_unit_types) and field_unit_types[i] == 'bytes':
161
+ unit_type = field_unit_types[i] if i < len(field_unit_types) else "unknown"
162
+ is_bytes_field = (unit_type == 'bytes') or is_bytes_measure(field_name, unit_type)
163
+
164
+ if is_bytes_field:
159
165
  headers.append(f'{field_name}_mb')
160
166
  else:
161
167
  headers.append(field_name)
@@ -172,12 +178,19 @@ def _format_app_stats_to_csv(response_data: Dict[str, Any]) -> str:
172
178
  for i, field in enumerate(field_names):
173
179
  value = fields_map.get(field, '')
174
180
 
175
- # Convert bytes to MB if the field type is bytes
176
- if (i < len(record_unit_types) and record_unit_types[i] == 'bytes'):
177
- formatted_value = convert_bytes_to_mb(value)
178
- row.append(formatted_value if formatted_value else value)
181
+ # Check if this is a bytes field using both unit type and field name
182
+ unit_type = record_unit_types[i] if i < len(record_unit_types) else "unknown"
183
+ is_bytes_field = (unit_type == 'bytes') or is_bytes_measure(field, unit_type)
184
+
185
+ # Convert bytes to MB if the field is a bytes field
186
+ if is_bytes_field:
187
+ try:
188
+ formatted_value = convert_bytes_to_mb(value) if value != '' else ''
189
+ row.append(formatted_value)
190
+ except (ValueError, ZeroDivisionError):
191
+ row.append(str(value) if value != '' else '')
179
192
  else:
180
- row.append(value)
193
+ row.append(str(value) if value != '' else '')
181
194
 
182
195
  writer.writerow(row)
183
196
 
@@ -16,12 +16,12 @@ from typing import Dict, List, Any, Tuple
16
16
 
17
17
  # Import shared utility functions
18
18
  try:
19
- from .formatter_utils import format_timestamp, parse_label_for_dimensions_and_measure
19
+ from .formatter_utils import format_timestamp, parse_label_for_dimensions_and_measure, is_bytes_measure, convert_bytes_to_mb
20
20
  except ImportError:
21
21
  try:
22
- from catocli.Utils.formatter_utils import format_timestamp, parse_label_for_dimensions_and_measure
22
+ from catocli.Utils.formatter_utils import format_timestamp, parse_label_for_dimensions_and_measure, is_bytes_measure, convert_bytes_to_mb
23
23
  except ImportError:
24
- from formatter_utils import format_timestamp, parse_label_for_dimensions_and_measure
24
+ from formatter_utils import format_timestamp, parse_label_for_dimensions_and_measure, is_bytes_measure, convert_bytes_to_mb
25
25
 
26
26
 
27
27
  def format_app_stats_timeseries(response_data: Dict[str, Any], output_format: str = 'json') -> str:
@@ -170,20 +170,18 @@ def _format_app_stats_timeseries_to_json(response_data: Dict[str, Any]) -> str:
170
170
  for timestamp, value in measure_data['data'].items():
171
171
  timestamp_str = format_timestamp(timestamp)
172
172
 
173
- if measure in ['downstream', 'upstream', 'traffic'] and value:
173
+ if is_bytes_measure(measure):
174
174
  try:
175
- mb_value = value
176
- # mb_value = float(value) / 1048576
177
- formatted_value = f"{mb_value:.3f}".rstrip('0').rstrip('.')
175
+ converted_value = convert_bytes_to_mb(value)
178
176
  formatted_data[timestamp_str] = {
179
177
  'value': value,
180
- 'formatted_mb': formatted_value,
181
- 'unit_type': 'mb'
178
+ 'formatted_mb': converted_value,
179
+ 'unit_type': 'bytes'
182
180
  }
183
181
  except (ValueError, ZeroDivisionError):
184
182
  formatted_data[timestamp_str] = {
185
183
  'value': value,
186
- 'unit_type': 'mb'
184
+ 'unit_type': 'bytes_err'
187
185
  }
188
186
  else:
189
187
  formatted_data[timestamp_str] = {
@@ -322,20 +320,14 @@ def _format_app_stats_timeseries_to_csv(response_data: Dict[str, Any]) -> str:
322
320
  value = data.get(timestamp, '')
323
321
 
324
322
  # Convert bytes measures to MB and add appropriate suffix
325
- if measure in ['downstream', 'upstream', 'traffic']:
326
- if value:
327
- try:
328
- # Current bug in appStatsTimeSeries returning mb indicating unit as bytes
329
- # mb_value = float(value) / 1048576
330
- mb_value = value
331
- formatted_value = f"{mb_value:.3f}".rstrip('0').rstrip('.')
332
- row_data[f'{measure}_mb'] = formatted_value
333
- except (ValueError, ZeroDivisionError):
334
- row_data[f'{measure}_mb'] = value
335
- else:
336
- row_data[f'{measure}_mb'] = value
323
+ if is_bytes_measure(measure):
324
+ try:
325
+ converted_value = convert_bytes_to_mb(value) if value != '' else ''
326
+ row_data[f'{measure}_mb'] = converted_value
327
+ except (ValueError, ZeroDivisionError):
328
+ row_data[f'{measure}_mb'] = str(value) if value != '' else ''
337
329
  else:
338
- row_data[measure] = value
330
+ row_data[measure] = str(value) if value != '' else ''
339
331
 
340
332
  rows.append(row_data)
341
333
 
@@ -118,6 +118,7 @@ def is_bytes_measure(measure: str, units: str = "") -> bool:
118
118
  """
119
119
  bytes_measures = {
120
120
  'downstream', 'upstream', 'traffic', 'bytes', 'bytesDownstream',
121
+ 'bytes_upstream', 'bytes_downstream', 'bytes_total',
121
122
  'bytesUpstream', 'bytesTotal', 'throughput_downstream', 'throughput_upstream'
122
123
  }
123
124
 
catocli/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "3.0.22"
1
+ __version__ = "3.0.25"
2
2
  __cato_host__ = "https://api.catonetworks.com/api/v1/graphql2"
@@ -350,7 +350,9 @@ def createRequest(args, configuration):
350
350
  print(f"Saved CSV report to: {output_path}")
351
351
 
352
352
  # Return structured response similar to export functions
353
- return [{"success": True, "output_file": output_path, "operation": operation_name}]
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}]
354
356
  elif csv_output is None:
355
357
  # Formatter returned None, indicating we should fall back to raw response
356
358
  print("INFO: No processable data found, returning raw API response")
@@ -1844,7 +1846,9 @@ def createPrivateRequest(args, configuration):
1844
1846
  print(f"Saved CSV report to: {output_path}")
1845
1847
 
1846
1848
  # Return structured response similar to export functions
1847
- 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}]
1848
1852
  else:
1849
1853
  print("WARNING: CSV formatter returned empty result")
1850
1854
  return response
@@ -58,6 +58,7 @@ catocli query appStatsTimeSeries '{
58
58
  "fieldName": "application_name"
59
59
  }
60
60
  ],
61
+ "perSecond": false,
61
62
  "measure": [
62
63
  {
63
64
  "aggType": "sum",
@@ -99,6 +100,7 @@ catocli query appStatsTimeSeries '{
99
100
  "fieldName": "user_name"
100
101
  }
101
102
  ],
103
+ "perSecond": false,
102
104
  "measure": [
103
105
  {
104
106
  "aggType": "sum",
@@ -183,9 +183,11 @@ def write_json_export(
183
183
  if verbose:
184
184
  print(f"Successfully exported data to JSON: {filepath}")
185
185
 
186
+ # Normalize path separators for better cross-platform display
187
+ display_path = filepath.replace(os.sep, '/')
186
188
  return {
187
189
  'success': True,
188
- 'output_file': filepath,
190
+ 'output_file': display_path,
189
191
  'format': 'json',
190
192
  'record_count': len(data) if isinstance(data, (list, dict)) else 1
191
193
  }
@@ -232,9 +234,11 @@ def write_csv_export(
232
234
  if verbose:
233
235
  print(f"Successfully exported {len(data)} records to CSV: {filepath}")
234
236
 
237
+ # Normalize path separators for better cross-platform display
238
+ display_path = filepath.replace(os.sep, '/')
235
239
  return {
236
240
  'success': True,
237
- 'output_file': filepath,
241
+ 'output_file': display_path,
238
242
  'format': 'csv',
239
243
  'record_count': len(data)
240
244
  }
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: catocli
3
+ Version: 3.0.25
4
+ Summary: Cato Networks cli wrapper for the GraphQL API.
5
+ Home-page: https://github.com/Cato-Networks/cato-cli
6
+ Author: Cato Networks
7
+ Author-email: [email protected]
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3.6
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.6
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: urllib3
22
+ Requires-Dist: certifi
23
+ Requires-Dist: six
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: license-file
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
34
+
35
+ # Cato Networks GraphQL API CLI
36
+
37
+ The package provides a simple to use CLI that reflects industry standards (such as the AWS cli), and enables customers to manage Cato Networks configurations and processes via the [Cato Networks GraphQL API](https://api.catonetworks.com/api/v1/graphql2) easily integrating into configurations management, orchestration or automation frameworks to support the DevOps model.
38
+
39
+ ## Overview
40
+
41
+ CatoCLI is a command-line interface that provides access to the Cato Networks GraphQL API, enabling you to:
42
+ - Generate detailed network and security reports
43
+ - Analyze user and application activity
44
+ - Monitor network performance and events
45
+ - Export data in multiple formats (JSON, CSV)
46
+ - Automate reporting and monitoring tasks
47
+
48
+ ## Prerequisites
49
+
50
+ - Python 3.6 or higher
51
+ - CatoCLI installed (`pip3 install catocli`)
52
+ - Valid Cato Networks API token and Account ID
53
+ - Proper authentication configuration (see [Authentication Setup](#authentication-setup))
54
+
55
+ ## Installation
56
+
57
+ `pip3 install catocli`
58
+
59
+ ## Authentication Setup
60
+
61
+ Configure your CatoCLI profile before using any query operations:
62
+
63
+ ```bash
64
+ # Interactive configuration
65
+ catocli configure set
66
+
67
+ # Non-interactive configuration
68
+ catocli configure set --cato-token "your-api-token" --account-id "12345"
69
+
70
+ # List configured profiles
71
+ catocli configure list
72
+
73
+ # Show current profile
74
+ catocli configure show
75
+ ```
76
+
77
+ ### Documentation
78
+
79
+ For detailed information about profile management, see [PROFILES.md](PROFILES.md).
80
+
81
+ [CLICK HERE](https://support.catonetworks.com/hc/en-us/articles/4413280536081-Generating-API-Keys-for-the-Cato-API) to see how create an API key to authenticate.
82
+
83
+ ## Running the CLI
84
+ catocli -h
85
+ catocli query -h
86
+ catocli query entityLookup -h
87
+ catocli query entityLookup '{"type":"country"}`
88
+
89
+ // Override the accountID value as a cli argument
90
+ catocli query entityLookup -accountID=12345 '{"type":"country"}`
91
+
92
+ ## Check out run locally not as pip package
93
+ git clone git@github.com:Cato-Networks/cato-cli.git
94
+ cd cato-cli
95
+ python3 -m catocli -h
96
+
97
+ ## Query Operations
98
+
99
+ ### Core Analytics Queries
100
+
101
+ | Operation | Description | Guide |
102
+ |-----------|-------------|--------|
103
+ | [Account Metrics](./catocli_user_guide/account-metrics.md) | Network performance metrics by site, user, or interface | 📊 |
104
+ | [Application Statistics](./catocli_user_guide/app-stats.md) | User activity and application usage analysis | 📱 |
105
+ | [Application Statistics Time Series](./catocli_user_guide/app-stats-timeseries.md) | Traffic analysis over time with hourly/daily breakdowns | 📈 |
106
+ | [Events Time Series](./catocli_user_guide/events-timeseries.md) | Security events, connectivity, and threat analysis | 🔒 |
107
+ | [Socket Port Metrics](./catocli_user_guide/socket-port-metrics.md) | Socket interface performance and traffic analysis | 🔌 |
108
+ | [Socket Port Time Series](./catocli_user_guide/socket-port-timeseries.md) | Socket performance metrics over time | ⏱️ |
109
+
110
+ ### Advanced Topics
111
+
112
+ - [Common Patterns & Best Practices](./catocli_user_guide/common-patterns.md) - Output formats, time frames, filtering patterns
113
+ - [Python Integration - Windows](./catocli_user_guide/python-integration-windows.md) - Windows-specific Python automation examples
114
+ - [Python Integration - Unix/Linux/macOS](./catocli_user_guide/python-integration-unix.md) - Unix-based Python integration guide
115
+ - [SIEM Integration Guide](./catocli_user_guide/siem-integration.md) - Real-time security event streaming to SIEM platforms
116
+
117
+ ## Quick Start Examples
118
+
119
+ ### Basic Network Health Check
120
+ ```bash
121
+ # Get last hour account metrics
122
+ catocli query accountMetrics '{"timeFrame":"last.PT1H"}'
123
+ ```
124
+
125
+ ### User Activity Report (csv format)
126
+ ```bash
127
+ # Export user activity for the last month to CSV
128
+ catocli query appStats '{
129
+ "dimension": [{"fieldName": "user_name"}],
130
+ "measure": [{"aggType": "sum", "fieldName": "flows_created"}],
131
+ "timeFrame": "last.P1M"
132
+ }' -f csv --csv-filename user_activity_report.csv
133
+ ```
134
+
135
+ ### Security Events Analysis
136
+ ```bash
137
+ # Weekly security events breakdown
138
+ catocli query eventsTimeSeries '{
139
+ "buckets": 7,
140
+ "eventsFilter": [{"fieldName": "event_type", "operator": "is", "values": ["Security"]}],
141
+ "eventsMeasure": [{"aggType": "sum", "fieldName": "event_count"}],
142
+ "timeFrame": "last.P7D"
143
+ }'
144
+ ```
145
+
146
+ ## Output Formats
147
+
148
+ CatoCLI supports multiple output formats:
149
+
150
+ - **Enhanced JSON** (default): Formatted with granularity adjustments
151
+ - **Raw JSON**: Original API response with `-raw` flag
152
+ - **CSV**: Structured data export with `-f csv`
153
+ - **Custom CSV**: Named files with `--csv-filename` and `--append-timestamp`
154
+
155
+ ## Time Frame Options
156
+
157
+ Common time frame patterns:
158
+ - `last.PT1H` - Last hour
159
+ - `last.P1D` - Last day
160
+ - `last.P7D` - Last week
161
+ - `last.P1M` - Last month
162
+ - `utc.2023-02-{28/00:00:00--28/23:59:59}` - Custom UTC range
163
+
164
+ ## Getting Help
165
+
166
+ - Use `-h` or `--help` with any command for detailed usage
167
+ - Check the [Cato API Documentation](https://api.catonetworks.com/documentation/)
168
+ - Review individual operation guides linked above
169
+
170
+
171
+ This CLI is a Python 3 application and has been tested with Python 3.6 -> 3.8
172
+
173
+ ## Requirements:
174
+ python 3.6 or higher
175
+
176
+ ## Confirm your version of python if installed:
177
+ Open a terminal
178
+ Enter: python -V or python3 -V
179
+
180
+ ## Installing the correct version for environment:
181
+ https://www.python.org/downloads/