diff --git a/src/gradescopeapi/classes/_helpers/_assignment_helpers.py b/src/gradescopeapi/classes/_helpers/_assignment_helpers.py index aa85e23..a909b88 100644 --- a/src/gradescopeapi/classes/_helpers/_assignment_helpers.py +++ b/src/gradescopeapi/classes/_helpers/_assignment_helpers.py @@ -179,15 +179,28 @@ def get_submission_files( f"{gradescope_base_url}/courses/{course_id}/assignments/{assignment_id}" ) - file_info_link = f"{ASSIGNMENT_ENDPOINT}/submissions/{submission_id}.json?content=react&only_keys[]=text_files&only_keys[]=file_comments" + file_info_link = ( + f"{ASSIGNMENT_ENDPOINT}/submissions/{submission_id}.json?content=react" + "&only_keys[]=text_files&only_keys[]=file_comments" + "&only_keys[]=pdf_attachment&only_keys[]=image_attachments" + ) file_info_resp = session.get(file_info_link) + aws_links = [] if file_info_resp.status_code == requests.codes.ok: file_info_json = json.loads(file_info_resp.text) if file_info_json.get("text_files"): - aws_links = [] + # autograder / code submissions for file_data in file_info_json["text_files"]: aws_links.append(file_data["file"]["url"]) + elif file_info_json.get("pdf_attachment", {}).get("url"): + # PDF / image submissions: Gradescope stores the student's original + # upload as a single combined pdf_attachment (the un-annotated file). + aws_links.append(file_info_json["pdf_attachment"]["url"]) + elif file_info_json.get("image_attachments"): + # fallback: individual image files when there is no combined PDF + for image in file_info_json["image_attachments"]: + if image.get("url"): + aws_links.append(image["url"]) else: - raise NotImplementedError("Image only submissions not yet supported") - # TODO add support for image questions + raise NotImplementedError("Submission has no downloadable files") return aws_links diff --git a/tests/test_submission_files_unit.py b/tests/test_submission_files_unit.py new file mode 100644 index 0000000..b304fba --- /dev/null +++ b/tests/test_submission_files_unit.py @@ -0,0 +1,62 @@ +"""Unit tests for get_submission_files — no network/credentials required. + +A fake session returns a canned submission JSON so we can exercise each +submission shape (autograder text_files, PDF/image submissions via +pdf_attachment, and image_attachments). +""" +import json + +import pytest + +from gradescopeapi.classes._helpers._assignment_helpers import get_submission_files + + +class _FakeResp: + status_code = 200 + + def __init__(self, payload): + self.text = json.dumps(payload) + + +class _FakeSession: + def __init__(self, payload): + self._payload = payload + + def get(self, url): + return _FakeResp(self._payload) + + +def _call(payload): + return get_submission_files(_FakeSession(payload), "1", "2", "3") + + +def test_text_files_submission(): + links = _call({"text_files": [{"file": {"url": "https://aws/a.py"}}]}) + assert links == ["https://aws/a.py"] + + +def test_pdf_attachment_submission(): + # PDF/image submission: no autograder text_files, just a combined PDF + links = _call( + {"text_files": [], "pdf_attachment": {"url": "https://aws/orig.pdf"}} + ) + assert links == ["https://aws/orig.pdf"] + + +def test_image_attachments_submission(): + links = _call( + { + "text_files": [], + "pdf_attachment": {}, + "image_attachments": [ + {"url": "https://aws/1.jpg"}, + {"url": "https://aws/2.jpg"}, + ], + } + ) + assert links == ["https://aws/1.jpg", "https://aws/2.jpg"] + + +def test_no_files_raises(): + with pytest.raises(NotImplementedError): + _call({"text_files": [], "pdf_attachment": {}, "image_attachments": []})