lms.backend.canvas.model
1import typing 2 3import lms.backend.canvas.common 4import lms.model.assignments 5import lms.model.backend 6import lms.model.courses 7import lms.model.groups 8import lms.model.groupsets 9import lms.model.scores 10import lms.model.users 11import lms.util.parse 12 13ENROLLMENT_TYPE_TO_ROLE: typing.Dict[str, lms.model.users.CourseRole] = { 14 'ObserverEnrollment': lms.model.users.CourseRole.OTHER, 15 'StudentEnrollment': lms.model.users.CourseRole.STUDENT, 16 'TaEnrollment': lms.model.users.CourseRole.GRADER, 17 'DesignerEnrollment': lms.model.users.CourseRole.ADMIN, 18 'TeacherEnrollment': lms.model.users.CourseRole.OWNER, 19} 20""" 21Canvas enrollment types mapped to roles. 22This map is ordered by priority/power. 23The later in the dict, the more power. 24""" 25 26_testing_override: bool = False # pylint: disable=invalid-name 27""" A special override to signal testing. """ 28 29def assignment(data: typing.Dict[str, typing.Any]) -> lms.model.assignments.Assignment: 30 """ 31 Create a Canvas assignment associated with a course. 32 33 See: https://developerdocs.instructure.com/services/canvas/resources/assignments 34 """ 35 36 for field in ['id']: 37 if (field not in data): 38 raise ValueError(f"Canvas assignment is missing '{field}' field.") 39 40 # Modify specific arguments before creation. 41 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 42 data['due_date'] = lms.backend.canvas.common.parse_timestamp(data.get('due_at', None)) 43 data['open_date'] = lms.backend.canvas.common.parse_timestamp(data.get('unlock_at', None)) 44 data['close_date'] = lms.backend.canvas.common.parse_timestamp(data.get('lock_at', None)) 45 46 return lms.model.assignments.Assignment(**data) 47 48def assignment_score(data: typing.Dict[str, typing.Any]) -> lms.model.scores.AssignmentScore: 49 """ 50 Create a Canvas assignment score. 51 52 See: https://developerdocs.instructure.com/services/canvas/resources/scores 53 """ 54 55 # Check for important fields. 56 for field in ['id', 'assignment_id', 'user_id']: 57 if (field not in data): 58 raise ValueError(f"Canvas assignment score is missing '{field}' field.") 59 60 # Modify specific arguments before creation. 61 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 62 data['score'] = lms.util.parse.optional_float(data.get('score', None), 'score') 63 data['points_possible'] = lms.util.parse.optional_float(data.get('points_possible', None), 'points_possible') 64 data['submission_date'] = lms.backend.canvas.common.parse_timestamp(data.get('submitted_at', None)) 65 data['graded_date'] = lms.backend.canvas.common.parse_timestamp(data.get('graded_at', None)) 66 67 assignment_id = lms.util.parse.required_string(data.get('assignment_id', None), 'assignment_id') 68 data['assignment'] = lms.model.assignments.AssignmentQuery(id = assignment_id) 69 70 user_id = lms.util.parse.required_string(data.get('user_id', None), 'user_id') 71 data['user'] = lms.model.users.UserQuery(id = user_id) 72 73 return lms.model.scores.AssignmentScore(**data) 74 75def course(data: typing.Dict[str, typing.Any]) -> lms.model.courses.Course: 76 """ 77 Create a Canvas course. 78 79 See: https://developerdocs.instructure.com/services/canvas/resources/courses 80 """ 81 82 # Check for important fields. 83 for field in ['id']: 84 if (field not in data): 85 raise ValueError(f"Canvas course is missing '{field}' field.") 86 87 # Modify specific arguments before creation. 88 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 89 90 return lms.model.courses.Course(**data) 91 92def course_user(backend: lms.model.backend.APIBackend, data: typing.Dict[str, typing.Any]) -> lms.model.users.CourseUser: 93 """ 94 Create a Canvas user associated with a course. 95 96 See: https://developerdocs.instructure.com/services/canvas/resources/users 97 """ 98 99 # Check for important fields. 100 for field in ['id']: 101 if (field not in data): 102 raise ValueError(f"Canvas user is missing '{field}' field.") 103 104 # Modify specific arguments before sending them to super. 105 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 106 107 # Canvas sometimes has email under different fields. 108 if ((data.get('email', None) is None) or (len(data.get('email', '')) == 0)): 109 data['email'] = data.get('login_id', None) 110 111 enrollments = data.get('enrollments', None) 112 if (enrollments is not None): 113 data['raw_role'] = _parse_role_from_enrollments(enrollments) 114 data['role'] = ENROLLMENT_TYPE_TO_ROLE.get(data['raw_role'], None) 115 116 # Canvas has a discontinuity with its default course roles. 117 # We need to patch this during testing. 118 if ((backend.is_testing() or _testing_override) and data['email'] == 'course-admin@test.edulinq.org'): 119 data['role'] = lms.model.users.CourseRole.ADMIN 120 121 return lms.model.users.CourseUser(**data) 122 123def group(data: typing.Dict[str, typing.Any]) -> lms.model.groups.Group: 124 """ 125 Create a Canvas group associated with a course. 126 127 See: https://developerdocs.instructure.com/services/canvas/resources/groups 128 """ 129 130 # Check for important fields. 131 for field in ['id']: 132 if (field not in data): 133 raise ValueError(f"Canvas group is missing '{field}' field.") 134 135 # Modify specific arguments before creation. 136 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 137 138 return lms.model.groups.Group(**data) 139 140def group_set(data: typing.Dict[str, typing.Any]) -> lms.model.groupsets.GroupSet: 141 """ 142 Create a Canvas group set associated with a course. 143 144 See: https://developerdocs.instructure.com/services/canvas/resources/group_categories 145 """ 146 147 # Check for important fields. 148 for field in ['id']: 149 if (field not in data): 150 raise ValueError(f"Canvas group set is missing '{field}' field.") 151 152 # Modify specific arguments before creation. 153 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 154 155 return lms.model.groupsets.GroupSet(**data) 156 157def _parse_role_from_enrollments(enrollments: typing.Any) -> typing.Union[str, None]: 158 """ 159 Try to parse the user's role from their enrollments. 160 If multiple roles are discovered, take the "highest" one. 161 162 See: https://developerdocs.instructure.com/services/canvas/resources/enrollments 163 """ 164 165 if (not isinstance(enrollments, list)): 166 return None 167 168 best_role = None 169 best_index = -1 170 171 enrollment_types = list(ENROLLMENT_TYPE_TO_ROLE.keys()) 172 173 for enrollment in enrollments: 174 if (not isinstance(enrollment, dict)): 175 continue 176 177 if (enrollment.get('enrollment_state', None) != 'active'): 178 continue 179 180 role = enrollment.get('role', None) 181 182 role_index = -1 183 if (role in enrollment_types): 184 role_index = enrollment_types.index(role) 185 186 if ((best_role is None) or (role_index > best_index)): 187 best_role = role 188 best_index = role_index 189 190 return best_role
Canvas enrollment types mapped to roles. This map is ordered by priority/power. The later in the dict, the more power.
30def assignment(data: typing.Dict[str, typing.Any]) -> lms.model.assignments.Assignment: 31 """ 32 Create a Canvas assignment associated with a course. 33 34 See: https://developerdocs.instructure.com/services/canvas/resources/assignments 35 """ 36 37 for field in ['id']: 38 if (field not in data): 39 raise ValueError(f"Canvas assignment is missing '{field}' field.") 40 41 # Modify specific arguments before creation. 42 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 43 data['due_date'] = lms.backend.canvas.common.parse_timestamp(data.get('due_at', None)) 44 data['open_date'] = lms.backend.canvas.common.parse_timestamp(data.get('unlock_at', None)) 45 data['close_date'] = lms.backend.canvas.common.parse_timestamp(data.get('lock_at', None)) 46 47 return lms.model.assignments.Assignment(**data)
Create a Canvas assignment associated with a course.
See: https://developerdocs.instructure.com/services/canvas/resources/assignments
49def assignment_score(data: typing.Dict[str, typing.Any]) -> lms.model.scores.AssignmentScore: 50 """ 51 Create a Canvas assignment score. 52 53 See: https://developerdocs.instructure.com/services/canvas/resources/scores 54 """ 55 56 # Check for important fields. 57 for field in ['id', 'assignment_id', 'user_id']: 58 if (field not in data): 59 raise ValueError(f"Canvas assignment score is missing '{field}' field.") 60 61 # Modify specific arguments before creation. 62 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 63 data['score'] = lms.util.parse.optional_float(data.get('score', None), 'score') 64 data['points_possible'] = lms.util.parse.optional_float(data.get('points_possible', None), 'points_possible') 65 data['submission_date'] = lms.backend.canvas.common.parse_timestamp(data.get('submitted_at', None)) 66 data['graded_date'] = lms.backend.canvas.common.parse_timestamp(data.get('graded_at', None)) 67 68 assignment_id = lms.util.parse.required_string(data.get('assignment_id', None), 'assignment_id') 69 data['assignment'] = lms.model.assignments.AssignmentQuery(id = assignment_id) 70 71 user_id = lms.util.parse.required_string(data.get('user_id', None), 'user_id') 72 data['user'] = lms.model.users.UserQuery(id = user_id) 73 74 return lms.model.scores.AssignmentScore(**data)
Create a Canvas assignment score.
See: https://developerdocs.instructure.com/services/canvas/resources/scores
76def course(data: typing.Dict[str, typing.Any]) -> lms.model.courses.Course: 77 """ 78 Create a Canvas course. 79 80 See: https://developerdocs.instructure.com/services/canvas/resources/courses 81 """ 82 83 # Check for important fields. 84 for field in ['id']: 85 if (field not in data): 86 raise ValueError(f"Canvas course is missing '{field}' field.") 87 88 # Modify specific arguments before creation. 89 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 90 91 return lms.model.courses.Course(**data)
Create a Canvas course.
See: https://developerdocs.instructure.com/services/canvas/resources/courses
93def course_user(backend: lms.model.backend.APIBackend, data: typing.Dict[str, typing.Any]) -> lms.model.users.CourseUser: 94 """ 95 Create a Canvas user associated with a course. 96 97 See: https://developerdocs.instructure.com/services/canvas/resources/users 98 """ 99 100 # Check for important fields. 101 for field in ['id']: 102 if (field not in data): 103 raise ValueError(f"Canvas user is missing '{field}' field.") 104 105 # Modify specific arguments before sending them to super. 106 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 107 108 # Canvas sometimes has email under different fields. 109 if ((data.get('email', None) is None) or (len(data.get('email', '')) == 0)): 110 data['email'] = data.get('login_id', None) 111 112 enrollments = data.get('enrollments', None) 113 if (enrollments is not None): 114 data['raw_role'] = _parse_role_from_enrollments(enrollments) 115 data['role'] = ENROLLMENT_TYPE_TO_ROLE.get(data['raw_role'], None) 116 117 # Canvas has a discontinuity with its default course roles. 118 # We need to patch this during testing. 119 if ((backend.is_testing() or _testing_override) and data['email'] == 'course-admin@test.edulinq.org'): 120 data['role'] = lms.model.users.CourseRole.ADMIN 121 122 return lms.model.users.CourseUser(**data)
Create a Canvas user associated with a course.
See: https://developerdocs.instructure.com/services/canvas/resources/users
124def group(data: typing.Dict[str, typing.Any]) -> lms.model.groups.Group: 125 """ 126 Create a Canvas group associated with a course. 127 128 See: https://developerdocs.instructure.com/services/canvas/resources/groups 129 """ 130 131 # Check for important fields. 132 for field in ['id']: 133 if (field not in data): 134 raise ValueError(f"Canvas group is missing '{field}' field.") 135 136 # Modify specific arguments before creation. 137 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 138 139 return lms.model.groups.Group(**data)
Create a Canvas group associated with a course.
See: https://developerdocs.instructure.com/services/canvas/resources/groups
141def group_set(data: typing.Dict[str, typing.Any]) -> lms.model.groupsets.GroupSet: 142 """ 143 Create a Canvas group set associated with a course. 144 145 See: https://developerdocs.instructure.com/services/canvas/resources/group_categories 146 """ 147 148 # Check for important fields. 149 for field in ['id']: 150 if (field not in data): 151 raise ValueError(f"Canvas group set is missing '{field}' field.") 152 153 # Modify specific arguments before creation. 154 data['id'] = lms.util.parse.required_string(data.get('id', None), 'id') 155 156 return lms.model.groupsets.GroupSet(**data)
Create a Canvas group set associated with a course.
See: https://developerdocs.instructure.com/services/canvas/resources/group_categories