zscaler-sdk-python 0.10.3__tar.gz → 0.10.5__tar.gz

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 (100) hide show
  1. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/PKG-INFO +2 -2
  2. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/pyproject.toml +4 -4
  3. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/__init__.py +1 -1
  4. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/__init__.py +115 -1
  5. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/client.py +11 -6
  6. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/devices.py +3 -1
  7. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/__init__.py +195 -34
  8. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/client.py +1 -1
  9. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/cloud_apps.py +4 -2
  10. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/isolation_profile.py +1 -2
  11. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/labels.py +3 -5
  12. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/locations.py +12 -14
  13. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/pac_files.py +254 -50
  14. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/traffic.py +7 -7
  15. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/url_categories.py +77 -5
  16. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/users.py +3 -5
  17. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/workload_groups.py +2 -4
  18. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/zpa_gateway.py +7 -3
  19. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/policies.py +12 -12
  20. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/LICENSE.md +0 -0
  21. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/README.md +0 -0
  22. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/__init__.py +0 -0
  23. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/cache.py +0 -0
  24. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/no_op_cache.py +0 -0
  25. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/cache/zscaler_cache.py +0 -0
  26. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/constants.py +0 -0
  27. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/__init__.py +0 -0
  28. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/error.py +0 -0
  29. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/http_error.py +0 -0
  30. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/errors/zscaler_api_error.py +0 -0
  31. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/exceptions/__init__.py +0 -0
  32. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/exceptions/exceptions.py +0 -0
  33. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/logger.py +0 -0
  34. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/ratelimiter/__init__.py +0 -0
  35. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/ratelimiter/ratelimiter.py +0 -0
  36. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/user_agent.py +0 -0
  37. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/utils.py +0 -0
  38. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcc/secrets.py +0 -0
  39. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/__init__.py +0 -0
  40. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/activation.py +0 -0
  41. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/admin_and_role_management.py +0 -0
  42. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/client.py +0 -0
  43. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/ecgroups.py +0 -0
  44. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/locations.py +0 -0
  45. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zcon/provisioning.py +0 -0
  46. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/__init__.py +0 -0
  47. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/admin.py +0 -0
  48. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/alerts.py +0 -0
  49. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/apps.py +0 -0
  50. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/devices.py +0 -0
  51. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/filters.py +0 -0
  52. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/inventory.py +0 -0
  53. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/troubleshooting.py +0 -0
  54. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/users.py +0 -0
  55. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zdx/zdx_client.py +0 -0
  56. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/activate.py +0 -0
  57. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/admin_and_role_management.py +0 -0
  58. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/apptotal.py +0 -0
  59. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/audit_logs.py +0 -0
  60. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/authentication_settings.py +0 -0
  61. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/cloudappcontrol.py +0 -0
  62. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/device_management.py +0 -0
  63. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/dlp.py +0 -0
  64. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/errors.py +0 -0
  65. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/firewall.py +0 -0
  66. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/forwarding_control.py +0 -0
  67. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/sandbox.py +0 -0
  68. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/security.py +0 -0
  69. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/ssl_inspection.py +0 -0
  70. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/url_filtering.py +0 -0
  71. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zia/web_dlp.py +0 -0
  72. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/README.md +0 -0
  73. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/__init__.py +0 -0
  74. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/app_segments.py +0 -0
  75. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/app_segments_inspection.py +0 -0
  76. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/app_segments_pra.py +0 -0
  77. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/authdomains.py +0 -0
  78. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/certificates.py +0 -0
  79. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/client.py +0 -0
  80. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/cloud_connector_groups.py +0 -0
  81. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/connectors.py +0 -0
  82. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/emergency_access.py +0 -0
  83. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/errors.py +0 -0
  84. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/idp.py +0 -0
  85. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/inspection.py +0 -0
  86. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/isolation.py +0 -0
  87. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/lss.py +0 -0
  88. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/machine_groups.py +0 -0
  89. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/microtenants.py +0 -0
  90. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/posture_profiles.py +0 -0
  91. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/privileged_remote_access.py +0 -0
  92. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/provisioning.py +0 -0
  93. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/saml_attributes.py +0 -0
  94. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/scim_attributes.py +0 -0
  95. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/scim_groups.py +0 -0
  96. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/segment_groups.py +0 -0
  97. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/server_groups.py +0 -0
  98. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/servers.py +0 -0
  99. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/service_edges.py +0 -0
  100. {zscaler_sdk_python-0.10.3 → zscaler_sdk_python-0.10.5}/zscaler/zpa/trusted_networks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscaler-sdk-python
