Skip to content

Commit 1bbf04e

Browse files
committed
Merge branch 'dev' into POP-2289
2 parents 7b00dd7 + 49d0948 commit 1bbf04e

File tree

10 files changed

+495
-0
lines changed

10 files changed

+495
-0
lines changed

dynatademand/api.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,22 @@ def buy_project(self, project_id, buy_data):
207207
)
208208
return response_data
209209

210+
def close_project(self, project_id):
211+
# Closes the requested project. Once a project is closed, all traffic
212+
# is stopped, and the project is automatically sent for invoicing.
213+
self.validator.validate_request(
214+
'close_project',
215+
path_data={'extProjectId': '{}'.format(project_id)},
216+
)
217+
response_data = self._api_post('/projects/{}/close'.format(project_id), {})
218+
if response_data.get('status').get('message') != 'success':
219+
raise DemandAPIError(
220+
"Could not close project. Demand API responded with: {}".format(
221+
response_data
222+
)
223+
)
224+
return response_data
225+
210226
def get_project(self, project_id):
211227
self.validator.validate_request(
212228
'get_project',
@@ -246,6 +262,69 @@ def get_project_detailed_report(self, project_id):
246262
)
247263
return self._api_get('/projects/{}/detailedReport'.format(project_id))
248264

265+
def add_line_item(self, project_id, lineitem_data):
266+
'''
267+
A line item is a project entity that exist for a specific market and
268+
language that your survey is aimed at. It defines the target panelists
269+
for the market that the survey is looking for, and the number of
270+
completes required. A line item is our unit of work and is what
271+
gets billed to you.
272+
'''
273+
'''
274+
#TODO: Waiting on a valid request body and path schema.
275+
self.validator.validate_request(
276+
'create_line_item',
277+
path_data={
278+
'extProjectId': '{}'.format(project_id)
279+
},
280+
request_body=lineitem_data
281+
)
282+
'''
283+
response_data = self._api_post('/projects/{}/lineItems'.format(project_id), lineitem_data)
284+
if response_data.get('status').get('message') != 'success':
285+
raise DemandAPIError(
286+
"Could not add line item. Demand API responded with: {}".format(
287+
response_data
288+
)
289+
)
290+
return response_data
291+
292+
def launch_line_item(self, project_id, line_item_id):
293+
# Starts traffic to a line item.
294+
self.validator.validate_request(
295+
'launch_line_item',
296+
path_data={
297+
'extProjectId': '{}'.format(project_id),
298+
'extLineItemId': '{}'.format(line_item_id),
299+
},
300+
)
301+
response_data = self._api_post('/projects/{}/lineItems/{}/launch'.format(project_id, line_item_id), {})
302+
if response_data.get('status').get('message') != 'success':
303+
raise DemandAPIError(
304+
"Could not close project. Demand API responded with: {}".format(
305+
response_data
306+
)
307+
)
308+
return response_data
309+
310+
def pause_line_item(self, project_id, line_item_id):
311+
# Stops traffic to a line item.
312+
self.validator.validate_request(
313+
'pause_line_item',
314+
path_data={
315+
'extProjectId': '{}'.format(project_id),
316+
'extLineItemId': '{}'.format(line_item_id),
317+
},
318+
)
319+
response_data = self._api_post('/projects/{}/lineItems/{}/pause'.format(project_id, line_item_id), {})
320+
if response_data.get('status').get('message') != 'success':
321+
raise DemandAPIError(
322+
"Could not close project. Demand API responded with: {}".format(
323+
response_data
324+
)
325+
)
326+
return response_data
327+
249328
def get_line_item(self, project_id, line_item_id):
250329
self.validator.validate_request(
251330
'get_line_item',
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
{
2+
"type": "object",
3+
"title": "New Line Item",
4+
"properties": {
5+
"extLineItemId": {
6+
"type": "string",
7+
"description": "A unique identifier for your Line Item"
8+
},
9+
"title": {
10+
"type": "string",
11+
"description": "A Line Item title of your choosing"
12+
},
13+
"countryISOCode": {
14+
"type": "string",
15+
"description": "2 letter ISO Country Code",
16+
"minLength": 2,
17+
"maxLength": 2
18+
},
19+
"languageISOCode": {
20+
"type": "string",
21+
"description": "2 letter ISO Language Code",
22+
"minLength": 2,
23+
"maxLength": 2
24+
},
25+
"surveyURL": {
26+
"type": "string",
27+
"description": "Survey URL to send panelist into. "
28+
},
29+
"surveyTestURL": {
30+
"type": "string",
31+
"description": "Survey Test URL. Required for survey verification"
32+
},
33+
"indicativeIncidence": {
34+
"type": "integer",
35+
"description": "Percent of panelists you expect to qualify for the survey after they go in"
36+
},
37+
"daysInField": {
38+
"type": "integer",
39+
"description": "Number of days the survey should be active in the field. We do not stop the survey after daysInField has expired"
40+
},
41+
"lengthOfInterview": {
42+
"type": "integer",
43+
"description": "Average number of minutes it takes a user to complete your survey"
44+
},
45+
"deliveryType": {
46+
"type": "string",
47+
"default": "BALANCED",
48+
"description": "The plan on how responses will flow into the survey.",
49+
"enum": [
50+
"SLOW",
51+
"BALANCED",
52+
"FAST"
53+
]
54+
},
55+
"sources": {
56+
"type": "array",
57+
"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",
58+
"default": 100,
59+
"items": {
60+
"type": "object",
61+
"properties": {
62+
"id": {
63+
"type": "integer"
64+
}
65+
}
66+
}
67+
},
68+
"targets": {
69+
"type": "array",
70+
"description": "Total count of survey completes required for this line item",
71+
"items": {
72+
"type": "object",
73+
"properties": {
74+
"count": {
75+
"description": "Total number of counts required for this line item for the specified target type",
76+
"type": "integer"
77+
},
78+
"dailyLimit": {
79+
"description": "Total number of daily counts required for this line item",
80+
"type": "integer"
81+
},
82+
"type": {
83+
"type": "string",
84+
"description": "The type of counts required for this line item.",
85+
"enum": [
86+
"COMPLETE"
87+
]
88+
}
89+
}
90+
}
91+
},
92+
"quotaPlan": {
93+
"type": "object",
94+
"title": "Quota Plan",
95+
"description": "Defines the type of respondents you want to invite for the survey",
96+
"properties": {
97+
"filters": {
98+
"type": "array",
99+
"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`",
100+
"items": {
101+
"type": "object",
102+
"properties": {
103+
"attributeId": {
104+
"type": "string",
105+
"description": "The attribute you want to target respondents on"
106+
},
107+
"options": {
108+
"type": "array",
109+
"description": "The options of the attribute you want to target respondents on",
110+
"uniqueItems": true,
111+
"items": {
112+
"type": "string"
113+
}
114+
},
115+
"operator": {
116+
"type": "string",
117+
"enum": [
118+
"exclude",
119+
"include"
120+
],
121+
"default": "include",
122+
"description": "The operator to use for the attribute options."
123+
}
124+
}
125+
}
126+
},
127+
"quotaGroups": {
128+
"type": "array",
129+
"description": "Quota groups define the allocated targeting attributes for panelists within this line item. Only attributes that have `isAllowedInQuotas = true` is allowed in `quotaGroups`.",
130+
"items": {
131+
"type": "object",
132+
"properties": {
133+
"name": {
134+
"type": "string",
135+
"description": "A quota group name of your choosing"
136+
},
137+
"quotaCells": {
138+
"type": "array",
139+
"description": "Quota Cells define the percentage allocation for the required targeting. A quota cell is made up of a collection of quota Nodes",
140+
"items": {
141+
"type": "object",
142+
"properties": {
143+
"quotaNodes": {
144+
"type": "array",
145+
"description": "Quota Nodes define the collection of attributes and options being targeted.",
146+
"items": {
147+
"type": "object",
148+
"properties": {
149+
"attributeId": {
150+
"type": "string",
151+
"description": "The attribute you want to target respondents on"
152+
},
153+
"options": {
154+
"type": "array",
155+
"description": "The options of the attribute you want to target respondents on",
156+
"uniqueItems": true,
157+
"items": {
158+
"type": "string"
159+
}
160+
},
161+
"operator": {
162+
"type": "string",
163+
"enum": [
164+
"exclude",
165+
"include"
166+
],
167+
"default": "include",
168+
"description": "**Deprecated field** The operator to use for the attribute options."
169+
}
170+
}
171+
}
172+
},
173+
"count": {
174+
"type": "integer",
175+
"description": "The count of respondents you want to qualify for the defined quota cell"
176+
}
177+
}
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
}
185+
},
186+
"required": [
187+
"extLineItemId",
188+
"countryISOCode",
189+
"languageISOCode",
190+
"indicativeIncidence",
191+
"daysInField",
192+
"lengthOfInterview",
193+
"targets"
194+
]
195+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"extProjectId": {
5+
"type": "string",
6+
"required": true
7+
}
8+
},
9+
"required": [
10+
"extProjectId"
11+
]
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"extProjectId": {
5+
"type": "string",
6+
"required": true
7+
}
8+
},
9+
"required": [
10+
"extProjectId"
11+
]
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"extProjectId": {
5+
"type": "string",
6+
"required": true
7+
},
8+
"extLineItemId": {
9+
"type": "string",
10+
"required": true
11+
}
12+
},
13+
"required": [
14+
"extProjectId",
15+
"extLineItemId"
16+
]
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"extProjectId": {
5+
"type": "string",
6+
"required": true
7+
},
8+
"extLineItemId": {
9+
"type": "string",
10+
"required": true
11+
}
12+
},
13+
"required": [
14+
"extProjectId",
15+
"extLineItemId"
16+
]
17+
}

dynatademand/validator.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@
1111
'create_project': ['body', ],
1212
'get_projects': ['query', ],
1313
'get_project': ['path', ],
14+
'close_project': ['path', ],
1415
'update_project': ['path', 'body', ],
1516
'buy_project': ['path', 'body', ],
1617
'get_project_detailed_report': ['path', ],
1718

1819
# Line items
20+
'create_line_item': ['path', 'body', ],
1921
'get_line_item': ['path', ],
2022
'get_line_items': ['path', 'query'],
2123
'get_line_item_detailed_report': ['path', ],
24+
'launch_line_item': ['path', ],
25+
'pause_line_item': ['path', ],
2226
'update_line_item': ['path', 'body'],
2327

2428
# Events
@@ -58,6 +62,13 @@ def _validate_object(self, schema_type, endpoint_name, data):
5862
jsonschema.validate(schema=self.schemas[schema_type][endpoint_name], instance=data)
5963

6064
def validate_request(self, endpoint_name, path_data=None, query_params=None, request_body=None):
65+
if path_data is None:
66+
path_data = {}
67+
if query_params is None:
68+
query_params = {}
69+
if request_body is None:
70+
request_body = {}
71+
6172
'''
6273
# TODO: None of the path schemas from the documentation are currently valid.
6374
if 'path' in ENDPOINTS[endpoint_name]:

0 commit comments

Comments
 (0)