zscaler-sdk-python 1.2.0__py3-none-any.whl → 1.2.2__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 (52) hide show
  1. zscaler/__init__.py +1 -1
  2. zscaler/helpers.py +104 -0
  3. zscaler/logger.py +36 -93
  4. zscaler/oneapi_object.py +7 -21
  5. zscaler/oneapi_response.py +28 -49
  6. zscaler/request_executor.py +10 -2
  7. zscaler/utils.py +47 -1
  8. zscaler/zcc/admin_user.py +11 -13
  9. zscaler/zcc/company.py +15 -10
  10. zscaler/zcc/devices.py +60 -44
  11. zscaler/zcc/models/admin_user.py +76 -0
  12. zscaler/zcc/models/devices.py +198 -0
  13. zscaler/zcc/models/getcompanyinfo.py +1322 -0
  14. zscaler/zia/bandwidth_classes.py +13 -7
  15. zscaler/zia/legacy.py +10 -13
  16. zscaler/zpa/app_connector_groups.py +1 -1
  17. zscaler/zpa/app_connectors.py +1 -1
  18. zscaler/zpa/app_segment_by_type.py +1 -68
  19. zscaler/zpa/app_segments_inspection.py +1 -2
  20. zscaler/zpa/app_segments_pra.py +1 -2
  21. zscaler/zpa/application_segment.py +1 -1
  22. zscaler/zpa/certificates.py +2 -2
  23. zscaler/zpa/cloud_connector_groups.py +1 -1
  24. zscaler/zpa/customer_version_profile.py +1 -1
  25. zscaler/zpa/emergency_access.py +1 -1
  26. zscaler/zpa/enrollment_certificates.py +1 -1
  27. zscaler/zpa/idp.py +1 -1
  28. zscaler/zpa/legacy.py +2 -2
  29. zscaler/zpa/lss.py +1 -1
  30. zscaler/zpa/machine_groups.py +1 -3
  31. zscaler/zpa/microtenants.py +1 -1
  32. zscaler/zpa/policies.py +1 -3
  33. zscaler/zpa/posture_profiles.py +1 -1
  34. zscaler/zpa/pra_approval.py +1 -1
  35. zscaler/zpa/pra_console.py +1 -1
  36. zscaler/zpa/pra_credential.py +1 -1
  37. zscaler/zpa/pra_credential_pool.py +1 -1
  38. zscaler/zpa/pra_portal.py +1 -1
  39. zscaler/zpa/provisioning.py +1 -1
  40. zscaler/zpa/saml_attributes.py +1 -1
  41. zscaler/zpa/scim_attributes.py +1 -4
  42. zscaler/zpa/scim_groups.py +1 -1
  43. zscaler/zpa/segment_groups.py +1 -1
  44. zscaler/zpa/server_groups.py +2 -2
  45. zscaler/zpa/servers.py +1 -1
  46. zscaler/zpa/service_edge_group.py +1 -1
  47. zscaler/zpa/service_edges.py +1 -1
  48. zscaler/zpa/trusted_networks.py +1 -1
  49. {zscaler_sdk_python-1.2.0.dist-info → zscaler_sdk_python-1.2.2.dist-info}/METADATA +1 -1
  50. {zscaler_sdk_python-1.2.0.dist-info → zscaler_sdk_python-1.2.2.dist-info}/RECORD +52 -51
  51. {zscaler_sdk_python-1.2.0.dist-info → zscaler_sdk_python-1.2.2.dist-info}/LICENSE.md +0 -0
  52. {zscaler_sdk_python-1.2.0.dist-info → zscaler_sdk_python-1.2.2.dist-info}/WHEEL +0 -0
zscaler/__init__.py CHANGED
@@ -29,7 +29,7 @@ __license__ = "MIT"
29
29
  __contributors__ = [
30
30
  "William Guilherme",
31
31
  ]
32
- __version__ = "1.2.0"
32
+ __version__ = "1.2.2"
33
33
 
34
34
 
35
35
  from zscaler.oneapi_client import Client as ZscalerClient # noqa
zscaler/helpers.py CHANGED
@@ -53,6 +53,58 @@ def to_snake_case(string):
53
53
  "extranetDNSList": "extranet_dns_list",
54
54
  "primaryDNSServer": "primary_dns_server",
55
55
  "secondaryDNSServer": "secondary_dns_server",
