From 5dc3b8a4626d5ef54ef9ea3f537b962f05a46fa6 Mon Sep 17 00:00:00 2001 From: "Jessica B. Hamrick" Date: Sun, 5 Feb 2017 18:18:04 -0800 Subject: [PATCH] Turn the gradebook into a context manager --- nbgrader/api.py | 15 +- nbgrader/apps/assignapp.py | 88 +++++----- nbgrader/apps/autogradeapp.py | 74 ++++---- nbgrader/apps/dbapp.py | 18 +- nbgrader/apps/exportapp.py | 7 +- .../docs/source/user_guide/extract_grades.py | 76 ++++----- nbgrader/docs/source/user_guide/faq.rst | 10 +- nbgrader/preprocessors/getgrades.py | 5 +- nbgrader/preprocessors/latesubmissions.py | 5 +- nbgrader/preprocessors/overwritecells.py | 4 +- nbgrader/preprocessors/saveautogrades.py | 4 +- nbgrader/preprocessors/savecells.py | 5 +- nbgrader/tests/apps/test_nbgrader_assign.py | 104 +++++------- .../tests/apps/test_nbgrader_autograde.py | 160 ++++++++---------- nbgrader/tests/apps/test_nbgrader_db.py | 42 ++--- 15 files changed, 280 insertions(+), 337 deletions(-) diff --git a/nbgrader/api.py b/nbgrader/api.py index f1696d8a8..c045dea73 100644 --- a/nbgrader/api.py +++ b/nbgrader/api.py @@ -1028,6 +1028,12 @@ def __init__(self, db_url): # this creates all the tables in the database if they don't already exist Base.metadata.create_all(bind=self.engine) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + def close(self): """Close the connection to the database. @@ -2412,12 +2418,3 @@ def notebook_submission_dicts(self, notebook_id, assignment_id): "failed_tests", "flagged" ] return [dict(zip(keys, x)) for x in submissions] - - -@contextlib.contextmanager -def open_gradebook(db_url): - gradebook = Gradebook(db_url) - try: - yield gradebook - finally: - gradebook.close() diff --git a/nbgrader/apps/assignapp.py b/nbgrader/apps/assignapp.py index 1df3898ff..156f10228 100644 --- a/nbgrader/apps/assignapp.py +++ b/nbgrader/apps/assignapp.py @@ -161,43 +161,39 @@ def build_extra_config(self): return extra_config def _clean_old_notebooks(self, assignment_id, student_id): - gb = Gradebook(self.db_url) - assignment = gb.find_assignment(assignment_id) - regexp = re.escape(os.path.sep).join([ - self._format_source("(?P.*)", "(?P.*)", escape=True), - "(?P.*).ipynb" - ]) - - # find a set of notebook ids for new notebooks - new_notebook_ids = set([]) - for notebook in self.notebooks: - m = re.match(regexp, notebook) - if m is None: - raise RuntimeError("Could not match '%s' with regexp '%s'", notebook, regexp) - gd = m.groupdict() - if gd['assignment_id'] == assignment_id and gd['student_id'] == student_id: - new_notebook_ids.add(gd['notebook_id']) - - # pull out the existing notebook ids - old_notebook_ids = set(x.name for x in assignment.notebooks) - - # no added or removed notebooks, so nothing to do - if old_notebook_ids == new_notebook_ids: - gb.close() - return - - # some notebooks have been removed, but there are submissions associated - # with the assignment, so we don't want to overwrite stuff - if len(assignment.submissions) > 0: - gb.close() - self.fail("Cannot modify existing assignment '%s' because there are submissions associated with it", assignment) - - # remove the old notebooks - for notebook_id in (old_notebook_ids - new_notebook_ids): - self.log.warning("Removing notebook '%s' from the gradebook", notebook_id) - gb.remove_notebook(notebook_id, assignment_id) - - gb.close() + with Gradebook(self.db_url) as gb: + assignment = gb.find_assignment(assignment_id) + regexp = re.escape(os.path.sep).join([ + self._format_source("(?P.*)", "(?P.*)", escape=True), + "(?P.*).ipynb" + ]) + + # find a set of notebook ids for new notebooks + new_notebook_ids = set([]) + for notebook in self.notebooks: + m = re.match(regexp, notebook) + if m is None: + raise RuntimeError("Could not match '%s' with regexp '%s'", notebook, regexp) + gd = m.groupdict() + if gd['assignment_id'] == assignment_id and gd['student_id'] == student_id: + new_notebook_ids.add(gd['notebook_id']) + + # pull out the existing notebook ids + old_notebook_ids = set(x.name for x in assignment.notebooks) + + # no added or removed notebooks, so nothing to do + if old_notebook_ids == new_notebook_ids: + return + + # some notebooks have been removed, but there are submissions associated + # with the assignment, so we don't want to overwrite stuff + if len(assignment.submissions) > 0: + self.fail("Cannot modify existing assignment '%s' because there are submissions associated with it", assignment) + + # remove the old notebooks + for notebook_id in (old_notebook_ids - new_notebook_ids): + self.log.warning("Removing notebook '%s' from the gradebook", notebook_id) + gb.remove_notebook(notebook_id, assignment_id) def init_assignment(self, assignment_id, student_id): super(AssignApp, self).init_assignment(assignment_id, student_id) @@ -215,17 +211,15 @@ def init_assignment(self, assignment_id, student_id): if 'name' in assignment: del assignment['name'] self.log.info("Updating/creating assignment '%s': %s", assignment_id, assignment) - gb = Gradebook(self.db_url) - gb.update_or_create_assignment(assignment_id, **assignment) - gb.close() + with Gradebook(self.db_url) as gb: + gb.update_or_create_assignment(assignment_id, **assignment) + else: - gb = Gradebook(self.db_url) - try: - gb.find_assignment(assignment_id) - except MissingEntry: - self.fail("No assignment called '%s' exists in the database", assignment_id) - finally: - gb.close() + with Gradebook(self.db_url) as gb: + try: + gb.find_assignment(assignment_id) + except MissingEntry: + self.fail("No assignment called '%s' exists in the database", assignment_id) # check if there are any extra notebooks in the db that are no longer # part of the assignment, and if so, remove them diff --git a/nbgrader/apps/autogradeapp.py b/nbgrader/apps/autogradeapp.py index abfd26740..0e33167ae 100644 --- a/nbgrader/apps/autogradeapp.py +++ b/nbgrader/apps/autogradeapp.py @@ -144,42 +144,37 @@ def init_assignment(self, assignment_id, student_id): if 'id' in student: del student['id'] self.log.info("Creating/updating student with ID '%s': %s", student_id, student) - gb = Gradebook(self.db_url) - gb.update_or_create_student(student_id, **student) - gb.close() + with Gradebook(self.db_url) as gb: + gb.update_or_create_student(student_id, **student) + else: - gb = Gradebook(self.db_url) - try: - gb.find_student(student_id) - except MissingEntry: - self.fail("No student with ID '%s' exists in the database", student_id) - finally: - gb.close() + with Gradebook(self.db_url) as gb: + try: + gb.find_student(student_id) + except MissingEntry: + self.fail("No student with ID '%s' exists in the database", student_id) # make sure the assignment exists - gb = Gradebook(self.db_url) - try: - gb.find_assignment(assignment_id) - except MissingEntry: - self.fail("No assignment with ID '%s' exists in the database", assignment_id) - finally: - gb.close() + with Gradebook(self.db_url) as gb: + try: + gb.find_assignment(assignment_id) + except MissingEntry: + self.fail("No assignment with ID '%s' exists in the database", assignment_id) # try to read in a timestamp from file src_path = self._format_source(assignment_id, student_id) timestamp = self._get_existing_timestamp(src_path) - gb = Gradebook(self.db_url) - if timestamp: - submission = gb.update_or_create_submission( - assignment_id, student_id, timestamp=timestamp) - self.log.info("%s submitted at %s", submission, timestamp) - - # if the submission is late, print out how many seconds late it is - if timestamp and submission.total_seconds_late > 0: - self.log.warning("%s is %s seconds late", submission, submission.total_seconds_late) - else: - submission = gb.update_or_create_submission(assignment_id, student_id) - gb.close() + with Gradebook(self.db_url) as gb: + if timestamp: + submission = gb.update_or_create_submission( + assignment_id, student_id, timestamp=timestamp) + self.log.info("%s submitted at %s", submission, timestamp) + + # if the submission is late, print out how many seconds late it is + if timestamp and submission.total_seconds_late > 0: + self.log.warning("%s is %s seconds late", submission, submission.total_seconds_late) + else: + submission = gb.update_or_create_submission(assignment_id, student_id) # copy files over from the source directory self.log.info("Overwriting files with master versions from the source directory") @@ -199,17 +194,16 @@ def init_assignment(self, assignment_id, student_id): # ignore notebooks that aren't in the database notebooks = [] - gb = Gradebook(self.db_url) - for notebook in self.notebooks: - notebook_id = os.path.splitext(os.path.basename(notebook))[0] - try: - gb.find_notebook(notebook_id, assignment_id) - except MissingEntry: - self.log.warning("Skipping unknown notebook: %s", notebook) - continue - else: - notebooks.append(notebook) - gb.close() + with Gradebook(self.db_url) as gb: + for notebook in self.notebooks: + notebook_id = os.path.splitext(os.path.basename(notebook))[0] + try: + gb.find_notebook(notebook_id, assignment_id) + except MissingEntry: + self.log.warning("Skipping unknown notebook: %s", notebook) + continue + else: + notebooks.append(notebook) self.notebooks = notebooks if len(self.notebooks) == 0: self.fail("No notebooks found, did you forget to run 'nbgrader assign'?") diff --git a/nbgrader/apps/dbapp.py b/nbgrader/apps/dbapp.py index e61d74d72..ebeeb62d5 100644 --- a/nbgrader/apps/dbapp.py +++ b/nbgrader/apps/dbapp.py @@ -8,7 +8,7 @@ from traitlets import default, Unicode, Bool from . import NbGrader -from ..api import open_gradebook, MissingEntry +from ..api import Gradebook, MissingEntry aliases = { 'log-level': 'Application.log_level', @@ -65,7 +65,7 @@ def start(self): } self.log.info("Creating/updating student with ID '%s': %s", student_id, student) - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: gb.update_or_create_student(student_id, **student) student_remove_flags = {} @@ -95,7 +95,7 @@ def start(self): student_id = self.extra_args[0] - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: try: student = gb.find_student(student_id) except MissingEntry: @@ -134,7 +134,7 @@ def start(self): allowed_keys = ["last_name", "first_name", "email", "id"] - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: with open(path, 'r') as fh: reader = csv.DictReader(fh) for row in reader: @@ -168,7 +168,7 @@ class DbStudentListApp(NbGrader): def start(self): super(DbStudentListApp, self).start() - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: print("There are %d students in the database:" % len(gb.students)) for student in gb.students: print("%s (%s, %s) -- %s" % (student.id, student.last_name, student.first_name, student.email)) @@ -206,7 +206,7 @@ def start(self): } self.log.info("Creating/updating assignment with ID '%s': %s", assignment_id, assignment) - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: gb.update_or_create_assignment(assignment_id, **assignment) @@ -237,7 +237,7 @@ def start(self): assignment_id = self.extra_args[0] - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: try: assignment = gb.find_assignment(assignment_id) except MissingEntry: @@ -276,7 +276,7 @@ def start(self): allowed_keys = ["duedate", "name"] - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: with open(path, 'r') as fh: reader = csv.DictReader(fh) for row in reader: @@ -310,7 +310,7 @@ class DbAssignmentListApp(NbGrader): def start(self): super(DbAssignmentListApp, self).start() - with open_gradebook(self.db_url) as gb: + with Gradebook(self.db_url) as gb: print("There are %d assignments in the database:" % len(gb.assignments)) for assignment in gb.assignments: print("%s (due: %s)" % (assignment.name, assignment.duedate)) diff --git a/nbgrader/apps/exportapp.py b/nbgrader/apps/exportapp.py index 909feb416..c68b54fe5 100644 --- a/nbgrader/apps/exportapp.py +++ b/nbgrader/apps/exportapp.py @@ -60,8 +60,5 @@ def _classes_default(self): def start(self): super(ExportApp, self).start() self.init_plugin() - gradebook = Gradebook(self.db_url) - try: - self.plugin_inst.export(gradebook) - finally: - gradebook.close() + with Gradebook(self.db_url) as gb: + self.plugin_inst.export(gb) diff --git a/nbgrader/docs/source/user_guide/extract_grades.py b/nbgrader/docs/source/user_guide/extract_grades.py index be8d117ca..32cec0e89 100644 --- a/nbgrader/docs/source/user_guide/extract_grades.py +++ b/nbgrader/docs/source/user_guide/extract_grades.py @@ -2,43 +2,39 @@ from nbgrader.api import Gradebook, MissingEntry # Create the connection to the database -gb = Gradebook('sqlite:///gradebook.db') - -grades = [] - -# Loop over each assignment in the database -for assignment in gb.assignments: - - # Loop over each student in the database - for student in gb.students: - - # Create a dictionary that will store information about this student's - # submitted assignment - score = {} - score['max_score'] = assignment.max_score - score['student'] = student.id - score['assignment'] = assignment.name - - # Try to find the submission in the database. If it doesn't exist, the - # `MissingEntry` exception will be raised, which means the student - # didn't submit anything, so we assign them a score of zero. - try: - submission = gb.find_submission(assignment.name, student.id) - except MissingEntry: - score['score'] = 0.0 - else: - score['score'] = submission.score - - grades.append(score) - -# Create a pandas dataframe with our grade information, and save it to disk -grades = pd.DataFrame(grades).set_index(['student', 'assignment']).sortlevel() -grades.to_csv('grades.csv') - -# Print out what the grades look like -with open('grades.csv', 'r') as fh: - print(fh.read()) - -# Close the connection to the database -gb.close() - +with Gradebook('sqlite:///gradebook.db') as gb: + + grades = [] + + # Loop over each assignment in the database + for assignment in gb.assignments: + + # Loop over each student in the database + for student in gb.students: + + # Create a dictionary that will store information about this student's + # submitted assignment + score = {} + score['max_score'] = assignment.max_score + score['student'] = student.id + score['assignment'] = assignment.name + + # Try to find the submission in the database. If it doesn't exist, the + # `MissingEntry` exception will be raised, which means the student + # didn't submit anything, so we assign them a score of zero. + try: + submission = gb.find_submission(assignment.name, student.id) + except MissingEntry: + score['score'] = 0.0 + else: + score['score'] = submission.score + + grades.append(score) + + # Create a pandas dataframe with our grade information, and save it to disk + grades = pd.DataFrame(grades).set_index(['student', 'assignment']).sortlevel() + grades.to_csv('grades.csv') + + # Print out what the grades look like + with open('grades.csv', 'r') as fh: + print(fh.read()) diff --git a/nbgrader/docs/source/user_guide/faq.rst b/nbgrader/docs/source/user_guide/faq.rst index 6814aa78f..b59396303 100644 --- a/nbgrader/docs/source/user_guide/faq.rst +++ b/nbgrader/docs/source/user_guide/faq.rst @@ -61,11 +61,11 @@ database. You can access the timestamps through the API, like so: .. code:: python from nbgrader.api import Gradebook - gb = Gradebook("sqlite:///gradebook.db") - assignment = gb.find_assignment("ps1") - for submission in assignment.submissions: - print("Submission from '{}' is {} seconds late".format( - submission.student_id, submission.total_seconds_late)) + with Gradebook("sqlite:///gradebook.db") gb: + assignment = gb.find_assignment("ps1") + for submission in assignment.submissions: + print("Submission from '{}' is {} seconds late".format( + submission.student_id, submission.total_seconds_late)) Note that if you use the release/fetch/submit/collect commands (see :doc:`managing_assignment_files`), the ``timestamp.txt`` files will be included diff --git a/nbgrader/preprocessors/getgrades.py b/nbgrader/preprocessors/getgrades.py index 358813444..67ad923c4 100644 --- a/nbgrader/preprocessors/getgrades.py +++ b/nbgrader/preprocessors/getgrades.py @@ -20,7 +20,7 @@ def preprocess(self, nb, resources): # connect to the database self.gradebook = Gradebook(self.db_url) - try: + with self.gradebook: # process the cells nb, resources = super(GetGrades, self).preprocess(nb, resources) notebook = self.gradebook.find_submission_notebook( @@ -35,9 +35,6 @@ def preprocess(self, nb, resources): resources['nbgrader']['max_score'] = notebook.max_score resources['nbgrader']['late_penalty'] = late_penalty - finally: - self.gradebook.close() - return nb, resources def _get_comment(self, cell, resources): diff --git a/nbgrader/preprocessors/latesubmissions.py b/nbgrader/preprocessors/latesubmissions.py index 8e0b12402..1e5395a15 100644 --- a/nbgrader/preprocessors/latesubmissions.py +++ b/nbgrader/preprocessors/latesubmissions.py @@ -47,7 +47,7 @@ def preprocess(self, nb, resources): # connect to the database self.gradebook = Gradebook(self.db_url) - try: + with self.gradebook: # process the late submissions nb, resources = super(AssignLatePenalties, self).preprocess(nb, resources) assignment = self.gradebook.find_submission( @@ -69,9 +69,6 @@ def preprocess(self, nb, resources): notebook.late_submission_penalty = late_penalty self.gradebook.db.commit() - finally: - self.gradebook.close() - return nb, resources def preprocess_cell(self, cell, resources, cell_index): diff --git a/nbgrader/preprocessors/overwritecells.py b/nbgrader/preprocessors/overwritecells.py index 0ceec9666..d3eb23bf2 100644 --- a/nbgrader/preprocessors/overwritecells.py +++ b/nbgrader/preprocessors/overwritecells.py @@ -16,10 +16,8 @@ def preprocess(self, nb, resources): # connect to the database self.gradebook = Gradebook(self.db_url) - try: + with self.gradebook: nb, resources = super(OverwriteCells, self).preprocess(nb, resources) - finally: - self.gradebook.close() return nb, resources diff --git a/nbgrader/preprocessors/saveautogrades.py b/nbgrader/preprocessors/saveautogrades.py index 02cb2311f..eb5286215 100644 --- a/nbgrader/preprocessors/saveautogrades.py +++ b/nbgrader/preprocessors/saveautogrades.py @@ -16,11 +16,9 @@ def preprocess(self, nb, resources): # connect to the database self.gradebook = Gradebook(self.db_url) - try: + with self.gradebook: # process the cells nb, resources = super(SaveAutoGrades, self).preprocess(nb, resources) - finally: - self.gradebook.close() return nb, resources diff --git a/nbgrader/preprocessors/savecells.py b/nbgrader/preprocessors/savecells.py index 3ce805dfe..3a465aa98 100644 --- a/nbgrader/preprocessors/savecells.py +++ b/nbgrader/preprocessors/savecells.py @@ -75,15 +75,12 @@ def preprocess(self, nb, resources): # connect to the database self.gradebook = Gradebook(self.db_url) - try: + with self.gradebook: nb, resources = super(SaveCells, self).preprocess(nb, resources) # create the notebook and save it to the database self._create_notebook() - finally: - self.gradebook.close() - return nb, resources def _create_grade_cell(self, cell): diff --git a/nbgrader/tests/apps/test_nbgrader_assign.py b/nbgrader/tests/apps/test_nbgrader_assign.py index 8e6df45ad..aa53923ee 100644 --- a/nbgrader/tests/apps/test_nbgrader_assign.py +++ b/nbgrader/tests/apps/test_nbgrader_assign.py @@ -82,11 +82,9 @@ def test_save_cells(self, db, course_dir): run_nbgrader(["assign", "ps1", "--db", db]) - gb = Gradebook(db) - notebook = gb.find_notebook("test", "ps1") - assert len(notebook.grade_cells) == 6 - - gb.close() + with Gradebook(db) as gb: + notebook = gb.find_notebook("test", "ps1") + assert len(notebook.grade_cells) == 6 def test_force(self, course_dir): """Ensure the force option works properly""" @@ -158,29 +156,27 @@ def test_add_remove_extra_notebooks(self, db, course_dir): fh.write("""c.NbGrader.db_assignments = [dict(name="ps1")]\n""") run_nbgrader(["assign", "ps1", "--db", db]) - gb = Gradebook(db) - assignment = gb.find_assignment("ps1") - assert len(assignment.notebooks) == 1 - notebook1 = gb.find_notebook("test", "ps1") - - self._copy_file(join("files", "test.ipynb"), join(course_dir, "source", "ps1", "test2.ipynb")) - run_nbgrader(["assign", "ps1", "--db", db, "--force"]) + with Gradebook(db) as gb: + assignment = gb.find_assignment("ps1") + assert len(assignment.notebooks) == 1 + notebook1 = gb.find_notebook("test", "ps1") - gb.db.refresh(assignment) - assert len(assignment.notebooks) == 2 - gb.db.refresh(notebook1) - notebook2 = gb.find_notebook("test2", "ps1") + self._copy_file(join("files", "test.ipynb"), join(course_dir, "source", "ps1", "test2.ipynb")) + run_nbgrader(["assign", "ps1", "--db", db, "--force"]) - os.remove(join(course_dir, "source", "ps1", "test2.ipynb")) - run_nbgrader(["assign", "ps1", "--db", db, "--force"]) + gb.db.refresh(assignment) + assert len(assignment.notebooks) == 2 + gb.db.refresh(notebook1) + notebook2 = gb.find_notebook("test2", "ps1") - gb.db.refresh(assignment) - assert len(assignment.notebooks) == 1 - gb.db.refresh(notebook1) - with pytest.raises(InvalidRequestError): - gb.db.refresh(notebook2) + os.remove(join(course_dir, "source", "ps1", "test2.ipynb")) + run_nbgrader(["assign", "ps1", "--db", db, "--force"]) - gb.close() + gb.db.refresh(assignment) + assert len(assignment.notebooks) == 1 + gb.db.refresh(notebook1) + with pytest.raises(InvalidRequestError): + gb.db.refresh(notebook2) def test_add_extra_notebooks_with_submissions(self, db, course_dir): """Is an error thrown when new notebooks are added and there are existing submissions?""" @@ -190,17 +186,15 @@ def test_add_extra_notebooks_with_submissions(self, db, course_dir): fh.write("""c.NbGrader.db_assignments = [dict(name="ps1")]\n""") run_nbgrader(["assign", "ps1", "--db", db]) - gb = Gradebook(db) - assignment = gb.find_assignment("ps1") - assert len(assignment.notebooks) == 1 + with Gradebook(db) as gb: + assignment = gb.find_assignment("ps1") + assert len(assignment.notebooks) == 1 - gb.add_student("hacker123") - gb.add_submission("ps1", "hacker123") + gb.add_student("hacker123") + gb.add_submission("ps1", "hacker123") - self._copy_file(join("files", "test.ipynb"), join(course_dir, "source", "ps1", "test2.ipynb")) - run_nbgrader(["assign", "ps1", "--db", db, "--force"], retcode=1) - - gb.close() + self._copy_file(join("files", "test.ipynb"), join(course_dir, "source", "ps1", "test2.ipynb")) + run_nbgrader(["assign", "ps1", "--db", db, "--force"], retcode=1) def test_remove_extra_notebooks_with_submissions(self, db, course_dir): """Is an error thrown when notebooks are removed and there are existing submissions?""" @@ -211,17 +205,15 @@ def test_remove_extra_notebooks_with_submissions(self, db, course_dir): fh.write("""c.NbGrader.db_assignments = [dict(name="ps1")]\n""") run_nbgrader(["assign", "ps1", "--db", db]) - gb = Gradebook(db) - assignment = gb.find_assignment("ps1") - assert len(assignment.notebooks) == 2 - - gb.add_student("hacker123") - gb.add_submission("ps1", "hacker123") + with Gradebook(db) as gb: + assignment = gb.find_assignment("ps1") + assert len(assignment.notebooks) == 2 - os.remove(join(course_dir, "source", "ps1", "test2.ipynb")) - run_nbgrader(["assign", "ps1", "--db", db, "--force"], retcode=1) + gb.add_student("hacker123") + gb.add_submission("ps1", "hacker123") - gb.close() + os.remove(join(course_dir, "source", "ps1", "test2.ipynb")) + run_nbgrader(["assign", "ps1", "--db", db, "--force"], retcode=1) def test_same_notebooks_with_submissions(self, db, course_dir): """Is it ok to run nbgrader assign with the same notebooks and existing submissions?""" @@ -231,24 +223,22 @@ def test_same_notebooks_with_submissions(self, db, course_dir): fh.write("""c.NbGrader.db_assignments = [dict(name="ps1")]\n""") run_nbgrader(["assign", "ps1", "--db", db]) - gb = Gradebook(db) - assignment = gb.find_assignment("ps1") - assert len(assignment.notebooks) == 1 - notebook = assignment.notebooks[0] - - gb.add_student("hacker123") - submission = gb.add_submission("ps1", "hacker123") - submission_notebook = submission.notebooks[0] + with Gradebook(db) as gb: + assignment = gb.find_assignment("ps1") + assert len(assignment.notebooks) == 1 + notebook = assignment.notebooks[0] - run_nbgrader(["assign", "ps1", "--db", db, "--force"]) + gb.add_student("hacker123") + submission = gb.add_submission("ps1", "hacker123") + submission_notebook = submission.notebooks[0] - gb.db.refresh(assignment) - assert len(assignment.notebooks) == 1 - gb.db.refresh(notebook) - gb.db.refresh(submission) - gb.db.refresh(submission_notebook) + run_nbgrader(["assign", "ps1", "--db", db, "--force"]) - gb.close() + gb.db.refresh(assignment) + assert len(assignment.notebooks) == 1 + gb.db.refresh(notebook) + gb.db.refresh(submission) + gb.db.refresh(submission_notebook) def test_force_single_notebook(self, course_dir): self._copy_file(join("files", "test.ipynb"), join(course_dir, "source", "ps1", "p1.ipynb")) diff --git a/nbgrader/tests/apps/test_nbgrader_autograde.py b/nbgrader/tests/apps/test_nbgrader_autograde.py index 91525c26b..45569a1bc 100644 --- a/nbgrader/tests/apps/test_nbgrader_autograde.py +++ b/nbgrader/tests/apps/test_nbgrader_autograde.py @@ -65,31 +65,29 @@ def test_grade(self, db, course_dir): assert os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "p1.ipynb")) assert not os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "timestamp.txt")) - gb = Gradebook(db) - notebook = gb.find_submission_notebook("p1", "ps1", "foo") - assert notebook.score == 1 - assert notebook.max_score == 7 - assert notebook.needs_manual_grade == False - - comment1 = gb.find_comment("set_a", "p1", "ps1", "foo") - comment2 = gb.find_comment("baz", "p1", "ps1", "foo") - comment3 = gb.find_comment("quux", "p1", "ps1", "foo") - assert comment1.comment == "No response." - assert comment2.comment == "No response." - assert comment3.comment == "No response." - - notebook = gb.find_submission_notebook("p1", "ps1", "bar") - assert notebook.score == 2 - assert notebook.max_score == 7 - assert notebook.needs_manual_grade == True - - comment1 = gb.find_comment("set_a", "p1", "ps1", "bar") - comment2 = gb.find_comment("baz", "p1", "ps1", "bar") - comment2 = gb.find_comment("quux", "p1", "ps1", "bar") - assert comment1.comment == None - assert comment2.comment == None - - gb.close() + with Gradebook(db) as gb: + notebook = gb.find_submission_notebook("p1", "ps1", "foo") + assert notebook.score == 1 + assert notebook.max_score == 7 + assert notebook.needs_manual_grade == False + + comment1 = gb.find_comment("set_a", "p1", "ps1", "foo") + comment2 = gb.find_comment("baz", "p1", "ps1", "foo") + comment3 = gb.find_comment("quux", "p1", "ps1", "foo") + assert comment1.comment == "No response." + assert comment2.comment == "No response." + assert comment3.comment == "No response." + + notebook = gb.find_submission_notebook("p1", "ps1", "bar") + assert notebook.score == 2 + assert notebook.max_score == 7 + assert notebook.needs_manual_grade == True + + comment1 = gb.find_comment("set_a", "p1", "ps1", "bar") + comment2 = gb.find_comment("baz", "p1", "ps1", "bar") + comment2 = gb.find_comment("quux", "p1", "ps1", "bar") + assert comment1.comment == None + assert comment2.comment == None def test_grade_timestamp(self, db, course_dir): """Is a timestamp correctly read in?""" @@ -113,17 +111,15 @@ def test_grade_timestamp(self, db, course_dir): assert os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "p1.ipynb")) assert os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "timestamp.txt")) - gb = Gradebook(db) - submission = gb.find_submission("ps1", "foo") - assert submission.total_seconds_late > 0 - submission = gb.find_submission("ps1", "bar") - assert submission.total_seconds_late == 0 + with Gradebook(db) as gb: + submission = gb.find_submission("ps1", "foo") + assert submission.total_seconds_late > 0 + submission = gb.find_submission("ps1", "bar") + assert submission.total_seconds_late == 0 # make sure it still works to run it a second time run_nbgrader(["autograde", "ps1", "--db", db]) - gb.close() - def test_grade_empty_timestamp(self, db, course_dir): """Issue #580 - Does the autograder handle empty or invalid timestamp strings""" @@ -141,10 +137,9 @@ def test_grade_empty_timestamp(self, db, course_dir): assert os.path.isfile(join(course_dir, "autograded", "foo", "ps1", "p1.ipynb")) assert os.path.isfile(join(course_dir, "autograded", "foo", "ps1", "timestamp.txt")) - gb = Gradebook(db) - submission = gb.find_submission("ps1", "foo") - assert submission.total_seconds_late == 0 - gb.close() + with Gradebook(db) as gb: + submission = gb.find_submission("ps1", "foo") + assert submission.total_seconds_late == 0 invalid_timestamp = "But I want to be a timestamp string :(" self._copy_file(join("files", "submitted-changed.ipynb"), join(course_dir, "submitted", "bar", "ps1", "p1.ipynb")) @@ -176,21 +171,19 @@ def test_late_submission_penalty_none(self, db, course_dir): assert os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "timestamp.txt")) # not late - gb = Gradebook(db) - submission = gb.find_submission("ps1", "foo") - nb = submission.notebooks[0] - assert nb.score == 1 - assert submission.total_seconds_late == 0 - assert nb.late_submission_penalty == None - - # 1h late - submission = gb.find_submission("ps1", "bar") - nb = submission.notebooks[0] - assert nb.score == 2 - assert submission.total_seconds_late > 0 - assert nb.late_submission_penalty == None - - gb.close() + with Gradebook(db) as gb: + submission = gb.find_submission("ps1", "foo") + nb = submission.notebooks[0] + assert nb.score == 1 + assert submission.total_seconds_late == 0 + assert nb.late_submission_penalty == None + + # 1h late + submission = gb.find_submission("ps1", "bar") + nb = submission.notebooks[0] + assert nb.score == 2 + assert submission.total_seconds_late > 0 + assert nb.late_submission_penalty == None def test_late_submission_penalty_zero(self, db, course_dir): """Does 'zero' method assign notebook.score as penalty if late?""" @@ -218,21 +211,19 @@ def test_late_submission_penalty_zero(self, db, course_dir): assert os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "timestamp.txt")) # not late - gb = Gradebook(db) - submission = gb.find_submission("ps1", "foo") - nb = submission.notebooks[0] - assert nb.score == 1 - assert submission.total_seconds_late == 0 - assert nb.late_submission_penalty == None - - # 1h late - submission = gb.find_submission("ps1", "bar") - nb = submission.notebooks[0] - assert nb.score == 2 - assert submission.total_seconds_late > 0 - assert nb.late_submission_penalty == nb.score - - gb.close() + with Gradebook(db) as gb: + submission = gb.find_submission("ps1", "foo") + nb = submission.notebooks[0] + assert nb.score == 1 + assert submission.total_seconds_late == 0 + assert nb.late_submission_penalty == None + + # 1h late + submission = gb.find_submission("ps1", "bar") + nb = submission.notebooks[0] + assert nb.score == 2 + assert submission.total_seconds_late > 0 + assert nb.late_submission_penalty == nb.score def test_late_submission_penalty_plugin(self, db, course_dir): """Does plugin set 1 point per hour late penalty?""" @@ -275,21 +266,19 @@ def late_submission_penalty(self, student_id, score, total_seconds_late): assert os.path.isfile(join(course_dir, "autograded", "bar", "ps1", "timestamp.txt")) # 4h late - gb = Gradebook(db) - submission = gb.find_submission("ps1", "foo") - nb = submission.notebooks[0] - assert nb.score == 1 - assert submission.total_seconds_late > 0 - assert nb.late_submission_penalty == nb.score - - # 1h late - submission = gb.find_submission("ps1", "bar") - nb = submission.notebooks[0] - assert nb.score == 2 - assert submission.total_seconds_late > 0 - assert nb.late_submission_penalty == 1 - - gb.close() + with Gradebook(db) as gb: + submission = gb.find_submission("ps1", "foo") + nb = submission.notebooks[0] + assert nb.score == 1 + assert submission.total_seconds_late > 0 + assert nb.late_submission_penalty == nb.score + + # 1h late + submission = gb.find_submission("ps1", "bar") + nb = submission.notebooks[0] + assert nb.score == 2 + assert submission.total_seconds_late > 0 + assert nb.late_submission_penalty == 1 def test_force(self, db, course_dir): """Ensure the force option works properly""" @@ -617,11 +606,10 @@ def test_hidden_tests_single_notebook(self, db, course_dir): "as it is, you WILL NOT" ) - gb = Gradebook(db) - submission = gb.find_submission("ps1", "foo") - nb1 = submission.notebooks[0] - assert nb1.score == 1.5 - gb.close() + with Gradebook(db) as gb: + submission = gb.find_submission("ps1", "foo") + nb1 = submission.notebooks[0] + assert nb1.score == 1.5 def test_handle_failure(self, course_dir): with open("nbgrader_config.py", "a") as fh: diff --git a/nbgrader/tests/apps/test_nbgrader_db.py b/nbgrader/tests/apps/test_nbgrader_db.py index 81bbb922d..01939fc30 100644 --- a/nbgrader/tests/apps/test_nbgrader_db.py +++ b/nbgrader/tests/apps/test_nbgrader_db.py @@ -4,7 +4,7 @@ from textwrap import dedent from os.path import join -from ...api import open_gradebook, MissingEntry +from ...api import Gradebook, MissingEntry from .. import run_nbgrader from .base import BaseTestApp @@ -39,28 +39,28 @@ def test_no_args(self): def test_student_add(self, db): run_nbgrader(["db", "student", "add", "foo", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name is None assert student.first_name is None assert student.email is None run_nbgrader(["db", "student", "add", "foo", "--last-name=FooBar", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name == "FooBar" assert student.first_name is None assert student.email is None run_nbgrader(["db", "student", "add", "foo", "--first-name=FooBar", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name is None assert student.first_name == "FooBar" assert student.email is None run_nbgrader(["db", "student", "add", "foo", "--email=foo@bar.com", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name is None assert student.first_name is None @@ -68,14 +68,14 @@ def test_student_add(self, db): def test_student_remove(self, db): run_nbgrader(["db", "student", "add", "foo", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name is None assert student.first_name is None assert student.email is None run_nbgrader(["db", "student", "remove", "foo", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: with pytest.raises(MissingEntry): gb.find_student("foo") @@ -90,21 +90,21 @@ def test_student_remove_with_submissions(self, db, course_dir): self._copy_file(join("files", "submitted-unchanged.ipynb"), join(course_dir, "submitted", "foo", "ps1", "p1.ipynb")) run_nbgrader(["autograde", "ps1", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: gb.find_student("foo") # it should fail if we don't run with --force run_nbgrader(["db", "student", "remove", "foo", "--db", db], retcode=1) # make sure we can still find the student - with open_gradebook(db) as gb: + with Gradebook(db) as gb: gb.find_student("foo") # now force it to complete run_nbgrader(["db", "student", "remove", "foo", "--force", "--db", db]) # student should be gone - with open_gradebook(db) as gb: + with Gradebook(db) as gb: with pytest.raises(MissingEntry): gb.find_student("foo") @@ -131,7 +131,7 @@ def test_student_import(self, db, temp_cwd): ).strip()) run_nbgrader(["db", "student", "import", "students.csv", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name == "xyz" assert student.first_name == "abc" @@ -164,7 +164,7 @@ def test_student_import(self, db, temp_cwd): ).strip()) run_nbgrader(["db", "student", "import", "students.csv", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: student = gb.find_student("foo") assert student.last_name == "xyzzzz" assert student.first_name == "abc" @@ -176,23 +176,23 @@ def test_student_import(self, db, temp_cwd): def test_assignment_add(self, db): run_nbgrader(["db", "assignment", "add", "foo", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: assignment = gb.find_assignment("foo") assert assignment.duedate is None run_nbgrader(["db", "assignment", "add", "foo", '--duedate="Sun Jan 8 2017 4:31:22 PM"', "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: assignment = gb.find_assignment("foo") assert assignment.duedate == datetime.datetime(2017, 1, 8, 16, 31, 22) def test_assignment_remove(self, db): run_nbgrader(["db", "assignment", "add", "foo", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: assignment = gb.find_assignment("foo") assert assignment.duedate is None run_nbgrader(["db", "assignment", "remove", "foo", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: with pytest.raises(MissingEntry): gb.find_assignment("foo") @@ -207,21 +207,21 @@ def test_assignment_remove_with_submissions(self, db, course_dir): self._copy_file(join("files", "submitted-unchanged.ipynb"), join(course_dir, "submitted", "foo", "ps1", "p1.ipynb")) run_nbgrader(["autograde", "ps1", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: gb.find_assignment("ps1") # it should fail if we don't run with --force run_nbgrader(["db", "assignment", "remove", "ps1", "--db", db], retcode=1) # make sure we can still find the assignment - with open_gradebook(db) as gb: + with Gradebook(db) as gb: gb.find_assignment("ps1") # now force it to complete run_nbgrader(["db", "assignment", "remove", "ps1", "--force", "--db", db]) # assignment should be gone - with open_gradebook(db) as gb: + with Gradebook(db) as gb: with pytest.raises(MissingEntry): gb.find_assignment("ps1") @@ -248,7 +248,7 @@ def test_assignment_import(self, db, temp_cwd): ).strip()) run_nbgrader(["db", "assignment", "import", "assignments.csv", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: assignment = gb.find_assignment("foo") assert assignment.duedate == datetime.datetime(2017, 1, 8, 16, 31, 22) assignment = gb.find_assignment("bar") @@ -277,7 +277,7 @@ def test_assignment_import(self, db, temp_cwd): ).strip()) run_nbgrader(["db", "assignment", "import", "assignments.csv", "--db", db]) - with open_gradebook(db) as gb: + with Gradebook(db) as gb: assignment = gb.find_assignment("foo") assert assignment.duedate == datetime.datetime(2017, 1, 8, 16, 31, 22) assignment = gb.find_assignment("bar")