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

Run the CLI.

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

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