Skip to content

Commit

Permalink
Merge pull request #14 from LibreTexts/master
Browse files Browse the repository at this point in the history
v0.5.1 release
  • Loading branch information
rkevin-arch authored Jun 12, 2020
2 parents 0054af2 + c3e0efb commit 0f0950f
Show file tree
Hide file tree
Showing 27 changed files with 1,423 additions and 286 deletions.
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
omit = ngshare_exchange/tests/*

[report]
precision = 2
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ before_install:
install:
- python3 -m pip install .
before_script:
- python3 -m pip install pytest pytest-cov pytest-tornado black requests_mock
- python3 -m pip install -r testing_reqs.txt
script:
- python3 -m pytest ./ngshare_exchange/ --cov=./ngshare_exchange/
- python3 -m black -S -l 80 --check .
after_success:
- python3 -m pip install "codecov>=2.1.0"
- codecov
deploy:
provider: pypi
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# ngshare_exchange
[![Build Status](https://travis-ci.org/lxylxy123456/ngshare_exchange.svg?branch=master)](https://travis-ci.org/lxylxy123456/ngshare_exchange)
[![codecov](https://codecov.io/gh/lxylxy123456/ngshare_exchange/branch/master/graph/badge.svg)](https://codecov.io/gh/lxylxy123456/ngshare_exchange)
[![Build Status](https://travis-ci.org/LibreTexts/ngshare_exchange.svg?branch=master)](https://travis-ci.org/LibreTexts/ngshare_exchange)
[![codecov](https://codecov.io/gh/LibreTexts/ngshare_exchange/branch/master/graph/badge.svg)](https://codecov.io/gh/LibreTexts/ngshare_exchange)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Documentation Status](https://readthedocs.org/projects/ngshare-exchange/badge/?version=latest)](https://ngshare-exchange.readthedocs.io/en/latest/?badge=latest)

Custom [nbgrader](https://github.com/jupyter/nbgrader) exchange to be used with [ngshare](https://github.com/lxylxy123456/ngshare). This should be installed in the singleuser image of [Z2JH](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) to allow the users to use ngshare.
Custom [nbgrader](https://github.com/jupyter/nbgrader) exchange to be used with [ngshare](https://github.com/LibreTexts/ngshare). This should be installed in the singleuser image of [Z2JH](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) to allow the users to use ngshare.

# Installation instructions

Expand Down
44 changes: 18 additions & 26 deletions ngshare_exchange/collect.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os
import shutil
import sys
from collections import defaultdict
import base64

Expand All @@ -9,13 +8,6 @@

from nbgrader.utils import parse_utc

# pwd is for matching unix names with student ide, so we shouldn't import it on
# windows machines
if sys.platform != 'win32':
import pwd
else:
pwd = None


def groupby(l, key=lambda x: x):
d = defaultdict(list)
Expand All @@ -26,15 +18,15 @@ def groupby(l, key=lambda x: x):

class ExchangeCollect(Exchange, ABCExchangeCollect):
def _get_submission(self, course_id, assignment_id, student_id):
"""
'''
Returns the student's submission. A submission is a dictionary
containing a "timestamp" and "files" list. Each file in the list is a
dictionary containing the "path" relative to the assignment root and the
"content" as an ASCII representation of the base64 encoded bytes.
"""
containing a 'timestamp' and 'files' list. Each file in the list is a
dictionary containing the 'path' relative to the assignment root and the
'content' as an ASCII representation of the base64 encoded bytes.
'''

response = self.ngshare_api_get(
"/submission/{}/{}/{}".format(course_id, assignment_id, student_id)
'/submission/{}/{}/{}'.format(course_id, assignment_id, student_id)
)
if response is None:
self.log.error('An error occurred downloading a submission.')
Expand All @@ -51,10 +43,10 @@ def _get_submission(self, course_id, assignment_id, student_id):
return {'timestamp': timestamp, 'files': files}

def _get_submission_list(self, course_id, assignment_id):
"""
'''
Returns a list of submission entries. Each entry is a dictionary
containing the "student_id" and "timestamp".
"""
containing the 'student_id' and 'timestamp'.
'''
response = self.ngshare_api_get(
'/submissions/{}/{}'.format(course_id, assignment_id)
)
Expand All @@ -74,7 +66,7 @@ def _sort_by_timestamp(self, records):

def init_src(self):
if self.coursedir.course_id == '':
self.fail("No course id specified. Re-run with --course flag.")
self.fail('No course id specified. Re-run with --course flag.')

records = self._get_submission_list(
self.coursedir.course_id, self.coursedir.assignment_id
Expand All @@ -92,13 +84,13 @@ def init_dest(self):
def copy_files(self):
if len(self.src_records) == 0:
self.log.warning(
"No submissions of '{}' for course '{}' to collect".format(
'No submissions of "{}" for course "{}" to collect'.format(
self.coursedir.assignment_id, self.coursedir.course_id
)
)
else:
self.log.info(
"Processing {} submissions of '{}' for course '{}'".format(
'Processing {} submissions of "{}" for course "{}"'.format(
len(self.src_records),
self.coursedir.assignment_id,
self.coursedir.course_id,
Expand Down Expand Up @@ -135,14 +127,14 @@ def copy_files(self):
if copy:
if updating:
self.log.info(
"Updating submission: {} {}".format(
'Updating submission: {} {}'.format(
student_id, self.coursedir.assignment_id
)
)
shutil.rmtree(dest_path)
else:
self.log.info(
"Collecting submission: {} {}".format(
'Collecting submission: {} {}'.format(
student_id, self.coursedir.assignment_id
)
)
Expand All @@ -157,19 +149,19 @@ def copy_files(self):
else:
if self.update:
self.log.info(
"No newer submission to collect: {} {}".format(
'No newer submission to collect: {} {}'.format(
student_id, self.coursedir.assignment_id
)
)
else:
self.log.info(
"Submission already exists, use --update to update: {} {}".format(
'Submission already exists, use --update to update: {} {}'.format(
student_id, self.coursedir.assignment_id
)
)

def do_copy(self, src, dest):
"""
'''
Repurposed version of Exchange.do_copy.
"""
'''
self.decode_dir(src, dest)
70 changes: 49 additions & 21 deletions ngshare_exchange/course_management.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import os
import sys
import getopt
import requests
import csv
import pwd
import grp
import subprocess
import json
import argparse
from urllib.parse import quote

# https://www.geeksforgeeks.org/print-colors-python-terminal/
def prRed(skk, exit=True):
Expand All @@ -21,6 +19,10 @@ def prGreen(skk):
print('\033[92m {}\033[00m'.format(skk))


def prYellow(skk):
print("\033[93m {}\033[00m".format(skk))


class User:
def __init__(self, id, first_name, last_name, email):
self.id = id
Expand Down Expand Up @@ -51,7 +53,7 @@ def ngshare_url():
return _ngshare_url
except Exception as e:
prRed(
"Cannot determine ngshare URL. Please check your nbgrader_config.py!",
'Cannot determine ngshare URL. Please check your nbgrader_config.py!',
False,
)
prRed(e)
Expand Down Expand Up @@ -89,11 +91,18 @@ def check_message(response):
return response


def encode_url(url):
return quote(url, safe='/', encoding=None, errors=None)


def post(url, data):
header = get_header()
encoded_url = encode_url(url)

try:
response = requests.post(url, data=data, headers=header)
response = requests.post(
ngshare_url() + encoded_url, data=data, headers=header
)
response.raise_for_status()
except requests.exceptions.ConnectionError:
prRed('Could not establish connection to ngshare server')
Expand All @@ -105,19 +114,35 @@ def post(url, data):

def delete(url, data):
header = get_header()

encoded_url = encode_url(url)
try:
response = requests.delete(url, data=data, headers=header)
response.raise_for_status
response = requests.delete(
ngshare_url() + encoded_url, data=data, headers=header
)
response.raise_for_status()
except requests.exceptions.ConnectionError:
prRed('Could not establish connection to ngshare server')
except Exception:
check_status_code(response)

return check_message(response)


def check_username_warning(users):
invalid_usernames = [n for n in users if n != n.lower()]
if invalid_usernames:
prYellow(
'The following usernames have upper-case letters. Normally JupyterHub forces usernames to be lowercase. If the user has trouble accessing the course, you should add their lowercase username to ngshare instead.',
)
for user in invalid_usernames:
prYellow(user)


def create_course(args):
instructors = args.instructors or []
url = '{}/course/{}'.format(ngshare_url(), args.course_id)
check_username_warning(instructors)

url = '/course/{}'.format(args.course_id)
data = {'user': get_username(), 'instructors': json.dumps(instructors)}

response = post(url, data)
Expand All @@ -126,8 +151,9 @@ def create_course(args):

def add_student(args):
# add student to ngshare
check_username_warning([args.student_id])
student = User(args.student_id, args.first_name, args.last_name, args.email)
url = '{}/student/{}/{}'.format(ngshare_url(), args.course_id, student.id)
url = '/student/{}/{}'.format(args.course_id, student.id)
data = {
'user': get_username(),
'first_name': student.first_name,
Expand Down Expand Up @@ -190,7 +216,9 @@ def add_students(args):
student_dict = {}
student_id = row[cols_dict['student_id']]
if len(student_id.replace(' ', '')) == 0:
prRed('Student ID cannot be empty (row {})'.format(i + 1))
prRed(
'Student ID cannot be empty (row {})'.format(i + 1), False
)
continue
first_name = row[cols_dict['first_name']]
last_name = row[cols_dict['last_name']]
Expand All @@ -202,7 +230,8 @@ def add_students(args):
student_dict['email'] = email
students.append(student_dict)

url = '{}/students/{}'.format(ngshare_url(), args.course_id)
check_username_warning([student['username'] for student in students])
url = '/students/{}'.format(args.course_id)
data = {'user': get_username(), 'students': json.dumps(students)}

response = post(url, data)
Expand All @@ -212,7 +241,9 @@ def add_students(args):
user = s['username']
if s['success']:
prGreen(
'{} was sucessfuly added to {}'.format(user, args.course_id)
'{} was successfully added to {}'.format(
user, args.course_id
)
)
student = User(
user,
Expand All @@ -225,7 +256,7 @@ def add_students(args):
else:
prRed(
'There was an error adding {} to {}: {}'.format(
user, course_id, s['message']
user, args.course_id, s['message']
),
False,
)
Expand All @@ -244,7 +275,7 @@ def remove_students(args):
if not args.no_gb:
remove_jh_student(student, args.force)

url = '{}/student/{}/{}'.format(ngshare_url(), args.course_id, student)
url = '/student/{}/{}'.format(args.course_id, student)
data = {'user': get_username()}
response = delete(url, data)
prGreen(
Expand All @@ -253,9 +284,8 @@ def remove_students(args):


def add_instructor(args):
url = '{}/instructor/{}/{}'.format(
ngshare_url(), args.course_id, args.instructor_id
)
check_username_warning([args.instructor_id])
url = '/instructor/{}/{}'.format(args.course_id, args.instructor_id)
data = {
'user': get_username(),
'first_name': args.first_name,
Expand All @@ -272,9 +302,7 @@ def add_instructor(args):


def remove_instructor(args):
url = '{}/instructor/{}/{}'.format(
ngshare_url(), args.course_id, args.instructor_id
)
url = '/instructor/{}/{}'.format(args.course_id, args.instructor_id)
data = {'user': get_username()}
response = delete(url, data)
prGreen(
Expand Down
Loading

0 comments on commit 0f0950f

Please sign in to comment.