3
- Version: 0.10.3
3
+ Version: 0.10.5
4
4
  Summary: Official Python SDK for the Zscaler Products (Beta)
5
5
  Home-page: https://github.com/zscaler/zscaler-sdk-python
6
6
  License: MIT
@@ -22,7 +22,6 @@ Classifier: Topic :: Security
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: aenum ; extra == "dev"
25
- Requires-Dist: aiohttp (>=3.11.11)
26
25
  Requires-Dist: arrow
27
26
  Requires-Dist: black (>=24.3.0) ; extra == "dev"
28
27
  Requires-Dist: certifi (>=2024.2.2)
@@ -31,6 +30,7 @@ Requires-Dist: cryptography (>=43.0.0)
31
30
  Requires-Dist: flake8
32
31
  Requires-Dist: flatdict
33
32
  Requires-Dist: idna (>=3.10)
33
+ Requires-Dist: jinja2 (>=3.1.6)
34
34
  Requires-Dist: okta (>=2.9.7)
35
35
  Requires-Dist: pycryptodomex (>=3.20.0)
36
36
  Requires-Dist: pydash (>=8.0.3) ; extra == "dev"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "zscaler-sdk-python"
3
- version = "0.10.3"
3
+ version = "0.10.5"
4
4
  description = "Official Python SDK for the Zscaler Products (Beta)"
5
5
  authors = ["Zscaler, Inc. <devrel@zscaler.com>"]
6
6
  license = "MIT"
@@ -51,16 +51,16 @@ pytz = ">=2024.2"
51
51
  black = ">=24.3.0"
52
52
  cryptography = ">=43.0.0"
53
53
  okta = ">=2.9.7"
54
- aiohttp = ">=3.11.11"
54
+ jinja2 = ">=3.1.6"
55
55
 
56
56
  [tool.poetry.dev-dependencies]
57
57
  black = ">=24.3.0"
58
58
  pytest = ">=8.3.1"
59
- pytest-asyncio = "^0.23.8"
59
+ pytest-asyncio = "^0.25.3"
60
60
  pytest-mock = "*"
61
61
  pytest-recording = "^0.13.2"
62
62
  pytest-cov = "*"
63
- pyfakefs = ">=5.6.0"
63
+ pyfakefs = ">=5.7.4"
64
64
  isort = "*"
65
65
  wheel = ">=0.45.1"
66
66
  sphinx = "^7.4.7"
@@ -29,7 +29,7 @@ __license__ = "MIT"
29
29
  __contributors__ = [
30
30
  "William Guilherme",
31
31
  ]
32
- __version__ = "0.10.3"
32
+ __version__ = "0.10.5"
33
33
 
34
34
  from zscaler.zdx import ZDXClientHelper # noqa
35
35
  from zscaler.zia import ZIAClientHelper # noqa
@@ -5,7 +5,7 @@ import uuid
5
5
  import time
6
6
  import requests
7
7
  from datetime import datetime, timedelta
8
-
8
+ from box import Box, BoxList
9
9
  from zscaler import __version__
10
10
  from zscaler.user_agent import UserAgent
