Source code for expfactory.vm

'''
vm.py: part of expfactory package
Functions to generate virtual machines to run expfactory batteries

'''
from expfactory.utils import save_template, clean_fields, copy_directory, get_installdir, sub_template, get_template
from git import Repo
import tempfile
import shutil
import os
import re

[docs]def download_repo(repo_type,destination): '''download_repo Download a expfactory infrastructure repo "repo_type" to a "destination" :param repo_type: can be one of "experiments" "battery" "vm" :param destination: the full path to the destination for the repo ''' acceptable_types = ["experiments","battery","vm","surveys","games"] if repo_type not in acceptable_types: print("repo_type must be in %s" %(",".join(acceptable_types))) else: return Repo.clone_from("https://github.com/expfactory/expfactory-%s" %(repo_type), destination)
[docs]def custom_battery_download(tmpdir=None,repos=["experiments","battery","surveys","games"]): '''custom_battery_download Download battery and experiment repos to a temporary folder to build a custom battery, return the path to the tmp folders :param tmpdir: The directory to download to. If none, a temporary directory will be made :param repos: The repositories to download, valid choices are "experiments" "battery" and "vm" ''' if isinstance(repos,str): repos = [repos] if not tmpdir: tmpdir = tempfile.mkdtemp() for repo in repos: download_repo(repo,"%s/%s/" %(tmpdir,repo)) return tmpdir
[docs]def generate_database_url(dbtype=None,username=None,password=None,host=None,table=None,template=None): '''generate_database_url Generate a database url from input parameters, or get a template :param dbtype: the type of database, must be one of "mysql" or "postgresql" :param username: username to connect to the database :param password: password to connect to the database :param host: database host :para table: table in the database :param template: can be one of "mysql" "sqlite3" or "postgresql" If specified, all other parameters are ignored, and a default database URL is produced to work in a Vagrant VM produced by expfactory ''' if not template: if not dbtype or not username or not password or not host or not table: print("Please provide all inputs: dbtype,username,password,host,table.") else: return "%s://%s:%s@%s/%s" %(dbtype, username, password, host, table) elif template == "mysql": return "mysql://expfactory:expfactory@localhost:3306/expfactory" elif template == "sqlite3": return "sqlite:///participants.db" elif template == "postgresql": return "postgresql://expfactory:expfactory@localhost:5432/expfactory"
[docs]def prepare_vm(battery_dest,fields=None,vm_repo=None,vm_type="vagrant"): '''prepare_vm Prepare virtual machine to run local with vagrant, or with vagrant-aws :param battery_dest: the battery destination folder :param fields: if specified, will be added to the config.txt :param vm_repo: the expfactory-vm repo to use for templates. If not provided, one will be downloaded to a temporary directory for use. :param vm_type: one of "vagrant" or "aws" Default is "vagrant" meaning a localvirtual machine with vagrant. ''' # Download vm_repo if it hasn't been specified if not vm_repo: download_repo("vm","%s/vm" %battery_dest) # Grab the appropriate vagrantfile template if vm_type == "aws": template = get_template("%s/vm/VagrantfileAWS" %(battery_dest)) else: template = get_template("%s/vm/VagrantfileLocal" %(battery_dest)) # If the user has custom config fields, write to file if fields != None: fields = clean_fields(fields) template = sub_template(template,"[SUB_CONFIG_SUB]",str(fields)) else: template = sub_template(template,"[SUB_CONFIG_SUB]","None") # Change file to be a custom install template = sub_template(template,'CUSTOM_INSTALL="False"','CUSTOM_INSTALL="True"') output_file = "%s/Vagrantfile" %(battery_dest) save_template(output_file,template) return template
[docs]def specify_experiments(battery_dest,experiments): '''specify_experiments Specify experiments for a Vagrantfile in an output folder :param battery_dest: destination folder for battery :param experiments: a list of experiment tags to include ''' experiments = [e.encode("utf-8") for e in experiments] vagrantfile = "%s/Vagrantfile" %(battery_dest) if not os.path.exists(vagrantfile): prepare_vm(battery_dest) template = get_template(vagrantfile) template = sub_template(template,"[SUB_EXPERIMENTS_SUB]",str(experiments)) save_template(vagrantfile,template) return template
[docs]def get_stylejs(experiment,url_prefix=""): '''get_stylejs return string for js and css scripts to insert into a page based on battery path structure ''' js = "" css = "" scripts = experiment[0]["run"] tag = experiment[0]["exp_id"] repo_type = "experiments" if experiment[0]["template"] == "survey": repo_type = "surveys" elif experiment[0]["template"] == "phaser": repo_type = "games" for script in scripts: ext = script.split(".")[-1] # Do we have a relative experiment path? if len(script.split("/")) == 1: if ext == "js": js = "%s\n<script src='%sstatic/%s/%s/%s'></script>" %(js,url_prefix,repo_type,tag,script) elif ext == "css": css = "%s\n<link rel='stylesheet' type='text/css' href='%sstatic/%s/%s/%s'>" %(css,url_prefix,repo_type,tag,script) # Do we have an https/https path? elif re.search("^http",script): if ext == "js": js = "%s\n<script src='%s'></script>" %(js,script) elif ext == "css": css = "%s\n<link rel='stylesheet' type='text/css' href='%s'>" %(css,script) # Do we have a battery relative path? else: if ext == "js": js = "%s\n<script src='%s%s'></script>" %(js,url_prefix,script) elif ext == "css": css = "%s\n<link rel='stylesheet' type='text/css' href='%s%s'>" %(css,url_prefix,script) return css,js
[docs]def get_jspsych_init(experiment,deployment="local",finished_message=None): '''get_jspsych_init return entire jspsych init structure :param experiment: the loaded config.json for the experiment :param deployment: specify to deploy local (default), or docker-mturk,docker-local (expfactory-docker). :param finished_message: custom message to show at the end of the experiment with Redo and Next Experiment Buttons ''' jspsych_init = "jsPsych.init({\ntimeline: %s_experiment,\n" %(experiment["exp_id"]) if finished_message == None: finished_message = 'You have completed the experiment. Click "Next Experiment" to keep your result, and progress to the next task. If you believe your ability to focus was significantly compromised by some external factor (e.g. someone started talking to you while you were doing the task) press "Redo Experiment" to be presented with the task again at a later time.' default_inits = dict() default_inits["local"] = {"on_finish":["jsPsych.data.localSave('%s_results.csv', 'csv');\nexpfactory_finished = true;" %(experiment["exp_id"])]} # Amazon Mechanical Turk default_inits["docker-mturk"] = {"on_finish":["""finished_message = '<div id="finished_message" style="margin:100px"><h1>Experiment Complete</h1><p>%s</p><button id="next_experiment_button" type="button" class="btn btn-success">Next Experiment</button><button type="button" id="redo_experiment_button" class="btn btn-danger">Redo Experiment</button></div>'\nexpfactory.recordTrialData(jsPsych.data.getData());\n$("body").append(finished_message);\n$(".display_stage").hide();\n$(".display_stage_background").hide();\n$("#redo_experiment_button").click( function(){\njavascript:window.location.reload();\n})\n$("#next_experiment_button").click( function(){\nexpfactory.djstatus = "FINISHED";\n$.ajax({ type: "POST",\ncontentType: "application/json",\nurl : "/sync/{{result.id}}/",\ndata : JSON.stringify(expfactory),\ndataType: "json",\nerror: function(error){\nconsole.log(error)\n},\nsuccess: function(data){\nconsole.log("Finished!");\nif (data.finished_battery == "FINISHED"){\n$("#turkey_form").submit()\n} else {\ndocument.location = "{{next_page}}";\n}\n}\n});\n});\n""" %finished_message], "on_data_update":["""expfactory.djstatus = "UPDATE";\n$.ajax({ type: "POST",\ncontentType: "application/json",\nurl : "/sync/{{result.id}}/",\ndata : JSON.stringify(expfactory),\ndataType: "json",\nsuccess: function(data){\nconsole.log("data update called")\n}\n});\n"""]} # Local Docker Deployment default_inits["docker-local"] = {"on_finish":["""finished_message = '<div id="finished_message" style="margin:100px"><h1>Experiment Complete</h1><p>%s</p><button id="next_experiment_button" type="button" class="btn btn-success">Next Experiment</button><button type="button" id="redo_experiment_button" class="btn btn-danger">Redo Experiment</button></div>'\nexpfactory.recordTrialData(jsPsych.data.getData());\n$("body").append(finished_message)\n$(".display_stage").hide()\n$(".display_stage_background").hide()\n$("#redo_experiment_button").click( function(){\njavascript:window.location.reload();\n})\n$("#next_experiment_button").click( function(){\nexpfactory.djstatus = "FINISHED";\n$.ajax({ type: "POST",\ncontentType: "application/json",\nurl : "/local/{{result.id}}/",\ndata : JSON.stringify(expfactory),\ndataType: "json",\nerror: function(error){\nconsole.log(error)\n},\nsuccess: function(data){\nconsole.log("Finished!");\ndocument.location = "{{next_page}}";\n}\n});\n});\n""" %finished_message], "on_data_update":["""expfactory.djstatus = "UPDATE";\n$.ajax({ type: "POST",\ncontentType: "application/json",\nurl : "/local/{{result.id}}/",\ndata : JSON.stringify(expfactory),\ndataType: "json",\nsuccess: function(data){\nconsole.log("data update called")\n}\n});\n"""]} # Docker Preview, no saving of data default_inits["docker-preview"] = {"on_finish":["""finished_message = '<div id="finished_message" style="margin:100px"><h1>Experiment Complete</h1><p>%s</p><button id="next_experiment_button" type="button" class="btn btn-success">Next Experiment</button><button type="button" id="redo_experiment_button" class="btn btn-danger">Redo Experiment</button></div>'\n$("body").append(finished_message)\n$(".display_stage").hide()\n$(".display_stage_background").hide()\n$("#redo_experiment_button").click( function(){\njavascript:window.location.reload();\n})\n$("#next_experiment_button").click( function(){\nconsole.log("Finished!");\ndocument.location = "{{next_page}}";\n});\n""" %finished_message], "on_data_update":["""console.log(data);\n"""]} if "deployment_variables" in experiment: if "jspsych_init" in experiment["deployment_variables"]: custom_variables = experiment["deployment_variables"]["jspsych_init"] # Fill user custom variables into data structure for jspsych_var,jspsych_val in custom_variables.items(): if deployment == "local": if jspsych_var in default_inits[deployment]: holder = default_inits[deployment][jspsych_var] holder.append(jspsych_val) default_inits[deployment][jspsych_var] = holder else: default_inits[deployment][jspsych_var] = [jspsych_val] elif deployment in ["docker-mturk","docker-local","docker-preview"]: if jspsych_var not in default_inits[deployment]: default_inits[deployment][jspsych_var] = [jspsych_val] # Write rest of config for v in range(len(default_inits[deployment])): jspsych_var = list(default_inits[deployment].keys())[v] jspsych_val = "\n".join([str(x) for x in list(default_inits[deployment].values())[v]]) if jspsych_var in ["on_finish","on_data_update","on_trial_start","on_trial_finish"]: jspsych_init = "%s%s: function(data){\n%s\n}" %(jspsych_init, jspsych_var, jspsych_val) # Boolean elif jspsych_var in ["show_progress_bar","fullscreen","skip_load_check"]: jspsych_init = "%s%s: %s" %(jspsych_init, jspsych_var, jspsych_val.lower()) # Numeric (no quotes) elif jspsych_var in ["default_iti","max_load_time"]: jspsych_init = "%s%s: %s" %(jspsych_init, jspsych_var, jspsych_val) # Everything else else: jspsych_init = '%s%s: "%s"' %(jspsych_init, jspsych_var, jspsych_val) if v != len(default_inits[deployment])-1: jspsych_init = "%s,\n" %(jspsych_init) else: jspsych_init = "%s\n});" %(jspsych_init) return jspsych_init