Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jlumbroso/767496d380f5be76285a12b7d927640e to your computer and use it in GitHub Desktop.
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
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 = "lumbroso@princeton.edu"
# + 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