diff --git a/CHANGELOG.md b/CHANGELOG.md index e205fcc..0dddfe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Version 0.6.1 - 2026-02-23 + +- Add exponential backoff to calls toward the auth endpoint + ## Version 0.6.0 - 2026-01-27 - Add update_validation method diff --git a/pyproject.toml b/pyproject.toml index d11bdba..84ffeb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cradl" -version = "0.6.0" +version = "0.6.1" description = "Python SDK for Cradl" authors = [{ name = "Cradl", email = "hello@cradl.ai" }] readme = "README.md" diff --git a/src/cradl/backoff.py b/src/cradl/backoff.py index 3b2913c..f490e14 100644 --- a/src/cradl/backoff.py +++ b/src/cradl/backoff.py @@ -2,6 +2,15 @@ import time from typing import Optional, Union, Type, Callable +import requests # type: ignore +from requests.exceptions import RequestException # type: ignore + + +def fatal_code(e: RequestException): + if isinstance(e.response, requests.Response) and isinstance(e.response.status_code, int): + return 400 <= e.response.status_code < 500 + raise e + def exponential_backoff( exceptions: Union[tuple[Type[Exception]], Type[Exception]], diff --git a/src/cradl/client.py b/src/cradl/client.py index dc13349..a449855 100644 --- a/src/cradl/client.py +++ b/src/cradl/client.py @@ -3,20 +3,18 @@ from base64 import b64encode from datetime import datetime from pathlib import Path - from typing import Callable, Dict, List, Optional, Sequence, Union from urllib.parse import urlparse, quote import requests # type: ignore from requests.exceptions import RequestException # type: ignore -from .credentials import Credentials, guess_credentials +from .backoff import exponential_backoff, fatal_code from .content import parse_content +from .credentials import Credentials, guess_credentials from .log import setup_logging -from .backoff import exponential_backoff from .response import decode_response, TooManyRequestsException, EmptyRequestError - logger = setup_logging(__name__) Content = Union[bytes, bytearray, str, Path, io.IOBase] Queryparam = Union[str, List[str]] @@ -35,12 +33,6 @@ def dictstrip(d): return {k: v for k, v in d.items() if v is not None} -def _fatal_code(e: RequestException): - if isinstance(e.response, requests.Response) and isinstance(e.response.status_code, int): - return 400 <= e.response.status_code < 500 - raise e - - class Client: """A low level client to invoke api methods from Cradl.""" def __init__(self, credentials: Optional[Credentials] = None, profile=None): @@ -49,7 +41,7 @@ def __init__(self, credentials: Optional[Credentials] = None, profile=None): self.credentials = credentials or guess_credentials(profile) @exponential_backoff(TooManyRequestsException, max_tries=4) - @exponential_backoff(RequestException, max_tries=3, giveup=_fatal_code) + @exponential_backoff(RequestException, max_tries=3, giveup=fatal_code) def _make_request( self, requests_fn: Callable, @@ -81,7 +73,7 @@ def _make_request( return decode_response(response) @exponential_backoff(TooManyRequestsException, max_tries=4) - @exponential_backoff(RequestException, max_tries=3, giveup=_fatal_code) + @exponential_backoff(RequestException, max_tries=3, giveup=fatal_code) def _make_fileserver_request( self, requests_fn: Callable, diff --git a/src/cradl/credentials.py b/src/cradl/credentials.py index 954dbb2..3313161 100644 --- a/src/cradl/credentials.py +++ b/src/cradl/credentials.py @@ -7,9 +7,11 @@ from typing import Optional, Tuple import requests # type: ignore +from requests.exceptions import RequestException # type: ignore +from .backoff import exponential_backoff, fatal_code from .log import setup_logging - +from .response import TooManyRequestsException, BadRequest logger = setup_logging(__name__) NULL_TOKEN = '', 0 @@ -82,6 +84,9 @@ def access_token(self) -> str: return access_token + # Backoff on BadRequest since Kinde seems to sometimes give bogus 400 responses + @exponential_backoff(exceptions=(TooManyRequestsException, BadRequest), max_tries=4) # type: ignore + @exponential_backoff(RequestException, max_tries=3, giveup=fatal_code) def _get_client_credentials(self) -> Tuple[str, int]: if any(endpoint in self.auth_endpoint for endpoint in ['auth.lucidtech.io', 'auth.cradl.ai', 'kinde.com']): data = {