diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6d369e67..4211f054 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,6 +13,7 @@ on: - master - beta - release + - temp workflow_dispatch: jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ddc6774..9b2a2aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ * Updated Otter Assign to throw a `ValueError` when invalid Python code is encountered in test cells per [#756](https://github.com/ucbds-infra/otter-grader/issues/756) * Fixed an error causing intercell seeding code to be added to cells using cell magic commands which caused syntax errors per [#754](https://github.com/ucbds-infra/otter-grader/issues/754) +**v5.2.3:** + +* Fixed the no PDF acknowledgement feature to handle when PDF exports throw an error instead of failing silently + **v5.2.2:** * Fixed an `AttributeError` when using `Notebook.check_all` per [#746](https://github.com/ucbds-infra/otter-grader/issues/746) diff --git a/otter/check/notebook.py b/otter/check/notebook.py index 562611af..5e5070f9 100644 --- a/otter/check/notebook.py +++ b/otter/check/notebook.py @@ -457,10 +457,11 @@ def export( zf = zipfile.ZipFile(zip_path, mode="w") zf.write(nb_path) - pdf_created = True + pdf_path, pdf_created, pdf_error = None, True, None if pdf: - pdf_path = export_notebook(nb_path, filtering=filtering, pagebreaks=pagebreaks) - if os.path.isfile(pdf_path): + try: pdf_path = export_notebook(nb_path, filtering=filtering, pagebreaks=pagebreaks) + except Exception as e: pdf_error = e + if pdf_path and os.path.isfile(pdf_path): pdf_created = True zf.write(pdf_path) self._logger.debug(f"Wrote PDF to zip file: {pdf_path}") @@ -520,10 +521,12 @@ def continue_export(): display(HTML(out_html)) if pdf_created or not self._nbmeta_config.require_no_pdf_confirmation: + if pdf_error is not None: + raise pdf_error continue_export() else: display_pdf_confirmation_widget( - self._nbmeta_config.export_pdf_failure_message, continue_export) + self._nbmeta_config.export_pdf_failure_message, pdf_error, continue_export) @grading_mode_disabled @logs_event(EventType.END_CHECK_ALL) diff --git a/otter/check/utils.py b/otter/check/utils.py index 80f6413c..4b6f259e 100644 --- a/otter/check/utils.py +++ b/otter/check/utils.py @@ -19,7 +19,7 @@ from ..nbmeta_config import NBMetadataConfig from ..test_files import GradingResults -from ..utils import import_or_raise +from ..utils import format_exception, import_or_raise T = TypeVar("T") @@ -332,7 +332,7 @@ def resolve_test_info( return test_path, test_name -def display_pdf_confirmation_widget(message: Optional[str], callback: Callable) -> None: +def display_pdf_confirmation_widget(message: Optional[str], pdf_error: Optional[Exception], callback: Callable) -> None: """ Display a widget to the user to acknowledge that a PDF will not be included in their submission zip. @@ -349,7 +349,11 @@ def wrapped_callback(*args): message = "Your notebook could not be exported as a PDF. To continue exporting your " \ "submission, please click the button below." - t = HTML(f"""

{message}

""") + message_html = f"""

{message}

""" + if pdf_error is not None: + message_html += f"""
{format_exception(pdf_error)}
""" + + t = HTML(message_html) b = Button(description="Continue export", button_style="warning") b.on_click(wrapped_callback) m = HTML("""
""") diff --git a/otter/test_files/__init__.py b/otter/test_files/__init__.py index 7ff4dd8b..eea55729 100644 --- a/otter/test_files/__init__.py +++ b/otter/test_files/__init__.py @@ -5,7 +5,6 @@ import nbformat as nbf import os import pickle -import traceback from typing import Any, Dict, List, Optional @@ -16,7 +15,7 @@ from .ottr_test import OttrTestFile from ..nbmeta_config import NBMetadataConfig, OK_FORMAT_VARNAME -from ..utils import QuestionNotInLogException +from ..utils import format_exception, QuestionNotInLogException __all__ = [ @@ -414,11 +413,7 @@ def to_gradescope_dict(self, ag_config): "output": "The autograder failed to produce any results. Please alert your instructor to this failure for assistance in debugging it.", "status": "failed", }) - tb = "".join(traceback.format_exception( - type(self._catastrophic_error), - self._catastrophic_error, - self._catastrophic_error.__traceback__, - )) + tb = format_exception(self._catastrophic_error) output["tests"].append({ "name": "Autograder Exception", "visibility": "hidden", diff --git a/otter/utils.py b/otter/utils.py index dc0a411b..5fc677da 100644 --- a/otter/utils.py +++ b/otter/utils.py @@ -10,6 +10,7 @@ import string import shutil import tempfile +import traceback import yaml from contextlib import contextmanager @@ -398,3 +399,16 @@ class QuestionNotInLogException(Exception): """ Exception that indicates that a specific question was not found in any entry in the log """ + + +def format_exception(e: Exception) -> str: + """ + Formats an exception for display with its traceback using the ``traceback`` module. + + Args: + e (``Exception``): the exception to format + + Returns: + ``str``: the formatted exception + """ + return "".join(traceback.format_exception(type(e), e, e.__traceback__))