zscaler-sdk-python 1.0.0__py2.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 (75) hide show
  1. zscaler/__init__.py +34 -0
  2. zscaler/cache/__init__.py +0 -0
  3. zscaler/cache/cache.py +105 -0
  4. zscaler/cache/no_op_cache.py +68 -0
  5. zscaler/cache/zscaler_cache.py +161 -0
  6. zscaler/constants.py +26 -0
  7. zscaler/errors/__init__.py +0 -0
  8. zscaler/errors/error.py +10 -0
  9. zscaler/errors/http_error.py +20 -0
  10. zscaler/errors/zscaler_api_error.py +24 -0
  11. zscaler/exceptions/__init__.py +1 -0
  12. zscaler/exceptions/exceptions.py +101 -0
  13. zscaler/logger.py +57 -0
  14. zscaler/ratelimiter/__init__.py +0 -0
  15. zscaler/ratelimiter/ratelimiter.py +39 -0
  16. zscaler/user_agent.py +23 -0
  17. zscaler/utils.py +577 -0
  18. zscaler/zia/__init__.py +657 -0
  19. zscaler/zia/activate.py +52 -0
  20. zscaler/zia/admin_and_role_management.py +344 -0
  21. zscaler/zia/apptotal.py +71 -0
  22. zscaler/zia/audit_logs.py +95 -0
  23. zscaler/zia/authentication_settings.py +98 -0
  24. zscaler/zia/client.py +88 -0
  25. zscaler/zia/cloud_apps.py +406 -0
  26. zscaler/zia/device_management.py +90 -0
  27. zscaler/zia/dlp.py +784 -0
  28. zscaler/zia/errors.py +37 -0
  29. zscaler/zia/firewall.py +1104 -0
  30. zscaler/zia/forwarding_control.py +271 -0
  31. zscaler/zia/isolation_profile.py +83 -0
  32. zscaler/zia/labels.py +180 -0
  33. zscaler/zia/locations.py +661 -0
  34. zscaler/zia/sandbox.py +180 -0
  35. zscaler/zia/security.py +236 -0
  36. zscaler/zia/ssl_inspection.py +175 -0
  37. zscaler/zia/traffic.py +853 -0
  38. zscaler/zia/url_categories.py +442 -0
  39. zscaler/zia/url_filtering.py +310 -0
  40. zscaler/zia/users.py +386 -0
  41. zscaler/zia/web_dlp.py +295 -0
  42. zscaler/zia/workload_groups.py +58 -0
  43. zscaler/zia/zpa_gateway.py +187 -0
  44. zscaler/zpa/__init__.py +683 -0
  45. zscaler/zpa/app_segments.py +331 -0
  46. zscaler/zpa/app_segments_inspection.py +311 -0
  47. zscaler/zpa/app_segments_pra.py +310 -0
  48. zscaler/zpa/certificates.py +234 -0
  49. zscaler/zpa/client.py +113 -0
  50. zscaler/zpa/cloud_connector_groups.py +75 -0
  51. zscaler/zpa/connectors.py +518 -0
  52. zscaler/zpa/emergency_access.py +178 -0
  53. zscaler/zpa/errors.py +37 -0
  54. zscaler/zpa/idp.py +83 -0
  55. zscaler/zpa/inspection.py +1012 -0
  56. zscaler/zpa/isolation_profile.py +85 -0
  57. zscaler/zpa/lss.py +568 -0
  58. zscaler/zpa/machine_groups.py +79 -0
  59. zscaler/zpa/policies.py +848 -0
  60. zscaler/zpa/posture_profiles.py +122 -0
  61. zscaler/zpa/privileged_remote_access.py +862 -0
  62. zscaler/zpa/provisioning.py +271 -0
  63. zscaler/zpa/saml_attributes.py +100 -0
  64. zscaler/zpa/scim_attributes.py +117 -0
  65. zscaler/zpa/scim_groups.py +146 -0
  66. zscaler/zpa/segment_groups.py +191 -0
  67. zscaler/zpa/server_groups.py +217 -0
  68. zscaler/zpa/servers.py +202 -0
  69. zscaler/zpa/service_edges.py +404 -0
  70. zscaler/zpa/trusted_networks.py +127 -0
  71. zscaler_sdk_python-1.0.0.dist-info/LICENSE.md +21 -0
  72. zscaler_sdk_python-1.0.0.dist-info/METADATA +59 -0
  73. zscaler_sdk_python-1.0.0.dist-info/RECORD +75 -0
  74. zscaler_sdk_python-1.0.0.dist-info/WHEEL +6 -0
  75. zscaler_sdk_python-1.0.0.dist-info/top_level.txt +1 -0