11
11
  from zscaler.utils import (
@@ -14,7 +14,9 @@ from zscaler.utils import (
14
14
  format_json_response,
15
15
  is_token_expired,
16
16
  retry_with_backoff,
17
+ convert_keys_to_snake
17
18
  )
19
+ from zscaler.ratelimiter.ratelimiter import RateLimiter
18
20
  from zscaler.logger import setup_logging
19
21
  from .devices import DevicesAPI
20
22
  from .secrets import SecretsAPI
@@ -74,6 +76,14 @@ class ZCCClientHelper(ZCCClient):
74
76
  self.url = f"https://api-mobile.{self._env_cloud}.net/papi/public/v1"
75
77
 
76
78
  self.user_agent = UserAgent().get_user_agent_string() # Ensure this returns a string
79
+ # Initialize rate limiter
80
+ # You may want to adjust these parameters as per your rate limit configuration
81
+ self.rate_limiter = RateLimiter(
82
+ get_limit=2, # Adjust as per actual limit
83
+ post_put_delete_limit=2, # Adjust as per actual limit
84
+ get_freq=2, # Adjust as per actual frequency (in seconds)
85
+ post_put_delete_freq=2, # Adjust as per actual frequency (in seconds)
86
+ )
77
87
  self.auth_token = None
78
88
  self.headers = {}
79
89
  self.refreshToken()
@@ -263,3 +273,107 @@ class ZCCClientHelper(ZCCClient):
263
273
  def secrets(self):
264
274
  """The interface object for the :ref:`ZCC Secrets interface <zcc-secrets>`."""
265
275
  return SecretsAPI(self)
276
+
277
+ def get_paginated_data(
278
+ self,
279
+ path=None,
280
+ expected_status_code=200,
281
+ page=None,
282
+ pagesize=None,
283
+ search=None,
284
+ ):
285
+ """
286
+ Fetches paginated data from the API based on specified parameters and handles pagination.
287
+
288
+ Args:
289
+ path (str): The API endpoint path to send requests to.
290
+ expected_status_code (int): The expected HTTP status code for a successful request.
291
+ page (int, optional): Specific page number to fetch (1-based).
292
+ pagesize (int, optional): Number of items per page, default is 100, max is 10,000.
293
+ search (str, optional): Search query to filter the results.
294
+
295
+ Returns:
296
+ tuple: A tuple containing:
297
+ - BoxList: A list of fetched items wrapped in a BoxList for easy access.
298
+ - str: An error message if any occurred during the data fetching process.
299
+ """
300
+ logger = logging.getLogger(__name__)
301
+
302
+ ERROR_MESSAGES = {
303
+ "UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
304
+ "EMPTY_RESULTS": "No results found for page {page}.",
305
+ }
306
+
307
+ # ✅ Ensure 'pageSize' is always converted to camelCase
308
+ params = {
309
+ "page": page if page is not None else 1, # Default is 1-based page
310
+ "pageSize": pagesize if pagesize is not None else 100, # Default is 100, max is 10,000
311
+ }
312
+
313
+ # Add optional filters to the params if provided
314
+ if search:
315
+ params["search"] = search
316
+
317
+ # If the user specifies a single page, fetch only that page
318
+ if page is not None:
319
+ response = self.send("GET", path=path, params=params)
320
+ if response.status_code != expected_status_code:
321
+ error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=params["page"])
322
+ logger.error(error_msg)
323
+ return BoxList([]), error_msg
324
+
325
+ response_data = response.json()
326
+ if not isinstance(response_data, list):
327
+ error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
328
+ logger.warn(error_msg)
329
+ return BoxList([]), error_msg
330
+
331
+ data = convert_keys_to_snake(response_data)
332
+ return BoxList(data), None
333
+
334
+ # If no page is specified, iterate through pages to fetch all items
335
+ ret_data = []
336
+
337
+ try:
338
+ while True:
339
+ should_wait, delay = self.rate_limiter.wait("GET")
340
+ if should_wait:
341
+ time.sleep(delay)
342
+
343
+ # Send the request to the API
344
+ response = self.send("GET", path=path, params=params)
345
+
346
+ # Check for unexpected status code
347
+ if response.status_code != expected_status_code:
348
+ error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
349
+ status_code=response.status_code, page=params["page"]
350
+ )
351
+ logger.error(error_msg)
352
+ return BoxList([]), error_msg
353
+
354
+ # Parse the response as a flat list of items
355
+ response_data = response.json()
356
+ if not isinstance(response_data, list):
357
+ error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
358
+ logger.warn(error_msg)
359
+ return BoxList([]), error_msg
360
+
361
+ data = convert_keys_to_snake(response_data)
362
+ ret_data.extend(data)
363
+
364
+ # Stop if fewer items than pageSize are returned (indicating last page)
365
+ if len(data) < params["pageSize"]:
366
+ break
367
+
368
+ # Move to the next page
369
+ params["page"] += 1
370
+
371
+ finally:
372
+ time.sleep(2) # Ensure a delay between requests regardless of outcome
373
+
374
+ if not ret_data:
375
+ error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
376
+ logger.warn(error_msg)
377
+ return BoxList([]), error_msg
378
+
379
+ return BoxList(ret_data), None
@@ -36,13 +36,18 @@ class ZCCClient:
36
36
  pass
