Repository: drvinceknight/nbchkr

Issue:
Write functionality to handle non notebook files

Discussion:

Patch:
diff --git a/docs/howto/handle_a_submission_in_the_wrong_format.rst b/docs/howto/handle_a_submission_in_the_wrong_format.rst
new file mode 100644
index 0000000..802d73a
--- /dev/null
+++ b/docs/howto/handle_a_submission_in_the_wrong_format.rst
@@ -0,0 +1,10 @@
+handle a submission in the wrong format
+=======================================
+
+If a file is checked that is not an `ipynb` file then the checker will write the
+following to the feedback file::
+
+    "Your notebook file was not in the correct format and could not be read"
+
+Note than when batch checking, this will not stop the checker from checking the
+other files.
diff --git a/docs/howto/index.rst b/docs/howto/index.rst
index 2ec2c78..4c70efd 100644
--- a/docs/howto/index.rst
+++ b/docs/howto/index.rst
@@ -15,5 +15,6 @@ How to:
    release_a_source_assignment.rst
    solve_an_assignment.rst
    check_a_submission.rst
+   handle_a_submission_in_the_wrong_format.rst
    contribute.rst
    release.rst
diff --git a/docs/howto/release.rst b/docs/howto/release.rst
new file mode 100644
index 0000000..2a365f4
--- /dev/null
+++ b/docs/howto/release.rst
@@ -0,0 +1,22 @@
+release
+-------
+
+The release process:
+
+1. Adjust the version number in :code:`version.py`.
+2. Tag a new release::
+
+    git tag <release number>
+
+3. Push the new tag to github::
+
+    git push --tags
+
+4. Create a new release on github. 
+5. Create a distribution::
+
+    python setup.py sdist bdist_wheel
+
+6. Use :code:`twine` to upload to pypi::
+
+    python -m twine upload dist/*
diff --git a/src/nbchkr/__main__.py b/src/nbchkr/__main__.py
index 3112550..62aa9a3 100644
--- a/src/nbchkr/__main__.py
+++ b/src/nbchkr/__main__.py
@@ -78,14 +78,24 @@ def check(
         with typer.progressbar(sorted(glob.iglob(submitted))) as bar:
             for path in bar:
                 nb_node = nbchkr.utils.read(path)
-                tags_match = nbchkr.utils.check_tags_match(
-                    source_nb_node=source_nb_node, nb_node=nb_node
-                )
+                if nb_node != {}:
+                    tags_match = nbchkr.utils.check_tags_match(
+                        source_nb_node=source_nb_node, nb_node=nb_node
+                    )
 
-                nb_node = nbchkr.utils.add_checks(
-                    nb_node=nb_node, source_nb_node=source_nb_node
-                )
-                score, maximum_score, feedback_md = nbchkr.utils.check(nb_node=nb_node)
+                    nb_node = nbchkr.utils.add_checks(
+                        nb_node=nb_node, source_nb_node=source_nb_node
+                    )
+                    score, maximum_score, feedback_md = nbchkr.utils.check(
+                        nb_node=nb_node
+                    )
+                else:
+                    score, maximum_score, feedback_md = (
+                        None,
+                        None,
+                        "Your notebook file was not in the correct format and could not be read",
+                    )
+                    tags_match = False
 
                 with open(f"{path}{feedback_suffix}", "w") as f:
                     f.write(feedback_md)
diff --git a/src/nbchkr/utils/__init__.py b/src/nbchkr/utils/__init__.py
index 5411673..3149286 100644
--- a/src/nbchkr/utils/__init__.py
+++ b/src/nbchkr/utils/__init__.py
@@ -2,7 +2,7 @@ import collections
 import json
 import pathlib
 import re
-from typing import Tuple, Union
+from typing import Optional, Tuple, Union
 
 import nbformat  # type: ignore
 import unidecode  # type: ignore
@@ -29,7 +29,10 @@ def read(nb_path: Union[pathlib.Path, str], as_version: int = 4) -> dict:
     Returns the python `dict` representation.
     """
     with open(nb_path, "r") as f:
-        nb = nbformat.read(f, as_version=as_version)
+        try:
+            nb = nbformat.read(f, as_version=as_version)
+        except nbformat.reader.NotJSONError:
+            return {}
     return nb
 
 
@@ -169,7 +172,7 @@ def check(
     timeout: int = 600,
     score_regex_pattern=None,
     answer_tag_pattern=None,
-) -> Tuple[int, int, str]:
+) -> Tuple[Optional[int], Optional[int], str]:
     """
     Given a `nb_node`, it executes the notebook and keep track of the score.
 


Test patch:
diff --git a/tests/test_check.py b/tests/test_check.py
index a83a2e3..ff76ef4 100644
--- a/tests/test_check.py
+++ b/tests/test_check.py
@@ -17,6 +17,15 @@ def test_read_nb_gives_dictionary():
     assert type(nb) is nbformat.NotebookNode
 
 
+def test_read_nb_gives_none_when_not_reading_notebook():
+    """
+    Tries to read in this test file.
+    """
+    nb_path = __file__
+    nb = nbchkr.utils.read(nb_path=nb_path)
+    assert nb == {}
+
+
 def test_add_checks_creates_notebook_with_assertions():
     nb_node = nbchkr.utils.read(nb_path=NB_PATH / "submission.ipynb")
     source_nb_node = nbchkr.utils.read(nb_path=NB_PATH / "test.ipynb")
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 68e51ac..369c7d1 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -292,3 +292,60 @@ def test_check_on_documentation_examples():
 
     expected_number_of_feedback_files = 3
     assert number_of_feedback_files == expected_number_of_feedback_files
+
+
+def test_check_on_a_non_notebook_file():
+    """
+    Tries to read in this test file.
+    """
+    # TODO add better tear down.
+
+    submission_nb = __file__
+    expected_output = ["", "", "False"]
+    expected_feedback = (
+        "Your notebook file was not in the correct format and could not be read"
+    )
+
+    output = subprocess.run(
+        [
+            "nbchkr",
+            "check",
+            "--source",
+            f"{NB_PATH}/test.ipynb",
+            "--submitted",
+            f"{submission_nb}",
+            "--feedback-suffix",
+            "_feedback.md",
+            "--output",
+            "output.csv",
+        ],
+        capture_output=True,
+    )
+    expected_stdout = str.encode(
+        f"{submission_nb} checked against {NB_PATH}/test.ipynb. Feedback written to {submission_nb}_feedback.md and output written to output.csv.\n"
+    )
+    expected_stdout += str.encode(
+        f"WARNING: {submission_nb} has tags that do not match the source.\n"
+    )
+    assert output.stdout == expected_stdout
+
+    with open("output.csv", "r") as f:
+        csv_reader = csv.reader(f)
+        output = list(csv_reader)
+
+    expected_output = [
+        ["Submission filepath", "Score", "Maximum score", "Tags match"],
+        [f"{submission_nb}"] + expected_output,
+    ]
+    assert output == expected_output
+
+    with open(f"{submission_nb}_feedback.md", "r") as f:
+        feedback = f.read()
+        assert feedback == expected_feedback
+
+    # TODO Add a better pytest cleanup.
+    try:
+        pathlib.Path(f"{submission_nb}_feedback.md").unlink()
+    except FileNotFoundError:  # TODO Ensure py3.8 is used so that can pass
+        # `missing_ok=True` to `path.unlink`.
+        pass