56
+
57
+ # ZCC Edge Case Attributes
58
+ "enableUDPTransportSelection": "enable_udp_transport_selection",
59
+ "interceptZIATrafficAllAdapters": "intercept_zia_traffic_all_adapters",
60
+ "enablePortBasedZPAFilter": "enable_port_based_zpa_filter",
61
+ "addAppBypassToVPNGateway": "add_app_bypass_to_vpn_gateway",
62
+ "showVPNTunNotification": "show_vpn_tun_notification",
63
+ "enableSetProxyOnVPNAdapters": "enable_set_proxy_on_vpn_adapters",
64
+ "disableDNSRouteExclusion": "disable_dns_route_exclusion",
65
+ "enableReactUI": "enable_react_ui",
66
+ "ziaGlobalDbUrlForDR": "zia_global_db_url_for_dr",
67
+ "useDefaultAdapterForDNS": "use_default_adapter_for_dns",
68
+ "enablePublicAPI": "enable_public_api",
69
+ "launchReactUIbyDefault": "launch_react_u_iby_default",
70
+ "addZDXServiceEntitlement": "add_zdx_service_entitlement",
71
+ "computeDeviceGroupsForZIA": "compute_device_groups_for_zia",
72
+ "computeDeviceGroupsForZPA": "compute_device_groups_for_zpa",
73
+ "computeDeviceGroupsForZAD": "compute_device_groups_for_zad",
74
+ "computeDeviceGroupsForZAD": "compute_device_groups_for_zad",
75
+ "deleteDHCPOption121RoutesVisibility": "delete_dhcp_option121_routes_visibility",
76
+ "enableOneIDAdminMigrationChanges": "enable_one_id_admin_migration_changes",
77
+ "purgeKerberosPreferredDCCacheVisibility": "purge_kerberos_preferred_dc_cache_visibility",
78
+ "slowRolloutZCC": "slow_rollout_zcc",
79
+ "supportMultiplePWLPostures": "support_multiple_pwl_postures",
80
+ "truncateLargeUDPDNSResponseVisibility": "truncate_large_udpdns_response_visibility",
81
+ "enforceSplitDNSVisibility": "enforce_split_dns_visibility",
82
+ "zccSyntheticIPRangeVisibility": "zcc_synthetic_ip_range_visibility",
83
+ "enableSetProxyOnVPNAdaptersVisibility": "enable_set_proxy_on_vpn_adapters_visibility",
84
+ "customMTUForZpaVisibility": "custom_mtu_for_zpa_visibility",
85
+ "flowLoggerZCCBlockedTrafficVisibility": "flow_logger_zcc_blocked_traffic_visibility",
86
+ "postureCrowdStrikeZTAScoreVisibilityForLinux": "posture_crowd_strike_zta_score_visibility_for_linux",
87
+ "flowLoggerVPNTunnelTypeVisibility": "flow_logger_vpn_tunnel_type_visibility",
88
+ "flowLoggerVPNTypeVisibility": "flow_logger_vpn_type_visibility",
89
+ "flowLoggerZPATypeVisibility": "flow_logger_zpa_type_visibility",
90
+ "useDefaultAdapterForDNSVisibility": "use_default_adapter_for_dns_visibility",
91
+ "useCustomDNS": "use_custom_dns",
92
+ "notificationForZPAReauthVisibility": "notification_for_zpa_reauth_visibility",
93
+ "crowdStrikeZTAScoreVisibility": "crowd_strike_zta_score_visibility",
94
+ "hideDTLSSupportSettings": "hide_dtls_support_settings",
95
+ "dynamicZPAServiceEdgeAssignmenttVisibility": "dynamic_zpa_service_edge_assignmentt_visibility",
96
+ "domainInclusionExclusionForDNSRequestVisibility": "domain_inclusion_exclusion_for_dns_request_visibility",
97
+ "overrideATCmdByPolicyVisibility": "override_at_cmd_by_policy_visibility",
98
+ "windowsAPCaptivePortalDetectionVisibility": "windows_ap_captive_portal_detection_visibility",
99
+ "windowsAPEnableFailOpenVisibility": "windows_ap_enable_fail_open_visibility",
100
+ "enableOneIDPhase2Changes": "enable_one_id_phase2_changes",
101
+ "linuxRPMBuildVisibility": "linux_rpm_build_visibility",
102
+ "crowdStrikeZTAOsScoreVisibility": "crowd_strike_zta_os_score_visibility",
103
+ "crowdStrikeZTASensorConfigScoreVisibility": "crowd_strike_zta_sensor_config_score_visibility",
104
+ "enableZCCFailCloseSettingsForSEMode": "enable_zcc_fail_close_settings_for_se_mode",
105
+ "defaultProtocolForZPA": "default_protocol_for_zpa",
106
+ "tunnelTwoForiOSDevices": "tunnel_two_fori_os_devices",
107
+ "prioritizeIPv4OverIpv6": "prioritize_ipv4_over_ipv6",
56
108
  }