37
37
 
38
38
  def get_paginated_data(
39
+ # self,
40
+ # path=None,
41
+ # params=None,
42
+ # search=None,
43
+ # search_field="name",
44
+ # page=None,
45
+ # pagesize=20,
39
46
  self,
40
- path=None,
41
- params=None,
42
- search=None,
43
- search_field="name",
44
- page=None,
45
- pagesize=20,
47
+ path: str = None,
48
+ data_key_name: str = None,
49
+ data_per_page: int = 500,
50
+ expected_status_code=200,
46
51
  ):
47
52
  """
48
53
  Send a GET request to the ZCC API to fetch all pages of a resources.
@@ -148,7 +148,9 @@ class DevicesAPI:
148
148
  else:
149
149
  raise ValueError("Invalid os_type specified. Check the Zscaler documentation for valid os_type options.")
150
150
 
151
- return self.rest.get("getDevices", **payload)
151
+ list, _ = self.rest.get_paginated_data(path="getDevices", **payload)
152
+ return list
153
+ # return self.rest.get("getDevices", **payload)
152
154
 
153
155
  def remove_devices(self, force: bool = False, **kwargs):
154
156
  """
@@ -169,11 +169,21 @@ class ZIAClientHelper(ZIAClient):
169
169
  return result.group(1)
170
170
 
171
171
  def is_session_expired(self):
172
+ """
173
+ Checks whether the current session is expired.
174
+
175
+ Returns:
176
+ bool: True if the session is expired or if the session details are missing.
177
+ """
172
178
  if self.auth_details is None:
173
179
  return True
180
+
174
181
  now = datetime.datetime.now()
175
- if self.auth_details["passwordExpiryTime"] > 0 and (self.session_refreshed - self.session_timeout_offset < now):
182
+
183
+ password_expiry_time = self.auth_details.get("passwordExpiryTime", -1)
184
+ if password_expiry_time > 0 and (self.session_refreshed - self.session_timeout_offset < now):
176
185
  return True
186
+
177
187
  return False
178
188
 
179
189
  @retry_with_backoff(MAX_RETRIES)
@@ -401,6 +411,145 @@ class ZIAClientHelper(ZIAClient):
401
411
  time.sleep(delay)
402
412
  return self.send("DELETE", path, json, params)
403
413
 
