diff --git a/hca/util/__init__.py b/hca/util/__init__.py index 6dab948e..b883433c 100755 --- a/hca/util/__init__.py +++ b/hca/util/__init__.py @@ -95,6 +95,7 @@ def special_cli_command(self, required_argument, optional_argument=""): from requests_oauthlib import OAuth2Session from urllib3.util import retry, timeout from jsonpointer import resolve_pointer +from datetime import datetime from .. import get_config, logger from .compat import USING_PYTHON2 @@ -176,6 +177,7 @@ class SwaggerClient(object): _authenticated_session = None _session = None _swagger_spec = None + _spec_valid_for_days = 7 _type_map = { "string": str, "number": float, @@ -238,7 +240,9 @@ def swagger_spec(self): else: swagger_filename = base64.urlsafe_b64encode(swagger_url.encode()).decode() + ".json" swagger_filename = os.path.join(self.config.user_config_dir, swagger_filename) - if not os.path.exists(swagger_filename): + is_cached = os.path.exists(swagger_filename) + if (not is_cached) or (is_cached and + self._get_days_since_last_modified(swagger_filename) >= self._spec_valid_for_days): try: os.makedirs(self.config.user_config_dir) except OSError as e: @@ -298,6 +302,11 @@ def login(self, access_token=""): token_type="Bearer") print("Storing access credentials") + def _get_days_since_last_modified(self, filename): + now = datetime.now() + last_modified = datetime.fromtimestamp(os.path.getmtime(filename)) + return (now - last_modified).days + def _get_oauth_token_from_service_account_credentials(self): scopes = ["https://www.googleapis.com/auth/userinfo.email"] assert 'GOOGLE_APPLICATION_CREDENTIALS' in os.environ diff --git a/test/test_dss_api_retry.py b/test/test_dss_api_retry.py index e9888317..1d463032 100644 --- a/test/test_dss_api_retry.py +++ b/test/test_dss_api_retry.py @@ -10,6 +10,7 @@ from requests import ConnectTimeout from urllib3 import Timeout +from datetime import datetime from hca.util import RetryPolicy @@ -61,10 +62,12 @@ def test_get_retry(self): client = hca.dss.DSSClient() file_uuid = str(uuid.uuid4()) creator_uid = client.config.get("creator_uid", 0) + version = datetime.utcnow().strftime("%Y-%m-%dT%H%M%S.%fZ") client.put_file._request( dict( uuid=file_uuid, + version=version, bundle_uuid=str(uuid.uuid4()), creator_uid=creator_uid, source_url=TestDssApiRetry.source_url, diff --git a/test/test_dss_swagger_spec.py b/test/test_dss_swagger_spec.py new file mode 100644 index 00000000..d79722bd --- /dev/null +++ b/test/test_dss_swagger_spec.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# coding: utf-8 + +import unittest +import requests +from hca.util.compat import USING_PYTHON2 + +import hca.dss + +if USING_PYTHON2: + import mock + from mock import mock_open +else: + from unittest import mock + from unittest.mock import mock_open + + +class TestDssSwaggerSpec(unittest.TestCase): + client = hca.dss.DSSClient() + swagger_url = "" + dummy_response = None + open_fn_name = "__builtin__.open" if USING_PYTHON2 else "builtins.open" + + def setUp(self): + self.client.__class__._swagger_spec = None + self.client._spec_valid_for_days = 1 + self.swagger_url = "test_swagger_url" + self.client._session = requests.Session() + self.client.config.__dict__.update({ + 'DSSClient': { + 'swagger_url': self.swagger_url + }, + 'swagger_filename': "test_filename" + }) + self.dummy_response = requests.models.Response() + self.dummy_response._content = b'{"swagger": ""}' + self.dummy_response.status_code = 200 + + def tearDown(self): + self.client.__class__._swagger_spec = None + + def test_get_swagger_spec_new(self): + with mock.patch('os.path.exists') as mock_exists, \ + mock.patch('hca.dss.SwaggerClient._get_days_since_last_modified') as mock_days_since_last_modified, \ + mock.patch('os.makedirs') as mock_makedirs, \ + mock.patch('requests.Session.get') as mock_get, \ + mock.patch('hca.dss.SwaggerClient.load_swagger_json'), \ + mock.patch(self.open_fn_name, mock_open()) as open_mock: + mock_exists.return_value = False + mock_days_since_last_modified.return_value = 0 + mock_get.return_value = self.dummy_response + + test = self.client.swagger_spec + self.assertTrue(mock_makedirs.called) + self.assertTrue(mock_get.calledWith(self.swagger_url)) + self.assertTrue(open_mock().write.calledWith(self.dummy_response._content)) + + def test_get_swagger_spec_cache_valid(self): + with mock.patch('os.path.exists') as mock_exists, \ + mock.patch('hca.dss.SwaggerClient._get_days_since_last_modified') as mock_days_since_last_modified, \ + mock.patch('os.makedirs') as mock_makedirs, \ + mock.patch('requests.Session.get') as mock_get, \ + mock.patch('hca.dss.SwaggerClient.load_swagger_json'), \ + mock.patch(self.open_fn_name, mock_open()) as open_mock: + mock_exists.return_value = True + mock_days_since_last_modified.return_value = 0 + mock_get.return_value = self.dummy_response + + test = self.client.swagger_spec + self.assertFalse(mock_makedirs.called) + self.assertFalse(mock_get.called) + self.assertFalse(open_mock().write.called) + + def test_get_swagger_spec_cache_expired(self): + with mock.patch('os.path.exists') as mock_exists, \ + mock.patch('hca.dss.SwaggerClient._get_days_since_last_modified') as mock_days_since_last_modified, \ + mock.patch('os.makedirs') as mock_makedirs, \ + mock.patch('requests.Session.get') as mock_get, \ + mock.patch('hca.dss.SwaggerClient.load_swagger_json'), \ + mock.patch(self.open_fn_name, mock_open()) as open_mock: + mock_exists.return_value = True + mock_days_since_last_modified.return_value = 2 + mock_get.return_value = self.dummy_response + + test = self.client.swagger_spec + self.assertTrue(mock_get.calledWith(self.swagger_url)) + self.assertTrue(open_mock().write.calledWith(self.dummy_response._content)) + + +if __name__ == '__main__': + unittest.main()