Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn the gradebook into a context manager #696

Merged
merged 1 commit into from
Feb 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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