diff --git a/cuenca/http/client.py b/cuenca/http/client.py index 1750432e..5d2a596c 100644 --- a/cuenca/http/client.py +++ b/cuenca/http/client.py @@ -12,6 +12,7 @@ OptionalDict, ) from requests import Response +from requests.adapters import HTTPAdapter, Retry from ..exc import CuencaResponseException from ..jwt import Jwt @@ -27,17 +28,29 @@ class Session: headers: DictStrAny = {} basic_auth: Tuple[str, str] jwt_token: Optional[Jwt] = None + session: requests.Session + timeout: int = 30 + retries: int = 3 + backoff_factor: float = 0.4 def __init__(self): + self.session = requests.Session() self.headers = { 'X-Cuenca-Api-Version': API_VERSION, 'User-Agent': f'cuenca-python/{CLIENT_VERSION}', } - # basic auth api_key = os.getenv('CUENCA_API_KEY', '') api_secret = os.getenv('CUENCA_API_SECRET', '') self.basic_auth = (api_key, api_secret) + retry = Retry( + total=self.retries, + backoff_factor=self.backoff_factor, + read=self.retries, + ) + adapter = HTTPAdapter(max_retries=retry) + self.session.mount("https://", adapter) + print('Retry adapter mounted') @property def auth(self) -> Optional[Tuple[str, str]]: @@ -102,22 +115,20 @@ def request( data: OptionalDict = None, **kwargs, ) -> bytes: - resp = None - with requests.Session() as session: - session.headers = self.headers # type: ignore - if self.jwt_token: - if self.jwt_token.is_expired: - self.jwt_token = Jwt.create(self) - self.headers['X-Cuenca-Token'] = self.jwt_token.token - session.headers = self.headers # type: ignore - resp = session.request( # type: ignore - method=method, - url='https://' + self.host + urljoin('/', endpoint), - auth=self.auth, - json=json.loads(JSONEncoder().encode(data)), - params=params, - **kwargs, - ) + if self.jwt_token: + if self.jwt_token.is_expired: + self.jwt_token = Jwt.create(self) + self.session.headers['X-Cuenca-Token'] = self.jwt_token.token + + resp = self.session.request( + method=method, + url='https://' + self.host + urljoin('/', endpoint), + auth=self.auth, + json=json.loads(JSONEncoder().encode(data)), + params=params, + timeout=self.timeout, + **kwargs, + ) self._check_response(resp) return resp.content diff --git a/cuenca/version.py b/cuenca/version.py index 7f440002..b65481ad 100644 --- a/cuenca/version.py +++ b/cuenca/version.py @@ -1,3 +1,3 @@ -__version__ = '0.15.9' +__version__ = '0.15.10.dev0' CLIENT_VERSION = __version__ API_VERSION = '2020-03-19' diff --git a/tests/http/test_client.py b/tests/http/test_client.py index bbcc0a0e..e28680ff 100644 --- a/tests/http/test_client.py +++ b/tests/http/test_client.py @@ -1,6 +1,6 @@ import datetime as dt -from unittest.mock import patch - +from unittest.mock import patch, Mock +from requests.exceptions import Timeout import pytest from cuenca_validations.errors import ( NoPasswordFoundError, @@ -90,3 +90,14 @@ def test_no_password(): def test_no_session(): with pytest.raises(UserNotLoggedInError): Transfer.count() + + +def test_timeout_raises(): + session = Session() + session.configure( + api_key='USER_API_KEY', api_secret='USER_SECRET', sandbox=True + ) + with patch('requests.Session.request', side_effect=Timeout()) as mock_timeout: + with pytest.raises(Timeout): + Card.first(user_id='USER_ID', session=session) + assert mock_timeout.call_count == 3