Skip to content

Commit e96580a

Browse files
committed
squash: more work on core, fields, plugin, mixins
1 parent f20d72c commit e96580a

File tree

4 files changed

+83
-55
lines changed

4 files changed

+83
-55
lines changed

xblock/core.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
import warnings
1212
import typing as t
1313
from collections import defaultdict
14+
from xml.etree import ElementTree as ET
1415

1516
import pkg_resources
1617
from opaque_keys.edx.keys import LearningContextKey, UsageKey
1718
from web_fragments.fragment import Fragment
1819

1920
import xblock.exceptions
2021
from xblock.exceptions import DisallowedFileError
21-
from xblock.fields import String, List, Scope
22+
from xblock.fields import String, List, Scope, ScopeIds
23+
from xblock.field_data import FieldData
2224
from xblock.internal import class_lazy
2325
import xblock.mixins
2426
from xblock.mixins import (
@@ -33,6 +35,10 @@
3335
from xblock.plugin import Plugin
3436
from xblock.validation import Validation
3537

38+
39+
if t.TYPE_CHECKING:
40+
from xblock.runtime import Runtime
41+
3642
# exposing XML_NAMESPACES as a member of core, in order to avoid importing mixins where
3743
# XML_NAMESPACES are needed (e.g. runtime.py).
3844
XML_NAMESPACES = xblock.mixins.XML_NAMESPACES
@@ -87,7 +93,7 @@ def get_i18n_js_namespace(cls) -> str | None:
8793
return cls.i18n_js_namespace
8894

8995
@classmethod
90-
def open_local_resource(cls, uri: str) -> t.BinaryIO:
96+
def open_local_resource(cls, uri: str) -> t.IO[bytes]:
9197
"""
9298
Open a local resource.
9399
@@ -143,11 +149,11 @@ class XBlock(XmlSerializationMixin, HierarchyMixin, ScopedStorageMixin, RuntimeS
143149
tags = List(help="Tags for this block", scope=Scope.settings)
144150

145151
@class_lazy
146-
def _class_tags(cls: type[XBlock]) -> set[str]: # pylint: disable=no-self-argument
152+
def _class_tags(cls: type[XBlock]) -> set[str]: # type: ignore[misc]
147153
"""
148154
Collect the tags from all base classes.
149155
"""
150-
class_tags = set()
156+
class_tags: set[str] = set()
151157

152158
for base in cls.mro()[1:]: # pylint: disable=no-member
153159
class_tags.update(getattr(base, '_class_tags', set()))
@@ -165,7 +171,9 @@ def dec(cls: type[XBlock]) -> type[XBlock]:
165171
return dec
166172

167173
@classmethod
168-
def load_tagged_classes(cls, tag, fail_silently=True) -> t.Iterable[type[XBlock]]:
174+
def load_tagged_classes(
175+
cls, tag: str, fail_silently: bool = True
176+
) -> t.Iterable[tuple[str, type[XBlock]]]:
169177
"""
170178
Produce a sequence of all XBlock classes tagged with `tag`.
171179
@@ -178,18 +186,17 @@ def load_tagged_classes(cls, tag, fail_silently=True) -> t.Iterable[type[XBlock]
178186
(e.g. on startup or first page load), and in what
179187
contexts. Hence, the flag.
180188
"""
181-
# Allow this method to access the `_class_tags`
182-
# pylint: disable=W0212
183189
for name, class_ in cls.load_classes(fail_silently):
184-
if tag in class_._class_tags:
185-
yield name, class_
190+
xblock_class: type[XBlock] = class_ # type: ignore
191+
if tag in xblock_class._class_tags: # pylint: disable=protected-access
192+
yield name, xblock_class
186193

187194
# pylint: disable=keyword-arg-before-vararg
188195
def __init__(
189196
self,
190197
runtime: Runtime,
191198
field_data: FieldData | None = None,
192-
scope_ids: ScopeIds = UNSET,
199+
scope_ids: ScopeIds | object = UNSET,
193200
*args,
194201
**kwargs
195202
):
@@ -264,7 +271,7 @@ def ugettext(self, text) -> str:
264271
runtime_ugettext = runtime_service.ugettext
265272
return runtime_ugettext(text)
266273

267-
def add_xml_to_node(self, node: etree.Element) -> None:
274+
def add_xml_to_node(self, node: ET.Element) -> None:
268275
"""
269276
For exporting, set data on etree.Element `node`.
270277
"""
@@ -273,18 +280,19 @@ def add_xml_to_node(self, node: etree.Element) -> None:
273280
self.add_children_to_node(node)
274281

275282

276-
XBlockAsideView: t.TypeAlias = t.Callable[[XBlockAside, XBlock, dict | None], Fragment]
283+
# An XBlockAside's view method takes itself, an XBlock, and optional context dict,
284+
# and returns a Fragment.
285+
AsideView = t.Callable[["XBlockAside", XBlock, t.Optional[dict]], Fragment]
277286

278287

279288
class XBlockAside(XmlSerializationMixin, ScopedStorageMixin, RuntimeServicesMixin, HandlersMixin, SharedBlockBase):
280289
"""
281290
This mixin allows Xblock-like class to declare that it provides aside functionality.
282291
"""
283-
284292
entry_point: str = "xblock_asides.v1"
285293

286294
@classmethod
287-
def aside_for(cls, view_name: str) -> t.Callable[[XBlockAsideView], XBlockAsideView]:
295+
def aside_for(cls, view_name: str) -> t.Callable[[AsideView], AsideView]:
288296
"""
289297
A decorator to indicate a function is the aside view for the given view_name.
290298
@@ -297,11 +305,11 @@ def student_aside(self, block, context=None):
297305
298306
"""
299307
# pylint: disable=protected-access
300-
def _decorator(func: XBlockAsideView) -> XBlockAsideView:
308+
def _decorator(func: AsideView) -> AsideView:
301309
if not hasattr(func, '_aside_for'):
302-
func._aside_for = []
310+
func._aside_for = [] # type: ignore
303311

304-
func._aside_for.append(view_name) # pylint: disable=protected-access
312+
func._aside_for.append(view_name) # type: ignore
305313
return func
306314
return _decorator
307315

@@ -321,14 +329,14 @@ def _combined_asides(cls) -> dict[str, str | None]: # pylint: disable=no-self-a
321329
"""
322330
# The method declares what views it decorates. We rely on `dir`
323331
# to handle subclasses and overrides.
324-
combined_asides = defaultdict(None)
332+
combined_asides: dict[str, str | None] = defaultdict(None)
325333
for _view_name, view_func in inspect.getmembers(cls, lambda attr: hasattr(attr, '_aside_for')):
326334
aside_for = getattr(view_func, '_aside_for', [])
327335
for view in aside_for:
328336
combined_asides[view] = view_func.__name__
329337
return combined_asides
330338

331-
def aside_view_declaration(self, view_name: str) -> XBlockAsideView | None:
339+
def aside_view_declaration(self, view_name: str) -> AsideView | None:
332340
"""
333341
Find and return a function object if one is an aside_view for the given view_name
334342

xblock/fields.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,7 @@ class Sentinel:
5757
"""
5858
Class for implementing sentinel objects (only equal to themselves).
5959
"""
60-
name: str = "" # Name is 'optional' for ease of making sub-class instances.
61-
62-
def __post_init__(self):
63-
"""
64-
Ensure `self.name` is set, either by constructor or subclass.
65-
"""
66-
if not self.name:
67-
raise ValueError("Coding error: sentinel must have a name!")
60+
name: str
6861

6962
def __repr__(self) -> str:
7063
return self.name
@@ -120,6 +113,11 @@ def scopes(cls) -> list[BlockScope]:
120113
"""
121114
return list(cls)
122115

116+
@property
117+
def attr_name(self) -> str:
118+
return self.name.lower().replace('.', '_')
119+
120+
123121

124122
class UserScope(Enum):
125123
"""
@@ -154,6 +152,10 @@ def scopes(cls) -> list[UserScope]:
154152
"""
155153
return list(cls)
156154

155+
@property
156+
def attr_name(self) -> str:
157+
return self.name.lower().replace('.', '_')
158+
157159

158160
class ScopeBase(t.NamedTuple):
159161
user: UserScope
@@ -224,9 +226,12 @@ def scopes(cls) -> list[ScopeBase]:
224226
if cls(user, block) not in named_scopes
225227
]
226228

227-
def __new__(cls, user: UserScope, block, name: str | None = None) -> Scope:
229+
def __new__(cls, user: UserScope, block, name: str | None = None) -> Scope: # type: ignore
228230
"""Create a new Scope, with an optional name."""
229-
return cls(user, block, name or f'{user}_{block}')
231+
# TODO: This is a pretty wacky way to set a default value for `name`.
232+
# We should try to refactor this so that Scope is just, like,
233+
# a dataclass with a __post_init__ hook that sets the default `name`.
234+
return ScopeBase.__new__(cls, user, block, name or f'{user}_{block}')
230235

231236
children: t.ClassVar = Sentinel('Scope.children')
232237
parent: t.ClassVar = Sentinel('Scope.parent')
@@ -262,7 +267,7 @@ class Unset(Sentinel):
262267
"""
263268
Indicates that default value has not been provided.
264269
"""
265-
name = "fields.UNSET"
270+
name: str = "fields.UNSET"
266271

267272

268273
@dataclass(frozen=True)
@@ -272,7 +277,7 @@ class UniqueId(Sentinel):
272277
definition to signal that the field should default to a unique string value
273278
calculated at runtime.
274279
"""
275-
name = "fields.UNIQUE_ID"
280+
name: str = "fields.UNIQUE_ID"
276281

277282

278283
@dataclass(frozen=True)
@@ -281,7 +286,7 @@ class NoCacheValue(Sentinel):
281286
Placeholder ('nil') value to indicate when nothing has been stored
282287
in the cache ("None" may be a valid value in the cache, so we cannot use it).
283288
"""
284-
name = "fields.NO_CACHE_VALUE"
289+
name: str = "fields.NO_CACHE_VALUE"
285290

286291

287292
@dataclass(frozen=True)
@@ -290,7 +295,7 @@ class ExplicitlySet(Sentinel):
290295
Placeholder value that indicates that a value is explicitly dirty,
291296
because it was explicitly set.
292297
"""
293-
name = "fields.EXPLICITLY_SET"
298+
name: str = "fields.EXPLICITLY_SET"
294299

295300

296301
# For backwards API compatibility, define an instance of each Field-related sentinel.

xblock/mixins.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
This module defines all of the Mixins that provide components of XBlock-family
33
functionality, such as ScopeStorage, RuntimeServices, and Handlers.
44
"""
5+
from __future__ import annotations
6+
57
from collections import OrderedDict
68
import copy
79
import functools
@@ -14,7 +16,8 @@
1416
from webob import Response
1517

1618
from xblock.exceptions import JsonHandlerError, KeyValueMultiSaveError, XBlockSaveError, FieldDataDeprecationWarning
17-
from xblock.fields import Field, Reference, Scope, ReferenceList
19+
from xblock.fields import Field, Reference, Scope, ScopeIds, ReferenceList
20+
from xblock.field_data import FieldData
1821
from xblock.internal import class_lazy, NamedAttributesMetaclass
1922

2023

@@ -188,7 +191,7 @@ def fields(cls): # pylint: disable=no-self-argument
188191

189192
return fields
190193

191-
def __init__(self, scope_ids, field_data=None, **kwargs):
194+
def __init__(self, scope_ids: ScopeIds, field_data: FieldData | None = None, **kwargs):
192195
"""
193196
Arguments:
194197
field_data (:class:`.FieldData`): Interface used by the XBlock

0 commit comments

Comments
 (0)