Skip to content

Commit e12378d

Browse files
Merge branch 'dev' into POP-2274
2 parents c1bf159 + e6ef332 commit e12378d

File tree

8 files changed

+454
-1
lines changed

8 files changed

+454
-1
lines changed

dynatademand/api.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import json
2+
import jsonschema
13
import os
24
import requests
35

46
from .errors import DemandAPIError
57

8+
SCHEMAS = [
9+
"project_new",
10+
]
11+
612

713
class DemandAPIClient(object):
814
def __init__(self, client_id=None, username=None, password=None, base_host=None):
@@ -31,7 +37,24 @@ def __init__(self, client_id=None, username=None, password=None, base_host=None)
3137
self.auth_base_url = '{}/auth/v1'.format(self.base_host)
3238
self.base_url = '{}/sample/v1'.format(self.base_host)
3339

40+
self._load_schemas()
41+
42+
def _load_schemas(self):
43+
# Load the compiled schemas for use in validation.
44+
self._schemas = {}
45+
for schema_type in SCHEMAS:
46+
schema_file = open('dynatademand/schemas/{}.json'.format(schema_type), 'r')
47+
self._schemas[schema_type] = json.load(schema_file)
48+
schema_file.close()
49+
50+
def _validate_object(self, schema_type, data):
51+
# jsonschema.validate will return none if there is no error,
52+
# otherwise it will raise its' own error with details on the failure.
53+
jsonschema.validate(self._schemas[schema_type], data)
54+
3455
def _check_authentication(self):
56+
# This doesn't check if the access token is valid, just that it exists.
57+
# The access_token is generated by calling the `authenticate` method.
3558
if self._access_token is None:
3659
raise DemandAPIError('The API instance must be authenticated before calling this method.')
3760

@@ -66,6 +89,7 @@ def _api_get(self, uri, query_params=None):
6689
return response.json()
6790

