Skip to content
This repository was archived by the owner on Jan 17, 2025. It is now read-only.

Commit 0d58673

Browse files
add context functionality, signing string creation from req body for respondent gateway only requests
1 parent 6f3ce98 commit 0d58673

File tree

3 files changed

+95
-35
lines changed

3 files changed

+95
-35
lines changed

dynata_rex/opportunity_registry.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,36 @@
2121

2222
class RegistryAPI:
2323

24+
_BASE_URL = 'https://registry.rex.dynata.com'
25+
2426
def __init__(self,
2527
access_key: str,
2628
secret_key: str,
27-
base_url: str,
29+
base_url: str = _BASE_URL,
30+
default_ttl: int = 10,
2831
shard_count: int = 1,
29-
current_shard: int = 1,
30-
signature_ttl: int = 10):
32+
current_shard: int = 1):
3133
"""
3234
@access_key: liam access key for REX
3335
@secret_key: liam secret key for REX
34-
@base_url : url of Opportunity Registry
3536
3637
# Optional
38+
@base_url : url of Opportunity Registry
3739
@shard_count : number of total shards consuming Opportunity Registry
3840
@current_shard: curent shard
39-
@signature_ttl: time to live for signature in seconds
41+
@default_ttl: time to live for signature in seconds
4042
"""
41-
self.signature_ttl = signature_ttl
43+
self.default_ttl = default_ttl
4244
self.make_request = RexRequest(access_key,
4345
secret_key,
44-
default_ttl=signature_ttl)
45-
self.base_url = self._format_base_url(base_url)
46+
default_ttl=default_ttl)
47+
self.base_url = base_url
4648

4749
if current_shard > shard_count:
4850
raise InvalidShardException
4951
self.shard_count = shard_count
5052
self.current_shard = current_shard
5153

52-
def _format_base_url(self, base_url: str) -> str:
53-
if base_url.startswith('http'):
54-
return base_url
55-
return f'https://{base_url}'
56-
5754
def _get_opportunity(self, opportunity_id: int) -> dict:
5855
"""Raw get opportunity"""
5956
endpoint = f"{self.base_url}/get-opportunity"

dynata_rex/respondent_gateway.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# Local Imports
1616
from dynata_rex.models import GatewayDispositionsEnum, GatewayStatusEnum
17-
from .signer import Signer
17+
from .signer import Signer, RexRequest
1818
from .exceptions import SignatureExpiredException, SignatureInvalidException
1919

2020