414
+ # def get_paginated_data(
415
+ # self,
416
+ # path=None,
417
+ # expected_status_code=200,
418
+ # page=None,
419
+ # pagesize=None,
420
+ # search=None,
421
+ # max_items=None, # Maximum number of items to retrieve across pages
422
+ # max_pages=None, # Maximum number of pages to retrieve
423
+ # type=None, # Specify type of VPN credentials (CN, IP, UFQDN, XAUTH)
424
+ # include_only_without_location=None, # Include only VPN credentials not associated with any location
425
+ # location_id=None, # VPN credentials for a specific location ID
426
+ # managed_by=None, # VPN credentials managed by a given partner
427
+ # prefix=None, # VPN credentials managed by a given partner
428
+ # ):
429
+ # """
430
+ # Fetches paginated data from the API based on specified parameters and handles pagination.
431
+
432
+ # Args:
433
+ # path (str): The API endpoint path to send requests to.
434
+ # expected_status_code (int): The expected HTTP status code for a successful request. Defaults to 200.
435
+ # page (int): Specific page number to fetch. Defaults to 1 if not provided.
436
+ # pagesize (int): Number of items per page, default is 100, with a maximum of 10000.
437
+ # search (str): Search query to filter the results.
438
+ # max_items (int): Maximum number of items to retrieve.
439
+ # max_pages (int): Maximum number of pages to fetch.
440
+ # type (str, optional): Type of VPN credentials (e.g., CN, IP, UFQDN, XAUTH).
441
+ # include_only_without_location (bool, optional): Filter to include only VPN credentials not associated with a location.
442
+ # location_id (int, optional): Retrieve VPN credentials for the specified location ID.
443
+ # managed_by (int, optional): Retrieve VPN credentials managed by the specified partner.
444
+ # prefix (int, optional): Retrieve VPN credentials managed by a given partner.
445
+
446
+ # Returns:
447
+ # tuple: A tuple containing:
448
+ # - BoxList: A list of fetched items wrapped in a BoxList for easy access.
449
+ # - str: An error message if any occurred during the data fetching process.
450
+ # """
451
+ # logger = logging.getLogger(__name__)
452
+
453
+ # ERROR_MESSAGES = {
454
+ # "UNEXPECTED_STATUS": "Unexpected status code {status_code} received for page {page}.",
455
+ # "EMPTY_RESULTS": "No results found for page {page}.",
456
+ # }
457
+
458
+ # # Initialize parameters
459
+ # params = {
460
+ # "page": page if page is not None else 1, # Start at page 1 if not specified
461
+ # "pageSize": pagesize if pagesize is not None else 100, # Allow any user-defined pagesize
462
+ # }
463
+
464
+ # # Add optional filters to the params if provided
465
+ # if search:
466
+ # params["search"] = search
467
+ # if type:
468
+ # params["type"] = type
469
+ # if include_only_without_location is not None:
470
+ # params["includeOnlyWithoutLocation"] = include_only_without_location
471
+ # if location_id:
472
+ # params["locationId"] = location_id
473
+ # if managed_by:
474
+ # params["managedBy"] = managed_by
475
+ # if prefix:
476
+ # params["prefix"] = prefix
477
+
478
+ # # If the user specifies a page, fetch only that page
479
+ # if page is not None:
480
+ # response = self.send("GET", path=path, params=params)
481
+ # if response.status_code != expected_status_code:
482
+ # error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=params["page"])
483
+ # logger.error(error_msg)
484
+ # return BoxList([]), error_msg
485
+
486
+ # response_data = response.json()
487
+ # if not isinstance(response_data, list):
488
+ # error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
489
+ # logger.warn(error_msg)
490
+ # return BoxList([]), error_msg
491
+
492
+ # data = convert_keys_to_snake(response_data)
493
+ # return BoxList(data), None
494
+
495
+ # # If no page is specified, iterate through pages to fetch all items
496
+ # ret_data = []
497
+ # total_collected = 0
498
+ # try:
499
+ # while True:
500
+ # should_wait, delay = self.rate_limiter.wait("GET")
501
+ # if should_wait:
502
+ # time.sleep(delay)
503
+
504
+ # # Send the request to the API
505
+ # response = self.send("GET", path=path, params=params)
506
+
507
+ # # Check for unexpected status code
508
+ # if response.status_code != expected_status_code:
509
+ # error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
510
+ # status_code=response.status_code, page=params["page"]
511
+ # )
512
+ # logger.error(error_msg)
513
+ # return BoxList([]), error_msg
514
+
515
+ # # Parse the response as a flat list of items
516
+ # response_data = response.json()
517
+ # if not isinstance(response_data, list):
518
+ # error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
519
+ # logger.warn(error_msg)
520
+ # return BoxList([]), error_msg
521
+
522
+ # data = convert_keys_to_snake(response_data)
523
+
524
+ # # Limit data collection based on max_items
525
+ # if max_items is not None:
526
+ # data = data[: max_items - total_collected] # Limit items on the current page
527
+ # ret_data.extend(data)
528
+ # total_collected += len(data)
529
+
530
+ # # Check if we've reached max_items or max_pages limits
531
+ # if (max_items is not None and total_collected >= max_items) or (
532
+ # max_pages is not None and params["page"] >= max_pages
533
+ # ):
534
+ # break
535
+
536
+ # # Stop if fewer items than pageSize are returned
537
+ # if len(data) < params["pageSize"]:
538
+ # break
539
+
540
+ # # Move to the next page
541
+ # params["page"] += 1
542
+
543
+ # finally:
544
+ # time.sleep(2) # Ensure a delay between requests regardless of outcome
545
+
546
+ # if not ret_data:
547
+ # error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
548
+ # logger.warn(error_msg)
549
+ # return BoxList([]), error_msg
550
+
551
+ # return BoxList(ret_data), None
552
+
404
553
  def get_paginated_data(
405
554
  self,
406
555
  path=None,
@@ -410,23 +559,27 @@ class ZIAClientHelper(ZIAClient):
410
559
  search=None,
411
560
  max_items=None, # Maximum number of items to retrieve across pages
412
561
  max_pages=None, # Maximum number of pages to retrieve
413
- type=None, # Specify type of VPN credentials (CN, IP, UFQDN, XAUTH)
414
- include_only_without_location=None, # Include only VPN credentials not associated with any location
415
- location_id=None, # VPN credentials for a specific location ID
416
- managed_by=None, # VPN credentials managed by a given partner
417
- prefix=None, # VPN credentials managed by a given partner
562
+ page_number=None, # New: Zero-based page number
563
+ limit=None, # New: Max number of items per page (default: 1000)
564
+ type=None,
565
+ include_only_without_location=None,
566
+ location_id=None,
567
+ managed_by=None,
568
+ prefix=None,
418
569
  ):
