Skip to content

Commit 92ad577

Browse files
author
Ludvig Kihlman
committed
Merge with master:bee19dc
2 parents 027a0d9 + bee19dc commit 92ad577

File tree

12 files changed

+114
-30
lines changed

12 files changed

+114
-30
lines changed

.travis.yml

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
language: python
22
python:
3-
- 3.5
43
- 3.6
54
- 3.7
5+
- 3.8
66
- nightly
77
env:
88
global:
@@ -13,22 +13,28 @@ env:
1313
- GROUP=python
1414
matrix:
1515
exclude:
16-
- python: 3.5
17-
env: GROUP=docs
18-
- python: 3.6
19-
env: GROUP=docs
20-
- python: nightly
21-
env: GROUP=docs
22-
- python: 3.5
23-
env: GROUP=nbextensions
24-
- python: 3.6
25-
env: GROUP=nbextensions
26-
- python: nightly
27-
env: GROUP=nbextensions
16+
- python: 3.6
17+
env: GROUP=docs
18+
- python: 3.7
19+
env: GROUP=docs
20+
- python: 3.8
21+
env: GROUP=docs
22+
- python: nightly
23+
env: GROUP=docs
24+
- python: 3.6
25+
env: GROUP=nbextensions
26+
- python: 3.7
27+
env: GROUP=nbextensions
28+
- python: 3.8
29+
env: GROUP=nbextensions
30+
- python: nightly
31+
env: GROUP=nbextensions
32+
allow_failures:
33+
- python: nightly
2834
addons:
2935
apt:
3036
packages:
31-
- pandoc
37+
- pandoc
3238
firefox: latest
3339
before_install:
3440
- pip install -U pip wheel setuptools

azure-pipelines.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ strategy:
2424
Python37Extensions:
2525
python.version: '3.7'
2626
testgroup: 'nbextensions'
27+
Python38:
28+
python.version: '3.8'
29+
testgroup: 'python'
30+
Python38Extensions:
31+
python.version: '3.8'
32+
testgroup: 'nbextensions'
2733

2834
steps:
2935
- script: |

dev-requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pytest>=4.5
22
pytest-cov
33
pytest-rerunfailures
44
pytest-xdist
5-
pytest-mypy==0.4.2
5+
#pytest-mypy
66
coverage
77
selenium
88
sphinx
@@ -11,3 +11,5 @@ sphinx-autodoc-typehints
1111
codecov
1212
nbval
1313
requests-mock
14+
wheel
15+

nbgrader/apps/collectapp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
{'ExchangeCollect' : {'update': True}},
2121
"Update existing submissions with ones that have newer timestamps."
2222
),
23+
'before-duedate': (
24+
{'ExchangeCollect' : {'before_duedate': True}},
25+
"Collect the last submission before due date or the last submission if no submission before due date."
26+
),
2327
})
2428

2529
class CollectApp(NbGrader):

nbgrader/converters/base.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sqlalchemy
66
import traceback
77

8+
from rapidfuzz import fuzz
89
from traitlets.config import LoggingConfigurable, Config
910
from traitlets import Bool, List, Dict, Integer, Instance, Type
1011
from traitlets import default
@@ -120,11 +121,6 @@ def init_notebooks(self) -> None:
120121
assignment_glob2 = self._format_source("*", self.coursedir.student_id)
121122
found = glob.glob(assignment_glob2)
122123
if found:
123-
# Normally it is a bad idea to put imports in the middle of
124-
# a function, but we do this here because otherwise fuzzywuzzy
125-
# prints an annoying message about python-Levenshtein every
126-
# time nbgrader is run.
127-
from fuzzywuzzy import fuzz
128124
scores = sorted([(fuzz.ratio(assignment_glob, x), x) for x in found])
129125
self.log.error("Did you mean: %s", scores[-1][1])
130126

nbgrader/exchange/abc/collect.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ class ExchangeCollect(Exchange):
1010
help="Update existing submissions with ones that have newer timestamps."
1111
).tag(config=True)
1212

