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.