Skip to content

API Reference

📌 Grader Functions


jupygrader.grade_notebooks()

The primary entry point for grading. All functionality is accessible through this single function.

from jupygrader import grade_notebooks

# Grade a list of notebooks
graded_results = grade_notebooks(["path/to/notebook1.ipynb", "path/to/notebook2.ipynb"])
from jupygrader import grade_notebooks

item1 = {
    "notebook_path": "path/to/notebook1.ipynb",
    "output_path": "path/to/output1",
    "copy_files": ["data1.csv"],
}

item2 = {
    "notebook_path": "path/to/notebook2.ipynb",
    "output_path": None,  # Will default to the notebook's parent directory
    "copy_files": {
        "data/population.csv": "another/path/population.csv",
    },
}

graded_results = grade_notebooks(
    [item1, item2],
    execution_timeout=300  # Set execution timeout to 300 seconds (5 minutes)
)
import openai
from jupygrader import grade_notebooks

client = openai.OpenAI(api_key="your-api-key")

# Grade based on notebook content — no execution
results = grade_notebooks(
    ["submissions/student1.ipynb", "submissions/student2.ipynb"],
    ai_mode="full",
    openai_client=client,
    openai_model="gpt-4o",
)
import openai
from jupygrader import grade_notebooks

client = openai.OpenAI(api_key="your-api-key")

# Execute notebooks, then send manual and failed cases to AI
results = grade_notebooks(
    ["submissions/student1.ipynb", "submissions/student2.ipynb"],
    ai_mode="manual_and_failed",
    openai_client=client,
    openai_model="gpt-4o",
    custom_prompt="Award partial credit for correct reasoning even if the final answer is wrong.",
)

Parameters

Parameter Type Default Description
grading_items list — Notebook paths (strings/Paths), dicts, or GradingItem objects
base_files str, Path, list, or dict None Files copied to every notebook's working directory
verbose bool True Print progress and diagnostic information
export_csv bool True Export results to a timestamped CSV file
csv_output_path str or Path None Custom path for the CSV export
regrade_existing bool False Re-grade notebooks even if cached results exist
execution_timeout int or None 600 Max seconds for notebook execution; None disables timeout
ai_mode str "off" AI grading mode — see table below
openai_client openai.OpenAI None OpenAI client instance; required when ai_mode is not "off"
openai_model str None Model name (e.g. "gpt-4o"); required when ai_mode is not "off"
custom_prompt str None Additional grading instructions appended to the AI system prompt

AI grading modes

ai_mode Description
"off" No AI grading (default)
"full" AI grades all test cases based on notebook content — notebook is not executed
"manual_only" AI grades test cases marked _grade_manually = True
"review_failed" AI reviews failed auto-graded test cases
"manual_and_failed" AI grades both manual items and failed test cases

Grade multiple Jupyter notebooks with test cases.

Processes a list of notebook grading items, executes each notebook in a clean environment, evaluates test cases, and produces graded outputs. Can handle both simple file paths and complex grading configurations.

Parameters:

Name Type Description Default
grading_items List[Union[FilePath, GradingItem, dict]]

List of items to grade, which can be: - Strings or Path objects with paths to notebook files - GradingItem objects with detailed grading configuration - Dictionaries that can be converted to GradingItem objects

required
base_files Optional[Union[FilePath, List[FilePath], FileDict]]

Optional files to include in all grading environments. Can be: - A single file path (string or Path) - A list of file paths - A dictionary mapping source paths to destination paths

None
verbose bool

Whether to print progress and diagnostic information. Defaults to True.

True
export_csv bool

Whether to export results to CSV file. Defaults to True.

True
csv_output_path Optional[FilePath]

Optional path for the CSV export. If None, uses notebook output directories. Defaults to None.

None
regrade_existing bool

Whether to regrade notebooks even if results already exist. Defaults to False.

False
execution_timeout Optional[int]

