From ac39ea400b21b7a8d949322550fab8cd4e591748 Mon Sep 17 00:00:00 2001 From: AlexViquez Date: Wed, 23 Aug 2023 12:45:00 -0600 Subject: [PATCH 1/4] new handle session --- cuenca/http/client.py | 46 +++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/cuenca/http/client.py b/cuenca/http/client.py index 1750432e..30f39abb 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,13 +28,18 @@ class Session: headers: DictStrAny = {} basic_auth: Tuple[str, str] jwt_token: Optional[Jwt] = None + session: requests.Session + timeout: int = 60 + retries: int = 3 + status_forcelist: Tuple[int, ...] = (401, 402) + 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', '') @@ -77,6 +83,14 @@ def configure( if session_token: self.headers['X-Cuenca-SessionId'] = session_token + retry = Retry( + total=self.retries, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + ) + adapter = HTTPAdapter(max_retries=retry) + self.session.mount("https://", adapter) + def get( self, endpoint: str, params: ClientRequestParams = None ) -> DictStrAny: @@ -102,22 +116,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 From d429bbf68f6d462026d6946c082e56841a4215d0 Mon Sep 17 00:00:00 2001 From: AlexViquez Date: Fri, 25 Aug 2023 12:48:27 -0600 Subject: [PATCH 2/4] remove status_forcelist --- cuenca/http/client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cuenca/http/client.py b/cuenca/http/client.py index 30f39abb..a0f7b3e5 100644 --- a/cuenca/http/client.py +++ b/cuenca/http/client.py @@ -29,9 +29,8 @@ class Session: basic_auth: Tuple[str, str] jwt_token: Optional[Jwt] = None session: requests.Session - timeout: int = 60 + timeout: int = 30 retries: int = 3 - status_forcelist: Tuple[int, ...] = (401, 402) backoff_factor: float = 0.4 def __init__(self): @@ -85,7 +84,6 @@ def configure( retry = Retry( total=self.retries, - status_forcelist=self.status_forcelist, backoff_factor=self.backoff_factor, ) adapter = HTTPAdapter(max_retries=retry) From bd175cf27a89289aab2dc935e0d5036831011698 Mon Sep 17 00:00:00 2001 From: AlexViquez Date: Fri, 25 Aug 2023 12:49:35 -0600 Subject: [PATCH 3/4] test new version --- cuenca/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' From e543b3d7ce5c51b71ff5f638426e7641ed4fe16a Mon Sep 17 00:00:00 2001 From: AlexViquez Date: Fri, 25 Aug 2023 13:43:54 -0600 Subject: [PATCH 4/4] test failed --- cuenca/http/client.py | 15 ++++++++------- tests/http/test_client.py | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cuenca/http/client.py b/cuenca/http/client.py index a0f7b3e5..5d2a596c 100644 --- a/cuenca/http/client.py +++ b/cuenca/http/client.py @@ -43,6 +43,14 @@ def __init__(self): 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]]: @@ -82,13 +90,6 @@ def configure( if session_token: self.headers['X-Cuenca-SessionId'] = session_token - retry = Retry( - total=self.retries, - backoff_factor=self.backoff_factor, - ) - adapter = HTTPAdapter(max_retries=retry) - self.session.mount("https://", adapter) - def get( self, endpoint: str, params: ClientRequestParams = None ) -> DictStrAny: 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