diff --git a/xblock/core.py b/xblock/core.py index 7ff362475..d3ca513d9 100644 --- a/xblock/core.py +++ b/xblock/core.py @@ -799,6 +799,7 @@ def __init__( """ if scope_ids is UNSET: raise TypeError('scope_ids are required') + scope_ids.validate_types() # A cache of the parent block, retrieved from .parent self._parent_block = None diff --git a/xblock/fields.py b/xblock/fields.py index 9f814ce06..69c11973e 100644 --- a/xblock/fields.py +++ b/xblock/fields.py @@ -3,8 +3,9 @@ **scopes** to associate each field with particular sets of blocks and users. The hosting runtime application decides what actual storage mechanism to use for each scope. - """ +from __future__ import annotations + from collections import namedtuple import copy import datetime @@ -15,6 +16,8 @@ import traceback import warnings +from opaque_keys.edx.keys import UsageKey, DefinitionKey + import dateutil.parser from lxml import etree import pytz @@ -242,6 +245,27 @@ class ScopeIds(namedtuple('ScopeIds', 'user_id block_type def_id usage_id')): """ __slots__ = () + def validate_types(self): + """ + Raise an AssertionError if any of the ids are an unexpected type. + + Originally, these fields were all freely-typed; but in practice, + edx-platform's XBlock runtime would fail if the ids did not match the + types below. In order to make the XBlock library reflect the + edx-platform reality and improve type-safety, we've decided to actually + enforce the types here, per: + https://github.com/openedx/XBlock/issues/708 + """ + if self.user_id is not None: + if not isinstance(self.user_id, (int, str)): + raise TypeError(f"got {self.user_id=}; should be an int, str, or None") + if not isinstance(self.block_type, str): + raise TypeError(f"got {self.block_type=}; should be a str") + if not isinstance(self.def_id, DefinitionKey): + raise TypeError(f"got {self.def_id=}; should be a DefinitionKey") + if not isinstance(self.usage_id, UsageKey): + raise TypeError(f"got {self.usage_id=}; should be a UsageKey") + # Define special reference that can be used as a field's default in field # definition to signal that the field should default to a unique string value