Maximum time (in seconds) allowed for notebook execution. Set to None to disable the timeout. Defaults to 600 seconds.

600
ai_mode AIGradingMode

Mode for AI grading. Defaults to AIGradingMode.OFF.

OFF
openai_client Optional[OpenAI]

OpenAI client instance (optional).

None
openai_model Optional[str]

Model name for OpenAI API. Required when ai_mode is not OFF.

None
custom_prompt Optional[str]

Optional additional instructions for the AI grading model. Only used when ai_mode is not OFF.

None

Returns:

Type Description
List[GradedResult]

List of GradedResult objects containing detailed results for each notebook.

Raises:

Type Description
TypeError

If an element in grading_items has an unsupported type.

ValueError

If a required path doesn't exist, has invalid configuration, or if ai_mode is not OFF but openai_model is not specified.

Examples:

>>> # Grade multiple notebooks with default settings
>>> results = grade_notebooks(["student1.ipynb", "student2.ipynb"])
>>>
>>> # With custom configurations
>>> results = grade_notebooks([
...     GradingItem(notebook_path="student1.ipynb", output_path="results"),
...     GradingItem(notebook_path="student2.ipynb", output_path="results"),
... ], base_files=["data.csv", "helpers.py"], export_csv=True)
Source code in src/jupygrader/grader.py
def grade_notebooks(
    grading_items: List[Union[FilePath, GradingItem, dict]],
    *,
    base_files: Optional[Union[FilePath, List[FilePath], FileDict]] = None,
    verbose: bool = True,
    export_csv: bool = True,
    csv_output_path: Optional[FilePath] = None,
    regrade_existing: bool = False,
    execution_timeout: Optional[int] = 600,
    ai_mode: AIGradingMode = AIGradingMode.OFF,
    openai_client: Optional["openai.OpenAI"] = None,
    openai_model: Optional[str] = None,
    custom_prompt: Optional[str] = None,
) -> List[GradedResult]:
    """Grade multiple Jupyter notebooks with test cases.

    Processes a list of notebook grading items, executes each notebook in a clean
    environment, evaluates test cases, and produces graded outputs. Can handle both
    simple file paths and complex grading configurations.

    Args:
        grading_items: List of items to grade, which can be:
            - Strings or Path objects with paths to notebook files
            - GradingItem objects with detailed grading configuration
            - Dictionaries that can be converted to GradingItem objects
        base_files: Optional files to include in all grading environments. Can be:
            - A single file path (string or Path)
            - A list of file paths
            - A dictionary mapping source paths to destination paths
        verbose: Whether to print progress and diagnostic information. Defaults to True.
        export_csv: Whether to export results to CSV file. Defaults to True.
        csv_output_path: Optional path for the CSV export. If None, uses notebook
            output directories. Defaults to None.
        regrade_existing: Whether to regrade notebooks even if results already exist.
            Defaults to False.
        execution_timeout: Maximum time (in seconds) allowed for notebook execution.
            Set to None to disable the timeout. Defaults to 600 seconds.
        ai_mode: Mode for AI grading. Defaults to AIGradingMode.OFF.
        openai_client: OpenAI client instance (optional).
        openai_model: Model name for OpenAI API. Required when ai_mode is not OFF.
        custom_prompt: Optional additional instructions for the AI grading model.
            Only used when ai_mode is not OFF.

    Returns:
        List of GradedResult objects containing detailed results for each notebook.

    Raises:
        TypeError: If an element in grading_items has an unsupported type.
        ValueError: If a required path doesn't exist, has invalid configuration,
            or if ai_mode is not OFF but openai_model is not specified.

    Examples:
        >>> # Grade multiple notebooks with default settings
        >>> results = grade_notebooks(["student1.ipynb", "student2.ipynb"])
        >>>
        >>> # With custom configurations
        >>> results = grade_notebooks([
        ...     GradingItem(notebook_path="student1.ipynb", output_path="results"),
        ...     GradingItem(notebook_path="student2.ipynb", output_path="results"),
        ... ], base_files=["data.csv", "helpers.py"], export_csv=True)
    """
    if ai_mode != AIGradingMode.OFF and openai_model is None:
        raise ValueError(
            f"openai_model must be specified when using an AI grading mode (ai_mode={ai_mode!r}). "
            "Please provide a model name (e.g., 'gpt-4o', 'gpt-4o-mini')."
        )

    batch_config = BatchGradingConfig(
        base_files=base_files,
        verbose=verbose,
        export_csv=export_csv,
        csv_output_path=csv_output_path,
        regrade_existing=regrade_existing,
        execution_timeout=execution_timeout,
        ai_mode=ai_mode,
        openai_model=openai_model,
        custom_prompt=custom_prompt,
    )

    manager = BatchGradingManager(
        grading_items=grading_items,
        batch_config=batch_config,
        openai_client=openai_client,
    )

    return manager.grade()

