Key Coherency for openedx-core v1.0
There are more radical changes being floated in https://github.com/openedx/opaque-keys/issues/411 , but I want to focus on two things which I think we could get done in time for openedx-core v1.0.
This will be an… OEP?
1. Consistent names for key-like things
I’m turning this part into an OEP: https://github.com/openedx/openedx-proposals/pull/773
I think we should standardize on what we call the variety of identifiers that we have flying around the platform. As core developers, we know how to context-switch between “entity keys” and “opaque keys” and “primary keys” but I think that’s unreasonable to expect platform developers (or LLMs ;) to do correctly.
Here’s my idea. It would mostly be find-and-replace, focusing on making openedx-core correct rather than on fixing openedx-platform as a whole. The two hard things to fix would be openedx-core database columns and the library backup format.
term | meaning | example use | example value |
|---|---|---|---|
| Database primary key.
|
|
|
| OpaqueKey object (parsed) A parsed key |
|
|
| OpaqueKey string (serialized) |
|
|
Alternative proposal:
| A short string identifier, often used as a building block to an opaque key, but which is not an opaque key on its own. |
|
|
@Braden MacDonald @Dave Ormsbee (Axim) Follow up | String which names something within openedx_content, either globally or locally. Should be suitable for use in URLs and file names. Nice if they are semi-human-readable. These are often identical to or derived from OpaqueKeys, but are not themselves OpaqueKeys. Furthermore, their formats are only by-convention, so they should not be parsed! |
(formerly LearningPackage.key)
(formerly PublishableEntity.key) |
Proposals:
|
2. Drop the term “Locator”
My understanding of the difference between a Key and a Locator within opaque-keys is that:
a Key class is abstract. It defines an entrypoint via its
KEY_TYPEwhich can be plugged into by its subclasses. Optionally it can define abstract methods for children to define. Keys can also be abstract subclasses of other Keys. But, the concrete subclasses of other Keys are always called Locators.a Locator class is concrete and constructible. It’s a subclass of a Key. It must be plugged into the parser via its superclass Key’s entrypoint using its
CANONICAL_NAMESPACE(i.e., its prefix) and can also be plugged in via other prefixes for backcompat.
Example:
class LearningContextKey(OpaqueKey), definesKEY_TYPE = context_keyclass CourseKey(LearningContextKey)class CourseLocator(CourseKey), definesCANONICAL_NAMESPACE = course-v1class LibraryLocator(CourseKey), definesCANONICAL_NAMESPACE = library-v1
class LibraryLocatorV2(LearningContextKey), definesCANONICAL_NAMESPACE = lib
setup.py registers the locators into the
context_keyentrypoint:entry_points={ ..., 'context_key': [ 'course-v1 = opaque_keys.edx.locator:CourseLocator', 'library-v1 = opaque_keys.edx.locator:LibraryLocator', 'lib = opaque_keys.edx.locator:LibraryLocatorV2', # don't use slashes in any new code 'slashes = opaque_keys.edx.locator:CourseLocator', ], ..., }
So, the confusing answer to the question “what’s the difference between locators and keys?” is:
every Key object is also a Locator object
every Locator object is also a Key object
Locator classes are the concrete subclasses of Key classes
I think very very few devs understand this distinction. I finally grokked it last week. I don’t think it’s a helpful distinction.
Without changing the mechanics of KEY_TYPE, CANONICAL_NAMESPACE, I think we could make this all easier to understand by just dropping the term “Locator”. Instead, we would just have abstract key classes and concrete key classes. Concrete classes would be ones that implement all methods and override all required class attributes, including but not limited to CANONICAL_NAMESPACE .
For example, in a fully-cleaned-up opaque-keys repo, we could have:
class OpaqueKey:
KEY_TYPE: str # The entrypoint. Should be set exactly once per Key hierarchy.
CANONICAL_NAMESPACE: str # The key prefix. If unspecified, class is abstract.
class ContextKey(OpaqueKey): # formerly LearningContextKey
KEY_TYPE: "context_key"
... # abstract!
class CourselikeKey(ContextKey): # formerly CourseKey
... # abstract!
class CourseRunKey(CourselikeKey): # formerly CourseLocator
CANONICAL_NAMESPACE = "course-v1"
...
class LegacyLibraryKey(CourselikeKey): # formerly LibraryLocator
CANONICAL_NAMESPACE = "library-v1"
...
class LibraryKey(ContextKey): # formerly LibraryLocatorV2
CANONICAL_NAMESPACE = "lib"
...