nbexchange is an extension to nbgrader which provides a mechanism for assignments to transferred in a distributed Jupyter Notebooks environment. (See https://nbgrader.readthedocs.io/en/stable/exchange/exchange_api.html for documentation on how nbgrader expects to operate.)
Configuration documentation is in the README.md
Fundamentally, the exchange revolves around action
table - this is where we record who does what, and the location of the file is held.
The location follows a standard format:
path.join(
base_storage_location,
org_id,
action,
course_code,
assignment_code,
time.now(),
filename
)
Lets follow an assignment cycle, and see how the exchange records everything
In all cases, the user is authenticated using the get_current_user
method, and subscribed to the course
with the role
defined in that call.
All calls check that the user is subscribed to the course given in the parameter
GET /assignments?course_id=$cid
Get list of all assignments associated with that course. We return a list of all released
assignments.
POST /assignment?course_id=$cid&assignment_id=$aid, files = _zip-file_
We verify the user is an instructor
, and subscribed to the course.
- Creates the assignment and link it to the course,
- Grabs the first uploaded file (we use
.zip
files for assignments) and store it in a location, - Creates an
action
record, notingaction=released
, the assignment, file location, who did the action, and add a timestamp
GET /assignment?course_id=$cid&assignment_id=$aid
- Finds the
released
action for that assignment, and download the file from the givenlocation
- Creates an
action
record, notingaction=fetched
, the assignment, file location, who did the action, and add a timestamp
POST /submission?course_id=$cid&assignment_id=$aid, files = _zip-file_
- Grabs the first uploaded file (we use
.zip
files for assignments) and store it in a location, - Creates an
action
record, notingaction=submitted
, the assignment, file location, who did the action, and add a timestamp
We verify the user is an instructor
, and subscribed to the course.
Get a list of all available submissions (GET /collections?course_id=$cid&assignment_id=$aid
- optional &user_id=$uid
)
For each submission listed:
- Download the file from the given
location
- Add the student details to the
gradebook
database - Create an
action
record, notingaction=collected
, the assignment, file location, who did the action, and add a timestamp
We verify the user is an instructor
, and subscribed to the course.
Each autograded .ipynb
file has a matching .html
file - For each .html
file:
- Grabs the first uploaded file (POST
/feedback?course_id=$cid&assignment_id=$aid¬ebook=$nb&student=$sid×tamp=$ts&checksum=$cs
, files = text-file), and store it in a location,timestamp ($ts)
, in this instance, it the timestamp recorded from the student submission.
- Records the details, noting the notebook, the instructor (this.user), the student ($sid), and the given timestamp ($ts)
- Creates an
action
record, notingaction=feedback_released
, the assignment, file location, who did the action, and add a timestamp
GET /feedback?course_id=$cid&assignment_id=$aid
- Downloads all the feedback for the current user, for the given course & assignment.
- Note that the (
.html
) feedback files are held asbase64-encoded
content in the returned data-object. - Creates an
action
record, notingaction=feedback_fetched
, the assignment, file location, who did the action, and add a timestamp
GET /history?course_id=$cid&action=$acc
Gets a list of courses, assignments, and actions - optionally filtered by course_id
and/or action
Users will see all the released
action for the courses they're subscribed to, plus any actions they themselved perfomed.
If a user has been an Instructor
on a course, they can see all the actions for that course.
All URLs relative to /services/nbexchange
.../assignments?course_id=$course_code
GET: returns list of assignments
Returns
{"success": True,
"value": [{
"assignment_id": "$assignment_code",
"course_id": "$course_code",
"student_id": Int
"status": Str,
"path": path,
"notebooks": [
{ "notebook_id": x.name,
"has_exchange_feedback": False,
"feedback_updated": False,
"feedback_timestamp": None, } for x in assignment.notebooks],
"timestamp": action.timestamp.strftime(
"%Y-%m-%d %H:%M:%S.%f %Z"
),
},
{},..
]}
or
{"success": False, "note": $note}
.../assignment?course_id=$course_code&assignment_id=$assignment_code
GET: downloads assignment
Returns binary data or raises Exception (which is returned as a 503
error)
POST: (role=instructor, with file): Add ("release") an assignment returns
{"success": True, "note": "Released"}
or raises Exception (which is returned as a 503
error)
DELETE: (role=instructor, with file): Remove an assignment.
Marks an asiignment as active: False
, and forgets any associated notebooks. Returns
{"success": True,
"note": "Assignment '$assignment_code' on course '$course_code' marked as unreleased by user $user"
}
Takes as optional parameter purge
. This will delete the notebooks, the assignment,
and any associated data (actions
, feedback
, etc). Returns
{"success": True,
"note": "Assignment '$assignment_code' on course '$course_code' deleted and purged from the database by user $user"
}
If there are permission issues, returns
{"success": False, "note": $note}
.../submission?course_id=$course_code&assignment_id=$assignment_code
POST: stores the submission for that user returns
{"success": True, "note": "Released"}
or raises Exception (which is returned as a 503
error)
.../collections?course_id=$course_code&assignment_id=$assignment_code
GET: gets a list of submitted items
Return: same as Assignments <#assignments>
.../collections?course_id=$course_code&assignment_id=$assignment_code&path=$url_encoded_path
GET: downloads submitted assignment
Return: similar to Assignment <#assignment>
, but adds the full_name
, email
, and lms_user_id
fields
along-side student_id
et al.
GET: downloads feedback
.../feedback?course_id=$course_code&assignment_id=$assignment_code
Optional parameter
user_id=$user_id
Return: Returns a data structure similar to the above, except value
is a list of feedback items:
{"success": True,
"value": [{
"content": base64-encoded html file
"filename": notebook-name.html
"timestamp": timestamp.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
"checksum": checksum
},
{},..
]}
POST: uploads feedback (one notebook at a time)
.../feedback?course_id=$course_code&assignment_id=$assignment_code¬ebook=$nb_name&student=$sid×tamp=$ts&checksum=$abc123
If there are permission issues, returns
{"success": False, "note": $note}
else
{"success": True, "note": "Feedback released"}
or raises an error - should be a 404 or 412.
.../history?course_id=$course_code&action=$action
GET: returns list of actions, grouped by course, and then assignment
Returns
{"success": True,
"value": [{
course_id: Int,
course_code: Str,
course_title: Str,
role: [Str, Str, ..],
user_id: [Str, Str, ..],
isInstructor: Bool,
assignments: [
{
assignment_code: Str,
assignment_id: Int
actions: [
{
action: Str,
timestamp: timestamp.strftime("%Y-%m-%d %H:%M:%S.%f %Z"),
user: Str
},
{...},
],
"action_summary": {
"released": Int
"submitted": Int
...
},
},
],
},
{...},
]}
or
{"success": False, "note": $note}