📦 @dataclasses


jupygrader.GradedResult

Complete results of grading a Jupyter notebook.

This comprehensive class stores all information related to grading a notebook, including scores, test case results, execution environment details, and file paths for generated outputs.

Parameters:

Name Type Description Default
filename str

Name of the graded notebook file. Defaults to "".

''
learner_autograded_score Union[int, float]

Points earned from automatically graded test cases. Defaults to 0.

0
max_autograded_score Union[int, float]

Maximum possible points from automatically graded test cases. Defaults to 0.

0
max_manually_graded_score Union[int, float]

Maximum possible points from manually graded test cases. Defaults to 0.

0
max_total_score Union[int, float]

Total maximum possible points across all test cases. Defaults to 0.

0
num_autograded_cases int

Number of automatically graded test cases. Defaults to 0.

0
num_passed_cases int

Number of passed test cases. Defaults to 0.

0
num_failed_cases int

Number of failed test cases. Defaults to 0.

0
num_manually_graded_cases int

Number of test cases requiring manual grading. Defaults to 0.

0
num_total_test_cases int

Total number of test cases in the notebook. Defaults to 0.

0
grading_finished_at str

Timestamp when grading completed. Defaults to "".

''
grading_duration_in_seconds float

Time taken to complete grading. Defaults to 0.0.

0.0
test_case_results List[TestCaseResult]

Detailed results for each individual test case. Defaults to empty list.

list()
submission_notebook_hash str

MD5 hash of the submitted notebook file. Defaults to "".

''
test_cases_hash str

MD5 hash of test case code in the notebook. Defaults to "".

''
grader_python_version str

Python version used for grading. Defaults to "".

''
grader_platform str

Platform information where grading occurred. Defaults to "".

''
jupygrader_version str

Version of Jupygrader used. Defaults to "".

''
ai_mode AIGradingMode

AI grading mode used for this grading run. Defaults to AIGradingMode.OFF.

OFF
extracted_user_code_file Optional[str]

Path to file containing extracted user code. Defaults to None.

None
graded_html_file Optional[str]

Path to HTML output of graded notebook. Defaults to None.

None
text_summary_file Optional[str]

Path to text summary file. Defaults to None.

None
graded_result_json_file Optional[str]

Path to JSON file containing the graded results. Defaults to None.

