syneto_api 0.2.43__tar.gz → 0.2.45__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: syneto-api
3
- Version: 0.2.43
3
+ Version: 0.2.45
4
4
  Summary: Syneto Client API library
5
5
  Home-page: https://github.com/SynetoNet/syneto-api
6
6
  License: Proprietary
@@ -11,13 +11,13 @@ Classifier: License :: Other/Proprietary License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.7
13
13
  Classifier: Programming Language :: Python :: 3.8
14
- Classifier: Programming Language :: Python :: 3.9
15
14
  Requires-Dist: aiodns (>=3,<4)
16
15
  Requires-Dist: aiohttp (>=3.7,<4.0)
17
16
  Requires-Dist: cchardet (>=2.1,<3.0)
18
17
  Requires-Dist: inflection (>=0.5,<0.6)
19
18
  Requires-Dist: python-dotenv (>=0.16,<0.17)
20
19
  Requires-Dist: requests[secure] (>=2.25,<3.0)
20
+ Requires-Dist: tenacity (>=8.0.1,<9.0.0)
21
21
  Project-URL: Repository, https://github.com/SynetoNet/syneto-api
22
22
  Description-Content-Type: text/markdown
23
23
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "syneto_api"
3
- version = "0.2.43"
3
+ version = "0.2.45"
4
4
  description = "Syneto Client API library"
5
5
  authors = ["Your Name <you@example.com>"]
6
6
  license = "Proprietary"
@@ -16,6 +16,7 @@ inflection = "^0.5"
16
16
  aiohttp = "^3.7"
17
17
  aiodns = "^3"
18
18
  cchardet = "^2.1"
19
+ tenacity = "^8.0.1"
19
20
 
20
21
  [tool.poetry.dev-dependencies]
21
22
  pytest = "^6.2.2"
@@ -16,11 +16,12 @@ install_requires = \
16
16
  'cchardet>=2.1,<3.0',
17
17
  'inflection>=0.5,<0.6',
18
18
  'python-dotenv>=0.16,<0.17',
19
- 'requests[secure]>=2.25,<3.0']
19
+ 'requests[secure]>=2.25,<3.0',
20
+ 'tenacity>=8.0.1,<9.0.0']
20
21
 
21
22
  setup_kwargs = {
22
23
  'name': 'syneto-api',
23
- 'version': '0.2.43',
24
+ 'version': '0.2.45',
24
25
  'description': 'Syneto Client API library',
25
26
  'long_description': '# Syneto API\n\nSyneto Client API library: authentication, storage, virtualization and protection\n\n# Installation\n\n```\n$ pip install syneto-api\n```\n\n# Basic Usage\n\n```\nfrom syneto_api import Authentication, Virtualization, Storage, Protection\n\nauth_api = Authentication(url_base="https://syneto-instance-ip-address/api/auth", insecure_ssl=True)\nresponse = auth_api.login(username="admin", password="admin")\njwt = response[\'jwt\']\n\nvirt_api = Virtualization(url_base="https://syneto-instance-ip-address/api/virtualization", insecure_ssl=True)\nvirt_api.set_auth_jwt(jwt)\nprint(virt_api.get_vms())\n\nstorage_api = Storage(url_base="https://syneto-instance-ip-address/api/storage", insecure_ssl=True)\nstorage_api.set_auth_jwt(jwt)\nprint(storage_api.get_pools())\n```\n\n# Environment Variables\n\nFor conveninence, the base urls for the api endpoints are also accepted as environment variables, please see below.\n\n```\nAUTH_SERVICE=https://syneto-instance-ip-address/api/auth\nVIRTUALIZATION_SERVICE=https://syneto-instance-ip-address/api/virtualization\nSTORAGE_SERVICE=https://syneto-instance-ip-address/api/storage\nPROTECTION_SERVICE=https://syneto-instance-ip-address/api/protection\n```\n\nIf you are using self-signed SSL certificates, set the following env. so that the http request library does not perform ssl verification. \n\n```\nALLOW_INSECURE_SSL=True\n```\n',
26
27
  'author': 'Your Name',
@@ -6,6 +6,8 @@ import urllib.parse
6
6
  import logging
7
7
  import traceback
8
8
  import urllib.parse
9
+ import concurrent.futures as futures
10
+ from tenacity import Retrying, AsyncRetrying, RetryError, stop_after_delay
9
11
 
10
12
  from .settings import get_env_flag
11
13
  from .keyring import KeyRing
@@ -102,17 +104,25 @@ class APIClientBase:
102
104
  ):
