sprout-ruby-aws 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +38 -0
- data/LICENSE.txt +202 -0
- data/Manifest.txt +69 -0
- data/NOTICE.txt +4 -0
- data/README.txt +105 -0
- data/Rakefile +20 -0
- data/bin/ruby-aws +9 -0
- data/lib/amazon/util.rb +10 -0
- data/lib/amazon/util/binder.rb +44 -0
- data/lib/amazon/util/data_reader.rb +157 -0
- data/lib/amazon/util/filter_chain.rb +79 -0
- data/lib/amazon/util/hash_nesting.rb +93 -0
- data/lib/amazon/util/lazy_results.rb +59 -0
- data/lib/amazon/util/logging.rb +23 -0
- data/lib/amazon/util/paginated_iterator.rb +70 -0
- data/lib/amazon/util/proactive_results.rb +116 -0
- data/lib/amazon/util/threadpool.rb +129 -0
- data/lib/amazon/util/user_data_store.rb +100 -0
- data/lib/amazon/webservices/mechanical_turk.rb +117 -0
- data/lib/amazon/webservices/mechanical_turk_requester.rb +340 -0
- data/lib/amazon/webservices/mturk/mechanical_turk_error_handler.rb +136 -0
- data/lib/amazon/webservices/mturk/question_generator.rb +58 -0
- data/lib/amazon/webservices/util/amazon_authentication_relay.rb +64 -0
- data/lib/amazon/webservices/util/command_line.rb +156 -0
- data/lib/amazon/webservices/util/convenience_wrapper.rb +90 -0
- data/lib/amazon/webservices/util/filter_proxy.rb +45 -0
- data/lib/amazon/webservices/util/mock_transport.rb +70 -0
- data/lib/amazon/webservices/util/request_signer.rb +42 -0
- data/lib/amazon/webservices/util/rest_transport.rb +108 -0
- data/lib/amazon/webservices/util/soap_simplifier.rb +48 -0
- data/lib/amazon/webservices/util/soap_transport.rb +38 -0
- data/lib/amazon/webservices/util/soap_transport_header_handler.rb +27 -0
- data/lib/amazon/webservices/util/unknown_result_exception.rb +27 -0
- data/lib/amazon/webservices/util/validation_exception.rb +55 -0
- data/lib/amazon/webservices/util/xml_simplifier.rb +61 -0
- data/lib/ruby-aws.rb +21 -0
- data/lib/ruby-aws/version.rb +8 -0
- data/samples/mturk/best_image/BestImage.rb +61 -0
- data/samples/mturk/best_image/best_image.properties +39 -0
- data/samples/mturk/best_image/best_image.question +82 -0
- data/samples/mturk/blank_slate/BlankSlate.rb +63 -0
- data/samples/mturk/blank_slate/BlankSlate_multithreaded.rb +67 -0
- data/samples/mturk/helloworld/MTurkHelloWorld.rb +56 -0
- data/samples/mturk/helloworld/mturk.yml +8 -0
- data/samples/mturk/reviewer/Reviewer.rb +103 -0
- data/samples/mturk/reviewer/mturk.yml +8 -0
- data/samples/mturk/simple_survey/SimpleSurvey.rb +90 -0
- data/samples/mturk/simple_survey/simple_survey.question +30 -0
- data/samples/mturk/site_category/SiteCategory.rb +87 -0
- data/samples/mturk/site_category/externalpage.htm +71 -0
- data/samples/mturk/site_category/site_category.input +6 -0
- data/samples/mturk/site_category/site_category.properties +45 -0
- data/samples/mturk/site_category/site_category.question +9 -0
- data/test/mturk/test_changehittypeofhit.rb +130 -0
- data/test/mturk/test_error_handler.rb +135 -0
- data/test/mturk/test_mechanical_turk_requester.rb +178 -0
- data/test/mturk/test_mock_mechanical_turk_requester.rb +205 -0
- data/test/test_ruby-aws.rb +22 -0
- data/test/unit/test_binder.rb +89 -0
- data/test/unit/test_data_reader.rb +135 -0
- data/test/unit/test_exceptions.rb +32 -0
- data/test/unit/test_hash_nesting.rb +93 -0
- data/test/unit/test_lazy_results.rb +89 -0
- data/test/unit/test_mock_transport.rb +132 -0
- data/test/unit/test_paginated_iterator.rb +58 -0
- data/test/unit/test_proactive_results.rb +108 -0
- data/test/unit/test_question_generator.rb +54 -0
- data/test/unit/test_threadpool.rb +50 -0
- data/test/unit/test_user_data_store.rb +80 -0
- metadata +177 -0
@@ -0,0 +1,340 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2007 Amazon Technologies, Inc.
|
2
|
+
# License:: Apache License, Version 2.0
|
3
|
+
|
4
|
+
require 'erb'
|
5
|
+
require 'monitor'
|
6
|
+
require 'amazon/util'
|
7
|
+
require 'amazon/webservices/util/xml_simplifier'
|
8
|
+
require 'amazon/webservices/util/convenience_wrapper'
|
9
|
+
require 'amazon/webservices/mechanical_turk'
|
10
|
+
|
11
|
+
module Amazon
|
12
|
+
module WebServices
|
13
|
+
|
14
|
+
class MechanicalTurkRequester < Amazon::WebServices::Util::ConvenienceWrapper
|
15
|
+
|
16
|
+
WSDL_VERSION = "2007-06-21"
|
17
|
+
|
18
|
+
ABANDONMENT_RATE_QUALIFICATION_TYPE_ID = "00000000000000000070";
|
19
|
+
APPROVAL_RATE_QUALIFICATION_TYPE_ID = "000000000000000000L0";
|
20
|
+
REJECTION_RATE_QUALIFICATION_TYPE_ID = "000000000000000000S0";
|
21
|
+
RETURN_RATE_QUALIFICATION_TYPE_ID = "000000000000000000E0";
|
22
|
+
SUBMISSION_RATE_QUALIFICATION_TYPE_ID = "00000000000000000000";
|
23
|
+
LOCALE_QUALIFICATION_TYPE_ID = "00000000000000000071";
|
24
|
+
|
25
|
+
DEFAULT_THREADCOUNT = 10
|
26
|
+
|
27
|
+
serviceCall :RegisterHITType, :RegisterHITTypeResult, {
|
28
|
+
:AssignmentDurationInSeconds => 60*60,
|
29
|
+
:AutoApprovalDelayInSeconds => 60*60*24*7
|
30
|
+
}
|
31
|
+
|
32
|
+
serviceCall :CreateHIT, :HIT, { :MaxAssignments => 1,
|
33
|
+
:AssignmentDurationInSeconds => 60*60,
|
34
|
+
:AutoApprovalDelayInSeconds => 60*60*24*7,
|
35
|
+
:LifetimeInSeconds => 60*60*24,
|
36
|
+
:ResponseGroup => %w( HITQuestion Minimal)
|
37
|
+
}
|
38
|
+
|
39
|
+
serviceCall :DisableHIT, :DisableHITResult
|
40
|
+
serviceCall :DisposeHIT, :DisposeHITResult
|
41
|
+
serviceCall :ExtendHIT, :ExtendHITResult
|
42
|
+
serviceCall :ForceExpireHIT, :ForceExpireHITResult
|
43
|
+
serviceCall :GetHIT, :HIT, { :ResponseGroup => %w( Minimal HITDetail HITQuestion HITAssignmentSummary ) }
|
44
|
+
serviceCall :ChangeHITTypeOfHIT, :ChangeHITTypeOfHITResult
|
45
|
+
|
46
|
+
serviceCall :SearchHITs, :SearchHITsResult
|
47
|
+
serviceCall :GetReviewableHITs, :GetReviewableHITsResult
|
48
|
+
serviceCall :SetHITAsReviewing, :SetHITAsReviewingResult
|
49
|
+
serviceCall :GetAssignmentsForHIT, :GetAssignmentsForHITResult
|
50
|
+
serviceCall :ApproveAssignment, :ApproveAssignmentResult
|
51
|
+
serviceCall :RejectAssignment, :RejectAssignmentResult
|
52
|
+
|
53
|
+
paginate :SearchHITs, :HIT
|
54
|
+
paginate :GetReviewableHITs, :HIT
|
55
|
+
paginate :GetAssignmentsForHIT, :Assignment
|
56
|
+
|
57
|
+
serviceCall :GrantBonus, :GrantBonusResult
|
58
|
+
serviceCall :GetBonusPayments, :GetBonusPaymentsResult
|
59
|
+
|
60
|
+
serviceCall :CreateQualificationType, :QualificationType, { :QualificationTypeStatus => 'Active' }
|
61
|
+
serviceCall :GetQualificationType, :QualificationType
|
62
|
+
serviceCall :SearchQualificationTypes, :SearchQualificationTypesResult, { :MustBeRequestable => true }
|
63
|
+
serviceCall :UpdateQualificationType, :QualificationType
|
64
|
+
serviceCall :GetQualificationsForQualificationType, :GetQualificationsForQualificationTypeResult, { :Status => 'Granted' }
|
65
|
+
serviceCall :GetHITsForQualificationType, :GetHITsForQualificationTypeResult
|
66
|
+
|
67
|
+
paginate :SearchQualificationTypes, :QualificationType
|
68
|
+
paginate :GetQualificationsForQualificationType, :Qualification
|
69
|
+
|
70
|
+
serviceCall :AssignQualification, :AssignQualificationResult
|
71
|
+
serviceCall :GetQualificationRequests, :GetQualificationRequestsResult
|
72
|
+
serviceCall :GrantQualification, :GrantQualificationResult
|
73
|
+
serviceCall :RejectQualificationRequest, :RejectQualificationRequestResult
|
74
|
+
serviceCall :GetQualificationScore, :Qualification
|
75
|
+
serviceCall :UpdateQualificationScore, :UpdateQualificationScoreResult
|
76
|
+
serviceCall :RevokeQualification, :RevokeQualificationResult
|
77
|
+
|
78
|
+
paginate :GetQualificationRequests, :QualificationRequest
|
79
|
+
|
80
|
+
serviceCall :SetHITTypeNotification, :SetHITTypeNotificationResult
|
81
|
+
serviceCall :SetWorkerAcceptLimit, :SetWorkerAcceptLimitResult
|
82
|
+
serviceCall :GetWorkerAcceptLimit, :GetWorkerAcceptLimitResult
|
83
|
+
serviceCall :BlockWorker, :BlockWorkerResult
|
84
|
+
serviceCall :UnblockWorker, :BlockWorkerResult
|
85
|
+
|
86
|
+
serviceCall :GetFileUploadURL, :GetFileUploadURLResult
|
87
|
+
serviceCall :GetAccountBalance, :GetAccountBalanceResult
|
88
|
+
serviceCall :GetRequesterStatistic, :GetStatisticResult, { :Count => 1 }
|
89
|
+
|
90
|
+
serviceCall :NotifyWorkers, :NotifyWorkersResult
|
91
|
+
|
92
|
+
def initialize(args={})
|
93
|
+
newargs = args.dup
|
94
|
+
unless args[:Config].nil?
|
95
|
+
loaded = Amazon::Util::DataReader.load( args[:Config], :YAML )
|
96
|
+
newargs = args.merge loaded.inject({}) {|a,b| a[b[0].to_sym] = b[1] ; a }
|
97
|
+
end
|
98
|
+
@threadcount = args[:ThreadCount].to_i
|
99
|
+
@threadcount = DEFAULT_THREADCOUNT unless @threadcount >= 1
|
100
|
+
raise "Cannot override WSDL version ( #{WSDL_VERSION} )" unless args[:Version].nil? or args[:Version].equals? WSDL_VERSION
|
101
|
+
super newargs.merge( :Name => :AWSMechanicalTurkRequester,
|
102
|
+
:ServiceClass => Amazon::WebServices::MechanicalTurk,
|
103
|
+
:Version => WSDL_VERSION )
|
104
|
+
end
|
105
|
+
|
106
|
+
# Create a series of similar HITs, sharing common parameters. Utilizes HITType
|
107
|
+
# * hit_template is the array of parameters to pass to createHIT.
|
108
|
+
# * question_template will be passed as a template into ERB to generate the :Question parameter
|
109
|
+
# * the RequesterAnnotation parameter of hit_template will also be passed through ERB
|
110
|
+
# * hit_data_set should consist of an array of hashes defining unique instance variables utilized by question_template
|
111
|
+
def createHITs( hit_template, question_template, hit_data_set )
|
112
|
+
hit_template = hit_template.dup
|
113
|
+
lifetime = hit_template[:LifetimeInSeconds]
|
114
|
+
numassignments_template = hit_template[:MaxAssignments]
|
115
|
+
annotation_template = hit_template[:RequesterAnnotation]
|
116
|
+
hit_template.delete :LifetimeInSeconds
|
117
|
+
hit_template.delete :MaxAssignments
|
118
|
+
hit_template.delete :RequesterAnnotation
|
119
|
+
|
120
|
+
ht = hit_template[:HITTypeId] || registerHITType( hit_template )[:HITTypeId]
|
121
|
+
|
122
|
+
tp = Amazon::Util::ThreadPool.new @threadcount
|
123
|
+
|
124
|
+
created = [].extend(MonitorMixin)
|
125
|
+
failed = [].extend(MonitorMixin)
|
126
|
+
hit_data_set.each do |hd|
|
127
|
+
tp.addWork(hd) do |hit_data|
|
128
|
+
begin
|
129
|
+
b = Amazon::Util::Binder.new( hit_data )
|
130
|
+
annotation = b.erb_eval( annotation_template )
|
131
|
+
numassignments = b.erb_eval( numassignments_template.to_s ).to_i
|
132
|
+
question = b.erb_eval( question_template )
|
133
|
+
result = self.createHIT( :HITTypeId => ht,
|
134
|
+
:LifetimeInSeconds => lifetime,
|
135
|
+
:MaxAssignments => ( hit_data[:MaxAssignments] || numassignments || 1 ),
|
136
|
+
:Question => question,
|
137
|
+
:RequesterAnnotation => ( hit_data[:RequesterAnnotation] || annotation || "")
|
138
|
+
)
|
139
|
+
created.synchronize do
|
140
|
+
created << result
|
141
|
+
end
|
142
|
+
rescue => e
|
143
|
+
failed.synchronize do
|
144
|
+
failed << hit_data.merge( :Error => e.message, :Description => e.description )
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end # tp.addWork
|
148
|
+
end # hit_data_set.each
|
149
|
+
tp.finish
|
150
|
+
|
151
|
+
return :Created => created, :Failed => failed
|
152
|
+
end
|
153
|
+
|
154
|
+
# Update a series of HITs to belong to a new HITType
|
155
|
+
# * hit_template is the array of parameters to pass to registerHITType
|
156
|
+
# * hit_ids is a list of HITIds (strings)
|
157
|
+
def updateHITs( hit_template, hit_ids )
|
158
|
+
hit_template = hit_template.dup
|
159
|
+
hit_template.delete :LifetimeInSeconds
|
160
|
+
hit_template.delete :RequesterAnnotation
|
161
|
+
|
162
|
+
hit_type_id = registerHITType( hit_template )[:HITTypeId]
|
163
|
+
|
164
|
+
tp = Amazon::Util::ThreadPool.new @threadcount
|
165
|
+
|
166
|
+
updated = [].extend(MonitorMixin)
|
167
|
+
failed = [].extend(MonitorMixin)
|
168
|
+
hit_ids.each do |hid|
|
169
|
+
tp.addWork(hid) do |hit_id|
|
170
|
+
begin
|
171
|
+
changeHITTypeOfHIT( :HITId => hit_id, :HITTypeId => hit_type_id )
|
172
|
+
updated.synchronize do
|
173
|
+
updated << hit_id
|
174
|
+
end
|
175
|
+
rescue => e
|
176
|
+
failed.synchronize do
|
177
|
+
failed << { :HITId => hit_id, :Error => e.message }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end # tp.addWork
|
181
|
+
end # hit_ids.each
|
182
|
+
tp.finish
|
183
|
+
|
184
|
+
return :Updated => updated, :Failed => failed
|
185
|
+
end
|
186
|
+
|
187
|
+
# Forces expiration of hits provided. Useful for pausing HITs
|
188
|
+
# * hit_ids is an array of HITIds
|
189
|
+
def forceExpireHITs(hit_ids)
|
190
|
+
tp = Amazon::Util::ThreadPool.new @threadcount
|
191
|
+
|
192
|
+
expired = [].extend(MonitorMixin)
|
193
|
+
failed = [].extend(MonitorMixin)
|
194
|
+
hit_ids.each do |hid|
|
195
|
+
tp.addWork(hid) do |hit_id|
|
196
|
+
begin
|
197
|
+
forceExpireHIT( :HITId => hit_id )
|
198
|
+
expired.synchronize do
|
199
|
+
expired << hit_id
|
200
|
+
end
|
201
|
+
rescue => e
|
202
|
+
failed.synchronize do
|
203
|
+
failed << { :HITId => hit_id, :Error => e.message }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end # tp.addWork
|
207
|
+
end # hit_ids.each
|
208
|
+
tp.finish
|
209
|
+
|
210
|
+
return :Expired => expired, :Failed => failed
|
211
|
+
end
|
212
|
+
|
213
|
+
# Adds time for given hits. Useful for resuming paused/expired HITs
|
214
|
+
# * hit_ids is an array of HITIds
|
215
|
+
# * time is time in second we want to add
|
216
|
+
def extendHITs(hit_ids, time)
|
217
|
+
tp = Amazon::Util::ThreadPool.new @threadcount
|
218
|
+
|
219
|
+
extended = [].extend(MonitorMixin)
|
220
|
+
failed = [].extend(MonitorMixin)
|
221
|
+
hit_ids.each do |hid|
|
222
|
+
tp.addWork(hid) do |hit_id|
|
223
|
+
begin
|
224
|
+
extendHIT( :HITId => hit_id, :ExpirationIncrementInSeconds => time )
|
225
|
+
extended.synchronize do
|
226
|
+
extended << hit_id
|
227
|
+
end
|
228
|
+
rescue => e
|
229
|
+
failed.synchronize do
|
230
|
+
failed << { :HITId => hit_id, :Error => e.message }
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end # tp.addWork
|
234
|
+
end # hit_ids.each
|
235
|
+
tp.finish
|
236
|
+
|
237
|
+
return :Extended => extended, :Failed => failed
|
238
|
+
end
|
239
|
+
|
240
|
+
# Deletes given hits from mturk
|
241
|
+
# * hit_ids is an array of HITIds
|
242
|
+
def deleteHITs(hit_ids)
|
243
|
+
tp = Amazon::Util::ThreadPool.new @threadcount
|
244
|
+
|
245
|
+
deleted = [].extend(MonitorMixin)
|
246
|
+
failed = [].extend(MonitorMixin)
|
247
|
+
hit_ids.each do |hid|
|
248
|
+
tp.addWork(hid) do |hit_id|
|
249
|
+
begin
|
250
|
+
disableHIT( :HITId => hit_id )
|
251
|
+
deleted.synchronize do
|
252
|
+
deleted << hit_id
|
253
|
+
end
|
254
|
+
rescue => e
|
255
|
+
failed.synchronize do
|
256
|
+
failed << { :HITId => hit_id, :Error => e.message }
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end # tp.addWork
|
260
|
+
end # hit_ids.each
|
261
|
+
tp.finish
|
262
|
+
|
263
|
+
return :Deleted => deleted, :Failed => failed
|
264
|
+
end
|
265
|
+
|
266
|
+
# Update a HIT with new properties.
|
267
|
+
# hit_id:: Id of the HIT to update
|
268
|
+
# hit_template:: hash ( parameter => value ) of parameters to update
|
269
|
+
#
|
270
|
+
# Acceptable attributes:
|
271
|
+
# * Title
|
272
|
+
# * Description
|
273
|
+
# * Keywords
|
274
|
+
# * Reward
|
275
|
+
# * QualificationRequirement
|
276
|
+
# * AutoApprovalDelayInSeconds
|
277
|
+
# * AssignmentDurationInSeconds
|
278
|
+
#
|
279
|
+
# Behind the scenes, this function retrieves the HIT, merges the HITs
|
280
|
+
# current attributes with any you specify, and registers a new HIT
|
281
|
+
# Template. It then uses the new ChangeHITTypeOfHIT function to move
|
282
|
+
# your HIT to the newly-created HIT Template.
|
283
|
+
def updateHIT( hit_id, hit_template )
|
284
|
+
hit_template = hit_template.dup
|
285
|
+
|
286
|
+
hit = getHIT( :HITId => hit_id )
|
287
|
+
|
288
|
+
props = %w( Title Description Keywords Reward QualificationRequirement
|
289
|
+
AutoApprovalDelayInSeconds AssignmentDurationInSeconds
|
290
|
+
).collect {|str| str.to_sym }
|
291
|
+
|
292
|
+
props.each do |p|
|
293
|
+
hit_template[p] = hit[p] if hit_template[p].nil?
|
294
|
+
end
|
295
|
+
|
296
|
+
hit_type_id = registerHITType( hit_template )[:HITTypeId]
|
297
|
+
|
298
|
+
changeHITTypeOfHIT( :HITId => hit_id, :HITTypeId => hit_type_id )
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
def getHITResults( list )
|
303
|
+
results = [].extend(MonitorMixin)
|
304
|
+
tp = Amazon::Util::ThreadPool.new @threadcount
|
305
|
+
list.each do |line|
|
306
|
+
tp.addWork(line) do |h|
|
307
|
+
hit = getHIT( :HITId => h[:HITId] )
|
308
|
+
getAssignmentsForHITAll( :HITId => h[:HITId] ).each {|assignment|
|
309
|
+
results.synchronize do
|
310
|
+
results << ( hit.merge( assignment ) )
|
311
|
+
end
|
312
|
+
}
|
313
|
+
end
|
314
|
+
end
|
315
|
+
tp.finish
|
316
|
+
results.flatten
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns available funds in USD
|
320
|
+
# Calls getAccountBalance and parses out the correct amount
|
321
|
+
def availableFunds
|
322
|
+
return getAccountBalance[:AvailableBalance][:Amount]
|
323
|
+
end
|
324
|
+
|
325
|
+
# helper function to simplify answer XML
|
326
|
+
def simplifyAnswer( answerXML )
|
327
|
+
answerHash = Amazon::WebServices::Util::XMLSimplifier.simplify REXML::Document.new(answerXML)
|
328
|
+
list = [answerHash[:Answer]].flatten
|
329
|
+
list.inject({}) { |list, answer|
|
330
|
+
id = answer[:QuestionIdentifier]
|
331
|
+
result = answer[:FreeText] || answer[:SelectionIdentifier] || answer[:UploadedFileKey]
|
332
|
+
list[id] = result
|
333
|
+
list
|
334
|
+
}
|
335
|
+
end
|
336
|
+
|
337
|
+
end # MechanicalTurkRequester
|
338
|
+
|
339
|
+
end # Amazon::WebServices
|
340
|
+
end # Amazon
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2007 Amazon Technologies, Inc.
|
2
|
+
# License:: Apache License, Version 2.0
|
3
|
+
|
4
|
+
require 'amazon/util/logging'
|
5
|
+
require 'amazon/webservices/util/validation_exception'
|
6
|
+
require 'amazon/webservices/util/unknown_result_exception'
|
7
|
+
|
8
|
+
module Amazon
|
9
|
+
module WebServices
|
10
|
+
module MTurk
|
11
|
+
|
12
|
+
class MechanicalTurkErrorHandler
|
13
|
+
include Amazon::Util::Logging
|
14
|
+
|
15
|
+
REQUIRED_PARAMETERS = [:Relay]
|
16
|
+
|
17
|
+
# Commands with these prefixes can be retried if we are unsure of success
|
18
|
+
RETRY_PRE = %w( search get register update disable assign set dispose )
|
19
|
+
|
20
|
+
# Max number of times to retry a call
|
21
|
+
MAX_RETRY = 6
|
22
|
+
|
23
|
+
# Base used in Exponential Backoff retry delay
|
24
|
+
BACKOFF_BASE = 2
|
25
|
+
# Scale factor for Exponential Backoff retry delay
|
26
|
+
BACKOFF_INITIAL = 0.1
|
27
|
+
|
28
|
+
# Matching pattern to find a 'Results' element in the Response
|
29
|
+
RESULT_PATTERN = /Result/
|
30
|
+
# Additional elements to be considered a 'Result' despite not matching RESULT_PATTERN
|
31
|
+
ACCEPTABLE_RESULTS = %w( HIT Qualification QualificationType QualificationRequest Information )
|
32
|
+
|
33
|
+
def initialize( args )
|
34
|
+
missing_parameters = REQUIRED_PARAMETERS - args.keys
|
35
|
+
raise "Missing paramters: #{missing_parameters.join(',')}" unless missing_parameters.empty?
|
36
|
+
@relay = args[:Relay]
|
37
|
+
end
|
38
|
+
|
39
|
+
def dispatch(method, *args)
|
40
|
+
try = 0
|
41
|
+
begin
|
42
|
+
try += 1
|
43
|
+
log "Dispatching call to #{method} (try #{try})"
|
44
|
+
response = @relay.send(method,*args)
|
45
|
+
validateResponse( response )
|
46
|
+
return response
|
47
|
+
rescue Exception => error
|
48
|
+
case handleError( error,method )
|
49
|
+
when :RetryWithBackoff
|
50
|
+
retry if doBackoff( try )
|
51
|
+
when :RetryImmediate
|
52
|
+
retry if canRetry( try )
|
53
|
+
when :Ignore
|
54
|
+
return :IgnoredError => error
|
55
|
+
when :Unknown
|
56
|
+
raise Util::UnknownResultException.new( error, method, args )
|
57
|
+
when :Fail
|
58
|
+
raise error
|
59
|
+
else
|
60
|
+
raise "Unknown error handling method: #{handleError( error,method )}"
|
61
|
+
end
|
62
|
+
raise error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def methodRetryable( method )
|
67
|
+
RETRY_PRE.each do |pre|
|
68
|
+
return true if method.to_s =~ /^#{pre}/i
|
69
|
+
end
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
|
73
|
+
def handleError( error, method )
|
74
|
+
log "Handling error: #{error.inspect}"
|
75
|
+
case error.class.to_s
|
76
|
+
when 'Timeout::Error','SOAP::HTTPStreamError'
|
77
|
+
if methodRetryable( method )
|
78
|
+
return :RetryImmediate
|
79
|
+
else
|
80
|
+
return :Unknown
|
81
|
+
end
|
82
|
+
when 'SOAP::FaultError'
|
83
|
+
case error.faultcode.data
|
84
|
+
when "aws:Server.ServiceUnavailable"
|
85
|
+
return :RetryWithBackoff
|
86
|
+
else
|
87
|
+
return :Unknown
|
88
|
+
end
|
89
|
+
when 'Amazon::WebServices::Util::ValidationException'
|
90
|
+
return :Fail
|
91
|
+
when 'RuntimeError'
|
92
|
+
case error.message
|
93
|
+
when 'Throttled'
|
94
|
+
return :RetryWithBackoff
|
95
|
+
else
|
96
|
+
return :RetryImmediate
|
97
|
+
end
|
98
|
+
else
|
99
|
+
return :Unknown
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def canRetry( try )
|
104
|
+
try <= MAX_RETRY
|
105
|
+
end
|
106
|
+
|
107
|
+
def doBackoff( try )
|
108
|
+
return false unless canRetry(try)
|
109
|
+
delay = BACKOFF_INITIAL * ( BACKOFF_BASE ** try )
|
110
|
+
sleep delay
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
|
114
|
+
def isResultTag( tag )
|
115
|
+
tag.to_s =~ RESULT_PATTERN or ACCEPTABLE_RESULTS.include?( tag.to_s )
|
116
|
+
end
|
117
|
+
|
118
|
+
def validateResponse(response)
|
119
|
+
log "Validating response: #{response.inspect}"
|
120
|
+
raise 'Throttled' if response[:Errors] and response[:Errors][:Error] and response[:Errors][:Error][:Code] == "ServiceUnavailable"
|
121
|
+
raise Util::ValidationException.new(response) unless response[:OperationRequest][:Errors].nil?
|
122
|
+
resultTags = response.keys.find_all {|r| isResultTag( r ) }
|
123
|
+
raise Util::ValidationException.new(response, "Didn't get back an acceptable result tag (got back #{response.keys.join(',')})") if resultTags.empty?
|
124
|
+
resultTags.each do |resultTag|
|
125
|
+
log "using result tag <#{resultTag}>"
|
126
|
+
result = response[resultTag]
|
127
|
+
raise Util::ValidationException.new(response) unless result[:Request][:Errors].nil?
|
128
|
+
end
|
129
|
+
response
|
130
|
+
end
|
131
|
+
|
132
|
+
end # MechanicalTurkErrorHandler
|
133
|
+
|
134
|
+
end # Amazon::WebServices::MTurk
|
135
|
+
end # Amazon::WebServices
|
136
|
+
end # Amazon
|