None
Source code in src/jupygrader/models/results.py
@dataclass
class GradedResult:
    """Complete results of grading a Jupyter notebook.

    This comprehensive class stores all information related to grading a notebook,
    including scores, test case results, execution environment details, and file paths
    for generated outputs.

    Args:
        filename: Name of the graded notebook file. Defaults to "".
        learner_autograded_score: Points earned from automatically graded test cases. Defaults to 0.
        max_autograded_score: Maximum possible points from automatically graded test cases. Defaults to 0.
        max_manually_graded_score: Maximum possible points from manually graded test cases. Defaults to 0.
        max_total_score: Total maximum possible points across all test cases. Defaults to 0.
        num_autograded_cases: Number of automatically graded test cases. Defaults to 0.
        num_passed_cases: Number of passed test cases. Defaults to 0.
        num_failed_cases: Number of failed test cases. Defaults to 0.
        num_manually_graded_cases: Number of test cases requiring manual grading. Defaults to 0.
        num_total_test_cases: Total number of test cases in the notebook. Defaults to 0.
        grading_finished_at: Timestamp when grading completed. Defaults to "".
        grading_duration_in_seconds: Time taken to complete grading. Defaults to 0.0.
        test_case_results: Detailed results for each individual test case. Defaults to empty list.
        submission_notebook_hash: MD5 hash of the submitted notebook file. Defaults to "".
        test_cases_hash: MD5 hash of test case code in the notebook. Defaults to "".
        grader_python_version: Python version used for grading. Defaults to "".
        grader_platform: Platform information where grading occurred. Defaults to "".
        jupygrader_version: Version of Jupygrader used. Defaults to "".
        ai_mode: AI grading mode used for this grading run. Defaults to AIGradingMode.OFF.
        extracted_user_code_file: Path to file containing extracted user code. Defaults to None.
        graded_html_file: Path to HTML output of graded notebook. Defaults to None.
        text_summary_file: Path to text summary file. Defaults to None.
        graded_result_json_file: Path to JSON file containing the graded results. Defaults to None.
    """

    filename: str = ""
    learner_autograded_score: Union[int, float] = 0
    max_autograded_score: Union[int, float] = 0
    max_manually_graded_score: Union[int, float] = 0
    max_total_score: Union[int, float] = 0
    num_autograded_cases: int = 0
    num_passed_cases: int = 0
    num_failed_cases: int = 0
    num_manually_graded_cases: int = 0
    num_ai_graded_cases: int = 0
    num_total_test_cases: int = 0
    grading_finished_at: str = ""
    grading_duration_in_seconds: float = 0.0
    test_case_results: List[TestCaseResult] = field(default_factory=list)
    submission_notebook_hash: str = ""
    test_cases_hash: str = ""
    grader_python_version: str = ""
    grader_platform: str = ""
    jupygrader_version: str = ""
    ai_mode: AIGradingMode = AIGradingMode.OFF
    extracted_user_code_file: Optional[str] = None
    graded_html_file: Optional[str] = None
    text_summary_file: Optional[str] = None
    graded_result_json_file: Optional[str] = None

    @property
    def text_summary(self) -> str:
        summary_parts = [
            f"File: {self.filename}",
            f"Autograded Score: {self.learner_autograded_score} out of {self.max_autograded_score}",
            f"Passed {self.num_passed_cases} out of {self.num_autograded_cases} test cases",
        ]

        if self.num_manually_graded_cases > 0:
            summary_parts.extend(
                [
                    f"{self.num_manually_graded_cases} items will be graded manually.",
                    f"{self.max_manually_graded_score} points are available for manually graded items.",
                    f"{self.max_total_score} total points are available.",
                ]
            )

        summary_parts.append(
            f"Grading took {self.grading_duration_in_seconds:.2f} seconds\n"
        )
        summary_parts.append("Test Case Summary")

        for test_case in self.test_case_results:
            summary_parts.append("-----------------")

            if test_case.grade_manually:
                summary_parts.append(
                    f"{test_case.test_case_name}: requires manual grading, {test_case.available_points} points available"
                )
            else:
                summary_parts.append(
                    f"{test_case.test_case_name}: {'PASS' if test_case.did_pass else 'FAIL'}, {test_case.points} out of {test_case.available_points} points"
                )

                if not test_case.did_pass:
                    summary_parts.extend(
                        ["\n[Autograder Output]", f"{test_case.error_message}"]
                    )

        return "\n".join(summary_parts)

    @classmethod
    def from_dict(cls, data: dict) -> "GradedResult":
        # Copy the dictionary to avoid modifying the original
        data_copy = data.copy()

        # Remove 'text_summary' if present in the data since it's now a computed property
        if "text_summary" in data_copy:
            del data_copy["text_summary"]

        # Process test_case_results
        test_case_results = [
            TestCaseResult(**item) for item in data_copy.get("test_case_results", [])
        ]
        data_copy["test_case_results"] = test_case_results
        return cls(**data_copy)

    def to_dict(self) -> dict:
        result_dict = asdict(self)

        # Add the computed text_summary to the dictionary
        result_dict["text_summary"] = self.text_summary

        return result_dict

