Auto Grading with API

You can run the grading job outside the Vocareum provided servers. It can be useful if you need a commercial tool not available on Vocareum servers or need to run grading on an environment like Raspberry Pi boards or Windows servers. You need to take the following steps to run the grading job

1. Get an API/Personal access token - Go to Settings from the drop-down menu on the top right and then click on Personal Access Tokens tab. You have the option to use the token that will work for all your courses or you can use one for your specific courses. Please make sure that this token is stored in a safe place. You will need to use this token in the API calls.

2. GET https://api.vocareum.com/api/v2/courses/{{courseid}}/assignments/{{assignmentid}}/parts/{{partid}}/next_gradeable_submission - gets the submission info for the students in JSON format. You can use multiple requesters to request the "next" submission in parallel and they will all get a different submission.

It will return userid as part of the JSON response which you can use in the next two steps.

3. GET https://api.vocareum.com/api/v2/courses/{{courseid}}/assignments/{{assignmentid}}/parts/{{partid}}/submissions/{{userid}} - gets the submission data including the content in a zip file

4. PUT https://api.vocareum.com/api/v2/courses/{{courseid}}/assignments/{{assignmentid}}/parts/{{partid}}/grades/{{userid}} - sets the grade for a specific student

If you need to regrade the entire class, you can use Dashboard=>Control=>"Reset autograde flag" to reset the flag and all the submissions will be graded again. To reset the flag for a single student, go to Dashboard=>Summary=>[student name] and select "Reset autograde flag".

Note: You can get the value of courseid, assignmentid, partid for the respective part by clicking the info icon under Edit Assignments->Parts.


Below is the sample code in python. There are two python scripts - one is the daemon that you can run to get the next submission to grade and eventually grade the user's submission. The code for grading is in second script.

###########################
## Top level daemon to run 
## voc_submission_daemon.py
###########################

import os
import requests
import time
import json
import base64
import zipfile
import subprocess

## The following 4 variables need to set to get the daemon going

#set the corresponding part-id from Vocareum (need to set)
courseid = ""
assignmentid = ""
partid = ""

#API header to pass with token from Vocareum (need to set)
token = ""

#max jobs to grade
max_jobs = 2;

#sleep timer
sleep_time = 50;
#sleep timer for cases when there is no submission to pick
failure_sleep_time = 300;

#API URL - v2
api_url = "https://api.vocareum.com/api/v2/courses/" + courseid + "/assignments/" + assignmentid + "/parts/" + partid ;

#submission directory where the zip files will be unzipped (need to be set) 
submission_dir = "submissions";

api_header = {'Authorization': 'Token ' + token, 'Content-Type': 'application/json'}

### NOTE ###
### the submission_dir is used to keep the submissions(individual directories) and grade them
### after grading these submissions(directory) needs to be deleted - so that we can use max_jobs
############

def get_submission_set_grade(userid):
    print("Get submission for this user:" + userid)
    get_submission_api = api_url + "/submissions/" + userid;
    response = requests.get(get_submission_api, headers=api_header)
    if (response.status_code == requests.codes.ok):
        json_response = response.json();
        print(json_response)

        # if we get a submission - get the zip file, unzip it and run grading 
        if (json_response['status'] == 'success'):
            submissions = json_response['submissions']
            print(submissions)
            submission = submissions[0]
            subdir = submission['dirname']
            zipcontent = base64.b64decode(submission['zipfilecontent'])
            fullzipdirpath = os.path.join(submission_dir, subdir);
            fullzipfilepath = fullzipdirpath + ".zip"
            print(fullzipfilepath)
            with open(fullzipfilepath, 'w') as f:
                f.write(zipcontent)
            # unzip under submission dir
            zip_ref = zipfile.ZipFile(fullzipfilepath, 'r')
            zip_ref.extractall(submission_dir)
            zip_ref.close()
            try:
                os.remove(fullzipfilepath)
            except OSError as e:  ## if failed, report it back to the user ##
                print ("Error: %s - %s." % (e.filename, e.strerror))

            subprocess.Popen(["python", "voc_grading.py", userid, subdir])
        else:
            print ("Something wrong!")

