def test_missing_roster_items(clean_dir): """Test loading Grader config with missing roster items """ # Forgot the name write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id": str(uuid.uuid4()), "roster": [ {"id": "fmmmm4"}, {"name": "Jake the Dog", "id": "jtdbb9"} ], }) with pytest.raises(ConfigValidationError): Grader(clean_dir) # Forgot the id write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id": str(uuid.uuid4()), "roster": [ {"name": "Finn Mertens"}, {"name": "Jake the Dog", "id": "jtdbb9"} ], }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def print_canvas_courses(args): """Show a list of current teacher's courses from Canvas via the API. """ g = Grader(args.path) if 'canvas-token' not in g.config: logger.error( "canvas-token configuration is missing! Please set the Canvas API access " "token before attempting to use Canvas API functionality") print("Canvas course listing failed: missing Canvas API access token.") return canvas = CanvasAPI(g.config["canvas-token"], g.config["canvas-host"]) courses = canvas.get_instructor_courses() if not courses: print("No courses found where current user is a teacher.") return output = PrettyTable(["#", "ID", "Name"]) output.align["Name"] = "l" for ix, c in enumerate(sorted(courses, key=lambda c: c['id'], reverse=True)): output.add_row((ix + 1, c['id'], c['name'])) print(output)
def run(args): g = Grader(args.path) assignments = g.assignments a_info = build_assignment_info(assignments, full=args.full) s_info = build_submission_info(assignments, full=args.full) columns = ["Assignment", "Total", "Graded", "Failed"] rows = a_info if args.submissions: columns = [ "Assignment", "User ID", "Submission UUID", "Import Time", "Last File MTime", "Last Commit", "SHA1", "Re-Grades", "Failed" ] rows = s_info rows = sort_by_assignment(rows, args.sortby) if args.assignment: try: a = assignments[args.assignment] rows = [r for r in rows if r['Assignment'] == a.name] except KeyError: rows = [] t = PrettyTable(columns) for row in rows: t.add_row([row[c] for c in columns]) print(t)
def test_missing_course_name(clean_dir): """Test loading Grader config with a missing course name """ write_config(clean_dir, "grader.yml", { "course-id": str(uuid.uuid4()) }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) if not a.gradesheet.templates: logger.critical("No available templates in gradesheet.") return # Find and load the Jinja2 template template = None try: with open(a.gradesheet.templates[args.template]) as template_file: template = jinja2.Template(template_file.read()) except KeyError: logger.error("Couldn't find '%s' template", args.template) return # Narrow down users, if necessary users = a.submissions_by_user if args.student_id: try: users = {args.student_id: users[args.student_id]} except KeyError: logger.error("Cannot find student %s", args.student_id) return # Make a unique directory for the output output_dir = os.path.join(args.path, args.template) for i in itertools.count(1): if os.path.exists(output_dir): template_name = "{}.{}".format(args.template, i) output_dir = os.path.join(args.path, template_name) else: os.makedirs(output_dir) break # Generate reports for user_id, submissions in users.items(): logger.info("Generating report for %s", user_id) for submission in submissions: # Load the report data with open(submission.latest_result) as result_file: data = load_data(result_file.read()) data.update({ 'student': { 'id': user_id, 'name': submission.student_name }, 'assignment': { 'name': submission.assignment.name } }) # Save it to a file filename = "{}.{}".format(submission.full_id, args.template) with open(os.path.join(output_dir, filename), 'w') as outfile: outfile.write(template.render(data))
def test_missing_course_id(clean_dir): """Test loading Grader config with a missing course id """ write_config(clean_dir, "grader.yml", { "course-name": "cs2001" }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def test_bad_course_name_value(clean_dir): """Test loading Grader config with a bad course name """ write_config(clean_dir, "grader.yml", { "course-name": "Noooo%%%pe", "course-id": str(uuid.uuid4()) }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def test_bad_course_id_value(clean_dir): """Test loading Grader config with a bad course id """ write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id": "invalid %%% key" }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def test_bad_course_id_key(clean_dir): """Test loading Grader config with a bad course id key """ write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id-wrong": str(uuid.uuid4()) }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def test_correct_config(clean_dir): """Test loading Grader with a correct config """ write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id": str(uuid.uuid4()), }) Grader(clean_dir) write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id": str(uuid.uuid4()), "roster": [ {"name": "Finn Mertens", "id": "fmmmm4"}, {"name": "Jake the Dog", "id": "jtdbb9"} ], }) Grader(clean_dir)
def test_build_missing_assignment_specific_dir(parse_and_run): """Test building an image without an assignment-specific dir """ path = parse_and_run(["init", "cpl"]) parse_and_run(["new", "a1"]) g = Grader(path) a = g.get_assignment("a1") shutil.rmtree(a.submissions_dir) with pytest.raises(FileNotFoundError): parse_and_run(["build", "a1"])
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) if args.student_id not in a.submissions_by_user: logger.error("User does not have a graded submission available.") return user_submissions = a.submissions_by_user[args.student_id] id = submission_choice(a, args.student_id, user_submissions).full_id inspect(a, id, args.user)
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) if len(a.submissions_by_user) == 0: logger.error("There are no graded submissions for this assignment.") return if args.start_at and args.start_at not in a.submissions_by_user: logger.error("User does not have a graded submission available.") return review_loop(a, args.start_at)
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) try: submissions = a.submissions_by_user[args.student_id] except KeyError: logger.error("Cannot find student %s", args.student_id) return latest_submission = max(submissions, key=last_graded) logger.debug("Catting %s", latest_submission.latest_result) with open(latest_submission.latest_result) as f: print(f.read())
def test_bad_roster_id(clean_dir): """Test loading Grader config with a bad course id """ write_config(clean_dir, "grader.yml", { "course-name": "cs2001", "course-id": str(uuid.uuid4()), "roster": [ {"name": "Finn Mertens", "id": "no.periods"}, {"name": "Jake the Dog", "id": "jtdbb9"} ], }) with pytest.raises(ConfigValidationError): Grader(clean_dir)
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) users = a.submissions_by_user if args.student_id: try: users = {args.student_id: users[args.student_id]} except KeyError: logger.error("Cannot find student %s", args.student_id) return for user_id, submissions in users.items(): logger.info("Grading submissions for %s", user_id) for submission in submissions: submission.grade(a, rebuild_container=args.rebuild, show_output=args.suppress_output)
def test_build(parse_and_run): """Test vanilla assignment build """ path = parse_and_run(["init", "cpl"]) parse_and_run(["new", "a1"]) # Give it a buildable Dockerfile dockerfile_path = os.path.join(path, "assignments", "a1", "gradesheet", "Dockerfile") with open(dockerfile_path, 'w') as dockerfile: dockerfile.write("FROM busybox") # Build the image parse_and_run(["build", "a1"]) # Remove the built image g = Grader(path) a, = g.assignments.values() a.delete_image()
def import_from_canvas(args): """Imports students from a Canvas course to the roster. """ g = Grader(args.path) if 'canvas-token' not in g.config: logger.error( "canvas-token configuration is missing! Please set the Canvas API access " "token before attempting to import users from Canvas") print("Import from canvas failed: missing Canvas API access token.") return course_id = args.id force = args.force canvas = CanvasAPI(g.config["canvas-token"], g.config["canvas-host"]) students = canvas.get_course_students(course_id) for s in students: if 'sis_user_id' not in s: logger.error("Could not get username for %s", s['sortable_name']) if not force: for student in g.config.roster: if student['name'] == s['sortable_name']: logger.warning( "User %s is already in the roster, skipping", s['sis_user_id']) break else: g.config.roster.append({ 'name': s['sortable_name'], 'id': s['sis_user_id'] }) else: g.config.roster.append({ 'name': s['sortable_name'], 'id': s['sis_user_id'] }) print("Imported {} students.".format(len(students))) g.config.save()
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) pattern = args.pattern if args.pattern else r"(?P<id>.*)" a.import_submission(args.submission_path, args.kind, pattern)
def run(args): g = Grader(args.path) g.create_assignment(args.name, repo=args.repo)
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) a.build_image()
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) a.import_submission(args.submission_path, args.kind)
def run(args): g = Grader(args.path) a = g.get_assignment(args.assignment) a.build_image(args.no_cache, args.pull, args.silent)