jupygrader.TestCaseResult

Result of an individual test case execution in a notebook.

This class stores the outcome of executing a test case during grading, including the points awarded, whether the test passed, and any output messages generated during execution.

Parameters:

Name Type Description Default
test_case_name str

Unique identifier for the test case. Defaults to "".

''
points Union[int, float]

Points awarded for this test case (0 if failed). Defaults to 0.

0
available_points Union[int, float]

Maximum possible points for this test case. Defaults to 0.

0
did_pass Optional[bool]

Whether the test case passed (True), failed (False), or requires manual grading (None). Defaults to None.

None
grade_manually bool

Whether this test case should be graded manually. Defaults to False.

False
error_message Optional[str]

Error message from the test execution, typically contains error information if the test failed. Defaults to None.

None
is_graded bool

Whether this test case has been graded (either using the test cases or with AI). Defaults to False.

False
ai_feedback Optional[str]

Optional feedback generated by AI grading (if applicable). Defaults to None.

None
Source code in src/jupygrader/models/results.py
@dataclass
class TestCaseResult:
    """Result of an individual test case execution in a notebook.

    This class stores the outcome of executing a test case during grading,
    including the points awarded, whether the test passed, and any output
    messages generated during execution.

    Args:
        test_case_name: Unique identifier for the test case. Defaults to "".
        points: Points awarded for this test case (0 if failed). Defaults to 0.
        available_points: Maximum possible points for this test case. Defaults to 0.
        did_pass: Whether the test case passed (True), failed (False),
            or requires manual grading (None). Defaults to None.
        grade_manually: Whether this test case should be graded manually. Defaults to False.
        error_message: Error message from the test execution, typically contains
            error information if the test failed. Defaults to None.
        is_graded: Whether this test case has been graded (either using the test cases or with AI). Defaults to False.
        ai_feedback: Optional feedback generated by AI grading (if applicable). Defaults to None.
    """

    test_case_name: str = ""
    points: Union[int, float] = 0
    available_points: Union[int, float] = 0
    did_pass: Optional[bool] = None  # Can be True, False, or None
    grade_manually: bool = False
    error_message: Optional[str] = None
    is_graded: bool = False
    ai_feedback: Optional[str] = None


📌 Notebook Operations


jupygrader.extract_test_case_metadata_from_code()

Extract test case metadata from a code cell string.

Parses a code string to extract test case metadata including the test case name, points value, and whether it requires manual grading. The function looks for specific patterns in the code:

  • _test_case = 'name' (required)
  • _points = value (optional, defaults to 0)
  • _grade_manually = True/False (optional, defaults to False)

Parameters:

Name Type Description Default
code_str str

The source code string to parse for test case metadata

required

Returns:

Type Description
Optional[TestCaseMetadata]

A TestCaseMetadata object with extracted values if a test case is found,

Optional[TestCaseMetadata]

None if a test case is not found

