From d1fd46a52906372c4682806eef6af6ba466bc5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20K=C3=B6se?= Date: Wed, 8 Mar 2023 13:57:26 +0200 Subject: [PATCH 1/6] Add missing method fail to coursedir.py --- nbgrader/coursedir.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nbgrader/coursedir.py b/nbgrader/coursedir.py index 9242e56a7..be5809714 100644 --- a/nbgrader/coursedir.py +++ b/nbgrader/coursedir.py @@ -1,5 +1,6 @@ import os import re +import sys from textwrap import dedent @@ -338,3 +339,7 @@ def get_existing_timestamp(self, dest_path: str) -> Optional[datetime.datetime]: "Invalid timestamp string: {}".format(timestamp_path)) else: return None + + def fail(self, msg, *args): + self.log.error(msg, *args) + sys.exit(1) From 77f14ac69711116e3e66f8ec674df6fbb64885bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20Ba=C5=9Far=20K=C3=B6se?= Date: Wed, 8 Mar 2023 09:03:44 +0200 Subject: [PATCH 2/6] Change CheckCellMetadata error message to mention possible duplicate ids --- nbgrader/preprocessors/checkcellmetadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nbgrader/preprocessors/checkcellmetadata.py b/nbgrader/preprocessors/checkcellmetadata.py index d49cb45d4..60c32b799 100644 --- a/nbgrader/preprocessors/checkcellmetadata.py +++ b/nbgrader/preprocessors/checkcellmetadata.py @@ -13,7 +13,8 @@ def preprocess(self, nb: NotebookNode, resources: Dict) -> Tuple[NotebookNode, D MetadataValidator().validate_nb(nb) except ValidationError: self.log.error(traceback.format_exc()) - msg = "Notebook failed to validate; the nbgrader metadata may be corrupted." + msg = "Notebook failed to validate; the nbgrader metadata may be corrupted " \ + "or a cell might have been duplicated." self.log.error(msg) raise ValidationError(msg) From 3192f85545b4c28f661951f01204ca4278f45edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20Ba=C5=9Far=20K=C3=B6se?= Date: Wed, 8 Mar 2023 09:04:25 +0200 Subject: [PATCH 3/6] Add new format with duplicate flag Changes in tests due to new format --- .../autograded/bitdiddle/ps1/problem1.ipynb | 20 +- .../autograded/bitdiddle/ps1/problem2.ipynb | 4 +- .../autograded/hacker/ps1/problem1.ipynb | 20 +- .../autograded/hacker/ps1/problem2.ipynb | 4 +- ...attempt_2016-01-30-20-30-10_problem1.ipynb | 18 +- .../user_guide/release/ps1/problem1.ipynb | 38 ++- .../user_guide/release/ps1/problem2.ipynb | 4 +- .../user_guide/source/ps1/problem1.ipynb | 20 +- .../user_guide/source/ps1/problem2.ipynb | 4 +- .../source/ps1_autotest/problem1.ipynb | 24 +- .../source/ps1_autotest/problem2.ipynb | 4 +- .../submitted/bitdiddle/ps1/problem1.ipynb | 20 +- .../submitted/bitdiddle/ps1/problem2.ipynb | 4 +- .../submitted/hacker/ps1/problem1.ipynb | 20 +- .../submitted/hacker/ps1/problem2.ipynb | 4 +- nbgrader/nbgraderformat/__init__.py | 6 +- nbgrader/nbgraderformat/v4.json | 61 ++++ nbgrader/nbgraderformat/v4.py | 139 ++++++++ .../apps/files/autotest-hashed-changed.ipynb | 4 +- .../files/autotest-hashed-unchanged.ipynb | 4 +- .../tests/apps/files/autotest-hashed.ipynb | 4 +- .../files/autotest-hidden-changed-right.ipynb | 4 +- .../files/autotest-hidden-changed-wrong.ipynb | 4 +- .../files/autotest-hidden-unchanged.ipynb | 4 +- .../tests/apps/files/autotest-hidden.ipynb | 4 +- .../apps/files/autotest-multi-changed.ipynb | 10 +- .../apps/files/autotest-multi-unchanged.ipynb | 10 +- .../tests/apps/files/autotest-multi.ipynb | 10 +- .../apps/files/autotest-simple-changed.ipynb | 4 +- .../files/autotest-simple-unchanged.ipynb | 4 +- .../tests/apps/files/autotest-simple.ipynb | 4 +- .../tests/apps/files/open_relative_file.ipynb | 2 +- .../tests/apps/files/submitted-changed.ipynb | 14 +- .../submitted-cheat-attempt-alternative.ipynb | 14 +- .../apps/files/submitted-cheat-attempt.ipynb | 14 +- .../files/submitted-grade-cell-changed.ipynb | 14 +- .../files/submitted-locked-cell-changed.ipynb | 14 +- .../apps/files/submitted-unchanged.ipynb | 14 +- .../tests/apps/files/test-hidden-tests.ipynb | 14 +- nbgrader/tests/apps/files/test-v3.ipynb | 300 ++++++++++++++++++ .../tests/apps/files/test-with-output.ipynb | 18 +- nbgrader/tests/apps/files/test.ipynb | 18 +- nbgrader/tests/apps/files/timeout.ipynb | 4 +- .../validating-environment-variable.ipynb | 2 +- .../apps/files/validation-zero-points.ipynb | 4 +- nbgrader/tests/apps/test_api.py | 3 +- nbgrader/tests/nbgraderformat/test_v3.py | 10 +- nbgrader/tests/nbgraderformat/test_v4.py | 286 +++++++++++++++++ .../files/bad-markdown-cell-1.ipynb | 2 +- .../files/bad-markdown-cell-2.ipynb | 2 +- .../preprocessors/files/blank-grade-id.ipynb | 2 +- .../preprocessors/files/blank-points.ipynb | 2 +- .../files/cell-type-changed.ipynb | 2 +- .../files/duplicate-grade-ids.ipynb | 4 +- .../files/manually-graded-code-cell.ipynb | 2 +- .../preprocessors/files/no-cell-type.ipynb | 2 +- .../submitted-answer-cell-type-changed.ipynb | 14 +- .../files/submitted-changed.ipynb | 14 +- .../files/submitted-grade-cell-changed.ipynb | 14 +- .../submitted-grade-cell-type-changed.ipynb | 14 +- .../files/submitted-locked-cell-changed.ipynb | 14 +- .../files/submitted-unchanged.ipynb | 14 +- nbgrader/tests/preprocessors/files/test.ipynb | 18 +- .../preprocessors/files/test_taskcell.ipynb | 20 +- .../ui-tests/files/open_relative_file.ipynb | 2 +- .../submitted-answer-cell-type-changed.ipynb | 14 +- .../ui-tests/files/submitted-changed.ipynb | 14 +- .../files/submitted-grade-cell-changed.ipynb | 14 +- .../submitted-grade-cell-type-changed.ipynb | 14 +- .../files/submitted-locked-cell-changed.ipynb | 14 +- .../ui-tests/files/submitted-unchanged.ipynb | 14 +- .../create_assignment_model.ts | 2 +- 72 files changed, 1128 insertions(+), 329 deletions(-) create mode 100644 nbgrader/nbgraderformat/v4.json create mode 100644 nbgrader/nbgraderformat/v4.py create mode 100644 nbgrader/tests/apps/files/test-v3.ipynb create mode 100644 nbgrader/tests/nbgraderformat/test_v4.py diff --git a/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem1.ipynb b/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem1.ipynb index 71ed5af99..bf8834f53 100644 --- a/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem1.ipynb @@ -35,7 +35,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -66,7 +66,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -124,7 +124,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -161,7 +161,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -205,7 +205,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -259,7 +259,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [ @@ -299,7 +299,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -339,7 +339,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -369,7 +369,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [ @@ -403,7 +403,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } diff --git a/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem2.ipynb b/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem2.ipynb index ad774477c..57ec1b76e 100644 --- a/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/autograded/bitdiddle/ps1/problem2.ipynb @@ -61,7 +61,7 @@ "grade_id": "part-a", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -90,7 +90,7 @@ "grade_id": "part-b", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem1.ipynb b/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem1.ipynb index ea34e15ec..8470f31c2 100644 --- a/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem1.ipynb @@ -35,7 +35,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -66,7 +66,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -121,7 +121,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -146,7 +146,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -190,7 +190,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -240,7 +240,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -265,7 +265,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -305,7 +305,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -335,7 +335,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -405,7 +405,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } diff --git a/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem2.ipynb b/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem2.ipynb index 0325b77ce..696ae538c 100644 --- a/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/autograded/hacker/ps1/problem2.ipynb @@ -61,7 +61,7 @@ "grade_id": "part-a", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -90,7 +90,7 @@ "grade_id": "part-b", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/downloaded/ps1/archive/ps1_hacker_attempt_2016-01-30-20-30-10_problem1.ipynb b/nbgrader/docs/source/user_guide/downloaded/ps1/archive/ps1_hacker_attempt_2016-01-30-20-30-10_problem1.ipynb index 8eaad7dc2..888032e73 100644 --- a/nbgrader/docs/source/user_guide/downloaded/ps1/archive/ps1_hacker_attempt_2016-01-30-20-30-10_problem1.ipynb +++ b/nbgrader/docs/source/user_guide/downloaded/ps1/archive/ps1_hacker_attempt_2016-01-30-20-30-10_problem1.ipynb @@ -35,7 +35,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 2, + "schema_version": 4, "solution": false } }, @@ -66,7 +66,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 2, + "schema_version": 4, "solution": true } }, @@ -110,7 +110,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 2, + "schema_version": 4, "solution": false } }, @@ -135,7 +135,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 2, + "schema_version": 4, "solution": false } }, @@ -179,7 +179,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 2, + "schema_version": 4, "solution": true } }, @@ -218,7 +218,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 2, + "schema_version": 4, "solution": false } }, @@ -243,7 +243,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 2, + "schema_version": 4, "solution": false } }, @@ -283,7 +283,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 2, + "schema_version": 4, "solution": true } }, @@ -313,7 +313,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 2, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/release/ps1/problem1.ipynb b/nbgrader/docs/source/user_guide/release/ps1/problem1.ipynb index 7706218da..ebc9a3856 100644 --- a/nbgrader/docs/source/user_guide/release/ps1/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/release/ps1/problem1.ipynb @@ -37,7 +37,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -68,7 +68,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -112,7 +112,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -138,7 +138,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -182,7 +182,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -223,7 +223,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -249,7 +249,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -289,7 +289,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -319,7 +319,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -341,7 +341,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } @@ -356,11 +356,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "python" + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 4 } diff --git a/nbgrader/docs/source/user_guide/release/ps1/problem2.ipynb b/nbgrader/docs/source/user_guide/release/ps1/problem2.ipynb index ac600e6b4..25d70e05e 100644 --- a/nbgrader/docs/source/user_guide/release/ps1/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/release/ps1/problem2.ipynb @@ -61,7 +61,7 @@ "grade_id": "part-a", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -90,7 +90,7 @@ "grade_id": "part-b", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/source/ps1/problem1.ipynb b/nbgrader/docs/source/user_guide/source/ps1/problem1.ipynb index af05b7340..3056c1ebb 100644 --- a/nbgrader/docs/source/user_guide/source/ps1/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/source/ps1/problem1.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -35,7 +35,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -78,7 +78,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -100,7 +100,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -141,7 +141,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -179,7 +179,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -201,7 +201,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -238,7 +238,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -265,7 +265,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -284,7 +284,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } diff --git a/nbgrader/docs/source/user_guide/source/ps1/problem2.ipynb b/nbgrader/docs/source/user_guide/source/ps1/problem2.ipynb index a8c653699..e50631092 100644 --- a/nbgrader/docs/source/user_guide/source/ps1/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/source/ps1/problem2.ipynb @@ -32,7 +32,7 @@ "grade_id": "part-a", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -58,7 +58,7 @@ "grade_id": "part-b", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/source/ps1_autotest/problem1.ipynb b/nbgrader/docs/source/user_guide/source/ps1_autotest/problem1.ipynb index 16d713402..712252f63 100644 --- a/nbgrader/docs/source/user_guide/source/ps1_autotest/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/source/ps1_autotest/problem1.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -35,7 +35,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "vscode": { @@ -85,7 +85,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "vscode": { @@ -108,7 +108,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "vscode": { @@ -150,7 +150,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "vscode": { @@ -195,7 +195,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false }, "vscode": { @@ -219,7 +219,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false }, "vscode": { @@ -256,7 +256,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -283,7 +283,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true }, "vscode": { @@ -305,7 +305,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } @@ -333,7 +333,7 @@ "grade": false, "grade_id": "cell-d3df8cd59fd0eb74", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true, "task": false }, @@ -359,7 +359,7 @@ "grade_id": "cell-6e9ff83aa5dfaf17", "locked": true, "points": 0, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false }, diff --git a/nbgrader/docs/source/user_guide/source/ps1_autotest/problem2.ipynb b/nbgrader/docs/source/user_guide/source/ps1_autotest/problem2.ipynb index a8c653699..e50631092 100644 --- a/nbgrader/docs/source/user_guide/source/ps1_autotest/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/source/ps1_autotest/problem2.ipynb @@ -32,7 +32,7 @@ "grade_id": "part-a", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -58,7 +58,7 @@ "grade_id": "part-b", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem1.ipynb b/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem1.ipynb index 6312f13ba..357949471 100644 --- a/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem1.ipynb @@ -37,7 +37,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -69,7 +69,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -119,7 +119,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -145,7 +145,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -175,7 +175,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -221,7 +221,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [ @@ -250,7 +250,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -290,7 +290,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -321,7 +321,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [ @@ -342,7 +342,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } diff --git a/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem2.ipynb b/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem2.ipynb index 5618d0d64..2aafc8dad 100644 --- a/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/submitted/bitdiddle/ps1/problem2.ipynb @@ -63,7 +63,7 @@ "grade_id": "part-a", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -92,7 +92,7 @@ "grade_id": "part-b", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem1.ipynb b/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem1.ipynb index 8076f0258..7991e24cf 100644 --- a/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem1.ipynb +++ b/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem1.ipynb @@ -37,7 +37,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -69,7 +69,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -116,7 +116,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -142,7 +142,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -187,7 +187,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -229,7 +229,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -255,7 +255,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -295,7 +295,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -326,7 +326,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -381,7 +381,7 @@ "grade_id": "cell-938593c4a215c6cc", "locked": true, "points": 4, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } diff --git a/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem2.ipynb b/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem2.ipynb index e298fe137..4630fe218 100644 --- a/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem2.ipynb +++ b/nbgrader/docs/source/user_guide/submitted/hacker/ps1/problem2.ipynb @@ -63,7 +63,7 @@ "grade_id": "part-a", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -92,7 +92,7 @@ "grade_id": "part-b", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/nbgraderformat/__init__.py b/nbgrader/nbgraderformat/__init__.py index a55696b4c..68529f3f4 100644 --- a/nbgrader/nbgraderformat/__init__.py +++ b/nbgrader/nbgraderformat/__init__.py @@ -1,6 +1,6 @@ from .common import ValidationError, SchemaTooOldError, SchemaTooNewError -from .v3 import MetadataValidatorV3 as MetadataValidator -from .v3 import read_v3 as read, write_v3 as write -from .v3 import reads_v3 as reads, writes_v3 as writes +from .v4 import MetadataValidatorV4 as MetadataValidator +from .v4 import read_v4 as read, write_v4 as write +from .v4 import reads_v4 as reads, writes_v4 as writes SCHEMA_VERSION = MetadataValidator.schema_version diff --git a/nbgrader/nbgraderformat/v4.json b/nbgrader/nbgraderformat/v4.json new file mode 100644 index 000000000..cb521ee9e --- /dev/null +++ b/nbgrader/nbgraderformat/v4.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "nbgrader cell metadata schema", + "type": "object", + "properties": { + "schema_version": { + "description": "the version of the cell metadata schema", + "type": "integer", + "minimum": 4, + "maximum": 4 + }, + "grade": { + "description": "whether this cell should be graded", + "type": "boolean", + "default": false + }, + "locked": { + "description": "whether the cell should be editable", + "type": "boolean", + "default": false + }, + "solution": { + "description": "whether this cell contains a solution", + "type": "boolean", + "default": false + }, + "task": { + "description": "whether this cell contains a task", + "type": "boolean", + "default": false + }, + "grade_id": { + "description": "the nbgrader id of the cell, only present if one or more of grade/locked/solution is true", + "type": "string", + "pattern": "^[a-zA-Z0-9_\\-]*$" + }, + "points": { + "description": "the number of points this cell is worth; only present if grade is true", + "type": "number", + "minimum": 0 + }, + "checksum": { + "description": "a checksum of the cell contents; generally only present in the student version of an assignment", + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "cell_type": { + "description": "the type the cell was when nbgrader generate_assignment was run; used for checking if the cell type has changed and should only be present in the student version of an assignment", + "type": "string", + "enum": ["raw", "code", "markdown"] + }, + "duplicate": { + "description": "whether this cell's grade_id has been encountered already during autograding/validation; should only be present during autograding/validation", + "type": "boolean", + "default": false + } + }, + "required": ["schema_version", "grade", "locked", "solution"], + "additionalProperties": false +} diff --git a/nbgrader/nbgraderformat/v4.py b/nbgrader/nbgraderformat/v4.py new file mode 100644 index 000000000..98db9ff70 --- /dev/null +++ b/nbgrader/nbgraderformat/v4.py @@ -0,0 +1,139 @@ +from nbformat import read as _read, reads as _reads +from nbformat import write as _write, writes as _writes +from nbformat.notebooknode import NotebookNode +import typing +from .v1 import MetadataValidatorV1 +from .v2 import MetadataValidatorV2 +from .v3 import MetadataValidatorV3 +from .common import BaseMetadataValidator, ValidationError +from nbformat.notebooknode import NotebookNode + + +class MetadataValidatorV4(BaseMetadataValidator): + + schema_version = 4 + + def __init__(self) -> None: + super().__init__() + self.v1 = MetadataValidatorV1() + self.v2 = MetadataValidatorV2() + self.v3 = MetadataValidatorV3() + + def _upgrade_v3_to_v4(self, cell: NotebookNode) -> NotebookNode: + meta = cell.metadata['nbgrader'] + meta['schema_version'] = self.schema_version + + return cell + + def upgrade_cell_metadata(self, cell: NotebookNode) -> NotebookNode: + if 'nbgrader' not in cell.metadata: + return cell + + if 'schema_version' not in cell.metadata['nbgrader']: + cell.metadata['nbgrader']['schema_version'] = 0 + + if cell.metadata['nbgrader']['schema_version'] == 0: + cell = self.v1._upgrade_v0_to_v1(cell) + if 'nbgrader' not in cell.metadata: + return cell + + if cell.metadata['nbgrader']['schema_version'] == 1: + cell = self.v2._upgrade_v1_to_v2(cell) + + if cell.metadata['nbgrader']['schema_version'] == 2: + cell = self.v3._upgrade_v2_to_v3(cell) + + if cell.metadata['nbgrader']['schema_version'] == 3: + cell = self._upgrade_v3_to_v4(cell) + + self._remove_extra_keys(cell) + return cell + + def validate_cell(self, cell: NotebookNode) -> None: + super(MetadataValidatorV4, self).validate_cell(cell) + + if 'nbgrader' not in cell.metadata: + return + + meta = cell.metadata['nbgrader'] + grade = meta['grade'] + solution = meta['solution'] + locked = meta['locked'] + task = meta.get('task', False) + duplicate = meta.get('duplicate', False) + + # check if the cell type has changed + if 'cell_type' in meta: + if meta['cell_type'] != cell.cell_type: + self.log.warning("Cell type has changed from {} to {}!".format( + meta['cell_type'], cell.cell_type), cell) + + # check for a valid grade id + if grade or solution or locked: + if 'grade_id' not in meta: + raise ValidationError("nbgrader cell does not have a grade_id: {}".format(cell.source)) + if meta['grade_id'] == '': + raise ValidationError("grade_id is empty") + + # check for valid points + if grade: + if 'points' not in meta: + raise ValidationError("nbgrader cell '{}' does not have points".format( + meta['grade_id'])) + + # check that markdown cells are grade AND solution (not either/or) + if not task: + if cell.cell_type == "markdown" and grade and not solution: + raise ValidationError( + "Markdown grade cell '{}' is not marked as a solution cell".format( + meta['grade_id'])) + if cell.cell_type == "markdown" and not grade and solution: + raise ValidationError( + "Markdown solution cell is not marked as a grade cell: {}".format(cell.source)) + else: + if cell.cell_type != "markdown": + raise ValidationError( + "Task cells have to be markdown: {}".format(cell.source)) + + def validate_nb(self, nb: NotebookNode) -> None: + super(MetadataValidatorV4, self).validate_nb(nb) + + ids = set([]) + for cell in nb.cells: + + if 'nbgrader' not in cell.metadata: + continue + + grade = cell.metadata['nbgrader']['grade'] + solution = cell.metadata['nbgrader']['solution'] + locked = cell.metadata['nbgrader']['locked'] + + if not grade and not solution and not locked: + continue + + grade_id = cell.metadata['nbgrader']['grade_id'] + if grade_id in ids: + raise ValidationError("Duplicate grade id: {}".format(grade_id)) + ids.add(grade_id) + + +def read_v4(source: typing.io.TextIO, as_version: int, **kwargs: typing.Any) -> NotebookNode: + nb = _read(source, as_version, **kwargs) + MetadataValidatorV4().validate_nb(nb) + return nb + + +def write_v4(nb: NotebookNode, fp: typing.io.TextIO, **kwargs: typing.Any) -> None: + MetadataValidatorV4().validate_nb(nb) + _write(nb, fp, **kwargs) + + +def reads_v4(source: str, as_version: int, **kwargs: typing.Any) -> NotebookNode: + nb = _reads(source, as_version, **kwargs) + MetadataValidatorV4().validate_nb(nb) + return nb + + +def writes_v4(nb: NotebookNode, **kwargs: typing.Any) -> None: + MetadataValidatorV4().validate_nb(nb) + _writes(nb, **kwargs) diff --git a/nbgrader/tests/apps/files/autotest-hashed-changed.ipynb b/nbgrader/tests/apps/files/autotest-hashed-changed.ipynb index 8ddbb8633..a9e2fd1f9 100644 --- a/nbgrader/tests/apps/files/autotest-hashed-changed.ipynb +++ b/nbgrader/tests/apps/files/autotest-hashed-changed.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -44,7 +44,7 @@ "grade_id": "test_hashed", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-hashed-unchanged.ipynb b/nbgrader/tests/apps/files/autotest-hashed-unchanged.ipynb index 86f485097..d5dd64f0b 100644 --- a/nbgrader/tests/apps/files/autotest-hashed-unchanged.ipynb +++ b/nbgrader/tests/apps/files/autotest-hashed-unchanged.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -42,7 +42,7 @@ "grade_id": "test_hashed", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-hashed.ipynb b/nbgrader/tests/apps/files/autotest-hashed.ipynb index 1b4df7b83..88b494ede 100644 --- a/nbgrader/tests/apps/files/autotest-hashed.ipynb +++ b/nbgrader/tests/apps/files/autotest-hashed.ipynb @@ -15,7 +15,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -37,7 +37,7 @@ "grade_id": "test_hashed", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-hidden-changed-right.ipynb b/nbgrader/tests/apps/files/autotest-hidden-changed-right.ipynb index d17fa7735..cdcb3204f 100644 --- a/nbgrader/tests/apps/files/autotest-hidden-changed-right.ipynb +++ b/nbgrader/tests/apps/files/autotest-hidden-changed-right.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -44,7 +44,7 @@ "grade_id": "test_hidden", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-hidden-changed-wrong.ipynb b/nbgrader/tests/apps/files/autotest-hidden-changed-wrong.ipynb index e84452ef5..a2b134a39 100644 --- a/nbgrader/tests/apps/files/autotest-hidden-changed-wrong.ipynb +++ b/nbgrader/tests/apps/files/autotest-hidden-changed-wrong.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -44,7 +44,7 @@ "grade_id": "test_hidden", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-hidden-unchanged.ipynb b/nbgrader/tests/apps/files/autotest-hidden-unchanged.ipynb index be153756b..3514e3afe 100644 --- a/nbgrader/tests/apps/files/autotest-hidden-unchanged.ipynb +++ b/nbgrader/tests/apps/files/autotest-hidden-unchanged.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -42,7 +42,7 @@ "grade_id": "test_hidden", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-hidden.ipynb b/nbgrader/tests/apps/files/autotest-hidden.ipynb index c2e6e8e23..275169059 100644 --- a/nbgrader/tests/apps/files/autotest-hidden.ipynb +++ b/nbgrader/tests/apps/files/autotest-hidden.ipynb @@ -15,7 +15,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -37,7 +37,7 @@ "grade_id": "test_hidden", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-multi-changed.ipynb b/nbgrader/tests/apps/files/autotest-multi-changed.ipynb index 04470cca4..51693358a 100644 --- a/nbgrader/tests/apps/files/autotest-multi-changed.ipynb +++ b/nbgrader/tests/apps/files/autotest-multi-changed.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -45,7 +45,7 @@ "grade_id": "test_multi", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -83,7 +83,7 @@ "grade_id": "cell-f2803ba7c42d03ab", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } @@ -155,7 +155,7 @@ "grade_id": "cell-693350420ec62f1b", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } @@ -208,7 +208,7 @@ "grade_id": "cell-13479eb0e5fff152", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } diff --git a/nbgrader/tests/apps/files/autotest-multi-unchanged.ipynb b/nbgrader/tests/apps/files/autotest-multi-unchanged.ipynb index 6090389a7..89bcb99d2 100644 --- a/nbgrader/tests/apps/files/autotest-multi-unchanged.ipynb +++ b/nbgrader/tests/apps/files/autotest-multi-unchanged.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "test_multi", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -73,7 +73,7 @@ "grade_id": "cell-f2803ba7c42d03ab", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } @@ -145,7 +145,7 @@ "grade_id": "cell-693350420ec62f1b", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } @@ -198,7 +198,7 @@ "grade_id": "cell-13479eb0e5fff152", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } diff --git a/nbgrader/tests/apps/files/autotest-multi.ipynb b/nbgrader/tests/apps/files/autotest-multi.ipynb index 7488e64df..426ee624e 100644 --- a/nbgrader/tests/apps/files/autotest-multi.ipynb +++ b/nbgrader/tests/apps/files/autotest-multi.ipynb @@ -8,7 +8,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -38,7 +38,7 @@ "grade_id": "test_multi", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -59,7 +59,7 @@ "grade_id": "cell-f2803ba7c42d03ab", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } @@ -93,7 +93,7 @@ "grade_id": "cell-693350420ec62f1b", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } @@ -113,7 +113,7 @@ "grade_id": "cell-13479eb0e5fff152", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } diff --git a/nbgrader/tests/apps/files/autotest-simple-changed.ipynb b/nbgrader/tests/apps/files/autotest-simple-changed.ipynb index d918a0df8..746ad9329 100644 --- a/nbgrader/tests/apps/files/autotest-simple-changed.ipynb +++ b/nbgrader/tests/apps/files/autotest-simple-changed.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -44,7 +44,7 @@ "grade_id": "test_simple", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-simple-unchanged.ipynb b/nbgrader/tests/apps/files/autotest-simple-unchanged.ipynb index 214e5fbbb..85eb38edc 100644 --- a/nbgrader/tests/apps/files/autotest-simple-unchanged.ipynb +++ b/nbgrader/tests/apps/files/autotest-simple-unchanged.ipynb @@ -18,7 +18,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -42,7 +42,7 @@ "grade_id": "test_simple", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/autotest-simple.ipynb b/nbgrader/tests/apps/files/autotest-simple.ipynb index 42c87b071..9384733a4 100644 --- a/nbgrader/tests/apps/files/autotest-simple.ipynb +++ b/nbgrader/tests/apps/files/autotest-simple.ipynb @@ -15,7 +15,7 @@ "grade": false, "grade_id": "soln", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -37,7 +37,7 @@ "grade_id": "test_simple", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/open_relative_file.ipynb b/nbgrader/tests/apps/files/open_relative_file.ipynb index 2e5a8bbe0..794c8f233 100644 --- a/nbgrader/tests/apps/files/open_relative_file.ipynb +++ b/nbgrader/tests/apps/files/open_relative_file.ipynb @@ -12,7 +12,7 @@ "grade_id": "open_file", "locked": true, "points": 10, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/submitted-changed.ipynb b/nbgrader/tests/apps/files/submitted-changed.ipynb index ffc9ef3fe..59713398a 100644 --- a/nbgrader/tests/apps/files/submitted-changed.ipynb +++ b/nbgrader/tests/apps/files/submitted-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -57,7 +57,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -97,7 +97,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -118,7 +118,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/submitted-cheat-attempt-alternative.ipynb b/nbgrader/tests/apps/files/submitted-cheat-attempt-alternative.ipynb index 1632063c0..d663831c0 100644 --- a/nbgrader/tests/apps/files/submitted-cheat-attempt-alternative.ipynb +++ b/nbgrader/tests/apps/files/submitted-cheat-attempt-alternative.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -36,7 +36,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -58,7 +58,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -78,7 +78,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -98,7 +98,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -119,7 +119,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -137,7 +137,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/submitted-cheat-attempt.ipynb b/nbgrader/tests/apps/files/submitted-cheat-attempt.ipynb index 8564ca2c8..b11ce40d6 100644 --- a/nbgrader/tests/apps/files/submitted-cheat-attempt.ipynb +++ b/nbgrader/tests/apps/files/submitted-cheat-attempt.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -36,7 +36,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -58,7 +58,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -78,7 +78,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -98,7 +98,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -119,7 +119,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -137,7 +137,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/submitted-grade-cell-changed.ipynb b/nbgrader/tests/apps/files/submitted-grade-cell-changed.ipynb index ae085eeb4..029c13b77 100644 --- a/nbgrader/tests/apps/files/submitted-grade-cell-changed.ipynb +++ b/nbgrader/tests/apps/files/submitted-grade-cell-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -57,7 +57,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -97,7 +97,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -118,7 +118,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/submitted-locked-cell-changed.ipynb b/nbgrader/tests/apps/files/submitted-locked-cell-changed.ipynb index 7aa6621c4..9bfda3ef0 100644 --- a/nbgrader/tests/apps/files/submitted-locked-cell-changed.ipynb +++ b/nbgrader/tests/apps/files/submitted-locked-cell-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -57,7 +57,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -97,7 +97,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -118,7 +118,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/submitted-unchanged.ipynb b/nbgrader/tests/apps/files/submitted-unchanged.ipynb index ce8770c58..f0b86a26a 100644 --- a/nbgrader/tests/apps/files/submitted-unchanged.ipynb +++ b/nbgrader/tests/apps/files/submitted-unchanged.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -57,7 +57,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -97,7 +97,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -118,7 +118,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/test-hidden-tests.ipynb b/nbgrader/tests/apps/files/test-hidden-tests.ipynb index 7235d0546..786039069 100644 --- a/nbgrader/tests/apps/files/test-hidden-tests.ipynb +++ b/nbgrader/tests/apps/files/test-hidden-tests.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -36,7 +36,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -87,7 +87,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -112,7 +112,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -154,7 +154,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -201,7 +201,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -226,7 +226,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/test-v3.ipynb b/nbgrader/tests/apps/files/test-v3.ipynb new file mode 100644 index 000000000..c070f6e38 --- /dev/null +++ b/nbgrader/tests/apps/files/test-v3.ipynb @@ -0,0 +1,300 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "jupyter", + "locked": true, + "schema_version": 3, + "solution": false + } + }, + "source": [ + "For this problem set, we'll be using the Jupyter notebook:\n", + "\n", + "![](jupyter.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Part A (2 points)\n", + "\n", + "Write a function that returns a list of numbers, such that $x_i=i^2$, for $1\\leq i \\leq n$. Make sure it handles the case where $n<1$ by raising a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "nbgrader": { + "grade": false, + "grade_id": "squares", + "locked": false, + "schema_version": 3, + "solution": true + } + }, + "outputs": [], + "source": [ + "def squares(n):\n", + " \"\"\"Compute the squares of numbers from 1 to n, such that the \n", + " ith element of the returned list equals i^2.\n", + " \n", + " \"\"\"\n", + " ### BEGIN SOLUTION\n", + " if n < 1:\n", + " raise ValueError(\"n must be greater than or equal to 1\")\n", + " return [i ** 2 for i in range(1, n + 1)]\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Your function should print `[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]` for $n=10$. Check that it does:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "squares(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "nbgrader": { + "grade": true, + "grade_id": "correct_squares", + "locked": false, + "points": 1.0, + "schema_version": 3, + "solution": false + } + }, + "outputs": [], + "source": [ + "\"\"\"Check that squares returns the correct output for several inputs\"\"\"\n", + "assert squares(1) == [1]\n", + "assert squares(2) == [1, 4]\n", + "assert squares(10) == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]\n", + "assert squares(11) == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "nbgrader": { + "grade": true, + "grade_id": "squares_invalid_input", + "locked": false, + "points": 1.0, + "schema_version": 3, + "solution": false + } + }, + "outputs": [], + "source": [ + "\"\"\"Check that squares raises an error for invalid inputs\"\"\"\n", + "try:\n", + " squares(0)\n", + "except ValueError:\n", + " pass\n", + "else:\n", + " raise AssertionError(\"did not raise\")\n", + "\n", + "try:\n", + " squares(-4)\n", + "except ValueError:\n", + " pass\n", + "else:\n", + " raise AssertionError(\"did not raise\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Part B (1 point)\n", + "\n", + "Using your `squares` function, write a function that computes the sum of the squares of the numbers from 1 to $n$. Your function should call the `squares` function -- it should NOT reimplement its functionality." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "nbgrader": { + "grade": false, + "grade_id": "sum_of_squares", + "locked": false, + "schema_version": 3, + "solution": true + } + }, + "outputs": [], + "source": [ + "def sum_of_squares(n):\n", + " \"\"\"Compute the sum of the squares of numbers from 1 to n.\"\"\"\n", + " ### BEGIN SOLUTION\n", + " return sum(squares(n))\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The sum of squares from 1 to 10 should be 385. Verify that this is the answer you get:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "sum_of_squares(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "nbgrader": { + "grade": true, + "grade_id": "correct_sum_of_squares", + "locked": false, + "points": 0.5, + "schema_version": 3, + "solution": false + } + }, + "outputs": [], + "source": [ + "\"\"\"Check that sum_of_squares returns the correct answer for various inputs.\"\"\"\n", + "assert sum_of_squares(1) == 1\n", + "assert sum_of_squares(2) == 5\n", + "assert sum_of_squares(10) == 385\n", + "assert sum_of_squares(11) == 506" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "nbgrader": { + "grade": true, + "grade_id": "sum_of_squares_uses_squares", + "locked": false, + "points": 0.5, + "schema_version": 3, + "solution": false + } + }, + "outputs": [], + "source": [ + "\"\"\"Check that sum_of_squares relies on squares.\"\"\"\n", + "orig_squares = squares\n", + "del squares\n", + "try:\n", + " sum_of_squares(1)\n", + "except NameError:\n", + " pass\n", + "else:\n", + " raise AssertionError(\"sum_of_squares does not use squares\")\n", + "finally:\n", + " squares = orig_squares" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Part C (1 point)\n", + "\n", + "Using LaTeX math notation, write out the equation that is implemented by your `sum_of_squares` function." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "sum_of_squares_equation", + "locked": false, + "points": 1.0, + "schema_version": 3, + "solution": true + } + }, + "source": [ + "$\\sum_{i=1}^n i^2$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Part D (2 points)\n", + "\n", + "Find a usecase for your `sum_of_squares` function and implement that usecase in the cell below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "nbgrader": { + "grade": true, + "grade_id": "sum_of_squares_application", + "locked": false, + "points": 2.0, + "schema_version": 3, + "solution": true + } + }, + "outputs": [], + "source": [ + "def pyramidal_number(n):\n", + " \"\"\"Returns the n^th pyramidal number\"\"\"\n", + " return sum_of_squares(n)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python", + "language": "python", + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/nbgrader/tests/apps/files/test-with-output.ipynb b/nbgrader/tests/apps/files/test-with-output.ipynb index a9cbf7cce..c37701953 100644 --- a/nbgrader/tests/apps/files/test-with-output.ipynb +++ b/nbgrader/tests/apps/files/test-with-output.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -36,7 +36,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -93,7 +93,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -116,7 +116,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -158,7 +158,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -210,7 +210,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -233,7 +233,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -270,7 +270,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -298,7 +298,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/apps/files/test.ipynb b/nbgrader/tests/apps/files/test.ipynb index c070f6e38..890159d56 100644 --- a/nbgrader/tests/apps/files/test.ipynb +++ b/nbgrader/tests/apps/files/test.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -36,7 +36,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -82,7 +82,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -105,7 +105,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -147,7 +147,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -188,7 +188,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -211,7 +211,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -248,7 +248,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -276,7 +276,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/apps/files/timeout.ipynb b/nbgrader/tests/apps/files/timeout.ipynb index 1ac989b83..e29713cce 100644 --- a/nbgrader/tests/apps/files/timeout.ipynb +++ b/nbgrader/tests/apps/files/timeout.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -36,7 +36,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/apps/files/validating-environment-variable.ipynb b/nbgrader/tests/apps/files/validating-environment-variable.ipynb index ebd35c03b..cdf932e99 100644 --- a/nbgrader/tests/apps/files/validating-environment-variable.ipynb +++ b/nbgrader/tests/apps/files/validating-environment-variable.ipynb @@ -18,7 +18,7 @@ "grade_id": "cell-bfbdf1784663b6b6", "locked": true, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false } diff --git a/nbgrader/tests/apps/files/validation-zero-points.ipynb b/nbgrader/tests/apps/files/validation-zero-points.ipynb index 3fc7a8a1a..0c459b1ef 100644 --- a/nbgrader/tests/apps/files/validation-zero-points.ipynb +++ b/nbgrader/tests/apps/files/validation-zero-points.ipynb @@ -17,7 +17,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -39,7 +39,7 @@ "grade_id": "correct_squares", "locked": true, "points": 0, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": false }, diff --git a/nbgrader/tests/apps/test_api.py b/nbgrader/tests/apps/test_api.py index 8b989ffe1..6c48aa39c 100644 --- a/nbgrader/tests/apps/test_api.py +++ b/nbgrader/tests/apps/test_api.py @@ -787,7 +787,8 @@ def test_release_feedback(self, api, course_dir, db, exchange): api.generate_feedback("ps2", "foo") result = api.release_feedback("ps2", "foo") assert result["success"] - assert os.path.exists(join(exchange, "abc101", "feedback", "6a0713045d217697b2ae0ab6b49fa1fe.html")) + print(os.listdir(join(exchange, "abc101", "feedback"))) + assert os.path.exists(join(exchange, "abc101", "feedback", "a2eea9fae1cfd3376a5ed6b7aac0bdc2.html")) @notwindows def test_fetch_feedback(self, api, course_dir, db, cache): diff --git a/nbgrader/tests/nbgraderformat/test_v3.py b/nbgrader/tests/nbgraderformat/test_v3.py index 49062f132..fea9f9b82 100644 --- a/nbgrader/tests/nbgraderformat/test_v3.py +++ b/nbgrader/tests/nbgraderformat/test_v3.py @@ -131,20 +131,20 @@ def test_task_value(): def test_read(): currdir = os.path.split(__file__)[0] - path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + path = os.path.join(currdir, "..", "apps", "files", "test-v3.ipynb") read_v3(path, current_nbformat) def test_reads(): currdir = os.path.split(__file__)[0] - path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + path = os.path.join(currdir, "..", "apps", "files", "test-v3.ipynb") contents = open(path, "r").read() reads_v3(contents, current_nbformat) def test_write(): currdir = os.path.split(__file__)[0] - path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + path = os.path.join(currdir, "..", "apps", "files", "test-v3.ipynb") nb = read_v3(path, current_nbformat) with tempfile.TemporaryFile(mode="w") as fh: write_v3(nb, fh) @@ -152,7 +152,7 @@ def test_write(): def test_writes(): currdir = os.path.split(__file__)[0] - path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + path = os.path.join(currdir, "..", "apps", "files", "test-v3.ipynb") nb = read_v3(path, current_nbformat) writes_v3(nb) @@ -166,7 +166,7 @@ def test_too_old(): def test_too_new(): currdir = os.path.split(__file__)[0] - path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + path = os.path.join(currdir, "..", "apps", "files", "test-v3.ipynb") nb = read_v3(path, current_nbformat) for cell in nb.cells: if hasattr(cell.metadata, "nbgrader"): diff --git a/nbgrader/tests/nbgraderformat/test_v4.py b/nbgrader/tests/nbgraderformat/test_v4.py new file mode 100644 index 000000000..162484ac5 --- /dev/null +++ b/nbgrader/tests/nbgraderformat/test_v4.py @@ -0,0 +1,286 @@ +import json +import os +import pytest +import tempfile +from nbformat import current_nbformat, read +from nbformat.v4 import new_notebook +from ...nbgraderformat.common import SchemaMismatchError, ValidationError +from ...nbgraderformat.v4 import ( + MetadataValidatorV4, read_v4, reads_v4, write_v4, writes_v4) +from .. import ( + create_code_cell, + create_grade_cell, + create_solution_cell, + create_task_cell, + create_regular_cell) + + +def test_set_false(): + cell = create_grade_cell("", "code", "foo", 2, 0) + del cell.metadata.nbgrader["solution"] + del cell.metadata.nbgrader["locked"] + + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert not cell.metadata.nbgrader["solution"] + assert not cell.metadata.nbgrader["locked"] + + cell = create_solution_cell("", "code", "foo", 0) + del cell.metadata.nbgrader["grade"] + del cell.metadata.nbgrader["locked"] + + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert not cell.metadata.nbgrader["grade"] + assert not cell.metadata.nbgrader["locked"] + + +def test_remove_metadata(): + cell = create_solution_cell("", "code", "foo", 0) + cell.metadata.nbgrader["solution"] = False + + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "nbgrader" not in cell.metadata + + +def test_remove_points(): + cell = create_solution_cell("", "code", "foo", 0) + cell.metadata.nbgrader["points"] = 2 + + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "points" not in cell.metadata.nbgrader + + +def test_set_points(): + cell = create_grade_cell("", "code", "foo", "", 0) + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader["points"] == 0.0 + + cell = create_grade_cell("", "code", "foo", "1.5", 0) + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader["points"] == 1.5 + + cell = create_grade_cell("", "code", "foo", 1, 0) + del cell.metadata.nbgrader["points"] + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader["points"] == 0.0 + + cell = create_grade_cell("", "code", "foo", -1, 0) + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader["points"] == 0.0 + + +def test_extra_keys(): + cell = create_grade_cell("", "code", "foo", "", 0) + cell.metadata.nbgrader["foo"] = "bar" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "foo" not in cell.metadata.nbgrader + + cell = create_grade_cell("", "code", "foo", "", 1) + cell.metadata.nbgrader["foo"] = "bar" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "foo" not in cell.metadata.nbgrader + + cell = create_grade_cell("", "code", "foo", "", 2) + cell.metadata.nbgrader["foo"] = "bar" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "foo" not in cell.metadata.nbgrader + + cell = create_grade_cell("", "code", "foo", "", 3) + cell.metadata.nbgrader["foo"] = "bar" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "foo" not in cell.metadata.nbgrader + + +def test_schema_version(): + cell = create_grade_cell("", "code", "foo", "", 0) + del cell.metadata.nbgrader["schema_version"] + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader["schema_version"] == 4 + + +def test_cell_type(): + cell = create_grade_cell("", "code", "foo", "", 0) + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert "cell_type" not in cell.metadata.nbgrader + + cell = create_grade_cell("", "code", "foo", "", 0) + cell.metadata.nbgrader["checksum"] = "abcd" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader['cell_type'] == "code" + + cell = create_grade_cell("", "code", "foo", "", 0) + cell.metadata.nbgrader["checksum"] = "abcd" + cell.metadata.nbgrader["cell_type"] = "markdown" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader['cell_type'] == "markdown" + + cell = create_grade_cell("", "code", "foo", "", 0) + cell.metadata.nbgrader["checksum"] = "abcd" + cell.metadata.nbgrader["cell_type"] = "code" + MetadataValidatorV4().upgrade_cell_metadata(cell) + assert cell.metadata.nbgrader['cell_type'] == "code" + + +def test_task_value(): + cell = create_task_cell("this is a task cell", "markdown", "foo", 0) + assert cell.metadata.nbgrader['task'] + + cell = create_grade_cell("", "code", "foo", "") + assert not cell.metadata.nbgrader['task'] + + cell = create_grade_cell("", "code", "foo", "") + assert not cell.metadata.nbgrader['task'] + + cell = create_solution_cell("", "code", "foo") + assert not cell.metadata.nbgrader['task'] + + +def test_read(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + read_v4(path, current_nbformat) + + +def test_reads(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + contents = open(path, "r").read() + reads_v4(contents, current_nbformat) + + +def test_write(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + nb = read_v4(path, current_nbformat) + with tempfile.TemporaryFile(mode="w") as fh: + write_v4(nb, fh) + + +def test_writes(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + nb = read_v4(path, current_nbformat) + writes_v4(nb) + + +def test_too_old(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test-v0.ipynb") + with pytest.raises(SchemaMismatchError): + read_v4(path, current_nbformat) + + +def test_too_new(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test.ipynb") + nb = read_v4(path, current_nbformat) + for cell in nb.cells: + if hasattr(cell.metadata, "nbgrader"): + cell.metadata.nbgrader.schema_version += 1 + nb = json.dumps(nb) + with pytest.raises(SchemaMismatchError): + reads_v4(nb, current_nbformat) + + +def test_upgrade_notebook_metadata(): + currdir = os.path.split(__file__)[0] + path = os.path.join(currdir, "..", "apps", "files", "test-v0.ipynb") + with open(path, "r") as fh: + nb = read(fh, current_nbformat) + nb = MetadataValidatorV4().upgrade_notebook_metadata(nb) + + +def test_upgrade_cell_metadata(): + cell = create_grade_cell("", "code", "foo", 5, 0) + MetadataValidatorV4().upgrade_cell_metadata(cell) + + cell = create_grade_cell("", "code", "foo", 5, 4) + MetadataValidatorV4().upgrade_cell_metadata(cell) + + cell = create_grade_cell("", "code", "foo", 5, 10) + MetadataValidatorV4().upgrade_cell_metadata(cell) + + +def test_regular_cells(): + validator = MetadataValidatorV4() + + # code cell without nbgrader metadata + cell = create_code_cell() + validator.validate_cell(cell) + validator.upgrade_cell_metadata(cell) + + # code cell with metadata, but not an nbgrader cell + cell = create_regular_cell("", "code", schema_version=4) + validator.validate_cell(cell) + + nb = new_notebook() + cell1 = create_code_cell() + cell2 = create_regular_cell("", "code", schema_version=4) + nb.cells = [cell1, cell2] + validator.validate_nb(nb) + + +def test_invalid_metadata(): + validator = MetadataValidatorV4() + + # make sure the default cell works ok + cell = create_grade_cell("", "code", "foo", 5, 4) + validator.validate_cell(cell) + + # missing grade_id + cell = create_grade_cell("", "code", "foo", 5, 4) + del cell.metadata.nbgrader["grade_id"] + with pytest.raises(ValidationError): + validator.validate_cell(cell) + + # grade_id is empty + cell = create_grade_cell("", "code", "", 5, 4) + del cell.metadata.nbgrader["task"] + with pytest.raises(ValidationError): + validator.validate_cell(cell) + + # missing points + cell = create_grade_cell("", "code", "foo", 5, 4) + del cell.metadata.nbgrader["points"] + with pytest.raises(ValidationError): + validator.validate_cell(cell) + + # markdown grade cell not marked as a solution cell + cell = create_grade_cell("", "markdown", "foo", 5, 4) + with pytest.raises(ValidationError): + validator.validate_cell(cell) + + # markdown solution cell not marked as a grade cell + cell = create_solution_cell("", "markdown", "foo", 4) + with pytest.raises(ValidationError): + validator.validate_cell(cell) + + # task cell shouldn't be a code cell + cell = create_task_cell("", "markdown", "foo", 5, 4) + validator.validate_cell(cell) + + # task cell shouldn't be able to be a code cell + cell = create_task_cell("", "code", "foo", 5, 4) + with pytest.raises(ValidationError): + validator.validate_cell(cell) + + +def test_duplicate_cells(): + validator = MetadataValidatorV4() + nb = new_notebook() + cell1 = create_grade_cell("", "code", "foo", 5, 4) + cell2 = create_grade_cell("", "code", "foo", 5, 4) + nb.cells = [cell1, cell2] + with pytest.raises(ValidationError): + validator.validate_nb(nb) + + +def test_celltype_changed(caplog): + cell = create_solution_cell("", "code", "foo", 4) + cell.metadata.nbgrader["cell_type"] = "code" + MetadataValidatorV4().validate_cell(cell) + assert "Cell type has changed from markdown to code!" not in caplog.text + + cell = create_solution_cell("", "code", "foo", 4) + cell.metadata.nbgrader["cell_type"] = "markdown" + MetadataValidatorV4().validate_cell(cell) + assert "Cell type has changed from markdown to code!" in caplog.text diff --git a/nbgrader/tests/preprocessors/files/bad-markdown-cell-1.ipynb b/nbgrader/tests/preprocessors/files/bad-markdown-cell-1.ipynb index 45807f8bb..2e4afd1d1 100644 --- a/nbgrader/tests/preprocessors/files/bad-markdown-cell-1.ipynb +++ b/nbgrader/tests/preprocessors/files/bad-markdown-cell-1.ipynb @@ -9,7 +9,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/bad-markdown-cell-2.ipynb b/nbgrader/tests/preprocessors/files/bad-markdown-cell-2.ipynb index e33b5f317..5199d7721 100644 --- a/nbgrader/tests/preprocessors/files/bad-markdown-cell-2.ipynb +++ b/nbgrader/tests/preprocessors/files/bad-markdown-cell-2.ipynb @@ -7,7 +7,7 @@ "nbgrader": { "grade": false, "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/preprocessors/files/blank-grade-id.ipynb b/nbgrader/tests/preprocessors/files/blank-grade-id.ipynb index 7e307227d..c8d923447 100644 --- a/nbgrader/tests/preprocessors/files/blank-grade-id.ipynb +++ b/nbgrader/tests/preprocessors/files/blank-grade-id.ipynb @@ -10,7 +10,7 @@ "grade_id": "", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/blank-points.ipynb b/nbgrader/tests/preprocessors/files/blank-points.ipynb index b455b122f..ac9a24fbb 100644 --- a/nbgrader/tests/preprocessors/files/blank-points.ipynb +++ b/nbgrader/tests/preprocessors/files/blank-points.ipynb @@ -9,7 +9,7 @@ "grade": true, "grade_id": "id1", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/cell-type-changed.ipynb b/nbgrader/tests/preprocessors/files/cell-type-changed.ipynb index 62b1dd76c..41a626461 100644 --- a/nbgrader/tests/preprocessors/files/cell-type-changed.ipynb +++ b/nbgrader/tests/preprocessors/files/cell-type-changed.ipynb @@ -9,7 +9,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/duplicate-grade-ids.ipynb b/nbgrader/tests/preprocessors/files/duplicate-grade-ids.ipynb index 16b493def..cf8197dec 100644 --- a/nbgrader/tests/preprocessors/files/duplicate-grade-ids.ipynb +++ b/nbgrader/tests/preprocessors/files/duplicate-grade-ids.ipynb @@ -10,7 +10,7 @@ "grade_id": "id1", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -35,7 +35,7 @@ "grade_id": "id1", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/preprocessors/files/manually-graded-code-cell.ipynb b/nbgrader/tests/preprocessors/files/manually-graded-code-cell.ipynb index 0a3215fc2..f31ccc327 100644 --- a/nbgrader/tests/preprocessors/files/manually-graded-code-cell.ipynb +++ b/nbgrader/tests/preprocessors/files/manually-graded-code-cell.ipynb @@ -10,7 +10,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/preprocessors/files/no-cell-type.ipynb b/nbgrader/tests/preprocessors/files/no-cell-type.ipynb index b79936077..5314beb67 100644 --- a/nbgrader/tests/preprocessors/files/no-cell-type.ipynb +++ b/nbgrader/tests/preprocessors/files/no-cell-type.ipynb @@ -8,7 +8,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/submitted-answer-cell-type-changed.ipynb b/nbgrader/tests/preprocessors/files/submitted-answer-cell-type-changed.ipynb index 307a83266..0b8b39ae4 100644 --- a/nbgrader/tests/preprocessors/files/submitted-answer-cell-type-changed.ipynb +++ b/nbgrader/tests/preprocessors/files/submitted-answer-cell-type-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -57,7 +57,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -97,7 +97,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -118,7 +118,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/submitted-changed.ipynb b/nbgrader/tests/preprocessors/files/submitted-changed.ipynb index a07a2892c..b0ae6398b 100644 --- a/nbgrader/tests/preprocessors/files/submitted-changed.ipynb +++ b/nbgrader/tests/preprocessors/files/submitted-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -65,7 +65,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -85,7 +85,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -105,7 +105,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -126,7 +126,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -152,7 +152,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/submitted-grade-cell-changed.ipynb b/nbgrader/tests/preprocessors/files/submitted-grade-cell-changed.ipynb index 45e4d5345..08b8c48a6 100644 --- a/nbgrader/tests/preprocessors/files/submitted-grade-cell-changed.ipynb +++ b/nbgrader/tests/preprocessors/files/submitted-grade-cell-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -47,7 +47,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -97,7 +97,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -117,7 +117,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -150,7 +150,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -176,7 +176,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/submitted-grade-cell-type-changed.ipynb b/nbgrader/tests/preprocessors/files/submitted-grade-cell-type-changed.ipynb index 790d4a150..d3314ec8b 100644 --- a/nbgrader/tests/preprocessors/files/submitted-grade-cell-type-changed.ipynb +++ b/nbgrader/tests/preprocessors/files/submitted-grade-cell-type-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -35,7 +35,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -57,7 +57,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -97,7 +97,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -118,7 +118,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/submitted-locked-cell-changed.ipynb b/nbgrader/tests/preprocessors/files/submitted-locked-cell-changed.ipynb index 7fe46e6c7..09857ccb2 100644 --- a/nbgrader/tests/preprocessors/files/submitted-locked-cell-changed.ipynb +++ b/nbgrader/tests/preprocessors/files/submitted-locked-cell-changed.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -47,7 +47,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -109,7 +109,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -129,7 +129,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -162,7 +162,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -180,7 +180,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/submitted-unchanged.ipynb b/nbgrader/tests/preprocessors/files/submitted-unchanged.ipynb index 2d377888d..27a455cfb 100644 --- a/nbgrader/tests/preprocessors/files/submitted-unchanged.ipynb +++ b/nbgrader/tests/preprocessors/files/submitted-unchanged.ipynb @@ -12,7 +12,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -47,7 +47,7 @@ "grade_id": "foo", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -77,7 +77,7 @@ "grade_id": "bar", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -109,7 +109,7 @@ "grade_id": "baz", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -129,7 +129,7 @@ "grade_id": "quux", "locked": false, "points": 3.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -162,7 +162,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -188,7 +188,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/preprocessors/files/test.ipynb b/nbgrader/tests/preprocessors/files/test.ipynb index 939251cec..be8e3e652 100644 --- a/nbgrader/tests/preprocessors/files/test.ipynb +++ b/nbgrader/tests/preprocessors/files/test.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -36,7 +36,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -82,7 +82,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -106,7 +106,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -138,7 +138,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -179,7 +179,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -202,7 +202,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -237,7 +237,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -265,7 +265,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2.0, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/preprocessors/files/test_taskcell.ipynb b/nbgrader/tests/preprocessors/files/test_taskcell.ipynb index e6565658d..856dce0a1 100644 --- a/nbgrader/tests/preprocessors/files/test_taskcell.ipynb +++ b/nbgrader/tests/preprocessors/files/test_taskcell.ipynb @@ -7,7 +7,7 @@ "grade": false, "grade_id": "jupyter", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -25,7 +25,7 @@ "grade_id": "task1", "locked": true, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": false, "task": true } @@ -59,7 +59,7 @@ "grade": false, "grade_id": "squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -102,7 +102,7 @@ "grade_id": "correct_squares", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -125,7 +125,7 @@ "grade_id": "squares_invalid_input", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -156,7 +156,7 @@ "grade": false, "grade_id": "sum_of_squares", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -194,7 +194,7 @@ "grade_id": "correct_sum_of_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -216,7 +216,7 @@ "grade_id": "sum_of_squares_uses_squares", "locked": false, "points": 0.5, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -251,7 +251,7 @@ "grade_id": "sum_of_squares_equation", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -279,7 +279,7 @@ "grade_id": "sum_of_squares_application", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, diff --git a/nbgrader/tests/ui-tests/files/open_relative_file.ipynb b/nbgrader/tests/ui-tests/files/open_relative_file.ipynb index 9c2028ae0..ee5f68b74 100644 --- a/nbgrader/tests/ui-tests/files/open_relative_file.ipynb +++ b/nbgrader/tests/ui-tests/files/open_relative_file.ipynb @@ -11,7 +11,7 @@ "grade_id": "open_file", "locked": true, "points": 10, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] diff --git a/nbgrader/tests/ui-tests/files/submitted-answer-cell-type-changed.ipynb b/nbgrader/tests/ui-tests/files/submitted-answer-cell-type-changed.ipynb index 68e9f7001..7d96ae5f0 100644 --- a/nbgrader/tests/ui-tests/files/submitted-answer-cell-type-changed.ipynb +++ b/nbgrader/tests/ui-tests/files/submitted-answer-cell-type-changed.ipynb @@ -10,7 +10,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -31,7 +31,7 @@ "grade_id": "foo", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -53,7 +53,7 @@ "grade_id": "bar", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -74,7 +74,7 @@ "grade_id": "baz", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -93,7 +93,7 @@ "grade_id": "quux", "locked": false, "points": 3, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -114,7 +114,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -133,7 +133,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/ui-tests/files/submitted-changed.ipynb b/nbgrader/tests/ui-tests/files/submitted-changed.ipynb index c07f912fd..49318317c 100644 --- a/nbgrader/tests/ui-tests/files/submitted-changed.ipynb +++ b/nbgrader/tests/ui-tests/files/submitted-changed.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -34,7 +34,7 @@ "grade_id": "foo", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -56,7 +56,7 @@ "grade_id": "bar", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -96,7 +96,7 @@ "grade_id": "quux", "locked": false, "points": 3, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -117,7 +117,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/ui-tests/files/submitted-grade-cell-changed.ipynb b/nbgrader/tests/ui-tests/files/submitted-grade-cell-changed.ipynb index deb502980..2139eccab 100644 --- a/nbgrader/tests/ui-tests/files/submitted-grade-cell-changed.ipynb +++ b/nbgrader/tests/ui-tests/files/submitted-grade-cell-changed.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -34,7 +34,7 @@ "grade_id": "foo", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -56,7 +56,7 @@ "grade_id": "bar", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -96,7 +96,7 @@ "grade_id": "quux", "locked": false, "points": 3, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -117,7 +117,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/ui-tests/files/submitted-grade-cell-type-changed.ipynb b/nbgrader/tests/ui-tests/files/submitted-grade-cell-type-changed.ipynb index 33c4246e6..2908d6c90 100644 --- a/nbgrader/tests/ui-tests/files/submitted-grade-cell-type-changed.ipynb +++ b/nbgrader/tests/ui-tests/files/submitted-grade-cell-type-changed.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -34,7 +34,7 @@ "grade_id": "foo", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -55,7 +55,7 @@ "grade_id": "bar", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false } }, @@ -74,7 +74,7 @@ "grade_id": "baz", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -93,7 +93,7 @@ "grade_id": "quux", "locked": false, "points": 3, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -114,7 +114,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -133,7 +133,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/ui-tests/files/submitted-locked-cell-changed.ipynb b/nbgrader/tests/ui-tests/files/submitted-locked-cell-changed.ipynb index 3246bc025..56d690a25 100644 --- a/nbgrader/tests/ui-tests/files/submitted-locked-cell-changed.ipynb +++ b/nbgrader/tests/ui-tests/files/submitted-locked-cell-changed.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -34,7 +34,7 @@ "grade_id": "foo", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -56,7 +56,7 @@ "grade_id": "bar", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -96,7 +96,7 @@ "grade_id": "quux", "locked": false, "points": 3, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -117,7 +117,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/nbgrader/tests/ui-tests/files/submitted-unchanged.ipynb b/nbgrader/tests/ui-tests/files/submitted-unchanged.ipynb index c60cd516e..1b4da8f39 100644 --- a/nbgrader/tests/ui-tests/files/submitted-unchanged.ipynb +++ b/nbgrader/tests/ui-tests/files/submitted-unchanged.ipynb @@ -11,7 +11,7 @@ "grade": false, "grade_id": "set_a", "locked": false, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -34,7 +34,7 @@ "grade_id": "foo", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -56,7 +56,7 @@ "grade_id": "bar", "locked": false, "points": 1, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -77,7 +77,7 @@ "grade_id": "baz", "locked": false, "points": 2, - "schema_version": 3, + "schema_version": 4, "solution": true } }, @@ -96,7 +96,7 @@ "grade_id": "quux", "locked": false, "points": 3, - "schema_version": 3, + "schema_version": 4, "solution": true }, "tags": [] @@ -117,7 +117,7 @@ "grade": false, "grade_id": "ro1", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false }, "tags": [] @@ -136,7 +136,7 @@ "grade": false, "grade_id": "ro2", "locked": true, - "schema_version": 3, + "schema_version": 4, "solution": false } }, diff --git a/src/create_assignment/create_assignment_model.ts b/src/create_assignment/create_assignment_model.ts index 48efd47e9..3450cb8f3 100644 --- a/src/create_assignment/create_assignment_model.ts +++ b/src/create_assignment/create_assignment_model.ts @@ -7,7 +7,7 @@ import { } from '@lumino/coreutils'; const NBGRADER_KEY = 'nbgrader'; -export const NBGRADER_SCHEMA_VERSION = 3; +export const NBGRADER_SCHEMA_VERSION = 4; /** * A namespace for conversions between {@link NbgraderMetadata} and From 112950aaed4cda32bd90ca054626f77c9f7e7787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20Ba=C5=9Far=20K=C3=B6se?= Date: Wed, 8 Mar 2023 09:05:08 +0200 Subject: [PATCH 4/6] Un-reverse DeduplicateIds order --- nbgrader/converters/base.py | 35 +++++++++++-------- nbgrader/preprocessors/deduplicateids.py | 6 ---- .../preprocessors/test_deduplicateids.py | 12 +++---- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/nbgrader/converters/base.py b/nbgrader/converters/base.py index 166386cc0..3d9632866 100644 --- a/nbgrader/converters/base.py +++ b/nbgrader/converters/base.py @@ -387,25 +387,32 @@ def _handle_failure(gd: typing.Dict[str, str]) -> None: # convert all the notebooks for notebook_filename in self.notebooks: - self.convert_single_notebook(notebook_filename) + try: + self.convert_single_notebook(notebook_filename) + + # Exceptions that shouldn't interrupt the entire conversion process should go here + # Those that should go in outer try/except + except UnresponsiveKernelError: + self.log.error( + "While processing assignment %s, the kernel became " + "unresponsive and we could not interrupt it. This probably " + "means that the students' code has an infinite loop that " + "consumes a lot of memory or something similar. nbgrader " + "doesn't know how to deal with this problem, so you will " + "have to manually edit the students' code (for example, to " + "just throw an error rather than enter an infinite loop). ", + assignment) + errors.append((gd['assignment_id'], gd['student_id'])) + _handle_failure(gd) + + except Exception as e: + raise e # set assignment permissions self.set_permissions(gd['assignment_id'], gd['student_id']) self.run_post_convert_hook() - except UnresponsiveKernelError: - self.log.error( - "While processing assignment %s, the kernel became " - "unresponsive and we could not interrupt it. This probably " - "means that the students' code has an infinite loop that " - "consumes a lot of memory or something similar. nbgrader " - "doesn't know how to deal with this problem, so you will " - "have to manually edit the students' code (for example, to " - "just throw an error rather than enter an infinite loop). ", - assignment) - errors.append((gd['assignment_id'], gd['student_id'])) - _handle_failure(gd) - + # Exceptions that should interrupt the entire conversion go here except sqlalchemy.exc.OperationalError: _handle_failure(gd) self.log.error(traceback.format_exc()) diff --git a/nbgrader/preprocessors/deduplicateids.py b/nbgrader/preprocessors/deduplicateids.py index 900bede8b..9251ab61e 100644 --- a/nbgrader/preprocessors/deduplicateids.py +++ b/nbgrader/preprocessors/deduplicateids.py @@ -12,15 +12,9 @@ def preprocess(self, nb: NotebookNode, resources: ResourcesDict) -> Tuple[Notebo # keep track of grade ids encountered so far self.grade_ids = set([]) - # reverse cell order - nb.cells = nb.cells[::-1] - # process each cell in reverse order nb, resources = super(DeduplicateIds, self).preprocess(nb, resources) - # unreverse cell order - nb.cells = nb.cells[::-1] - return nb, resources def preprocess_cell(self, diff --git a/nbgrader/tests/preprocessors/test_deduplicateids.py b/nbgrader/tests/preprocessors/test_deduplicateids.py index c0e0642f4..e3f513b04 100644 --- a/nbgrader/tests/preprocessors/test_deduplicateids.py +++ b/nbgrader/tests/preprocessors/test_deduplicateids.py @@ -25,8 +25,8 @@ def test_duplicate_grade_cell(self, preprocessor): nb, resources = preprocessor.preprocess(nb, {}) - assert nb.cells[0].metadata.nbgrader == {} - assert nb.cells[1].metadata.nbgrader != {} + assert nb.cells[0].metadata.nbgrader != {} + assert nb.cells[1].metadata.nbgrader == {} def test_duplicate_solution_cell(self, preprocessor): cell1 = create_solution_cell("hello", "code", "foo") @@ -37,8 +37,8 @@ def test_duplicate_solution_cell(self, preprocessor): nb, resources = preprocessor.preprocess(nb, {}) - assert nb.cells[0].metadata.nbgrader == {} - assert nb.cells[1].metadata.nbgrader != {} + assert nb.cells[0].metadata.nbgrader != {} + assert nb.cells[1].metadata.nbgrader == {} def test_duplicate_locked_cell(self, preprocessor): cell1 = create_locked_cell("hello", "code", "foo") @@ -49,5 +49,5 @@ def test_duplicate_locked_cell(self, preprocessor): nb, resources = preprocessor.preprocess(nb, {}) - assert nb.cells[0].metadata.nbgrader == {} - assert nb.cells[1].metadata.nbgrader != {} + assert nb.cells[0].metadata.nbgrader != {} + assert nb.cells[1].metadata.nbgrader == {} From 5cb3e59549b9387e5165fa976a752c2f726b73f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20K=C3=B6se?= Date: Mon, 13 Mar 2023 10:08:06 +0200 Subject: [PATCH 5/6] Don't skip the assignment if a notebook raises an exception --- nbgrader/converters/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nbgrader/converters/base.py b/nbgrader/converters/base.py index 3d9632866..209a6dabf 100644 --- a/nbgrader/converters/base.py +++ b/nbgrader/converters/base.py @@ -391,7 +391,7 @@ def _handle_failure(gd: typing.Dict[str, str]) -> None: self.convert_single_notebook(notebook_filename) # Exceptions that shouldn't interrupt the entire conversion process should go here - # Those that should go in outer try/except + # Those that should interrupt go in the outer try/except except UnresponsiveKernelError: self.log.error( "While processing assignment %s, the kernel became " @@ -405,6 +405,7 @@ def _handle_failure(gd: typing.Dict[str, str]) -> None: errors.append((gd['assignment_id'], gd['student_id'])) _handle_failure(gd) + # Raise unhandled exceptions for the outer try/except except Exception as e: raise e From bda844c822a73e6eca5507307e11f1cb237bef56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tun=C3=A7=20K=C3=B6se?= Date: Mon, 13 Mar 2023 12:51:53 +0200 Subject: [PATCH 6/6] Skip duplicated grade_ids during autograding --- nbgrader/converters/autograde.py | 4 +++ nbgrader/converters/base.py | 8 ++++++ nbgrader/nbgraderformat/__init__.py | 6 +++++ nbgrader/postprocessors/__init__.py | 6 +++++ nbgrader/postprocessors/checkduplicateflag.py | 27 +++++++++++++++++++ nbgrader/preprocessors/deduplicateids.py | 6 ++++- .../preprocessors/test_deduplicateids.py | 11 +++++--- 7 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 nbgrader/postprocessors/__init__.py create mode 100644 nbgrader/postprocessors/checkduplicateflag.py diff --git a/nbgrader/converters/autograde.py b/nbgrader/converters/autograde.py index d3b4e8bac..7912c7241 100644 --- a/nbgrader/converters/autograde.py +++ b/nbgrader/converters/autograde.py @@ -10,6 +10,7 @@ from ..preprocessors import ( AssignLatePenalties, ClearOutput, DeduplicateIds, OverwriteCells, SaveAutoGrades, Execute, LimitOutput, OverwriteKernelspec, CheckCellMetadata, IgnorePattern) +from ..postprocessors import CheckDuplicateFlag from ..api import Gradebook, MissingEntry from .. import utils @@ -196,3 +197,6 @@ def convert_single_notebook(self, notebook_filename: str) -> None: super(Autograde, self).convert_single_notebook(notebook_filename) finally: self._sanitizing = True + + self.log.info(f"Post-processing {notebook_filename}") + CheckDuplicateFlag(notebook_filename) diff --git a/nbgrader/converters/base.py b/nbgrader/converters/base.py index 209a6dabf..4b5124e8f 100644 --- a/nbgrader/converters/base.py +++ b/nbgrader/converters/base.py @@ -17,6 +17,7 @@ from ..coursedir import CourseDirectory from ..utils import find_all_files, rmtree, remove from ..preprocessors.execute import UnresponsiveKernelError +from ..postprocessors import DuplicateIdError from ..nbgraderformat import SchemaTooOldError, SchemaTooNewError import typing from nbconvert.exporters.exporter import ResourcesDict @@ -405,6 +406,13 @@ def _handle_failure(gd: typing.Dict[str, str]) -> None: errors.append((gd['assignment_id'], gd['student_id'])) _handle_failure(gd) + except DuplicateIdError: + self.log.error( + f"Encountered a cell with duplicate id when processing {notebook_filename}. " + "Autograding with skipping cells marked as duplicate." + ) + errors.append((gd['assignment_id'], gd['student_id'])) + # Raise unhandled exceptions for the outer try/except except Exception as e: raise e diff --git a/nbgrader/nbgraderformat/__init__.py b/nbgrader/nbgraderformat/__init__.py index 68529f3f4..0a0494682 100644 --- a/nbgrader/nbgraderformat/__init__.py +++ b/nbgrader/nbgraderformat/__init__.py @@ -4,3 +4,9 @@ from .v4 import reads_v4 as reads, writes_v4 as writes SCHEMA_VERSION = MetadataValidator.schema_version + +# Metadata required by latest schema, along with default values +SCHEMA_REQUIRED = {"schema_version": 4, + "grade": False, + "locked": False, + "solution": False} diff --git a/nbgrader/postprocessors/__init__.py b/nbgrader/postprocessors/__init__.py new file mode 100644 index 000000000..8643b2413 --- /dev/null +++ b/nbgrader/postprocessors/__init__.py @@ -0,0 +1,6 @@ +from .checkduplicateflag import CheckDuplicateFlag, DuplicateIdError + +__all__ = [ + "CheckDuplicateFlag", + "DuplicateIdError" +] \ No newline at end of file diff --git a/nbgrader/postprocessors/checkduplicateflag.py b/nbgrader/postprocessors/checkduplicateflag.py new file mode 100644 index 000000000..b5acac955 --- /dev/null +++ b/nbgrader/postprocessors/checkduplicateflag.py @@ -0,0 +1,27 @@ +import nbformat +from nbformat.notebooknode import NotebookNode + + +class DuplicateIdError(Exception): + + def __init__(self, message): + super(DuplicateIdError, self).__init__(message) + + +class CheckDuplicateFlag: + + def __init__(self, notebook_filename): + with open(notebook_filename, encoding="utf-8") as f: + nb = nbformat.read(f, as_version=nbformat.NO_CONVERT) + self.postprocess(nb) + + def postprocess(self, nb: NotebookNode): + for cell in nb.cells: + self.postprocess_cell(cell) + + @staticmethod + def postprocess_cell(cell: NotebookNode): + if "nbgrader" in cell.metadata and "duplicate" in cell.metadata.nbgrader: + del cell.metadata.nbgrader["duplicate"] + msg = "Detected cells with same ids" + raise DuplicateIdError(msg) diff --git a/nbgrader/preprocessors/deduplicateids.py b/nbgrader/preprocessors/deduplicateids.py index 9251ab61e..08f4f9a7f 100644 --- a/nbgrader/preprocessors/deduplicateids.py +++ b/nbgrader/preprocessors/deduplicateids.py @@ -1,5 +1,6 @@ from .. import utils from . import NbGraderPreprocessor +from ..nbgraderformat import SCHEMA_REQUIRED from nbconvert.exporters.exporter import ResourcesDict from nbformat.notebooknode import NotebookNode from typing import Tuple @@ -27,7 +28,10 @@ def preprocess_cell(self, grade_id = cell.metadata.nbgrader['grade_id'] if grade_id in self.grade_ids: self.log.warning("Cell with id '%s' exists multiple times!", grade_id) - cell.metadata.nbgrader = {} + # Replace problematic metadata and leave message + cell.source = "# THIS CELL CONTAINED A DUPLICATE ID DURING AUTOGRADING\n" + cell.source + cell.metadata.nbgrader = SCHEMA_REQUIRED #| {"duplicate": True} # doesn't work in python 3.8 + cell.metadata.nbgrader["duplicate"] = True else: self.grade_ids.add(grade_id) diff --git a/nbgrader/tests/preprocessors/test_deduplicateids.py b/nbgrader/tests/preprocessors/test_deduplicateids.py index e3f513b04..9a319ec6a 100644 --- a/nbgrader/tests/preprocessors/test_deduplicateids.py +++ b/nbgrader/tests/preprocessors/test_deduplicateids.py @@ -3,6 +3,7 @@ from nbformat.v4 import new_notebook from ...preprocessors import DeduplicateIds +from ...nbgraderformat import SCHEMA_REQUIRED from .base import BaseTestPreprocessor from .. import ( create_grade_cell, create_solution_cell, create_locked_cell) @@ -14,6 +15,10 @@ def preprocessor(): return pp +EXPECTED_DUPLICATE_METADATA = SCHEMA_REQUIRED # | {"duplicate": True} # doesn't work in python 3.8 +EXPECTED_DUPLICATE_METADATA["duplicate"] = True + + class TestDeduplicateIds(BaseTestPreprocessor): def test_duplicate_grade_cell(self, preprocessor): @@ -26,7 +31,7 @@ def test_duplicate_grade_cell(self, preprocessor): nb, resources = preprocessor.preprocess(nb, {}) assert nb.cells[0].metadata.nbgrader != {} - assert nb.cells[1].metadata.nbgrader == {} + assert nb.cells[1].metadata.nbgrader == EXPECTED_DUPLICATE_METADATA def test_duplicate_solution_cell(self, preprocessor): cell1 = create_solution_cell("hello", "code", "foo") @@ -38,7 +43,7 @@ def test_duplicate_solution_cell(self, preprocessor): nb, resources = preprocessor.preprocess(nb, {}) assert nb.cells[0].metadata.nbgrader != {} - assert nb.cells[1].metadata.nbgrader == {} + assert nb.cells[1].metadata.nbgrader == EXPECTED_DUPLICATE_METADATA def test_duplicate_locked_cell(self, preprocessor): cell1 = create_locked_cell("hello", "code", "foo") @@ -50,4 +55,4 @@ def test_duplicate_locked_cell(self, preprocessor): nb, resources = preprocessor.preprocess(nb, {}) assert nb.cells[0].metadata.nbgrader != {} - assert nb.cells[1].metadata.nbgrader == {} + assert nb.cells[1].metadata.nbgrader == EXPECTED_DUPLICATE_METADATA