57
109
 
58
110
  if string in FIELD_EXCEPTIONS:
@@ -114,6 +166,58 @@ def to_lower_camel_case(string):
114
166
  "extranet_dns_list": "extranetDNSList",
115
167
  "primary_dns_server": "primaryDNSServer",
116
168
  "secondary_dns_server": "secondaryDNSServer",
169
+
170
+ # ZCC Edge Case Attributes
171
+ "enable_udp_transport_selection": "enableUDPTransportSelection",
172
+ "intercept_zia_traffic_all_adapters": "interceptZIATrafficAllAdapters",
173
+ "enable_port_based_zpa_filter": "enablePortBasedZPAFilter",
174
+ "add_app_bypass_to_vpn_gateway": "addAppBypassToVPNGateway",
175
+ "show_vpn_tun_notification": "showVPNTunNotification",
176
+ "enable_set_proxy_on_vpn_adapters": "enableSetProxyOnVPNAdapters",
177
+ "disable_dns_route_exclusion": "disableDNSRouteExclusion",
178
+ "enable_react_ui": "enableReactUI",
179
+ "zia_global_db_url_for_dr": "ziaGlobalDbUrlForDR",
180
+ "use_default_adapter_for_dns": "useDefaultAdapterForDNS",
181
+ "enable_public_api": "enablePublicAPI",
182
+ "launch_react_u_iby_default": "launchReactUIbyDefault",
183
+ "add_zdx_service_entitlement": "addZDXServiceEntitlement",
184
+ "compute_device_groups_for_zia": "computeDeviceGroupsForZIA",
185
+ "compute_device_groups_for_zpa": "computeDeviceGroupsForZPA",
186
+ "compute_device_groups_for_zad": "computeDeviceGroupsForZAD",
187
+ "delete_dhcp_option121_routes_visibility": "deleteDHCPOption121RoutesVisibility",
188
+ "enable_one_id_admin_migration_changes": "enableOneIDAdminMigrationChanges",
189
+ "purge_kerberos_preferred_dc_cache_visibility": "purgeKerberosPreferredDCCacheVisibility",
190
+ "slow_rollout_zcc": "slowRolloutZCC",
191
+ "support_multiple_pwl_postures": "supportMultiplePWLPostures",
192
+ "truncate_large_udpdns_response_visibility": "truncateLargeUDPDNSResponseVisibility",
193
+ "enforce_split_dns_visibility": "enforceSplitDNSVisibility",
194
+ "zcc_synthetic_ip_range_visibility": "zccSyntheticIPRangeVisibility",
195
+ "enable_set_proxy_on_vpn_adapters_visibility": "enableSetProxyOnVPNAdaptersVisibility",
196
+ "custom_mtu_for_zpa_visibility": "customMTUForZpaVisibility",
197
+ "flow_logger_zcc_blocked_traffic_visibility": "flowLoggerZCCBlockedTrafficVisibility",
198
+ "posture_crowd_strike_zta_score_visibility_for_linux": "postureCrowdStrikeZTAScoreVisibilityForLinux",
199
+ "flow_logger_vpn_tunnel_type_visibility": "flowLoggerVPNTunnelTypeVisibility",
200
+ "flow_logger_vpn_type_visibility": "flowLoggerVPNTypeVisibility",
201
+ "flow_logger_zpa_type_visibility": "flowLoggerZPATypeVisibility",
202
+ "use_default_adapter_for_dns_visibility": "useDefaultAdapterForDNSVisibility",
203
+ "use_custom_dns": "useCustomDNS",
204
+ "notification_for_zpa_reauth_visibility": "notificationForZPAReauthVisibility",
205
+ "crowd_strike_zta_score_visibility": "crowdStrikeZTAScoreVisibility",
206
+ "hide_dtls_support_settings": "hideDTLSSupportSettings",
207
+ "dynamic_zpa_service_edge_assignmentt_visibility": "dynamicZPAServiceEdgeAssignmenttVisibility",
208
+ "domain_inclusion_exclusion_for_dns_request_visibility": "domainInclusionExclusionForDNSRequestVisibility",
209
+ "override_at_cmd_by_policy_visibility": "overrideATCmdByPolicyVisibility",
210
+ "windows_ap_captive_portal_detection_visibility": "windowsAPCaptivePortalDetectionVisibility",
211
+ "windows_ap_enable_fail_open_visibility": "windowsAPEnableFailOpenVisibility",
212
+ "enable_one_id_phase2_changes": "enableOneIDPhase2Changes",
213
+ "linux_rpm_build_visibility": "linuxRPMBuildVisibility",
214
+ "crowd_strike_zta_os_score_visibility": "crowdStrikeZTAOsScoreVisibility",
215
+ "crowd_strike_zta_sensor_config_score_visibility": "crowdStrikeZTASensorConfigScoreVisibility",
216
+ "enable_zcc_fail_close_settings_for_se_mode": "enableZCCFailCloseSettingsForSEMode",
217
+ "default_protocol_for_zpa": "defaultProtocolForZPA",
218
+ "tunnelTwoForiOSDevices": "tunnel_two_fori_os_devices",
219
+ "prioritize_ipv4_over_ipv6": "prioritizeIPv4OverIpv6",
220
+
117
221
  }
