diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1baf246..784e97c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,8 +1,8 @@ -msal>=1.18.0 +msal>=1.24.1 msal-extensions>=1.0.0 -PyYAML>=5.4 -setuptools>=49.2.1 +PyYAML>=6.0.1 +setuptools>=68.0.0 pyjwt>=2.4.0 -httpx>=0.25.0 -tenacity>=8.2.3 -azure-identity>=1.14.0 +httpx>=0.24.1 +tenacity>=8.2.2 +azure-identity>=1.13.0 diff --git a/src/sumo/wrapper/_blob_client.py b/src/sumo/wrapper/_blob_client.py index 8dd3273..672b612 100644 --- a/src/sumo/wrapper/_blob_client.py +++ b/src/sumo/wrapper/_blob_client.py @@ -1,13 +1,37 @@ import httpx -from ._decorators import raise_for_status, http_retry, raise_for_status_async +from ._decorators import ( + is_retryable_exception, + is_retryable_status_code, + raise_for_status, + raise_for_status_async, + return_last_value, +) +from tenacity import ( + retry, + retry_if_exception, + retry_if_result, + wait_exponential, + wait_random_exponential, + stop_after_attempt, +) class BlobClient: """Upload blobs to blob store using pre-authorized URLs""" @raise_for_status - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) def upload_blob(self, blob: bytes, url: str): """Upload a blob. @@ -26,7 +50,17 @@ def upload_blob(self, blob: bytes, url: str): return response @raise_for_status_async - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) async def upload_blob_async(self, blob: bytes, url: str): """Upload a blob async. diff --git a/src/sumo/wrapper/_decorators.py b/src/sumo/wrapper/_decorators.py index a2c52cc..2c11451 100644 --- a/src/sumo/wrapper/_decorators.py +++ b/src/sumo/wrapper/_decorators.py @@ -50,6 +50,10 @@ def is_retryable_status_code(response): return response.status_code in [502, 503, 504] +def return_last_value(retry_state): + return retry_state.outcome.result() + + def http_retry(func): return tn.retry( func, diff --git a/src/sumo/wrapper/sumo_client.py b/src/sumo/wrapper/sumo_client.py index 19c271b..4640abd 100644 --- a/src/sumo/wrapper/sumo_client.py +++ b/src/sumo/wrapper/sumo_client.py @@ -9,7 +9,21 @@ from ._auth_provider import get_auth_provider from .config import APP_REGISTRATION, TENANT_ID, AUTHORITY_HOST_URI -from ._decorators import raise_for_status, http_retry, raise_for_status_async +from ._decorators import ( + is_retryable_exception, + is_retryable_status_code, + raise_for_status, + raise_for_status_async, + return_last_value, +) +from tenacity import ( + retry, + retry_if_exception, + retry_if_result, + wait_exponential, + wait_random_exponential, + stop_after_attempt, +) logger = logging.getLogger("sumo.wrapper") @@ -109,7 +123,17 @@ def blob_client(self) -> BlobClient: return self._blob_client @raise_for_status - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) def get(self, path: str, params: dict = None) -> dict: """Performs a GET-request to the Sumo API. @@ -156,7 +180,17 @@ def get(self, path: str, params: dict = None) -> dict: return response @raise_for_status - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) def post( self, path: str, @@ -229,7 +263,17 @@ def post( return response @raise_for_status - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) def put( self, path: str, blob: bytes = None, json: dict = None ) -> httpx.Response: @@ -274,7 +318,17 @@ def put( return response @raise_for_status - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) def delete(self, path: str, params: dict = None) -> dict: """Performs a DELETE-request to the Sumo API. @@ -329,7 +383,17 @@ def getLogger(self, name): return logger @raise_for_status_async - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) async def get_async(self, path: str, params: dict = None): """Performs an async GET-request to the Sumo API. @@ -375,7 +439,17 @@ async def get_async(self, path: str, params: dict = None): return response @raise_for_status_async - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) async def post_async( self, path: str, @@ -451,7 +525,17 @@ async def post_async( return response @raise_for_status_async - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) async def put_async( self, path: str, blob: bytes = None, json: dict = None ) -> httpx.Response: @@ -497,7 +581,17 @@ async def put_async( return response @raise_for_status_async - @http_retry + @retry( + stop=stop_after_attempt(6), + retry=( + retry_if_exception(is_retryable_exception) + | retry_if_result(is_retryable_status_code) + ), + wait=wait_exponential(multiplier=0.5) + + wait_random_exponential(multiplier=0.5), + reraise=True, + retry_error_callback=return_last_value, + ) async def delete_async(self, path: str, params: dict = None) -> dict: """Performs an async DELETE-request to the Sumo API.