419
570
  """
420
571
  Fetches paginated data from the API based on specified parameters and handles pagination.
421
572
 
422
573
  Args:
423
574
  path (str): The API endpoint path to send requests to.
424
- expected_status_code (int): The expected HTTP status code for a successful request. Defaults to 200.
425
- page (int): Specific page number to fetch. Defaults to 1 if not provided.
426
- pagesize (int): Number of items per page, default is 100, with a maximum of 10000.
427
- search (str): Search query to filter the results.
428
- max_items (int): Maximum number of items to retrieve.
429
- max_pages (int): Maximum number of pages to fetch.
575
+ expected_status_code (int): The expected HTTP status code for a successful request.
576
+ page (int, optional): Specific page number to fetch (1-based).
577
+ pagesize (int, optional): Number of items per page, default is 100, max is 10,000.
578
+ page_number (int, optional): Specifies the page number (zero-based, starting at 0).
579
+ limit (int, optional): Specifies the maximum number of items retrieved per page (default 1000).
580
+ search (str, optional): Search query to filter the results.
581
+ max_items (int, optional): Maximum number of items to retrieve across pages.
582
+ max_pages (int, optional): Maximum number of pages to retrieve.
430
583
  type (str, optional): Type of VPN credentials (e.g., CN, IP, UFQDN, XAUTH).
431
584
  include_only_without_location (bool, optional): Filter to include only VPN credentials not associated with a location.
432
585
  location_id (int, optional): Retrieve VPN credentials for the specified location ID.
@@ -445,11 +598,18 @@ class ZIAClientHelper(ZIAClient):
445
598
  "EMPTY_RESULTS": "No results found for page {page}.",
446
599
  }
447
600
 
448
- # Initialize parameters
449
- params = {
450
- "page": page if page is not None else 1, # Start at page 1 if not specified
451
- "pageSize": pagesize if pagesize is not None else 100, # Allow any user-defined pagesize
452
- }
601
+ # Initialize parameters based on what the user has provided
602
+ params = {}
603
+
604
+ # Use new API pagination model (pageNumber, limit) if provided
605
+ if page_number is not None or limit is not None:
606
+ params["pageNumber"] = page_number if page_number is not None else 0 # Default is 0
607
+ params["limit"] = limit if limit is not None else 1000 # Default is 1000
608
+
609
+ # ✅ Otherwise, use existing pagination parameters (page, pageSize)
610
+ else:
611
+ params["page"] = page if page is not None else 1 # Default is 1-based page
612
+ params["pageSize"] = pagesize if pagesize is not None else 100 # Default is 100, max is 10,000
453
613
 
454
614
  # Add optional filters to the params if provided
455
615
  if search:
@@ -465,19 +625,17 @@ class ZIAClientHelper(ZIAClient):
465
625
  if prefix:
466
626
  params["prefix"] = prefix
467
627
 
468
- # If the user specifies a page, fetch only that page
469
- if page is not None:
628
+ # If the user specifies a single page, fetch only that page
629
+ if page is not None or page_number is not None:
470
630
  response = self.send("GET", path=path, params=params)
471
631
  if response.status_code != expected_status_code:
472
- error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
473
- status_code=response.status_code, page=params["page"]
474
- )
632
+ error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(status_code=response.status_code, page=params.get("page", params.get("pageNumber", 0)))
475
633
  logger.error(error_msg)
476
634
  return BoxList([]), error_msg
477
635
 
478
636
  response_data = response.json()
479
637
  if not isinstance(response_data, list):
480
- error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
638
+ error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params.get("page", params.get("pageNumber", 0)))
481
639
  logger.warn(error_msg)
482
640
  return BoxList([]), error_msg
483
641
 
@@ -487,6 +645,7 @@ class ZIAClientHelper(ZIAClient):
487
645
  # If no page is specified, iterate through pages to fetch all items
488
646
  ret_data = []
489
647
  total_collected = 0
648
+
490
649
  try:
491
650
  while True:
492
651
  should_wait, delay = self.rate_limiter.wait("GET")
@@ -499,7 +658,7 @@ class ZIAClientHelper(ZIAClient):
499
658
  # Check for unexpected status code
500
659
  if response.status_code != expected_status_code:
501
660
  error_msg = ERROR_MESSAGES["UNEXPECTED_STATUS"].format(
502
- status_code=response.status_code, page=params["page"]
661
+ status_code=response.status_code, page=params.get("page", params.get("pageNumber", 0))
503
662
  )
504
663
  logger.error(error_msg)
505
664
  return BoxList([]), error_msg
@@ -507,7 +666,7 @@ class ZIAClientHelper(ZIAClient):
507
666
  # Parse the response as a flat list of items
508
667
  response_data = response.json()
509
668
  if not isinstance(response_data, list):
510
- error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
669
+ error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params.get("page", params.get("pageNumber", 0)))
511
670
  logger.warn(error_msg)
512
671
  return BoxList([]), error_msg
513
672
 
@@ -521,28 +680,30 @@ class ZIAClientHelper(ZIAClient):
521
680
 
522
681
  # Check if we've reached max_items or max_pages limits
523
682
  if (max_items is not None and total_collected >= max_items) or (
524
- max_pages is not None and params["page"] >= max_pages
683
+ max_pages is not None and params.get("page", params.get("pageNumber", 0)) >= max_pages
525
684
  ):
526
685
  break
527
686
 
528
- # Stop if fewer items than pageSize are returned
529
- if len(data) < params["pageSize"]:
687
+ # Stop if fewer items than pageSize/limit are returned
688
+ if len(data) < params.get("pageSize", params.get("limit", 1000)):
530
689
  break
531
690
 
532
691
  # Move to the next page
533
- params["page"] += 1
692
+ if "page" in params:
693
+ params["page"] += 1
694
+ elif "pageNumber" in params:
695
+ params["pageNumber"] += 1
534
696
 
535
697
  finally:
536
698
  time.sleep(2) # Ensure a delay between requests regardless of outcome
537
699
 
538
700
  if not ret_data:
539
- error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params["page"])
701
+ error_msg = ERROR_MESSAGES["EMPTY_RESULTS"].format(page=params.get("page", params.get("pageNumber", 0)))
540
702
  logger.warn(error_msg)
541
703
  return BoxList([]), error_msg
542
704
 
543
705
  return BoxList(ret_data), None
544
706
 
545
-
546
707
  # def get_paginated_data(
547
708
  # self,
548
709
  # path=None,
@@ -597,7 +758,7 @@ class ZIAClientHelper(ZIAClient):
597
758
  # "page": page if page is not None else 1, # Start at page 1 if not specified
598
759
  # "pagesize": max(100, min(pagesize or 100, 10000)), # Ensure pagesize is within API limits
599
760
  # }
600
-
761
+
601
762
  # # Add optional filters to the params if provided
602
763
  # if search:
603
764
  # params["search"] = search
@@ -717,7 +878,7 @@ class ZIAClientHelper(ZIAClient):
717
878
 
718
879
  """
