Coverage for e2xgrader/preprocessors/saveautogrades.py: 38%
53 statements
« prev ^ index » next coverage.py v7.4.2, created at 2024-03-14 13:22 +0100
« prev ^ index » next coverage.py v7.4.2, created at 2024-03-14 13:22 +0100
1from textwrap import dedent
2from typing import Tuple
4from e2xcore.utils.e2xgrader_cells import is_extra_cell
5from e2xcore.utils.nbgrader_cells import grade_id
6from nbconvert.exporters.exporter import ResourcesDict
7from nbformat.notebooknode import NotebookNode
8from nbgrader.api import Gradebook
9from nbgrader.preprocessors import SaveAutoGrades as NbgraderSaveAutoGrades
10from nbgrader.utils import determine_grade
11from traitlets import Dict, Instance, List, TraitError, Unicode, validate
13from ..graders import BaseGrader, CodeGrader, MultipleChoiceGrader, SingleChoiceGrader
16class SaveAutoGrades(NbgraderSaveAutoGrades):
17 graders = Dict(
18 key_trait=Unicode(),
19 value_trait=Instance(klass=BaseGrader),
20 default_value={
21 "code": CodeGrader(),
22 "singlechoice": SingleChoiceGrader(),
23 "multiplechoice": MultipleChoiceGrader(),
24 },
25 ).tag(config=True)
27 cells = List(
28 [],
29 help=dedent(
30 """
31 List of cells to save the autogrades for. If this is empty all cells will be saved.
32 """
33 ),
34 ).tag(config=True)
36 @validate("cells")
37 def _validate_cells(self, proposal):
38 value = proposal["value"]
39 if len(value) == 1:
40 elem = value[0].strip()
41 if elem.startswith("[") and elem.endswith("]"):
42 elem = elem[1:-1]
43 value = [v.strip() for v in elem.split(",")]
44 if not isinstance(value, list):
45 raise TraitError("cells must be a list")
46 return value
48 def cell_type(self, cell: NotebookNode):
49 if is_extra_cell(cell):
50 return cell.metadata.extended_cell.type
51 return cell.cell_type
53 def _add_score(self, cell: NotebookNode, resources: ResourcesDict) -> None:
54 """Graders can override the autograder grades, and may need to
55 manually grade written solutions anyway. This function adds
56 score information to the database if it doesn't exist. It does
57 NOT override the 'score' field, as this is the manual score
58 that might have been provided by a grader.
60 """
61 # these are the fields by which we will identify the score
62 # information
63 grade = self.gradebook.find_grade(
64 cell.metadata["nbgrader"]["grade_id"],
65 self.notebook_id,
66 self.assignment_id,
67 self.student_id,
68 )
70 # determine what the grade is
71 if self.cell_type(cell) in self.graders:
72 auto_score, _ = self.graders[self.cell_type(cell)].determine_grade(
73 cell, self.log
74 )
75 grade.auto_score = auto_score
76 else:
77 auto_score, _ = determine_grade(cell, self.log)
78 grade.auto_score = auto_score
80 # if there was previously a manual grade, or if there is no autograder
81 # score, then we should mark this as needing review
82 if (grade.manual_score is not None) or (grade.auto_score is None):
83 grade.needs_manual_grade = True
84 else:
85 grade.needs_manual_grade = False
87 self.gradebook.db.commit()
89 def preprocess(
90 self, nb: NotebookNode, resources: ResourcesDict
91 ) -> Tuple[NotebookNode, ResourcesDict]:
92 # pull information from the resources
93 self.notebook_id = resources["nbgrader"]["notebook"]
94 self.assignment_id = resources["nbgrader"]["assignment"]
95 self.student_id = resources["nbgrader"]["student"]
96 self.db_url = resources["nbgrader"]["db_url"]
98 # connect to the database
99 self.gradebook = Gradebook(self.db_url)
101 with self.gradebook:
102 # process the cells
103 nb, resources = super(SaveAutoGrades, self).preprocess(nb, resources)
105 return nb, resources
107 def preprocess_cell(
108 self, cell: NotebookNode, resources: ResourcesDict, cell_index: int
109 ) -> Tuple[NotebookNode, ResourcesDict]:
110 if len(self.cells) == 0 or grade_id(cell) in self.cells:
111 return super().preprocess_cell(cell, resources, cell_index)
112 return cell, resources