From e210ea02fed890debd94ca943c11bf3c04074124 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Sun, 20 Apr 2025 20:10:17 +0530 Subject: [PATCH 1/5] feat: context key property for new container locator keys --- opaque_keys/edx/keys.py | 9 +++++++++ opaque_keys/edx/locator.py | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/opaque_keys/edx/keys.py b/opaque_keys/edx/keys.py index 2e0da97..41486ae 100644 --- a/opaque_keys/edx/keys.py +++ b/opaque_keys/edx/keys.py @@ -114,6 +114,15 @@ def org(self) -> str | None: # pragma: no cover def library_key(self): return self.lib_key + @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() + class DefinitionKey(OpaqueKey): """ diff --git a/opaque_keys/edx/locator.py b/opaque_keys/edx/locator.py index d139a71..1eb17c5 100644 --- a/opaque_keys/edx/locator.py +++ b/opaque_keys/edx/locator.py @@ -1675,6 +1675,10 @@ def _from_string(cls, serialized: str) -> Self: except (ValueError, TypeError) as error: raise InvalidKeyError(cls, serialized) from error + @property + def context_key(self) -> LibraryLocatorV2: + return self.library_key + class LibraryContainerLocator(CheckFieldMixin, LibraryItemKey): """ @@ -1714,6 +1718,10 @@ def org(self) -> str | None: # pragma: no cover """ return self.lib_key.org + @property + def context_key(self) -> LibraryLocatorV2: + return self.library_key + def _to_string(self) -> str: """ Serialize this key as a string From 9a217ae46837aca8f4cb2e6bd9059ec5a7dbbb4a Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Mon, 21 Apr 2025 20:22:10 +0530 Subject: [PATCH 2/5] feat: library container usage locator --- opaque_keys/edx/keys.py | 2 +- opaque_keys/edx/locator.py | 26 ++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/opaque_keys/edx/keys.py b/opaque_keys/edx/keys.py index 41486ae..9c5f628 100644 --- a/opaque_keys/edx/keys.py +++ b/opaque_keys/edx/keys.py @@ -118,7 +118,7 @@ def library_key(self): @abstractmethod def context_key(self) -> LearningContextKey: """ - Get the learning context key (LearningContextKey) for this XBlock usage. + Get the learning context key (LearningContextKey) for this item key. May be a course key, library key, or some other LearningContextKey type. """ raise NotImplementedError() diff --git a/opaque_keys/edx/locator.py b/opaque_keys/edx/locator.py index 1eb17c5..1f2ee20 100644 --- a/opaque_keys/edx/locator.py +++ b/opaque_keys/edx/locator.py @@ -1680,6 +1680,13 @@ def context_key(self) -> LibraryLocatorV2: return self.library_key +class LibraryContainerUsageLocator(LibraryUsageLocatorV2): + """ + Virtual usage_key to represet LibraryContainerLocator + """ + CANONICAL_NAMESPACE = 'lb-mapped' + + class LibraryContainerLocator(CheckFieldMixin, LibraryItemKey): """ When serialized, these keys look like: @@ -1722,6 +1729,25 @@ def org(self) -> str | None: # pragma: no cover def context_key(self) -> LibraryLocatorV2: return self.library_key + @property + def lib_usage_key(self) -> LibraryContainerUsageLocator: + usage_key = LibraryContainerUsageLocator( + lib_key=self.library_key, + block_type=self.container_type, + usage_id=self.container_id, + ) + return usage_key + + @classmethod + def from_usage_key(cls, usage_key: LibraryContainerUsageLocator) -> Self: + if not isinstance(usage_key, LibraryContainerUsageLocator): + raise InvalidKeyError(cls, str(usage_key)) + try: + key_str = str(usage_key).replace(LibraryContainerUsageLocator.CANONICAL_NAMESPACE, cls.CANONICAL_NAMESPACE, 1) + return cls.from_string(key_str) + except (ValueError, TypeError) as error: + raise InvalidKeyError(cls, str(usage_key)) from error + def _to_string(self) -> str: """ Serialize this key as a string diff --git a/setup.py b/setup.py index ee96343..7f4077d 100644 --- a/setup.py +++ b/setup.py @@ -167,6 +167,7 @@ def get_version(*file_paths): 'block-v1 = opaque_keys.edx.locator:BlockUsageLocator', 'lib-block-v1 = opaque_keys.edx.locator:LibraryUsageLocator', 'lb = opaque_keys.edx.locator:LibraryUsageLocatorV2', + 'lb-mapped = opaque_keys.edx.locator:LibraryContainerUsageLocator', 'location = opaque_keys.edx.locations:DeprecatedLocation', 'aside-usage-v1 = opaque_keys.edx.asides:AsideUsageKeyV1', 'aside-usage-v2 = opaque_keys.edx.asides:AsideUsageKeyV2', From 1eb8b90e6e838b2aa83a5435df185fdf24d036ce Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 22 Apr 2025 10:14:58 +0530 Subject: [PATCH 3/5] Revert "feat: library container usage locator" This reverts commit e9f7d4a706544fb578c0619436141a02359f3103. --- opaque_keys/edx/keys.py | 2 +- opaque_keys/edx/locator.py | 26 -------------------------- setup.py | 1 - 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/opaque_keys/edx/keys.py b/opaque_keys/edx/keys.py index 9c5f628..41486ae 100644 --- a/opaque_keys/edx/keys.py +++ b/opaque_keys/edx/keys.py @@ -118,7 +118,7 @@ def library_key(self): @abstractmethod def context_key(self) -> LearningContextKey: """ - Get the learning context key (LearningContextKey) for this item key. + Get the learning context key (LearningContextKey) for this XBlock usage. May be a course key, library key, or some other LearningContextKey type. """ raise NotImplementedError() diff --git a/opaque_keys/edx/locator.py b/opaque_keys/edx/locator.py index 1f2ee20..1eb17c5 100644 --- a/opaque_keys/edx/locator.py +++ b/opaque_keys/edx/locator.py @@ -1680,13 +1680,6 @@ def context_key(self) -> LibraryLocatorV2: return self.library_key -class LibraryContainerUsageLocator(LibraryUsageLocatorV2): - """ - Virtual usage_key to represet LibraryContainerLocator - """ - CANONICAL_NAMESPACE = 'lb-mapped' - - class LibraryContainerLocator(CheckFieldMixin, LibraryItemKey): """ When serialized, these keys look like: @@ -1729,25 +1722,6 @@ def org(self) -> str | None: # pragma: no cover def context_key(self) -> LibraryLocatorV2: return self.library_key - @property - def lib_usage_key(self) -> LibraryContainerUsageLocator: - usage_key = LibraryContainerUsageLocator( - lib_key=self.library_key, - block_type=self.container_type, - usage_id=self.container_id, - ) - return usage_key - - @classmethod - def from_usage_key(cls, usage_key: LibraryContainerUsageLocator) -> Self: - if not isinstance(usage_key, LibraryContainerUsageLocator): - raise InvalidKeyError(cls, str(usage_key)) - try: - key_str = str(usage_key).replace(LibraryContainerUsageLocator.CANONICAL_NAMESPACE, cls.CANONICAL_NAMESPACE, 1) - return cls.from_string(key_str) - except (ValueError, TypeError) as error: - raise InvalidKeyError(cls, str(usage_key)) from error - def _to_string(self) -> str: """ Serialize this key as a string diff --git a/setup.py b/setup.py index 7f4077d..ee96343 100644 --- a/setup.py +++ b/setup.py @@ -167,7 +167,6 @@ def get_version(*file_paths): 'block-v1 = opaque_keys.edx.locator:BlockUsageLocator', 'lib-block-v1 = opaque_keys.edx.locator:LibraryUsageLocator', 'lb = opaque_keys.edx.locator:LibraryUsageLocatorV2', - 'lb-mapped = opaque_keys.edx.locator:LibraryContainerUsageLocator', 'location = opaque_keys.edx.locations:DeprecatedLocation', 'aside-usage-v1 = opaque_keys.edx.asides:AsideUsageKeyV1', 'aside-usage-v2 = opaque_keys.edx.asides:AsideUsageKeyV2', From 53f9ba4b447694a31fd520e27200d116c4753026 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 23 Apr 2025 10:53:32 +0530 Subject: [PATCH 4/5] refactor: use lib_key instead of deprecated library_key --- opaque_keys/edx/locator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opaque_keys/edx/locator.py b/opaque_keys/edx/locator.py index 1eb17c5..5ebd1a7 100644 --- a/opaque_keys/edx/locator.py +++ b/opaque_keys/edx/locator.py @@ -1677,7 +1677,7 @@ def _from_string(cls, serialized: str) -> Self: @property def context_key(self) -> LibraryLocatorV2: - return self.library_key + return self.lib_key class LibraryContainerLocator(CheckFieldMixin, LibraryItemKey): @@ -1720,7 +1720,7 @@ def org(self) -> str | None: # pragma: no cover @property def context_key(self) -> LibraryLocatorV2: - return self.library_key + return self.lib_key def _to_string(self) -> str: """ From 168abc6f00e5616f67936efc3cf4b77191ed37c8 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Wed, 23 Apr 2025 12:49:19 +0530 Subject: [PATCH 5/5] feat: add library item model field --- opaque_keys/edx/django/models.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/opaque_keys/edx/django/models.py b/opaque_keys/edx/django/models.py index d9ed737..7c747b3 100644 --- a/opaque_keys/edx/django/models.py +++ b/opaque_keys/edx/django/models.py @@ -3,6 +3,7 @@ If Django is unavailable, none of the classes below will work as intended. """ from __future__ import annotations + import logging import warnings @@ -17,8 +18,7 @@ IsNull = object from opaque_keys import OpaqueKey -from opaque_keys.edx.keys import BlockTypeKey, CourseKey, LearningContextKey, UsageKey - +from opaque_keys.edx.keys import BlockTypeKey, CourseKey, LearningContextKey, LibraryItemKey, UsageKey log = logging.getLogger(__name__) @@ -224,6 +224,17 @@ class UsageKeyField(OpaqueKeyField): _pyi_private_get_type: UsageKey | None +class LibraryItemField(OpaqueKeyField): + """ + A django Field that stores a LibraryItemKey object as a string. + """ + description = "A Location object, saved to the DB in the form of a string" + KEY_CLASS = LibraryItemKey + # Declare the field types for the django-stubs mypy type hint plugin: + _pyi_private_set_type: LibraryItemKey | str | None + _pyi_private_get_type: LibraryItemKey | None + + class LocationKeyField(UsageKeyField): """ A django Field that stores a UsageKey object as a string.