Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(483)

Side by Side Diff: third_party/gsutil/20110627/boto/boto/mturk/connection.py

Issue 10199002: Upgrade gsutil to 3.4 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed comments Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a
4 # copy of this software and associated documentation files (the
5 # "Software"), to deal in the Software without restriction, including
6 # without limitation the rights to use, copy, modify, merge, publish, dis-
7 # tribute, sublicense, and/or sell copies of the Software, and to permit
8 # persons to whom the Software is furnished to do so, subject to the fol-
9 # lowing conditions:
10 #
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 # IN THE SOFTWARE.
21
22 import xml.sax
23 import datetime
24 import itertools
25
26 from boto import handler
27 from boto import config
28 from boto.mturk.price import Price
29 import boto.mturk.notification
30 from boto.connection import AWSQueryConnection
31 from boto.exception import EC2ResponseError
32 from boto.resultset import ResultSet
33 from boto.mturk.question import QuestionForm, ExternalQuestion
34
35 class MTurkRequestError(EC2ResponseError):
36 "Error for MTurk Requests"
37 # todo: subclass from an abstract parent of EC2ResponseError
38
39 class MTurkConnection(AWSQueryConnection):
40
41 APIVersion = '2008-08-02'
42
43 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
44 is_secure=False, port=None, proxy=None, proxy_port=None,
45 proxy_user=None, proxy_pass=None,
46 host=None, debug=0,
47 https_connection_factory=None):
48 if not host:
49 if config.has_option('MTurk', 'sandbox') and config.get('MTurk', 'sa ndbox') == 'True':
50 host = 'mechanicalturk.sandbox.amazonaws.com'
51 else:
52 host = 'mechanicalturk.amazonaws.com'
53
54 AWSQueryConnection.__init__(self, aws_access_key_id,
55 aws_secret_access_key,
56 is_secure, port, proxy, proxy_port,
57 proxy_user, proxy_pass, host, debug,
58 https_connection_factory)
59
60 def _required_auth_capability(self):
61 return ['mturk']
62
63 def get_account_balance(self):
64 """
65 """
66 params = {}
67 return self._process_request('GetAccountBalance', params,
68 [('AvailableBalance', Price),
69 ('OnHoldBalance', Price)])
70
71 def register_hit_type(self, title, description, reward, duration,
72 keywords=None, approval_delay=None, qual_req=None):
73 """
74 Register a new HIT Type
75 title, description are strings
76 reward is a Price object
77 duration can be a timedelta, or an object castable to an int
78 """
79 params = dict(
80 Title=title,
81 Description=description,
82 AssignmentDurationInSeconds=
83 self.duration_as_seconds(duration),
84 )
85 params.update(MTurkConnection.get_price_as_price(reward).get_as_params(' Reward'))
86
87 if keywords:
88 params['Keywords'] = self.get_keywords_as_string(keywords)
89
90 if approval_delay is not None:
91 d = self.duration_as_seconds(approval_delay)
92 params['AutoApprovalDelayInSeconds'] = d
93
94 if qual_req is not None:
95 params.update(qual_req.get_as_params())
96
97 return self._process_request('RegisterHITType', params)
98
99 def set_email_notification(self, hit_type, email, event_types=None):
100 """
101 Performs a SetHITTypeNotification operation to set email
102 notification for a specified HIT type
103 """
104 return self._set_notification(hit_type, 'Email', email, event_types)
105
106 def set_rest_notification(self, hit_type, url, event_types=None):
107 """
108 Performs a SetHITTypeNotification operation to set REST notification
109 for a specified HIT type
110 """
111 return self._set_notification(hit_type, 'REST', url, event_types)
112
113 def _set_notification(self, hit_type, transport, destination, event_types=No ne):
114 """
115 Common SetHITTypeNotification operation to set notification for a
116 specified HIT type
117 """
118 assert type(hit_type) is str, "hit_type argument should be a string."
119
120 params = {'HITTypeId': hit_type}
121
122 # from the Developer Guide:
123 # The 'Active' parameter is optional. If omitted, the active status of
124 # the HIT type's notification specification is unchanged. All HIT types
125 # begin with their notification specifications in the "inactive" status.
126 notification_params = {'Destination': destination,
127 'Transport': transport,
128 'Version': boto.mturk.notification.NotificationMe ssage.NOTIFICATION_VERSION,
129 'Active': True,
130 }
131
132 # add specific event types if required
133 if event_types:
134 self.build_list_params(notification_params, event_types, 'EventType' )
135
136 # Set up dict of 'Notification.1.Transport' etc. values
137 notification_rest_params = {}
138 num = 1
139 for key in notification_params:
140 notification_rest_params['Notification.%d.%s' % (num, key)] = notifi cation_params[key]
141
142 # Update main params dict
143 params.update(notification_rest_params)
144
145 # Execute operation
146 return self._process_request('SetHITTypeNotification', params)
147
148 def create_hit(self, hit_type=None, question=None,
149 lifetime=datetime.timedelta(days=7),
150 max_assignments=1,
151 title=None, description=None, keywords=None,
152 reward=None, duration=datetime.timedelta(days=7),
153 approval_delay=None, annotation=None,
154 questions=None, qualifications=None,
155 response_groups=None):
156 """
157 Creates a new HIT.
158 Returns a ResultSet
159 See: http://docs.amazonwebservices.com/AWSMechanicalTurkRequester/2006-1 0-31/ApiReference_CreateHITOperation.html
160 """
161
162 # handle single or multiple questions
163 neither = question is None and questions is None
164 both = question is not None and questions is not None
165 if neither or both:
166 raise ValueError("Must specify either question (single Question inst ance) or questions (list or QuestionForm instance), but not both")
167
168 if question:
169 questions = [question]
170 question_param = QuestionForm(questions)
171 if isinstance(question, QuestionForm):
172 question_param = question
173 elif isinstance(question, ExternalQuestion):
174 question_param = question
175
176 # Handle basic required arguments and set up params dict
177 params = {'Question': question_param.get_as_xml(),
178 'LifetimeInSeconds' :
179 self.duration_as_seconds(lifetime),
180 'MaxAssignments' : max_assignments,
181 }
182
183 # if hit type specified then add it
184 # else add the additional required parameters
185 if hit_type:
186 params['HITTypeId'] = hit_type
187 else:
188 # Handle keywords
189 final_keywords = MTurkConnection.get_keywords_as_string(keywords)
190
191 # Handle price argument
192 final_price = MTurkConnection.get_price_as_price(reward)
193
194 final_duration = self.duration_as_seconds(duration)
195
196 additional_params = dict(
197 Title=title,
198 Description=description,
199 Keywords=final_keywords,
200 AssignmentDurationInSeconds=final_duration,
201 )
202 additional_params.update(final_price.get_as_params('Reward'))
203
204 if approval_delay is not None:
205 d = self.duration_as_seconds(approval_delay)
206 additional_params['AutoApprovalDelayInSeconds'] = d
207
208 # add these params to the others
209 params.update(additional_params)
210
211 # add the annotation if specified
212 if annotation is not None:
213 params['RequesterAnnotation'] = annotation
214
215 # Add the Qualifications if specified
216 if qualifications is not None:
217 params.update(qualifications.get_as_params())
218
219 # Handle optional response groups argument
220 if response_groups:
221 self.build_list_params(params, response_groups, 'ResponseGroup')
222
223 # Submit
224 return self._process_request('CreateHIT', params, [('HIT', HIT),])
225
226 def change_hit_type_of_hit(self, hit_id, hit_type):
227 """
228 Change the HIT type of an existing HIT. Note that the reward associated
229 with the new HIT type must match the reward of the current HIT type in
230 order for the operation to be valid.
231
232 :type hit_id: str
233 :type hit_type: str
234 """
235 params = {'HITId' : hit_id,
236 'HITTypeId': hit_type}
237
238 return self._process_request('ChangeHITTypeOfHIT', params)
239
240 def get_reviewable_hits(self, hit_type=None, status='Reviewable',
241 sort_by='Expiration', sort_direction='Ascending',
242 page_size=10, page_number=1):
243 """
244 Retrieve the HITs that have a status of Reviewable, or HITs that
245 have a status of Reviewing, and that belong to the Requester
246 calling the operation.
247 """
248 params = {'Status' : status,
249 'SortProperty' : sort_by,
250 'SortDirection' : sort_direction,
251 'PageSize' : page_size,
252 'PageNumber' : page_number}
253
254 # Handle optional hit_type argument
255 if hit_type is not None:
256 params.update({'HITTypeId': hit_type})
257
258 return self._process_request('GetReviewableHITs', params, [('HIT', HIT), ])
259
260 @staticmethod
261 def _get_pages(page_size, total_records):
262 """
263 Given a page size (records per page) and a total number of
264 records, return the page numbers to be retrieved.
265 """
266 pages = total_records/page_size+bool(total_records%page_size)
267 return range(1, pages+1)
268
269
270 def get_all_hits(self):
271 """
272 Return all of a Requester's HITs
273
274 Despite what search_hits says, it does not return all hits, but
275 instead returns a page of hits. This method will pull the hits
276 from the server 100 at a time, but will yield the results
277 iteratively, so subsequent requests are made on demand.
278 """
279 page_size = 100
280 search_rs = self.search_hits(page_size=page_size)
281 total_records = int(search_rs.TotalNumResults)
282 get_page_hits = lambda(page): self.search_hits(page_size=page_size, page _number=page)
283 page_nums = self._get_pages(page_size, total_records)
284 hit_sets = itertools.imap(get_page_hits, page_nums)
285 return itertools.chain.from_iterable(hit_sets)
286
287 def search_hits(self, sort_by='CreationTime', sort_direction='Ascending',
288 page_size=10, page_number=1, response_groups=None):
289 """
290 Return a page of a Requester's HITs, on behalf of the Requester.
291 The operation returns HITs of any status, except for HITs that
292 have been disposed with the DisposeHIT operation.
293 Note:
294 The SearchHITs operation does not accept any search parameters
295 that filter the results.
296 """
297 params = {'SortProperty' : sort_by,
298 'SortDirection' : sort_direction,
299 'PageSize' : page_size,
300 'PageNumber' : page_number}
301 # Handle optional response groups argument
302 if response_groups:
303 self.build_list_params(params, response_groups, 'ResponseGroup')
304
305
306 return self._process_request('SearchHITs', params, [('HIT', HIT),])
307
308 def get_assignments(self, hit_id, status=None,
309 sort_by='SubmitTime', sort_direction='Ascending',
310 page_size=10, page_number=1, response_groups=None):
311 """
312 Retrieves completed assignments for a HIT.
313 Use this operation to retrieve the results for a HIT.
314
315 The returned ResultSet will have the following attributes:
316
317 NumResults
318 The number of assignments on the page in the filtered results
319 list, equivalent to the number of assignments being returned
320 by this call.
321 A non-negative integer
322 PageNumber
323 The number of the page in the filtered results list being
324 returned.
325 A positive integer
326 TotalNumResults
327 The total number of HITs in the filtered results list based
328 on this call.
329 A non-negative integer
330
331 The ResultSet will contain zero or more Assignment objects
332
333 """
334 params = {'HITId' : hit_id,
335 'SortProperty' : sort_by,
336 'SortDirection' : sort_direction,
337 'PageSize' : page_size,
338 'PageNumber' : page_number}
339
340 if status is not None:
341 params['AssignmentStatus'] = status
342
343 # Handle optional response groups argument
344 if response_groups:
345 self.build_list_params(params, response_groups, 'ResponseGroup')
346
347 return self._process_request('GetAssignmentsForHIT', params,
348 [('Assignment', Assignment),])
349
350 def approve_assignment(self, assignment_id, feedback=None):
351 """
352 """
353 params = {'AssignmentId' : assignment_id,}
354 if feedback:
355 params['RequesterFeedback'] = feedback
356 return self._process_request('ApproveAssignment', params)
357
358 def reject_assignment(self, assignment_id, feedback=None):
359 """
360 """
361 params = {'AssignmentId' : assignment_id,}
362 if feedback:
363 params['RequesterFeedback'] = feedback
364 return self._process_request('RejectAssignment', params)
365
366 def get_hit(self, hit_id, response_groups=None):
367 """
368 """
369 params = {'HITId' : hit_id,}
370 # Handle optional response groups argument
371 if response_groups:
372 self.build_list_params(params, response_groups, 'ResponseGroup')
373
374 return self._process_request('GetHIT', params, [('HIT', HIT),])
375
376 def set_reviewing(self, hit_id, revert=None):
377 """
378 Update a HIT with a status of Reviewable to have a status of Reviewing,
379 or reverts a Reviewing HIT back to the Reviewable status.
380
381 Only HITs with a status of Reviewable can be updated with a status of
382 Reviewing. Similarly, only Reviewing HITs can be reverted back to a
383 status of Reviewable.
384 """
385 params = {'HITId' : hit_id,}
386 if revert:
387 params['Revert'] = revert
388 return self._process_request('SetHITAsReviewing', params)
389
390 def disable_hit(self, hit_id, response_groups=None):
391 """
392 Remove a HIT from the Mechanical Turk marketplace, approves all
393 submitted assignments that have not already been approved or rejected,
394 and disposes of the HIT and all assignment data.
395
396 Assignments for the HIT that have already been submitted, but not yet
397 approved or rejected, will be automatically approved. Assignments in
398 progress at the time of the call to DisableHIT will be approved once
399 the assignments are submitted. You will be charged for approval of
400 these assignments. DisableHIT completely disposes of the HIT and
401 all submitted assignment data. Assignment results data cannot be
402 retrieved for a HIT that has been disposed.
403
404 It is not possible to re-enable a HIT once it has been disabled.
405 To make the work from a disabled HIT available again, create a new HIT.
406 """
407 params = {'HITId' : hit_id,}
408 # Handle optional response groups argument
409 if response_groups:
410 self.build_list_params(params, response_groups, 'ResponseGroup')
411
412 return self._process_request('DisableHIT', params)
413
414 def dispose_hit(self, hit_id):
415 """
416 Dispose of a HIT that is no longer needed.
417
418 Only HITs in the "reviewable" state, with all submitted
419 assignments approved or rejected, can be disposed. A Requester
420 can call GetReviewableHITs to determine which HITs are
421 reviewable, then call GetAssignmentsForHIT to retrieve the
422 assignments. Disposing of a HIT removes the HIT from the
423 results of a call to GetReviewableHITs. """
424 params = {'HITId' : hit_id,}
425 return self._process_request('DisposeHIT', params)
426
427 def expire_hit(self, hit_id):
428
429 """
430 Expire a HIT that is no longer needed.
431
432 The effect is identical to the HIT expiring on its own. The
433 HIT no longer appears on the Mechanical Turk web site, and no
434 new Workers are allowed to accept the HIT. Workers who have
435 accepted the HIT prior to expiration are allowed to complete
436 it or return it, or allow the assignment duration to elapse
437 (abandon the HIT). Once all remaining assignments have been
438 submitted, the expired HIT becomes"reviewable", and will be
439 returned by a call to GetReviewableHITs.
440 """
441 params = {'HITId' : hit_id,}
442 return self._process_request('ForceExpireHIT', params)
443
444 def extend_hit(self, hit_id, assignments_increment=None, expiration_incremen t=None):
445 """
446 Increase the maximum number of assignments, or extend the
447 expiration date, of an existing HIT.
448
449 NOTE: If a HIT has a status of Reviewable and the HIT is
450 extended to make it Available, the HIT will not be returned by
451 GetReviewableHITs, and its submitted assignments will not be
452 returned by GetAssignmentsForHIT, until the HIT is Reviewable
453 again. Assignment auto-approval will still happen on its
454 original schedule, even if the HIT has been extended. Be sure
455 to retrieve and approve (or reject) submitted assignments
456 before extending the HIT, if so desired.
457 """
458 # must provide assignment *or* expiration increment
459 if (assignments_increment is None and expiration_increment is None) or \
460 (assignments_increment is not None and expiration_increment is not No ne):
461 raise ValueError("Must specify either assignments_increment or expir ation_increment, but not both")
462
463 params = {'HITId' : hit_id,}
464 if assignments_increment:
465 params['MaxAssignmentsIncrement'] = assignments_increment
466 if expiration_increment:
467 params['ExpirationIncrementInSeconds'] = expiration_increment
468
469 return self._process_request('ExtendHIT', params)
470
471 def get_help(self, about, help_type='Operation'):
472 """
473 Return information about the Mechanical Turk Service
474 operations and response group NOTE - this is basically useless
475 as it just returns the URL of the documentation
476
477 help_type: either 'Operation' or 'ResponseGroup'
478 """
479 params = {'About': about, 'HelpType': help_type,}
480 return self._process_request('Help', params)
481
482 def grant_bonus(self, worker_id, assignment_id, bonus_price, reason):
483 """
484 Issues a payment of money from your account to a Worker. To
485 be eligible for a bonus, the Worker must have submitted
486 results for one of your HITs, and have had those results
487 approved or rejected. This payment happens separately from the
488 reward you pay to the Worker when you approve the Worker's
489 assignment. The Bonus must be passed in as an instance of the
490 Price object.
491 """
492 params = bonus_price.get_as_params('BonusAmount', 1)
493 params['WorkerId'] = worker_id
494 params['AssignmentId'] = assignment_id
495 params['Reason'] = reason
496
497 return self._process_request('GrantBonus', params)
498
499 def block_worker(self, worker_id, reason):
500 """
501 Block a worker from working on my tasks.
502 """
503 params = {'WorkerId': worker_id, 'Reason': reason}
504
505 return self._process_request('BlockWorker', params)
506
507 def unblock_worker(self, worker_id, reason):
508 """
509 Unblock a worker from working on my tasks.
510 """
511 params = {'WorkerId': worker_id, 'Reason': reason}
512
513 return self._process_request('UnblockWorker', params)
514
515 def notify_workers(self, worker_ids, subject, message_text):
516 """
517 Send a text message to workers.
518 """
519 params = {'Subject' : subject,
520 'MessageText': message_text}
521 self.build_list_params(params, worker_ids, 'WorkerId')
522
523 return self._process_request('NotifyWorkers', params)
524
525 def create_qualification_type(self,
526 name,
527 description,
528 status,
529 keywords=None,
530 retry_delay=None,
531 test=None,
532 answer_key=None,
533 answer_key_xml=None,
534 test_duration=None,
535 auto_granted=False,
536 auto_granted_value=1):
537 """
538 Create a new Qualification Type.
539
540 name: This will be visible to workers and must be unique for a
541 given requester.
542
543 description: description shown to workers. Max 2000 characters.
544
545 status: 'Active' or 'Inactive'
546
547 keywords: list of keyword strings or comma separated string.
548 Max length of 1000 characters when concatenated with commas.
549
550 retry_delay: number of seconds after requesting a
551 qualification the worker must wait before they can ask again.
552 If not specified, workers can only request this qualification
553 once.
554
555 test: a QuestionForm
556
557 answer_key: an XML string of your answer key, for automatically
558 scored qualification tests.
559 (Consider implementing an AnswerKey class for this to support.)
560
561 test_duration: the number of seconds a worker has to complete the test.
562
563 auto_granted: if True, requests for the Qualification are granted immedi ately.
564 Can't coexist with a test.
565
566 auto_granted_value: auto_granted qualifications are given this value.
567
568 """
569
570 params = {'Name' : name,
571 'Description' : description,
572 'QualificationTypeStatus' : status,
573 }
574 if retry_delay is not None:
575 params['RetryDelay'] = retry_delay
576
577 if test is not None:
578 assert(isinstance(test, QuestionForm))
579 assert(test_duration is not None)
580 params['Test'] = test.get_as_xml()
581
582 if test_duration is not None:
583 params['TestDurationInSeconds'] = test_duration
584
585 if answer_key is not None:
586 if isinstance(answer_key, basestring):
587 params['AnswerKey'] = answer_key # xml
588 else:
589 raise TypeError
590 # Eventually someone will write an AnswerKey class.
591
592 if auto_granted:
593 assert(test is False)
594 params['AutoGranted'] = True
595 params['AutoGrantedValue'] = auto_granted_value
596
597 if keywords:
598 params['Keywords'] = self.get_keywords_as_string(keywords)
599
600 return self._process_request('CreateQualificationType', params,
601 [('QualificationType', QualificationType),] )
602
603 def get_qualification_type(self, qualification_type_id):
604 params = {'QualificationTypeId' : qualification_type_id }
605 return self._process_request('GetQualificationType', params,
606 [('QualificationType', QualificationType),] )
607
608 def get_qualifications_for_qualification_type(self, qualification_type_id):
609 params = {'QualificationTypeId' : qualification_type_id }
610 return self._process_request('GetQualificationsForQualificationType', pa rams,
611 [('QualificationType', QualificationType),] )
612
613 def update_qualification_type(self, qualification_type_id,
614 description=None,
615 status=None,
616 retry_delay=None,
617 test=None,
618 answer_key=None,
619 test_duration=None,
620 auto_granted=None,
621 auto_granted_value=None):
622
623 params = {'QualificationTypeId' : qualification_type_id }
624
625 if description is not None:
626 params['Description'] = description
627
628 if status is not None:
629 params['QualificationTypeStatus'] = status
630
631 if retry_delay is not None:
632 params['RetryDelay'] = retry_delay
633
634 if test is not None:
635 assert(isinstance(test, QuestionForm))
636 params['Test'] = test.get_as_xml()
637
638 if test_duration is not None:
639 params['TestDuration'] = test_duration
640
641 if answer_key is not None:
642 if isinstance(answer_key, basestring):
643 params['AnswerKey'] = answer_key # xml
644 else:
645 raise TypeError
646 # Eventually someone will write an AnswerKey class.
647
648 if auto_granted is not None:
649 params['AutoGranted'] = auto_granted
650
651 if auto_granted_value is not None:
652 params['AutoGrantedValue'] = auto_granted_value
653
654 return self._process_request('UpdateQualificationType', params,
655 [('QualificationType', QualificationType),] )
656
657 def dispose_qualification_type(self, qualification_type_id):
658 """TODO: Document."""
659 params = {'QualificationTypeId' : qualification_type_id}
660 return self._process_request('DisposeQualificationType', params)
661
662 def search_qualification_types(self, query=None, sort_by='Name',
663 sort_direction='Ascending', page_size=10,
664 page_number=1, must_be_requestable=True,
665 must_be_owned_by_caller=True):
666 """TODO: Document."""
667 params = {'Query' : query,
668 'SortProperty' : sort_by,
669 'SortDirection' : sort_direction,
670 'PageSize' : page_size,
671 'PageNumber' : page_number,
672 'MustBeRequestable' : must_be_requestable,
673 'MustBeOwnedByCaller' : must_be_owned_by_caller}
674 return self._process_request('SearchQualificationTypes', params,
675 [('QualificationType', QualificationType),])
676
677 def get_qualification_requests(self, qualification_type_id,
678 sort_by='Expiration',
679 sort_direction='Ascending', page_size=10,
680 page_number=1):
681 """TODO: Document."""
682 params = {'QualificationTypeId' : qualification_type_id,
683 'SortProperty' : sort_by,
684 'SortDirection' : sort_direction,
685 'PageSize' : page_size,
686 'PageNumber' : page_number}
687 return self._process_request('GetQualificationRequests', params,
688 [('QualificationRequest', QualificationRequest),])
689
690 def grant_qualification(self, qualification_request_id, integer_value=1):
691 """TODO: Document."""
692 params = {'QualificationRequestId' : qualification_request_id,
693 'IntegerValue' : integer_value}
694 return self._process_request('GrantQualification', params)
695
696 def revoke_qualification(self, subject_id, qualification_type_id,
697 reason=None):
698 """TODO: Document."""
699 params = {'SubjectId' : subject_id,
700 'QualificationTypeId' : qualification_type_id,
701 'Reason' : reason}
702 return self._process_request('RevokeQualification', params)
703
704 def assign_qualification(self, qualification_type_id, worker_id,
705 value=1, send_notification=True):
706 params = {'QualificationTypeId' : qualification_type_id,
707 'WorkerId' : worker_id,
708 'IntegerValue' : value,
709 'SendNotification' : send_notification}
710 return self._process_request('AssignQualification', params)
711
712 def get_qualification_score(self, qualification_type_id, worker_id):
713 """TODO: Document."""
714 params = {'QualificationTypeId' : qualification_type_id,
715 'SubjectId' : worker_id}
716 return self._process_request('GetQualificationScore', params,
717 [('Qualification', Qualification),])
718
719 def update_qualification_score(self, qualification_type_id, worker_id,
720 value):
721 """TODO: Document."""
722 params = {'QualificationTypeId' : qualification_type_id,
723 'SubjectId' : worker_id,
724 'IntegerValue' : value}
725 return self._process_request('UpdateQualificationScore', params)
726
727 def _process_request(self, request_type, params, marker_elems=None):
728 """
729 Helper to process the xml response from AWS
730 """
731 response = self.make_request(request_type, params, verb='POST')
732 return self._process_response(response, marker_elems)
733
734 def _process_response(self, response, marker_elems=None):
735 """
736 Helper to process the xml response from AWS
737 """
738 body = response.read()
739 #print body
740 if '<Errors>' not in body:
741 rs = ResultSet(marker_elems)
742 h = handler.XmlHandler(rs, self)
743 xml.sax.parseString(body, h)
744 return rs
745 else:
746 raise MTurkRequestError(response.status, response.reason, body)
747
748 @staticmethod
749 def get_keywords_as_string(keywords):
750 """
751 Returns a comma+space-separated string of keywords from either
752 a list or a string
753 """
754 if type(keywords) is list:
755 keywords = ', '.join(keywords)
756 if type(keywords) is str:
757 final_keywords = keywords
758 elif type(keywords) is unicode:
759 final_keywords = keywords.encode('utf-8')
760 elif keywords is None:
761 final_keywords = ""
762 else:
763 raise TypeError("keywords argument must be a string or a list of str ings; got a %s" % type(keywords))
764 return final_keywords
765
766 @staticmethod
767 def get_price_as_price(reward):
768 """
769 Returns a Price data structure from either a float or a Price
770 """
771 if isinstance(reward, Price):
772 final_price = reward
773 else:
774 final_price = Price(reward)
775 return final_price
776
777 @staticmethod
778 def duration_as_seconds(duration):
779 if isinstance(duration, datetime.timedelta):
780 duration = duration.days*86400 + duration.seconds
781 try:
782 duration = int(duration)
783 except TypeError:
784 raise TypeError("Duration must be a timedelta or int-castable, got % s" % type(duration))
785 return duration
786
787 class BaseAutoResultElement:
788 """
789 Base class to automatically add attributes when parsing XML
790 """
791 def __init__(self, connection):
792 pass
793
794 def startElement(self, name, attrs, connection):
795 return None
796
797 def endElement(self, name, value, connection):
798 setattr(self, name, value)
799
800 class HIT(BaseAutoResultElement):
801 """
802 Class to extract a HIT structure from a response (used in ResultSet)
803
804 Will have attributes named as per the Developer Guide,
805 e.g. HITId, HITTypeId, CreationTime
806 """
807
808 # property helper to determine if HIT has expired
809 def _has_expired(self):
810 """ Has this HIT expired yet? """
811 expired = False
812 if hasattr(self, 'Expiration'):
813 now = datetime.datetime.utcnow()
814 expiration = datetime.datetime.strptime(self.Expiration, '%Y-%m-%dT% H:%M:%SZ')
815 expired = (now >= expiration)
816 else:
817 raise ValueError("ERROR: Request for expired property, but no Expira tion in HIT!")
818 return expired
819
820 # are we there yet?
821 expired = property(_has_expired)
822
823 class Qualification(BaseAutoResultElement):
824 """
825 Class to extract an Qualification structure from a response (used in
826 ResultSet)
827
828 Will have attributes named as per the Developer Guide such as
829 QualificationTypeId, IntegerValue. Does not seem to contain GrantTime.
830 """
831
832 pass
833
834 class QualificationType(BaseAutoResultElement):
835 """
836 Class to extract an QualificationType structure from a response (used in
837 ResultSet)
838
839 Will have attributes named as per the Developer Guide,
840 e.g. QualificationTypeId, CreationTime, Name, etc
841 """
842
843 pass
844
845 class QualificationRequest(BaseAutoResultElement):
846 """
847 Class to extract an QualificationRequest structure from a response (used in
848 ResultSet)
849
850 Will have attributes named as per the Developer Guide,
851 e.g. QualificationRequestId, QualificationTypeId, SubjectId, etc
852
853 TODO: Ensure that Test and Answer attribute are treated properly if the
854 qualification requires a test. These attributes are XML-encoded.
855 """
856
857 pass
858
859 class Assignment(BaseAutoResultElement):
860 """
861 Class to extract an Assignment structure from a response (used in
862 ResultSet)
863
864 Will have attributes named as per the Developer Guide,
865 e.g. AssignmentId, WorkerId, HITId, Answer, etc
866 """
867
868 def __init__(self, connection):
869 BaseAutoResultElement.__init__(self, connection)
870 self.answers = []
871
872 def endElement(self, name, value, connection):
873 # the answer consists of embedded XML, so it needs to be parsed independ antly
874 if name == 'Answer':
875 answer_rs = ResultSet([('Answer', QuestionFormAnswer),])
876 h = handler.XmlHandler(answer_rs, connection)
877 value = connection.get_utf8_value(value)
878 xml.sax.parseString(value, h)
879 self.answers.append(answer_rs)
880 else:
881 BaseAutoResultElement.endElement(self, name, value, connection)
882
883 class QuestionFormAnswer(BaseAutoResultElement):
884 """
885 Class to extract Answers from inside the embedded XML
886 QuestionFormAnswers element inside the Answer element which is
887 part of the Assignment structure
888
889 A QuestionFormAnswers element contains an Answer element for each
890 question in the HIT or Qualification test for which the Worker
891 provided an answer. Each Answer contains a QuestionIdentifier
892 element whose value corresponds to the QuestionIdentifier of a
893 Question in the QuestionForm. See the QuestionForm data structure
894 for more information about questions and answer specifications.
895
896 If the question expects a free-text answer, the Answer element
897 contains a FreeText element. This element contains the Worker's
898 answer
899
900 *NOTE* - currently really only supports free-text and selection answers
901 """
902
903 def __init__(self, connection):
904 BaseAutoResultElement.__init__(self, connection)
905 self.fields = []
906 self.qid = None
907
908 def endElement(self, name, value, connection):
909 if name == 'QuestionIdentifier':
910 self.qid = value
911 elif name in ['FreeText', 'SelectionIdentifier'] and self.qid:
912 self.fields.append((self.qid,value))
913 elif name == 'Answer':
914 self.qid = None
OLDNEW
« no previous file with comments | « third_party/gsutil/20110627/boto/boto/mturk/__init__.py ('k') | third_party/gsutil/20110627/boto/boto/mturk/notification.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698