mturk 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/.gemtest +0 -0
  5. data/History.md +105 -0
  6. data/LICENSE.txt +202 -0
  7. data/Manifest.txt +72 -0
  8. data/NOTICE.txt +4 -0
  9. data/README.md +100 -0
  10. data/Rakefile +33 -0
  11. data/bin/mturk +9 -0
  12. data/lib/amazon/util.rb +10 -0
  13. data/lib/amazon/util/binder.rb +48 -0
  14. data/lib/amazon/util/data_reader.rb +169 -0
  15. data/lib/amazon/util/filter_chain.rb +79 -0
  16. data/lib/amazon/util/hash_nesting.rb +93 -0
  17. data/lib/amazon/util/lazy_results.rb +59 -0
  18. data/lib/amazon/util/logging.rb +23 -0
  19. data/lib/amazon/util/paginated_iterator.rb +70 -0
  20. data/lib/amazon/util/proactive_results.rb +116 -0
  21. data/lib/amazon/util/threadpool.rb +129 -0
  22. data/lib/amazon/util/user_data_store.rb +100 -0
  23. data/lib/amazon/webservices/mechanical_turk.rb +123 -0
  24. data/lib/amazon/webservices/mechanical_turk_requester.rb +285 -0
  25. data/lib/amazon/webservices/mturk/mechanical_turk_error_handler.rb +153 -0
  26. data/lib/amazon/webservices/mturk/question_generator.rb +58 -0
  27. data/lib/amazon/webservices/util/amazon_authentication_relay.rb +72 -0
  28. data/lib/amazon/webservices/util/command_line.rb +155 -0
  29. data/lib/amazon/webservices/util/convenience_wrapper.rb +90 -0
  30. data/lib/amazon/webservices/util/filter_proxy.rb +45 -0
  31. data/lib/amazon/webservices/util/mock_transport.rb +70 -0
  32. data/lib/amazon/webservices/util/request_signer.rb +42 -0
  33. data/lib/amazon/webservices/util/rest_transport.rb +120 -0
  34. data/lib/amazon/webservices/util/soap_simplifier.rb +48 -0
  35. data/lib/amazon/webservices/util/soap_transport.rb +20 -0
  36. data/lib/amazon/webservices/util/soap_transport_header_handler.rb +27 -0
  37. data/lib/amazon/webservices/util/unknown_result_exception.rb +27 -0
  38. data/lib/amazon/webservices/util/validation_exception.rb +55 -0
  39. data/lib/amazon/webservices/util/xml_simplifier.rb +61 -0
  40. data/lib/mturk.rb +19 -0
  41. data/lib/mturk/version.rb +6 -0
  42. data/run_rcov.sh +1 -0
  43. data/samples/best_image/BestImage.rb +61 -0
  44. data/samples/best_image/best_image.properties +39 -0
  45. data/samples/best_image/best_image.question +82 -0
  46. data/samples/blank_slate/BlankSlate.rb +63 -0
  47. data/samples/blank_slate/BlankSlate_multithreaded.rb +67 -0
  48. data/samples/helloworld/MTurkHelloWorld.rb +56 -0
  49. data/samples/helloworld/mturk.yml +8 -0
  50. data/samples/review_policy/ReviewPolicy.rb +139 -0
  51. data/samples/review_policy/review_policy.question +30 -0
  52. data/samples/reviewer/Reviewer.rb +103 -0
  53. data/samples/reviewer/mturk.yml +8 -0
  54. data/samples/simple_survey/SimpleSurvey.rb +98 -0
  55. data/samples/simple_survey/simple_survey.question +30 -0
  56. data/samples/site_category/SiteCategory.rb +87 -0
  57. data/samples/site_category/externalpage.htm +71 -0
  58. data/samples/site_category/site_category.input +6 -0
  59. data/samples/site_category/site_category.properties +56 -0
  60. data/samples/site_category/site_category.question +9 -0
  61. data/test/mturk/test_changehittypeofhit.rb +130 -0
  62. data/test/mturk/test_error_handler.rb +403 -0
  63. data/test/mturk/test_mechanical_turk_requester.rb +178 -0
  64. data/test/mturk/test_mock_mechanical_turk_requester.rb +205 -0
  65. data/test/test_mturk.rb +21 -0
  66. data/test/unit/test_binder.rb +89 -0
  67. data/test/unit/test_data_reader.rb +135 -0
  68. data/test/unit/test_exceptions.rb +32 -0
  69. data/test/unit/test_hash_nesting.rb +99 -0
  70. data/test/unit/test_lazy_results.rb +89 -0
  71. data/test/unit/test_mock_transport.rb +132 -0
  72. data/test/unit/test_paginated_iterator.rb +58 -0
  73. data/test/unit/test_proactive_results.rb +108 -0
  74. data/test/unit/test_question_generator.rb +55 -0
  75. data/test/unit/test_threadpool.rb +50 -0
  76. data/test/unit/test_user_data_store.rb +80 -0
  77. metadata +225 -0
  78. metadata.gz.sig +0 -0
