Skip to content

Commit

Permalink
Added support for a number of MTurk features:
Browse files Browse the repository at this point in the history
1) Added support for SendTestEventNotification operation as send_test_event_notification().
2) Modified create_hit() to support layouts.
3) Added support for GetAssignment operation as get_assignment().
4) Implemented the QualificationRequest class.
5) Now printing out body of response object when debug level is set to 2 in the connection.
  • Loading branch information
dmcritchie committed Nov 27, 2012
1 parent 02d9d2b commit 1def92e
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 36 deletions.
126 changes: 90 additions & 36 deletions boto/mturk/connection.py
Expand Up @@ -50,6 +50,7 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
host = 'mechanicalturk.sandbox.amazonaws.com'
else:
host = 'mechanicalturk.amazonaws.com'
self.debug = debug

AWSQueryConnection.__init__(self, aws_access_key_id,
aws_secret_access_key,
Expand Down Expand Up @@ -102,14 +103,14 @@ def set_email_notification(self, hit_type, email, event_types=None):
Performs a SetHITTypeNotification operation to set email
notification for a specified HIT type
"""
return self._set_notification(hit_type, 'Email', email, event_types)
return self._set_notification(hit_type, 'Email', email, 'SetHITTypeNotification', event_types)

def set_rest_notification(self, hit_type, url, event_types=None):
"""
Performs a SetHITTypeNotification operation to set REST notification
for a specified HIT type
"""
return self._set_notification(hit_type, 'REST', url, event_types)
return self._set_notification(hit_type, 'REST', url, 'SetHITTypeNotification', event_types)

def set_sqs_notification(self, hit_type, queue_url, event_types=None):
"""
Expand All @@ -118,15 +119,20 @@ def set_sqs_notification(self, hit_type, queue_url, event_types=None):
https://queue.amazonaws.com/<CUSTOMER_ID>/<QUEUE_NAME> and can be
found when looking at the details for a Queue in the AWS Console"
"""
return self._set_notification(hit_type, "SQS", queue_url, event_types)
return self._set_notification(hit_type, "SQS", queue_url, 'SetHITTypeNotification', event_types)

def _set_notification(self, hit_type, transport, destination, event_types=None):
def send_test_event_notification(self, hit_type, url, event_types=None, test_event_type='Ping'):
"""
Common SetHITTypeNotification operation to set notification for a
specified HIT type
Performs a SendTestEventNotification operation with REST notification
for a specified HIT type
"""
assert isinstance(hit_type, str), "hit_type argument should be a string."
return self._set_notification(hit_type, 'REST', url, 'SendTestEventNotification', event_types, test_event_type)

def _set_notification(self, hit_type, transport, destination, request_type, event_types=None, test_event_type=None):
"""
Common operation to set notification or send a test event notification for a
specified HIT type
"""
params = {'HITTypeId': hit_type}

# from the Developer Guide:
Expand All @@ -152,45 +158,55 @@ def _set_notification(self, hit_type, transport, destination, event_types=None):
# Update main params dict
params.update(notification_rest_params)

# If test notification, specify the notification type to be tested
if test_event_type:
params.update({'TestEventType': test_event_type})

# Execute operation
return self._process_request('SetHITTypeNotification', params)
return self._process_request(request_type, params)

def create_hit(self, hit_type=None, question=None,
def create_hit(self, hit_type=None, question=None, hit_layout=None,
lifetime=datetime.timedelta(days=7),
max_assignments=1,
title=None, description=None, keywords=None,
reward=None, duration=datetime.timedelta(days=7),
approval_delay=None, annotation=None,
questions=None, qualifications=None,
response_groups=None):
layout_params=None, response_groups=None):
"""
Creates a new HIT.
Returns a ResultSet
See: http://docs.amazonwebservices.com/AWSMechanicalTurkRequester/2006-10-31/ApiReference_CreateHITOperation.html
See: http://docs.amazonwebservices.com/AWSMechTurk/2012-03-25/AWSMturkAPI/ApiReference_CreateHITOperation.html
"""

# handle single or multiple questions
neither = question is None and questions is None
both = question is not None and questions is not None
if neither or both:
raise ValueError("Must specify either question (single Question instance) or questions (list or QuestionForm instance), but not both")

if question:
questions = [question]
question_param = QuestionForm(questions)
if isinstance(question, QuestionForm):
question_param = question
elif isinstance(question, ExternalQuestion):
question_param = question
elif isinstance(question, HTMLQuestion):
question_param = question

# Handle basic required arguments and set up params dict
params = {'Question': question_param.get_as_xml(),
'LifetimeInSeconds':
params = {'LifetimeInSeconds':
self.duration_as_seconds(lifetime),
'MaxAssignments': max_assignments,
}
}

# handle single or multiple questions or layouts
neither = question is None and questions is None
if hit_layout is None:
both = question is not None and questions is not None
if neither or both:
raise ValueError("Must specify question (single Question instance) or questions (list or QuestionForm instance), but not both")
if question:
questions = [question]
question_param = QuestionForm(questions)
if isinstance(question, QuestionForm):
question_param = question
elif isinstance(question, ExternalQuestion):
question_param = question
elif isinstance(question, HTMLQuestion):
question_param = question
params['Question'] = question_param.get_as_xml()
else:
if not neither:
raise ValueError("Must not specify question (single Question instance) or questions (list or QuestionForm instance) when specifying hit_layout")
params['HITLayoutId'] = hit_layout
if layout_params:
params.update(layout_params.get_as_params())

# if hit type specified then add it
# else add the additional required parameters
Expand Down Expand Up @@ -317,6 +333,33 @@ def search_hits(self, sort_by='CreationTime', sort_direction='Ascending',

return self._process_request('SearchHITs', params, [('HIT', HIT),])

def get_assignment(self, assignment_id, response_groups=None):
"""
Retrieves an assignment using the assignment's ID. Requesters can only
retrieve their own assignments, and only assignments whose related HIT
has not been disposed.
The returned ResultSet will have the following attributes:
Request
This element is present only if the Request ResponseGroup is specified.
Assignment
The assignment. The response includes one Assignment object.
HIT
The HIT associated with this assignment. The response includes one HIT object.
"""

params = {'AssignmentId' : assignment_id}

# Handle optional response groups argument
if response_groups:
self.build_list_params(params, response_groups, 'ResponseGroup')

return self._process_request('GetAssignment', params,
[('Assignment', Assignment),
('HIT', HIT),])

def get_assignments(self, hit_id, status=None,
sort_by='SubmitTime', sort_direction='Ascending',
page_size=10, page_number=1, response_groups=None):
Expand Down Expand Up @@ -767,7 +810,8 @@ def _process_response(self, response, marker_elems=None):
Helper to process the xml response from AWS
"""
body = response.read()
#print body
if self.debug == 2:
print body
if '<Errors>' not in body:
rs = ResultSet(marker_elems)
h = handler.XmlHandler(rs, self)
Expand Down Expand Up @@ -887,12 +931,22 @@ class QualificationRequest(BaseAutoResultElement):
Will have attributes named as per the Developer Guide,
e.g. QualificationRequestId, QualificationTypeId, SubjectId, etc
TODO: Ensure that Test and Answer attribute are treated properly if the
qualification requires a test. These attributes are XML-encoded.
"""

pass
def __init__(self, connection):
BaseAutoResultElement.__init__(self, connection)
self.answers = []

def endElement(self, name, value, connection):
# the answer consists of embedded XML, so it needs to be parsed independantly
if name == 'Answer':
answer_rs = ResultSet([('Answer', QuestionFormAnswer),])
h = handler.XmlHandler(answer_rs, connection)
value = connection.get_utf8_value(value)
xml.sax.parseString(value, h)
self.answers.append(answer_rs)
else:
BaseAutoResultElement.endElement(self, name, value, connection)

class Assignment(BaseAutoResultElement):
"""
Expand Down Expand Up @@ -922,7 +976,7 @@ class QuestionFormAnswer(BaseAutoResultElement):
"""
Class to extract Answers from inside the embedded XML
QuestionFormAnswers element inside the Answer element which is
part of the Assignment structure
part of the Assignment and QualificationRequest structures
A QuestionFormAnswers element contains an Answer element for each
question in the HIT or Qualification test for which the Worker
Expand Down
55 changes: 55 additions & 0 deletions boto/mturk/layoutparam.py
@@ -0,0 +1,55 @@
# Copyright (c) 2008 Chris Moyer http://coredumped.org/
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, dis-
# tribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the fol-
# lowing conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

class LayoutParameters:

def __init__(self, layoutParameters=None):
if layoutParameters == None:
layoutParameters = []
self.layoutParameters = layoutParameters

def add(self, req):
self.layoutParameters.append(req)

def get_as_params(self):
params = {}
assert(len(self.layoutParameters) <= 25)
for n, layoutParameter in enumerate(self.layoutParameters):
kv = layoutParameter.get_as_params()
for key in kv:
params['HITLayoutParameter.%s.%s' % ((n+1), key) ] = kv[key]
return params

class LayoutParameter(object):
"""
Representation of a single HIT layout parameter
"""

def __init__(self, name, value):
self.name = name
self.value = value

def get_as_params(self):
params = {
"Name": self.name,
"Value": self.value,
}
return params

0 comments on commit 1def92e

Please sign in to comment.