From 5410e4e6ff553f3634ad2eaa6bf8620291369d19 Mon Sep 17 00:00:00 2001 From: Kyle McCormick Date: Wed, 15 Nov 2023 14:50:43 -0500 Subject: [PATCH] feat: XBlock.usage_key, XBlock.context_key convenience props Adds opaque-keys as a dependency for some new unit tests. Normally I wouldn't add a dependency just for a couple tests, but we anticipate to make the repo depend on opaque-keys soon anyway: * https://github.com/openedx/XBlock/issues/707 * https://github.com/openedx/XBlock/issues/708 Bumps version from 1.9.1 to 1.10.0 --- CHANGELOG.rst | 8 ++++++++ requirements/base.in | 1 + requirements/base.txt | 12 +++++++++++- requirements/ci.txt | 2 +- requirements/dev.txt | 26 ++++++++++++++++--------- requirements/django.txt | 24 ++++++++++++++++++++--- requirements/doc.txt | 27 +++++++++++++++++++++----- requirements/test.txt | 35 +++++++++++++++++++++++----------- xblock/__init__.py | 2 +- xblock/core.py | 22 +++++++++++++++++++++ xblock/test/test_core.py | 41 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 169 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7c81085e9..05f21a2c4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,14 @@ These are notable changes in XBlock. Unreleased ---------- +1.10.0 - 2024-01-12 +------------------- + +* Add two new properties to ``XBlock`` objects: ``.usage_key`` and ``.context_key``. + These simply expose the values of ``.scope_ids.usage_id`` and ``.scope_ids.usage_id.context_key``, + providing a convenient replacement to the deprecated, edx-platform-specific block properties ``.location`` + and ``.course_id``, respectively. + 1.9.1 - 2023-12-22 ------------------ diff --git a/requirements/base.in b/requirements/base.in index 5bb2941e0..07b5047d2 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,6 +1,7 @@ # Core requirements for using this package -c constraints.txt +edx-opaque-keys fs lxml mako diff --git a/requirements/base.txt b/requirements/base.txt index 6ea042552..3e2a58220 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -6,9 +6,11 @@ # appdirs==1.4.4 # via fs +edx-opaque-keys==2.5.1 + # via -r requirements/base.in fs==2.4.16 # via -r requirements/base.in -lxml==5.0.0 +lxml==5.1.0 # via -r requirements/base.in mako==1.3.0 # via -r requirements/base.in @@ -16,6 +18,10 @@ markupsafe==2.1.3 # via # -r requirements/base.in # mako +pbr==6.0.0 + # via stevedore +pymongo==3.13.0 + # via edx-opaque-keys python-dateutil==2.8.2 # via -r requirements/base.in pytz==2023.3.post1 @@ -28,6 +34,10 @@ six==1.16.0 # via # fs # python-dateutil +stevedore==5.1.0 + # via edx-opaque-keys +typing-extensions==4.9.0 + # via edx-opaque-keys web-fragments==2.1.0 # via -r requirements/base.in webob==1.8.7 diff --git a/requirements/ci.txt b/requirements/ci.txt index 3d0ba7872..760c848cb 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -34,7 +34,7 @@ tomli==2.0.1 # via # pyproject-api # tox -tox==4.11.4 +tox==4.12.0 # via -r requirements/ci.in virtualenv==20.25.0 # via tox diff --git a/requirements/dev.txt b/requirements/dev.txt index 822d10db9..07ef425f7 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -17,15 +17,15 @@ astroid==3.0.2 # -r requirements/test.txt # pylint # pylint-celery -attrs==23.1.0 +attrs==23.2.0 # via # -r requirements/test.txt # hypothesis -boto3==1.34.11 +boto3==1.34.17 # via # -r requirements/test.txt # fs-s3fs -botocore==1.34.11 +botocore==1.34.17 # via # -r requirements/test.txt # boto3 @@ -71,7 +71,7 @@ coverage[toml]==7.4.0 # -r requirements/test.txt # coverage # pytest-cov -ddt==1.7.0 +ddt==1.7.1 # via -r requirements/test.txt diff-cover==4.0.0 # via @@ -93,6 +93,8 @@ django==2.2.28 # openedx-django-pyfs edx-lint==5.3.6 # via -r requirements/test.txt +edx-opaque-keys==2.5.1 + # via -r requirements/test.txt exceptiongroup==1.2.0 # via # -r requirements/test.txt @@ -113,7 +115,7 @@ fs-s3fs==1.1.1 # via # -r requirements/test.txt # openedx-django-pyfs -hypothesis==6.92.2 +hypothesis==6.92.9 # via -r requirements/test.txt importlib-metadata==7.0.1 # via @@ -131,7 +133,7 @@ isort==5.13.2 # via # -r requirements/test.txt # pylint -jinja2==3.1.2 +jinja2==3.1.3 # via # -r requirements/test.txt # code-annotations @@ -148,7 +150,7 @@ jmespath==1.0.1 # botocore lazy==1.6 # via -r requirements/test.txt -lxml==5.0.0 +lxml==5.1.0 # via -r requirements/test.txt mako==1.3.0 # via -r requirements/test.txt @@ -230,6 +232,10 @@ pylint-plugin-utils==0.8.2 # -r requirements/test.txt # pylint-celery # pylint-django +pymongo==3.13.0 + # via + # -r requirements/test.txt + # edx-opaque-keys pyproject-api==1.6.1 # via # -r requirements/ci.txt @@ -239,7 +245,7 @@ pyproject-hooks==1.0.0 # via # -r requirements/pip-tools.txt # build -pytest==7.4.3 +pytest==7.4.4 # via # -r requirements/test.txt # pytest-cov @@ -289,6 +295,7 @@ stevedore==5.1.0 # via # -r requirements/test.txt # code-annotations + # edx-opaque-keys text-unidecode==1.3 # via # -r requirements/test.txt @@ -310,7 +317,7 @@ tomlkit==0.12.3 # via # -r requirements/test.txt # pylint -tox==4.11.4 +tox==4.12.0 # via # -r requirements/ci.txt # -r requirements/test.txt @@ -319,6 +326,7 @@ typing-extensions==4.9.0 # -r requirements/test.txt # annotated-types # astroid + # edx-opaque-keys # inflect # pydantic # pydantic-core diff --git a/requirements/django.txt b/requirements/django.txt index a0b44892c..3faefb599 100644 --- a/requirements/django.txt +++ b/requirements/django.txt @@ -8,9 +8,9 @@ appdirs==1.4.4 # via # -r requirements/base.txt # fs -boto3==1.34.11 +boto3==1.34.17 # via fs-s3fs -botocore==1.34.11 +botocore==1.34.17 # via # boto3 # s3transfer @@ -19,6 +19,8 @@ django==2.2.28 # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/django.in # openedx-django-pyfs +edx-opaque-keys==2.5.1 + # via -r requirements/base.txt fs==2.4.16 # via # -r requirements/base.txt @@ -32,7 +34,7 @@ jmespath==1.0.1 # botocore lazy==1.6 # via -r requirements/django.in -lxml==5.0.0 +lxml==5.1.0 # via -r requirements/base.txt mako==1.3.0 # via -r requirements/base.txt @@ -42,6 +44,14 @@ markupsafe==2.1.3 # mako openedx-django-pyfs==3.4.1 # via -r requirements/django.in +pbr==6.0.0 + # via + # -r requirements/base.txt + # stevedore +pymongo==3.13.0 + # via + # -r requirements/base.txt + # edx-opaque-keys python-dateutil==2.8.2 # via # -r requirements/base.txt @@ -64,6 +74,14 @@ six==1.16.0 # python-dateutil sqlparse==0.4.4 # via django +stevedore==5.1.0 + # via + # -r requirements/base.txt + # edx-opaque-keys +typing-extensions==4.9.0 + # via + # -r requirements/base.txt + # edx-opaque-keys urllib3==1.26.18 # via botocore web-fragments==2.1.0 diff --git a/requirements/doc.txt b/requirements/doc.txt index 036f0cf99..ed9f494e5 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -18,11 +18,11 @@ babel==2.14.0 # sphinx beautifulsoup4==4.12.2 # via pydata-sphinx-theme -boto3==1.34.11 +boto3==1.34.17 # via # -r requirements/django.txt # fs-s3fs -botocore==1.34.11 +botocore==1.34.17 # via # -r requirements/django.txt # boto3 @@ -40,6 +40,8 @@ docutils==0.19 # via # pydata-sphinx-theme # sphinx +edx-opaque-keys==2.5.1 + # via -r requirements/django.txt fs==2.4.16 # via # -r requirements/django.txt @@ -55,7 +57,7 @@ imagesize==1.4.1 # via sphinx importlib-metadata==7.0.1 # via sphinx -jinja2==3.1.2 +jinja2==3.1.3 # via sphinx jmespath==1.0.1 # via @@ -64,7 +66,7 @@ jmespath==1.0.1 # botocore lazy==1.6 # via -r requirements/django.txt -lxml==5.0.0 +lxml==5.1.0 # via -r requirements/django.txt mako==1.3.0 # via -r requirements/django.txt @@ -81,6 +83,10 @@ packaging==23.2 # via # pydata-sphinx-theme # sphinx +pbr==6.0.0 + # via + # -r requirements/django.txt + # stevedore pydata-sphinx-theme==0.14.4 # via sphinx-book-theme pygments==2.17.2 @@ -88,6 +94,10 @@ pygments==2.17.2 # accessible-pygments # pydata-sphinx-theme # sphinx +pymongo==3.13.0 + # via + # -r requirements/django.txt + # edx-opaque-keys python-dateutil==2.8.2 # via # -r requirements/django.txt @@ -140,8 +150,15 @@ sqlparse==0.4.4 # via # -r requirements/django.txt # django +stevedore==5.1.0 + # via + # -r requirements/django.txt + # edx-opaque-keys typing-extensions==4.9.0 - # via pydata-sphinx-theme + # via + # -r requirements/django.txt + # edx-opaque-keys + # pydata-sphinx-theme urllib3==1.26.18 # via # -r requirements/django.txt diff --git a/requirements/test.txt b/requirements/test.txt index b008a9d30..6f9a07209 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -15,13 +15,13 @@ astroid==3.0.2 # -r requirements/test.in # pylint # pylint-celery -attrs==23.1.0 +attrs==23.2.0 # via hypothesis -boto3==1.34.11 +boto3==1.34.17 # via # -r requirements/django.txt # fs-s3fs -botocore==1.34.11 +botocore==1.34.17 # via # -r requirements/django.txt # boto3 @@ -45,7 +45,7 @@ coverage[toml]==7.4.0 # via # -r requirements/test.in # pytest-cov -ddt==1.7.0 +ddt==1.7.1 # via -r requirements/test.in diff-cover==4.0.0 # via @@ -61,6 +61,8 @@ distlib==0.3.8 # openedx-django-pyfs edx-lint==5.3.6 # via -r requirements/test.in +edx-opaque-keys==2.5.1 + # via -r requirements/django.txt exceptiongroup==1.2.0 # via # hypothesis @@ -78,7 +80,7 @@ fs-s3fs==1.1.1 # via # -r requirements/django.txt # openedx-django-pyfs -hypothesis==6.92.2 +hypothesis==6.92.9 # via -r requirements/test.in inflect==7.0.0 # via jinja2-pluralize @@ -86,7 +88,7 @@ iniconfig==2.0.0 # via pytest isort==5.13.2 # via pylint -jinja2==3.1.2 +jinja2==3.1.3 # via # code-annotations # diff-cover @@ -100,7 +102,7 @@ jmespath==1.0.1 # botocore lazy==1.6 # via -r requirements/django.txt -lxml==5.0.0 +lxml==5.1.0 # via -r requirements/django.txt mako==1.3.0 # via -r requirements/django.txt @@ -123,7 +125,9 @@ packaging==23.2 path==16.9.0 # via -r requirements/test.in pbr==6.0.0 - # via stevedore + # via + # -r requirements/django.txt + # stevedore platformdirs==4.1.0 # via # pylint @@ -157,9 +161,13 @@ pylint-plugin-utils==0.8.2 # via # pylint-celery # pylint-django +pymongo==3.13.0 + # via + # -r requirements/django.txt + # edx-opaque-keys pyproject-api==1.6.1 # via tox -pytest==7.4.3 +pytest==7.4.4 # via # -r requirements/test.in # pytest-cov @@ -202,7 +210,10 @@ sqlparse==0.4.4 # -r requirements/django.txt # django stevedore==5.1.0 - # via code-annotations + # via + # -r requirements/django.txt + # code-annotations + # edx-opaque-keys text-unidecode==1.3 # via python-slugify tomli==2.0.1 @@ -214,12 +225,14 @@ tomli==2.0.1 # tox tomlkit==0.12.3 # via pylint -tox==4.11.4 +tox==4.12.0 # via -r requirements/test.in typing-extensions==4.9.0 # via + # -r requirements/django.txt # annotated-types # astroid + # edx-opaque-keys # inflect # pydantic # pydantic-core diff --git a/xblock/__init__.py b/xblock/__init__.py index 564ce3837..d10628e18 100644 --- a/xblock/__init__.py +++ b/xblock/__init__.py @@ -27,4 +27,4 @@ def __init__(self, *args, **kwargs): # without causing a circular import xblock.fields.XBlockMixin = XBlockMixin -__version__ = '1.9.1' +__version__ = '1.10.0' diff --git a/xblock/core.py b/xblock/core.py index 407b5ee68..396a5c264 100644 --- a/xblock/core.py +++ b/xblock/core.py @@ -205,6 +205,28 @@ def __init__(self, runtime, field_data=None, scope_ids=UNSET, *args, **kwargs): # Provide backwards compatibility for external access through _field_data super().__init__(runtime=runtime, scope_ids=scope_ids, field_data=field_data, *args, **kwargs) + @property + def usage_key(self): + """ + Convenient abbreviation for `XBlock.scope_ids.usage_id`. + """ + return self.scope_ids.usage_id + + @property + def context_key(self): + """ + Convenient abbreviation for `.XBlock.scope_ids.usage_id.context_key`. + + Returns: + * `LearningContextKey`, if `.XBlock.scope_ids.usage_id` is a `UsageKey` instance. + * `None`, otherwise. + + After https://github.com/openedx/XBlock/issues/708 is complete, we can assume that + `.XBlock.scope_ids.usage_id` is always a `UsageKey`, and that this method will + always return a `LeraningContextKey`. + """ + return getattr(self.scope_ids.usage_id, "context_key", None) + def render(self, view, context=None): """Render `view` with this block's runtime and the supplied `context`""" return self.runtime.render(self, view, context) diff --git a/xblock/test/test_core.py b/xblock/test/test_core.py index 9f469850b..7f7cd4700 100644 --- a/xblock/test/test_core.py +++ b/xblock/test/test_core.py @@ -4,6 +4,7 @@ """ # Allow accessing protected members for testing purposes # pylint: disable=protected-access + from datetime import datetime import json import re @@ -12,6 +13,7 @@ import ddt import pytest +from opaque_keys.edx.locator import LibraryUsageLocatorV2, LibraryLocatorV2 from webob import Response from xblock.core import XBlock @@ -1103,3 +1105,42 @@ def test_override_index_view(self): self.assertTrue(index_info) self.assertTrue(isinstance(index_info, dict)) self.assertEqual(index_info["test_field"], "ABC123") + + +class TestScopeIdProperties(unittest.TestCase): + """ + Test the .usage_key and .context_key convenience properties. + """ + + class TestXBlock(XBlock): + pass + + library_key = LibraryLocatorV2(org="myOrg", slug="myLib") + library_block_key = LibraryUsageLocatorV2(library_key, "myType", "myBlock") + + def test_key_properties(self): + scope_ids = ScopeIds( + user_id="myUser", + block_type="myType", + def_id="myDefId", + usage_id=self.library_block_key, + ) + block = XBlock(Mock(spec=Runtime), scope_ids=scope_ids) + self.assertEqual(block.usage_key, self.library_block_key) + self.assertEqual(block.context_key, self.library_key) + + def test_key_properties_when_usage_is_not_an_opaque_key(self): + """ + Tests a legacy scenario that we believe only happens in xblock-sdk at this point. + + Remove this test as part of https://github.com/openedx/XBlock/issues/708. + """ + scope_ids = ScopeIds( + user_id="myUser", + block_type="myType", + def_id="myDefId", + usage_id="myWeirdOldUsageId", + ) + block = XBlock(Mock(spec=Runtime), scope_ids=scope_ids) + self.assertEqual(block.usage_key, "myWeirdOldUsageId") + self.assertIsNone(block.context_key)