Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLIs for managing workers. #11

Merged
merged 7 commits into from
May 1, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion amti/clis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"""CLIs for managing HITs and their results"""

from amti.clis import (
associate,
block,
create,
delete,
disassociate,
expire,
extract,
extraction,
notify,
review,
save,
status)
status,
unblock
)
86 changes: 86 additions & 0 deletions amti/clis/associate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Command line interface for associating quals with Workers"""

import logging

import click
import csv

from amti import actions
from amti import settings
from amti import utils


logger = logging.getLogger(__name__)


@click.command(
context_settings={
'help_option_names': ['--help', '-h']
})
@click.argument(
'ids',
type=str,
nargs=-1)
@click.option(
'--file', '-f',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Path to file of WorkerIds.")
@click.option(
'--qual', '-q',
help='QualificationId (or name, if --name flag passed).')
@click.option(
'--value', '-v',
help='Integer value for qual.')
@click.option(
'--name', '-n',
is_flag=True,
help='Search for qual by name instead of id.')
@click.option(
'--notify',
is_flag=True,
help='Send notification message about qual.')
@click.option(
'--live', '-l',
is_flag=True,
help='View the status of HITs from the live MTurk site.')
def associate_qual(file, ids, qual, name, value, notify, live):
"""Associate workers with a qualification.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation conventions for the code base are implicit at the moment, and should probably be put in a document somewhere, but the gist is that:

  1. click commands and command groups
    • Doc strings should begin with 1 line summarizing the function of the script.
    • After the summary line, optionally several paragraphs can explain the arguments and usage of the script in more detail.
    • Since arguments are described in the main text, they don't need a separate section describing them.
    • Arguments should be referred to in all caps with underscores (matching how they'll appear in help output).
    • Options are documented with their own help strings and should not be referred to in the main text (unless there's a really good reason).
  2. python functions and classes (that are not click commands)

The click command doc strings here mostly just need the arguments described in the running text, the parameters sections removed, and the argument names put in all caps.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be helpful if you could provide a more complete description of the args as well as what the method returns.


Given a space seperated list of WorkerIds (IDS) and/or a path to
a CSV of WorkerIds, associate each worker with a qualification (QUAL).

NOTE: Only works with quals that both exist and are owned by the user.
"""
env = 'live' if live else 'sandbox'

client = utils.mturk.get_mturk_client(env)

worker_ids = list(ids)

# read ids from file (adds to provided ids)
if file is not None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like file might be more suitable as a default argument. As well as some other arguments such as which can take the value None.

worker_ids += utils.workers.read_workerids_from_file(file)

# set qual_id
qual_id = qual
if name:
qual_id = utils.mturk.get_qual_by_name(client, qual)
if qual_id is None:
raise ValueError(f"No qual with name {qual} found.")

args = {
"QualificationTypeId": qual_id,
"SendNotification": notify
}
if value is not None:
args['IntegerValue'] = value

# associate qual with workers
for worker_id in worker_ids:
logger.info(f'Associating qualification {qual_id} with worker {worker_id}.')
response = client.associate_qualification_with_worker(
WorkerId=worker_id,
**args
)

logger.info('Finished associating quals.')
60 changes: 60 additions & 0 deletions amti/clis/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Command line interfaces for blocking Workers"""

import logging

import click
import csv

from amti import actions
from amti import settings
from amti import utils


logger = logging.getLogger(__name__)


@click.command(
context_settings={
'help_option_names': ['--help', '-h']
})
@click.argument(
'ids',
type=str,
nargs=-1)
@click.option(
'--file', '-f',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Path to file of WorkerIds to block.")
@click.option(
'--reason', '-r',
default="Worker has produced low quality work, or is suspected of producing spam.",
help='Reason for blocking worker(s) (workers do not see).')
@click.option(
'--live', '-l',
is_flag=True,
help='View the status of HITs from the live MTurk site.')
def block_workers(file, ids, reason, live):
"""Block workers by WorkerId.

Given a space seperated list of WorkerIds (IDS) and/or a path to
a CSV of WorkerIds, create a block for each worker in the list.
"""
env = 'live' if live else 'sandbox'

client = utils.mturk.get_mturk_client(env)

worker_ids = list(ids)

# read ids from file (adds to provided ids)
if file is not None:
worker_ids += utils.workers.read_workerids_from_file(file)

# create blocks
for worker_id in worker_ids:
logger.info(f'Creating block for worker {worker_id}.')
response = client.create_worker_block(
WorkerId=worker_id,
Reason=reason
)

logger.info('Finished creating blocks.')
80 changes: 80 additions & 0 deletions amti/clis/disassociate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Command line interface for disassociating quals with Workers"""

import logging

import click
import csv

from amti import actions
from amti import settings
from amti import utils


logger = logging.getLogger(__name__)


@click.command(
context_settings={
'help_option_names': ['--help', '-h']
})
@click.argument(
'ids',
type=str,
nargs=-1)
@click.option(
'--file', '-f',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Path to file of WorkerIds.")
@click.option(
'--qual', '-q',
help='QualificationId (or name, if --name flag passed).')
@click.option(
'--name', '-n',
is_flag=True,
help='Search for qual by name instead of id.')
@click.option(
'--reason', '-r',
help='Reason for disassociation (worker sees this).')
@click.option(
'--live', '-l',
is_flag=True,
help='View the status of HITs from the live MTurk site.')
def disassociate_qual(file, ids, qual, name, reason, live):
"""Disassociate workers with a qualification.

Given a space seperated list of WorkerIds (IDS) and/or a path to
a CSV of WorkerIds, disassociate each worker with a qualification
(QUAL).

NOTE: Only works with quals that both exist and are owned by the user.
"""
env = 'live' if live else 'sandbox'

client = utils.mturk.get_mturk_client(env)

worker_ids = list(ids)

# read ids from file (adds to provided ids)
if file is not None:
worker_ids += utils.workers.read_workerids_from_file(file)

# set qual_id
qual_id = qual
if name:
qual_id = utils.mturk.get_qual_by_name(client, qual)
if qual_id is None:
raise ValueError(f"No qual with name {qual} found.")

args = {"QualificationTypeId": qual_id}
if reason is not None:
args['Reason'] = reason

# associate qual with workers
for worker_id in worker_ids:
logger.info(f'Disassociating qualification {qual_id} from worker {worker_id}.')
response = client.disassociate_qualification_from_worker(
WorkerId=worker_id,
**args
)

logger.info('Finished disassociating quals.')
81 changes: 81 additions & 0 deletions amti/clis/notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Command line interfaces for blocking Workers"""

import logging

import click
import csv

from amti import actions
from amti import settings
from amti import utils


logger = logging.getLogger(__name__)


@click.command(
context_settings={
'help_option_names': ['--help', '-h']
})
@click.argument(
'ids',
type=str,
nargs=-1)
@click.option(
'--file', '-f',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Path to file of WorkerIds to block.")
@click.option(
'--subject', '-s',
help='Subject line for message.')
@click.option(
'--message', '-m',
help='Text content of message.')
@click.option(
'--message_file',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Path to file of WorkerIds to block.")
@click.option(
'--live', '-l',
is_flag=True,
help='View the status of HITs from the live MTurk site.')
def notify_workers(file, ids, subject, message, message_file, live):
"""Send notification message to workers.

Given a space seperated list of WorkerIds (IDS), or a path to
a CSV of WorkerIds, send a notification to each worker.
"""
env = 'live' if live else 'sandbox'

client = utils.mturk.get_mturk_client(env)

worker_ids = list(ids)

# read ids from file (adds to provided ids)
if file is not None:
worker_ids += utils.workers.read_workerids_from_file(file)

# create message (file values overrides Subject, Message args)
message = {'Subject': subject, 'MessageText': message}
if message_file is not None:
with open(args.message, 'r') as f:
message = json.loads(f.read())

if any(val is None for val in message.values()):
raise ValueError('Missing Message or Subject value.')

# break ids into chunks of 100, notify workers in each chunk
for chunk_ids in utils.workers.chunk_list(worker_ids):

logger.info(f"Sending notification to workers: {chunk_ids}")

response = client.notify_workers(
Subject=message['Subject'],
MessageText=message['MessageText'],
WorkerIds=chunk_ids
)

for failure in response['NotifyWorkersFailureStatuses']:
logger.warn(f"Notification failed for {failure.pop('WorkerId')}: {failure}")

logger.info('Finished sending notifications.')
60 changes: 60 additions & 0 deletions amti/clis/unblock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Command line interfaces for unblocking Workers"""

import logging

import click
import csv

from amti import actions
from amti import settings
from amti import utils


logger = logging.getLogger(__name__)


@click.command(
context_settings={
'help_option_names': ['--help', '-h']
})
@click.argument(
'ids',
type=str,
nargs=-1)
@click.option(
'--file', '-f',
type=click.Path(exists=True, file_okay=True, dir_okay=False),
help="Path to file of WorkerIds to block.")
@click.option(
'--reason', '-r',
default="Worker was blocked by mistake.",
help='Reason for blocking worker(s) (workers do not see).')
@click.option(
'--live', '-l',
is_flag=True,
help='View the status of HITs from the live MTurk site.')
def unblock_workers(file, ids, reason, live):
"""Unblock workers by WorkerId.

Given a space seperated list of WorkerIds (IDS) and/or a path to
a CSV of WorkerIds, remove a block for each worker listed.
"""
env = 'live' if live else 'sandbox'

client = utils.mturk.get_mturk_client(env)

worker_ids = list(ids)

# read ids from file (adds to provided ids)
if file is not None:
worker_ids += utils.workers.read_workerids_from_file(file)

# remove blocks
for worker_id in worker_ids:
logger.info(f'Removing block for worker {worker_id}.')
response = client.delete_worker_block(
WorkerId=worker_id,
Reason=reason
)

logger.info('Finished removing blocks.')
Loading