Skip to content

Commit 3d63d4f

Browse files
committed
SDK-1076: Update tests for SignedRequest and SignedRequestBuilder, as well as updating the rest of the SDK to make use of the new builders
1 parent 36b730a commit 3d63d4f

File tree

7 files changed

+223
-136
lines changed

7 files changed

+223
-136
lines changed

yoti_python_sdk/client.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import json
55
from os import environ
66

7-
import requests
8-
97
import yoti_python_sdk
108
from yoti_python_sdk import aml
119
from yoti_python_sdk.activity_details import ActivityDetails
@@ -26,12 +24,36 @@
2624

2725

2826
class Client(object):
29-
def __init__(self, sdk_id=None, pem_file_path=None):
27+
def __init__(self, sdk_id=None, pem_file_path=None, request_handler=None):
3028
self.sdk_id = sdk_id or environ.get("YOTI_CLIENT_SDK_ID")
3129
pem_file_path_env = environ.get("YOTI_KEY_FILE_PATH", pem_file_path)
3230

33-
self.__crypto = Crypto.read_pem_file(pem_file_path_env)
31+
if pem_file_path is not None:
32+
error_source = "argument specified in Client()"
33+
self.__crypto = Crypto.read_pem_file(pem_file_path, error_source)
34+
elif pem_file_path_env is not None:
35+
error_source = "specified by the YOTI_KEY_FILE_PATH env variable"
36+
self.__crypto = Crypto.read_pem_file(pem_file_path_env, error_source)
37+
else:
38+
raise RuntimeError(NO_KEY_FILE_SPECIFIED_ERROR)
39+
3440
self.__endpoint = Endpoint(sdk_id)
41+
self.__request_handler = request_handler
42+
43+
def make_request(self, http_method, endpoint, body):
44+
signed_request = (
45+
SignedRequest.builder()
46+
.with_pem_file(self.__crypto)
47+
.with_base_url(yoti_python_sdk.YOTI_API_ENDPOINT)
48+
.with_endpoint(endpoint)
49+
.with_http_method(http_method)
50+
.with_payload(body)
51+
.with_request_handler(self.__request_handler)
52+
.build()
53+
)
54+
55+
response = signed_request.execute()
56+
return response
3557

3658
def get_activity_details(self, encrypted_request_token):
3759
response = self.__make_activity_details_request(encrypted_request_token)
@@ -70,24 +92,10 @@ def perform_aml_check(self, aml_profile):
7092
if aml_profile is None:
7193
raise TypeError("aml_profile not set")
7294

73-
http_method = "POST"
74-
75-
response = self.__make_aml_check_request(http_method, aml_profile)
95+
response = self.__make_aml_check_request(aml_profile)
7696

7797
return aml.AmlResult(response.text)
7898

79-
def make_request(self, http_method, endpoint, body):
80-
url = yoti_python_sdk.YOTI_API_ENDPOINT + endpoint
81-
headers = self.__get_request_headers(endpoint, http_method, body)
82-
response = requests.request(
83-
http_method,
84-
url,
85-
headers=headers,
86-
data=body,
87-
verify=yoti_python_sdk.YOTI_API_VERIFY_SSL,
88-
)
89-
return response
90-
9199
@property
92100
def endpoints(self):
93101
return self.__endpoint
@@ -123,6 +131,7 @@ def __make_activity_details_request(self, encrypted_request_token):
123131
.with_base_url(yoti_python_sdk.YOTI_API_ENDPOINT)
124132
.with_endpoint(path)
125133
.with_param("appId", self.sdk_id)
134+
.with_request_handler(self.__request_handler)
126135
.build()
127136
)
128137

@@ -134,20 +143,25 @@ def __make_activity_details_request(self, encrypted_request_token):
134143

135144
return response
136145