118
222
 
119
223
  if string in FIELD_EXCEPTIONS:
zscaler/logger.py CHANGED
@@ -8,117 +8,60 @@ from http.client import HTTPConnection
8
8
  LOG_FORMAT = "%(asctime)s - %(name)s - %(module)s - %(levelname)s - %(message)s"
9
9
 
10
10
 
11
- # def setup_logging(logger_name="zscaler-sdk-python", enabled=None, verbose=None):
12
- # """
13
- # Set up logging with specified level and logger name.
14
- # Log level is controlled via ZSCALER_SDK_VERBOSE environment variable.
15
- # Logging can be enabled/disabled via ZSCALER_SDK_LOG environment variable.
16
-
17
- # Parameters:
18
- # - logger_name (str, optional): Logger name. Defaults to "zscaler-sdk-python".
19
- # - enabled (bool, optional): Enable logging. Defaults to None, which uses the environment variable.
20
- # - verbose (bool, optional): Set verbose logging. Defaults to None, which uses the environment variable.
21
- # """
22
- # if enabled is None:
23
- # enabled = os.getenv("ZSCALER_SDK_LOG", "false").lower() == "true"
24
-
25
- # # if not enabled:
26
- # # # If logging is not enabled, set up a null handler
27
- # # logging.disable(logging.INFO)
28
- # # return
29
-
30
- # if not enabled:
31
- # # Do not globally disable logging
32
- # # Just keep the SDK logger silent
33
- # sdk_logger = logging.getLogger(logger_name)
34
- # sdk_logger.addHandler(logging.NullHandler())
35
- # return
36
-
37
- # if verbose is None:
38
- # verbose = os.getenv("ZSCALER_SDK_VERBOSE", "false").lower() == "true"
39
-
40
- # log_level = logging.DEBUG if verbose else logging.INFO
41
- # HTTPConnection.debuglevel = 0
42
- # # Create a logger with the specified name
43
- # logger = logging.getLogger(logger_name)
44
- # default_logger = logging.getLogger()
45
-
46
- # # If the logger already has handlers, remove them to avoid duplicate logging
47
- # for handler in logger.handlers[:]:
48
- # logger.removeHandler(handler)
49
-
50
- # for handler in default_logger.handlers[:]:
51
- # default_logger.removeHandler(handler)
52
-
53
- # # Set log level
54
- # logger.setLevel(log_level)
55
- # default_logger.setLevel(log_level)
56
- # logging.basicConfig(level=log_level)
57
- # # Create a stream handler with the specified level and formatter
58
- # stream_handler = logging.StreamHandler()
59
- # stream_handler.setLevel(log_level)
60
- # log_formatter = logging.Formatter(LOG_FORMAT)
61
- # stream_handler.setFormatter(log_formatter)
62
-
63
- # # Add the handler to the logger
64
- # logger.addHandler(stream_handler)
65
-
66
-
67
- # # Option: Add FileHandler if you want logs to be written to a file.
68
- # if os.getenv("LOG_TO_FILE", "false").lower() == "true":
69
- # file_handler = logging.FileHandler(os.getenv("LOG_FILE_PATH", "sdk.log"))
70
- # file_handler.setLevel(log_level)
71
- # file_handler.setFormatter(log_formatter)
72
- # logger.addHandler(file_handler)
73
11
  def setup_logging(logger_name="zscaler-sdk-python", enabled=None, verbose=None):