6891
def authenticate(self):
92+
# Sends the authentication data to
6993
url = '{}/token/password'.format(self.auth_base_url)
7094
auth_response = requests.post(url, json={
7195
'clientId': self.client_id,
@@ -113,6 +137,18 @@ def logout(self):
113137
def get_attributes(self, country_code, language_code):
114138
return self._api_get('/attributes/{}/{}'.format(country_code, language_code))
115139

140+
def create_project(self, project_data):
141+
# Creates a new project. Uses the "new project" schema.
142+
self._validate_object("project_new", project_data)
143+
response_data = self._api_post('/projects', project_data)
144+
if response_data.get('status').get('message') != 'success':
145+
raise DemandAPIError(
146+
"Could not create project. Demand API responded with: {}".format(
147+
response_data
148+
)
149+
)
150+
return response_data
151+
116152
def get_project(self, project_id):
117153
return self._api_get('/projects/{}'.format(project_id))
118154

@@ -121,3 +157,6 @@ def get_lineitem(self, project_id, lineitem_id):
121157

122158
def get_feasibility(self, project_id):
123159
return self._api_get('/projects/{}/feasibility'.format(project_id))
160+
161+
def get_sources(self):
162+
return self._api_get('/sources')

dynatademand/schemas/project_new.json

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
{
2+
"type": "object",
3+
"title": "New Project",
4+
"properties": {
5+
"extProjectId": {
6+
"type": "string",
7+
"description": "A unique identifier for your project",
8+
"maxLength": 255
9+
},
10+
"title": {
11+
"type": "string",
12+
"description": "A project title of your choosing",
13+
"maxLength": 255
14+
},
15+
"jobNumber": {
16+
"type": "string",
17+
"description": "A job number of your choosing",
18+
"maxLength": 100
19+
},
20+
"notificationEmails": {
21+
"type": "array",
22+
"description": "The email addresses to receive notifications",
23+
"format": "email",
24+
"items": {
25+
"type": "string"
26+
}
27+
},
28+
"devices": {
29+
"type": "array",
30+
"description": "Device targeting for the project",
31+
"enum": [
32+
"mobile",
33+
"desktop",
34+
"tablet"
35+
],
36+
"default": [
37+
"mobile",
38+
"desktop",
39+
"tablet"
40+
],
41+
"items": {
42+
"type": "string"
43+
}
44+
},
45+
"category": {
46+
"type": "object",
47+
"required": [
48+
"surveyTopic"
49+
],
50+
"properties": {
51+
"surveyTopic": {
52+
"type": "array",
53+
"description": "List of possible topics for a survey.",
54+
"items": {
55+
"type": "string",
56+
"minLength": 1
57+
}
58+
}
59+
}
60+
},
61+
"lineItems": {
62+
"type": "array",
63+
"uniqueItems": true,
64+
"minItems": 1,
65+
"items": {
66+
"type": "object",
67+
"title": "New Line Item",
68+
"required": [
69+
"extLineItemId",
70+
"countryISOCode",
71+
"languageISOCode",
72+
"indicativeIncidence",
73+
"daysInField",
74+
"lengthOfInterview",
75+
"targets"
76+
],
77+
"properties": {
78+
"extLineItemId": {
79+
"type": "string",
80+
"description": "A unique identifier for your Line Item"
81+
},
82+
"title": {
83+
"type": "string",
84+
"description": "A Line Item title of your choosing"
85+
},
86+
"countryISOCode": {
87+
"type": "string",
88+
"description": "2 letter ISO Country Code",
89+
"minLength": 2,
90+
"maxLength": 2
91+
},
92+
"languageISOCode": {
93+
"type": "string",
94+
"description": "2 letter ISO Language Code",
95+
"minLength": 2,
96+
"maxLength": 2
97+
},
98+
"surveyURL": {
99+
"type": "string",
100+
"description": "Survey URL to send panelist into. "
101+
},
102+
"surveyTestURL": {
103+
"type": "string",
104+
"description": "Survey Test URL. Required for survey verification"
105+
},
106+
"indicativeIncidence": {
107+
"type": "integer",
108+
"description": "Percent of panelists you expect to qualify for the survey after they go in"
109+
},
110+
"daysInField": {
111+
"type": "integer",
112+
"description": "Number of days the survey should be active in the field. We do not stop the survey after daysInField has expired"
113+
},
114+
"lengthOfInterview": {
115+
"type": "integer",
116+
"description": "Average number of minutes it takes a user to complete your survey"
117+
},
118+
"deliveryType": {
119+
"type": "string",
120+
"default": "BALANCED",
121+
"description": "The plan on how responses will flow into the survey.",
122+
"enum": [
123+
"SLOW",
124+
"BALANCED",
125+
"FAST"
126+
]
127+
},
128+
"sources": {
129+
"type": "array",
130+
"description": "The supplier source that you would like to buy sample from. You can get the list of sources available to your account using the [Get List of Sources](/demand-api-reference/data_endpoints/supplier-sources/get-sources) endpoint",
131+
"default": 100,
132+
"items": {
133+
"type": "object",
134+
"properties": {
135+
"id": {
136+
"type": "integer"
137+
}
138+
}
139+
}
140+
},
141+
"targets": {
142+
"type": "array",
143+
"description": "Total count of survey completes required for this line item",
144+
"items": {
145+
"type": "object",
146+
"properties": {
147+
"count": {
148+
"description": "Total number of counts required for this line item for the specified target type",
149+
"type": "integer"
150+
},
151+
"dailyLimit": {
152+
"description": "Total number of daily counts required for this line item",
153+
"type": "integer"
154+
},
155+
"type": {
156+
"type": "string",
157+
"description": "The type of counts required for this line item.",
158+
"enum": [
159+
"COMPLETE"
160+
]
161+
}
162+
}
163+
}
164+
},
165+
"quotaPlan": {
166+
"type": "object",
167+
"title": "Quota Plan",
168+
"description": "Defines the type of respondents you want to invite for the survey",
169+
"properties": {
170+
"filters": {
171+
"type": "array",
172+
"description": "Filters are minimum set of targeting that every respondent must have in order to qualify for the study. Only attributes that have `isAllowedInFilters = true` is allowed to be used in `filters`",
173+
"items": {
174+
"type": "object",
175+
"properties": {
176+
"attributeId": {
177+
"type": "string",
178+
"description": "The attribute you want to target respondents on"
179+
},
180+
"options": {
181+
"type": "array",
182+
"description": "The options of the attribute you want to target respondents on",
183+
"uniqueItems": true,
184+
"items": {
185+
"type": "string"
186+
}
187+
},
188+
"operator": {
189+
"type": "string",
190+
"enum": [
191+
"exclude",
192+
"include"
193+
],
194+
"default": "include",
195+
"description": "The operator to use for the attribute options."
196+
}
197+
}
198+
}
199+
},
200+
"quotaGroups": {
201+
"type": "array",
202+
"description": "Quota groups define the allocated targeting attributes for panelists within this line item. Only attributes that have `isAllowedInQuotas = true` is allowed in `quotaGroups`.",
203+
"items": {
204+
"type": "object",
205+
"properties": {
206+
"name": {
207+
"type": "string",
208+
"description": "A quota group name of your choosing"
209+
},
210+
"quotaCells": {
211+
"type": "array",
212+
"description": "Quota Cells define the percentage allocation for the required targeting. A quota cell is made up of a collection of quota Nodes",
213+
"items": {
214+
"type": "object",
215+
"properties": {
216+
"quotaNodes": {
217+
"type": "array",
218+
"description": "Quota Nodes define the collection of attributes and options being targeted.",
219+
"items": {
220+
"type": "object",
221+
"properties": {
222+
"attributeId": {
223+
"type": "string",
224+
"description": "The attribute you want to target respondents on"
225+
},
226+
"options": {
227+
"type": "array",
228+
"description": "The options of the attribute you want to target respondents on",
229+
"uniqueItems": true,
230+
"items": {
231+
"type": "string"
232+
}
233+
},
234+
"operator": {
235+
"type": "string",
236+
"enum": [
237+
"exclude",
238+
"include"
239+
],
240+
"default": "include",
241+
"description": "**Deprecated field** The operator to use for the attribute options."
242+
}
243+
}
244+
}
245+
},
246+
"count": {
247+
"type": "integer",
248+
"description": "The count of respondents you want to qualify for the defined quota cell"
249+
}
250+
}
251+
}
252+
}
253+
}
254+
}
255+
}
256+
}
257+
}
258+
}
259+
}
260+
},
261+
"exclusions": {
262+
"type": "object",
263+
"properties": {
264+
"type": {
265+
"type": "string"
266+
},
267+
"list": {
268+
"type": "array",
269+
"items": {
270+
"type": "object"
271+
}
272+
}
273+
}
274+
}
275+
},
276+
"required": [
277+
"extProjectId",
278+
"category",
279+
"lineItems"
280+
]
281+
}

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
jsonschema==3.2.0
12
pytest==4.6.6
23
pytest-runner==5.2
34
requests==2.22.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
download_url="https://pypi.python.org/pypi/dynata-demandapi-client",
1313
packages=find_packages(exclude=('tests', )),
1414
platforms=['Any'],
15-
install_requires=['requests', ],
15+
install_requires=['requests', 'jsonschema'],
1616
setup_requires=['pytest-runner'],
1717
tests_require=['pytest'],
1818
keywords='dynata demand api',

0 commit comments

Comments
 (0)