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.