Skip to content

Commit

Permalink
Merge pull request #133 from vintasoftware/refresh
Browse files Browse the repository at this point in the history
Token refresh process
  • Loading branch information
filipeximenes committed Jan 19, 2017
2 parents 275090f + 50be03b commit 7832485
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/source/adapter_class.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions docs/source/buildingawrapper.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 6 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down
1 change: 0 additions & 1 deletion tapioca/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
25 changes: 20 additions & 5 deletions tapioca/tapioca.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ 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
self._api_params = api_params or {}
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
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
13 changes: 12 additions & 1 deletion tests/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
47 changes: 46 additions & 1 deletion tests/test_tapioca.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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')

0 comments on commit 7832485

Please sign in to comment.