74
12
  """
75
- Set up logging for the Zscaler SDK.
76
-
77
- Logging behavior can be controlled via environment variables or config:
78
-
79
- Environment Variables:
80
- ZSCALER_SDK_LOG (true/false): Enable or disable SDK logging.
81
- ZSCALER_SDK_VERBOSE (true/false): Enable verbose (DEBUG) logging.
82
- LOG_TO_FILE (true/false): Enable file-based logging.
83
- LOG_FILE_PATH: File path for logs (default: sdk.log)
13
+ Set up logging with specified level and logger name.
14
+ Log level is controlled via ZSCALER_SDK_VERBOSE environment variable.
15
+ Logging can be enabled/disabled via ZSCALER_SDK_LOG environment variable.
84
16
 
85
17
  Parameters:
86
- logger_name (str): Name of the logger. Defaults to "zscaler-sdk-python".
87
- enabled (bool): Enable logging. If None, reads from env.
88
- verbose (bool): Enable DEBUG-level logging. If None, reads from env.
18
+ - logger_name (str, optional): Logger name. Defaults to "zscaler-sdk-python".
19
+ - enabled (bool, optional): Enable logging. Defaults to None, which uses the environment variable.
20
+ - verbose (bool, optional): Set verbose logging. Defaults to None, which uses the environment variable.
89
21
  """
90
22
  if enabled is None:
91
23
  enabled = os.getenv("ZSCALER_SDK_LOG", "false").lower() == "true"
92
24
 
93
25
  if not enabled:
94
- # Do NOT disable global logging.
95
- # Just suppress SDK logs quietly with a NullHandler.
96
- logging.getLogger(logger_name).addHandler(logging.NullHandler())
26
+ # If logging is not enabled, set up a null handler
27
+ logging.disable(logging.INFO)
97
28
  return
98
29
 
99
30
  if verbose is None:
100
31
  verbose = os.getenv("ZSCALER_SDK_VERBOSE", "false").lower() == "true"
101
32
 
102
33
  log_level = logging.DEBUG if verbose else logging.INFO
103
- HTTPConnection.debuglevel = 0 # Optional: Enable HTTP debug logging
104
-
105
- # SDK logger (isolated)
34
+ HTTPConnection.debuglevel = 0
35
+ # Create a logger with the specified name
106
36
  logger = logging.getLogger(logger_name)
37
+ default_logger = logging.getLogger()
38
+
39
+ # If the logger already has handlers, remove them to avoid duplicate logging
40
+ for handler in logger.handlers[:]:
41
+ logger.removeHandler(handler)
42
+
43
+ for handler in default_logger.handlers[:]:
44
+ default_logger.removeHandler(handler)
45
+
46
+ # Set log level
107
47
  logger.setLevel(log_level)
48
+ default_logger.setLevel(log_level)
49
+ logging.basicConfig(level=log_level)
50
+ # Create a stream handler with the specified level and formatter
51
+ stream_handler = logging.StreamHandler()
52
+ stream_handler.setLevel(log_level)
53
+ log_formatter = logging.Formatter(LOG_FORMAT)
54
+ stream_handler.setFormatter(log_formatter)
108
55
 
109
- # Clean up any duplicate handlers
110
- if not logger.handlers:
111
- stream_handler = logging.StreamHandler()
112
- stream_handler.setLevel(log_level)
113
- stream_handler.setFormatter(logging.Formatter(LOG_FORMAT))
114
- logger.addHandler(stream_handler)
56
+ # Add the handler to the logger
57
+ logger.addHandler(stream_handler)
115
58
 
116
- # Optional file logging
117
- if os.getenv("LOG_TO_FILE", "false").lower() == "true":
118
- file_handler = logging.FileHandler(os.getenv("LOG_FILE_PATH", "sdk.log"))
119
- file_handler.setLevel(log_level)
120
- file_handler.setFormatter(logging.Formatter(LOG_FORMAT))
121
- logger.addHandler(file_handler)
59
+ # Option: Add FileHandler if you want logs to be written to a file.
60
+ if os.getenv("LOG_TO_FILE", "false").lower() == "true":
61
+ file_handler = logging.FileHandler(os.getenv("LOG_FILE_PATH", "sdk.log"))
62
+ file_handler.setLevel(log_level)
63
+ file_handler.setFormatter(log_formatter)
64
+ logger.addHandler(file_handler)
122
65
 