def main():
    if (token == '' or courseid == '' or assignmentid == '' or partid == ''):
        print("ERROR: Token, courseid, assignmentid and partid needs to be set");
        exit();

    while True:
        #if there are directories under the submission_dir, they are still processing (make sure they don't exceed max_jobs)
        num_submissions = len([name for name in os.listdir(submission_dir) if os.path.isdir(os.path.join(submission_dir, name))])
        if (num_submissions >= max_jobs):
            print("Waiting for clean-up");
            time.sleep(sleep_time)
        else:
            #get the next submission info to grade
            next_submission_api = api_url + "/next_gradeable_submission";

            response = requests.get(next_submission_api, headers=api_header)
            print (response.url)
            #if there is a valid response - get json
            if (response.status_code == requests.codes.ok):
                json_response = response.json();
                print(json_response)

                # if we get a submission info - process it, else sleep for sometime
                if (json_response['status'] == 'success'):
                    print("Success")
                    get_submission_set_grade(json_response['userid'])
                else:
                    print("Got failure, sleep for sometime")
                    time.sleep(failure_sleep_time)

if __name__ == "__main__":
   # stuff only to run when not called via 'import' here
   main()
#####################
##Script to grade the user 
#####################

import voc_submission_daemon
import sys
import os
import time
import shutil
import json
import csv
import base64
import requests

def removeFileIfThere(filepath):
    if (os.path.isfile(filepath)):
        try:
            os.remove(filepath)
        except OSError as e:  ## if failed, report it back to the user ##
            print ("Error: %s - %s." % (e.filename, e.strerror))

def runGrading():
    ############################# 
    ## run grading in subdirpath and generate grade_csv_file and grade_report_file if applicable
    ## TBD - to be done as per the usage
    ## Please replace the rubric elements with the part specific rubric names
    #############################
    ### For now just sample and create grade csv file in there

    grades = "Test 1, 5\n" + "Test 2, 0\n" + "Test 3, 5";
    with open(grade_csv_file_path,"w+") as f:
        f.write(grades)
    with open(grade_report_file_path,"w+") as f:
        f.write(grades)

def setGrades():
    grades = []
    if (os.path.isfile(grade_csv_file_path)):
        with open(grade_csv_file_path) as f:
            csv_reader = csv.reader(f, delimiter=",")
            for row in csv_reader:
                rubric_score = []
                rubric_score.append(row[0].strip())
                rubric_score.append(row[1].strip())
                grades.append(rubric_score)
            
    print(grades)

    report_content = ''
    if (os.path.isfile(grade_report_file_path)):
        with open(grade_report_file_path) as f:
            report_content = f.read()

    grade_report = base64.b64encode(report_content)
    grades_api = api_url + "/grades/" + userid;

    data = { "grades": grades, "report" : grade_report }
    print(data)
    response = requests.put(grades_api, headers=api_header, data=json.dumps(data))
    print (response.url)
    json_response = response.json();
    print(json_response)

print (str(sys.argv))

userid = sys.argv[1]
subdir = sys.argv[2]
submission_dir = voc_submission_daemon.submission_dir

#re-use the same variable from the submission daemon
#part id
partid = voc_submission_daemon.partid 
#API header 
api_header = voc_submission_daemon.api_header;
#API URL
api_url = voc_submission_daemon.api_url;

subdirpath = os.path.join(submission_dir, subdir)
print("Directory to grade for " + userid + " : " + subdirpath)

####### IMPORTANT NOTE ON GRADING ##########
## Run the grading on this directory and generate grades in a csv file using following format
#<Rubric 1>, <score-1>
#<Rubric 2>, <score-2>
# e.g.
#Test 1, 4
#Test 2, 5
############################################
grade_csv_file = ".vocGrade.csv"
grade_report_file = ".vocGradingReport.txt"

grade_csv_file_path = os.path.join(subdirpath, grade_csv_file)
grade_report_file_path = os.path.join(subdirpath, grade_report_file)

## first remove the grade CSV and report files if student has creeated it or already there
removeFileIfThere(grade_csv_file_path)
removeFileIfThere(grade_report_file_path)

runGrading()
setGrades()

## remove the submission directory after grading
try:
    #print(subdirpath)
    shutil.rmtree(subdirpath)
except OSError as e:
    print ("Error: %s - %s." % (e.filename, e.strerror))

Still need help? Contact Us Contact Us