13+
before_duedate = Bool(
14+
False,
15+
help="Collect the last submission before due date or the last submission if no submission before due date."
16+
).tag(config=True)
17+
1318
check_owner = Bool(
1419
default_value=True,
1520
help="Whether to cross-check the student_id with the UNIX-owner of the submitted directory."

nbgrader/exchange/default/collect.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import sys
55
from collections import defaultdict
66
from textwrap import dedent
7+
import datetime
78

89
from nbgrader.exchange.abc import ExchangeCollect as ABCExchangeCollect
910
from .exchange import Exchange
1011

1112
from nbgrader.utils import check_mode, parse_utc
13+
from ...api import Gradebook, MissingEntry
14+
from ...utils import check_mode, parse_utc
1215

1316
# pwd is for matching unix names with student ide, so we shouldn't import it on
1417
# windows machines
@@ -54,7 +57,24 @@ def init_src(self):
5457
pattern = os.path.join(self.inbound_path, '{}+{}+*'.format(student_id, self.coursedir.assignment_id))
5558
records = [self._path_to_record(f) for f in glob.glob(pattern)]
5659
usergroups = groupby(records, lambda item: item['username'])
57-
self.src_records = [self._sort_by_timestamp(v)[0] for v in usergroups.values()]
60+
61+
with Gradebook(self.coursedir.db_url, self.coursedir.course_id) as gb:
62+
try:
63+
assignment = gb.find_assignment(self.coursedir.assignment_id)
64+
self.duedate = assignment.duedate
65+
except MissingEntry:
66+
self.duedate = None
67+
if self.duedate is None or not self.before_duedate:
68+
self.src_records = [self._sort_by_timestamp(v)[0] for v in usergroups.values()]
69+
else:
70+
self.src_records = []
71+
for v in usergroups.values():
72+
records = self._sort_by_timestamp(v)
73+
records_before_duedate = [record for record in records if record['timestamp'] <= self.duedate]
74+
if records_before_duedate:
75+
self.src_records.append(records_before_duedate[0])
76+
else:
77+
self.src_records.append(records[0])
5878

5979
def init_dest(self):
6080
pass
@@ -99,6 +119,10 @@ def copy_files(self):
99119
if self.update and (existing_timestamp is None or new_timestamp > existing_timestamp):
100120
copy = True
101121
updating = True
122+
elif self.before_duedate and existing_timestamp != new_timestamp:
123+
copy = True
124+
updating = True
125+
102126
else:
103127
copy = True
104128

nbgrader/exchange/default/exchange.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from textwrap import dedent
88

9+
10+
from rapidfuzz import fuzz
911
from traitlets import Unicode, Bool, default
1012
from jupyter_core.paths import jupyter_data_dir
1113

@@ -117,11 +119,6 @@ def _assignment_not_found(self, src_path, other_path):
117119
self.log.fatal(msg)
118120
found = glob.glob(other_path)
119121
if found:
120-
# Normally it is a bad idea to put imports in the middle of
121-
# a function, but we do this here because otherwise fuzzywuzzy
122-
# prints an annoying message about python-Levenshtein every
123-
# time nbgrader is run.
124-
from fuzzywuzzy import fuzz
125122
scores = sorted([(fuzz.ratio(self.src_path, x), x) for x in found])
126123
self.log.error("Did you mean: %s", scores[-1][1])
127124

nbgrader/exchange/default/fetch_assignment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ def _load_config(self, cfg, **kwargs):
1414
"Use ExchangeFetchAssignment in config, not ExchangeFetch. Outdated config:\n%s",
1515
'\n'.join(
1616
'ExchangeFetch.{key} = {value!r}'.format(key=key, value=value)
17-
for key, value in cfg.ExchangeFetchAssignment.items()
17+
for key, value in cfg.ExchangeFetch.items()
1818
)
1919
)
2020
cfg.ExchangeFetchAssignment.merge(cfg.ExchangeFetch)
21-
del cfg.ExchangeFetchAssignment
21+
del cfg.ExchangeFetch
2222

2323
super(ExchangeFetchAssignment, self)._load_config(cfg, **kwargs)
2424

nbgrader/tests/apps/test_nbgrader_collect.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import os
23
import time
34
import pytest
@@ -7,6 +8,7 @@
78
from .. import run_nbgrader
89
from .base import BaseTestApp
910
from .conftest import notwindows
11+
from ...api import Gradebook
1012
from ...utils import parse_utc, get_username
1113

1214

@@ -151,3 +153,45 @@ def test_permissions(self, exchange, course_dir, cache, groupshared):
151153
assert self._get_permissions(join(exchange, "abc101", "inbound")) == ("2733" if not groupshared else "2773")
152154
assert self._get_permissions(join(course_dir, "submitted", "foobar_student", "ps1")) == ("777" if not groupshared else "2777")
153155
assert self._get_permissions(join(course_dir, "submitted", "foobar_student", "ps1", "p1.ipynb")) == ("644" if not groupshared else "664")
156+
157+
@pytest.mark.parametrize('before_duedate',
158+
['yes', 'no', 'nofirst'])
159+
def test_collect_before_due_date(self, exchange, course_dir, cache, db, before_duedate):
160+
"""Test --before-duedate flag.
161+
162+
Test is parameterized so we test both with it and without the flag.
163+
164+
'yes': test with --before-duedate
165+
'no': test without
166+
'nofirst': test with --before-duedate but no assignment before duedate
167+
168+
"""
169+
# Release assignment
170+
self._release_and_fetch("ps1", exchange, course_dir)
171+
172+
# Submit something, wait, submit again. Due date is between.
173+
if before_duedate != 'nofirst':
174+
# We don't submit first assignment.
175+
self._submit("ps1", exchange, cache)
176+
time.sleep(.05)
177+
time_duedate = datetime.datetime.utcnow()
178+
time.sleep(.05)
179+
self._submit("ps1", exchange, cache)
180+
181+
# Set the due date
182+
with Gradebook(db) as gb:
183+
gb.update_or_create_assignment('ps1', duedate=time_duedate)
184+
185+
# Collect
186+
flags = ['--db', db]
187+
if before_duedate != 'no':
188+
flags.append('--before-duedate')
189+
self._collect("ps1", exchange, flags=flags)
190+
191+
root = os.path.os.path.join(os.path.join(course_dir, "submitted", get_username(), "ps1"))
192+
timestamp = self._read_timestamp(root)
193+
# Test both ways: with --before-duedate flag and without
194+
if before_duedate == 'yes':
195+
assert timestamp < time_duedate
196+
else: # 'no', 'nofirst'
197+
assert timestamp > time_duedate

0 commit comments

Comments
 (0)