123
66
 
124
67
  def dump_request(logger, url: str, method: str, json, params, headers, request_uuid: str, body=True):
@@ -178,4 +121,4 @@ def dump_response(
178
121
  if response_body and response_body != "" and response_body != "null":
179
122
  log_lines.append(f"\n{response_body}")
180
123
  log_lines.append("-" * 68)
181
- logger.info("\n".join(log_lines))
124
+ logger.info("\n".join(log_lines))
zscaler/oneapi_object.py CHANGED
@@ -13,27 +13,13 @@ class ZscalerObject:
13
13
  def __repr__(self):
14
14
  return str(vars(self))
15
15
 
16
- # def as_dict(self):
17
- # """
18
- # Returns dictionary object of ZscalerObject with keys in snake_case for user-facing output.
19
- # """
20
- # result = {}
21
- # for key, val in self.request_format().items():
22
- # if val is None:
23
- # continue
24
- # if isinstance(val, list):
25
- # formatted_list = []
26
- # for item in val:
27
- # if isinstance(item, ZscalerObject):
28
- # formatted_list.append(item.as_dict()) # Recursive call for nested objects
29
- # else:
30
- # formatted_list.append(item)
31
- # result[to_snake_case(key)] = formatted_list # Convert key to snake_case
32
- # elif not isinstance(val, ZscalerObject):
33
- # result[to_snake_case(key)] = val # Convert key to snake_case for simple types
34
- # else:
35
- # result[to_snake_case(key)] = val.as_dict() # Convert nested objects
36
- # return result
16
+ def __getitem__(self, key):
17
+ if hasattr(self, key):
18
+ return getattr(self, key)
19
+ raise KeyError(f"{key} not found in {self.__class__.__name__}")
20
+
21
+ def __contains__(self, key):
22
+ return hasattr(self, key)
37
23
 
38
24
  def as_dict(self):
39
25
  result = {}
@@ -26,8 +26,6 @@ class ZscalerAPIResponse:
26
26
  res_details=None,
27
27
  response_body="",
28
28
  data_type=None,
29
- # max_items=None,
30
- # max_pages=None,
31
29
  all_entries=False,
32
30
  sort_order=None,
33
31
  sort_by=None,
@@ -80,19 +78,7 @@ class ZscalerAPIResponse:
80
78
 
81
79
  if res_details:
82
80
  content_type = res_details.headers.get("Content-Type", "").lower()
83
- # if "application/json" in content_type:
84
- # self._build_json_response(response_body)
85
- # else:
86
- # # Attempt JSON parse, else store as text
87
- # try:
88
- # self._build_json_response(response_body)
89
- # except json.JSONDecodeError:
90
- # self._body = response_body
91
- # else:
92
- # try:
93
- # self._build_json_response(response_body)
94
- # except json.JSONDecodeError:
95
- # self._body = response_body
81
+
96
82
  if "application/json" in content_type:
97
83
  try:
98
84
  self._build_json_response(response_body)
@@ -180,13 +166,21 @@ class ZscalerAPIResponse:
180
166
 
181
167
  self._items_fetched += len(self._list)
182
168
  self._pages_fetched += 1
183
-
169
+
184
170
  def get_results(self):
185
171
  """
186
172
  Returns the current page of results.
187
173
  The initial call to the API returns only one page.
188
174
  """
189
175
  logger.debug("Fetching current page results")
176
+
177
+ if self._service_type.upper() == "ZCC" and self._type:
178
+ try:
179
+ return [self._type(item) for item in self._list if isinstance(item, dict)]
180
+ except Exception as wrap_error:
181
+ logger.warning(f"Failed to wrap results with {self._type}: {wrap_error}")
182
+ return self._list
183
+
190
184
  return self._list
191
185
 
192
186
  def has_next(self):
@@ -199,35 +193,28 @@ class ZscalerAPIResponse:
199
193
  return self._has_next()
200
194
 
201
195
  def next(self):
202
- """
203
- Fetch the next page of results if available.
196
+ if not self.has_next():
197
+ raise StopIteration("No more pages available.")
204
198
 
205
- Returns:
206
- tuple: (list of items, error)
199
+ results, error = self._fetch_next_page()
200
+ if error:
201
+ return None, self, error
202
+ if not results:
203
+ return None, self, None
204
+
205
+ if self._type:
206
+ try:
207
+ results = [self._type(item) for item in results if isinstance(item, dict)]
208
+ except Exception as wrap_error:
209
+ logger.warning(f"Failed to wrap pagination results with {self._type}: {wrap_error}")
210
+
211
+ return results, self, None
207
212
 
208
- If there's an error fetching the next page, returns (None, error).
209
- If no more pages exist, returns (None, None).
210
- """
211
- if not self.has_next():
212
- logger.debug("No further pages to retrieve.")
213
- return (None, None)
214
-
215
- next_page_results = self._fetch_next_page()
216
- if next_page_results is None:
217
- # An error occurred, already logged
218
- return (None, ValueError("Error fetching next page."))
219
- if not next_page_results:
220
- # Empty result means no more data
221
- return (None, None)
222
- return (next_page_results, None)
223
213
 
224
214
  def _fetch_next_page(self):
225
- """
226
- Internal method that fetches and returns the next page of results.
227
- """
228
215
  if not self._has_next():
229
216
  logger.debug("No more pages to fetch")
230
- return []
217
+ return [], None
231
218
 
232
219
  if self._service_type == "ZDX":
233
220
  self._params["offset"] = self._next_offset
@@ -248,20 +235,12 @@ class ZscalerAPIResponse:
248
235
 
249
236
  if error:
250
237
  logger.error(f"Error fetching the next page: {error}")
251
- return None
238
+ return None, error
252
239
 
253
- # Rebuild the response body for the next page
254
240
  self._build_json_response(response_body)
255
- return self._list
241
+ return self._list, None
256
242
 
257
243
  def _has_next(self):
258
- # Check max_items or max_pages constraints
259
- # if self._max_items is not None and self._items_fetched >= self._max_items:
260
- # logger.debug("Reached max items limit: %d", self._max_items)
261
- # return False
262
- # if self._max_pages is not None and self._pages_fetched >= self._max_pages:
263
- # logger.debug("Reached max pages limit: %d", self._max_pages)
264
- # return False
265
244
 
266
245
  if self._service_type == "ZPA":
267
246
  # More pages if current page < total_pages
@@ -14,8 +14,9 @@ from zscaler.zdx.legacy import LegacyZDXClientHelper
14
14
  from zscaler.zpa.legacy import LegacyZPAClientHelper
15
15
  from zscaler.zia.legacy import LegacyZIAClientHelper
16
16
  from zscaler.zwa.legacy import LegacyZWAClientHelper
17
+ # from zscaler.logger import setup_logging # ✅ Import here
17
18
 
18
- logger = logging.getLogger(__name__)
19
+ logger = logging.getLogger('zscaler-sdk-python')
19
20
 
20
21
 
21
22
  class RequestExecutor:
@@ -75,6 +76,13 @@ class RequestExecutor:
75
76
  self._config = config
76
77
  self._cache = cache
77
78
 
79
+ # ✅ Setup logging based on config flags
80
+ # log_config = self._config["client"].get("logging", {})
81
+ # setup_logging(
82
+ # enabled=log_config.get("enabled", False),
83
+ # verbose=log_config.get("verbose", False),
84
+ # )
85
+
78
86
  # Retrieve cloud, service, and customer ID (optional)
79
87
  self.cloud = self._config["client"].get("cloud", "production").lower()
80
88
  self.sandbox_cloud = self._config["client"].get("sandboxCloud", "").lower()
@@ -462,7 +470,7 @@ class RequestExecutor:
462
470
  req_timeout = self._request_timeout
463
471
 
464
472
  if req_timeout > 0 and (current_req_start_time - request_start_time) > req_timeout:
465
- logger.error("Request Timeout exceeded.")
473
+ logger.warning("Request Timeout exceeded.")
466
474
  return None, None, None, Exception("Request Timeout exceeded.")
467
475
 
468
476
  response, error = self._http_client.send_request(request)
zscaler/utils.py CHANGED
@@ -26,7 +26,7 @@ import time
26
26
  from typing import Dict, Optional
27
27
  from urllib.parse import urlencode
28
28
  from datetime import datetime as dt
29
-
29
+ from functools import wraps
30
30
  import pytz
31
31
  from box import Box, BoxList
32
32
  from dateutil import parser
@@ -783,6 +783,52 @@ def format_url(base_string):
783
783
  """
784
784
  return "".join([line.strip() for line in base_string.splitlines()])
785
785
 
786
+ def zcc_param_mapper(func):
787
+ @wraps(func)
788
+ def wrapper(self, *args, **kwargs):
789
+ query_params = kwargs.get("query_params", {}) or {}
790
+ mapped_params = {}
791
+
792
+ # Normalize and map os_types
793
+ if "os_types" in query_params:
794
+ os_raw = query_params["os_types"]
795
+ if isinstance(os_raw, str):
796
+ os_raw = [os_raw] # ✅ support single string value
797
+
798
+ mapped = [
799
+ str(zcc_param_map["os"].get(os_type.lower()))
800
+ for os_type in os_raw
801
+ if zcc_param_map["os"].get(os_type.lower())
802
+ ]
803
+ if not mapped:
804
+ raise ValueError("Invalid `os_types` provided.")
805
+ mapped_params["osTypes"] = ",".join(mapped)
806
+
807
+ # Normalize and map registration_types
808
+ if "registration_types" in query_params:
809
+ reg_raw = query_params["registration_types"]
810
+ if isinstance(reg_raw, str):
811
+ reg_raw = [reg_raw]
812
+
813
+ mapped = [
814
+ str(zcc_param_map["reg_type"].get(rt.lower()))
815
+ for rt in reg_raw
816
+ if zcc_param_map["reg_type"].get(rt.lower())
817
+ ]
818
+ if not mapped:
819
+ raise ValueError("Invalid `registration_types` provided.")
820
+ mapped_params["registrationTypes"] = ",".join(mapped)
821
+
822
+ # Drop user-friendly keys
823
+ query_params.pop("os_types", None)
824
+ query_params.pop("registration_types", None)
825
+
826
+ # Merge in mapped numeric params
827
+ query_params.update(mapped_params)
828
+ kwargs["query_params"] = query_params
829
+
830
+ return func(self, *args, **kwargs)
831
+ return wrapper
786
832
 
787
833
  # Maps ZCC numeric os_type and registration_type arguments to a human-readable string
788
834
  zcc_param_map = {
zscaler/zcc/admin_user.py CHANGED
@@ -17,7 +17,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
17
  from zscaler.api_client import APIClient
18
18
  from zscaler.request_executor import RequestExecutor
19
19
  from zscaler.utils import format_url
20
- from zscaler.zcc.models.admin_user import AdminUser
20
+ from zscaler.zcc.models.admin_user import AdminUser, AdminUserSyncInfo
21
21
  from zscaler.zcc.models.admin_roles import AdminRoles
22
22
 
23
23
 
@@ -92,9 +92,11 @@ class AdminUserAPI(APIClient):
92
92
  Examples:
93
93
  Prints all admins in the Client Connector Portal to the console:
94
94
 
95
- >>> for admin in zcc.admin_user.list_admin_users():
96
- ... print(admin)
97
-
95
+ >>> sync_info, _, error = client.zcc.admin_user.get_admin_user_sync_info()
96
+ >>> if error:
97
+ ... print(f"Error: {error}")
98
+ ... return
99
+ ... print(sync_info.as_dict())
98
100
  """
99
101
  http_method = "get".upper()
100
102
  api_url = format_url(
@@ -108,20 +110,19 @@ class AdminUserAPI(APIClient):
108
110
  headers = {}
109
111
 
110
112
  request, error = self._request_executor.create_request(http_method, api_url, body, headers)
111
-
112
113
  if error:
113
- return None
114
+ return None, None, error
114
115
 
115
116
  response, error = self._request_executor.execute(request)
116
117
  if error:
117
- return None
118
+ return None, response, error
118
119
 
119
120
  try:
120
- result = self.form_response_body(response.get_body())
121
+ result = AdminUserSyncInfo(self.form_response_body(response.get_body()))
121
122
  except Exception as error:
122
- return None
123
+ return None, response, error
123
124
 
124
- return result
125
+ return result, response, None
125
126
 
126
127
  def list_admin_roles(self, query_params=None) -> tuple:
127
128
  """
@@ -152,7 +153,6 @@ class AdminUserAPI(APIClient):
152
153
 
153
154
  query_params = query_params or {}
154
155
 
155
- # Prepare request body and headers
156
156
  body = {}
157
157
  headers = {}
158
158
 
@@ -198,7 +198,6 @@ class AdminUserAPI(APIClient):
198
198
  """
199
199
  )
200
200
 
201
- # Prepare request body and headers
202
201
  body = {}
203
202
  headers = {}
204
203
 
@@ -244,7 +243,6 @@ class AdminUserAPI(APIClient):
244
243
  """
245
244
  )
246
245
 
247
- # Prepare request body and headers
248
246
  body = {}
249
247
  headers = {}
250
248