Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow upload of submission PDF #765

Merged
merged 1 commit into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

* 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)
* Add support for submitting the PDF uploaded in a submission for manual grading instead of generating a new one per [#764](https://github.com/ucbds-infra/otter-grader/issues/764)
* Validate that a course ID and assignment ID were provided before attempting PDF upload

**v5.2.3:**

Expand Down
11 changes: 9 additions & 2 deletions otter/run/run_autograder/autograder_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ class AutograderConfig(fica.Config):

course_id = fica.Key(
description="a Gradescope course ID for uploading a PDF of the notebook",
default="None",
default=None,
)

assignment_id = fica.Key(
description="a Gradescope assignment ID for uploading a PDF of the notebook",
default="None",
default=None,
)

filtering = fica.Key(
Expand Down Expand Up @@ -163,5 +163,12 @@ class AutograderConfig(fica.Config):
default=False,
)

use_submission_pdf = fica.Key(
description="use the PDF in the submission zip file instead of exporting a new one; if " \
"no PDF is present, a new one is generated anyway; assumes there is only 1 PDF file " \
"in the submission",
default=False,
)

_otter_run = False
"""whether this autograder run is being run by Otter Run (i.e. without containerization)"""
13 changes: 12 additions & 1 deletion otter/run/run_autograder/runners/abstract_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import tempfile

from abc import ABC, abstractmethod
from glob import glob

from ..autograder_config import AutograderConfig
from ..utils import OtterRuntimeError, print_output, write_blank_page_to_stare_at_before_you
Expand Down Expand Up @@ -100,7 +101,11 @@
appended if needed)
"""
try:
pdf_path = self.write_pdf(submission_path)
subm_pdfs = glob("*.pdf")
if self.ag_config.use_submission_pdf and subm_pdfs:
pdf_path = subm_pdfs[0]
else:
pdf_path = self.write_pdf(submission_path)

if submit:
self.submit_pdf(client, pdf_path)
Expand Down Expand Up @@ -132,6 +137,12 @@
for user in metadata.get("users", []):
student_emails.append(user["email"])

# validate that a course and assignment ID were specified
if self.ag_config.course_id is None:
raise OtterRuntimeError("PDF upload course ID not specified")

Check warning on line 142 in otter/run/run_autograder/runners/abstract_runner.py

View check run for this annotation

Codecov / codecov/patch

otter/run/run_autograder/runners/abstract_runner.py#L142

Added line #L142 was not covered by tests
if self.ag_config.assignment_id is None:
raise OtterRuntimeError("PDF upload assignment ID not specified")

Check warning on line 144 in otter/run/run_autograder/runners/abstract_runner.py

View check run for this annotation

Codecov / codecov/patch

otter/run/run_autograder/runners/abstract_runner.py#L144

Added line #L144 was not covered by tests

for student_email in student_emails:
res = client.upload_pdf_submission(
self.ag_config.course_id,
Expand Down
3 changes: 2 additions & 1 deletion test/test_check/test_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ def test_export_with_no_pdf_ack(
mocked_dt.datetime.now.return_value = timestmap
mocked_export.return_value = FILE_MANAGER.get_path(f"{NB_PATH_STEM}.pdf")

grader.export()
with pytest.warns(UserWarning, match="Could not locate a PDF to include"):
grader.export()

mocked_export.assert_called_with(NB_PATH, filtering=True, pagebreaks=True)

Expand Down
2 changes: 1 addition & 1 deletion test/test_run/files/autograder/source/otter_config.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"autograder_dir": "test/test_run/files/autograder", "plugins": [{"otter.plugins.builtin.RateLimiting": {"days": 1, "allowed_submissions": 1}}], "warn_missing_pdf": true, "token": "abc123", "show_hidden": false, "force_public_test_summary": false}
{"autograder_dir": "test/test_run/files/autograder", "plugins": [{"otter.plugins.builtin.RateLimiting": {"days": 1, "allowed_submissions": 1}}], "token": "abc123", "show_hidden": false, "force_public_test_summary": false, "course_id": "12345", "assignment_id": "67890"}
7 changes: 6 additions & 1 deletion test/test_run/files/autograder/submission_metadata.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"created_at": "2020-01-01T01:01:01.000-0700",
"previous_submissions": []
"previous_submissions": [],
"users": [
{
"email": "student@univ.edu"
}
]
}
33 changes: 33 additions & 0 deletions test/test_run/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from contextlib import contextmanager, nullcontext
from unittest import mock

from otter.generate.token import APIClient
from otter.run.run_autograder import main as run_autograder
from otter.run.run_autograder.utils import OtterRuntimeError
from otter.utils import NBFORMAT_VERSION
Expand Down Expand Up @@ -223,6 +224,38 @@ def test_pdf_generation_failure(mocked_export, get_config_path, load_config, exp
f"Actual results did not matched expected:\n{actual_results}"


@mock.patch.object(APIClient, "upload_pdf_submission")
@mock.patch("otter.run.run_autograder.runners.python_runner.export_notebook")
def test_use_submission_pdf(
mocked_export,
mocked_upload_pdf_submission,
get_config_path,
load_config,
expected_results,
):
config = load_config()
config["use_submission_pdf"] = True
config["token"] = "abc123"

FILE_MANAGER.open("autograder/submission/fails2and6H.pdf", "wb+").close()

with alternate_config(get_config_path(), config):
run_autograder(config['autograder_dir'])

with FILE_MANAGER.open("autograder/results/results.json") as f:
actual_results = json.load(f)

mocked_export.assert_not_called()
mocked_upload_pdf_submission.assert_called_with(
config["course_id"],
config["assignment_id"],
"student@univ.edu", # from submission_metadata.json in autograder dir
"fails2and6H.pdf",
)
assert actual_results == expected_results, \
f"Actual results did not matched expected:\n{actual_results}"


def test_force_public_test_summary(get_config_path, load_config):
config = load_config()

Expand Down
5 changes: 1 addition & 4 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ def __init__(self, test_file_path):
def get_path(self, path):
return str(self.dir / path)

@contextmanager
def open(self, path, *args, **kwargs):
f = open(self.get_path(path), *args, **kwargs)
yield f
f.close()
return open(self.get_path(path), *args, **kwargs)

def assert_path_exists(self, path, file_okay=True, dir_okay=True):
path = self.get_path(path)
Expand Down
Loading