Skip to content

Commit

Permalink
Merge pull request #696 from jhamrick/gradebook-contextmanager
Browse files Browse the repository at this point in the history
Turn the gradebook into a context manager
  • Loading branch information
willingc authored Feb 6, 2017
2 parents 7b456d5 + 5dc3b8a commit 34d9351
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 337 deletions.
15 changes: 6 additions & 9 deletions nbgrader/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
88 changes: 41 additions & 47 deletions nbgrader/apps/assignapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<assignment_id>.*)", "(?P<student_id>.*)", escape=True),
"(?P<notebook_id>.*).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<assignment_id>.*)", "(?P<student_id>.*)", escape=True),
"(?P<notebook_id>.*).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)
Expand All @@ -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
Expand Down
74 changes: 34 additions & 40 deletions nbgrader/apps/autogradeapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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'?")
Expand Down
18 changes: 9 additions & 9 deletions nbgrader/apps/dbapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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))
Expand Down
7 changes: 2 additions & 5 deletions nbgrader/apps/exportapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
76 changes: 36 additions & 40 deletions nbgrader/docs/source/user_guide/extract_grades.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Loading

0 comments on commit 34d9351

Please sign in to comment.