https://github.com/NYUCCL/psiTurk
Raw File
Tip revision: a42eafbdc23185a6fb735441dc4df3f9d63a87d7 authored by Dave Eargle on 27 June 2019, 22:09:34 UTC
bump version to 2.3.1
Tip revision: a42eafb
amt_services_wrapper.py
# coding: utf-8
"""
The initial motivation for this wrapper is to abstract away
the mturk functionality from the shell
"""
from __future__ import print_function
from __future__ import absolute_import

from functools import wraps

import urllib.error
import urllib.parse
import urllib.request
from .utils import *
from .models import Participant, Hit
from .db import db_session, init_db
from .psiturk_exceptions import *
from .psiturk_statuses import *
from .psiturk_config import PsiturkConfig
from .psiturk_org_services import PsiturkOrgServices
from .amt_services import MTurkServices
from sqlalchemy import or_, and_
import sqlalchemy as sa
import webbrowser
from fuzzywuzzy import process
import signal
import datetime
import random
import string
import os
import json
import time
import re
import subprocess
import sys
from builtins import object
from builtins import range
from builtins import str
from future import standard_library
standard_library.install_aliases()

try:
    import gnureadline as readline
except ImportError:
    import readline


class WrapperResponse(object):
    def __init__(self, status=None, message='', data={}, operation='', **kwargs):
        self.status = status
        self.message = message
        self.data = data
        self.operation = operation
        for k, v in kwargs.items():
            setattr(self, k, v)

    def __repr__(self):
        details = []
        if self.operation:
            details.append(('Operation', self.operation))
        if self.status:
            details.append(('Status', self.status))
        if self.message:
            details.append(('Message', self.message))
        if hasattr(self, 'exception'):
            details.append(('Exception', self.exception))

        details = ' | '.join(['{}: {}'.format(key, value)
                              for key, value in details])

        return 'Response({})'.format(details)


class WrapperResponseSuccess(WrapperResponse):
    def __init__(self, *args, **kwargs):
        super(WrapperResponseSuccess, self).__init__(
            *args, status='success', **kwargs)

class WrapperResponseError(WrapperResponse):
    def __init__(self, *args, **kwargs):
        super(WrapperResponseError, self).__init__(
            *args, status='error', success=False, **kwargs)
        self.exception = kwargs.pop('exception', None)
            

