lms.cli.courses.gradebook.upload

Upload a gradebook (as a table).

usage: python3 -m lms.cli.courses.gradebook.upload [-h] [--version]
                                                   [--server SERVER]
                                                   [--server-type {blackboard,canvas,moodle}]
                                                   [--auth-user AUTH_USER]
                                                   [--auth-password AUTH_PASSWORD]
                                                   [--auth-token AUTH_TOKEN]
                                                   [--course COURSE]
                                                   [--strict]
                                                   PATH

Upload a gradebook (as a table).

positional arguments:
    PATH                Path to a TSV file where each row has 2-3 columns:
                        user query, score, and comment (optional).

options:
    -h, --help          show this help message and exit
    --version           show program's version number and exit
    --course COURSE     The course to target for this operation.
    --strict            Enable strict mode, which is stricter about what
                        counts as an error (default: False).

server options:
    --server SERVER     The address of the LMS server to connect to.
    --server-type {blackboard,canvas,moodle}
                        The type of LMS being connected to (this can normally
                        be guessed from the server address).

authentication options:
    --auth-user AUTH_USER
                        The user to authenticate with.
    --auth-password AUTH_PASSWORD
                        The password to authenticate with.
    --auth-token AUTH_TOKEN
                        The token to authenticate with.
  1"""
  2Upload a gradebook (as a table).
  3"""
  4
  5import argparse
  6import ast
  7import sys
  8
  9import edq.util.dirent
 10
 11import lms.backend.instance
 12import lms.cli.common
 13import lms.cli.parser
 14import lms.model.backend
 15import lms.model.scores
 16
 17def run_cli(args: argparse.Namespace) -> int:
 18    """ Run the CLI. """
 19
 20    config = args._config_info.application_config
 21    backend = lms.backend.instance.get_backend(config)
 22
 23    course_query = lms.cli.common.check_required_course(backend, config)
 24    if (course_query is None):
 25        return 1
 26
 27    gradebook = _load_gradebook(backend, args.path)
 28
 29    count = backend.courses_gradebook_resolve_and_upload(course_query, gradebook)
 30
 31    print(f"Uploaded {count} Scores")
 32
 33    return lms.cli.common.strict_check(config, (count != len(gradebook)),
 34        f"Expected to upload {len(gradebook)} scores, but uploaded {count}.", 2)
 35
 36def _load_gradebook(
 37        backend: lms.model.backend.APIBackend,
 38        path: str,
 39        ) -> lms.model.scores.Gradebook:
 40    assignments = []
 41    users = []
 42    scores = []
 43
 44    with open(path, 'r', encoding = edq.util.dirent.DEFAULT_ENCODING) as file:
 45        lineno = 0
 46        for line in file:
 47            if (line.strip() == ''):
 48                continue
 49
 50            lineno += 1
 51
 52            parts = [part.strip() for part in line.split("\t")]
 53
 54            # Process the assignment queries.
 55            if (lineno == 1):
 56                if (len(parts) < 2):
 57                    raise ValueError(f"File '{path}' line {lineno} (assignments line) has the incorrect number of values."
 58                            + f" Need at least 2 values (user and single assignment), found {len(parts)}.")
 59
 60                # Skip the users column.
 61                parts = parts[1:]
 62
 63                for part in parts:
 64                    assignment = backend.parse_assignment_query(part)
 65                    if (assignment is None):
 66                        raise ValueError(f"File '{path}' line {lineno} has an assignment query that could not be parsed: '{part}'.")
 67
 68                    assignments.append(assignment)
 69
 70                continue
 71
 72            if (len(parts) != (1 + len(assignments))):
 73                raise ValueError(f"File '{path}' line {lineno} has the incorrect number of values."
 74                        + f" Expecting {1 + len(assignments)}, found {len(parts)}.")
 75
 76            # Process user row.
 77            user = backend.parse_user_query(parts[0])
 78            if (user is None):
 79                raise ValueError(f"File '{path}' line {lineno} has an user query that could not be parsed: '{parts[0]}'.")
 80
 81            users.append(user)
 82
 83            # User part already processed.
 84            parts = parts[1:]
 85
 86            for (i, part) in enumerate(parts):
 87                part = part.strip()
 88                if (len(part) == 0):
 89                    continue
 90
 91                try:
 92                    float_score = float(ast.literal_eval(part))
 93                except Exception:
 94                    raise ValueError(f"File '{path}' line {lineno} has a score that cannot be converted to a number: '{part}'.")  # pylint: disable=raise-missing-from
 95
 96                assignment_score = lms.model.scores.AssignmentScore(score = float_score, assignment = assignments[i], user = user)
 97                scores.append(assignment_score)
 98
 99    gradebook = lms.model.scores.Gradebook(assignments, users)
100
101    for score in scores:
102        gradebook.add(score)
103
104    return gradebook
105
106def main() -> int:
107    """ Get a parser, parse the args, and call run. """
108    return run_cli(_get_parser().parse_args())
109
110def _get_parser() -> argparse.ArgumentParser:
111    """ Get the parser. """
112
113    parser = lms.cli.parser.get_parser(__doc__.strip(),
114            include_course = True,
115            include_strict = True,
116    )
117
118    parser.add_argument('path', metavar = 'PATH',
119        action = 'store', type = str,
120        help = 'Path to a TSV file where each row has 2-3 columns: user query, score, and comment (optional).')
121
122    return parser
123
124if (__name__ == '__main__'):
125    sys.exit(main())
def run_cli(args: argparse.Namespace) -> int:
18def run_cli(args: argparse.Namespace) -> int:
19    """ Run the CLI. """
20
21    config = args._config_info.application_config
22    backend = lms.backend.instance.get_backend(config)
23
24    course_query = lms.cli.common.check_required_course(backend, config)
25    if (course_query is None):
26        return 1
27
28    gradebook = _load_gradebook(backend, args.path)
29
30    count = backend.courses_gradebook_resolve_and_upload(course_query, gradebook)
31
32    print(f"Uploaded {count} Scores")
33
34    return lms.cli.common.strict_check(config, (count != len(gradebook)),
35        f"Expected to upload {len(gradebook)} scores, but uploaded {count}.", 2)

Run the CLI.

def main() -> int:
107def main() -> int:
108    """ Get a parser, parse the args, and call run. """
109    return run_cli(_get_parser().parse_args())

Get a parser, parse the args, and call run.