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

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

Issue 12042069: Scripts to download files from google storage based on sha1 sums (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Removed gsutil/tests and gsutil/docs Created 7 years, 10 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
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 example_config_file = '''Example configuration file:
44
45 {
46 "title": "Pick your favorite color",
47 "description": "In this task, you are asked to pick your favorite color.",
48 "reward": 0.50,
49 "assignments": 10,
50 "duration": "20 min",
51 "keywords": ["color", "favorites", "survey"],
52 "lifetime": "7 d",
53 "approval_delay": "14 d",
54 "qualifications": [
55 "PercentAssignmentsApproved > 90",
56 "Locale == US"
57 ],
58 "question_url": "http://example.com/myhit",
59 "question_frame_height": 450
60 }'''
61
62 # --------------------------------------------------
63 # Subroutines
64 # --------------------------------------------------
65
66 def unjson(path):
67 with open(path) as o:
68 return json.load(o)
69
70 def add_argparse_arguments(parser):
71 parser.add_argument('-P', '--production',
72 dest = 'sandbox', action = 'store_false', default = True,
73 help = 'use the production site (default: use the sandbox)')
74 parser.add_argument('--nicknames',
75 dest = 'nicknames_path', metavar = 'PATH',
76 default = default_nicknames_path,
77 help = 'where to store HIT nicknames (default: {})'.format(
78 default_nicknames_path))
79
80 def init_by_args(args):
81 init(args.sandbox, args.nicknames_path)
82
83 def init(sandbox = False, nicknames_path = default_nicknames_path):
84 global con, mturk_website, nicknames, original_nicknames
85
86 mturk_website = 'workersandbox.mturk.com' if sandbox else 'www.mturk.com'
87 con = boto.mturk.connection.MTurkConnection(
88 host = 'mechanicalturk.sandbox.amazonaws.com' if sandbox else 'mechanica lturk.amazonaws.com')
89
90 try:
91 nicknames = unjson(nicknames_path)
92 except IOError:
93 nicknames = {}
94 original_nicknames = nicknames.copy()
95
96 def save_nicknames(nicknames_path = default_nicknames_path):
97 if nicknames != original_nicknames:
98 with open(nicknames_path, 'w') as o:
99 json.dump(nicknames, o, sort_keys = True, indent = 4)
100 print >>o
101
102 time_units = dict(s = 1, min = 60, h = 60 * 60, d = 24 * 60 * 60)
103 def parse_duration(s):
104 '''Parses durations like "2 d", "48 h", "2880 min",
105 "172800 s", or "172800".'''
106 x = s.split()
107 return int(x[0]) * time_units['s' if len(x) == 1 else x[1]]
108 def display_duration(n):
109 for unit, m in sorted(time_units.items(), key = lambda x: -x[1]):
110 if n % m == 0:
111 return '{} {}'.format(n / m, unit)
112
113 def parse_qualification(s):
114 '''Parses qualifications like "PercentAssignmentsApproved > 90" and "Locale == US".'''
115 name, comparator, value = s.split()
116 f = dict(
117 PercentAssignmentsApproved = boto.mturk.qualification.PercentAssignments ApprovedRequirement,
118 Locale = boto.mturk.qualification.LocaleRequirement)[name]
119 comparator = {v : k for k, v in dict(
120 LessThan = '<', LessThanOrEqualTo = '<=',
121 GreaterThan = '>', GreaterThanOrEqualTo = '>=',
122 EqualTo = '==', NotEqualTo = '!=').items()}[comparator]
123 return f(comparator, value, required_to_preview = False)
124
125 def preview_url(hit):
126 return 'https://{}/mturk/preview?groupId={}'.format(
127 mturk_website, hit.HITTypeId)
128
129 def parse_timestamp(s):
130 '''Takes a timestamp like "2012-11-24T16:34:41Z".
131
132 Returns a datetime object in the local time zone.'''
133 return datetime.datetime.fromtimestamp(
134 calendar.timegm(
135 datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%SZ').timetuple()))
136
137 def get_hitid(nickname_or_hitid):
138 return nicknames.get(nickname_or_hitid) or nickname_or_hitid
139
140 def get_nickname(hitid):
141 for k, v in nicknames.items():
142 if v == hitid:
143 return k
144 return None
145
146 def display_datetime(dt):
147 return dt.strftime('%e %b %Y, %l:%M %P')
148
149 def display_hit(hit, verbose = False):
150 et = parse_timestamp(hit.Expiration)
151 return '\n'.join([
152 '{} - {} ({}, {}, {})'.format(
153 get_nickname(hit.HITId),
154 hit.Title,
155 hit.FormattedPrice,
156 display_duration(int(hit.AssignmentDurationInSeconds)),
157 hit.HITStatus),
158 'HIT ID: ' + hit.HITId,
159 'Type ID: ' + hit.HITTypeId,
160 'Group ID: ' + hit.HITGroupId,
161 'Preview: ' + preview_url(hit),
162 'Created {} {}'.format(
163 display_datetime(parse_timestamp(hit.CreationTime)),
164 'Expired' if et <= datetime.datetime.now() else
165 'Expires ' + display_datetime(et)),
166 'Assignments: {} -- {} avail, {} pending, {} reviewable, {} reviewed'.fo rmat(
167 hit.MaxAssignments,
168 hit.NumberOfAssignmentsAvailable,
169 hit.NumberOfAssignmentsPending,
170 int(hit.MaxAssignments) - (int(hit.NumberOfAssignmentsAvailable) + i nt(hit.NumberOfAssignmentsPending) + int(hit.NumberOfAssignmentsCompleted)),
171 hit.NumberOfAssignmentsCompleted)
172 if hasattr(hit, 'NumberOfAssignmentsAvailable')
173 else 'Assignments: {} total'.format(hit.MaxAssignments),
174 # For some reason, SearchHITs includes the
175 # NumberOfAssignmentsFoobar fields but GetHIT doesn't.
176 ] + ([] if not verbose else [
177 '\nDescription: ' + hit.Description,
178 '\nKeywords: ' + hit.Keywords
179 ])) + '\n'
180
181 def digest_assignment(a):
182 return dict(
183 answers = {str(x.qid): str(x.fields[0]) for x in a.answers[0]},
184 **{k: str(getattr(a, k)) for k in (
185 'AcceptTime', 'SubmitTime',
186 'HITId', 'AssignmentId', 'WorkerId',
187 'AssignmentStatus')})
188
189 # --------------------------------------------------
190 # Commands
191 # --------------------------------------------------
192
193 def get_balance():
194 return con.get_account_balance()
195
196 def show_hit(hit):
197 return display_hit(con.get_hit(hit)[0], verbose = True)
198
199 def list_hits():
200 'Lists your 10 most recently created HITs, with the most recent last.'
201 return '\n'.join(reversed(map(display_hit, con.search_hits(
202 sort_by = 'CreationTime',
203 sort_direction = 'Descending',
204 page_size = 10))))
205
206 def make_hit(title, description, keywords, reward, question_url, question_frame_ height, duration, assignments, approval_delay, lifetime, qualifications = []):
207 r = con.create_hit(
208 title = title,
209 description = description,
210 keywords = con.get_keywords_as_string(keywords),
211 reward = con.get_price_as_price(reward),
212 question = boto.mturk.question.ExternalQuestion(
213 question_url,
214 question_frame_height),
215 duration = parse_duration(duration),
216 qualifications = boto.mturk.qualification.Qualifications(
217 map(parse_qualification, qualifications)),
218 max_assignments = assignments,
219 approval_delay = parse_duration(approval_delay),
220 lifetime = parse_duration(lifetime))
221 nick = None
222 available_nicks = nickname_pool - set(nicknames.keys())
223 if available_nicks:
224 nick = min(available_nicks)
225 nicknames[nick] = r[0].HITId
226 if interactive:
227 print 'Nickname:', nick
228 print 'HIT ID:', r[0].HITId
229 print 'Preview:', preview_url(r[0])
230 else:
231 return r[0]
232
233 def extend_hit(hit, assignments_increment = None, expiration_increment = None):
234 con.extend_hit(hit, assignments_increment, expiration_increment)
235
236 def expire_hit(hit):
237 con.expire_hit(hit)
238
239 def delete_hit(hit):
240 '''Deletes a HIT using DisableHIT.
241
242 Unreviewed assignments get automatically approved. Unsubmitted
243 assignments get automatically approved upon submission.
244
245 The API docs say DisableHIT doesn't work with Reviewable HITs,
246 but apparently, it does.'''
247 con.disable_hit(hit)
248 global nicknames
249 nicknames = {k: v for k, v in nicknames.items() if v != hit}
250
251 def list_assignments(hit, only_reviewable = False):
252 assignments = map(digest_assignment, con.get_assignments(
253 hit_id = hit,
254 page_size = 100,
255 status = 'Submitted' if only_reviewable else None))
256 if interactive:
257 print json.dumps(assignments, sort_keys = True, indent = 4)
258 print ' '.join([a['AssignmentId'] for a in assignments])
259 print ' '.join([a['WorkerId'] + ',' + a['AssignmentId'] for a in assignm ents])
260 else:
261 return assignments
262
263 def grant_bonus(message, amount, pairs):
264 for worker, assignment in pairs:
265 con.grant_bonus(worker, assignment, con.get_price_as_price(amount), mess age)
266 if interactive: print 'Bonused', worker
267
268 def approve_assignments(message, assignments):
269 for a in assignments:
270 con.approve_assignment(a, message)
271 if interactive: print 'Approved', a
272
273 def reject_assignments(message, assignments):
274 for a in assignments:
275 con.reject_assignment(a, message)
276 if interactive: print 'Rejected', a
277
278 def unreject_assignments(message, assignments):
279 for a in assignments:
280 con.approve_rejected_assignment(a, message)
281 if interactive: print 'Unrejected', a
282
283 # --------------------------------------------------
284 # Mainline code
285 # --------------------------------------------------
286
287 if __name__ == '__main__':
288 interactive = True
289
290 parser = argparse.ArgumentParser()
291 add_argparse_arguments(parser)
292 subs = parser.add_subparsers()
293
294 sub = subs.add_parser('bal',
295 help = 'display your prepaid balance')
296 sub.set_defaults(f = get_balance, a = lambda: [])
297
298 sub = subs.add_parser('hit',
299 help = 'get information about a HIT')
300 sub.add_argument('hit',
301 help = 'nickname or ID of the HIT to show')
302 sub.set_defaults(f = show_hit, a = lambda:
303 [get_hitid(args.hit)])
304
305 sub = subs.add_parser('hits',
306 help = 'list all your HITS')
307 sub.set_defaults(f = list_hits, a = lambda: [])
308
309 sub = subs.add_parser('new',
310 help = 'create a new HIT (external questions only)',
311 epilog = example_config_file,
312 formatter_class = argparse.RawDescriptionHelpFormatter)
313 sub.add_argument('json_path',
314 help = 'path to JSON configuration file for the HIT')
315 sub.add_argument('-u', '--question-url', dest = 'question_url',
316 metavar = 'URL',
317 help = 'URL for the external question')
318 sub.add_argument('-a', '--assignments', dest = 'assignments',
319 type = int, metavar = 'N',
320 help = 'number of assignments')
321 sub.add_argument('-r', '--reward', dest = 'reward',
322 type = float, metavar = 'PRICE',
323 help = 'reward amount, in USD')
324 sub.set_defaults(f = make_hit, a = lambda: dict(
325 unjson(args.json_path).items() + [(k, getattr(args, k))
326 for k in ('question_url', 'assignments', 'reward')
327 if getattr(args, k) is not None]))
328
329 sub = subs.add_parser('extend',
330 help = 'add assignments or time to a HIT')
331 sub.add_argument('hit',
332 help = 'nickname or ID of the HIT to extend')
333 sub.add_argument('-a', '--assignments', dest = 'assignments',
334 metavar = 'N', type = int,
335 help = 'number of assignments to add')
336 sub.add_argument('-t', '--time', dest = 'time',
337 metavar = 'T',
338 help = 'amount of time to add to the expiration date')
339 sub.set_defaults(f = extend_hit, a = lambda:
340 [get_hitid(args.hit), args.assignments,
341 args.time and parse_duration(args.time)])
342
343 sub = subs.add_parser('expire',
344 help = 'force a HIT to expire without deleting it')
345 sub.add_argument('hit',
346 help = 'nickname or ID of the HIT to expire')
347 sub.set_defaults(f = expire_hit, a = lambda:
348 [get_hitid(args.hit)])
349
350 sub = subs.add_parser('rm',
351 help = 'delete a HIT')
352 sub.add_argument('hit',
353 help = 'nickname or ID of the HIT to delete')
354 sub.set_defaults(f = delete_hit, a = lambda:
355 [get_hitid(args.hit)])
356
357 sub = subs.add_parser('as',
358 help = "list a HIT's submitted assignments")
359 sub.add_argument('hit',
360 help = 'nickname or ID of the HIT to get assignments for')
361 sub.add_argument('-r', '--reviewable', dest = 'only_reviewable',
362 action = 'store_true',
363 help = 'show only unreviewed assignments')
364 sub.set_defaults(f = list_assignments, a = lambda:
365 [get_hitid(args.hit), args.only_reviewable])
366
367 for command, fun, helpmsg in [
368 ('approve', approve_assignments, 'approve assignments'),
369 ('reject', reject_assignments, 'reject assignments'),
370 ('unreject', unreject_assignments, 'approve previously rejected assi gnments')]:
371 sub = subs.add_parser(command, help = helpmsg)
372 sub.add_argument('assignment', nargs = '+',
373 help = 'ID of an assignment')
374 sub.add_argument('-m', '--message', dest = 'message',
375 metavar = 'TEXT',
376 help = 'feedback message shown to workers')
377 sub.set_defaults(f = fun, a = lambda:
378 [args.message, args.assignment])
379
380 sub = subs.add_parser('bonus',
381 help = 'give a worker a bonus')
382 sub.add_argument('amount', type = float,
383 help = 'bonus amount, in USD')
384 sub.add_argument('message',
385 help = 'the reason for the bonus (shown to workers in an email sent by M Turk)')
386 sub.add_argument('widaid', nargs = '+',
387 help = 'a WORKER_ID,ASSIGNMENT_ID pair')
388 sub.set_defaults(f = grant_bonus, a = lambda:
389 [args.message, args.amount,
390 [p.split(',') for p in args.widaid]])
391
392 args = parser.parse_args()
393
394 init_by_args(args)
395
396 f = args.f
397 a = args.a()
398 if isinstance(a, dict):
399 # We do some introspective gymnastics so we can produce a
400 # less incomprehensible error message if some arguments
401 # are missing.
402 spec = inspect.getargspec(f)
403 missing = set(spec.args[: len(spec.args) - len(spec.defaults)]) - set(a. keys())
404 if missing:
405 raise ValueError('Missing arguments: ' + ', '.join(missing))
406 doit = lambda: f(**a)
407 else:
408 doit = lambda: f(*a)
409
410 try:
411 x = doit()
412 except boto.mturk.connection.MTurkRequestError as e:
413 print 'MTurk error:', e.error_message
414 sys.exit(1)
415
416 if x is not None:
417 print x
418
419 save_nicknames()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698