Skip to content

Commit 7a09e72

Browse files
committed
refactor: reduce reliance on regex in path handling
This change tries to modernize the way we handle paths in nbgrader by using pathlib.Path and centralizing logic for searching and extracting information from paths into CourseDirectory. By using `Path(root).glob(pat)` instead of `glob.glob(root / pat)`, we can avoid some issues of needing to escape CourseDirectory.root because the glob will be scoped underneath a fixed path. Introduces a new `CourseDirectory.find_assignments` method to help locate assignment directories across steps and students. Fixes #1965
1 parent 99e145b commit 7a09e72

File tree

12 files changed

+357
-308
lines changed

12 files changed

+357
-308
lines changed

nbgrader/apps/api.py

Lines changed: 21 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import logging
66
import warnings
7+
from pathlib import Path
78

89
from traitlets.config import LoggingConfigurable, Config, get_config
910
from traitlets import Instance, Enum, Unicode, observe
@@ -123,7 +124,7 @@ def gradebook(self):
123124
"""
124125
return Gradebook(self.coursedir.db_url, self.course_id)
125126

126-
def get_source_assignments(self):
127+
def get_source_assignments(self) -> set[str]:
127128
"""Get the names of all assignments in the `source` directory.
128129
129130
Returns
@@ -132,29 +133,14 @@ def get_source_assignments(self):
132133
A set of assignment names
133134
134135
"""
135-
filenames = glob.glob(self.coursedir.format_path(
136-
self.coursedir.source_directory,
137-
student_id='.',
138-
assignment_id='*'))
139-
140-
assignments = set([])
141-
for filename in filenames:
142-
# skip files that aren't directories
143-
if not os.path.isdir(filename):
144-
continue
145136

146-
# parse out the assignment name
147-
regex = self.coursedir.format_path(
148-
self.coursedir.source_directory,
149-
student_id='.',
150-
assignment_id='(?P<assignment_id>.*)',
151-
escape=True)
152-
153-
matches = re.match(regex, filename)
154-
if matches:
155-
assignments.add(matches.groupdict()['assignment_id'])
156-
157-
return assignments
137+
return {
138+
entry['assignment_id'] for entry in
139+
self.coursedir.find_assignments(
140+
nbgrader_step=self.coursedir.source_directory,
141+
student_id=".",
142+
)
143+
}
158144

159145
def get_released_assignments(self):
160146
"""Get the names of all assignments that have been released to the
@@ -194,32 +180,14 @@ def get_submitted_students(self, assignment_id):
194180
A set of student ids
195181
196182
"""
197-
# get the names of all student submissions in the `submitted` directory
198-
filenames = glob.glob(self.coursedir.format_path(
199-
self.coursedir.submitted_directory,
200-
student_id='*',
201-
assignment_id=assignment_id))
202183

203-
students = set([])
204-
for filename in filenames:
205-
# skip files that aren't directories
206-
if not os.path.isdir(filename):
207-
continue
208-
209-
# parse out the student id
210-
if assignment_id == "*":
211-
assignment_id = ".*"
212-
regex = self.coursedir.format_path(
213-
self.coursedir.submitted_directory,
214-
student_id='(?P<student_id>.*)',
215-
assignment_id=assignment_id,
216-
escape=True)
217-
218-
matches = re.match(regex, filename)
219-
if matches:
220-
students.add(matches.groupdict()['student_id'])
221-
222-
return students
184+
return {
185+
entry['student_id'] for entry in
186+
self.coursedir.find_assignments(
187+
nbgrader_step=self.coursedir.submitted_directory,
188+
assignment_id=assignment_id
189+
)
190+
}
223191

224192
def get_submitted_timestamp(self, assignment_id, student_id):
225193
"""Gets the timestamp of a submitted assignment.
@@ -243,10 +211,7 @@ def get_submitted_timestamp(self, assignment_id, student_id):
243211
student_id,
244212
assignment_id))
245213

246-
timestamp_pth = os.path.join(assignment_dir, 'timestamp.txt')
247-
if os.path.exists(timestamp_pth):
248-
with open(timestamp_pth, 'r') as fh:
249-
return parse_utc(fh.read().strip())
214+
return self.coursedir.get_existing_timestamp(assignment_dir)
250215

251216
def get_autograded_students(self, assignment_id):
252217
"""Get the ids of students whose submission for a given assignment
@@ -439,21 +404,14 @@ def get_notebooks(self, assignment_id):
439404

440405
# if it doesn't exist in the database
441406
else:
442-
sourcedir = self.coursedir.format_path(
443-
self.coursedir.source_directory,
444-
student_id='.',
445-
assignment_id=assignment_id)
446-
escaped_sourcedir = self.coursedir.format_path(
407+
sourcedir = Path(self.coursedir.format_path(
447408
self.coursedir.source_directory,
448409
student_id='.',
449-
assignment_id=assignment_id,
450-
escape=True)
410+
assignment_id=assignment_id))
451411

452412
notebooks = []
453-
for filename in glob.glob(os.path.join(sourcedir, "*.ipynb")):
454-
regex = re.escape(os.path.sep).join([escaped_sourcedir, "(?P<notebook_id>.*).ipynb"])
455-
matches = re.match(regex, filename)
456-
notebook_id = matches.groupdict()['notebook_id']
413+
for filename in sourcedir.glob("*.ipynb"):
414+
notebook_id = filename.stem
457415
notebooks.append({
458416
"name": notebook_id,
459417
"id": None,

nbgrader/converters/autograde.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,18 @@ def _init_preprocessors(self) -> None:
181181
for pp in preprocessors:
182182
self.exporter.register_preprocessor(pp)
183183

184-
def convert_single_notebook(self, notebook_filename: str) -> None:
184+
def convert_single_notebook(self, notebook_filename: str, assignment_id: str, student_id: str) -> None:
185185
self.log.info("Sanitizing %s", notebook_filename)
186186
self._sanitizing = True
187187
self._init_preprocessors()
188-
super(Autograde, self).convert_single_notebook(notebook_filename)
188+
super(Autograde, self).convert_single_notebook(notebook_filename, assignment_id, student_id)
189189

190190
notebook_filename = os.path.join(self.writer.build_directory, os.path.basename(notebook_filename))
191191
self.log.info("Autograding %s", notebook_filename)
192192
self._sanitizing = False
193193
self._init_preprocessors()
194194
try:
195195
with utils.setenv(NBGRADER_EXECUTION='autograde'):
196-
super(Autograde, self).convert_single_notebook(notebook_filename)
196+
super(Autograde, self).convert_single_notebook(notebook_filename, assignment_id, student_id)
197197
finally:
198198
self._sanitizing = True

0 commit comments

Comments
 (0)