mturk 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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