@@ -23,20 +23,31 @@ class RespondentGateway:
2323
Respondent Gateway interactions
2424
"""
2525

26+
_BASE_URL = 'https://respondent.rex.dynata.com'
27+
2628
def __init__(self,
2729
access_key: str,
2830
secret_key: str,
31+
base_url: str = _BASE_URL,
2932
default_ttl: int = 10):
3033
"""
3134
@access_key: liam access key for REX
3235
@secret_key: liam secret key for REX
3336
3437
# Optional
38+
@base_url: url of Gateway
3539
@ttl: time to live for signature in seconds
3640
"""
3741
self.access_key = access_key
3842
self.secret_key = secret_key
3943
self.default_ttl = default_ttl
44+
self.base_url = base_url
45+
46+
# MR for API requests
47+
self.make_request = RexRequest(access_key,
48+
secret_key,
49+
default_ttl=default_ttl)
50+
# Signer for signing/verifying URLs
4051
self.signer = Signer(access_key, secret_key, default_ttl=default_ttl)
4152

4253
def create_respondent_url(self,
@@ -130,6 +141,11 @@ def verify_url(self,
130141
secret_key: str = None) -> bool:
131142
"""
132143
Verify a URL's signature matches for the given access and secret keys
144+
@url: URL to verify
145+
146+
Optional
147+
@access_key: liam access key for signing
148+
@secret_key: liam secret key for signing
133149
"""
134150
if access_key is None:
135151
access_key = self.access_key
@@ -150,6 +166,8 @@ def get_respondent_disposition(
150166
self, url) -> Union[GatewayDispositionsEnum, None]:
151167
"""
152168
Get the disposition of a respondent from a URL
169+
170+
@url: URL to get disposition from
153171
"""
154172
parsed = urlparse(url)
155173
query_parameters = dict(parse_qsl(parsed.query))
@@ -163,6 +181,8 @@ def get_respondent_status(
163181
self, url) -> Union[GatewayStatusEnum, None]:
164182
"""
165183
Get the status of a respondent from a URL
184+
185+
@url: URL to get status from
166186
"""
167187
parsed = urlparse(url)
168188
query_parameters = dict(parse_qsl(parsed.query))
@@ -174,3 +194,45 @@ def get_respondent_status(
174194
return GatewayStatusEnum((disposition, status))
175195
except KeyError:
176196
return None
197+
198+
def create_context(self, context_id: str, context_data: dict) -> int:
199+
"""
200+
Create a context with the given context_id and context_data
201+
202+
@context_id: unique identifier for the context
203+
@context_data: dictionary of context data ie:
204+
{
205+
"ctx": "a987dsglh34t435jkhsdg98u",
206+
"gender": "male",
207+
"postal_code": "60081",
208+
"birth_date": "1959-10-05",
209+
"country": "US"
210+
}
211+
"""
212+
endpoint = f"{self.base_url}/create-context"
213+
data = {
214+
"id": context_id,
215+
"items": context_data
216+
}
217+
response = self.make_request.post(endpoint, data)
218+
return response['id']
219+
220+
def expire_context(self, context_id: str) -> None:
221+
"""
222+
Expire a context with the given context_id and account_id
223+
224+
@context_id: identifier for the context
225+
"""
226+
endpoint = f"{self.base_url}/expire-context"
227+
data = {"id": context_id}
228+
res = self.make_request.post(endpoint, data)
229+
return res if res else None
230+
231+
def get_context(self, context_id: int) -> dict:
232+
"""Get specific opportunity from SMOR
233+
234+
@context_id: identifier for the context
235+
"""
236+
endpoint = f"{self.base_url}/get-context"
237+
data = {"id": context_id}
238+
return self.make_request.post(endpoint, data)

dynata_rex/signer.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,11 @@ def _create_query_params_signing_string(self, parameters: dict) -> str:
9999
encoded_params = urlencode(sorted_params)
100100
return hashlib.sha256(encoded_params.encode('utf-8')).hexdigest()
101101

102-
def sign_rex_request(self,
103-
url: str,
104-
method: str,
105-
ttl: Union[int, None] = None) -> str:
106-
if ttl is None:
107-
ttl = self.default_ttl
108-
expiration_date_str = self.create_expiration_date(ttl)
109-
signing_string = self.signing_string
110-
signature, _ = self.sign_from_expiration_date(self.access_key,
111-
self.secret_key,
112-
expiration_date_str,
113-
signing_string)
114-
return signature
102+
def _create_request_body_signing_string(self, request_body: str) -> str:
103+
"""SHA256 digest of the request body as a hexidecimal string
104+
in lowercase
105+
"""
106+
return hashlib.sha256(request_body.encode('utf-8')).hexdigest()
115107

116108
def sign_query_params_from_expiration_date(self,
117109
parameters: dict,
@@ -159,17 +151,26 @@ def __init__(self, access_key, secret_key, default_ttl: int = 10):
159151
self.signer = Signer(access_key, secret_key)
160152
self.session = make_session()
161153

162-
def _signature(self, ttl: int = None) -> str:
154+
def _signature(self, ttl: int = None, signing_string: str = None) -> str:
163155
if ttl is None:
164156
ttl = self.default_ttl
165157
return self.signer.sign_from_ttl(
166158
self.access_key,
167159
self.secret_key,
168-
ttl
160+
ttl,
161+
signing_string=signing_string
169162
)
170163

171-
def _create_auth_headers(self, additional_headers={}):
172-
signature, expiration = self._signature()
164+
def _create_auth_headers(self, url, additional_headers={}, body=''):
165+
signing_string = self.signer._create_request_body_signing_string(body)
166+
167+
# TODO: Hack alert: Registry doesn't like the sha256 body signing
168+
# string yet
169+
if 'https://registry' in url:
170+
signing_string = ''
171+
# END Hack alert
172+
173+
signature, expiration = self._signature(signing_string=signing_string)
173174
base = {
174175
'dynata-expiration': expiration,
175176
'dynata-access-key': self.access_key,
@@ -179,18 +180,18 @@ def _create_auth_headers(self, additional_headers={}):
179180

180181
def dispatch(self,
181182
url,
182-
data=None,
183+
data='',
183184
method='GET') -> Union[dict, str]:
184185

185186
additional_headers = {}
186187
if data:
187188
additional_headers = {'Content-type': 'application/json'}
188189
data = json.dumps(data)
189190

190-
headers = self._create_auth_headers(additional_headers)
191+
headers = self._create_auth_headers(url, additional_headers, body=data)
191192

192193
if not hasattr(self.session, method.lower()):
193-
raise Exception('Invalid http method provided.')
194+
raise AttributeError('Invalid http method provided.')
194195

195196
method = getattr(self.session, method.lower())
196197

@@ -201,7 +202,7 @@ def dispatch(self,
201202
if data:
202203
logger.warning(data)
203204
logger.warning(res.__dict__)
204-
raise RexServiceException(res.content)
205+
raise RexServiceException(res.content.decode('utf-8'))
205206
try:
206207
return res.json()
207208
except json.decoder.JSONDecodeError as e:

0 commit comments

Comments
 (0)