lms .cli .courses .assignments .scores .upload
Upload scores (and optional comments) for an assignment.
usage: python3 -m lms.cli.courses.assignments.scores.upload
[-h] [--version] [--server SERVER]
[--server-type {blackboard,canvas,moodle}] [--auth-user AUTH_USER]
[--auth-password AUTH_PASSWORD] [--auth-token AUTH_TOKEN]
[--course COURSE] [--assignment ASSIGNMENT] [--skip-rows SKIP_ROWS]
[--strict]
PATH
Upload scores (and optional comments) for an assignment.
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.
--assignment ASSIGNMENT
The assignment to target for this operation.
--skip-rows SKIP_ROWS
The number of header rows to skip (default: 0).
--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 scores (and optional comments) for an assignment. 3""" 4 5import argparse 6import ast 7import sys 8import typing 9 10import edq.util.dirent 11 12import lms.backend.instance 13import lms.cli.common 14import lms.cli.parser 15import lms.model.backend 16import lms.model.scores 17import lms.model.users 18 19def run_cli(args: argparse.Namespace) -> int: 20 """ Run the CLI. """ 21 22 config = args._config 23 24 backend = lms.backend.instance.get_backend(**config) 25 26 course_query = lms.cli.common.check_required_course(backend, config) 27 if (course_query is None): 28 return 1 29 30 assignment_query = lms.cli.common.check_required_assignment(backend, config) 31 if (assignment_query is None): 32 return 2 33 34 scores = _load_scores(backend, args.path, args.skip_rows) 35 36 count = backend.courses_assignments_scores_resolve_and_upload(course_query, assignment_query, scores) 37 38 print(f"Uploaded {count} Scores") 39 40 return lms.cli.common.strict_check(args.strict, (count != len(scores)), 41 f"Expected to upload {len(scores)} scores, but uploaded {count}.", 3) 42 43def _load_scores( 44 backend: lms.model.backend.APIBackend, 45 path: str, 46 skip_rows: bool, 47 ) -> typing.Dict[lms.model.users.UserQuery, lms.model.scores.ScoreFragment]: 48 scores = {} 49 50 with open(path, 'r', encoding = edq.util.dirent.DEFAULT_ENCODING) as file: 51 lineno = 0 52 real_rows = 0 53 for line in file: 54 lineno += 1 55 56 if (line.strip() == ''): 57 continue 58 59 real_rows += 1 60 61 if (real_rows <= skip_rows): 62 continue 63 64 parts = [part.strip() for part in line.split("\t")] 65 if (len(parts) not in [2, 3]): 66 raise ValueError(f"File '{path}' line {lineno} has the incorrect number of values. Expecting 2-3, found {len(parts)}.") 67 68 user_query = backend.parse_user_query(parts[0]) 69 if (user_query is None): 70 raise ValueError(f"File '{path}' line {lineno} has a user query that could not be parsed: '{parts[0]}'.") 71 72 score = None 73 if (parts[1] != ''): 74 try: 75 score = float(ast.literal_eval(parts[1])) 76 except Exception: 77 raise ValueError(f"File '{path}' line {lineno} has a score that cannot be converted to a number: '{parts[1]}'.") # pylint: disable=raise-missing-from 78 79 comment = None 80 if (len(parts) == 3): 81 comment = parts[2] 82 83 scores[user_query] = lms.model.scores.ScoreFragment(score = score, comment = comment) 84 85 return scores 86 87def main() -> int: 88 """ Get a parser, parse the args, and call run. """ 89 return run_cli(_get_parser().parse_args()) 90 91def _get_parser() -> argparse.ArgumentParser: 92 """ Get the parser. """ 93 94 parser = lms.cli.parser.get_parser(__doc__.strip(), 95 include_course = True, 96 include_assignment = True, 97 include_skip_rows = True, 98 include_strict = True, 99 ) 100 101 parser.add_argument('path', metavar = 'PATH', 102 action = 'store', type = str, 103 help = 'Path to a TSV file where each row has 2-3 columns: user query, score, and comment (optional).') 104 105 return parser 106 107if (__name__ == '__main__'): 108 sys.exit(main())
def
run_cli(args: argparse.Namespace) -> int:
20def run_cli(args: argparse.Namespace) -> int: 21 """ Run the CLI. """ 22 23 config = args._config 24 25 backend = lms.backend.instance.get_backend(**config) 26 27 course_query = lms.cli.common.check_required_course(backend, config) 28 if (course_query is None): 29 return 1 30 31 assignment_query = lms.cli.common.check_required_assignment(backend, config) 32 if (assignment_query is None): 33 return 2 34 35 scores = _load_scores(backend, args.path, args.skip_rows) 36 37 count = backend.courses_assignments_scores_resolve_and_upload(course_query, assignment_query, scores) 38 39 print(f"Uploaded {count} Scores") 40 41 return lms.cli.common.strict_check(args.strict, (count != len(scores)), 42 f"Expected to upload {len(scores)} scores, but uploaded {count}.", 3)
Run the CLI.
def
main() -> int:
88def main() -> int: 89 """ Get a parser, parse the args, and call run. """ 90 return run_cli(_get_parser().parse_args())
Get a parser, parse the args, and call run.