Created
May 12, 2023 23:33
-
-
Save jlumbroso/767496d380f5be76285a12b7d927640e to your computer and use it in GitHub Desktop.
codePost snippet to automatically compute a grade based on extracting data from one of the files of the submissions (such as an auto-grader output), finalize all submissions of an assignment, and automatically add a comment that sets the grade of the submission
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import codepost | |
# ================================================================ | |
# VARIABLE PARAMETERS | |
# get the API key here: https://codepost.io/settings | |
API_KEY = "... see above where to get this ..." | |
COURSE_NAME = "COS126" | |
COURSE_TERM = "S2023" | |
ASSIGNMENT = "Atomic" | |
AUTOGRADER_OUTPUT_FILE = "TESTS.txt" | |
DEFAULT_GRADER = "[email protected]" | |
# + edit the patterns in the parse_test_results function below to | |
# match the output of your autograder | |
# ================================================================ | |
# authenticate | |
codepost.configure_api_key(API_KEY) | |
# retrieve the course, and then the assignment | |
# (will crash if the user of the API key doesn't have access to the course) | |
course = codepost.course.list_available(name=COURSE_NAME, period=COURSE_TERM)[0] | |
assignment = course.assignments.by_name(name=ASSIGNMENT) | |
# function that parses the autograder output of submissions to extract results | |
def parse_test_results(submission, tests_file_name=AUTOGRADER_OUTPUT_FILE): | |
# Each submission contains a TESTS.txt file that at some point | |
# contains the text: | |
# ================================ | |
# Correctness: 42/42 tests passed | |
# Memory: 10/10 tests passed | |
# Timing: 19/19 tests passed | |
# ================================ | |
match = re.search( | |
"Correctness:.*(\nMemory:.*)+(\nTiming:.*)+\n", | |
submission.files.by_name(tests_file_name).code, | |
re.MULTILINE | |
) | |
if match is None: | |
return | |
lines = match.group(0).splitlines() | |
def parse_test_result(line): | |
# Assumes the line is of the form: "..... N/M ........." | |
# and extracts (N, M) as a tuple of integers | |
match = re.search("(?P<passed>\d+)/(?P<total>\d+)", line) | |
if match is None: | |
return | |
return (int(match.group("passed")), int(match.group("total"))) | |
results = list(map(parse_test_result, lines)) | |
return results | |
# main loop | |
for submission in assignment.list_submissions(): | |
# claim submission (must assign a grader, here I assign myself) | |
submission.grader = DEFAULT_GRADER | |
# save changes (necessary to be able to assign comments) | |
submission.save() | |
# then select a file on which to add a comment | |
test_file = submission.files.by_name(name=AUTOGRADER_OUTPUT_FILE) | |
# remove all previous comments | |
# (to avoid duplicates, if we rerun this process several times) | |
for comment in test_file.comments: | |
comment.delete() | |
# compute the grade | |
test_results = parse_test_results(submission) | |
tests_passed = sum(map(lambda x: x[0], test_results)) | |
tests_total = sum(map(lambda x: x[1], test_results)) | |
grade = ( | |
round(float(tests_passed)/float(tests_total)*100.0, 2) | |
if float(tests_total) > 0 else 0 | |
) | |
# describe and create the comment | |
comment = { | |
'file': test_file.id, | |
'text': ( | |
"Your final grade is: {} / 100.0\n\n" + | |
"Please look at {} for a detailed computation.\n\n" + | |
"This is computed directly from the number of " + | |
"auto-grader tests passed." | |
).format(grade, AUTOGRADER_OUTPUT_FILE), | |
'pointDelta': -grade, | |
'rubricComment': None, | |
'startChar': 0, | |
'endChar': 1, | |
'startLine': 1, | |
'endLine': 1, | |
} | |
codepost.comment.create(**comment) | |
# finalize the submission | |
# (so it is visible to students, once the assignment is "published") | |
submission.isFinalized = True | |
submission.save() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment