Beispiel #1
0
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)
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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))
Beispiel #6
0
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)
Beispiel #7
0
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)
Beispiel #8
0
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)
Beispiel #9
0
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)
Beispiel #10
0
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)
Beispiel #11
0
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"])
Beispiel #12
0
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)
Beispiel #13
0
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)
Beispiel #14
0
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())
Beispiel #15
0
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)
Beispiel #16
0
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)
Beispiel #17
0
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()
Beispiel #18
0
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()
Beispiel #19
0
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)
Beispiel #20
0
def run(args):
    g = Grader(args.path)
    g.create_assignment(args.name, repo=args.repo)
Beispiel #21
0
def run(args):
    g = Grader(args.path)
    a = g.get_assignment(args.assignment)
    a.build_image()
Beispiel #22
0
def run(args):
    g = Grader(args.path)
    a = g.get_assignment(args.assignment)
    a.import_submission(args.submission_path, args.kind)
Beispiel #23
0
def run(args):
    g = Grader(args.path)
    a = g.get_assignment(args.assignment)
    a.build_image(args.no_cache, args.pull, args.silent)