Skip to content

Commit

Permalink
move answer submodule to exercise submodule
Browse files Browse the repository at this point in the history
* AnswerRegistry -> ExerciseRegistry
* AnswerWidget -> ExerciseWidget
* answer_key -> exercise_key
* answer_registry -> exercise_registry
  • Loading branch information
agoscinski committed Dec 26, 2023
1 parent 7b4f61e commit f98d5a1
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 135 deletions.
3 changes: 0 additions & 3 deletions src/scwidgets/answer/__init__.py

This file was deleted.

3 changes: 2 additions & 1 deletion src/scwidgets/exercise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ._widget_code_exercise import CodeExercise
from ._widget_exercise_registry import ExerciseRegistry, ExerciseWidget
from ._widget_text_exercise import TextExercise

__all__ = ["CodeExercise", "TextExercise"]
__all__ = ["CodeExercise", "TextExercise", "ExerciseWidget", "ExerciseRegistry"]
20 changes: 10 additions & 10 deletions src/scwidgets/exercise/_widget_code_exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from widget_code_input.utils import CodeValidationError

from .._utils import Formatter
from ..answer import AnswerRegistry, AnswerWidget
from ..check import Check, CheckableWidget, CheckRegistry, ChecksResult
from ..code._widget_code_input import CodeInput
from ..code._widget_parameter_panel import ParameterPanel
Expand All @@ -24,9 +23,10 @@
UpdateCueBox,
UpdateResetCueButton,
)
from ._widget_exercise_registry import ExerciseRegistry, ExerciseWidget


class CodeExercise(VBox, CheckableWidget, AnswerWidget):
class CodeExercise(VBox, CheckableWidget, ExerciseWidget):
"""
A widget to demonstrate code interactively in a variety of ways. It is a combination
of the several widgets that allow to check check, run and visualize code.
Expand Down Expand Up @@ -59,8 +59,8 @@ def __init__(
self,
code: Union[None, WidgetCodeInput, types.FunctionType] = None,
check_registry: Optional[CheckRegistry] = None,
answer_registry: Optional[AnswerRegistry] = None,
answer_key: Optional[str] = None,
exercise_registry: Optional[ExerciseRegistry] = None,
exercise_key: Optional[str] = None,
parameters: Optional[
Union[Dict[str, Union[Check.FunInParamT, Widget]], ParameterPanel]
] = None,
Expand Down Expand Up @@ -90,12 +90,12 @@ def __init__(
else:
self._exercise_description_html = HTMLMath(self._exercise_description)
if exercise_title is None:
if answer_key is None:
if exercise_key is None:
self._exercise_title = None
self._exercise_title_html = None
else:
self._exercise_title = answer_key
self._exercise_title_html = HTML(f"<b>{answer_key}</b>")
self._exercise_title = exercise_key
self._exercise_title_html = HTML(f"<b>{exercise_key}</b>")
else:
self._exercise_title = exercise_title
self._exercise_title_html = HTML(f"<b>{exercise_title}</b>")
Expand Down Expand Up @@ -148,8 +148,8 @@ def __init__(
elif not (isinstance(cue_outputs, list)):
cue_outputs = [cue_outputs]

CheckableWidget.__init__(self, check_registry, answer_key)
AnswerWidget.__init__(self, answer_registry, answer_key)
CheckableWidget.__init__(self, check_registry, exercise_key)
ExerciseWidget.__init__(self, exercise_registry, exercise_key)

self._code = code
self._output = CueOutput()
Expand Down Expand Up @@ -320,7 +320,7 @@ def __init__(
button_tooltip=button_tooltip,
)

if self._answer_registry is None or (
if self._exercise_registry is None or (
self._code is None and self._parameter_panel is None
):
self._save_button = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
from .._utils import Formatter


class AnswerWidget:
class ExerciseWidget:
"""
Any widget inheriting from this class can be (de)serialized
:py:class:`WidgetStateRegistry`. The serialization offered by ipywidgets
cannot be loaded out-of-the-box for restarted notebook since the widget IDs change
:param answer_registry:
the answer registry that registers the answers for this widget
:param exercise_registry:
the exercise registry that registers the answers for this widget
:param answer_key:
:param exercise_key:
Identifier for the widget, must be unique for each regestired widget
Reference
Expand All @@ -34,19 +34,23 @@ class AnswerWidget:

def __init__(
self,
answer_registry: Union[None, AnswerRegistry],
answer_key: Union[None, Hashable],
exercise_registry: Union[None, ExerciseRegistry],
exercise_key: Union[None, Hashable],
):
if answer_registry is not None and answer_key is None:
raise ValueError("answer registry was given but no answer key was given")
elif answer_registry is None and answer_key is not None:
raise ValueError("answer key was given but no answer registry was given")
if exercise_registry is not None and exercise_key is None:
raise ValueError(
"exercise registry was given but no exercise key was given"
)
elif exercise_registry is None and exercise_key is not None:
raise ValueError(
"exercise key was given but no exercise registry was given"
)
# we need to use a key because self is not persistent on kernel restarts
self._answer_registry = answer_registry
self._answer_key = answer_key
self._exercise_registry = exercise_registry
self._exercise_key = exercise_key

