Coverage for e2xgrader/graders/code.py: 95%

43 statements  

« prev     ^ index     » next       coverage.py v7.4.2, created at 2024-03-14 13:22 +0100

1from logging import Logger 

2from typing import Optional, Tuple 

3 

4from nbformat.notebooknode import NotebookNode 

5from nbgrader.utils import get_partial_grade, is_solution 

6 

7from .base import BaseGrader 

8 

9 

10class CodeGrader(BaseGrader): 

11 def extract_grade(self, text, log: Logger = None): 

12 delimiter_start = "### BEGIN GRADE" 

13 delimiter_end = "### END GRADE" 

14 inside = False 

15 

16 points = None 

17 

18 grade_lines = [] 

19 

20 for line in text.split("\n"): 

21 if line.startswith(delimiter_start): 

22 inside = True 

23 elif line.startswith(delimiter_end): 

24 inside = False 

25 break 

26 elif inside: 

27 grade_lines.append(line) 

28 

29 if len(grade_lines) > 0 and not inside: 

30 # Try casting result to float 

31 try: 

32 points = float("".join(grade_lines)) 

33 except ValueError: 

34 pass 

35 return points 

36 

37 def determine_grade( 

38 self, cell: NotebookNode, log: Logger = None 

39 ) -> Tuple[Optional[float], float]: 

40 max_points = float(cell.metadata["nbgrader"]["points"]) 

41 

42 if not self.cell_changed(cell): 

43 return 0, max_points 

44 elif is_solution(cell): 

45 return None, max_points 

46 

47 for output in cell.outputs: 

48 # option 1: error, return 0 

49 if ( 

50 output.output_type == "error" 

51 or output.output_type == "stream" 

52 and output.name == "stderr" 

53 ): 

54 return 0, max_points 

55 if output.output_type == "execute_result": 

56 partial_grade = get_partial_grade(output, max_points, log) 

57 return partial_grade, max_points 

58 if output.output_type == "stream" and output.name == "stdout": 

59 partial_grade = self.extract_grade(output.text, log) 

60 if partial_grade is not None: 

61 return partial_grade, max_points 

62 

63 return max_points, max_points