diff --git a/src/sigma/assignments.py b/src/sigma/assignments.py index ecabe2f..ef3c229 100644 --- a/src/sigma/assignments.py +++ b/src/sigma/assignments.py @@ -11,6 +11,8 @@ from pathlib import Path import shutil import tempfile +import sys +import subprocess import nbformat as nbf from nbconvert.preprocessors import ExecutePreprocessor @@ -111,6 +113,11 @@ def grade_all(self): for s in submissions: s.grade() + def grade_notebook(self, notebook_path): + notebook_path = Path(notebook_path) + grader = AssignmentGrader(assignment=self, notebook_path=notebook_path) + grader.grade() + class Notebook: def __init__(self): self.nb = nbf.v4.new_notebook() @@ -206,10 +213,33 @@ def get_notebook_path(self) -> Path: def grade(self): """Grade the assignment submission. + + Executes the grading process as user nobody to sandbox the execution. """ - path = self.get_notebook_path() - grader = AssignmentGrader(self.assignment, path) - grader.grade() + notebook_path = self.get_notebook_path() + print(notebook_path) + + with tempfile.TemporaryDirectory() as workdir: + workdir = Path(workdir) + workdir.chmod(0o777) + + # copy the notebook to workdir + path = workdir / notebook_path.name + shutil.copyfile(notebook_path, path) + + # invoke the grading as user nobody + cmd = [sys.executable, "-m", "sigma.cli", "grade-assignment-notebook", self.assignment.name, path] + subprocess.run(cmd, user="nobody") + + def save_result(filename): + path = workdir / filename + path2 = self.submission_dir / filename + shutil.copy(path, path2) + print("cp", path, path2) + + # save the results + save_result("graded.ipynb") + save_result("grades.json") class AssignmentGrader: def __init__(self, assignment: Assignment, notebook_path: Path): diff --git a/src/sigma/cli.py b/src/sigma/cli.py index 96cb690..07f4b2f 100644 --- a/src/sigma/cli.py +++ b/src/sigma/cli.py @@ -44,5 +44,16 @@ def grade_assignment(assignment_name): assignment = Assignment.from_name(assignment_name) assignment.grade_all() +@app.command() +@click.argument("assignment_name") +@click.argument("notebook_path") +def grade_assignment_notebook(assignment_name, notebook_path): + """Grades a single assignment notebook. + + This is meant for internal use only. + """ + assignment = Assignment.from_name(assignment_name) + assignment.grade_notebook(notebook_path) + if __name__ == "__main__": app()