719
880
  return CloudAppsAPI(self)
720
-
881
+
721
882
  @property
722
883
  def dlp(self):
723
884
  """
@@ -774,7 +935,7 @@ class ZIAClientHelper(ZIAClient):
774
935
 
775
936
  """
776
937
  return CloudSandboxAPI(self)
777
-
938
+
778
939
  @property
779
940
  def security(self):
780
941
  """
@@ -34,7 +34,7 @@ class ZIAClient:
34
34
  self,
35
35
  path: str = None,
36
36
  data_key_name: str = None,
37
- data_per_page: int = 500,
37
+ data_per_page: int = 1000,
38
38
  expected_status_code=200,
39
39
  ):
40
40
  """
@@ -367,7 +367,7 @@ class CloudAppsAPI:
367
367
 
368
368
  return self.rest.post(f"shadowIT/applications/{entity}/exportCsv", json=payload).text
369
369
 
370
- def list_apps(self):
370
+ def list_apps(self, **kwargs):
371
371
  """
372
372
  List all predefined and custom cloud applications by name and id.
373
373
 
@@ -382,7 +382,9 @@ class CloudAppsAPI:
382
382
  print(app.name)
383
383
 
384
384
  """
385
- return self.rest.get("cloudApplications/lite")
385
+ list, _ = self.rest.get_paginated_data(path="cloudApplications/lite", **kwargs)
386
+ return list
387
+ # return self.rest.get("cloudApplications/lite")
386
388
 