103
105
  try:
104
106
  logger.debug(f"Calling {method} {url} with aiohttp lib")
105
- # loop = asyncio.get_event_loop()
106
- # return loop.run_until_complete(self._async_request(method, url, body, success_codes, error_subject))
107
- return await asyncio.wait_for(
108
- self._unsafe_async_request(method, url, body, success_codes, error_subject, on_response_callback),
109
- timeout=self.timeout,
110
- )
107
+ async for attempt in AsyncRetrying(stop=stop_after_delay(self.timeout), reraise=True):
108
+ with attempt:
109
+ return await asyncio.wait_for(
110
+ self._unsafe_async_request(
111
+ method, url, body, success_codes, error_subject, on_response_callback
112
+ ),
113
+ timeout=self.timeout,
114
+ )
111
115
 
112
116
  except APIException as e:
113
117
  raise
114
118
 
115
- except (aiohttp.ServerTimeoutError, requests.Timeout, asyncio.TimeoutError) as e:
119
+ except (
120
+ aiohttp.ServerTimeoutError,
121
+ requests.Timeout,
122
+ asyncio.TimeoutError,
123
+ futures.TimeoutError,
124
+ RetryError,
125
+ ) as e:
116
126
  logger.warning(f"Failed {method} request to {url}, network timeout: {str(e)}")
117
127
  error = self._append_url_if_not_local_service("network timeout")
118
128
  raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
@@ -184,12 +194,16 @@ class APIClientBase:
184
194
  ):
185
195
  try:
186
196
  logger.debug(f"Calling {method} {url} with requests lib")
187
- return self._unsafe_sync_request(method, url, body, success_codes, error_subject, on_response_callback)
197
+ for attempt in Retrying(stop=stop_after_delay(self.timeout), reraise=True):
198
+ with attempt:
199
+ return self._unsafe_sync_request(
200
+ method, url, body, success_codes, error_subject, on_response_callback
201
+ )
188
202
 
189
203
  except APIException as e:
190
204
  raise
191
205
 
192
- except (aiohttp.ServerTimeoutError, requests.Timeout) as e:
206
+ except (aiohttp.ServerTimeoutError, requests.Timeout, futures.TimeoutError, RetryError) as e:
193
207
  logger.warning(f"Failed {method} request to {url}, network timeout: {str(e)}")
194
208
  error = self._append_url_if_not_local_service("network timeout")
195
209
  raise APIException(url=url, message=f"{error_subject} has failed ({error})", request_body=body) from e
@@ -225,7 +239,7 @@ class APIClientBase:
225
239
  if body is not None:
226
240
  method_args["json"] = body
227
241
 
228
- response = method_func(url, timeout=60, **method_args)
242
+ response = method_func(url, timeout=self.timeout, **method_args)
229
243
  is_json = "json" in response.headers.get("content-type", "").lower()
230
244
  if response.status_code in success_codes:
231
245
  logger.debug(f"Received {response.status_code} from {method} {url}")
@@ -255,16 +269,19 @@ class APIClientBase:
255
269
  def _append_url_if_not_local_service(self, message):
256
270
  if self._is_local_service():
257
271
  return message
272
+
273
+ hostname = self._get_hostname()
274
+ if not hostname:
275
+ return f"{message}: <host not specified>"
258
276
  else:
259
- hostname = self._get_hostname()
260
- return f"{message}: {hostname}"
277
+ return f"{message}: {hostname.strip()}"
261
278
 
262
279
  def _get_hostname(self):
263
280
  parsed_uri = urllib.parse.urlparse(self.url_base)
264
281
  hostname = parsed_uri.netloc
265
282
  if ":" in hostname:
266
283
  hostname, port = parsed_uri.netloc.split(":")[:2]
267
- return hostname
284
+ return hostname.strip() if hostname else hostname
268
285
 
269
286
  def _get_method_func(self, method):
270
287
  requests_methods = {
@@ -6,6 +6,7 @@ from .api_client import APIClientBase
6
6
  class Central(APIClientBase):
7
7
  def __init__(self, url_base=None, **kwargs):
8
8
  super().__init__(url_base or os.environ.get("CENTRAL_SERVICE", ""), **kwargs)
9
+ self._force_json = False
9
10
 
10
11
  def activate_product(
11
12
  self, email: str, password: str, activation_key: Optional[str]
File without changes