Source code in src/jupygrader/notebook_operations.py
def extract_test_case_metadata_from_code(code_str: str) -> Optional[TestCaseMetadata]:
    """Extract test case metadata from a code cell string.

    Parses a code string to extract test case metadata including the test case name,
    points value, and whether it requires manual grading. The function looks for
    specific patterns in the code:

    - `_test_case = 'name'`  (required)
    - `_points = value`      (optional, defaults to 0)
    - `_grade_manually = True/False`  (optional, defaults to `False`)

    Args:
        code_str: The source code string to parse for test case metadata

    Returns:
        A TestCaseMetadata object with extracted values if a test case is found,
        None if a test case is not found
    """
    test_case_name = _extract_assignment_literal_value(code_str, "_test_case")
    if not isinstance(test_case_name, str):
        return None

    metadata = TestCaseMetadata(
        test_case_name=test_case_name,
        points=0,
        grade_manually=False,
    )

    points = _extract_assignment_literal_value(code_str, "_points")
    if isinstance(points, (int, float)):
        metadata.points = float(points)

    manual_grading = _extract_assignment_literal_value(code_str, "_grade_manually")
    if isinstance(manual_grading, bool):
        metadata.grade_manually = manual_grading

    return metadata

jupygrader.extract_test_cases_metadata_from_notebook()

Extract metadata from all test cases in a notebook.

Iterates through all code cells in the notebook and identifies test case cells by looking for specific pattern markers. For each test case found, extracts the metadata into a TestCaseMetadata object.

Parameters:

Name Type Description Default
nb NotebookNode

The notebook to extract test case metadata from

required

Returns:

Type Description
List[TestCaseMetadata]

A list of TestCaseMetadata objects for all test cases found in the notebook

Source code in src/jupygrader/notebook_operations.py
def extract_test_cases_metadata_from_notebook(
    nb: NotebookNode,
) -> List[TestCaseMetadata]:
    """Extract metadata from all test cases in a notebook.

    Iterates through all code cells in the notebook and identifies test case cells
    by looking for specific pattern markers. For each test case found, extracts
    the metadata into a `TestCaseMetadata` object.

    Args:
        nb: The notebook to extract test case metadata from

    Returns:
        A list of TestCaseMetadata objects for all test cases found in the notebook
    """
    metadata_list: List[TestCaseMetadata] = []

    for cell in nb.cells:
        if cell.cell_type == "code":
            test_case_metadata = extract_test_case_metadata_from_code(cell.source)

            if test_case_metadata:
                metadata_list.append(test_case_metadata)

    return metadata_list

jupygrader.does_cell_contain_test_case()

Determine if a notebook cell contains a test case.

A cell is considered a test case if it contains the pattern '_test_case = "name"'. This function uses a regular expression to check for this pattern.

Parameters:

Name Type Description Default
cell NotebookNode

The notebook cell to check

required

Returns:

Type Description
bool

True if the cell contains a test case pattern, False otherwise

Source code in src/jupygrader/notebook_operations.py
def does_cell_contain_test_case(cell: NotebookNode) -> bool:
    """Determine if a notebook cell contains a test case.

    A cell is considered a test case if it contains the pattern '_test_case = "name"'.
    This function uses a regular expression to check for this pattern.

    Args:
        cell: The notebook cell to check

    Returns:
        True if the cell contains a test case pattern, False otherwise
    """
    return isinstance(
        _extract_assignment_literal_value(cell.source, "_test_case"),
        str,
    )

jupygrader.is_manually_graded_test_case()

Determine if a notebook cell contains a manually graded test case.

A test case is considered manually graded if it contains the pattern '_grade_manually = True'. This function checks for this specific pattern in the cell's source code.

Parameters:

Name Type Description Default
cell NotebookNode

The notebook cell to check

required

Returns:

Type Description
bool

True if the cell is a manually graded test case, False otherwise

Source code in src/jupygrader/notebook_operations.py
def is_manually_graded_test_case(cell: NotebookNode) -> bool:
    """Determine if a notebook cell contains a manually graded test case.

    A test case is considered manually graded if it contains the pattern
    '_grade_manually = True'. This function checks for this specific pattern
    in the cell's source code.

    Args:
        cell: The notebook cell to check

    Returns:
        True if the cell is a manually graded test case, False otherwise
    """
    return isinstance(
        _extract_assignment_literal_value(cell.source, "_grade_manually"),
        bool,
    )

