Commit 9cc85d29 authored by Yan Sushchynski (EPAM)'s avatar Yan Sushchynski (EPAM)
Browse files

GONRG-3119: Python SDk usage

parent f5f33d14
Pipeline #76877 passed with stages
in 37 seconds
......@@ -12,14 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import importlib
import os
from configparser import SafeConfigParser
import logging
import os
import requests
from configparser import SafeConfigParser
from osdu_api.auth.authorization import authorize, TokenRefresher
from osdu_api.configuration.base_config_manager import BaseConfigManager
from osdu_api.configuration.config_manager import DefaultConfigManager
from osdu_api.exceptions.exceptions import MakeRequestError
from osdu_api.model.http_method import HttpMethod
......@@ -28,7 +28,13 @@ class BaseClient:
Base client that is meant to be extended by service specific clients
"""
def __init__(self, config_manager: BaseConfigManager = None, data_partition_id = None, logger = None):
def __init__(
self,
config_manager: BaseConfigManager = None,
data_partition_id = None,
token_refresher: TokenRefresher = None,
logger = None
):
"""
Base client gets initialized with configuration values and a bearer token
based on provider-specific logic
......@@ -41,6 +47,7 @@ class BaseClient:
self.logger = logger
if self.logger is None:
self.logger = logging.getLogger(__name__)
self.token_refresher = token_refresher
def _parse_config(self, config_manager: BaseConfigManager = None, data_partition_id = None):
"""
......@@ -82,29 +89,24 @@ class BaseClient:
entitlements_client = importlib.import_module('osdu_api.providers.%s.%s' % (self.provider, self.service_principal_module_name))
self.service_principal_token = entitlements_client.get_service_principal_token()
def make_request(self, method: HttpMethod, url: str, data = '', add_headers = {}, params = {}, bearer_token = None):
"""
Makes a request using python's built in requests library. Takes additional headers if
necessary
@staticmethod
def _send_request(method: HttpMethod, url: str, data: str, headers: dict, params: dict) -> requests.Response:
"""
if bearer_token is None:
bearer_token = self.service_principal_token
if bearer_token is not None and 'Bearer ' not in bearer_token:
bearer_token = 'Bearer ' + bearer_token
headers = {
'content-type': 'application/json',
'data-partition-id': self.data_partition_id,
'Authorization': bearer_token
}
if (len(add_headers) > 0):
for key, value in add_headers.items():
headers[key] = value
response = object
Send request to OSDU
:param method: HTTP method
:type method: HttpMethod
:param url: service's URL
:type url: str
:param data: request's data
:type data: str
:param headers: request's headers
:type headers: dict
:param params: params
:type params: dict
:return: response from OSDU service
:rtype: requests.Response
"""
if (method == HttpMethod.GET):
response = requests.get(url=url, params=params, headers=headers, verify=False)
elif (method == HttpMethod.DELETE):
......@@ -113,13 +115,106 @@ class BaseClient:
response = requests.post(url=url, params=params, data=data, headers=headers, verify=False)
elif (method == HttpMethod.PUT):
response = requests.put(url=url, params=params, data=data, headers=headers, verify=False)
return response
if (response.status_code == 401 or response.status_code == 403) and self.unauth_retries < 1:
if self.use_service_principal == 'True' or self.use_service_principal == 'true':
self.unauth_retries += 1
self._refresh_service_principal_token()
self.make_request(method, url, data, add_headers, params, None)
def _send_request_with_principle_token(
self,
method: HttpMethod,
url: str,
data: str,
headers: dict,
params: dict,
):
bearer_token = self.service_principal_token
if bearer_token is not None and 'Bearer ' not in bearer_token:
bearer_token = 'Bearer ' + bearer_token
headers["Authorization"] = bearer_token
response = self._send_request(method, url, data, headers, params)
if (response.status_code == 401 or response.status_code == 403) and self.unauth_retries < 1:
self.unauth_retries += 1
self._refresh_service_principal_token()
self._send_request_with_principle_token(method, url, data, headers, params)
self.unauth_retries = 0
return response
def _send_request_with_bearer_token(
self,
method: HttpMethod,
url: str,
data: str,
headers: dict,
params: dict,
bearer_token: str
) -> requests.Response:
"""
Send request with bearer_token provided by SDK user.
:param method: HTTP method
:type method: HttpMethod
:param url: service's URL
:type url: str
:param data: request's data
:type data: str
:param headers: request's headers
:type headers: dict
:param params: params
:type params: dict
:param bearer_token: bearer_token
:type params: str
:return: response from OSDU service
:rtype: requests.Response
"""
if bearer_token is not None and 'Bearer ' not in bearer_token:
bearer_token = 'Bearer ' + bearer_token
headers["Authorization"] = bearer_token
response = self._send_request(method, url, data, headers, params)
if not response.ok:
response.raise_for_status()
return response
@authorize()
def _send_request_with_token_refresher(self, headers: dict, method: HttpMethod, url: str, data: str, params: dict):
return self._send_request(method, url, data, headers, params)
def make_request(
self,
method: HttpMethod,
url: str,
data = '',
add_headers: dict = None,
params: dict = None,
bearer_token = None
):
"""
Makes a request using python's built in requests library. Takes additional headers if
necessary
"""
add_headers = add_headers or {}
params = params or {}
headers = {
'content-type': 'application/json',
'data-partition-id': self.data_partition_id,
}
for key, value in add_headers.items():
headers[key] = value
if bearer_token:
response = self._send_request_with_bearer_token(method, url, data, headers, params, bearer_token)
elif self.token_refresher:
# _send_request_with_token_refresher has other method signature to work with @authorize decorator
response = self._send_request_with_token_refresher(headers, method, url, data, params)
elif self.use_service_principal:
response = self._send_request_with_principle_token(method, url, data, headers, params)
else:
raise MakeRequestError("There is no strategy to get Bearer token.")
return response
......@@ -30,6 +30,16 @@ class DataWorkflowClient(BaseClient):
return self.make_request(method=HttpMethod.POST, url='{}{}'.format(self.data_workflow_url, '/startWorkflow'),
data=start_workflow.to_JSON(), bearer_token=bearer_token)
def update_status(self, update_status_request: UpdateStatusRequest, bearer_token=None):
return self.make_request(method=HttpMethod.POST, url='{}{}'.format(self.data_workflow_url, '/updateStatus'),
data=update_status_request.to_JSON(), bearer_token=bearer_token)
\ No newline at end of file
def update_status(
self,
update_status_request: UpdateStatusRequest,
workflow_name: str,
run_id: str,
bearer_token=None
):
return self.make_request(
method=HttpMethod.PUT,
url=f'{workflow_name}/workflowRun/{run_id}',
data=update_status_request.to_JSON(),
bearer_token=bearer_token
)
\ No newline at end of file
......@@ -18,3 +18,8 @@
class TokenRefresherNotPresentError(Exception):
"""Raise when token refresher is not present in "refresh_token' decorator."""
pass
class MakeRequestError(Exception):
"""Raise when there are errors in Make request."""
pass
......@@ -15,6 +15,5 @@ from osdu_api.model.base import Base
class UpdateStatusRequest(Base):
def __init__(self, workflow_id: str, workflow_status_type: str):
self.workflowId = workflow_id
def __init__(self, workflow_status_type: str):
self.status = workflow_status_type
......@@ -21,7 +21,7 @@ class Legal:
Legal model mirroring what's found in core common
"""
def __init__(self, legaltags: list, other_relevant_data_countries: list, status: LegalCompliance):
def __init__(self, legaltags: list, other_relevant_data_countries: list, status: LegalCompliance = None):
self.legaltags = legaltags
self.other_relevant_data_countries = other_relevant_data_countries
self.status = status
......@@ -30,5 +30,6 @@ class Legal:
legal_dict = {}
legal_dict['legaltags'] = self.legaltags
legal_dict['otherRelevantDataCountries'] = self.other_relevant_data_countries
legal_dict['status'] = str(self.status)
if self.status:
legal_dict['status'] = str(self.status)
return legal_dict
\ No newline at end of file
......@@ -41,12 +41,15 @@ class Record:
'''
@classmethod
def from_dict(cls, record_dict: dict):
id = record_dict['id']
version = record_dict['version']
id = record_dict.get('id')
version = record_dict.get('version')
kind = record_dict['kind']
acl = Acl(record_dict['acl']['viewers'], record_dict['acl']['owners'])
legal = Legal(record_dict['legal']['legaltags'], record_dict['legal']['otherRelevantDataCountries'],
LegalCompliance[record_dict['legal']['status']])
legal = Legal(
record_dict['legal']['legaltags'],
record_dict['legal']['otherRelevantDataCountries'],
LegalCompliance(record_dict['legal']['status']) if record_dict["legal"].get("status") else None
)
data = record_dict['data']
meta = record_dict['meta']
......
......@@ -20,7 +20,7 @@ class QueryRequest(Base):
def __init__(self, kind: str, query: str, limit: int = None, return_highlighted_fields: bool = None,
returned_fields: list = None, sort: SortQuery = None, query_as_owner: bool = None, spatial_filter: SpatialFilter = None,
from_num: int = None, aggregate_by: str = None, cursor: str = None):
from_num: int = None, aggregate_by: str = None, offset: int = None, cursor: str = None):
self.kind = kind
self.limit = limit
self.query = query
......@@ -31,6 +31,7 @@ class QueryRequest(Base):
self.spatialFilter = spatial_filter
self.from_num = from_num
self.aggregateBy = aggregate_by
self.offset = offset
self.cursor = cursor
......
......@@ -14,6 +14,7 @@
from osdu_api.model.base import Base
from osdu_api.model.storage.acl import Acl
from osdu_api.model.storage.legal import Legal
from osdu_api.model.legal.legal_compliance import LegalCompliance
from osdu_api.model.storage.record_ancestry import RecordAncestry
......@@ -32,3 +33,35 @@ class Record(Base):
self.data = data
self.ancestry = ancestry
self.meta = meta
@classmethod
def from_dict(cls, record_dict: dict):
id = record_dict.get('id')
version = record_dict.get('version')
kind = record_dict['kind']
acl = Acl(record_dict['acl']['viewers'], record_dict['acl']['owners'])
legal = Legal(
record_dict['legal']['legaltags'],
record_dict['legal']['otherRelevantDataCountries'],
record_dict['legal']['status'] if record_dict["legal"].get("status") else None
)
data = record_dict['data']
meta = record_dict.get('meta')
parents = []
try:
parents = record_dict['ancestry']['parents']
except KeyError:
# warn the user that ancestry wasn't found, not essential attribute
print('Attribute "ancestry" is missing from dict being converted to record')
ancestry = RecordAncestry(parents)
return cls(kind, acl, legal, data, id, version, ancestry, meta)
def convert_to_dict(self):
record_converted = self.__dict__
record_converted['acl'] = self.acl.__dict__
record_converted['legal'] = self.legal.get_dict()
record_converted['ancestry'] = self.ancestry.__dict__
return record_converted
......@@ -27,7 +27,7 @@ class TestDefaultConfigManager:
@pytest.fixture
def default_config_file(self):
cwd = os.getcwd()
config_file = f"{os.path.dirname(__file__)}/fake_config/osdu_api/test/osdu_api.ini"
config_file = f"{os.path.dirname(__file__)}/fake_config/osdu_api.ini"
path = shutil.copy(config_file, cwd)
yield path
os.remove(path)
......@@ -37,12 +37,12 @@ class TestDefaultConfigManager:
DefaultConfigManager()
def test_configmanager_with_env_var(self):
config_file = f"{os.path.dirname(__file__)}/fake_config/osdu_api/test/osdu_api.ini"
config_file = f"{os.path.dirname(__file__)}/fake_config/osdu_api.ini"
os.environ["OSDU_API_CONFIG_INI"] = config_file
DefaultConfigManager()
def test_configmanager_with_passed_directly(self):
config_file = f"{os.path.dirname(__file__)}/fake_config/osdu_api/test/osdu_api.ini"
config_file = f"{os.path.dirname(__file__)}/fake_config/osdu_api.ini"
DefaultConfigManager(config_file)
def test_raise_error_if_no_file(self):
......
......@@ -17,10 +17,21 @@ import os
import mock
import responses
from osdu_api.auth.authorization import TokenRefresher
from osdu_api.clients.base_client import BaseClient
from osdu_api.model.http_method import HttpMethod
from osdu_api.configuration.config_manager import DefaultConfigManager
class MockTokenRefresher(TokenRefresher):
def refresh_token():
"""
We need to define this method, because original TokenRefresher.refresh_token is abstract one.
"""
return "stubbed"
class TestBaseClient(unittest.TestCase):
@mock.patch.object(BaseClient, '_refresh_service_principal_token', return_value="stubbed")
......@@ -47,3 +58,45 @@ class TestBaseClient(unittest.TestCase):
# Assert
mocked_token_method.assert_called()
assert response.content == b'{"response": "true"}'
@responses.activate
@mock.patch.object(BaseClient, '_send_request_with_bearer_token', return_value="stubbed")
def test_make_request_with_bearer(self, mocked_send_request):
config_manager = DefaultConfigManager(os.getcwd() + '/osdu_api/test/osdu_api.ini')
config_manager._parser._sections["environment"]["use_service_principal"] = "False"
client = BaseClient(config_manager, "opendes")
client.make_request(method=HttpMethod.PUT, url='http://stubbed', data={}, bearer_token="subbed")
mocked_send_request.assert_called()
@responses.activate
@mock.patch.object(BaseClient, '_send_request_with_principle_token', return_value="stubbed")
@mock.patch.object(BaseClient, '_refresh_service_principal_token', return_value="stubbed")
def test_make_request_with_principle(self, mocked_send_request, mocked_refresh_principle):
config_manager = DefaultConfigManager(os.getcwd() + '/osdu_api/test/osdu_api.ini')
config_manager._parser._sections["environment"]["use_service_principal"] = "True"
client = BaseClient(config_manager, "opendes")
client.service_principal_token = 'stubbed'
client.make_request(method=HttpMethod.PUT, url='http://stubbed', data={})
mocked_refresh_principle.assert_called()
mocked_send_request.assert_called()
@responses.activate
@mock.patch.object(BaseClient, '_send_request_with_token_refresher', return_value="stubbed")
def test_make_request_with_token_refresher(
self,
mocked_send_request,
):
token_refresher = MockTokenRefresher()
config_manager = DefaultConfigManager(os.getcwd() + '/osdu_api/test/osdu_api.ini')
config_manager._parser._sections["environment"]["use_service_principal"] = "False"
client = BaseClient(config_manager, "opendes", token_refresher=token_refresher)
client.make_request(method=HttpMethod.PUT, url='http://stubbed', data={})
mocked_send_request.assert_called()
Supports Markdown
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