if self._answer_registry is not None and answer_key is not None:
self._answer_registry.register_widget(self, self._answer_key)
if self._exercise_registry is not None and exercise_key is not None:
self._exercise_registry.register_widget(self, self._exercise_key)

@property
def answer(self) -> dict:
Expand Down Expand Up @@ -77,34 +81,34 @@ def handle_load_result(self, result: Union[str, Exception]) -> None:
raise NotImplementedError("handle_load_result has not been implemented")

def save(self) -> Union[str, Exception]:
if self._answer_registry is None:
if self._exercise_registry is None:
raise ValueError(
"No answer registry given on initialization, save cannot be used"
"No exercise registry given on initialization, save cannot be used"
)
if self._answer_key is None:
if self._exercise_key is None:
raise ValueError(
"No answer key given on initialization, save cannot be used"
"No exercise key given on initialization, save cannot be used"
)
return self._answer_registry.save_answer(self._answer_key)
return self._exercise_registry.save_answer(self._exercise_key)

def load(self) -> Union[str, Exception]:
if self._answer_registry is None:
if self._exercise_registry is None:
raise ValueError(
"No answer registry given on initialization, load cannot be used"
"No exercise registry given on initialization, load cannot be used"
)
if self._answer_key is None:
if self._exercise_key is None:
raise ValueError(
"No answer key given on initialization, save cannot be used"
"No exercise key given on initialization, save cannot be used"
)
return self._answer_registry.load_answer(self._answer_key)
return self._exercise_registry.load_answer(self._exercise_key)

@property
def answer_registry(self):
return self._answer_registry
def exercise_registry(self):
return self._exercise_registry

@property
def answer_key(self):
return self._answer_key
def exercise_key(self):
return self._exercise_key


class FilenameParser:
Expand Down Expand Up @@ -152,7 +156,7 @@ def verify_valid_student_name(student_name: str):
)


class AnswerRegistry(VBox):
class ExerciseRegistry(VBox):
""" """

def __init__(self, filename_prefix: Optional[str] = None, *args, **kwargs):
Expand Down Expand Up @@ -268,15 +272,15 @@ def registered_widgets(self):
def loaded_file_name(self):
return self._loaded_file_name

def register_widget(self, widget: AnswerWidget, answer_key: Hashable):
def register_widget(self, widget: ExerciseWidget, exercise_key: Hashable):
"""
:param widget:
widget answer is save on click of save button
:param answer_key:
unique answer key for widget to store, so it can be reloaded persistently
:param exercise_key:
unique exercise key for widget to store, so it can be reloaded persistently
after a restart of the python kernel
"""
self._widgets[answer_key] = widget
self._widgets[exercise_key] = widget

def create_new_file(self) -> str:
FilenameParser.verify_valid_student_name(self._student_name_text.value)
Expand Down Expand Up @@ -314,17 +318,17 @@ def create_new_file(self) -> str:
self._loaded_file_name = answers_filename
return f"File {answers_filename!r} created and loaded."

def load_answer(self, answer_key: Hashable) -> str:
def load_answer(self, exercise_key: Hashable) -> str:
"""
Only works when file has been loaded
:param answer_key:
unique answer key for widget to store, so it can be reloaded persistently
:param exercise_key:
unique exercise key for widget to store, so it can be reloaded persistently
after a restart of the python kernel
"""
if answer_key not in self._widgets.keys():
if exercise_key not in self._widgets.keys():
raise KeyError(
f"There is no widget registered with answer key {answer_key!r}."
f"There is no widget registered with exercise key {exercise_key!r}."
)
if self._loaded_file_name is None:
raise ValueError("No file has been selected in the dropdown list.")
Expand All @@ -340,14 +344,15 @@ def load_answer(self, answer_key: Hashable) -> str:
answers_filename = self._answers_files_dropdown.value
with open(answers_filename, "r") as answers_file:
answers = json.load(answers_file)
if answer_key not in answers.keys():
if exercise_key not in answers.keys():
raise KeyError(
f"Your file does not contain the answer with answer key {answer_key!r}."
"Your file does not contain the answer with exercise key "
f"{exercise_key!r}."
)
else:
self._widgets[answer_key].answer = answers[answer_key]
self._widgets[exercise_key].answer = answers[exercise_key]
self._loaded_file_name = answers_filename
return f"Answer has been loaded from file {answers_filename!r}."
return f"Exercise has been loaded from file {answers_filename!r}."

