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
ENROLLMENT_TYPE_TO_ROLE: Dict[str, lms.model.users.CourseRole] = {'ObserverEnrollment': <CourseRole.OTHER: 'other'>, 'StudentEnrollment': <CourseRole.STUDENT: 'student'>, 'TaEnrollment': <CourseRole.GRADER: 'grader'>, 'DesignerEnrollment': <CourseRole.ADMIN: 'admin'>, 'TeacherEnrollment': <CourseRole.OWNER: 'owner'>}

Canvas enrollment types mapped to roles. This map is ordered by priority/power. The later in the dict, the more power.

def assignment(data: Dict[str, Any]) -> lms.model.assignments.Assignment:
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

def assignment_score(data: Dict[str, Any]) -> lms.model.scores.AssignmentScore:
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)
def course(data: Dict[str, Any]) -> lms.model.courses.Course:
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)
def course_user( backend: lms.model.backend.APIBackend, data: Dict[str, Any]) -> lms.model.users.CourseUser:
 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

def group(data: Dict[str, Any]) -> lms.model.groups.Group:
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

def group_set(data: Dict[str, Any]) -> lms.model.groupsets.GroupSet:
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