From 41712ddf140507fcb0f99904d508f46cfb9c9857 Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Wed, 14 Mar 2018 15:04:53 +0530 Subject: [PATCH 1/7] Adding ability to pull reports --- appnexus/client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/appnexus/client.py b/appnexus/client.py index a99d49e..b6ec0a7 100644 --- a/appnexus/client.py +++ b/appnexus/client.py @@ -84,7 +84,13 @@ def _send(self, send_method, service, data=None, **kwargs): logger.debug(' '.join(map(str, (headers, uri, data)))) response = send_method(uri, headers=headers, json=data) - response_data = response.json() + content_type = response.headers['Content-Type'].split(';')[0] + + if content_type == 'application/json': + response_data = response.json() + else: + return response.content + try: self.check_errors(response, response_data["response"]) except RateExceeded: @@ -227,7 +233,7 @@ def base_url(self): "PostalCode", "Profile", "ProfileSummary", "Publisher", "Region", "ReportStatus", "Search", "Segment", "Site", "TechnicalAttribute", "Template", "ThirdpartyPixel", "User", - "UsergroupPattern", "VisibilityProfile"] + "UsergroupPattern", "VisibilityProfile", "Report"] class Service(object): From 4843032d3ff02cfa4008635e64af7e1b73dc8ecf Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Wed, 14 Mar 2018 15:06:56 +0530 Subject: [PATCH 2/7] Report Model to store data related to reporting and download them --- appnexus/model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/appnexus/model.py b/appnexus/model.py index 6a132f2..cf79a38 100644 --- a/appnexus/model.py +++ b/appnexus/model.py @@ -74,7 +74,7 @@ def save(self, **kwargs): result = self.create(payload, **kwargs) else: result = self.modify(payload, id=self.id, **kwargs) - return result + return type(self)(result) class Campaign(Model): @@ -84,6 +84,12 @@ def profile(self): return Profile.find_one(id=self.profile_id) +class Report(Model): + + def download(self, **kwargs): + return self.client.get("report-download", id=self.report_id) + + def create_models(services_list): for service in services_list: model = type(service, (Model,), {}) From bafe447ca0de62661ff069cb440acd654fbba503 Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Tue, 13 Mar 2018 17:53:19 +0530 Subject: [PATCH 3/7] Fixed bug with raising exception; key 'error_code' is not present always and results in keyError --- appnexus/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appnexus/exceptions.py b/appnexus/exceptions.py index 9d05956..6f81503 100644 --- a/appnexus/exceptions.py +++ b/appnexus/exceptions.py @@ -8,7 +8,7 @@ def __str__(self): if not self.response: return "Error with AppNexus API" data = self.response.json()["response"] - error_name = data["error_code"] or data["error_id"] + error_name = data.get('error_code', data["error_id"]) description_error = data.get("error", "(indisponible)") return "{}: {}".format(error_name, description_error) From b553dc58569d9e28492a96301ab413d83dd64b7f Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Tue, 13 Mar 2018 17:57:25 +0530 Subject: [PATCH 4/7] Fixed tests for exceptions --- tests/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/exceptions.py b/tests/exceptions.py index 18ac1ac..e3ad567 100644 --- a/tests/exceptions.py +++ b/tests/exceptions.py @@ -9,6 +9,7 @@ def appnexus_exception(mocker): response.json.return_value = { "response": { "error_code": "INVALID_LOGIN", + "error_id": "INVALID_LOGIN", "error": "You didn't provided credentials" } } From 98365ce308473d454bda5efddccf66cb598bdb46 Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Tue, 13 Mar 2018 13:49:52 +0530 Subject: [PATCH 5/7] Fixed tests to pass --- tests/client.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/client.py b/tests/client.py index a287840..a0f7cde 100644 --- a/tests/client.py +++ b/tests/client.py @@ -103,6 +103,7 @@ def test_check_errors_noauth(mocker, client): def test_send_success(mocker, connected_client): mocker.patch("requests.get") + requests.get.return_value.headers = {'Content-Type': 'application/json'} requests.get().json.return_value = {"response": {"campaign": {}}} response = connected_client._send(requests.get, "campaign", id=3) assert "campaign" in response @@ -112,6 +113,7 @@ def test_send_reconnect(mocker, connected_client): mocker.patch("requests.get") mocker.patch("requests.post") requests.post().json.return_value = {"response": {"token": token}} + requests.get.return_value.headers = {'Content-Type': 'application/json'} requests.get().json.side_effect = [{"response": {"error_id": "NOAUTH"}}, {"response": {"campaign": {}}}] response = connected_client._send(requests.get, "campaign", id=3) @@ -126,12 +128,14 @@ def test_send_handle_rate_exceeded(mocker, connected_client): {"response": {"error_code": "RATE_EXCEEDED"}}, {"response": {"campaign": {}}} ] + requests.get.return_value.headers = {'Content-Type': 'application/json'} connected_client._send(requests.get, "campaign", id=3) assert connected_client._handle_rate_exceeded.called def test_send_unknown_error(mocker, connected_client): mocker.patch("requests.get") + requests.get.return_value.headers = {'Content-Type': 'application/json'} requests.get().json.return_value = {"response": {"error_id": "WHATEVER"}} with pytest.raises(AppNexusException): connected_client._send(requests.get, "campaign", id=3) @@ -148,6 +152,7 @@ def test_send_method_send_json(mocker, connected_client): def test_send_raw(mocker, connected_client): mocker.patch("requests.get") requests.get().json.return_value = {"response": {"campaign": {}}} + requests.get.return_value.headers = {'Content-Type': 'application/json'} response = connected_client._send(requests.get, "campaign", id=3, raw=True) assert "response" in response @@ -155,6 +160,7 @@ def test_send_raw(mocker, connected_client): def test_get_return_dict(mocker, connected_client): mocker.patch("requests.get") requests.get().json.return_value = {"response": {"campaign": {}}} + requests.get.return_value.headers = {'Content-Type': 'application/json'} cursor = connected_client.get("campaign") assert isinstance(cursor, dict) @@ -162,6 +168,7 @@ def test_get_return_dict(mocker, connected_client): def test_modify_return_dict(mocker, connected_client): mocker.patch("requests.put") requests.put().json.return_value = {"response": {"campaign": {}}} + requests.put.return_value.headers = {'Content-Type': 'application/json'} cursor = connected_client.modify("campaign", None) assert isinstance(cursor, dict) @@ -177,6 +184,7 @@ def test_modify_send_json(mocker, connected_client): def test_create_return_dict(mocker, connected_client): mocker.patch("requests.post") requests.post().json.return_value = {"response": {"campaign": {}}} + requests.post.return_value.headers = {'Content-Type': 'application/json'} cursor = connected_client.create("campaign", None) assert isinstance(cursor, dict) @@ -192,6 +200,7 @@ def test_create_send_json(mocker, connected_client): def test_delete_return_dict(mocker, connected_client): mocker.patch("requests.delete") requests.delete().json.return_value = {"response": {"campaign": {}}} + requests.delete.return_value.headers = {'Content-Type': 'application/json'} cursor = connected_client.delete("campaign", 42) assert isinstance(cursor, dict) @@ -208,6 +217,7 @@ def test_delete_send_ids(mocker, connected_client): def test_append_return_dict(mocker, connected_client): mocker.patch("requests.put") requests.put().json.return_value = {"response": {"campaign": {}}} + requests.put.return_value.headers = {'Content-Type': 'application/json'} cursor = connected_client.append("campaign", None) assert isinstance(cursor, dict) From f6c7374afe6a9164979b7051187c7d89d155b95a Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Wed, 14 Mar 2018 16:05:50 +0530 Subject: [PATCH 6/7] Added retry logic and is_ready method to check if the report is ready for download --- appnexus/model.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/appnexus/model.py b/appnexus/model.py index cf79a38..9a7d4a5 100644 --- a/appnexus/model.py +++ b/appnexus/model.py @@ -1,5 +1,6 @@ import logging import re +import time from thingy import Thingy @@ -86,9 +87,17 @@ def profile(self): class Report(Model): - def download(self, **kwargs): + def download(self, retry_count=3, **kwargs): + # Check if the report is ready to download + while self.is_ready() != 'ready' and retry_count > 0: + retry_count -= 1 + time.sleep(1) + return self.client.get("report-download", id=self.report_id) + def is_ready(self): + return self.client.get('report', id=self.report_id)['execution_status'] + def create_models(services_list): for service in services_list: From 43cdcbcd8cb01396c1d1fad552ee639d2ede4fed Mon Sep 17 00:00:00 2001 From: Mevin Babu Date: Wed, 14 Mar 2018 16:17:46 +0530 Subject: [PATCH 7/7] Added logging for retry --- appnexus/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/appnexus/model.py b/appnexus/model.py index 9a7d4a5..24a4292 100644 --- a/appnexus/model.py +++ b/appnexus/model.py @@ -90,6 +90,7 @@ class Report(Model): def download(self, retry_count=3, **kwargs): # Check if the report is ready to download while self.is_ready() != 'ready' and retry_count > 0: + logger.debug("Report not ready yet; retrying again") retry_count -= 1 time.sleep(1)