def amt_services_wrapper_response(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        try:
            response = func(self, *args, **kwargs)
            if isinstance(response, WrapperResponseSuccess): # this so that func.__name__ can be set
                response = response.data
            elif isinstance(response, WrapperResponseError): # this so that func.__name__ can be set
                raise response.exception
            return WrapperResponseSuccess(operation=func.__name__, data=response)
        except Exception as e:
            return WrapperResponseError(operation=func.__name__, exception=e)
    return wrapper

class MTurkServicesWrapper(object):

    _cached_web_services = None
    _cached_dbs_services = None
    _cached_amt_services = None

    @property
    def web_services(self):
        if not self._cached_web_services:
            self._cached_web_services = PsiturkOrgServices(
                self.config.get('psiTurk Access', 'psiturk_access_key_id'),
                self.config.get('psiTurk Access', 'psiturk_secret_access_id'))
        return self._cached_web_services

    def set_web_services(self, web_services):
        self._cached_web_services = web_services

    @property
    def amt_services(self):
        if not self._cached_amt_services:
            self._cached_amt_services = MTurkServices(
                self.config.get('AWS Access', 'aws_access_key_id'),
                self.config.get('AWS Access', 'aws_secret_access_key'),
                self.sandbox)
        return self._cached_amt_services

    def __init__(self, config=None, web_services=None, sandbox=None):
        init_db()

        if not config:
            config = PsiturkConfig()
            config.load_config()
        self.config = config

        if web_services:
            self._cached_web_services = web_services

        if not sandbox:
            sandbox = config.getboolean(
                'Shell Parameters', 'launch_in_sandbox_mode')
        self.sandbox = sandbox

    # +-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.
    #   Miscellaneous
    # +-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.

    def set_sandbox(self, is_sandbox):
        self.sandbox = is_sandbox
        self.amt_services.set_sandbox(is_sandbox)

    def random_id_generator(self, size=6, chars=string.ascii_uppercase +
                            string.digits):
        ''' Generate random id numbers '''
        return ''.join(random.choice(chars) for x in range(size))
    
    @amt_services_wrapper_response
    def amt_balance(self):
        ''' Get MTurk balance '''
        return self.amt_services.check_balance().data

    # +-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.
    #   worker management
    # +-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.
    @staticmethod
    def add_bonus_info(assignment_dict):
        " Adds DB-logged worker bonus to worker list data "
        try:
            unique_id = '{}:{}'.format(
                assignment_dict['workerId'], assignment_dict['assignmentId'])
            worker = Participant.query.filter(
                Participant.uniqueid == unique_id).one()
            assignment_dict['bonus'] = worker.bonus
        except sa.exc.InvalidRequestError:
            # assignment is found on mturk but not in local database.
            assignment_dict['bonus'] = 'not-found-in-study-database'
        return assignment_dict

    @amt_services_wrapper_response
    def count_workers(self, codeversion=None, mode='live', status='completed'):
        ''' 
        Counts the number of participants in the database who have made it through
        the experiment.
        '''
        if not codeversion:
            codeversion = self.config.get(
                'Task Parameters', 'experiment_code_version')

        worker_count = Participant.query.filter(and_(
            Participant.status.in_([3, 4, 5, 7]),
            Participant.codeversion == codeversion,
            Participant.mode == mode
        )).count()
        return worker_count

    @amt_services_wrapper_response
    def get_assignments(self, hit_ids=None, assignment_status=None, all_studies=False):
        '''
        assignment_status, if set, can be one of `Submitted`, `Approved`, or `Rejected`
        '''
        if not all_studies and assignment_status != 'Rejected':
            p_query = Participant.query
            p_query = p_query.filter(~Participant.uniqueid.contains('debug'))
            if assignment_status:
                mturk_status_to_local_status = {
                    'Submitted': SUBMITTED,
                    'Approved': CREDITED,
                }
                local_status = mturk_status_to_local_status[assignment_status]
                p_query = p_query.filter(Participant.status == local_status)
            if hit_ids:
                p_query = p_query.filter(Participant.hitid.in_(hit_ids))
            assignments = p_query.all()
        else:
            assignments = self.amt_services.get_assignments(
                assignment_status=assignment_status, hit_ids=hit_ids).data
            if assignments:
                assignments = [self.add_bonus_info(
                    assignment) for assignment in assignments]
        return {'assignments': assignments}

    def _filter_assignments_for_current_study(self, assignments):
        my_hitids = self._get_local_hitids()
        assignments_filtered = [
            assignment for assignment in assignments if assignment['hitId'] in my_hitids]
        return assignments_filtered

    def _try_fetch_local_credited_assignments(self, try_these):
        attempts_were_made = []
        for try_this in try_these:
            attempt_was_made = self._try_fetch_local_credited_assignment(
                try_this)
            attempts_were_made.append(attempt_was_made)
        return attempts_were_made

    def _try_fetch_local_credited_assignment(self, try_this):
        '''
        Can accept either an assignment_id or the return of a mturk boto grab...
        '''
        query = Participant.query\
            .filter(Participant.status == CREDITED)\
            .order_by(Participant.beginhit.desc())
        if isinstance(try_this, str):  # then assume that it's an assignment_id
            assignment_id = try_this
            query = query.filter(Participant.assignmentid == assignment_id)
        elif isinstance(try_this, dict):  # then assume that it's a return from mturk
            assignment = try_this
            assignment_id = assignment['assignmentId']
            query = query.filter(Participant.workerid == assignment['workerId'])\
                .filter(Participant.assignmentid == assignment_id)
        local_assignment = query.first()
        if local_assignment:
            return local_assignment
        else:
            return try_this

    @amt_services_wrapper_response
    def approve_all_assignments(self, all_studies=False):
        if all_studies:
            results = self._approve_all_assignments_from_mturk(ignore_local_not_found=all_studies)
        else:
            results = self._approve_all_assignments_from_local_records()
        return {'results': results}

    @amt_services_wrapper_response
    def approve_assignment_by_assignment_id(self, assignment_id, all_studies=False):
        tried_this = self._try_fetch_local_credited_assignment(assignment_id)
        if isinstance(tried_this, Participant):
            result = self.approve_local_assignment(tried_this)
            return result
        else:
            if not all_studies:
                raise AssignmentIdNotFoundInLocalDBError(assignment_id=assignment_id)
            else:
                response = self.amt_services.approve_assignment(assignment_id)
                if response.success:
                    return True
                else:
                    raise response.exception

    @amt_services_wrapper_response
    def approve_assignments_for_hit(self, hit_id, all_studies=False):
        if all_studies:
            assignments = self.amt_services.get_assignments(
                assignment_status='Submitted', hit_ids=[hit_id]).data
            results = []
            for assignment in assignments:
                results.append(self.approve_mturk_assignment(assignment, ignore_local_not_found=all_studies))
        else:
            assignments = self._get_local_submitted_assignments(hit_id=hit_id)
            results = []
            for assignment in assignments:
                results.append(self.approve_local_assignment(assignment))
        return {'results': results}

    def _get_local_submitted_assignments(self, hit_id=None):
        query = Participant.query.filter(
            or_(Participant.status == COMPLETED, Participant.status == SUBMITTED))
        if hit_id:
            query.filter(Participant.hitid == hit_id)
        assignments = query.all()
        return assignments

    def _approve_all_assignments_from_local_records(self):
        assignments = self._get_local_submitted_assignments()
        results = []
        for assignment in assignments:
            results.append(self.approve_local_assignment(assignment))
        return results

    def _approve_all_assignments_from_mturk(self, ignore_local_not_found=False):
        assignments = self.amt_services.get_assignments(
            assignment_status="Submitted").data
        results = []
        for assignment in assignments:
            results.append(self.approve_mturk_assignment(assignment, ignore_local_not_found=ignore_local_not_found))
        return results

    @amt_services_wrapper_response
    def approve_local_assignment(self, assignment):
        try:
            assignment_id = assignment.assignmentid
            response = self.amt_services.approve_assignment(assignment_id)
            if not response.success:
                raise response.exception
            assignment.status = CREDITED
            db_session.add(assignment)
            db_session.commit()
            return {'assignment_id': assignment_id}
        except Exception as e:
            return WrapperResponseError(exception=e, data={
                'assignment_id': assignment_id,
            })

    @amt_services_wrapper_response
    def approve_mturk_assignment(self, assignment, ignore_local_not_found=False):
        ''' Approve assignment '''
        assignment_id = assignment['assignmentId']

        found_assignment = False
        parts = Participant.query.\
            filter(Participant.assignmentid == assignment_id).\
            filter(Participant.status.in_([3, 4])).\
            all()
        # Iterate through all the people who completed this assignment.
        # This should be one person, and it should match the person who
        # submitted the HIT, but that doesn't always hold.
        status_report = ''
        for part in parts:
            if part.workerid == assignment['workerId']:
                found_assignment = True
                response = self.amt_services.approve_assignment(assignment_id)
                if not response.success:
                    raise response.exception
                else:
                    part.status = CREDITED
                    db_session.add(part)
                    db_session.commit()
                break
        if not found_assignment:
            # approve assignments not found in DB if the assignment id has been specified
            if ignore_local_not_found:
                response = self.amt_services.approve_assignment(assignment_id)
                if response.success:
                    pass # yay
                else:
                    raise response.exception
            else:
                raise WorkerIdNotFoundInLocalDBError()
        return {'assignment_id': assignment_id}

    @amt_services_wrapper_response
    def reject_assignments_for_hit(self, hit_id, all_studies=False):
        if all_studies:
            assignments = self.amt_services.get_assignments("Submitted").data
            assignment_ids = [assignment['assignmentId'] for assignment in assignments if
                              assignment['hitId'] == hit_id]
        else:
            assignments = self._get_local_submitted_assignments()
            assignment_ids = [
                assignment.assignmentid for assignment in assignments]
        response = self.reject_assignments(assignment_ids)
        return response.data

    @amt_services_wrapper_response
    def reject_assignments(self, assignment_ids, all_studies=False):
        results = []
        for assignment_id in assignment_ids:
            result = self.reject_assignment(assignment_id)
            results.append(result)
        return {'results': results}

    @amt_services_wrapper_response
    def reject_assignment(self, assignment_id, all_studies=False):
        response = self.amt_services.reject_assignment(assignment_id)
        if not response.success:
            raise response.exception
        else:
            return True

    @amt_services_wrapper_response
    def unreject_assignments_for_hit(self, hit_id, all_studies=False):
        if all_studies:
            assignments = self.amt_services.get_assignments("Rejected").data
            assignment_ids = [assignment['assignmentId'] for assignment in assignments if
                              assignment['hitId'] == hit_id]
        else:
            assignments = self._get_local_submitted_assignments()
            assignment_ids = [
                assignment.assignmentid for assignment in assignments]
        response = self.unreject_assignments(assignment_ids)
        return response.data

    @amt_services_wrapper_response
    def unreject_assignments(self, assignment_ids, all_studies=False):
        results = []
        for assignment_id in assignment_ids:
            result = self.unreject_assignment(assignment_id)
            results.append(result)
        return {'results': results}

    @amt_services_wrapper_response
    def unreject_assignment(self, assignment_id, all_studies=False):
        ''' Unreject assignment '''
        response = self.amt_services.unreject_assignment(assignment_id)
        result = {}
        if not response.success:
            raise response.exception
        else:
            message='unrejected {}'.format(assignment_id)
            try:
                participant = Participant.query\
                    .filter(Participant.assignmentid == assignment_id)\
                    .order_by(Participant.beginhit.desc()).first()
                participant.status = CREDITED
                db_session.add(participant)
                db_session.commit()
            except:
                message = '{} but failed to update local db'.format(
                    message)
        return message

    @amt_services_wrapper_response
    def bonus_all_local_assignments(self, amount, reason, override_bonused_status=False):
        mode = 'sandbox' if self.sandbox else 'live'
        assignments = Participant.query.filter(Participant.status == CREDITED)\
            .filter(Participant.mode == mode)\
            .all()

        results = []
        for assignment in assignments:
            result = self.bonus_local_assignment(
                assignment, amount, reason, override_bonused_status)
            results.append(result)
        return {'results': results}

    @amt_services_wrapper_response
    def bonus_local_assignment(self, local_assignment, amount, reason, override_bonused_status=False):
        assignment_id = local_assignment.assignmentid
        if local_assignment.status == BONUSED and not override_bonused_status:
            message = 'Participant with assignment_id {} already bonused, and override not set. Not bonusing.'.format(
                local_assignment.assignmentid)
            raise AssignmentAlreadyBonusedError(message=message)
        if amount == 'auto':
            amount = local_assignment.bonus

        response = self.bonus_nonlocal_assignment(
            assignment_id, amount, reason, worker_id=local_assignment.workerid)
        if response.status == 'success':
            # result['message'] = "gave bonus of ${} for assignment {}".format(str(amount), local_assignment.assignmentid)

            local_assignment.status = BONUSED
            db_session.add(local_assignment)
            db_session.commit()
        return response

    @amt_services_wrapper_response
    def bonus_assignments_for_hit(self, hit_id, amount, reason, all_studies=False, override_bonused_status=False):
        '''
        Fetch assignments for local hit. 
        * If all_studies, try to map them to a local_assignment.
        * If not all_studies, just pull from local db. Already a Participant.
        
        For each, if isinstance Participant, assignment, then send to `bonus_local_assignment`.
        Otherwise, send directly to bonus_assignment. Record the result either way.
        '''
        if all_studies:
            mturk_assignments = self.amt_services.get_assignments(
                assignment_status="Approved", hit_ids=[hit_id]).data
            assignments = self._try_fetch_local_credited_assignments(
                mturk_assignments)
        else:
            assignments = Participant.query\
                .filter(Participant.status.in_([CREDITED, BONUSED]))\
                .filter(Participant.hitid == hit_id)\
                .all()
        results = self._bonus_list(
            assignments, amount, reason, override_bonused_status)
        return {'results': results}

    def _bonus_list(self, bonus_these, amount, reason, override_bonused_status=False):
        results = []
        for bonus_this in bonus_these:
            if isinstance(bonus_this, Participant):
                result = self.bonus_local_assignment(
                    bonus_this, amount, reason, override_bonused_status)
                results.append(result)
            elif isinstance(bonus_this, str):
                # assume that the str is just an assignment_id
                assignment_id = bonus_this
                result = self.bonus_nonlocal_assignment(
                    assignment_id=bonus_this, amount=amount, reason=reason, worker_id=None)
                results.append(result)
        return results
    
    @amt_services_wrapper_response
    def bonus_assignment_for_assignment_id(self, assignment_id, amount, reason, all_studies=False):
        tried_this = self._try_fetch_local_credited_assignment(
            assignment_id)
        if isinstance(tried_this, Participant):
            result = self.bonus_local_assignment(tried_this, amount, reason, override_bonused_status=True)
            return result
        else:
            if not all_studies:
                raise AssignmentIdNotFoundInLocalDBError(assignment_id=assignment_id)
            else:
                response = self.bonus_nonlocal_assignment(assignment_id, amount, reason, worker_id=None)
                if response.success:
                    return True
                else:
                    raise response.exception

    @amt_services_wrapper_response
    def bonus_nonlocal_assignment(self, assignment_id, amount, reason, worker_id=None):
        ''' Bonus assignment '''

        '''
        If this is supposed to bonus a local_assignment, then call `bonus_local_assignment`, 
        passing the local assignment.
        
        Try to match up local_assignments with whatever arguments are passed (assignment_ids, hit_id) 
        before coming into this function.
        `amount` has to be greater than 0
        
        Otherwise, just give at least an assignment_id. Worker_id is nice if you have it, but if
        you don't, amt_services will look it up.
        '''
        mode = 'sandbox' if self.sandbox else 'live'

        result = {}
        if amount <= 0:
            raise BadBonusAmountError(amount, assignment_id=assignment_id)
        if not reason:
            raise BonusReasonMissingError()
        response = self.amt_services.bonus_assignment(
            assignment_id, worker_id, amount, reason)
        if not response.success:
            raise response.exception
        message = "gave bonus of ${} for assignment {}".format(
            str(amount), assignment_id)
        return {'message': message}

    # +-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.
    #   hit management
    # +-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.+-+.
    
    @amt_services_wrapper_response
    def tally_hits(self):
        hits = self.get_active_hits(all_studies=False).data['active_hits']
        num_hits = 0
        if hits:
            num_hits = len(hits)
        return {'hit_tally': num_hits}

    def _get_local_hitids(self):
        participant_hitids = [
            part.hitid for part in Participant.query.distinct(Participant.hitid)]
        hit_hitids = [hit.hitid for hit in Hit.query.distinct(Hit.hitid)]
        my_hitids = list(set(participant_hitids + hit_hitids))
        return my_hitids

    @amt_services_wrapper_response
    def get_active_hits(self, all_studies=False):
        hits = self._get_hits(all_studies)
        active_hits = [hit for hit in hits if not hit.options['is_expired']]
        return {'active_hits': active_hits}

    @amt_services_wrapper_response
    def get_reviewable_hits(self, all_studies=False):
        hits = self._get_hits(all_studies)
        reviewable_hits = [hit for hit in hits if hit.options['status'] == "Reviewable"
                           or hit.options['status'] == "Reviewing"]
        return {'reviewable_hits': reviewable_hits}

    @amt_services_wrapper_response
    def get_all_hits(self, all_studies=False):
        hits = self._get_hits(all_studies)
        return {'hits': hits}

    def _get_hits(self, all_studies=False):
        # get all hits from amt
        # then filter to just the ones that have an id that appears in either local Hit or Worker tables
        response = self.amt_services.get_all_hits()
        if not response.success:
            raise response.exception
        hits = response.data
        
        if not all_studies:
            hits = [hit for hit in hits if hit.options['hitid']
                    in self._get_local_hitids()]
        return hits

    @amt_services_wrapper_response
    def extend_hit(self, hit_id, assignments=None, minutes=None):
        """ Add additional worker assignments or minutes to a HIT.

        Args:
            hit_id: A list conaining one hit_id string.
            assignments: Variable <int> for number of assignments to add.
            minutes: Variable <int> for number of minutes to add.

        Returns:
            A side effect of this function is that the state of a HIT changes
            on AMT servers.

        Raises:

        """

        response = self.amt_services.extend_hit(hit_id, assignments_increment=assignments, expiration_increment=minutes)
        if not response.success:
            raise response.exception
            
        return True

    @amt_services_wrapper_response
    def delete_all_hits(self):
        '''
        Deletes all reviewable hits
        '''
        response = self.amt_services.get_all_hits()
        if not response.success:
            raise response.exception
        hits = response.data
        hit_ids = [hit.options['hitid'] for hit in hits if
                   hit.options['status'] == "Reviewable"]
        results = []
        for hit_id in hit_ids:
            _result = self.delete_hit(hit_id)
            results.append(_result)
        return {'results': results}

    @amt_services_wrapper_response
    def delete_hit(self, hit_id):
        '''
        Deletes a single hit if it is reviewable
        '''
        # Check that the HIT is reviewable
        
        response = self.amt_services.delete_hit(hit_id)
        # self.web_services.delete_ad(hit)  # also delete the ad
        if not response.success:
            raise response.exception
        else:
            if self.sandbox:
                success_message = "deleted sandbox HIT {}".format(hit_id)
            else:
                success_message = "deleted live HIT {}".format(hit_id)
            return True

    @amt_services_wrapper_response
    def expire_hit(self, hit_id):
        response = self.amt_services.expire_hit(hit_id)
        if not response.success:
            raise response.exception
        else:
            return True

    @amt_services_wrapper_response
    def expire_all_hits(self):
        hits_data = self.get_active_hits().data['active_hits']
        hit_ids = [hit.options['hitid'] for hit in hits_data]
        results = []
        for hit_id in hit_ids:
            results.append(self.expire_hit(hit_id))
        return {'results': results}

    @amt_services_wrapper_response
    def create_hit(self, num_workers, reward, duration):
        ''' Create a HIT '''
        if not num_workers or not reward or not duration:
            raise MissingArgumentsError()

        if self.sandbox:
            mode = 'sandbox'
        else:
            mode = 'live'
        server_loc = str(self.config.get('Server Parameters', 'host'))

        use_psiturk_ad_server = self.config.getboolean(
            'Shell Parameters', 'use_psiturk_ad_server')

        if use_psiturk_ad_server:
            if not self.web_services.check_credentials():
                raise InvalidPsiturkCredentialsError()

        if not self.amt_services.verify_aws_login():
            raise InvalidAWSCredentialsError()

        ad_id = None
        if use_psiturk_ad_server:
            create_ad_id_response = self.create_psiturk_ad()
            if not create_ad_id_response.success:
                raise create_ad_id_response.exception
            else:
                ad_id = create_ad_id_response.data['ad_id']
            if ad_id:
                ad_location = self.web_services.get_ad_url(
                    ad_id, int(self.sandbox))
                hit_config = self._generate_hit_config(
                    ad_location, num_workers, reward, duration)
                response = self.amt_services.create_hit(hit_config)
                if not response.success:
                    raise response.exception
                else:
                    hit_id = response.data['HITId']
                    if not self.web_services.set_ad_hitid(ad_id, hit_id, int(self.sandbox)):
                        raise AdPsiturkOrgError("Unable to update Ad on http://ad.psiturk.org to point at HIT.")
            else:
                create_failed = True
                raise AdPsiturkOrgError("Unable to create Ad on http://ad.psiturk.org.")

        else:  # not using psiturk ad server
            ad_location = "{}?mode={}".format(self.config.get(
                'Shell Parameters', 'ad_location'), mode)
            hit_config = self._generate_hit_config(
                ad_location, num_workers, reward, duration)
            response = self.amt_services.create_hit(hit_config)
            if not response.success:
                raise response.exception
            else:
                hit_id = response.data['HITId']

        # stash hit id in psiturk database
        hit = Hit(hitid=hit_id)
        db_session.add(hit)
        db_session.commit()

        return {'hit_id': hit_id, 'ad_id': ad_id}

    @amt_services_wrapper_response
    def create_psiturk_ad(self):
        # register with the ad server (psiturk.org/ad/register) using POST
        if os.path.exists('templates/ad.html'):
            ad_html = open('templates/ad.html').read()
        else:
            raise AdHtmlNotFoundError()

        size_of_ad = sys.getsizeof(ad_html)
        if size_of_ad >= 1048576:
            raise AdHtmlTooLarge(size_of_ad)

        # what all do we need to send to server?
        # 1. server
        # 2. port
        # 3. support_ie?
        # 4. ad.html template
        # 5. contact_email in case an error happens

        if self.config.has_option('Server Parameters', 'adserver_revproxy_host'):
            # misnomer, could actually be a fqdn sans protocol
            ip_address = self.config.get(
                'Server Parameters', 'adserver_revproxy_host')
            if self.config.has_option('Server Parameters', 'adserver_revproxy_port'):
                port = self.config.getint(
                    'Server Parameters', 'adserver_revproxy_port')
            else:
                port = 80
        else:
            ip_address = str(get_my_ip())
            port = str(self.config.get('Server Parameters', 'port'))

        if self.config.has_option('HIT Configuration', 'allow_repeats'):
            allow_repeats = self.config.getboolean(
                'HIT Configuration', 'allow_repeats')
        else:
            allow_repeats = False

        ad_content = {
            'psiturk_external': True,
            'server': ip_address,
            'port': port,
            'browser_exclude_rule': str(self.config.get('HIT Configuration', 'browser_exclude_rule')),
            'is_sandbox': int(self.sandbox),
            'ad_html': ad_html,
            # 'amt_hit_id': hitid, Don't know this yet
            'organization_name': str(self.config.get('HIT Configuration', 'organization_name')),
            'experiment_name': str(self.config.get('HIT Configuration', 'title', raw=True)),
            'contact_email_on_error': str(self.config.get('HIT Configuration', 'contact_email_on_error')),
            'ad_group': str(self.config.get('HIT Configuration', 'ad_group')),
            'keywords': str(self.config.get('HIT Configuration', 'psiturk_keywords')),
            'allow_repeats': int(allow_repeats)
        }
        ad_id = self.web_services.create_ad(ad_content)
        if not ad_id:
            raise AdPsiturkOrgError('Error creating the ad.')
        return {'ad_id': ad_id}

    def _generate_hit_config(self, ad_location, num_workers, reward, duration):
        hit_config = {
            "ad_location": ad_location,
            "approve_requirement": self.config.getint('HIT Configuration', 'Approve_Requirement'),
            "us_only": self.config.getboolean('HIT Configuration', 'US_only'),
            "lifetime": datetime.timedelta(hours=self.config.getfloat('HIT Configuration', 'lifetime')),
            "max_assignments": num_workers,
            "title": self.config.get('HIT Configuration', 'title', raw=True),
            "description": self.config.get('HIT Configuration', 'description', raw=True),
            "keywords": self.config.get('HIT Configuration', 'amt_keywords'),
            "reward": reward,
            "duration": datetime.timedelta(hours=duration),
            "number_hits_approved": self.config.getint('HIT Configuration', 'number_hits_approved'),
            "require_master_workers": self.config.getboolean('HIT Configuration', 'require_master_workers')
        }
        return hit_config
back to top