Skip to content

Commit

Permalink
feat: first pass at type-hints for core, fields, internal
Browse files Browse the repository at this point in the history
several TODOs in fields, still
  • Loading branch information
kdmccormick committed Jan 31, 2024
1 parent 2aea5be commit 2606d4e
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 157 deletions.
78 changes: 43 additions & 35 deletions xblock/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
This code is in the Runtime layer, because it is authored once by edX
and used by all runtimes.
"""
from collections import defaultdict
from __future__ import annotations

import inspect
import os
import warnings
import typing as t
from collections import defaultdict

import pkg_resources
from opaque_keys.edx.keys import LearningContextKey, UsageKey
from web_fragments.fragment import Fragment

import xblock.exceptions
from xblock.exceptions import DisallowedFileError
Expand Down Expand Up @@ -53,26 +57,26 @@ class SharedBlockBase(Plugin):
Behaviors and attrs which all XBlock like things should share
"""

resources_dir = ''
public_dir = 'public'
i18n_js_namespace = None
resources_dir: str = ''
public_dir: str = 'public'
i18n_js_namespace: str | None = None

@classmethod
def get_resources_dir(cls):
def get_resources_dir(cls) -> str:
"""
Gets the resource directory for this XBlock.
"""
return cls.resources_dir

@classmethod
def get_public_dir(cls):
def get_public_dir(cls) -> str:
"""
Gets the public directory for this XBlock.
"""
return cls.public_dir

@classmethod
def get_i18n_js_namespace(cls):
def get_i18n_js_namespace(cls) -> str | None:
"""
Gets the JavaScript translations namespace for this XBlock.
Expand All @@ -83,7 +87,7 @@ def get_i18n_js_namespace(cls):
return cls.i18n_js_namespace

@classmethod
def open_local_resource(cls, uri):
def open_local_resource(cls, uri: str) -> t.BinaryIO:
"""
Open a local resource.
Expand Down Expand Up @@ -125,21 +129,21 @@ def open_local_resource(cls, uri):
# -- Base Block
class XBlock(XmlSerializationMixin, HierarchyMixin, ScopedStorageMixin, RuntimeServicesMixin, HandlersMixin,
IndexInfoMixin, ViewsMixin, SharedBlockBase):
"""Base class for XBlocks.
"""
Base class for XBlocks.
Derive from this class to create a new kind of XBlock. There are no
required methods, but you will probably need at least one view.
Don't provide the ``__init__`` method when deriving from this class.
"""
entry_point = 'xblock.v1'
entry_point: str = 'xblock.v1'

name = String(help="Short name for the block", scope=Scope.settings)
tags = List(help="Tags for this block", scope=Scope.settings)

@class_lazy
def _class_tags(cls): # pylint: disable=no-self-argument
def _class_tags(cls: type[XBlock]) -> set[str]: # pylint: disable=no-self-argument
"""
Collect the tags from all base classes.
"""
Expand All @@ -153,15 +157,15 @@ def _class_tags(cls): # pylint: disable=no-self-argument
@staticmethod
def tag(tags):
"""Returns a function that adds the words in `tags` as class tags to this class."""
def dec(cls):
def dec(cls: type[XBlock]) -> type[XBlock]:
"""Add the words in `tags` as class tags to this class."""
# Add in this class's tags
cls._class_tags.update(tags.replace(",", " ").split()) # pylint: disable=protected-access
return cls
return dec

@classmethod
def load_tagged_classes(cls, tag, fail_silently=True):
def load_tagged_classes(cls, tag, fail_silently=True) -> t.Iterable[type[XBlock]]:
"""
Produce a sequence of all XBlock classes tagged with `tag`.
Expand All @@ -181,7 +185,14 @@ def load_tagged_classes(cls, tag, fail_silently=True):
yield name, class_

# pylint: disable=keyword-arg-before-vararg
def __init__(self, runtime, field_data=None, scope_ids=UNSET, *args, **kwargs):
def __init__(
self,
runtime: Runtime,
field_data: FieldData | None = None,
scope_ids: ScopeIds = UNSET,
*args,
**kwargs
):
"""
Construct a new XBlock.
Expand All @@ -206,7 +217,7 @@ def __init__(self, runtime, field_data=None, scope_ids=UNSET, *args, **kwargs):
super().__init__(runtime=runtime, scope_ids=scope_ids, field_data=field_data, *args, **kwargs)

@property
def usage_key(self):
def usage_key(self) -> UsageKey:
"""
A key identifying this particular usage of the XBlock, unique across all learning contexts in the system.
Expand All @@ -215,7 +226,7 @@ def usage_key(self):
return self.scope_ids.usage_id

