From 31ac26d3182f905e2f95b2b6708bfff07682b839 Mon Sep 17 00:00:00 2001 From: Michael LeGore Date: Wed, 30 May 2018 16:01:26 -0700 Subject: [PATCH 1/4] Add get session decisions Added API client method for calling user session get decisions API described here: https://siftscience.com/developers/docs/curl/decisions-api/decision-status for https://github.com/SiftScience/code/issues/25317 --- CHANGES.md | 6 +++++- sift/client.py | 32 ++++++++++++++++++++++++++++++++ sift/version.py | 2 +- tests/test_client.py | 25 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) 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/sift/client.py b/sift/client.py index b3719db..831d69a 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..3334d0d 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":{"payment_abuse":{"decision":{"id":"user_decision"},"time":1468707128659,"webhook_succeeded":false}}}' + 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']['payment_abuse']['decision']['id'] == 'user_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}}}' From ef2589a833e38572cfabda08f2bda325123e2a6f Mon Sep 17 00:00:00 2001 From: Michael LeGore Date: Thu, 31 May 2018 14:19:32 -0700 Subject: [PATCH 2/4] Add example to readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) 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') From 4a20f6f76acda704e9da4052f36482e1b6f2aec4 Mon Sep 17 00:00:00 2001 From: Michael LeGore Date: Thu, 31 May 2018 14:19:50 -0700 Subject: [PATCH 3/4] Add Jintae's comments --- sift/client.py | 2 +- tests/test_client.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sift/client.py b/sift/client.py index 831d69a..9b50a35 100644 --- a/sift/client.py +++ b/sift/client.py @@ -538,7 +538,7 @@ def get_session_decisions(self, user_id, session_id, timeout=None): Args: user_id: The ID of a user. - session_id: The ID of a session + session_id: The ID of a session. Returns: A sift.client.Response object if the call succeeded. diff --git a/tests/test_client.py b/tests/test_client.py index 3334d0d..fa1e982 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -945,7 +945,7 @@ def test_get_order_decisions(self): def test_get_session_decisions(self): mock_response = mock.Mock() - mock_response.content = '{"decisions":{"payment_abuse":{"decision":{"id":"user_decision"},"time":1468707128659,"webhook_succeeded":false}}}' + mock_response.content = '{"decisions":{"account_abuse": {"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() @@ -960,7 +960,7 @@ def test_get_session_decisions(self): assert(isinstance(response, sift.client.Response)) assert(response.is_ok()) - assert(response.body['decisions']['payment_abuse']['decision']['id'] == 'user_decision') + assert(response.body['decisions']['account_abuse']['decision']['id'] == 'session_decision') def test_get_content_decisions(self): mock_response = mock.Mock() From 3445b7b52a729227a20b5fb5dd4ce54e6f9a9393 Mon Sep 17 00:00:00 2001 From: Michael LeGore Date: Fri, 1 Jun 2018 09:29:22 -0700 Subject: [PATCH 4/4] getsessiondecisions test use relevant test data --- tests/test_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index fa1e982..5d11280 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -945,7 +945,7 @@ def test_get_order_decisions(self): def test_get_session_decisions(self): mock_response = mock.Mock() - mock_response.content = '{"decisions":{"account_abuse": {"decision": {"id": "session_decision"},"time": 1461963839151,"webhook_succeeded": true}}}' + 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() @@ -960,7 +960,7 @@ def test_get_session_decisions(self): assert(isinstance(response, sift.client.Response)) assert(response.is_ok()) - assert(response.body['decisions']['account_abuse']['decision']['id'] == 'session_decision') + assert(response.body['decisions']['account_takeover']['decision']['id'] == 'session_decision') def test_get_content_decisions(self): mock_response = mock.Mock()