-
Notifications
You must be signed in to change notification settings - Fork 22
/
keys.py
341 lines (279 loc) · 9.43 KB
/
keys.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
"""
OpaqueKey abstract classes for edx-platform object types (courses, definitions, usages, and assets).
"""
from __future__ import annotations
import json
from abc import abstractmethod
import warnings
from typing_extensions import Self # For python 3.11 plus, can just use "from typing import Self"
from opaque_keys import OpaqueKey
class LearningContextKey(OpaqueKey): # pylint: disable=abstract-method
"""
An :class:`opaque_keys.OpaqueKey` identifying a course, a library, a
program, a website, or some other collection of content where learning
happens.
This concept is more generic than "course."
A learning context does not necessarily have an org, course, or, run.
"""
KEY_TYPE = 'context_key'
__slots__ = ()
# is_course: subclasses should override this to indicate whether or not this
# key type represents a course (as opposed to a library or something else).
# We can't just use isinstance(key, CourseKey) because LibraryLocators
# are subclasses of CourseKey for historical reasons. Once modulestore-
# based content libraries are removed, one can replace this with
# just isinstance(key, CourseKey)
is_course = False
class CourseKey(LearningContextKey):
"""
An :class:`opaque_keys.OpaqueKey` identifying a particular Course object.
"""
__slots__ = ()
is_course = True
@property
@abstractmethod
def org(self) -> str | None: # pragma: no cover
"""
The organization that this course belongs to.
"""
raise NotImplementedError()
@property
@abstractmethod
def course(self) -> str | None: # pragma: no cover
"""
The name for this course.
In old-style IDs, it's the "course" in org/course/run
"""
raise NotImplementedError()
@property
@abstractmethod
def run(self) -> str | None: # pragma: no cover
"""
The run for this course.
In old-style IDs, it's the "run" in org/course/run
"""
raise NotImplementedError()
@abstractmethod
def make_usage_key(self, block_type: str, block_id: str) -> UsageKey: # pragma: no cover
"""
Return a usage key, given the given the specified block_type and block_id.
This function should not actually create any new ids, but should simply
return one that already exists.
"""
raise NotImplementedError()
@abstractmethod
def make_asset_key(self, asset_type: str, path: str) -> AssetKey: # pragma: no cover
"""
Return an asset key, given the given the specified path.
This function should not actually create any new ids, but should simply
return one that already exists.
"""
raise NotImplementedError()
class DefinitionKey(OpaqueKey):
"""
An :class:`opaque_keys.OpaqueKey` identifying an XBlock definition.
"""
KEY_TYPE = 'definition_key'
__slots__ = ()
@property
@abstractmethod
def block_type(self) -> str: # pragma: no cover
"""
The XBlock type of this definition.
"""
raise NotImplementedError()
class CourseObjectMixin:
"""
An abstract :class:`opaque_keys.OpaqueKey` mixin
for keys that belong to courses.
"""
__slots__ = ()
@property
@abstractmethod
def course_key(self) -> CourseKey: # pragma: no cover
"""
Return the :class:`CourseKey` for the course containing this usage.
"""
raise NotImplementedError()
@abstractmethod
def map_into_course(self, course_key: CourseKey) -> Self: # pragma: no cover
"""
Return a new :class:`UsageKey` or :class:`AssetKey` representing this usage inside the
course identified by the supplied :class:`CourseKey`. It returns the same type as
`self`
Args:
course_key (:class:`CourseKey`): The course to map this object into.
Returns:
A new :class:`CourseObjectMixin` instance.
"""
raise NotImplementedError()
class AssetKey(CourseObjectMixin, OpaqueKey):
"""
An :class:`opaque_keys.OpaqueKey` identifying a course asset.
"""
KEY_TYPE = 'asset_key'
__slots__ = ()
@property
@abstractmethod
def asset_type(self) -> str: # pragma: no cover
"""
Return what type of asset this is.
"""
raise NotImplementedError()
@property
@abstractmethod
def path(self) -> str: # pragma: no cover
"""
Return the path for this asset.
"""
raise NotImplementedError()
class UsageKey(CourseObjectMixin, OpaqueKey):
"""
An :class:`opaque_keys.OpaqueKey` identifying an XBlock usage.
"""
KEY_TYPE = 'usage_key'
__slots__ = ()
@property
@abstractmethod
def definition_key(self) -> DefinitionKey: # pragma: no cover
"""
Return the :class:`DefinitionKey` for the XBlock containing this usage.
"""
raise NotImplementedError()
@property
@abstractmethod
def block_type(self) -> str:
"""
The XBlock type of this usage.
"""
raise NotImplementedError()
@property
@abstractmethod
def block_id(self) -> str:
"""
The name of this usage.
"""
raise NotImplementedError()
@property
def context_key(self) -> LearningContextKey:
"""
Get the learning context key (LearningContextKey) for this XBlock usage.
"""
return self.course_key
class UsageKeyV2(UsageKey):
"""
An :class:`opaque_keys.OpaqueKey` identifying an XBlock used in a specific
learning context (e.g. a course).
Definition + Learning Context = Usage
UsageKeyV2 is just a subclass of UsageKey with slightly different behavior,
but not a distinct key type (same KEY_TYPE). UsageKeyV2 should be used for
new usage key types; the main differences between it and UsageKey are:
* the .course_key property is considered deprecated for the new V2 key
types, and they should implement .context_key instead.
* the .definition_key property is explicitly disabled for V2 usage keys
"""
__slots__ = ()
@property
@abstractmethod
def context_key(self) -> LearningContextKey:
"""
Get the learning context key (LearningContextKey) for this XBlock usage.
May be a course key, library key, or some other LearningContextKey type.
"""
raise NotImplementedError()
@property
def definition_key(self) -> DefinitionKey:
"""
Returns the definition key for this usage. For the newer V2 key types,
this cannot be done with the key alone, so it's necessary to ask the
key's learning context to provide the underlying definition key.
"""
raise AttributeError(
"Version 2 usage keys do not support direct .definition_key access. "
"To get the definition key within edxapp, use: "
"get_learning_context_impl(usage_key).definition_for_usage(usage_key)"
)
@property
def course_key(self) -> CourseKey:
warnings.warn("Use .context_key instead of .course_key", DeprecationWarning, stacklevel=2)
return self.context_key # type: ignore
def map_into_course(self, course_key: CourseKey) -> Self:
"""
Implement map_into_course for API compatibility. Shouldn't be used in
new code.
"""
if course_key == self.context_key:
return self
raise ValueError("Cannot use map_into_course like that with this key type.")
class AsideDefinitionKey(DefinitionKey):
"""
A definition key for an aside.
"""
__slots__ = ()
@property
@abstractmethod
def definition_key(self):
"""
Return the DefinitionKey that this aside is decorating.
"""
raise NotImplementedError()
@property
@abstractmethod
def aside_type(self):
"""
Return the type of this aside.
"""
raise NotImplementedError()
class AsideUsageKey(UsageKey):
"""
A usage key for an aside.
"""
__slots__ = ()
@property
@abstractmethod
def usage_key(self):
"""
Return the UsageKey that this aside is decorating.
"""
raise NotImplementedError()
@property
@abstractmethod
def aside_type(self):
"""
Return the type of this aside.
"""
raise NotImplementedError()
# Allow class name to start with a lowercase letter
class i4xEncoder(json.JSONEncoder): # pylint: disable=invalid-name
"""
If provided as the cls to json.dumps, will serialize and Locations as i4x strings and other
keys using the unicode strings.
"""
def default(self, o):
if isinstance(o, OpaqueKey):
return str(o)
super().default(o)
return None
class BlockTypeKey(OpaqueKey):
"""
A key class that encodes XBlock-family block types, including which family the block
was loaded from.
"""
KEY_TYPE = 'block_type'
__slots__ = ()
@property
@abstractmethod
def block_family(self) -> str:
"""
Return the block-family identifier (the entry-point used to load that block
family).
"""
raise NotImplementedError()
@property
@abstractmethod
def block_type(self) -> str:
"""
Return the block_type of this block (the key in the entry-point to load the block
with).
"""
raise NotImplementedError()