zscaler/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright (c) 2023, Zscaler Inc.
4
+ #
5
+ # Permission to use, copy, modify, and/or distribute this software for any
6
+ # purpose with or without fee is hereby granted, provided that the above
7
+ # copyright notice and this permission notice appear in all copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+
17
+ """Zscaler SDK for Python
18
+
19
+ Zscaler SDK Python is an SDK that provides a uniform and easy-to-use interface for each of the Zscaler product APIs.
20
+
21
+ Documentation available at https://zscaler-sdk-python.readthedocs.io
22
+
23
+ """
24
+
25
+ __author__ = "Zscaler Inc"
26
+ __email__ = "devrel@zscaler.com"
27
+ __license__ = "MIT"
28
+ __contributors__ = [
29
+ "William Guilherme",
30
+ ]
31
+ __version__ = "1.0.0"
32
+
33
+ from zscaler.zia import ZIAClientHelper # noqa
34
+ from zscaler.zpa import ZPAClientHelper # noqa
File without changes
zscaler/cache/cache.py ADDED
@@ -0,0 +1,105 @@
1
+ from urllib.parse import parse_qs, urlencode, urlparse
2
+
3
+
4
+ class Cache:
5
+ """
6
+ This is the ABSTRACT class that defines a Cache object for the ZPA Client
7
+ """
8
+
9
+ def __init__(self):
10
+ pass
11
+
12
+ def get(self, key):
13
+ """
14
+ A method which retrieves the desired value from the cache.
15
+
16
+ Arguments:
17
+ key {str} -- The key used to find the desired value
18
+
19
+ Raises:
20
+ NotImplementedError: If the subclass inheriting this class
21
+ has not implemented this function
22
+ """
23
+ raise NotImplementedError
24
+
25
+ def contains(self, key):
26
+ """
27
+ A method which checks if the cache contains the desired value.
28
+
29
+ Arguments:
30
+ key {str} -- The key used to check the desired value
31
+
32
+ Raises:
33
+ NotImplementedError: If the subclass inheriting this class
34
+ has not implemented this function
35
+ """
36
+ raise NotImplementedError
37
+
38
+ def add(self, key, value):
39
+ """
40
+ A method which adds a key-value pair to the cache.
41
+
42
+ Arguments:
43
+ key {str} -- The key used to identify the entry.
44
+ value {[type]} -- The value in the pair
45
+
46
+ Raises:
47
+ NotImplementedError: If the subclass inheriting this class
48
+ has not implemented this function
49
+ """
50
+ raise NotImplementedError
51
+
52
+ def delete(self, key):
53
+ """
54
+ A method which deletes a key-value pair from the cache.
55
+
56
+ Arguments:
57
+ key {str} -- The key used to identify the entry
58
+
59
+ Raises:
60
+ NotImplementedError: If the subclass inheriting this class
61
+ has not implemented this function
62
+ """
63
+ raise NotImplementedError
64
+
65
+ def clear(self):
66
+ """
67
+ A method used to empty the cache.
68
+
69
+ Raises:
70
+ NotImplementedError: If the subclass inheriting this class
71
+ has not implemented this function
72
+ """
73
+ raise NotImplementedError
74
+
75
+ def create_key(self, request, params):
76
+ """
77
+ A method used to create a unique key for an entry in the cache.
78
+ Used with URLs that requests fire at.
79
+
80
+ Arguments:
81
+ request {str} -- The key to use to produce a unique key
82
+
83
+ Returns:
84
+ str -- Unique key based on the input URL without query parameters
85
+ """
86
+ # Validate URL and return URL string without query parameters
87
+ # Parse the original URL
88
+ url_object = urlparse(request)
89
+
90
+ # Extract the query parameters from the URL
91
+ original_query_params = parse_qs(url_object.query)
92
+
93
+ # Update the query parameters with the provided `params` dictionary
94
+ if params is not None and len(params) > 0:
95
+ original_query_params.update(params)
96
+
97
+ # Create a new query string with the updated parameters
98
+ updated_query_string = urlencode(original_query_params, doseq=True)
99
+
100
+ # Combine the netloc, path, and the updated query string to form the new URL
101
+ base_url = f"{url_object.netloc}{url_object.path}"
102
+ if updated_query_string != "":
103
+ base_url = f"{base_url}?{updated_query_string}"
104
+
105
+ return base_url
@@ -0,0 +1,68 @@
1
+ import logging
2
+
3
+ from zscaler.cache.cache import Cache
4
+
5
+ # Setting up the logger
6
+ logging.basicConfig(
7
+ level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
8
+ )
9
+
10
+
11
+ class NoOpCache(Cache):
12
+ """
13
+ This is a disabled Cache Class where no operations occur
14
+ in the cache.
15
+ Implementing the zscaler.cache.cache.Cache abstract class.
16
+ """
17
+
18
+ def __init__(self):
19
+ super()
20
+
21
+ def get(self, key):
22
+ """
23
+ Nothing is returned.
24
+
25
+ Arguments:
26
+ key {str} -- Key to look for
27
+
28
+ Returns:
29
+ None -- No op cache doesn't contain any data to return
30
+ """
31
+ logging.debug("Serving from cache.")
32
+ return None
33
+
34
+ def contains(self, key):
35
+ """
36
+ False is returned
37
+
38
+ Arguments:
39
+ key {str} -- Key to look for
40
+
41
+ Returns:
42
+ False -- No data to return so key can never be in cache
43
+ """
44
+ return False
45
+
46
+ def add(self, key, value):
47
+ """
48
+ This is a void method.
49
+
50
+ Arguments:
51
+ key {str} -- Key in pair
52
+ value {str} -- Val in pair
53
+ """
54
+ logging.warning("Saving to cache.")
55
+
56
+ def delete(self, key):
57
+ """This is a void method. No need to delete anything not contained.
58
+
59
+ Arguments:
60
+ key {str} -- Key to delete
61
+ """
62
+ pass
63
+
64
+ def clear(self):
65
+ """
66
+ This is a void method. No need to clear when nothing's stored.
67
+ """
68
+ pass
@@ -0,0 +1,161 @@
1
+ import logging
2
+ import time
3
+ from urllib.parse import urlparse
4
+
5
+ from zscaler.cache.cache import Cache
6
+
7
+ logger = logging.getLogger("zscaler-sdk-python")
8
+
9
+
10
+ class ZscalerCache(Cache):
11
+ """
12
+ This is a base class implementing a Cache using TTL and TTI.
13
+ Implementing the zscaler.cache.cache.Cache abstract class.
14
+ """
15
+
16
+ def __init__(self, ttl, tti):
17
+ """
18
+ Constructor.
19
+
20
+ Arguments:
21
+ ttl {float} -- Time to Live: for cache entries
22
+ tti {float} -- Time to Idle: for cache entries
23
+ """
24
+ super() # Inherit from parent class
25
+ self._store = {} # key -> {value, TTI, TTL}
26
+ self._time_to_live = ttl
27
+ self._time_to_idle = tti
28
+
29
+ def get(self, key):
30
+ """
31
+ Retrieves value from cache using key
32
+
33
+ Arguments:
34
+ key {str} -- Desired key
35
+
36
+ Returns:
37
+ str -- Corresponding value to given key
38
+ None -- Unable to find value for this key
39
+ """
40
+ # Get current time
41
+ now = self._get_current_time()
42
+ # Check if key is in cache and valid
43
+ if self.contains(key):
44
+ entry = self._store[key]
45
+ # Reset TTI
46
+ entry["tti"] = now + self._time_to_idle
47
+ # Return desired value and update cache
48
+ self._clean_cache()
49
+ logger.info(f'Got value from cache for key "{key}".')
50
+ logger.debug(f'Cached value for key {key}: {entry["value"]}')
51
+ return entry["value"]
52
+
53
+ # Return None if key isn't in cache and update cache
54
+ self._clean_cache()
55
+ return None
56
+
57
+ def contains(self, key):
58
+ """
59
+ Returns existence of key in cache, as boolean
60
+
61
+ Arguments:
62
+ key {str} -- Desired key
63
+
64
+ Returns:
65
+ bool -- Existence of key in cache
66
+ """
67
+ return key in self._store and self._is_valid_entry(self._store[key])
68
+
69
+ def add(self, key: str, value: tuple):
70
+ """
71
+ Adds a key-value pair to the cache.
72
+
73
+ Arguments:
74
+ key {str} -- Key in pair
75
+ value {tuple} -- Tuple of response and response body
76
+ """
77
+ if type(key) == str and (type(value) != list or type(value[1]) != list):
78
+ # Get current time
79
+ now = self._get_current_time()
80
+
81
+ # Add new entry to cache with timers
82
+ self._store[key] = {
83
+ "value": value,
84
+ "tti": now + self._time_to_idle,
85
+ "ttl": now + self._time_to_live,
86
+ }
87
+ logger.info(f'Added to cache value for key "{key}".')
88
+ logger.debug(f"Cached value for key {key}: {value}.")
89
+ # Update cache
90
+ self._clean_cache()
91
+
92
+ def delete(self, key):
93
+ """
94
+ Delete a key-value pair from the cache.
95
+
96
+ Arguments:
97
+ key {str} -- Desired key
98
+ """
99
+ logger.info(f'Removing value from cache for key "{key}".')
100
+ # Make sure key is in cache
101
+ if key in self._store:
102
+ # Delete entry
103
+ del self._store[key]
104
+ logger.info(f'Removed value from cache for key "{key}".')
105
+ url_object = urlparse(key)
106
+ base_url = f"{url_object.netloc}{url_object.path}"
107
+ for other_key in self._store.keys():
108
+ other_url_object = urlparse(other_key)
109
+ other_base_url = f"{other_url_object.netloc}{other_url_object.path}"
110
+ if not self._is_valid_entry(
111
+ self._store[other_key]
112
+ ) and other_base_url.startswith(base_url):
113
+ del self._store[other_key]
114
+ logger.info(f'Removed also value from cache for key "{other_key}".')
115
+
116
+ def clear(self):
117
+ """
118
+ Clear the cache.
119
+ """
120
+ self._store.clear()
121
+ logger.info("Cleared the cache.")
122
+
123
+ def _clean_cache(self):
124
+ """
125
+ Updates cache by removing expired entries at time of call
126
+ """
127
+ expired = []
128
+ # Check every entry
129
+ for key in self._store.keys():
130
+ # If not valid, delete
131
+ if not self._is_valid_entry(self._store[key]):
132
+ expired.append(key)
133
+ # Delete keys
134
+ for expired_key in expired:
135
+ self.delete(expired_key)
136
+
137
+ def _is_valid_entry(self, entry):
138
+ """
139
+ Determines if a given cache entry is not expired.
140
+
141
+ Args:
142
+ entry (dict): An entry from the cache composed of value,
143
+ TTI, and TTL
144
+
145
+ Returns:
146
+ bool: Boolean value representing if entry is expired
147
+ """
148
+ # Get Current time
149
+ now = self._get_current_time()
150
+ # Check timers and compare against current time
151
+ timers = [entry["tti"], entry["ttl"]]
152
+ return not any(timer <= now for timer in timers)
153
+
154
+ def _get_current_time(self):
155
+ """
156
+ Helper function to get current time
157
+
158
+ Returns:
159
+ float: value representing the number of seconds since the epoch
160
+ """
161
+ return time.time()
zscaler/constants.py ADDED
@@ -0,0 +1,26 @@
1
+ import os
2
+
3
+ ZPA_BASE_URLS = {
4
+ "PRODUCTION": "https://config.private.zscaler.com",
5
+ "ZPATWO": "https://config.zpatwo.net",
6
+ "BETA": "https://config.zpabeta.net",
7
+ "GOV": "https://config.zpagov.net",
8
+ "GOVUS": "https://config.zpagov.us",
9
+ "PREVIEW": "https://config.zpapreview.net",
10
+ "QA": "https://config.qa.zpath.net",
11
+ "QA2": "https://pdx2-zpa-config.qa2.zpath.net",
12
+ }
13
+
14
+ RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
15
+ MAX_RETRIES = 5
16
+ BACKOFF_FACTOR = 1
17
+ BACKOFF_BASE_DURATION = 2
18
+
19
+ DATETIME_FORMAT = "%a, %d %b %Y %H:%M:%S %Z"
20
+
21
+ EPOCH_YEAR = 1970
22
+ EPOCH_MONTH = 1
23
+ EPOCH_DAY = 1
24
+
25
+ _GLOBAL_YAML_PATH = os.path.join(os.path.expanduser("~"), ".zpa", "zpa.yaml")
26
+ _LOCAL_YAML_PATH = os.path.join(os.getcwd(), "zpa.yaml")
File without changes
@@ -0,0 +1,10 @@
1
+ class Error:
2
+ """
3
+ Base Error Class
4
+ """
5
+
6
+ def __init__(self):
7
+ self.message = ""
8
+
9
+ def __repr__(self):
10
+ return str({"message": self.message})
@@ -0,0 +1,20 @@
1
+ import json
2
+
3
+ from zscaler.errors.error import Error
4
+
5
+
6
+ class HTTPError(Error):
7
+ def __init__(self, url, response_details, response_body):
8
+ self.status = response_details.status
9
+ self.url = url
10
+ self.response_headers = response_details.headers
11
+ self.stack = ""
12
+ self.message = f"HTTP {self.status} {response_body}"
13
+
14
+
15
+ class ZscalerAPIError(Error):
16
+ def __init__(self, url, response, response_body):
17
+ self.status = response.status_code
18
+ self.url = url
19
+ self.response_body = json.dumps(response_body)
20
+ self.message = f"ZSCALER HTTP {url} {self.status} {self.response_body}"
@@ -0,0 +1,24 @@
1
+ from zscaler.errors.error import Error
2
+
3
+
4
+ # ZPA API Errors
5
+ class ZPAAPIError(Error):
6
+ def __init__(self, url, response_details, response_body):
7
+ self.status = response_details.status
8
+ self.error_id = response_body.get("id", "")
9
+ self.reason = response_body.get("reason", "")
10
+ self.params = response_body.get("params", [])
11
+
12
+ params_string = ", ".join(self.params)
13
+
14
+ self.url = url
15
+ self.headers = response_details.headers
16
+ self.stack = ""
17
+
18
+ self.message = (
19
+ f"ZPA HTTP {self.status} {self.error_id} "
20
+ f"{self.reason}\nParameters: {params_string}"
21
+ )
22
+
23
+
24
+ # ZIA API Errors
@@ -0,0 +1 @@
1
+ from .exceptions import HTTPException, ZpaAPIException # noqa
@@ -0,0 +1,101 @@
1
+ import json
2
+
3
+
4
+ # Zscaler Base Exceptions
5
+ class ZscalerBaseException(Exception):
6
+ def __init__(self, url, response, response_body):
7
+ self.status = response.status_code
8
+ self.url = url
9
+ self.response_body = json.dumps(response_body)
10
+ self.message = f"ZSCALER HTTP {url} {self.status} {self.response_body}"
11
+
12
+ def __repr__(self):
13
+ return str({"message": self.message})
14
+
15
+ def __str__(self):
16
+ return self.message
17
+
18
+
19
+ class HTTPException(ZscalerBaseException):
20
+ pass
21
+
22
+
23
+ class ZscalerAPIException(ZscalerBaseException):
24
+ pass
25
+
26
+
27
+ # Zscaler Private Access specific exceptions (Potential Future Use)
28
+ class ZpaBaseException(Exception):
29
+ pass
30
+
31
+
32
+ class ZpaAPIException(ZpaBaseException):
33
+ pass
34
+
35
+
36
+ class RateLimitExceededError(Exception):
37
+ """Raised when the API rate limit is exceeded."""
38
+
39
+ pass
40
+
41
+
42
+ class RetryLimitExceededError(Exception):
43
+ """Raised when the maximum number of retries is exceeded."""
44
+
45
+ pass
46
+
47
+
48
+ class CacheError(Exception):
49
+ """Raised for errors related to caching operations."""
50
+
51
+ pass
52
+
53
+
54
+ class BadRequestError(Exception):
55
+ """Raised when the API responds with a 400 status code."""
56
+
57
+ pass
58
+
59
+
60
+ class UnauthorizedError(Exception):
61
+ pass
62
+
63
+
64
+ class ForbiddenError(Exception):
65
+ pass
66
+
67
+
68
+ class NotFoundError(Exception):
69
+ pass
70
+
71
+
72
+ class APIClientError(Exception):
73
+ """General exception related to the API client operations."""
74
+
75
+ pass
76
+
77
+
78
+ class InvalidCloudEnvironmentError(Exception):
79
+ """Raised when an unrecognized cloud environment is specified."""
80
+
81
+ def __init__(self, cloud: str):
82
+ self.cloud = cloud
83
+ super().__init__(f"Unrecognized cloud environment: {self.cloud}")
84
+
85
+
86
+ class TokenExpirationError(Exception):
87
+ """Raised when the authentication token has expired."""
88
+
89
+ pass
90
+
91
+
92
+ class TokenRefreshError(Exception):
93
+ """Raised when there's an issue refreshing the authentication token."""
94
+
95
+ pass
96
+
97
+
98
+ class HeaderUpdateError(Exception):
99
+ """Raised if there's a problem updating the session headers."""
100
+
101
+ pass
zscaler/logger.py ADDED
@@ -0,0 +1,57 @@
1
+ import logging
2
+ import os
3
+ from http.client import HTTPConnection
4
+
5
+ LOG_FORMAT = "%(asctime)s - %(name)s - %(module)s - %(levelname)s - %(message)s"
6
+
7
+
8
+ def setup_logging(logger_name="zscaler-sdk-python"):
9
+ """
10
+ Set up logging with specified level and logger name.
11
+ Log level is controlled via ZSCALER_SDK_VERBOSE environment variable.
12
+ Logging can be enabled/disabled via ZSCALER_SDK_LOG environment variable.
13
+
14
+ Parameters:
15
+ - logger_name (str, optional): Logger name. Defaults to "zscaler-sdk-python".
16
+ """
17
+ logging_enabled = os.getenv("ZSCALER_SDK_LOG", "false").lower() == "true"
18
+
19
+ if not logging_enabled:
20
+ # If logging is not enabled, set up a null handler
21
+ logging.disable(logging.INFO)
22
+ return
23
+
24
+ verbose = os.getenv("ZSCALER_SDK_VERBOSE", "false").lower() == "true"
25
+ log_level = logging.DEBUG if verbose else logging.INFO
26
+ HTTPConnection.debuglevel = 0
27
+ # Create a logger with the specified name
28
+ logger = logging.getLogger(logger_name)
29
+ default_logger = logging.getLogger()
30
+
31
+ # If the logger already has handlers, remove them to avoid duplicate logging
32
+ for handler in logger.handlers[:]:
33
+ logger.removeHandler(handler)
34
+
35
+ for handler in default_logger.handlers[:]:
36
+ default_logger.removeHandler(handler)
37
+
38
+ # Set log level
39
+ logger.setLevel(log_level)
40
+ default_logger.setLevel(log_level)
41
+ logging.basicConfig(level=log_level)
42
+
43
+ # Create a stream handler with the specified level and formatter
44
+ stream_handler = logging.StreamHandler()
45
+ stream_handler.setLevel(log_level)
46
+ log_formatter = logging.Formatter(LOG_FORMAT)
47
+ stream_handler.setFormatter(log_formatter)
48
+
49
+ # Add the handler to the logger
50
+ logger.addHandler(stream_handler)
51
+
52
+ # Option: Add FileHandler if you want logs to be written to a file.
53
+ if os.getenv("LOG_TO_FILE", "false").lower() == "true":
54
+ file_handler = logging.FileHandler(os.getenv("LOG_FILE_PATH", "sdk.log"))
55
+ file_handler.setLevel(log_level)
56
+ file_handler.setFormatter(log_formatter)
57
+ logger.addHandler(file_handler)
File without changes
@@ -0,0 +1,39 @@
1
+ import threading
2
+ import time
3
+
4
+
5
+ class RateLimiter:
6
+ def __init__(
7
+ self, get_limit, post_put_delete_limit, get_freq, post_put_delete_freq
8
+ ):
9
+ self.lock = threading.Lock()
10
+ self.get_requests = []
11
+ self.post_put_delete_requests = []
12
+ self.get_limit = get_limit
13
+ self.post_put_delete_limit = post_put_delete_limit
14
+ self.get_freq = get_freq
15
+ self.post_put_delete_freq = post_put_delete_freq
16
+
17
+ def wait(self, method):
18
+ with self.lock:
19
+ now = time.time()
20
+
21
+ if method == "GET":
22
+ if len(self.get_requests) >= self.get_limit:
23
+ oldest_request = self.get_requests[0]
24
+ if now - oldest_request < self.get_freq:
25
+ d = self.get_freq - (now - oldest_request)
26
+ return True, d
27
+ self.get_requests.pop(0)
28
+ self.get_requests.append(now)
29
+
30
+ elif method in ["POST", "PUT", "DELETE"]:
31
+ if len(self.post_put_delete_requests) >= self.post_put_delete_limit:
32
+ oldest_request = self.post_put_delete_requests[0]
33
+ if now - oldest_request < self.post_put_delete_freq:
34
+ d = self.post_put_delete_freq - (now - oldest_request)
35
+ return True, d
36
+ self.post_put_delete_requests.pop(0)
37
+ self.post_put_delete_requests.append(now)
38
+
39
+ return False, 0
zscaler/user_agent.py ADDED
@@ -0,0 +1,23 @@
1
+ import platform
2
+
3
+ from zscaler import __version__ as VERSION
4
+
5
+
6
+ class UserAgent:
7
+ SDK_NAME = "zscaler-sdk-python"
8
+ PYTHON = "python"
9
+
10
+ def __init__(self, user_agent_extra=None):
11
+ python_version = platform.python_version()
12
+ os_name = platform.system()
13
+ os_version = platform.release()
14
+ self._user_agent_string = (
15
+ f"{UserAgent.SDK_NAME}/{VERSION} "
16
+ f"{UserAgent.PYTHON}/{python_version} "
17
+ f"{os_name}/{os_version}"
18
+ )
19
+ if user_agent_extra:
20
+ self._user_agent_string += f" {user_agent_extra}"
21
+
22
+ def get_user_agent_string(self):
23
+ return self._user_agent_string