diff --git a/docs/source/adapter_class.rst b/docs/source/adapter_class.rst index 6c66d68..d787bc9 100644 --- a/docs/source/adapter_class.rst +++ b/docs/source/adapter_class.rst @@ -110,4 +110,4 @@ defaults to ```False``` in the client initialization. .. method:: refresh_authentication(self, api_params, *args, **kwargs): -Should run refresh authentication logic. Make sure you update `api_params` dictionary with the new token. +Should do refresh authentication logic. Make sure you update `api_params` dictionary with the new token. If it successfully refreshs token it should return a truthy value that will be stored for later access in the executor class in the ``refresh_data`` attribute. If the refresh logic fails, return a falsy value. The original request will be retried only if a truthy is returned. diff --git a/docs/source/buildingawrapper.rst b/docs/source/buildingawrapper.rst index 1cc6cf3..229ba3e 100644 --- a/docs/source/buildingawrapper.rst +++ b/docs/source/buildingawrapper.rst @@ -120,6 +120,8 @@ You can implement the ```refresh_authentication``` and ```is_authentication_expi ```is_authentication_expired``` receives an error object from the request method (it contains the server response and HTTP Status code). You can use it to decide if a request failed because of the token. This method should return ```True``` if the authentication is expired or ```False``` otherwise (default behavior). +``refresh_authentication`` receives ``api_params`` and should perform the token refresh protocol. If it is successfull it should return a truthy value (the original request will then be automatically tried). If the token refresh fails, it should return a falsy value (and the the original request wont be retried). + Once these methods are implemented, the client can be instantiated with ```refresh_token_by_default=True``` (or pass ```refresh_token=True``` in HTTP calls) and ```refresh_authentication``` will be called automatically. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index c128496..7c2acd2 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -2,6 +2,12 @@ Changelog ========= +1.3 +=== +- ``refresh_authentication`` should return data about the refresh token process +- If a falsy value is returned by ``refresh_authentication`` the request wont be retried automatically +- Data returned by ``refresh_authentication`` is stored in the tapioca class and can be accessed in the executor via the attribute ``refresh_data`` + 1.2.3 ====== - ``refresh_token_by_default`` introduced to prevent passing ``refresh_token`` on every request. diff --git a/docs/source/features.rst b/docs/source/features.rst index 811ec2e..90ac357 100644 --- a/docs/source/features.rst +++ b/docs/source/features.rst @@ -39,7 +39,7 @@ Everytive you ``call`` in ``TapiocaClient`` you will get a ``TapiocaClientExecut Accessing raw response data --------------------------- -To access the raw data contained in the executor, use the ``data`` **attribute**. To access the raw response, use the ``response`` **attribute**. To access the status code of the response, use the ``status_code`` **attribute**. +To access the raw data contained in the executor, use the ``data`` **attribute**. To access the raw response, use the ``response`` **attribute**. To access the status code of the response, use the ``status_code`` **attribute**. If during the request the ``Auth refreshing`` process was executed, the returned value from it will be accessible in the ``refresh_data`` **attribute**. **TODO: add examples** diff --git a/tapioca/adapters.py b/tapioca/adapters.py index eaf7aaf..88ab9f2 100644 --- a/tapioca/adapters.py +++ b/tapioca/adapters.py @@ -52,7 +52,6 @@ def get_request_kwargs(self, api_params, *args, **kwargs): return kwargs def process_response(self, response): - if 500 <= response.status_code < 600: raise ResponseProcessException(ServerError, None) diff --git a/tapioca/tapioca.py b/tapioca/tapioca.py index c4849a1..56cc05e 100755 --- a/tapioca/tapioca.py +++ b/tapioca/tapioca.py @@ -29,7 +29,7 @@ class TapiocaClient(object): def __init__(self, api, data=None, response=None, request_kwargs=None, api_params=None, resource=None, refresh_token_by_default=False, - *args, **kwargs): + refresh_data=None, *args, **kwargs): self._api = api self._data = data self._response = response @@ -37,6 +37,7 @@ def __init__(self, api, data=None, response=None, request_kwargs=None, self._request_kwargs = request_kwargs self._resource = resource self._refresh_token_default = refresh_token_by_default + self._refresh_data = refresh_data def _instatiate_api(self): serializer_class = None @@ -51,6 +52,7 @@ def _wrap_in_tapioca(self, data, *args, **kwargs): api_params=self._api_params, request_kwargs=request_kwargs, refresh_token_by_default=self._refresh_token_default, + refresh_data=self._refresh_data, *args, **kwargs) def _wrap_in_tapioca_executor(self, data, *args, **kwargs): @@ -59,6 +61,7 @@ def _wrap_in_tapioca_executor(self, data, *args, **kwargs): api_params=self._api_params, request_kwargs=request_kwargs, refresh_token_by_default=self._refresh_token_default, + refresh_data=self._refresh_data, *args, **kwargs) def _get_doc(self): @@ -206,6 +209,10 @@ def response(self): def status_code(self): return self.response.status_code + @property + def refresh_data(self): + return self._refresh_data + def _make_request(self, request_method, refresh_token=None, *args, **kwargs): if 'url' not in kwargs: kwargs['url'] = self._data @@ -224,10 +231,18 @@ def _make_request(self, request_method, refresh_token=None, *args, **kwargs): should_refresh_token = (refresh_token is not False and self._refresh_token_default) - if should_refresh_token and self._api.is_authentication_expired(tapioca_exception): - self._api.refresh_authentication(self._api_params) - return self._make_request(request_method, *args, **kwargs) - else: + auth_expired = self._api.is_authentication_expired(tapioca_exception) + + propagate_exception = True + + if should_refresh_token and auth_expired: + self._refresh_data = self._api.refresh_authentication(self._api_params) + if self._refresh_data: + propagate_exception = False + return self._make_request(request_method, + refresh_token=False, *args, **kwargs) + + if propagate_exception: raise tapioca_exception return self._wrap_in_tapioca(data, response=response, diff --git a/tests/client.py b/tests/client.py index 33850b4..163dd81 100644 --- a/tests/client.py +++ b/tests/client.py @@ -67,7 +67,18 @@ def is_authentication_expired(self, exception, *args, **kwargs): return exception.status_code == 401 def refresh_authentication(self, api_params, *args, **kwargs): - api_params['token'] = 'new_token' + new_token = 'new_token' + api_params['token'] = new_token + return new_token TokenRefreshClient = generate_wrapper_from_adapter(TokenRefreshClientAdapter) + + +class FailTokenRefreshClientAdapter(TokenRefreshClientAdapter): + + def refresh_authentication(self, api_params, *args, **kwargs): + return None + + +FailTokenRefreshClient = generate_wrapper_from_adapter(FailTokenRefreshClientAdapter) diff --git a/tests/test_tapioca.py b/tests/test_tapioca.py index 5261fa7..947f619 100755 --- a/tests/test_tapioca.py +++ b/tests/test_tapioca.py @@ -9,7 +9,7 @@ from tapioca.tapioca import TapiocaClient from tapioca.exceptions import ClientError -from tests.client import TesterClient, TokenRefreshClient +from tests.client import TesterClient, TokenRefreshClient, FailTokenRefreshClient class TestTapiocaClient(unittest.TestCase): @@ -486,3 +486,48 @@ def request_callback(request): # refresh_authentication method should be able to update api_params self.assertEqual(response._api_params['token'], 'new_token') + + @responses.activate + def test_raises_error_if_refresh_authentication_method_returns_falsy_value(self): + client = FailTokenRefreshClient(token='token', refresh_token_by_default=True) + + self.first_call = True + + def request_callback(request): + if self.first_call: + self.first_call = False + return (401, {}, '') + else: + self.first_call = None + return (201, {}, '') + + responses.add_callback( + responses.POST, client.test().data, + callback=request_callback, + content_type='application/json', + ) + + with self.assertRaises(ClientError): + client.test().post() + + @responses.activate + def test_stores_refresh_authentication_method_response_for_further_access(self): + self.first_call = True + + def request_callback(request): + if self.first_call: + self.first_call = False + return (401, {}, '') + else: + self.first_call = None + return (201, {}, '') + + responses.add_callback( + responses.POST, self.wrapper.test().data, + callback=request_callback, + content_type='application/json', + ) + + response = self.wrapper.test().post() + + self.assertEqual(response().refresh_data, 'new_token')