@@ -0,0 +1,123 @@
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/amazon_authentication_relay'
6
+ require 'amazon/webservices/mturk/mechanical_turk_error_handler'
7
+ require 'amazon/webservices/util/validation_exception'
8
+ require 'amazon/webservices/util/rest_transport'
9
+ require 'amazon/webservices/util/soap_transport'
10
+
11
+ module Amazon
12
+ module WebServices
13
+
14
+ class MechanicalTurk
15
+ include Amazon::Util::Logging
16
+
17
+ SOFTWARE_NAME = 'MTurkRubySDK'
18
+
19
+ SANDBOX = 'mechanicalturk.sandbox.amazonaws.com'
20
+ PROD = 'mechanicalturk.amazonaws.com'
21
+
22
+ # By default, MechanicalTurk will operate on the MechanicalTurk Sandbox. To run against the main site, pass +:Host => :Production+ into the constructor.
23
+ def initialize(args={})
24
+ name = args[:Name] || 'AWSMechanicalTurkRequester'
25
+ software = args.has_key?(:SoftwareName) ? "#{SOFTWARE_NAME}, #{args[:SoftwareName]}" : "#{SOFTWARE_NAME}"
26
+ @host = case args[:Host].to_s
27
+ when /^Prod/i
28
+ PROD
29
+ when /^Sandbox/i,""
30
+ SANDBOX
31
+ else
32
+ args[:Host].to_s
33
+ end
34
+ ssl = ( args[:UseSSL].nil? ? true : args[:UseSSL] )
35
+ newargs = args.merge( :Name => name, :SoftwareName => software, :Host => @host, :UseSSL => ssl)
36
+ if args[:Transport].to_s =~ /^SOAP/i
37
+ unless Util::SOAPTransport.canSOAP?
38
+ log "Unable to use SOAP transport. Falling back to REST."
39
+ args[:Transport] = :REST
40
+ end
41
+ end
42
+ transport = case args[:Transport]
43
+ when :SOAP,/^SOAP/i
44
+ getSOAPTransport(newargs)
45
+ when :REST,/^REST/i
46
+ getRESTTransport(newargs)
47
+ else
48
+ require 'amazon/webservices/util/soap_transport.rb'
49
+ allowOverride( 'Transport', args[:Transport], newargs ) { |a|
50
+ if Util::RESTTransport.canPost?
51
+ getRESTTransport(newargs)
52
+ elsif Util::SOAPTransport.canSOAP?
53
+ getSOAPTransport(newargs)
54
+ else
55
+ getRESTTransport(newargs)
56
+ end
57
+ }
58
+ end
59
+ newargs.merge!( :Transport => transport )
60
+ log "Generating relay with following args: #{newargs.inspect}"
61
+ relay = allowOverride('Relay',args[:Relay],newargs) { |a| Amazon::WebServices::Util::AmazonAuthenticationRelay.new(a) }
62
+ newargs.merge!( :Relay => relay )
63
+ log "Generating error handler with the following args: #{newargs.inspect}"
64
+ @errorHandler = allowOverride('ErrorHandler',args[:ErrorHandler],newargs) { |a| Amazon::WebServices::MTurk::MechanicalTurkErrorHandler.new(a) }
65
+ end
66
+
67
+ attr_accessor :host
68
+
69
+ def method_missing(method,*args)
70
+ log "Sending request: #{method} #{args.inspect}"
71
+ @errorHandler.dispatch(method,*args)
72
+ end
73
+
74
+ private
75
+
76
+ def allowOverride(name,override,args,&default)
77
+ newargs = args.merge( :DefaultOverride => default )
78
+ case override
79
+ when nil
80
+ yield( args )
81
+ when String,Symbol,Array,Hash,Integer
82
+ raise "Invalid #{name}: #{override.inspect}"
83
+ when Class
84
+ override.new( newargs )
85
+ else
86
+ override.configure( newargs ) if override.respond_to? :configure
87
+ override
88
+ end
89
+ end
90
+
91
+ def getRESTTransport(args)
92
+ endpoint = findRestEndpoint( args[:Name], args[:Host], args[:UseSSL] )
93
+ require 'amazon/webservices/util/rest_transport.rb'
94
+ @transport = Amazon::WebServices::Util::RESTTransport.new( args.merge( :Endpoint => endpoint ) )
95
+ end
96
+
97
+ def getSOAPTransport(args)
98
+ wsdl = findWSDL( args[:Name], args[:Host], args[:Version], args[:UseSSL] )
99
+ endpoint = findSOAPEndpoint( args[:Name], args[:Host], args[:UseSSL] )
100
+ require 'amazon/webservices/util/soap_transport.rb'
101
+ Amazon::WebServices::Util::SOAPTransport.new( args.merge( :Wsdl => wsdl, :Endpoint => endpoint ) )
102
+ end
103
+
104
+ def findWSDL( name, host, version, ssl )
105
+ if version.nil?
106
+ "#{ssl ? 'https' : 'http'}://#{host}/AWSMechanicalTurk/#{name}.wsdl"
107
+ else
108
+ "#{ssl ? 'https' : 'http'}://#{host}/AWSMechanicalTurk/#{version}/#{name}.wsdl"
109
+ end
110
+ end
111
+
112
+ def findSOAPEndpoint( name, host, ssl )
113
+ "#{ssl ? 'https' : 'http'}://#{host}/onca/soap?Service=#{name}"
114
+ end
115
+
116
+ def findRestEndpoint( name, host, ssl )
117
+ "#{ssl ? 'https' : 'http'}://#{host}/?Service=#{name}"
118
+ end
119
+
120
+ end # MTurk
121
+
122
+ end # Amazon::WebServices
123
+ end # Amazon
@@ -0,0 +1,285 @@
1
+ # Copyright:: Copyright (c) 2007-2014 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 = "2014-08-15"
17
+
18
+ # Deprecated qualification type IDs
19
+ ABANDONMENT_RATE_QUALIFICATION_TYPE_ID = "00000000000000000070";
20
+ APPROVAL_RATE_QUALIFICATION_TYPE_ID = "000000000000000000L0";
21
+ REJECTION_RATE_QUALIFICATION_TYPE_ID = "000000000000000000S0";
22
+ RETURN_RATE_QUALIFICATION_TYPE_ID = "000000000000000000E0";
23
+ SUBMISSION_RATE_QUALIFICATION_TYPE_ID = "00000000000000000000";
24
+
25
+ # Current qualification type IDs
26
+ LOCALE_QUALIFICATION_TYPE_ID = "00000000000000000071";
27
+ TOTAL_NUMBER_OF_HITS_APPROVED_QUALIFICATION_TYPE_ID = "00000000000000000040";
28
+ ADULT_QUALIFICATION_TYPE_ID = "00000000000000000060";
29
+ PHOTO_MODERATION_MASTERS_QUALIFICATION_TYPE_ID = "21VZU98JHSTLZ5BPP4A9NOBJEK3DPG";
30
+ PHOTO_MODERATION_MASTERS_SANDBOX_QUALIFICATION_TYPE_ID = "2TGBB6BFMFFOM08IBMAFGGESC1UWJX";
31
+ CATEGORIZATION_MASTERS_QUALIFICATION_TYPE_ID = "2NDP2L92HECWY8NS8H3CK0CP5L9GHO";
32
+ CATEGORIZATION_MASTERS_SANDBOX_QUALIFICATION_TYPE_ID = "2F1KVCNHMVHV8E9PBUB2A4J79LU20F";
33
+ MASTERS_QUALIFICATION_TYPE_ID = "2F1QJWKUDD8XADTFD2Q0G6UTO95ALH";
34
+ MASTERS_SANDBOX_QUALIFICATION_TYPE_ID = "2ARFPLSP75KLA8M8DH1HTEQVJT3SY6";
35
+
36
+ DEFAULT_THREADCOUNT = 10
37
+
38
+ serviceCall :RegisterHITType, :RegisterHITTypeResult, {
39
+ :AssignmentDurationInSeconds => 60*60,
40
+ :AutoApprovalDelayInSeconds => 60*60*24*7
41
+ }
42
+ serviceCall :SetHITTypeNotification, :SetHITTypeNotificationResult
43
+
44
+
45
+ serviceCall :CreateHIT, :HIT, { :MaxAssignments => 1,
46
+ :AssignmentDurationInSeconds => 60*60,
47
+ :AutoApprovalDelayInSeconds => 60*60*24*7,
48
+ :LifetimeInSeconds => 60*60*24,
49
+ }
50
+
51
+ serviceCall :DisableHIT, :DisableHITResult
52
+ serviceCall :DisposeHIT, :DisposeHITResult
53
+ serviceCall :ExtendHIT, :ExtendHITResult
54
+ serviceCall :ForceExpireHIT, :ForceExpireHITResult
55
+ serviceCall :GetHIT, :HIT, { :ResponseGroup => %w( Minimal HITDetail HITQuestion HITAssignmentSummary ) }
56
+ serviceCall :ChangeHITTypeOfHIT, :ChangeHITTypeOfHITResult
57
+
58
+ serviceCall :SearchHITs, :SearchHITsResult
59
+ serviceCall :GetReviewableHITs, :GetReviewableHITsResult
60
+ serviceCall :SetHITAsReviewing, :SetHITAsReviewingResult
61
+ serviceCall :GetAssignmentsForHIT, :GetAssignmentsForHITResult
62
+ serviceCall :GetReviewResultsForHIT, :GetReviewResultsForHITResult
63
+
64
+ paginate :SearchHITs, :HIT
65
+ paginate :GetReviewableHITs, :HIT
66
+ paginate :GetAssignmentsForHIT, :Assignment
67
+
68
+ serviceCall :GetAssignment, :GetAssignmentResult
69
+ serviceCall :ApproveAssignment, :ApproveAssignmentResult
70
+ serviceCall :RejectAssignment, :RejectAssignmentResult
71
+ serviceCall :ApproveRejectedAssignment, :ApproveRejectedAssignmentResult
72
+
73
+ serviceCall :GrantBonus, :GrantBonusResult
74
+ serviceCall :GetBonusPayments, :GetBonusPaymentsResult
75
+
76
+ serviceCall :CreateQualificationType, :QualificationType, { :QualificationTypeStatus => 'Active' }
77
+ serviceCall :GetQualificationType, :QualificationType
78
+ serviceCall :SearchQualificationTypes, :SearchQualificationTypesResult, { :MustBeRequestable => true }
79
+ serviceCall :UpdateQualificationType, :QualificationType
80
+ serviceCall :GetQualificationsForQualificationType, :GetQualificationsForQualificationTypeResult, { :Status => 'Granted' }
81
+ serviceCall :GetHITsForQualificationType, :GetHITsForQualificationTypeResult
82
+ serviceCall :DisposeQualficationType, :DisposeQualificationTypeResult
83
+
84
+ paginate :SearchQualificationTypes, :QualificationType
85
+ paginate :GetQualificationsForQualificationType, :Qualification
86
+
87
+ serviceCall :AssignQualification, :AssignQualificationResult
88
+ serviceCall :GetQualificationRequests, :GetQualificationRequestsResult
89
+ serviceCall :GrantQualification, :GrantQualificationResult
90
+ serviceCall :RejectQualificationRequest, :RejectQualificationRequestResult
91
+ serviceCall :GetQualificationScore, :Qualification
92
+ serviceCall :UpdateQualificationScore, :UpdateQualificationScoreResult
93
+ serviceCall :RevokeQualification, :RevokeQualificationResult
94
+
95
+ paginate :GetQualificationRequests, :QualificationRequest
96
+
97
+ serviceCall :GetBlockedWorkers, :GetBlockedWorkersResult
98
+ serviceCall :BlockWorker, :BlockWorkerResult
99
+ serviceCall :UnblockWorker, :UnblockWorkerResult
100
+
101
+ paginate :GetBlockedWorkers, :WorkerBlock
102
+
103
+ serviceCall :GetFileUploadURL, :GetFileUploadURLResult
104
+ serviceCall :GetAccountBalance, :GetAccountBalanceResult
105
+ serviceCall :GetRequesterStatistic, :GetStatisticResult, { :Count => 1 }
106
+ serviceCall :GetRequesterWorkerStatistic, :GetStatisticResult, { :Count => 1 }
107
+
108
+ serviceCall :NotifyWorkers, :NotifyWorkersResult
109
+
110
+ def initialize(args={})
111
+ newargs = args.dup
112
+ unless args[:Config].nil?
113
+ if args[:Config] == :Rails
114
+ rails_config = Amazon::Util::DataReader.load( File.join(::RAILS_ROOT,'config','mturk.yml'), :YAML )
115
+ newargs = args.merge rails_config[::RAILS_ENV].inject({}) {|a,b| a[b[0].to_sym] = b[1] ; a }
116
+ else
117
+ loaded = Amazon::Util::DataReader.load( args[:Config], :YAML )
118
+ newargs = args.merge loaded.inject({}) {|a,b| a[b[0].to_sym] = b[1] ; a }
119
+ end
120
+ end
121
+ @threadcount = args[:ThreadCount].to_i
122
+ @threadcount = DEFAULT_THREADCOUNT unless @threadcount >= 1
123
+ raise "Cannot override WSDL version ( #{WSDL_VERSION} )" unless args[:Version].nil? or args[:Version].equals? WSDL_VERSION
124
+ super newargs.merge( :Name => :AWSMechanicalTurkRequester,
125
+ :ServiceClass => Amazon::WebServices::MechanicalTurk,
126
+ :Version => WSDL_VERSION )
127
+ end
128
+
129
+ # Create a series of similar HITs, sharing common parameters. Utilizes HITType
130
+ # * hit_template is the array of parameters to pass to createHIT.
131
+ # * question_template will be passed as a template into ERB to generate the :Question parameter
132
+ # * the RequesterAnnotation parameter of hit_template will also be passed through ERB
133
+ # * hit_data_set should consist of an array of hashes defining unique instance variables utilized by question_template
134
+ def createHITs( hit_template, question_template, hit_data_set )
135
+ hit_template = hit_template.dup
136
+ lifetime = hit_template[:LifetimeInSeconds]
137
+ numassignments_template = hit_template[:MaxAssignments]
138
+ annotation_template = hit_template[:RequesterAnnotation]
139
+ hit_template.delete :LifetimeInSeconds
140
+ hit_template.delete :MaxAssignments
141
+ hit_template.delete :RequesterAnnotation
142
+
143
+ ht = hit_template[:HITTypeId] || registerHITType( hit_template )[:HITTypeId]
144
+
145
+ tp = Amazon::Util::ThreadPool.new @threadcount
146
+
147
+ created = [].extend(MonitorMixin)
148
+ failed = [].extend(MonitorMixin)
149
+ hit_data_set.each do |hd|
150
+ tp.addWork(hd) do |hit_data|
151
+ begin
152
+ b = Amazon::Util::Binder.new( hit_data )
153
+ annotation = annotation_template.nil? ? nil : b.erb_eval( annotation_template )
154
+ numassignments = numassignments_template.nil? ? nil : b.erb_eval( numassignments_template.to_s ).to_i
155
+ question = b.erb_eval( question_template )
156
+ result = self.createHIT( :HITTypeId => ht,
157
+ :LifetimeInSeconds => lifetime,
158
+ :MaxAssignments => ( hit_data[:MaxAssignments] || numassignments || 1 ),
159
+ :Question => question,
160
+ :RequesterAnnotation => ( hit_data[:RequesterAnnotation] || annotation || "")
161
+ )
162
+ created.synchronize do
163
+ created << result
164
+ end
165
+ rescue => e
166
+ failed.synchronize do
167
+ failed << hit_data.merge( :Error => e.message )
168
+ end
169
+ end
170
+ end # tp.addWork
171
+ end # hit_data_set.each
172
+ tp.finish
173
+
174
+ return :Created => created, :Failed => failed
175
+ end
176
+
177
+ # Update a series of HITs to belong to a new HITType
178
+ # * hit_template is the array of parameters to pass to registerHITType
179
+ # * hit_ids is a list of HITIds (strings)
180
+ def updateHITs( hit_template, hit_ids )
181
+ hit_template = hit_template.dup
182
+ hit_template.delete :LifetimeInSeconds
183
+ hit_template.delete :RequesterAnnotation
184
+
185
+ hit_type_id = registerHITType( hit_template )[:HITTypeId]
186
+
187
+ tp = Amazon::Util::ThreadPool.new @threadcount
188
+
189
+ updated = [].extend(MonitorMixin)
190
+ failed = [].extend(MonitorMixin)
191
+ hit_ids.each do |hid|
192
+ tp.addWork(hid) do |hit_id|
193
+ begin
194
+ changeHITTypeOfHIT( :HITId => hit_id, :HITTypeId => hit_type_id )
195
+ updated.synchronize do
196
+ updated << hit_id
197
+ end
198
+ rescue => e
199
+ failed.synchronize do
200
+ failed << { :HITId => hit_id, :Error => e.message }
201
+ end
202
+ end
203
+ end # tp.addWork
204
+ end # hit_ids.each
205
+ tp.finish
206
+
207
+ return :Updated => updated, :Failed => failed
208
+ end
209
+
210
+
211
+ # Update a HIT with new properties.
212
+ # hit_id:: Id of the HIT to update
213
+ # hit_template:: hash ( parameter => value ) of parameters to update
214
+ #
215
+ # Acceptable attributes:
216
+ # * Title
217
+ # * Description
218
+ # * Keywords
219
+ # * Reward
220
+ # * QualificationRequirement
221
+ # * AutoApprovalDelayInSeconds
222
+ # * AssignmentDurationInSeconds
223
+ #
224
+ # Behind the scenes, this function retrieves the HIT, merges the HITs
225
+ # current attributes with any you specify, and registers a new HIT
226
+ # Template. It then uses the new ChangeHITTypeOfHIT function to move
227
+ # your HIT to the newly-created HIT Template.
228
+ def updateHIT( hit_id, hit_template )
229
+ hit_template = hit_template.dup
230
+
231
+ hit = getHIT( :HITId => hit_id )
232
+
233
+ props = %w( Title Description Keywords Reward QualificationRequirement
234
+ AutoApprovalDelayInSeconds AssignmentDurationInSeconds
235
+ ).collect {|str| str.to_sym }
236
+
237
+ props.each do |p|
238
+ hit_template[p] = hit[p] if hit_template[p].nil?
239
+ end
240
+
241
+ hit_type_id = registerHITType( hit_template )[:HITTypeId]
242
+
243
+ changeHITTypeOfHIT( :HITId => hit_id, :HITTypeId => hit_type_id )
244
+ end
245
+
246
+
247
+ def getHITResults( list )
248
+ results = [].extend(MonitorMixin)
249
+ tp = Amazon::Util::ThreadPool.new @threadcount
250
+ list.each do |line|
251
+ tp.addWork(line) do |h|
252
+ hit = getHIT( :HITId => h[:HITId] )
253
+ getAssignmentsForHITAll( :HITId => h[:HITId] ).each {|assignment|
254
+ results.synchronize do
255
+ results << ( hit.merge( assignment ) )
256
+ end
257
+ }
258
+ end
259
+ end
260
+ tp.finish
261
+ results.flatten
262
+ end
263
+
264
+ # Returns available funds in USD
265
+ # Calls getAccountBalance and parses out the correct amount
266
+ def availableFunds
267
+ return getAccountBalance[:AvailableBalance][:Amount]
268
+ end
269
+
270
+ # helper function to simplify answer XML
271
+ def simplifyAnswer( answerXML )
272
+ answerHash = Amazon::WebServices::Util::XMLSimplifier.simplify REXML::Document.new(answerXML)
273
+ list = [answerHash[:Answer]].flatten
274
+ list.inject({}) { |answers, answer|
275
+ id = answer[:QuestionIdentifier]
276
+ result = answer[:FreeText] || answer[:SelectionIdentifier] || answer[:UploadedFileKey]
277
+ answers[id] = result
278
+ answers
279
+ }
280
+ end
281
+
282
+ end # MechanicalTurkRequester
283
+
284
+ end # Amazon::WebServices
285
+ end # Amazon
@@ -0,0 +1,153 @@
1
+ # Copyright:: Copyright (c) 2007-2014 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,args )
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, args )
74
+ log "Handling error: #{error.inspect}"
75
+ idempotent = args.none? {|params| params[:UniqueRequestToken].nil? || params[:UniqueRequestToken].empty? }
76
+ retryable = methodRetryable( method ) || idempotent
77
+
78
+ case error.class.to_s
79
+ when 'Timeout::Error','SOAP::HTTPStreamError','Errno::ECONNRESET','Errno::EPIPE'
80
+ if retryable
81
+ return :RetryImmediate
82
+ else
83
+ return :Unknown
84
+ end
85
+ when 'SOAP::FaultError'
86
+ case error.faultcode.data
87
+ when "aws:Server.ServiceUnavailable"
88
+ return :RetryWithBackoff
89
+ else
90
+ return :Unknown
91
+ end
92
+ when 'Amazon::WebServices::Util::ValidationException'
93
+ case error.message
94
+ when 'AWS.ServiceUnavailable'
95
+ return :RetryWithBackoff if retryable
96
+ end
97
+ return :Fail
98
+ when 'RuntimeError'
99
+ case error.message
100
+ when 'Throttled'
101
+ return :RetryWithBackoff
102
+ else
103
+ return :RetryImmediate
104
+ end
105
+ else
106
+ return :Unknown
107
+ end
108
+ end
109
+
110
+ def canRetry( try )
111
+ try <= MAX_RETRY
112
+ end
113
+
114
+ def doBackoff( try )
115
+ return false unless canRetry(try)
116
+ delay = BACKOFF_INITIAL * ( BACKOFF_BASE ** try )
117
+ sleep delay
118
+ return true
119
+ end
120
+
121
+ def isResultTag( tag )
122
+ tag.to_s =~ RESULT_PATTERN or ACCEPTABLE_RESULTS.include?( tag.to_s )
123
+ end
124
+
125
+ def validateResponse(response)
126
+ log "Validating response: #{response.inspect}"
127
+ if response[:Errors] and response[:Errors][:Error]
128
+ if response[:Errors][:Error][:Code] == "ServiceUnavailable"
129
+ raise 'Throttled'
130
+ else
131
+ raise Util::ValidationException.new(response)
132
+ end
133
+ end
134
+ if response[:OperationRequest] and response[:OperationRequest][:Errors]
135
+ raise Util::ValidationException.new(response)
136
+ end
137
+ resultTags = response.keys.find_all {|r| isResultTag( r ) }
138
+ raise Util::ValidationException.new(response, "Didn't get back an acceptable result tag (got back #{response.keys.join(',')})") if resultTags.empty?
139
+ resultTags.each do |resultTag|
140
+ log "using result tag <#{resultTag}>"
141
+ result = response[resultTag]
142
+ if result[:Request] and result[:Request][:Errors]
143
+ raise Util::ValidationException.new(result)
144
+ end
145
+ end
146
+ response
147
+ end
148
+
149
+ end # MechanicalTurkErrorHandler
150
+
151
+ end # Amazon::WebServices::MTurk
152
+ end # Amazon::WebServices
153
+ end # Amazon