diff --git a/CHANGES.md b/CHANGES.md index e05c95a..d9edf26 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +4.1.0.0 2018-06-01 +================== + +- Add get session level decisions in Get Decisions APIs. + 4.0.1 2018-04-06 ================== @@ -86,4 +91,3 @@ INCOMPATIBLE CHANGES INTRODUCED IN API V205: ================== - Just the Python REST client itself. - diff --git a/README.md b/README.md index 7987134..b98fb1e 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,13 @@ except sift.client.ApiException: # request failed pass +# Get the latest decisions for a session +try: + response = client.get_session_decisions('example_user', 'example_session') +except sift.client.ApiException: + # request failed + pass + # Get the latest decisions for a piece of content try: response = client.get_content_decisions('example_user', 'example_content') diff --git a/sift/client.py b/sift/client.py index b3719db..9b50a35 100644 --- a/sift/client.py +++ b/sift/client.py @@ -533,6 +533,35 @@ def get_content_decisions(self, user_id, content_id, timeout=None): except requests.exceptions.RequestException as e: raise ApiException(str(e)) + def get_session_decisions(self, user_id, session_id, timeout=None): + """Gets the decisions for a user's session. + + Args: + user_id: The ID of a user. + session_id: The ID of a session. + + Returns: + A sift.client.Response object if the call succeeded. + Otherwise, raises an ApiException. + + """ + if not isinstance(user_id, self.UNICODE_STRING) or len(user_id.strip()) == 0: + raise ApiException("user_id must be a string") + if not isinstance(session_id, self.UNICODE_STRING) or len(session_id.strip()) == 0: + raise ApiException("session_id must be a string") + + if timeout is None: + timeout = self.timeout + + try: + return Response(requests.get( + self._session_decisions_url(self.account_id, user_id, session_id), + auth=requests.auth.HTTPBasicAuth(self.api_key, ''), + headers={'User-Agent': self._user_agent()}, + timeout=timeout)) + + except requests.exceptions.RequestException as e: + raise ApiException(str(e)) def apply_session_decision(self, user_id, session_id, properties, timeout=None): """Apply decision to session @@ -638,6 +667,9 @@ def _user_decisions_url(self, account_id, user_id): def _order_decisions_url(self, account_id, order_id): return API3_URL + '/v3/accounts/%s/orders/%s/decisions' % (account_id, order_id) + def _session_decisions_url(self, account_id, user_id, session_id): + return API3_URL + '/v3/accounts/%s/users/%s/sessions/%s/decisions' % (account_id, user_id, session_id) + def _content_decisions_url(self, account_id, user_id, content_id): return API3_URL + '/v3/accounts/%s/users/%s/content/%s/decisions' % (account_id, user_id, content_id) diff --git a/sift/version.py b/sift/version.py index b5a3c06..634a545 100644 --- a/sift/version.py +++ b/sift/version.py @@ -1,2 +1,2 @@ -VERSION = '4.0.1' +VERSION = '4.1.0' API_VERSION = '205' diff --git a/tests/test_client.py b/tests/test_client.py index 84ef0d7..5d11280 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -452,6 +452,12 @@ def test_apply_decision_to_session_fails_with_no_session_id(self): except Exception as e: assert(isinstance(e, sift.client.ApiException)) + def test_get_session_decisions_fails_with_no_session_id(self): + try: + self.sift_client.get_session_decisions("user_id", None) + except Exception as e: + assert(isinstance(e, sift.client.ApiException)) + def test_apply_decision_to_content_fails_with_no_content_id(self): try: self.sift_client.apply_content_decision("user_id", None, {}) @@ -937,6 +943,25 @@ def test_get_order_decisions(self): assert(response.body['decisions']['payment_abuse']['decision']['id'] == 'decision7') assert(response.body['decisions']['promotion_abuse']['decision']['id'] == 'good_order') + def test_get_session_decisions(self): + mock_response = mock.Mock() + mock_response.content = '{"decisions":{"account_takeover": {"decision": {"id": "session_decision"},"time": 1461963839151,"webhook_succeeded": true}}}' + mock_response.json.return_value = json.loads(mock_response.content) + mock_response.status_code = 200 + mock_response.headers = response_with_data_header() + + with mock.patch('requests.get') as mock_get: + mock_get.return_value = mock_response + + response = self.sift_client.get_session_decisions('example_user','example_session') + mock_get.assert_called_with( + 'https://api3.siftscience.com/v3/accounts/ACCT/users/example_user/sessions/example_session/decisions', + headers=mock.ANY, auth=mock.ANY, timeout=mock.ANY) + + assert(isinstance(response, sift.client.Response)) + assert(response.is_ok()) + assert(response.body['decisions']['account_takeover']['decision']['id'] == 'session_decision') + def test_get_content_decisions(self): mock_response = mock.Mock() mock_response.content = '{"decisions":{"content_abuse":{"decision":{"id":"content_looks_bad_content_abuse"},"time":1468517407135,"webhook_succeeded":true}}}'