def load_file(self) -> str:
"""
Expand Down Expand Up @@ -376,26 +381,26 @@ def load_file(self) -> str:

with open(answers_filename, "r") as answers_file:
answers = json.load(answers_file)
for answer_key, answer in answers.items():
if answer_key not in self._widgets.keys():
for exercise_key, answer in answers.items():
if exercise_key not in self._widgets.keys():
raise ValueError(
f"Your file contains an answer with key {answer_key!r} "
f"Your file contains an answer with key {exercise_key!r} "
f"with no corresponding registered widget."
)
else:
self._widgets[answer_key].answer = answer
self._widgets[exercise_key].answer = answer
self._loaded_file_name = answers_filename

# only notifiy all widgets when result was successful
for widget in self._widgets.values():
result = f"Answer has been loaded from file {self._loaded_file_name!r}."
result = f"Exercise has been loaded from file {self._loaded_file_name!r}."
widget.handle_load_result(result)
return f"All answers loaded from file {answers_filename!r}."

def save_answer(self, answer_key: Hashable) -> str:
if not (answer_key in self._widgets.keys()):
def save_answer(self, exercise_key: Hashable) -> str:
if not (exercise_key in self._widgets.keys()):
raise KeyError(
f"There is no widget registered with answer key {answer_key!r}."
f"There is no widget registered with exercise key {exercise_key!r}."
)

if self._loaded_file_name is None:
Expand All @@ -411,11 +416,11 @@ def save_answer(self, answer_key: Hashable) -> str:
else:
with open(self._loaded_file_name, "r") as answers_file:
answers = json.load(answers_file)
answers[answer_key] = self._widgets[answer_key].answer
answers[exercise_key] = self._widgets[exercise_key].answer

with open(self._loaded_file_name, "w") as answers_file:
json.dump(answers, answers_file)
result = f"Answer has been saved in file {self._loaded_file_name!r}."
result = f"Exercise has been saved in file {self._loaded_file_name!r}."
return result

def save_all_answers(self) -> str:
Expand All @@ -434,15 +439,15 @@ def save_all_answers(self) -> str:
else:
with open(self._loaded_file_name, "r") as answers_file:
answers = json.load(answers_file)
for answer_key, widget in self._widgets.items():
answers[answer_key] = widget.answer
for exercise_key, widget in self._widgets.items():
answers[exercise_key] = widget.answer

with open(self._loaded_file_name, "w") as answers_file:
json.dump(answers, answers_file)

# only notifiy all widgets when result was successful
for widget in self._widgets.values():
result = f"Answer has been saved in file {self._loaded_file_name!r}."
result = f"Exercise has been saved in file {self._loaded_file_name!r}."
widget.handle_save_result(result)

return f"All answers were saved in file {self._loaded_file_name!r}."
Expand Down
18 changes: 9 additions & 9 deletions src/scwidgets/exercise/_widget_text_exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
from ipywidgets import HTML, HBox, HTMLMath, Layout, Output, Textarea, VBox

from .._utils import Formatter
from ..answer import AnswerRegistry, AnswerWidget
from ..cue import SaveCueBox, SaveResetCueButton
from ._widget_exercise_registry import ExerciseRegistry, ExerciseWidget


class TextExercise(VBox, AnswerWidget):
class TextExercise(VBox, ExerciseWidget):
"""
:param textarea:
a custom textarea with custom styling, if not specified the standard parameters
Expand All @@ -19,8 +19,8 @@ class TextExercise(VBox, AnswerWidget):
def __init__(
self,
value: Optional[str] = None,
answer_key: Optional[str] = None,
answer_registry: Optional[AnswerRegistry] = None,
exercise_key: Optional[str] = None,
exercise_registry: Optional[ExerciseRegistry] = None,
exercise_description: Optional[str] = None,
exercise_title: Optional[str] = None,
*args,
Expand All @@ -32,12 +32,12 @@ def __init__(
else:
self._exercise_description_html = HTMLMath(self._exercise_description)
if exercise_title is None:
if answer_key is None:
if exercise_key is None:
self._exercise_title = None
self._exercise_title_html = None
else:
self._exercise_title = answer_key
self._exercise_title_html = HTML(f"<b>{answer_key}</b>")
self._exercise_title = exercise_key
self._exercise_title_html = HTML(f"<b>{exercise_key}</b>")
else:
self._exercise_title = exercise_title
self._exercise_title_html = HTML(f"<b>{exercise_title}</b>")
Expand All @@ -52,7 +52,7 @@ def __init__(
self._cue_textarea = self._textarea
self._output = Output()

if answer_registry is None:
if exercise_registry is None:
self._save_button = None
self._load_button = None
self._button_panel = None
Expand Down Expand Up @@ -97,7 +97,7 @@ def __init__(
layout=Layout(justify_content="flex-end"),
)

AnswerWidget.__init__(self, answer_registry, answer_key)
ExerciseWidget.__init__(self, exercise_registry, exercise_key)

widget_children = []
if self._exercise_title_html is not None:
Expand Down
Loading

0 comments on commit f98d5a1

Please sign in to comment.