@property
def context_key(self):
def context_key(self) -> LearningContextKey | None:
"""
A key identifying the learning context (course, library, etc.) that contains this XBlock usage.
Expand All @@ -231,11 +242,11 @@ def context_key(self):
"""
return getattr(self.scope_ids.usage_id, "context_key", None)

def render(self, view, context=None):
def render(self, view: str, context: dict | None = None) -> Fragment:
"""Render `view` with this block's runtime and the supplied `context`"""
return self.runtime.render(self, view, context)

def validate(self):
def validate(self) -> Validation:
"""
Ask this xblock to validate itself. Subclasses are expected to override this
method, as there is currently only a no-op implementation. Any overriding method
Expand All @@ -244,7 +255,7 @@ def validate(self):
"""
return Validation(self.scope_ids.usage_id)

def ugettext(self, text):
def ugettext(self, text) -> str:
"""
Translates message/text and returns it in a unicode string.
Using runtime to get i18n service.
Expand All @@ -253,7 +264,7 @@ def ugettext(self, text):
runtime_ugettext = runtime_service.ugettext
return runtime_ugettext(text)

def add_xml_to_node(self, node):
def add_xml_to_node(self, node: etree.Element) -> None:
"""
For exporting, set data on etree.Element `node`.
"""
Expand All @@ -262,15 +273,18 @@ def add_xml_to_node(self, node):
self.add_children_to_node(node)


XBlockAsideView: t.TypeAlias = t.Callable[[XBlockAside, XBlock, dict | None], Fragment]


class XBlockAside(XmlSerializationMixin, ScopedStorageMixin, RuntimeServicesMixin, HandlersMixin, SharedBlockBase):
"""
This mixin allows Xblock-like class to declare that it provides aside functionality.
"""

entry_point = "xblock_asides.v1"
entry_point: str = "xblock_asides.v1"

@classmethod
def aside_for(cls, view_name):
def aside_for(cls, view_name: str) -> t.Callable[[XBlockAsideView], XBlockAsideView]:
"""
A decorator to indicate a function is the aside view for the given view_name.
Expand All @@ -283,7 +297,7 @@ def student_aside(self, block, context=None):
"""
# pylint: disable=protected-access
def _decorator(func):
def _decorator(func: XBlockAsideView) -> XBlockAsideView:
if not hasattr(func, '_aside_for'):
func._aside_for = []

Expand All @@ -292,15 +306,15 @@ def _decorator(func):
return _decorator

@classmethod
def should_apply_to_block(cls, block): # pylint: disable=unused-argument
def should_apply_to_block(cls, block: XBlock) -> bool: # pylint: disable=unused-argument
"""
Return True if the aside should be applied to a given block. This can be overridden
if some aside should only wrap blocks with certain properties.
"""
return True

@class_lazy
def _combined_asides(cls): # pylint: disable=no-self-argument
def _combined_asides(cls) -> dict[str, str | None]: # pylint: disable=no-self-argument
"""
A dictionary mapping XBlock view names to the aside method that
decorates them (or None, if there is no decorator for the specified view).
Expand All @@ -314,25 +328,19 @@ def _combined_asides(cls): # pylint: disable=no-self-argument
combined_asides[view] = view_func.__name__
return combined_asides

def aside_view_declaration(self, view_name):
def aside_view_declaration(self, view_name: str) -> XBlockAsideView | None:
"""
Find and return a function object if one is an aside_view for the given view_name
Aside methods declare their view provision via @XBlockAside.aside_for(view_name)
This function finds those declarations for a block.
Arguments:
view_name (str): the name of the view requested.
Returns:
either the function or None
"""
if view_name in self._combined_asides: # pylint: disable=unsupported-membership-test
return getattr(self, self._combined_asides[view_name]) # pylint: disable=unsubscriptable-object
else:
return None

def needs_serialization(self):
def needs_serialization(self) -> bool:
"""
Return True if the aside has any data to serialize to XML.
Expand Down
Loading

0 comments on commit 2606d4e

Please sign in to comment.