Commit b9caabcf authored by Thomas Matthews's avatar Thomas Matthews

Merge branch 'api_singleton' into 'development'

Api singleton

See merge request !55
parents 4459638f 53930617
Pipeline #7541 passed with stage
in 6 minutes and 32 seconds
......@@ -22,35 +22,77 @@ from Exceptions.SampleSheetError import SampleSheetError
from Validation.offlineValidation import validate_URL_form
from API.pubsub import send_message
class ApiCalls(object):
def __init__(self, client_id, client_secret,
base_URL, username, password, max_wait_time=20):
_instance = None
def __new__(cls, client_id, client_secret, base_URL, username, password, max_wait_time=20):
"""
Create OAuth2Session and store it
Overriding __new__ to implement a singleton
This is done instead of a decorator so that mocking still works for class.
If the instance has not been created yet, or the passed in arguments are different, create a new instance,
and drop the old (if existing) instance
If the instance already exists and is valid, return the instance
arguments:
client_id -- client_id for creating access token.
client_secret -- client_secret for creating access token.
base_URL -- url of the IRIDA server
username -- username for server
password -- password for given username
arguments:
client_id -- client_id for creating access token.
client_secret -- client_secret for creating access token.
base_URL -- url of the IRIDA server
username -- username for server
password -- password for given username
max_wait_time -- timeout (seconds), default=20
return ApiCalls object
"""
self.client_id = client_id
self.client_secret = client_secret
self.base_URL = base_URL
self.username = username
self.password = password
self.max_wait_time = max_wait_time
if not ApiCalls._instance or ApiCalls._instance.parameters_are_different(
client_id, client_secret, base_URL,username, password, max_wait_time):
self._session_lock = threading.Lock()
self._session_set_externally = False
self.create_session()
self.cached_projects = None
self.cached_samples = {}
# Create a new instance of the API
ApiCalls._instance = object.__new__(cls)
# initialize API instance variables
ApiCalls._instance.client_id = client_id
ApiCalls._instance.client_secret = client_secret
ApiCalls._instance.base_URL = base_URL
ApiCalls._instance.username = username
ApiCalls._instance.password = password
ApiCalls._instance.max_wait_time = max_wait_time
# initialize API object
ApiCalls._instance._session_lock = threading.Lock()
ApiCalls._instance._session_set_externally = False
ApiCalls._instance.create_session()
ApiCalls._instance.cached_projects = None
ApiCalls._instance.cached_samples = {}
return ApiCalls._instance
@classmethod
def close(cls):
"""
Close the current session by setting the current instance to None so
the next call with re-initialize the session
"""
ApiCalls._instance = None
def parameters_are_different(self, client_id, client_secret, base_URL, username, password, max_wait_time):
"""
Compare the current instance variables with a new set of variables
"""
result = (self.client_id != client_id or
self.client_secret != client_secret or
self.base_URL != base_URL or
self.username != username or
self.password != password or
self.max_wait_time != max_wait_time)
if result:
logging.warning("ApiCalls session instance parameters are different, "
"a new session instance will be created")
return result
@property
def session(self):
......@@ -94,11 +136,8 @@ class ApiCalls(object):
access_token = self.get_access_token(oauth_service)
self._session = oauth_service.get_session(access_token)
if self.validate_URL_existence(self.base_URL, use_session=True) is\
False:
raise Exception("Cannot create session. " +
"Verify your credentials are correct.")
if self.validate_URL_existence(self.base_URL, use_session=True) is False:
raise Exception("Cannot create session. Verify your credentials are correct.")
else:
raise URLError(self.base_URL + " is not a valid URL")
......@@ -139,10 +178,8 @@ class ApiCalls(object):
"password": self.password
}
}
access_token = oauth_service.get_access_token(
decoder=self.decoder, **params)
return access_token
def decoder(self, return_dict):
......@@ -212,8 +249,6 @@ class ApiCalls(object):
returns link if it exists
"""
retVal = None
if self.validate_URL_existence(targ_url, use_session=True):
response = self.session.get(targ_url)
......@@ -235,7 +270,7 @@ class ApiCalls(object):
else:
links_list = response.json()["resource"]["links"]
try:
retVal = next(link["href"] for link in links_list
ret_val = next(link["href"] for link in links_list
if link["rel"] == target_key)
except StopIteration:
......@@ -248,7 +283,7 @@ class ApiCalls(object):
raise request_HTTPError("Error: " +
targ_url + " is not a valid URL")
return retVal
return ret_val
def get_projects(self):
"""
......@@ -312,8 +347,7 @@ class ApiCalls(object):
})
except StopIteration:
raise ProjectError("The given project ID: " +
project_id + " doesn't exist")
raise ProjectError("The given project ID: " + project_id + " doesn't exist")
response = self.session.get(url)
result = response.json()["resource"]["resources"]
......@@ -329,7 +363,6 @@ class ApiCalls(object):
sample -- a Sample object used to get sample_id
returns list of sequencefile dictionary for given sample_id
"""
......@@ -394,7 +427,6 @@ class ApiCalls(object):
}
response = self.session.post(url, json_obj, **headers)
if response.status_code == httplib.CREATED: # 201
json_res = json.loads(response.text)
else:
......@@ -446,7 +478,7 @@ class ApiCalls(object):
"Content-Type": "application/json"
}
}
json_obj = json.dumps(sample, cls=Sample.JsonEncoder)
response = self.session.post(url, json_obj, **headers)
......@@ -457,8 +489,8 @@ class ApiCalls(object):
logging.error("Didn't create sample on server, response code is [{}] and error message is [{}]".format(response.status_code, response.text))
e = SampleError("Error {status_code}: {err_msg}.\nSample data: {sample_data}".format(status_code=str(response.status_code), err_msg=response.text, sample_data=str(sample)), ["IRIDA rejected the sample."])
send_message(sample.upload_failed_topic, exception = e)
raise e
raise e
return json_res_list
def get_file_size_list(self, samples_list):
......@@ -551,8 +583,7 @@ class ApiCalls(object):
"value": project_id
})
except StopIteration:
raise ProjectError("The given project ID: " +
project_id + " doesn't exist")
raise ProjectError("The given project ID: " + project_id + " doesn't exist")
try:
sample_id = sample.get_id()
......@@ -667,7 +698,6 @@ class ApiCalls(object):
send_message(sample.upload_started_topic)
logging.info("Sending files to [{}]".format(url))
response = self.session.post(url, data=_sample_upload_generator(sample),
headers={"Content-Type": "multipart/form-data; boundary={}".format(boundary)})
......@@ -736,7 +766,6 @@ class ApiCalls(object):
del metadata_dict[key]
json_obj = json.dumps(metadata_dict)
response = self.session.post(url, json_obj, **headers)
if response.status_code == httplib.CREATED: # 201
json_res = json.loads(response.text)
......
......@@ -26,6 +26,10 @@ class TestApiCalls(unittest.TestCase):
def setUp(self):
print "\nStarting " + self.__module__ + ": " + self._testMethodName
print "\nResetting api"
# Sets api params to "reset" so a new instance is created when the test
# initializes the api with the parameters it needs for the test
API.apiCalls.ApiCalls.close()
@patch("API.apiCalls.urlopen")
@patch("API.apiCalls.ApiCalls.create_session")
......@@ -116,7 +120,7 @@ class TestApiCalls(unittest.TestCase):
mock_get_access_token.side_effect = [access_token]
mock_validate_url_existence.side_effect = [True]
base_URL1 = "http://localhost:8080"
base_URL1 = "http://localhost:8082"
api1 = API.apiCalls.ApiCalls(
client_id="",
client_secret="",
......@@ -158,6 +162,51 @@ class TestApiCalls(unittest.TestCase):
mock_validate_url_existence.assert_called_with(
base_URL2, use_session=True)
# This test validates that the api is a singleton, and does not make extra requests when re-init with same params
@patch("API.apiCalls.ApiCalls.validate_URL_existence")
@patch("API.apiCalls.ApiCalls.get_access_token")
@patch("API.apiCalls.ApiCalls.get_oauth_service")
@patch("API.apiCalls.validate_URL_form")
def test_create_session_back_to_back(
self, mock_validate_url_form,
mock_get_oauth_service, mock_get_access_token,
mock_validate_url_existence):
oauth_service = Foo()
access_token = Foo()
setattr(oauth_service, "get_session", lambda x: "newSession3")
mock_validate_url_form.side_effect = [True]
mock_get_oauth_service.side_effect = [oauth_service]
mock_get_access_token.side_effect = [access_token]
mock_validate_url_existence.side_effect = [True]
base_URL3 = "http://localhost:8083/"
api3 = API.apiCalls.ApiCalls(
client_id="",
client_secret="",
base_URL=base_URL3,
username="",
password=""
)
mock_validate_url_existence.assert_called_with(
base_URL3, use_session=True)
api4 = API.apiCalls.ApiCalls(
client_id="",
client_secret="",
base_URL=base_URL3,
username="",
password=""
)
# Should only call the server once, when having the same parameters
mock_validate_url_existence.assert_called_once_with(
base_URL3, use_session=True)
# Should have the same API
self.assertTrue(api3 is api4)
@patch("API.apiCalls.validate_URL_form")
def test_create_session_invalid_form(self, mock_validate_url_form):
......@@ -990,7 +1039,7 @@ class TestApiCalls(unittest.TestCase):
session_response = Foo()
setattr(session_response, "status_code", httplib.CONFLICT)
setattr(session_response, "text",
setattr(session_response, "text",
"An entity already exists with that identifier")
session_post = MagicMock(side_effect=[session_response])
session = Foo()
......@@ -999,7 +1048,7 @@ class TestApiCalls(unittest.TestCase):
api.session = session
api.get_link = lambda x, y, targ_dict="": None
sample = API.apiCalls.Sample({"sampleProject": "1", "sampleName": "123"})
sample = API.apiCalls.Sample({"sampleProject": "1", "sampleName": "123"})
seq_file = SequenceFile({}, [])
sample.set_seq_file(seq_file)
sample.run = SequencingRun(sample_sheet="sheet", sample_list=[sample])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment