Skip to content

Commit b15613e

Browse files
authored
Merge pull request #1238 from lzach/pluggable_exchange
Pluggable exchange
2 parents bee19dc + 92ad577 commit b15613e

39 files changed

+912
-180
lines changed

nbgrader/apps/api.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import logging
66
import warnings
77

8-
from traitlets.config import LoggingConfigurable, Config
8+
from traitlets.config import LoggingConfigurable, Config, get_config
99
from traitlets import Instance, Enum, Unicode, observe
1010

1111
from ..coursedir import CourseDirectory
1212
from ..converters import GenerateAssignment, Autograde, GenerateFeedback
13-
from ..exchange import ExchangeList, ExchangeReleaseAssignment, ExchangeReleaseFeedback, ExchangeFetchFeedback, ExchangeCollect, ExchangeError, ExchangeSubmit
13+
from ..exchange import ExchangeFactory, ExchangeError
1414
from ..api import MissingEntry, Gradebook, Student, SubmittedAssignment
1515
from ..utils import parse_utc, temp_attrs, capture_log, as_timezone, to_numeric_tz
1616
from ..auth import Authenticator
@@ -21,6 +21,7 @@ class NbGraderAPI(LoggingConfigurable):
2121

2222
coursedir = Instance(CourseDirectory, allow_none=True)
2323
authenticator = Instance(Authenticator, allow_none=True)
24+
exchange = Instance(ExchangeFactory, allow_none=True)
2425

2526
# The log level for the application
2627
log_level = Enum(
@@ -48,7 +49,7 @@ def _log_level_changed(self, change):
4849
self.log_level = new
4950
self.log.setLevel(new)
5051

51-
def __init__(self, coursedir=None, authenticator=None, **kwargs):
52+
def __init__(self, coursedir=None, authenticator=None, exchange=None, **kwargs):
5253
"""Initialize the API.
5354
5455
Arguments
@@ -58,6 +59,9 @@ def __init__(self, coursedir=None, authenticator=None, **kwargs):
5859
authenticator : :class:~`nbgrader.auth.BaseAuthenticator`
5960
(Optional) An authenticator instance for communicating with an
6061
external database.
62+
exchange : :class:~`nbgrader.exchange.ExchangeFactory`
63+
(Optional) A factory for creating the exchange classes used
64+
for distributing assignments and feedback.
6165
kwargs:
6266
Additional keyword arguments (e.g. ``parent``, ``config``)
6367
@@ -75,13 +79,22 @@ def __init__(self, coursedir=None, authenticator=None, **kwargs):
7579
else:
7680
self.authenticator = authenticator
7781

82+
if exchange is None:
83+
self.exchange = ExchangeFactory(parent=self)
84+
else:
85+
self.exchange = exchange
86+
7887
if sys.platform != 'win32':
79-
lister = ExchangeList(
88+
lister = self.exchange.List(
8089
coursedir=self.coursedir,
8190
authenticator=self.authenticator,
8291
parent=self)
8392
self.course_id = self.coursedir.course_id
84-
self.exchange = lister.root
93+
if hasattr(lister, "root"):
94+
self.exchange_root = lister.root
95+
else:
96+
# For non-fs based exchanges
97+
self.exchange_root = ''
8598

8699
try:
87100
lister.start()
@@ -92,7 +105,7 @@ def __init__(self, coursedir=None, authenticator=None, **kwargs):
92105

93106
else:
94107
self.course_id = ''
95-
self.exchange = ''
108+
self.exchange_root = ''
96109
self.exchange_missing = True
97110

98111
@property
@@ -155,7 +168,7 @@ def get_released_assignments(self):
155168
156169
"""
157170
if self.exchange_is_functional:
158-
lister = ExchangeList(
171+
lister = self.exchange.List(
159172
coursedir=self.coursedir,
160173
authenticator=self.authenticator,
161174
parent=self)
@@ -640,7 +653,7 @@ def _filter_existing_notebooks(self, assignment_id, notebooks):
640653
# should be here already so we don't need to filter for only
641654
# existing notebooks in that case.
642655
if self.exchange_is_functional:
643-
app = ExchangeSubmit(
656+
app = self.exchange.Submit(
644657
coursedir=self.coursedir,
645658
authenticator=self.authenticator,
646659
parent=self)
@@ -924,7 +937,7 @@ def unrelease(self, assignment_id):
924937
"""
925938
if sys.platform != 'win32':
926939
with temp_attrs(self.coursedir, assignment_id=assignment_id):
927-
app = ExchangeList(
940+
app = self.exchange.List(
928941
coursedir=self.coursedir,
929942
authenticator=self.authenticator,
930943
parent=self)
@@ -960,7 +973,7 @@ def release_assignment(self, assignment_id):
960973
"""
961974
if sys.platform != 'win32':
962975
with temp_attrs(self.coursedir, assignment_id=assignment_id):
963-
app = ExchangeReleaseAssignment(
976+
app = self.exchange.ReleaseAssignment(
964977
coursedir=self.coursedir,
965978
authenticator=self.authenticator,
966979
parent=self)
@@ -989,7 +1002,7 @@ def collect(self, assignment_id, update=True):
9891002
"""
9901003
if sys.platform != 'win32':
9911004
with temp_attrs(self.coursedir, assignment_id=assignment_id):
992-
app = ExchangeCollect(
1005+
app = self.exchange.Collect(
9931006
coursedir=self.coursedir,
9941007
authenticator=self.authenticator,
9951008
parent=self)
@@ -1095,14 +1108,14 @@ def release_feedback(self, assignment_id, student_id=None):
10951108
"""
10961109
if student_id is not None:
10971110
with temp_attrs(self.coursedir, assignment_id=assignment_id, student_id=student_id):
1098-
app = ExchangeReleaseFeedback(
1111+
app = self.exchange.ReleaseFeedback(
10991112
coursedir=self.coursedir,
11001113
authentictor=self.authenticator,
11011114
parent=self)
11021115
return capture_log(app)
11031116
else:
11041117
with temp_attrs(self.coursedir, assignment_id=assignment_id, student_id='*'):
1105-
app = ExchangeReleaseFeedback(
1118+
app = self.exchange.ReleaseFeedback(
11061119
coursedir=self.coursedir,
11071120
authentictor=self.authenticator,
11081121
parent=self)
@@ -1130,15 +1143,15 @@ def fetch_feedback(self, assignment_id, student_id):
11301143
11311144
"""
11321145
with temp_attrs(self.coursedir, assignment_id=assignment_id, student_id=student_id):
1133-
app = ExchangeFetchFeedback(
1146+
app = self.exchange.FetchFeedback(
11341147
coursedir=self.coursedir,
11351148
authentictor=self.authenticator,
11361149
parent=self)
11371150
ret_dic = capture_log(app)
11381151
# assignment tab needs a 'value' field with the info needed to repopulate
11391152
# the tables.
11401153
with temp_attrs(self.coursedir, assignment_id='*', student_id=student_id):
1141-
lister_rel = ExchangeList(
1154+
lister_rel = self.exchange.List(
11421155
inbound=False, cached=True,
11431156
coursedir=self.coursedir,
11441157
authenticator=self.authenticator,

nbgrader/apps/baseapp.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from traitlets.config.application import catch_config_error
1717
from traitlets.config.loader import Config
1818

19+
from nbgrader.exchange import ExchangeFactory
1920
from ..coursedir import CourseDirectory
2021
from ..auth import Authenticator
2122
from .. import preprocessors
@@ -117,14 +118,15 @@ def deinit_logging(self) -> None:
117118

118119
coursedir = Instance(CourseDirectory, allow_none=True)
119120
authenticator = Instance(Authenticator, allow_none=True)
121+
exchange = Instance(ExchangeFactory, allow_none=True)
120122
verbose_crash = Bool(False)
121123

122124
# The classes added here determine how configuration will be documented
123125
classes = List()
124126

125127
@default("classes")
126128
def _classes_default(self) -> TypingList[MetaHasTraits]:
127-
return [NbGrader, CourseDirectory]
129+
return [ExchangeFactory, NbGrader, CourseDirectory]
128130

129131
def all_configurable_classes(self) -> TypingList[MetaHasTraits]:
130132
"""Get a list of all configurable classes for nbgrader
@@ -162,6 +164,12 @@ def all_configurable_classes(self) -> TypingList[MetaHasTraits]:
162164
if hasattr(ex, "class_traits") and ex.class_traits(config=True):
163165
classes.append(ex)
164166

167+
# include all the default exchange actions
168+
for ex_name in exchange.default.__all__:
169+
ex = getattr(exchange, ex_name)
170+
if hasattr(ex, "class_traits") and ex.class_traits(config=True):
171+
classes.append(ex)
172+
165173
# include all the converters
166174
for ex_name in converters.__all__:
167175
ex = getattr(converters, ex_name)
@@ -352,3 +360,5 @@ def load_config_file(self, **kwargs: Any) -> None:
352360
def start(self) -> None:
353361
super(NbGrader, self).start()
354362
self.authenticator = Authenticator(parent=self)
363+
self.exchange = ExchangeFactory(parent=self)
364+

nbgrader/apps/collectapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def start(self):
9696
elif self.coursedir.assignment_id == "":
9797
self.fail("Must provide assignment name:\nnbgrader <command> ASSIGNMENT [ --course COURSE ]")
9898

99-
collect = ExchangeCollect(
99+
collect = self.exchange.Collect(
100100
coursedir=self.coursedir,
101101
authenticator=self.authenticator,
102102
parent=self)

nbgrader/apps/dbapp.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
from . import NbGrader
1414
from ..api import Gradebook, MissingEntry, Student, Assignment
15-
from ..exchange import ExchangeList
1615
from .. import dbutil
1716

1817
aliases = {
@@ -36,7 +35,6 @@ class DbBaseApp(NbGrader):
3635

3736
def start(self):
3837
if sys.platform != 'win32':
39-
lister = ExchangeList(coursedir=self.coursedir, parent=self)
4038
self.course_id = self.coursedir.course_id
4139
else:
4240
self.course_id = ''

nbgrader/apps/fetchassignmentapp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def start(self):
8080
self.fail("Must provide assignment name:\nnbgrader fetch_assignment ASSIGNMENT [ --course COURSE ]")
8181

8282
if self.coursedir.assignment_id != "":
83-
fetch = ExchangeFetchAssignment(
83+
fetch = self.exchange.FetchAssignment(
8484
coursedir=self.coursedir,
8585
authenticator=self.authenticator,
8686
parent=self)
@@ -93,7 +93,7 @@ def start(self):
9393

9494
for arg in self.extra_args:
9595
self.coursedir.assignment_id = arg
96-
fetch = ExchangeFetchAssignment(
96+
fetch = self.exchange.FetchAssignment(
9797
coursedir=self.coursedir,
9898
authenticator=self.authenticator,
9999
parent=self)

nbgrader/apps/fetchfeedbackapp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def start(self):
6464
self.fail("Must provide assignment name:\nnbgrader fetch_feedback ASSIGNMENT [ --course COURSE ]")
6565

6666
if self.coursedir.assignment_id != "":
67-
fetch = ExchangeFetchFeedback(
67+
fetch = self.exchange.FetchFeedback(
6868
coursedir=self.coursedir,
6969
authenticator=self.authenticator,
7070
parent=self)
@@ -77,7 +77,7 @@ def start(self):
7777

7878
for arg in self.extra_args:
7979
self.coursedir.assignment_id = arg
80-
fetch = ExchangeFetchFeedback(
80+
fetch = self.exchange.FetchFeedback(
8181
coursedir=self.coursedir,
8282
authenticator=self.authenticator,
8383
parent=self)

nbgrader/apps/listapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def start(self):
117117
elif len(self.extra_args) > 2:
118118
self.fail("Too many arguments")
119119

120-
lister = ExchangeList(
120+
lister = self.exchange.List(
121121
coursedir=self.coursedir,
122122
authenticator=self.authenticator,
123123
parent=self)

nbgrader/apps/releaseassignmentapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def start(self):
9999
elif self.coursedir.assignment_id == "":
100100
self.fail("Must provide assignment name:\nnbgrader <command> ASSIGNMENT [ --course COURSE ]")
101101

102-
release = ExchangeReleaseAssignment(
102+
release = self.exchange.ReleaseAssignment(
103103
coursedir=self.coursedir,
104104
authenticator=self.authenticator,
105105
parent=self)

nbgrader/apps/releasefeedbackapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def start(self):
6060
elif self.coursedir.assignment_id == "":
6161
self.fail("Must provide assignment name:\nnbgrader <command> ASSIGNMENT [ --course COURSE ]")
6262

63-
release_feedback = ExchangeReleaseFeedback(
63+
release_feedback = self.exchange.ReleaseFeedback(
6464
coursedir=self.coursedir,
6565
authenticator=self.authenticator,
6666
parent=self)

nbgrader/apps/submitapp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def start(self):
9595
elif self.coursedir.assignment_id == "":
9696
self.fail("Must provide assignment name:\nnbgrader <command> ASSIGNMENT [ --course COURSE ]")
9797

98-
submit = ExchangeSubmit(
98+
submit = self.exchange.Submit(
9999
coursedir=self.coursedir,
100100
authenticator=self.authenticator,
101101
parent=self)

0 commit comments

Comments
 (0)