137-
def __make_aml_check_request(self, http_method, aml_profile):
146+
def __make_aml_check_request(self, aml_profile):
138147
aml_profile_json = json.dumps(aml_profile.__dict__, sort_keys=True)
139148
aml_profile_bytes = aml_profile_json.encode()
140-
path = self.__endpoint.get_aml_request_url()
141-
url = yoti_python_sdk.YOTI_API_ENDPOINT + path
142-
headers = self.__get_request_headers(path, http_method, aml_profile_bytes)
143-
144-
response = requests.post(
145-
url=url,
146-
headers=headers,
147-
data=aml_profile_bytes,
148-
verify=yoti_python_sdk.YOTI_API_VERIFY_SSL,
149+
path = self.__endpoint.get_aml_request_url(no_params=True)
150+
151+
signed_request = (
152+
SignedRequest.builder()
153+
.with_pem_file(self.__crypto)
154+
.with_base_url(yoti_python_sdk.YOTI_API_ENDPOINT)
155+
.with_endpoint(path)
156+
.with_payload(aml_profile_bytes)
157+
.with_param("appId", self.sdk_id)
158+
.with_post()
159+
.with_request_handler(self.__request_handler)
160+
.build()
149161
)
150162

163+
response = signed_request.execute()
164+
151165
self.http_error_handler(
152166
response, {"default": "Unsuccessful Yoti API call: {} {}"}
153167
)

yoti_python_sdk/dynamic_sharing_service/share_url.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
def create_share_url(yoti_client, dynamic_scenario):
2020
http_method = "POST"
2121
payload = json.dumps(dynamic_scenario, sort_keys=True).encode()
22-
endpoint = yoti_client.endpoints.get_dynamic_share_request_url()
22+
endpoint = yoti_client.endpoints.get_dynamic_share_request_url(no_params=True)
2323
response = yoti_client.make_request(http_method, endpoint, payload)
2424

2525
client.Client.http_error_handler(response, SHARE_URL_ERRORS)

yoti_python_sdk/http.py

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
JSON_CONTENT_TYPE,
99
)
1010
from cryptography.fernet import base64
11+
from abc import ABCMeta, abstractmethod
1112

1213
import yoti_python_sdk
1314
import requests
@@ -17,19 +18,52 @@
1718
try: # pragma: no cover
1819
from urllib.parse import urlencode
1920
except ImportError: # pragma: no cover
20-
from urlparse import urlencode
21+
from urllib import urlencode
2122

2223
HTTP_POST = "POST"
2324
HTTP_GET = "GET"
2425
HTTP_SUPPORTED_METHODS = ["POST", "PUT", "PATCH", "GET", "DELETE"]
2526

2627

28+
class RequestHandler:
29+
"""
30+
Default request handler for signing requests using the requests library.
31+
This type can be inherited and the execute method overridden to use any
32+
preferred HTTP library.
33+
"""
34+
35+
__metaclass__ = ABCMeta # Python 2 compatability
36+
37+
@staticmethod
38+
@abstractmethod
39+
def execute(request):
40+
return NotImplemented
41+
42+
43+
class DefaultRequestHandler(RequestHandler):
44+
@staticmethod
45+
def execute(request):
46+
"""
47+
Execute the HTTP request supplied
48+
"""
49+
if not isinstance(request, SignedRequest):
50+
raise TypeError("RequestHandler expects instance of SignedRequest")
51+
52+
return requests.request(
53+
url=request.url,
54+
method=request.method,
55+
data=request.data,
56+
headers=request.headers,
57+
)
58+
59+
2760
class SignedRequest(object):
28-
def __init__(self, url, http_method, payload, headers):
61+
def __init__(self, url, http_method, payload, headers, request_handler=None):
2962
self.__url = url
3063
self.__http_method = http_method
3164
self.__payload = payload
3265
self.__headers = headers
66+
self.__request_handler = request_handler
3367

