Skip to content

Commit 543e5b0

Browse files
authored
Merge pull request #6 from researchnow/feature/get-project-endpoint
Get Project
2 parents 8a9b112 + 1158df2 commit 543e5b0

File tree

8 files changed

+420
-5
lines changed

8 files changed

+420
-5
lines changed

dynatademand/api.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
import os
22
import requests
3+
34
from .errors import DemandAPIError
45

56

67
class DemandAPIClient(object):
7-
def __init__(self):
8-
self.client_id = os.getenv('DYNATA_DEMAND_CLIENT_ID', None)
9-
self.username = os.getenv('DYNATA_DEMAND_USERNAME', None)
10-
self.password = os.getenv('DYNATA_DEMAND_PASSWORD', None)
8+
def __init__(self, client_id=None, username=None, password=None, base_host=None):
9+
if client_id is not None:
10+
self.client_id = client_id
11+
else:
12+
self.client_id = os.getenv('DYNATA_DEMAND_CLIENT_ID', None)
13+
if username is not None:
14+
self.username = username
15+
else:
16+
self.username = os.getenv('DYNATA_DEMAND_USERNAME', None)
17+
if password is not None:
18+
self.password = password
19+
else:
20+
self.password = os.getenv('DYNATA_DEMAND_PASSWORD', None)
21+
if base_host is not None:
22+
self.base_host = base_host
23+
else:
24+
self.base_host = os.getenv('DYNATA_DEMAND_BASE_URL', default='https://api.researchnow.com')
25+
1126
if None in [self.client_id, self.username, self.password]:
1227
raise DemandAPIError("All authentication data is required.")
28+
1329
self._access_token = None
1430
self._refresh_token = None
15-
self.base_host = os.getenv('DYNATA_DEMAND_BASE_URL', default='https://api.researchnow.com')
1631
self.auth_base_url = '{}/auth/v1'.format(self.base_host)
1732
self.base_url = '{}/sample/v1'.format(self.base_host)
1833

@@ -35,6 +50,21 @@ def _api_post(self, uri, payload):
3550
))
3651
return response.json()
3752

53+
def _api_get(self, uri, query_params=None):
54+
# Send an authenticated POST request to an API endpoint.
55+
self._check_authentication()
56+
url = '{}{}'.format(self.base_url, uri)
57+
request_headers = {
58+
'oauth_access_token': self._access_token,
59+
'Content-Type': "application/json",
60+
}
61+
response = requests.get(url=url, params=query_params, headers=request_headers)
62+
if response.status_code > 399:
63+
raise DemandAPIError('Demand API request to {} failed with status {}. Response: {}'.format(
64+
url, response.status_code, response.content
65+
))
66+
return response.json()
67+
3868
def authenticate(self):
3969
url = '{}/token/password'.format(self.auth_base_url)
4070
auth_response = requests.post(url, json={
@@ -79,3 +109,12 @@ def logout(self):
79109
logout_response.status_code, logout_response.content
80110
))
81111
return logout_response.json()
112+
113+
def get_project(self, project_id):
114+
return self._api_get('/projects/{}'.format(project_id))
115+
116+
def get_lineitem(self, project_id, lineitem_id):
117+
return self._api_get('/projects/{}/lineItems/{}'.format(project_id, lineitem_id))
118+
119+
def get_feasibility(self, project_id):
120+
return self._api_get('/projects/{}/feasibility'.format(project_id))

tests/test_authentication.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,33 @@
1616

1717