jupygrader.extract_user_code_from_notebook()

Extract user code from a notebook.

Collects all code from non-test-case code cells in the notebook.

Parameters:

Name Type Description Default
nb NotebookNode

The notebook to extract code from

required

Returns:

Type Description
str

String containing all user code concatenated with newlines

Source code in src/jupygrader/notebook_operations.py
def extract_user_code_from_notebook(nb: NotebookNode) -> str:
    """Extract user code from a notebook.

    Collects all code from non-test-case code cells in the notebook.

    Args:
        nb: The notebook to extract code from

    Returns:
        String containing all user code concatenated with newlines
    """
    full_code = ""

    for cell in nb.cells:
        if (
            (cell.cell_type == "code")
            and not does_cell_contain_test_case(cell)
            and cell.source
        ):
            full_code += cell.source + "\n\n"

    return full_code

jupygrader.remove_code_cells_that_contain()

Source code in src/jupygrader/notebook_operations.py
def remove_code_cells_that_contain(
    nb: NotebookNode, search_str: Union[str, List[str]]
) -> NotebookNode:
    if isinstance(search_str, str):
        search_list = [search_str]
    else:
        search_list = search_str

    nb.cells = [
        cell
        for cell in nb.cells
        if not (cell.cell_type == "code" and any(s in cell.source for s in search_list))
    ]
    return nb

jupygrader.remove_comments()

Remove comments from Python source code.

Removes both single line comments (starting with #) and multi-line comments (/ ... /), while preserving strings.

Parameters:

Name Type Description Default
source str

Python source code as string

required

Returns:

Type Description
str

Source code with comments removed

Source code in src/jupygrader/notebook_operations.py
def remove_comments(source: str) -> str:
    """Remove comments from Python source code.

    Removes both single line comments (starting with #) and
    multi-line comments (/* ... */), while preserving strings.

    Args:
        source: Python source code as string

    Returns:
        Source code with comments removed
    """
    pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|#[^\r\n]*$)"
    # first group captures quoted strings (double or single)
    # second group captures comments (# single-line or /* multi-line */)
    regex = re.compile(pattern, re.MULTILINE | re.DOTALL)

    def _replacer(match):
        # if the 2nd group (capturing comments) is not None,
        # it means we have captured a non-quoted (real) comment string.
        if match.group(2) is not None:
            return ""  # so we will return empty to remove the comment
        else:  # otherwise, we will return the 1st group
            return match.group(1)  # captured quoted-string

    return regex.sub(_replacer, source)

jupygrader.get_test_cases_hash()

Generate a hash of all test cases in a notebook.

Creates a standardized representation of all test case cells by removing comments and formatting with Black, then generates an MD5 hash.

Parameters:

Name Type Description Default
nb NotebookNode

The notebook to generate a hash for

required

Returns:

Type Description
str

MD5 hash string representing the test cases

Source code in src/jupygrader/notebook_operations.py
def get_test_cases_hash(nb: NotebookNode) -> str:
    """Generate a hash of all test cases in a notebook.

    Creates a standardized representation of all test case cells by
    removing comments and formatting with Black, then generates an MD5 hash.

    Args:
        nb: The notebook to generate a hash for

    Returns:
        MD5 hash string representing the test cases
    """
    test_cases_code = ""

    for cell in nb.cells:
        if (cell.cell_type == "code") and does_cell_contain_test_case(cell):
            # standardize code before hasing
            # by removing comments and formatting the code using the Black formatter
            standardized_code = remove_comments(cell.source)
            standardized_code = black.format_str(standardized_code, mode=black.Mode())

            # concatenate to test_cases_code
            test_cases_code += standardized_code

    # generate an MD5 hash
    hash_str = hashlib.md5(test_cases_code.encode("utf-8")).hexdigest()
    return hash_str