3468
@property
3569
def url(self):
@@ -59,22 +93,15 @@ def headers(self):
5993
"""
6094
return self.__headers
6195

62-
def prepare(self):
63-
"""
64-
Creates a PreparedRequest object for use in a requests Session
65-
"""
66-
r = requests.Request(
67-
method=self.method, url=self.url, headers=self.headers, data=self.data
68-
)
69-
return r.prepare()
70-
7196
def execute(self):
7297
"""
73-
Send the signed request, returning the requests Response object
98+
Send the signed request, using the default RequestHandler if one has not be supplied
7499
"""
75-
return requests.request(
76-
url=self.url, method=self.method, data=self.data, headers=self.headers
77-
)
100+
if self.__request_handler is None:
101+
print("Using default request handler")
102+
return DefaultRequestHandler.execute(self)
103+
104+
return self.__request_handler.execute(self)
78105

79106
@staticmethod
80107
def builder():
@@ -93,6 +120,7 @@ def __init__(self):
93120
self.__params = None
94121
self.__headers = None
95122
self.__payload = None
123+
self.__request_handler = None
96124

97125
def with_pem_file(self, pem_file):
98126
"""
@@ -157,6 +185,25 @@ def with_http_method(self, http_method):
157185
self.__http_method = http_method
158186
return self
159187

188+
def with_request_handler(self, handler):
189+
# If no handler is passed, just return as the default will be used
190+
if handler is None:
191+
return self
192+
193+
try:
194+
if not issubclass(handler, RequestHandler):
195+
raise TypeError(
196+
"Handler must be instance of yoti_python_sdk.http.RequestHandler"
197+
)
198+
except Exception:
199+
# ABC
200+
raise TypeError(
201+
"Handler must be instance of yoti_python_sdk.http.RequestHandler"
202+
)
203+
204+
self.__request_handler = handler
205+
return self
206+
160207
def with_post(self):
161208
"""
162209
Sets the HTTP method for a POST request
@@ -270,4 +317,6 @@ def build(self):
270317
)
271318
url = self.__base_url + endpoint
272319

273-
return SignedRequest(url, self.__http_method, self.__payload, headers)
320+
return SignedRequest(
321+
url, self.__http_method, self.__payload, headers, self.__request_handler
322+
)

yoti_python_sdk/tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from yoti_python_sdk import Client
88
from yoti_python_sdk.crypto import Crypto
9+
from yoti_python_sdk.tests.mocks import MockRequestHandler
910

1011
FIXTURES_DIR = join(dirname(abspath(__file__)), "fixtures")
1112
PEM_FILE_PATH = join(FIXTURES_DIR, "sdk-test.pem")
@@ -17,6 +18,11 @@
1718
YOTI_CLIENT_SDK_ID = "737204aa-d54e-49a4-8bde-26ddbe6d880c"
1819

1920

21+
@pytest.fixture(scope="module")
22+
def mock_request_handler():
23+
return MockRequestHandler
24+
25+
2026
@pytest.fixture(scope="module")
2127
def client():
2228
return Client(YOTI_CLIENT_SDK_ID, PEM_FILE_PATH)

yoti_python_sdk/tests/mocks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from uuid import UUID
2+
from yoti_python_sdk.http import RequestHandler, SignedRequest
23

34

45
class MockResponse:
@@ -7,6 +8,18 @@ def __init__(self, status_code, text):
78
self.text = text
89

910

11+
class MockRequestHandler(RequestHandler):
12+
@staticmethod
13+
def execute(request):
14+
"""
15+
Execute the HTTP request supplied
16+
"""
17+
if not isinstance(request, SignedRequest):
18+
raise RuntimeError("RequestHandler expects instance of SignedRequest")
19+
20+
return mocked_requests_get()
21+
22+
1023
def mocked_requests_get(*args, **kwargs):
1124
with open("yoti_python_sdk/tests/fixtures/response.txt", "r") as f:
1225
response = f.read()

0 commit comments

Comments
 (0)