1818
class AuthenticationTestMissingCredentials(unittest.TestCase):
19+
def test_authentication_params(self):
20+
DemandAPIClient(client_id="test", username="test", password="test", base_host=BASE_URL)
21+
22+
with self.assertRaises(DemandAPIError):
23+
DemandAPIClient(username="test", password="test", base_host=BASE_URL)
24+
25+
with self.assertRaises(DemandAPIError):
26+
DemandAPIClient(client_id="test", password="test", base_host=BASE_URL)
27+
28+
with self.assertRaises(DemandAPIError):
29+
DemandAPIClient(client_id="test", username="test", base_host=BASE_URL)
30+
31+
@patch('os.getenv')
32+
def test_authentication_params_with_env(self, mock_getenv):
33+
mock_getenv.side_effect = [
34+
"test_client_id",
35+
"test_username",
36+
"test_password",
37+
BASE_URL
38+
]
39+
40+
# None of these should raise an error if the appropriate missing data is available in the environment.
41+
DemandAPIClient(client_id="test", username="test", password="test", base_host=BASE_URL)
42+
DemandAPIClient(username="test", password="test", base_host=BASE_URL)
43+
DemandAPIClient(client_id="test", password="test", base_host=BASE_URL)
44+
DemandAPIClient(client_id="test", username="test", base_host=BASE_URL)
45+
1946
@patch('os.getenv')
2047
def test_missing_client_id(self, mock_getenv):
2148
mock_getenv.side_effect = [

tests/test_feasibility_pricing.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# encoding: utf-8
2+
from __future__ import unicode_literals, print_function
3+
4+
import json
5+
import unittest
6+
import responses
7+
8+
try:
9+
from unittest.mock import patch
10+
except ImportError:
11+
from mock import patch
12+
13+
from dynatademand.api import DemandAPIClient
14+
15+
BASE_HOST = "http://test-url.example"
16+
17+
18+
class TestFeasibilityEndpoints(unittest.TestCase):
19+
def setUp(self):
20+
self.api = DemandAPIClient(client_id='test', username='testuser', password='testpass', base_host=BASE_HOST)
21+
self.api._access_token = 'Bearer testtoken'
22+
23+
@responses.activate
24+
def test_get_feasibility(self):
25+
with open('./tests/test_files/get_feasibility.json', 'r') as project_file:
26+
project_json = json.load(project_file)
27+
responses.add(responses.GET, '{}/sample/v1/projects/1/feasibility'.format(BASE_HOST), json=project_json, status=200)
28+
self.api.get_feasibility(1)
29+
self.assertEqual(len(responses.calls), 1)
30+
self.assertEqual(responses.calls[0].response.json(), project_json)

tests/test_files/get_feasibility.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"data": [
3+
{
4+
"extLineItemId": "lineItem001",
5+
"feasibility": {
6+
"status": "READY",
7+
"costPerInterview": 3.14,
8+
"expiry": "05/01/2018 00:00:00",
9+
"currency": "USD",
10+
"feasible": true,
11+
"totalCount": 50343,
12+
"valueCounts": [
13+
{
14+
"quotaCells": [
15+
{
16+
"quotaNodes": [
17+
{
18+
"attributeId": "11",
19+
"options": [
20+
"1"
21+
]
22+
}
23+
],
24+
"feasibilityCount": 1000
25+
},
26+
{
27+
"quotaNodes": [
28+
{
29+
"attributeId": "11",
30+
"options": [
31+
"2"
32+
]
33+
}
34+
],
35+
"feasibilityCount": 1200
36+
}
37+
]
38+
}
39+
]
40+
},
41+
"quote": {
42+
"costPerUnit": 3.14,
43+
"currency": "USD",
44+
"detailedQuote": [
45+
{
46+
"costPerUnit": 2.14,
47+
"estimatedCost": 2140,
48+
"title": "Completes",
49+
"type": "BASE",
50+
"units": 1000
51+
},
52+
{
53+
"costPerUnit": 1,
54+
"estimatedCost": 1000,
55+
"title": "Completes",
56+
"type": "PREMIUM",
57+
"units": 1000
58+
}
59+
],
60+
"estimatedCost": 3140
61+
}
62+
},
63+
{
64+
"extLineItemId": "lineItem002",
65+
"feasibility": {
66+
"status": "PROCESSING",
67+
"costPerInterview": null,
68+
"expiry": null,
69+
"currency": null,
70+
"feasible": null,
71+
"totalCount": null,
72+
"valueCounts": []
73+
}
74+
}
75+
]
76+
}

tests/test_files/get_lineitem.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"extLineItemId": "lineItem002",
3+
"title": "US College",
4+
"countryISOCode": "US",
5+
"languageISOCode": "en",
6+
"surveyURL": "www.mysurvey.com/live/survey?pid=<#DubKnowledge[1500/Entity id]>&k2=<#Project[Secure Key 2]>&psid=<#IdParameter[Value]>",
7+
"surveyTestURL": "www.mysurvey.com/test/survey?pid=<#DubKnowledge[1500/Entity id]>&k2=<#Project[Secure Key 2]>&psid=<#IdParameter[Value]>",
8+
"indicativeIncidence": 20,
9+
"daysInField": 20,
10+
"lengthOfInterview": 10,
11+
"deliveryType": "BALANCED",
12+
"sources": [
13+
{
14+
"id": 100
15+
}
16+
],
17+
"targets": [
18+
{
19+
"count": 200,
20+
"dailyLimit": 0,
21+
"type": "COMPLETE"
22+
}
23+
],
24+
"quotaPlan": {
25+
"filters": [
26+
{
27+
"attributeId": "61961",
28+
"options": [
29+
"3",
30+
"4"
31+
],
32+
"operator": "include"
33+
}
34+
],
35+
"quotaGroups": [
36+
{
37+
"name": "Gender Distribution",
38+
"quotaCells": [
39+
{
40+
"quotaNodes": [
41+
{
42+
"attributeId": "11",
43+
"options": [
44+
"1"
45+
]
46+
}
47+
],
48+
"count": 130
49+
},
50+
{
51+
"quotaNodes": [
52+
{
53+
"attributeId": "11",
54+
"options": [
55+
"2"
56+
]
57+
}
58+
],
59+
"count": 70
60+
}
61+
]
62+
}
63+
]
64+
},
65+
"state": "PROVISIONED",
66+
"stateReason": "Created by Client",
67+
"stateLastUpdatedAt": "04/01/2018 00:00:00",
68+
"createdAt": "04/01/2018 00:00:00",
69+
"updatedAt": "04/01/2018 00:00:00",
70+
"launchedAt": null,
71+
"endLinks": {
72+
"complete": "https://api.researchnow.com/respondent/exit?rst=1&psid={psid}&med={calculatedSecurityCode}",
73+
"screenout": "https://api.researchnow.com/respondent/exit?rst=2&psid={psid}",
74+
"overquota": "https://api.researchnow.com/respondent/exit?rst=3&psid={psid}",
75+
"securityKey1": "35040",
76+
"securityLevel": "MEDIUM"
77+
}
78+
}

0 commit comments

Comments
 (0)