Skip to content

Commit f4a4ea3

Browse files
committed
Add find_notebooks and remove more usages of re
1 parent ebe6778 commit f4a4ea3

File tree

6 files changed

+89
-48
lines changed

6 files changed

+89
-48
lines changed

nbgrader/apps/api.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import glob
2-
import re
31
import sys
42
import os
53
import logging
64
import warnings
75
import typing
8-
from pathlib import Path
96

107
from traitlets.config import LoggingConfigurable, Config, get_config
118
from traitlets import Instance, Enum, Unicode, observe
@@ -386,6 +383,7 @@ def get_notebooks(self, assignment_id):
386383
A list of dictionaries containing information about each notebook
387384
388385
"""
386+
notebooks = []
389387
with self.gradebook as gb:
390388
try:
391389
assignment = gb.find_assignment(assignment_id)
@@ -394,7 +392,6 @@ def get_notebooks(self, assignment_id):
394392

395393
# if the assignment exists in the database
396394
if assignment and assignment.notebooks:
397-
notebooks = []
398395
for notebook in assignment.notebooks:
399396
x = notebook.to_dict()
400397
x["average_score"] = gb.average_notebook_score(notebook.name, assignment.name)
@@ -405,16 +402,13 @@ def get_notebooks(self, assignment_id):
405402

406403
# if it doesn't exist in the database
407404
else:
408-
sourcedir = Path(self.coursedir.format_path(
409-
self.coursedir.source_directory,
405+
for notebook in self.coursedir.find_notebooks(
406+
nbgrader_step=self.coursedir.source_directory,
410407
student_id='.',
411-
assignment_id=assignment_id))
412-
413-
notebooks = []
414-
for filename in sourcedir.glob("*.ipynb"):
415-
notebook_id = filename.stem
408+
assignment_id=assignment_id
409+
):
416410
notebooks.append({
417-
"name": notebook_id,
411+
"name": notebook['notebook_id'],
418412
"id": None,
419413
"average_score": 0,
420414
"average_code_score": 0,

nbgrader/converters/generate_assignment.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
import re
31
from textwrap import dedent
42
from pathlib import Path
53

nbgrader/converters/generate_solution.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
import re
31
from textwrap import dedent
42

53
from traitlets import List, Bool, default

nbgrader/converters/generate_source_with_tests.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import os
2-
import re
3-
41
from traitlets import List, default
52

63
from .base import BaseConverter

nbgrader/coursedir.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def find_assignments(self,
341341
assignment_id: str = "*",
342342
) -> typing.List[typing.Dict]:
343343
"""
344-
Find all entries that match the given criteria.
344+
Find all directories that match the given criteria.
345345
346346
The default value for each acts as a wildcard. To exclude a directory, use
347347
a value of "." (e.g. nbgrader_step="source", student_id=".").
@@ -389,6 +389,65 @@ def find_assignments(self,
389389

390390
return results
391391

392+
def find_notebooks(
393+
self,
394+
nbgrader_step: str = "*",
395+
student_id: str = "*",
396+
assignment_id: str = "*",
397+
notebook_id: str = "*",
398+
ext: str = "ipynb",
399+
) -> typing.List[typing.Dict]:
400+
"""
401+
Find all notebooks that match the given criteria.
402+
403+
The default value for each acts as a wildcard. To exclude a directory, use
404+
a value of "." (e.g. nbgrader_step="source", student_id=".").
405+
406+
Returns:
407+
A list of dicts containing input values, one per matching directory.
408+
"""
409+
410+
results = []
411+
412+
kwargs = dict(
413+
nbgrader_step=nbgrader_step,
414+
student_id=student_id,
415+
assignment_id=assignment_id,
416+
notebook_id=notebook_id,
417+
ext=ext,
418+
)
419+
420+
pattern = os.path.join(self.directory_structure, "{notebook_id}.{ext}")
421+
422+
# Locate all matching files using a glob
423+
files = list(
424+
filter(
425+
lambda p: p.is_file() and not is_ignored(p.name, self.ignore),
426+
Path(self.root).glob(pattern.format(**kwargs))
427+
)
428+
)
429+
430+
if not files:
431+
return results
432+
433+
pattern_args = {
434+
key: value.replace("*", f"(?P<{key}>.*)")
435+
for key, value in kwargs.items()
436+
}
437+
438+
# Convert to a Path and back to a string to remove any instances of `/.`
439+
pattern = str(Path(pattern.format(**pattern_args)))
440+
441+
if sys.platform == 'win32':
442+
# Escape backslashes on Windows
443+
pattern = pattern.replace('\\', r'\\')
444+
445+
for file in files:
446+
match = re.match(pattern, str(file.relative_to(self.root)))
447+
if match:
448+
results.append({ **kwargs, **match.groupdict(), "path": file})
449+
450+
return results
392451

393452
def get_existing_timestamp(self, dest_path: str) -> typing.Optional[datetime.datetime]:
394453
"""Get the timestamp, as a datetime object, of an existing submission."""

nbgrader/exchange/default/release_feedback.py

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import os
22
import shutil
3-
import glob
4-
import re
53
from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IXOTH, S_ISGID
64

75
from nbgrader.exchange.abc import ExchangeReleaseFeedback as ABCExchangeReleaseFeedback
86
from .exchange import Exchange
9-
from nbgrader.utils import notebook_hash, make_unique_key
7+
from nbgrader.utils import notebook_hash
108

119

1210
class ExchangeReleaseFeedback(Exchange, ABCExchangeReleaseFeedback):
@@ -37,40 +35,37 @@ def copy_files(self):
3735
else:
3836
exclude_students = set()
3937

40-
html_files = glob.glob(os.path.join(self.src_path, "*.html"))
41-
for html_file in html_files:
42-
regexp = re.escape(os.path.sep).join([
43-
self.coursedir.format_path(
44-
self.coursedir.feedback_directory,
45-
"(?P<student_id>.*)",
46-
self.coursedir.assignment_id, escape=True),
47-
"(?P<notebook_id>.*).html"
48-
])
49-
50-
m = re.match(regexp, html_file)
51-
if m is None:
52-
msg = "Could not match '%s' with regexp '%s'" % (html_file, regexp)
53-
self.log.error(msg)
54-
continue
55-
56-
gd = m.groupdict()
57-
student_id = gd['student_id']
58-
notebook_id = gd['notebook_id']
38+
for feedback in self.coursedir.find_notebooks(
39+
nbgrader_step=self.coursedir.feedback_directory,
40+
student_id=self.coursedir.student_id or "*",
41+
assignment_id=self.coursedir.assignment_id,
42+
notebook_id="*",
43+
ext="html"
44+
):
45+
html_file = feedback['path']
46+
student_id = feedback['student_id']
5947
if student_id in exclude_students:
6048
self.log.debug("Skipping student '{}'".format(student_id))
6149
continue
6250

63-
feedback_dir = os.path.split(html_file)[0]
51+
notebook_id = feedback['notebook_id']
52+
feedback_dir = html_file.parent
6453

65-
timestamp = open(os.path.join(feedback_dir, 'timestamp.txt')).read()
66-
with open(os.path.join(feedback_dir, "submission_secret.txt")) as fh:
67-
submission_secret = fh.read()
54+
timestamp = feedback_dir.joinpath('timestamp.txt').read_text()
55+
submission_secret = feedback_dir.joinpath("submission_secret.txt").read_text()
6856

6957
checksum = notebook_hash(secret=submission_secret, notebook_id=notebook_id)
7058
dest = os.path.join(self.dest_path, "{}-tmp.html".format(checksum))
7159

72-
self.log.info("Releasing feedback for student '{}' on assignment '{}/{}/{}' ({})".format(
73-
student_id, self.coursedir.course_id, self.coursedir.assignment_id, notebook_id, timestamp))
60+
self.log.info(
61+
"Releasing feedback for student '%s' on assignment '%s/%s/%s' (%s)",
62+
student_id,
63+
self.coursedir.course_id,
64+
feedback["assignment_id"],
65+
notebook_id,
66+
timestamp
67+
)
68+
7469
shutil.copy(html_file, dest)
7570
# Copy to temporary location and mv to update atomically.
7671
updated_feedback = os.path.join(self.dest_path, "{}.html". format(checksum))

0 commit comments

Comments
 (0)