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

Side by Side Diff: third_party/gsutil/boto/bin/mturk

Issue 12317103: Added gsutil to depot tools (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 7 years, 9 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
« no previous file with comments | « third_party/gsutil/boto/bin/lss3 ('k') | third_party/gsutil/boto/bin/pyami_sendmail » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2012 Kodi Arfer
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a
5 # copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish, dis-
8 # tribute, sublicense, and/or sell copies of the Software, and to permit
9 # persons to whom the Software is furnished to do so, subject to the fol-
10 # lowing conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
22 import argparse # Hence, Python 2.7 is required.
23 import sys
24 import os.path
25 import string
26 import inspect
27 import datetime, calendar
28 import json
29 import boto.mturk.connection, boto.mturk.price, boto.mturk.question, boto.mturk. qualification
30
31 # --------------------------------------------------
32 # Globals
33 # -------------------------------------------------
34
35 interactive = False
36 con = None
37 mturk_website = None
38
39 default_nicknames_path = os.path.expanduser('~/.boto_mturkcli_hit_nicknames')
40 nicknames = {}
41 nickname_pool = set(string.ascii_lowercase)
42
43 time_units = dict(
44 s = 1,
45 min = 60,
46 h = 60 * 60,
47 d = 24 * 60 * 60)
48
49 qual_requirements = dict(
50 Adult = '00000000000000000060',
51 Locale = '00000000000000000071',
52 NumberHITsApproved = '00000000000000000040',
53 PercentAssignmentsSubmitted = '00000000000000000000',
54 PercentAssignmentsAbandoned = '00000000000000000070',
55 PercentAssignmentsReturned = '000000000000000000E0',
56 PercentAssignmentsApproved = '000000000000000000L0',
57 PercentAssignmentsRejected = '000000000000000000S0')
58
59 qual_comparators = {v : k for k, v in dict(
60 LessThan = '<', LessThanOrEqualTo = '<=',
61 GreaterThan = '>', GreaterThanOrEqualTo = '>=',
62 EqualTo = '==', NotEqualTo = '!=',
63 Exists = 'exists').items()}
64
65 example_config_file = '''Example configuration file:
66
67 {
68 "title": "Pick your favorite color",
69 "description": "In this task, you are asked to pick your favorite color.",
70 "reward": 0.50,
71 "assignments": 10,
72 "duration": "20 min",
73 "keywords": ["color", "favorites", "survey"],
74 "lifetime": "7 d",
75 "approval_delay": "14 d",
76 "qualifications": [
77 "PercentAssignmentsApproved > 90",
78 "Locale == US",
79 "2ARFPLSP75KLA8M8DH1HTEQVJT3SY6 exists"
80 ],
81 "question_url": "http://example.com/myhit",
82 "question_frame_height": 450
83 }'''
84
85 # --------------------------------------------------
86 # Subroutines
87 # --------------------------------------------------
88
89 def unjson(path):
90 with open(path) as o:
91 return json.load(o)
92
93 def add_argparse_arguments(parser):
94 parser.add_argument('-P', '--production',
95 dest = 'sandbox', action = 'store_false', default = True,
96 help = 'use the production site (default: use the sandbox)')
97 parser.add_argument('--nicknames',
98 dest = 'nicknames_path', metavar = 'PATH',
99 default = default_nicknames_path,
100 help = 'where to store HIT nicknames (default: {})'.format(
101 default_nicknames_path))
102
103 def init_by_args(args):
104 init(args.sandbox, args.nicknames_path)
105
106 def init(sandbox = False, nicknames_path = default_nicknames_path):
107 global con, mturk_website, nicknames, original_nicknames
108
109 mturk_website = 'workersandbox.mturk.com' if sandbox else 'www.mturk.com'
110 con = boto.mturk.connection.MTurkConnection(
111 host = 'mechanicalturk.sandbox.amazonaws.com' if sandbox else 'mechanica lturk.amazonaws.com')
112
113 try:
114 nicknames = unjson(nicknames_path)
115 except IOError:
116 nicknames = {}
117 original_nicknames = nicknames.copy()
118
119 def save_nicknames(nicknames_path = default_nicknames_path):
120 if nicknames != original_nicknames:
121 with open(nicknames_path, 'w') as o:
122 json.dump(nicknames, o, sort_keys = True, indent = 4)
123 print >>o
124
125 def parse_duration(s):
126 '''Parses durations like "2 d", "48 h", "2880 min",
127 "172800 s", or "172800".'''
128 x = s.split()
129 return int(x[0]) * time_units['s' if len(x) == 1 else x[1]]
130 def display_duration(n):
131 for unit, m in sorted(time_units.items(), key = lambda x: -x[1]):
132 if n % m == 0:
133 return '{} {}'.format(n / m, unit)
134
135 def parse_qualification(inp):
136 '''Parses qualifications like "PercentAssignmentsApproved > 90",
137 "Locale == US", and "2ARFPLSP75KLA8M8DH1HTEQVJT3SY6 exists".'''
138 inp = inp.split()
139 name, comparator, value = inp.pop(0), inp.pop(0), (inp[0] if len(inp) else N one)
140 qtid = qual_requirements.get(name)
141 if qtid is None:
142 # Treat "name" as a Qualification Type ID.
143 qtid = name
144 if qtid == qual_requirements['Locale']:
145 return boto.mturk.qualification.LocaleRequirement(
146 qual_comparators[comparator],
147 value,
148 required_to_preview = False)
149 return boto.mturk.qualification.Requirement(
150 qtid,
151 qual_comparators[comparator],
152 value,
153 required_to_preview = qtid == qual_requirements['Adult'])
154 # Thus required_to_preview is true only for the
155 # Worker_Adult requirement.
156
157 def preview_url(hit):
158 return 'https://{}/mturk/preview?groupId={}'.format(
159 mturk_website, hit.HITTypeId)
160
161 def parse_timestamp(s):
162 '''Takes a timestamp like "2012-11-24T16:34:41Z".
163
164 Returns a datetime object in the local time zone.'''
165 return datetime.datetime.fromtimestamp(
166 calendar.timegm(
167 datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%SZ').timetuple()))
168
169 def get_hitid(nickname_or_hitid):
170 return nicknames.get(nickname_or_hitid) or nickname_or_hitid
171
172 def get_nickname(hitid):
173 for k, v in nicknames.items():
174 if v == hitid:
175 return k
176 return None
177
178 def display_datetime(dt):
179 return dt.strftime('%e %b %Y, %l:%M %P')
180
181 def display_hit(hit, verbose = False):
182 et = parse_timestamp(hit.Expiration)
183 return '\n'.join([
184 '{} - {} ({}, {}, {})'.format(
185 get_nickname(hit.HITId),
186 hit.Title,
187 hit.FormattedPrice,
188 display_duration(int(hit.AssignmentDurationInSeconds)),
189 hit.HITStatus),
190 'HIT ID: ' + hit.HITId,
191 'Type ID: ' + hit.HITTypeId,
192 'Group ID: ' + hit.HITGroupId,
193 'Preview: ' + preview_url(hit),
194 'Created {} {}'.format(
195 display_datetime(parse_timestamp(hit.CreationTime)),
196 'Expired' if et <= datetime.datetime.now() else
197 'Expires ' + display_datetime(et)),
198 'Assignments: {} -- {} avail, {} pending, {} reviewable, {} reviewed'.fo rmat(
199 hit.MaxAssignments,
200 hit.NumberOfAssignmentsAvailable,
201 hit.NumberOfAssignmentsPending,
202 int(hit.MaxAssignments) - (int(hit.NumberOfAssignmentsAvailable) + i nt(hit.NumberOfAssignmentsPending) + int(hit.NumberOfAssignmentsCompleted)),
203 hit.NumberOfAssignmentsCompleted)
204 if hasattr(hit, 'NumberOfAssignmentsAvailable')
205 else 'Assignments: {} total'.format(hit.MaxAssignments),
206 # For some reason, SearchHITs includes the
207 # NumberOfAssignmentsFoobar fields but GetHIT doesn't.
208 ] + ([] if not verbose else [
209 '\nDescription: ' + hit.Description,
210 '\nKeywords: ' + hit.Keywords
211 ])) + '\n'
212
213 def digest_assignment(a):
214 return dict(
215 answers = {str(x.qid): str(x.fields[0]) for x in a.answers[0]},
216 **{k: str(getattr(a, k)) for k in (
217 'AcceptTime', 'SubmitTime',
218 'HITId', 'AssignmentId', 'WorkerId',
219 'AssignmentStatus')})
220
221 # --------------------------------------------------
222 # Commands
223 # --------------------------------------------------
224
225 def get_balance():
226 return con.get_account_balance()
227
228 def show_hit(hit):
229 return display_hit(con.get_hit(hit)[0], verbose = True)
230
231 def list_hits():
232 'Lists your 10 most recently created HITs, with the most recent last.'
233 return '\n'.join(reversed(map(display_hit, con.search_hits(
234 sort_by = 'CreationTime',
235 sort_direction = 'Descending',
236 page_size = 10))))
237
238 def make_hit(title, description, keywords, reward, question_url, question_frame_ height, duration, assignments, approval_delay, lifetime, qualifications = []):
239 r = con.create_hit(
240 title = title,
241 description = description,
242 keywords = con.get_keywords_as_string(keywords),
243 reward = con.get_price_as_price(reward),
244 question = boto.mturk.question.ExternalQuestion(
245 question_url,
246 question_frame_height),
247 duration = parse_duration(duration),
248 qualifications = boto.mturk.qualification.Qualifications(
249 map(parse_qualification, qualifications)),
250 max_assignments = assignments,
251 approval_delay = parse_duration(approval_delay),
252 lifetime = parse_duration(lifetime))
253 nick = None
254 available_nicks = nickname_pool - set(nicknames.keys())
255 if available_nicks:
256 nick = min(available_nicks)
257 nicknames[nick] = r[0].HITId
258 if interactive:
259 print 'Nickname:', nick
260 print 'HIT ID:', r[0].HITId
261 print 'Preview:', preview_url(r[0])
262 else:
263 return r[0]
264
265 def extend_hit(hit, assignments_increment = None, expiration_increment = None):
266 con.extend_hit(hit, assignments_increment, expiration_increment)
267
268 def expire_hit(hit):
269 con.expire_hit(hit)
270
271 def delete_hit(hit):
272 '''Deletes a HIT using DisableHIT.
273
274 Unreviewed assignments get automatically approved. Unsubmitted
275 assignments get automatically approved upon submission.
276
277 The API docs say DisableHIT doesn't work with Reviewable HITs,
278 but apparently, it does.'''
279 con.disable_hit(hit)
280 global nicknames
281 nicknames = {k: v for k, v in nicknames.items() if v != hit}
282
283 def list_assignments(hit, only_reviewable = False):
284 assignments = map(digest_assignment, con.get_assignments(
285 hit_id = hit,
286 page_size = 100,
287 status = 'Submitted' if only_reviewable else None))
288 if interactive:
289 print json.dumps(assignments, sort_keys = True, indent = 4)
290 print ' '.join([a['AssignmentId'] for a in assignments])
291 print ' '.join([a['WorkerId'] + ',' + a['AssignmentId'] for a in assignm ents])
292 else:
293 return assignments
294
295 def grant_bonus(message, amount, pairs):
296 for worker, assignment in pairs:
297 con.grant_bonus(worker, assignment, con.get_price_as_price(amount), mess age)
298 if interactive: print 'Bonused', worker
299
300 def approve_assignments(message, assignments):
301 for a in assignments:
302 con.approve_assignment(a, message)
303 if interactive: print 'Approved', a
304
305 def reject_assignments(message, assignments):
306 for a in assignments:
307 con.reject_assignment(a, message)
308 if interactive: print 'Rejected', a
309
310 def unreject_assignments(message, assignments):
311 for a in assignments:
312 con.approve_rejected_assignment(a, message)
313 if interactive: print 'Unrejected', a
314
315 def notify_workers(subject, text, workers):
316 con.notify_workers(workers, subject, text)
317
318 # --------------------------------------------------
319 # Mainline code
320 # --------------------------------------------------
321
322 if __name__ == '__main__':
323 interactive = True
324
325 parser = argparse.ArgumentParser()
326 add_argparse_arguments(parser)
327 subs = parser.add_subparsers()
328
329 sub = subs.add_parser('bal',
330 help = 'display your prepaid balance')
331 sub.set_defaults(f = get_balance, a = lambda: [])
332
333 sub = subs.add_parser('hit',
334 help = 'get information about a HIT')
335 sub.add_argument('hit',
336 help = 'nickname or ID of the HIT to show')
337 sub.set_defaults(f = show_hit, a = lambda:
338 [get_hitid(args.hit)])
339
340 sub = subs.add_parser('hits',
341 help = 'list all your HITs')
342 sub.set_defaults(f = list_hits, a = lambda: [])
343
344 sub = subs.add_parser('new',
345 help = 'create a new HIT (external questions only)',
346 epilog = example_config_file,
347 formatter_class = argparse.RawDescriptionHelpFormatter)
348 sub.add_argument('json_path',
349 help = 'path to JSON configuration file for the HIT')
350 sub.add_argument('-u', '--question-url', dest = 'question_url',
351 metavar = 'URL',
352 help = 'URL for the external question')
353 sub.add_argument('-a', '--assignments', dest = 'assignments',
354 type = int, metavar = 'N',
355 help = 'number of assignments')
356 sub.add_argument('-r', '--reward', dest = 'reward',
357 type = float, metavar = 'PRICE',
358 help = 'reward amount, in USD')
359 sub.set_defaults(f = make_hit, a = lambda: dict(
360 unjson(args.json_path).items() + [(k, getattr(args, k))
361 for k in ('question_url', 'assignments', 'reward')
362 if getattr(args, k) is not None]))
363
364 sub = subs.add_parser('extend',
365 help = 'add assignments or time to a HIT')
366 sub.add_argument('hit',
367 help = 'nickname or ID of the HIT to extend')
368 sub.add_argument('-a', '--assignments', dest = 'assignments',
369 metavar = 'N', type = int,
370 help = 'number of assignments to add')
371 sub.add_argument('-t', '--time', dest = 'time',
372 metavar = 'T',
373 help = 'amount of time to add to the expiration date')
374 sub.set_defaults(f = extend_hit, a = lambda:
375 [get_hitid(args.hit), args.assignments,
376 args.time and parse_duration(args.time)])
377
378 sub = subs.add_parser('expire',
379 help = 'force a HIT to expire without deleting it')
380 sub.add_argument('hit',
381 help = 'nickname or ID of the HIT to expire')
382 sub.set_defaults(f = expire_hit, a = lambda:
383 [get_hitid(args.hit)])
384
385 sub = subs.add_parser('rm',
386 help = 'delete a HIT')
387 sub.add_argument('hit',
388 help = 'nickname or ID of the HIT to delete')
389 sub.set_defaults(f = delete_hit, a = lambda:
390 [get_hitid(args.hit)])
391
392 sub = subs.add_parser('as',
393 help = "list a HIT's submitted assignments")
394 sub.add_argument('hit',
395 help = 'nickname or ID of the HIT to get assignments for')
396 sub.add_argument('-r', '--reviewable', dest = 'only_reviewable',
397 action = 'store_true',
398 help = 'show only unreviewed assignments')
399 sub.set_defaults(f = list_assignments, a = lambda:
400 [get_hitid(args.hit), args.only_reviewable])
401
402 for command, fun, helpmsg in [
403 ('approve', approve_assignments, 'approve assignments'),
404 ('reject', reject_assignments, 'reject assignments'),
405 ('unreject', unreject_assignments, 'approve previously rejected assi gnments')]:
406 sub = subs.add_parser(command, help = helpmsg)
407 sub.add_argument('assignment', nargs = '+',
408 help = 'ID of an assignment')
409 sub.add_argument('-m', '--message', dest = 'message',
410 metavar = 'TEXT',
411 help = 'feedback message shown to workers')
412 sub.set_defaults(f = fun, a = lambda:
413 [args.message, args.assignment])
414
415 sub = subs.add_parser('bonus',
416 help = 'give some workers a bonus')
417 sub.add_argument('amount', type = float,
418 help = 'bonus amount, in USD')
419 sub.add_argument('message',
420 help = 'the reason for the bonus (shown to workers in an email sent by M Turk)')
421 sub.add_argument('widaid', nargs = '+',
422 help = 'a WORKER_ID,ASSIGNMENT_ID pair')
423 sub.set_defaults(f = grant_bonus, a = lambda:
424 [args.message, args.amount,
425 [p.split(',') for p in args.widaid]])
426
427 sub = subs.add_parser('notify',
428 help = 'send a message to some workers')
429 sub.add_argument('subject',
430 help = 'subject of the message')
431 sub.add_argument('message',
432 help = 'text of the message')
433 sub.add_argument('worker', nargs = '+',
434 help = 'ID of a worker')
435 sub.set_defaults(f = notify_workers, a = lambda:
436 [args.subject, args.message, args.worker])
437
438 args = parser.parse_args()
439
440 init_by_args(args)
441
442 f = args.f
443 a = args.a()
444 if isinstance(a, dict):
445 # We do some introspective gymnastics so we can produce a
446 # less incomprehensible error message if some arguments
447 # are missing.
448 spec = inspect.getargspec(f)
449 missing = set(spec.args[: len(spec.args) - len(spec.defaults)]) - set(a. keys())
450 if missing:
451 raise ValueError('Missing arguments: ' + ', '.join(missing))
452 doit = lambda: f(**a)
453 else:
454 doit = lambda: f(*a)
455
456 try:
457 x = doit()
458 except boto.mturk.connection.MTurkRequestError as e:
459 print 'MTurk error:', e.error_message
460 sys.exit(1)
461
462 if x is not None:
463 print x
464
465 save_nicknames()
OLDNEW
« no previous file with comments | « third_party/gsutil/boto/bin/lss3 ('k') | third_party/gsutil/boto/bin/pyami_sendmail » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698