387
389
  def list_custom_tags(self):
388
390
  """
@@ -17,7 +17,6 @@
17
17
 
18
18
  from box import BoxList
19
19
 
20
- from zscaler.utils import snake_to_camel
21
20
  from zscaler.zia.client import ZIAClient
22
21
 
23
22
 
@@ -25,7 +24,7 @@ class IsolationProfileAPI:
25
24
  def __init__(self, client: ZIAClient):
26
25
  self.rest = client
27
26
 
28
- def list_isolation_profiles(self, **kwargs) -> BoxList:
27
+ def list_isolation_profiles(self) -> BoxList:
29
28
  """
30
29
  Returns a list of all profiles in the Isolation Profile field for URL Filtering rules and Cloud App Control rules.
31
30
 
@@ -31,11 +31,9 @@ class RuleLabelsAPI:
31
31
  Returns the list of ZIA Rule Labels.
32
32
 
33
33
  Keyword Args:
34
- **max_items (int, optional):
35
- The maximum number of items to request before stopping iteration.
36
- **max_pages (int, optional):
37
- The maximum number of pages to request before stopping iteration.
38
- **page_size (int, optional):
34
+ **page (int, optional):
35
+ Specifies the page offset.
36
+ **pagesize (int, optional):
39
37
  Specifies the page size. The default size is 100, but the maximum size is 1000.
40
38
 
41
39
  Returns: