scwidgets 0.1.0.dev0__tar.gz → 0.1.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/PKG-INFO +28 -12
- scwidgets-0.1.0.dev1/README.rst +35 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/__init__.py +2 -20
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/check/_widget_check_registry.py +26 -2
- scwidgets-0.1.0.dev1/src/scwidgets/code/__init__.py +7 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/code/_widget_code_input.py +127 -35
- scwidgets-0.1.0.dev0/src/scwidgets/code/_widget_parameter_panel.py → scwidgets-0.1.0.dev1/src/scwidgets/code/_widget_parameters_panel.py +42 -23
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/_widget_cue.py +1 -1
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/_widget_cue_box.py +12 -4
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/_widget_cue_figure.py +5 -5
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/_widget_cue_object.py +13 -13
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/_widget_cue_output.py +1 -1
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/exercise/_widget_code_exercise.py +140 -109
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/exercise/_widget_exercise_registry.py +115 -45
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/exercise/_widget_text_exercise.py +38 -30
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets.egg-info/PKG-INFO +28 -12
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets.egg-info/SOURCES.txt +2 -2
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/tests/test_answer.py +32 -24
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/tests/test_code.py +119 -44
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/tests/test_widgets.py +3 -3
- scwidgets-0.1.0.dev0/README.rst +0 -19
- scwidgets-0.1.0.dev0/src/scwidgets/code/__init__.py +0 -7
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/LICENSE +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/pyproject.toml +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/setup.cfg +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/_utils.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/check/__init__.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/check/_asserts.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/check/_check.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/css/widgets.css +0 -0
- /scwidgets-0.1.0.dev0/src/scwidgets/_css_style.py → /scwidgets-0.1.0.dev1/src/scwidgets/css_style.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/__init__.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/cue/_widget_reset_cue_button.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets/exercise/__init__.py +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets.egg-info/dependency_links.txt +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets.egg-info/requires.txt +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/src/scwidgets.egg-info/top_level.txt +0 -0
- {scwidgets-0.1.0.dev0 → scwidgets-0.1.0.dev1}/tests/test_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scwidgets
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
4
|
Summary: A collection of widgets to prepare interactive scientific visualisations, including user code input and validation
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -24,22 +24,38 @@ Requires-Dist: widget_code_input>=4.0.13
|
|
|
24
24
|
Requires-Dist: matplotlib
|
|
25
25
|
Requires-Dist: termcolor
|
|
26
26
|
|
|
27
|
-
Important
|
|
28
|
-
=========
|
|
29
|
-
|
|
30
|
-
So far scicode-widget has been created by prototyping without much concern about the code quality. This resulted in faster development time but in cost of readability and maintanability of the code. Since we finished now the prototype phase and have converged on a set of functionalities we are satisfied with, we are in the process of refactoring the resulting code in this branch. While we are refactoring we recommend the usage of the `vertical-slice branch <https://github.com/osscar-org/scicode-widgets/tree/vertical-slice>`_ till all features have been implemented in the refactor.
|
|
31
|
-
|
|
32
|
-
|
|
33
27
|
scicode-widgets
|
|
34
28
|
===============
|
|
35
29
|
|
|
36
30
|
.. marker-package-description
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
*scicode-widgets* is a widget library for the purpose of creating computational
|
|
33
|
+
experiments for educational content. It is targeted to teach students how to
|
|
34
|
+
code and interpret computational experiments while hiding technical details and
|
|
35
|
+
boiler plate code that are not essential for the learning experience. The logic
|
|
36
|
+
is purely written in Python to simplify the development process for scientific
|
|
37
|
+
researchers as they usually are more capable in writing Python code than
|
|
38
|
+
JavaScript. It therefore builds on top of widgets provided by ipywidgets to
|
|
39
|
+
creating a framework out of it.
|
|
40
|
+
|
|
41
|
+
Features
|
|
42
|
+
--------
|
|
43
|
+
|
|
44
|
+
The core features of scicode-widgets are:
|
|
45
|
+
|
|
46
|
+
**Customizable coding exercises and demos**
|
|
47
|
+
|
|
48
|
+
**Checks for students to verify their solution**
|
|
49
|
+
|
|
50
|
+
**Automatic grading using nbgrader**
|
|
51
|
+
|
|
52
|
+
Please continue with our `getting started page <https://scicode-widgets.readthedocs.io/en/latest/getting_started.html>`_
|
|
53
|
+
for a more detailed overview of our features.
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
Supportde jupyter environments
|
|
56
|
+
------------------------------
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
We support the following widget environments
|
|
44
59
|
|
|
45
|
-
|
|
60
|
+
* jupyterlab
|
|
61
|
+
* notebook < 7
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
scicode-widgets
|
|
2
|
+
===============
|
|
3
|
+
|
|
4
|
+
.. marker-package-description
|
|
5
|
+
|
|
6
|
+
*scicode-widgets* is a widget library for the purpose of creating computational
|
|
7
|
+
experiments for educational content. It is targeted to teach students how to
|
|
8
|
+
code and interpret computational experiments while hiding technical details and
|
|
9
|
+
boiler plate code that are not essential for the learning experience. The logic
|
|
10
|
+
is purely written in Python to simplify the development process for scientific
|
|
11
|
+
researchers as they usually are more capable in writing Python code than
|
|
12
|
+
JavaScript. It therefore builds on top of widgets provided by ipywidgets to
|
|
13
|
+
creating a framework out of it.
|
|
14
|
+
|
|
15
|
+
Features
|
|
16
|
+
--------
|
|
17
|
+
|
|
18
|
+
The core features of scicode-widgets are:
|
|
19
|
+
|
|
20
|
+
**Customizable coding exercises and demos**
|
|
21
|
+
|
|
22
|
+
**Checks for students to verify their solution**
|
|
23
|
+
|
|
24
|
+
**Automatic grading using nbgrader**
|
|
25
|
+
|
|
26
|
+
Please continue with our `getting started page <https://scicode-widgets.readthedocs.io/en/latest/getting_started.html>`_
|
|
27
|
+
for a more detailed overview of our features.
|
|
28
|
+
|
|
29
|
+
Supportde jupyter environments
|
|
30
|
+
------------------------------
|
|
31
|
+
|
|
32
|
+
We support the following widget environments
|
|
33
|
+
|
|
34
|
+
* jupyterlab
|
|
35
|
+
* notebook < 7
|
|
@@ -1,38 +1,21 @@
|
|
|
1
|
-
__version__ = "0.1.0-
|
|
1
|
+
__version__ = "0.1.0-dev1"
|
|
2
2
|
__authors__ = "the scicode-widgets developer team"
|
|
3
3
|
|
|
4
|
-
from ._css_style import CssStyle, get_css_style
|
|
5
4
|
from .check import * # noqa: F403
|
|
6
5
|
from .code import * # noqa: F403
|
|
7
6
|
from .cue import * # noqa: F403
|
|
8
7
|
from .exercise import * # noqa: F403
|
|
9
8
|
|
|
10
9
|
__all__ = [ # noqa: F405
|
|
11
|
-
# css_style
|
|
12
|
-
"CssStyle",
|
|
13
|
-
"get_css_style",
|
|
14
10
|
# cue
|
|
15
|
-
"CueWidget",
|
|
16
|
-
"CheckCueBox",
|
|
17
|
-
"CueBox",
|
|
18
|
-
"SaveCueBox",
|
|
19
|
-
"UpdateCueBox",
|
|
20
|
-
"ResetCueButton",
|
|
21
|
-
"SaveResetCueButton",
|
|
22
|
-
"CheckResetCueButton",
|
|
23
|
-
"UpdateResetCueButton",
|
|
24
11
|
"CueOutput",
|
|
25
12
|
"CueObject",
|
|
26
13
|
"CueFigure",
|
|
27
14
|
# code
|
|
28
15
|
"CodeInput",
|
|
29
|
-
"
|
|
16
|
+
"ParametersPanel",
|
|
30
17
|
# check
|
|
31
|
-
"Check",
|
|
32
|
-
"CheckResult",
|
|
33
|
-
"AssertResult",
|
|
34
18
|
"CheckRegistry",
|
|
35
|
-
"CheckableWidget",
|
|
36
19
|
"assert_equal",
|
|
37
20
|
"assert_shape",
|
|
38
21
|
"assert_numpy_allclose",
|
|
@@ -42,6 +25,5 @@ __all__ = [ # noqa: F405
|
|
|
42
25
|
# exercise
|
|
43
26
|
"CodeExercise",
|
|
44
27
|
"TextExercise",
|
|
45
|
-
"ExerciseWidget",
|
|
46
28
|
"ExerciseRegistry",
|
|
47
29
|
]
|
|
@@ -7,8 +7,8 @@ from typing import Callable, List, Optional, Union
|
|
|
7
7
|
|
|
8
8
|
from ipywidgets import Button, HBox, Layout, Output, VBox, Widget
|
|
9
9
|
|
|
10
|
-
from .._css_style import CssStyle
|
|
11
10
|
from .._utils import Formatter
|
|
11
|
+
from ..css_style import CssStyle
|
|
12
12
|
from ._check import Check, CheckResult
|
|
13
13
|
|
|
14
14
|
|
|
@@ -147,11 +147,19 @@ class CheckRegistry(VBox):
|
|
|
147
147
|
self._check_all_widgets_button = Button(description="Check all widgets")
|
|
148
148
|
self._output = Output()
|
|
149
149
|
kwargs["layout"] = kwargs.pop("layout", Layout(width="100%"))
|
|
150
|
+
|
|
151
|
+
self._buttons_hbox = HBox()
|
|
152
|
+
|
|
153
|
+
# needs to be after the _buttons_hbox already was created
|
|
154
|
+
self.display_set_all_references_button = kwargs.pop(
|
|
155
|
+
"display_set_all_references_button", False
|
|
156
|
+
)
|
|
157
|
+
|
|
150
158
|
VBox.__init__(
|
|
151
159
|
self,
|
|
152
160
|
[
|
|
153
161
|
CssStyle(),
|
|
154
|
-
|
|
162
|
+
self._buttons_hbox,
|
|
155
163
|
self._output,
|
|
156
164
|
],
|
|
157
165
|
*args,
|
|
@@ -170,6 +178,22 @@ class CheckRegistry(VBox):
|
|
|
170
178
|
"""
|
|
171
179
|
return self._checks
|
|
172
180
|
|
|
181
|
+
@property
|
|
182
|
+
def display_set_all_references_button(self) -> bool:
|
|
183
|
+
return self._display_set_all_references_button
|
|
184
|
+
|
|
185
|
+
@display_set_all_references_button.setter
|
|
186
|
+
def display_set_all_references_button(self, value: bool):
|
|
187
|
+
if value:
|
|
188
|
+
self._display_set_all_references_button = True
|
|
189
|
+
self._buttons_hbox.children = (
|
|
190
|
+
self._check_all_widgets_button,
|
|
191
|
+
self._set_all_references_button,
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
self._display_set_all_references_button = False
|
|
195
|
+
self._buttons_hbox.children = (self._check_all_widgets_button,)
|
|
196
|
+
|
|
173
197
|
@property
|
|
174
198
|
def registered_widgets(self):
|
|
175
199
|
return self._widgets.copy()
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import copy
|
|
1
3
|
import inspect
|
|
2
4
|
import re
|
|
3
5
|
import sys
|
|
6
|
+
import textwrap
|
|
4
7
|
import traceback
|
|
5
8
|
import types
|
|
6
9
|
import warnings
|
|
7
10
|
from functools import wraps
|
|
8
|
-
from typing import List, Optional
|
|
11
|
+
from typing import Any, List, Optional, Tuple
|
|
9
12
|
|
|
10
13
|
from widget_code_input import WidgetCodeInput
|
|
11
14
|
from widget_code_input.utils import (
|
|
@@ -20,6 +23,20 @@ from ..check import Check
|
|
|
20
23
|
class CodeInput(WidgetCodeInput):
|
|
21
24
|
"""
|
|
22
25
|
Small wrapper around WidgetCodeInput that controls the output
|
|
26
|
+
|
|
27
|
+
:param function: We can automatically parse the function. Note that during
|
|
28
|
+
parsing the source code might be differently formatted and certain
|
|
29
|
+
python functionalities are not formatted. If you notice undesired
|
|
30
|
+
changes by the parsing, please directly specify the function as string
|
|
31
|
+
using the other parameters.
|
|
32
|
+
:param function_name: The name of the function
|
|
33
|
+
:param function_paramaters: The parameters as continuous string as specified in
|
|
34
|
+
the signature of the function. e.g for `foo(x, y = 5)` it should be
|
|
35
|
+
`"x, y = 5"`
|
|
36
|
+
:param docstring: The docstring of the function
|
|
37
|
+
:param function_body: The function definition without indentation
|
|
38
|
+
:param builtins: A dict of variable name and value that is added to the
|
|
39
|
+
globals __builtins__ and thus available on initialization
|
|
23
40
|
"""
|
|
24
41
|
|
|
25
42
|
valid_code_themes = ["nord", "solarizedLight", "basicLight"]
|
|
@@ -31,6 +48,7 @@ class CodeInput(WidgetCodeInput):
|
|
|
31
48
|
function_parameters: Optional[str] = None,
|
|
32
49
|
docstring: Optional[str] = None,
|
|
33
50
|
function_body: Optional[str] = None,
|
|
51
|
+
builtins: Optional[dict[str, Any]] = None,
|
|
34
52
|
code_theme: str = "basicLight",
|
|
35
53
|
):
|
|
36
54
|
if function is not None:
|
|
@@ -38,13 +56,15 @@ class CodeInput(WidgetCodeInput):
|
|
|
38
56
|
function.__name__ if function_name is None else function_name
|
|
39
57
|
)
|
|
40
58
|
function_parameters = (
|
|
41
|
-
|
|
59
|
+
self.get_function_parameters(function)
|
|
42
60
|
if function_parameters is None
|
|
43
61
|
else function_parameters
|
|
44
62
|
)
|
|
45
|
-
docstring =
|
|
63
|
+
docstring = self.get_docstring(function) if docstring is None else docstring
|
|
46
64
|
function_body = (
|
|
47
|
-
self.
|
|
65
|
+
self.get_function_body(function)
|
|
66
|
+
if function_body is None
|
|
67
|
+
else function_body
|
|
48
68
|
)
|
|
49
69
|
|
|
50
70
|
# default parameters from WidgetCodeInput
|
|
@@ -53,6 +73,7 @@ class CodeInput(WidgetCodeInput):
|
|
|
53
73
|
function_parameters = "" if function_parameters is None else function_parameters
|
|
54
74
|
docstring = "\n" if docstring is None else docstring
|
|
55
75
|
function_body = "" if function_body is None else function_body
|
|
76
|
+
self._builtins = {} if builtins is None else builtins
|
|
56
77
|
super().__init__(
|
|
57
78
|
function_name, function_parameters, docstring, function_body, code_theme
|
|
58
79
|
)
|
|
@@ -66,7 +87,7 @@ class CodeInput(WidgetCodeInput):
|
|
|
66
87
|
)
|
|
67
88
|
|
|
68
89
|
@property
|
|
69
|
-
def
|
|
90
|
+
def unwrapped_function(self) -> types.FunctionType:
|
|
70
91
|
"""
|
|
71
92
|
Return the compiled function object.
|
|
72
93
|
|
|
@@ -78,11 +99,37 @@ class CodeInput(WidgetCodeInput):
|
|
|
78
99
|
:raise SyntaxError: if the function code has syntax errors (or if
|
|
79
100
|
the function name is not a valid identifier)
|
|
80
101
|
"""
|
|
81
|
-
|
|
102
|
+
# we shallow copy the builtins to be able to overwrite it
|
|
103
|
+
# if self.builtins changes
|
|
104
|
+
globals_dict = {
|
|
105
|
+
"__builtins__": copy.copy(globals()["__builtins__"]),
|
|
106
|
+
"__name__": "__main__",
|
|
107
|
+
"__doc__": None,
|
|
108
|
+
"__package__": None,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
globals_dict["__builtins__"].update(self._builtins)
|
|
112
|
+
|
|
113
|
+
if not is_valid_variable_name(self.function_name):
|
|
114
|
+
raise SyntaxError("Invalid function name '{}'".format(self.function_name))
|
|
115
|
+
|
|
116
|
+
# Optionally one could do a ast.parse here already, to check syntax
|
|
117
|
+
# before execution
|
|
118
|
+
try:
|
|
119
|
+
exec(
|
|
120
|
+
compile(self.full_function_code, __name__, "exec", dont_inherit=True),
|
|
121
|
+
globals_dict,
|
|
122
|
+
)
|
|
123
|
+
except SyntaxError as exc:
|
|
124
|
+
raise CodeValidationError(
|
|
125
|
+
format_syntax_error_msg(exc), orig_exc=exc
|
|
126
|
+
) from exc
|
|
127
|
+
|
|
128
|
+
return globals_dict[self.function_name]
|
|
82
129
|
|
|
83
130
|
def __call__(self, *args, **kwargs) -> Check.FunOutParamsT:
|
|
84
131
|
"""Calls the wrapped function"""
|
|
85
|
-
return self.
|
|
132
|
+
return self.function(*args, **kwargs)
|
|
86
133
|
|
|
87
134
|
def compatible_with_signature(self, parameters: List[str]) -> str:
|
|
88
135
|
"""
|
|
@@ -105,8 +152,68 @@ class CodeInput(WidgetCodeInput):
|
|
|
105
152
|
return self.function_parameters.replace(",", "").split(" ")
|
|
106
153
|
|
|
107
154
|
@staticmethod
|
|
108
|
-
def
|
|
109
|
-
|
|
155
|
+
def get_docstring(function: types.FunctionType) -> str:
|
|
156
|
+
docstring = function.__doc__
|
|
157
|
+
return "" if docstring is None else textwrap.dedent(docstring)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _get_function_source_and_def(
|
|
161
|
+
function: types.FunctionType,
|
|
162
|
+
) -> Tuple[str, ast.FunctionDef]:
|
|
163
|
+
function_source = inspect.getsource(function)
|
|
164
|
+
function_source = textwrap.dedent(function_source)
|
|
165
|
+
module = ast.parse(function_source)
|
|
166
|
+
if len(module.body) != 1:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"Expected code with one function definition but found {module.body}"
|
|
169
|
+
)
|
|
170
|
+
function_definition = module.body[0]
|
|
171
|
+
if not isinstance(function_definition, ast.FunctionDef):
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"While parsing code found {module.body[0]}"
|
|
174
|
+
" but only ast.FunctionDef is supported."
|
|
175
|
+
)
|
|
176
|
+
return function_source, function_definition
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def get_function_parameters(function: types.FunctionType) -> str:
|
|
180
|
+
function_parameters = []
|
|
181
|
+
function_source, function_definition = CodeInput._get_function_source_and_def(
|
|
182
|
+
function
|
|
183
|
+
)
|
|
184
|
+
idx_start_defaults = len(function_definition.args.args) - len(
|
|
185
|
+
function_definition.args.defaults
|
|
186
|
+
)
|
|
187
|
+
for i, arg in enumerate(function_definition.args.args):
|
|
188
|
+
function_parameter = ast.get_source_segment(function_source, arg)
|
|
189
|
+
# Following PEP 8 in formatting
|
|
190
|
+
if arg.annotation:
|
|
191
|
+
annotation = function_parameter = ast.get_source_segment(
|
|
192
|
+
function_source, arg.annotation
|
|
193
|
+
)
|
|
194
|
+
function_parameter = f"{arg.arg}: {annotation}"
|
|
195
|
+
else:
|
|
196
|
+
function_parameter = f"{arg.arg}"
|
|
197
|
+
if i >= idx_start_defaults:
|
|
198
|
+
default_val = ast.get_source_segment(
|
|
199
|
+
function_source,
|
|
200
|
+
function_definition.args.defaults[i - idx_start_defaults],
|
|
201
|
+
)
|
|
202
|
+
# Following PEP 8 in formatting
|
|
203
|
+
if arg.annotation:
|
|
204
|
+
function_parameter = f"{function_parameter} = {default_val}"
|
|
205
|
+
else:
|
|
206
|
+
function_parameter = f"{function_parameter}={default_val}"
|
|
207
|
+
function_parameters.append(function_parameter)
|
|
208
|
+
|
|
209
|
+
if function_definition.args.kwarg is not None:
|
|
210
|
+
function_parameters.append(f"**{function_definition.args.kwarg.arg}")
|
|
211
|
+
|
|
212
|
+
return ", ".join(function_parameters)
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def get_function_body(function: types.FunctionType) -> str:
|
|
216
|
+
source_lines, _ = inspect.getsourcelines(function)
|
|
110
217
|
|
|
111
218
|
found_def = False
|
|
112
219
|
def_index = 0
|
|
@@ -144,10 +251,10 @@ class CodeInput(WidgetCodeInput):
|
|
|
144
251
|
line[leading_indent:] if line.strip() else "" for line in lines
|
|
145
252
|
)
|
|
146
253
|
|
|
147
|
-
return source
|
|
254
|
+
return source.strip()
|
|
148
255
|
|
|
149
256
|
@property
|
|
150
|
-
def
|
|
257
|
+
def function(self) -> types.FunctionType:
|
|
151
258
|
"""
|
|
152
259
|
Return the compiled function object wrapped by an try-catch block
|
|
153
260
|
raising a `CodeValidationError`.
|
|
@@ -160,29 +267,6 @@ class CodeInput(WidgetCodeInput):
|
|
|
160
267
|
:raise SyntaxError: if the function code has syntax errors (or if
|
|
161
268
|
the function name is not a valid identifier)
|
|
162
269
|
"""
|
|
163
|
-
globals_dict = {
|
|
164
|
-
"__builtins__": globals()["__builtins__"],
|
|
165
|
-
"__name__": "__main__",
|
|
166
|
-
"__doc__": None,
|
|
167
|
-
"__package__": None,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if not is_valid_variable_name(self.function_name):
|
|
171
|
-
raise SyntaxError("Invalid function name '{}'".format(self.function_name))
|
|
172
|
-
|
|
173
|
-
# Optionally one could do a ast.parse here already, to check syntax
|
|
174
|
-
# before execution
|
|
175
|
-
try:
|
|
176
|
-
exec(
|
|
177
|
-
compile(self.full_function_code, __name__, "exec", dont_inherit=True),
|
|
178
|
-
globals_dict,
|
|
179
|
-
)
|
|
180
|
-
except SyntaxError as exc:
|
|
181
|
-
raise CodeValidationError(
|
|
182
|
-
format_syntax_error_msg(exc), orig_exc=exc
|
|
183
|
-
) from exc
|
|
184
|
-
|
|
185
|
-
function_object = globals_dict[self.function_name]
|
|
186
270
|
|
|
187
271
|
def catch_exceptions(func):
|
|
188
272
|
@wraps(func)
|
|
@@ -198,7 +282,15 @@ class CodeInput(WidgetCodeInput):
|
|
|
198
282
|
|
|
199
283
|
return wrapper
|
|
200
284
|
|
|
201
|
-
return catch_exceptions(
|
|
285
|
+
return catch_exceptions(self.unwrapped_function)
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def builtins(self) -> dict[str, Any]:
|
|
289
|
+
return self._builtins
|
|
290
|
+
|
|
291
|
+
@builtins.setter
|
|
292
|
+
def builtins(self, value: dict[str, Any]):
|
|
293
|
+
self._builtins = value
|
|
202
294
|
|
|
203
295
|
|
|
204
296
|
# Temporary fix until https://github.com/osscar-org/widget-code-input/pull/26
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Callable, Dict, List, Union
|
|
1
|
+
from typing import Any, Callable, Dict, List, Union
|
|
2
2
|
|
|
3
3
|
from ipywidgets import Output, VBox, Widget, fixed, interactive
|
|
4
4
|
from traitlets.utils.sentinel import Sentinel
|
|
@@ -6,7 +6,7 @@ from traitlets.utils.sentinel import Sentinel
|
|
|
6
6
|
from ..check import Check
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class ParametersPanel(VBox):
|
|
10
10
|
"""
|
|
11
11
|
A wrapper around ipywidgets.interactive to have more control how to connect the
|
|
12
12
|
parameters and the observation of parameters by buttons and the panels
|
|
@@ -25,7 +25,7 @@ class ParameterPanel(VBox):
|
|
|
25
25
|
if "_option" in parameters.keys():
|
|
26
26
|
raise ValueError(
|
|
27
27
|
"Found interactive argument `_option` in paramaters, but "
|
|
28
|
-
"
|
|
28
|
+
"ParametersPanels should be controled by an exercise widget "
|
|
29
29
|
"to ensure correct initialization."
|
|
30
30
|
)
|
|
31
31
|
|
|
@@ -39,42 +39,61 @@ class ParameterPanel(VBox):
|
|
|
39
39
|
"Assumed that interactive returns an output as last child. "
|
|
40
40
|
"Parameter will be wrongly initialized if this is not True."
|
|
41
41
|
)
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# Because interact only keeps a list of the widgets we build a map
|
|
43
|
+
# so the params can be changed in arbitrary order.
|
|
44
|
+
# Last widget is an output that interact adds to the widgets.
|
|
45
|
+
self._param_to_widget_map = {
|
|
46
|
+
key: widget
|
|
47
|
+
for key, widget in zip(
|
|
48
|
+
parameters.keys(), self._interactive_widget.kwargs_widgets
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
super().__init__(self.panel_parameters_widget)
|
|
44
52
|
|
|
45
53
|
@property
|
|
46
|
-
def
|
|
47
|
-
return self.
|
|
54
|
+
def param_to_widget_map(self) -> dict[str, Widget]:
|
|
55
|
+
return self._param_to_widget_map
|
|
48
56
|
|
|
49
57
|
@property
|
|
50
|
-
def
|
|
51
|
-
return ["value"] * len(self.
|
|
58
|
+
def panel_parameters_trait(self) -> List[str]:
|
|
59
|
+
return ["value"] * len(self.panel_parameters)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def panel_parameters_widget(self) -> List[Widget]:
|
|
63
|
+
"""
|
|
64
|
+
:return: Only parameters that are tunable in the parameter panel are returned.
|
|
65
|
+
Fixed parameters are ignored.
|
|
66
|
+
"""
|
|
67
|
+
return [
|
|
68
|
+
widget
|
|
69
|
+
for widget in self._param_to_widget_map.values()
|
|
70
|
+
if not (isinstance(widget, fixed))
|
|
71
|
+
]
|
|
52
72
|
|
|
53
73
|
@property
|
|
54
|
-
def
|
|
74
|
+
def parameters(self) -> Dict[str, Any]:
|
|
55
75
|
"""
|
|
56
76
|
:return: All parameters that were given on initialization are returned,
|
|
57
77
|
also including also fixed parameters.
|
|
58
78
|
"""
|
|
59
|
-
return self.
|
|
60
|
-
|
|
61
|
-
@params.setter
|
|
62
|
-
def params(self, parameters: dict):
|
|
63
|
-
for i, key in enumerate(self._interactive_widget.kwargs.keys()):
|
|
64
|
-
self._interactive_widget.kwargs_widgets[i].value = parameters[key]
|
|
79
|
+
return {key: widget.value for key, widget in self._param_to_widget_map.items()}
|
|
65
80
|
|
|
66
81
|
@property
|
|
67
|
-
def panel_parameters(self) ->
|
|
82
|
+
def panel_parameters(self) -> Dict[str, Any]:
|
|
68
83
|
"""
|
|
69
84
|
:return: Only parameters that are tunable in the parameter panel are returned.
|
|
70
85
|
Fixed parameters are ignored.
|
|
71
86
|
"""
|
|
72
87
|
return {
|
|
73
|
-
key:
|
|
74
|
-
for
|
|
75
|
-
if not (isinstance(
|
|
88
|
+
key: widget.value
|
|
89
|
+
for key, widget in self._param_to_widget_map.items()
|
|
90
|
+
if not (isinstance(widget, fixed))
|
|
76
91
|
}
|
|
77
92
|
|
|
93
|
+
def update_parameters(self, new_parameters: Dict[str, Any]):
|
|
94
|
+
for key, value in new_parameters.items():
|
|
95
|
+
self.param_to_widget_map[key].value = value
|
|
96
|
+
|
|
78
97
|
def observe_parameters(
|
|
79
98
|
self,
|
|
80
99
|
handler: Callable[[dict], None],
|
|
@@ -82,7 +101,7 @@ class ParameterPanel(VBox):
|
|
|
82
101
|
notification_type: Union[None, str, Sentinel] = "change",
|
|
83
102
|
):
|
|
84
103
|
""" """
|
|
85
|
-
for widget in self.
|
|
104
|
+
for widget in self.panel_parameters_widget:
|
|
86
105
|
widget.observe(handler, trait_name, notification_type)
|
|
87
106
|
|
|
88
107
|
def unobserve_parameters(
|
|
@@ -91,10 +110,10 @@ class ParameterPanel(VBox):
|
|
|
91
110
|
trait_name: Union[str, Sentinel, List[str]],
|
|
92
111
|
notification_type: Union[None, str, Sentinel] = "change",
|
|
93
112
|
):
|
|
94
|
-
for widget in self.
|
|
113
|
+
for widget in self.panel_parameters_widget:
|
|
95
114
|
widget.unobserve(handler, trait_name, notification_type)
|
|
96
115
|
|
|
97
116
|
def set_parameters_widget_attr(self, name: str, value):
|
|
98
|
-
for widget in self.
|
|
117
|
+
for widget in self.panel_parameters_widget:
|
|
99
118
|
if hasattr(widget, name):
|
|
100
119
|
setattr(widget, name, value)
|
|
@@ -56,7 +56,7 @@ class CueWidget:
|
|
|
56
56
|
"traits_to_observe cannot contain lists when "
|
|
57
57
|
"widgets_to_observe is not a list."
|
|
58
58
|
)
|
|
59
|
-
traits_to_observe = [traits_to_observe]
|
|
59
|
+
traits_to_observe = [traits_to_observe] # type: ignore[list-item]
|
|
60
60
|
else:
|
|
61
61
|
if not (isinstance(traits_to_observe, list)):
|
|
62
62
|
raise ValueError(
|
|
@@ -37,7 +37,9 @@ class CueBox(VBox, CueWidget):
|
|
|
37
37
|
def __init__(
|
|
38
38
|
self,
|
|
39
39
|
widgets_to_observe: Union[List[Widget], Widget],
|
|
40
|
-
traits_to_observe: Union[
|
|
40
|
+
traits_to_observe: Union[
|
|
41
|
+
str, Sentinel, List[Union[str, Sentinel, List[str]]]
|
|
42
|
+
] = "value",
|
|
41
43
|
widget_to_cue: Optional[Widget] = None,
|
|
42
44
|
cued: bool = True,
|
|
43
45
|
css_style: Optional[dict] = None,
|
|
@@ -112,7 +114,9 @@ class SaveCueBox(CueBox):
|
|
|
112
114
|
def __init__(
|
|
113
115
|
self,
|
|
114
116
|
widgets_to_observe: Widget,
|
|
115
|
-
traits_to_observe: Union[
|
|
117
|
+
traits_to_observe: Union[
|
|
118
|
+
str, Sentinel, List[Union[str, Sentinel, List[str]]]
|
|
119
|
+
] = "value",
|
|
116
120
|
widget_to_cue: Optional[Widget] = None,
|
|
117
121
|
cued: bool = True,
|
|
118
122
|
*args,
|
|
@@ -151,7 +155,9 @@ class CheckCueBox(CueBox):
|
|
|
151
155
|
def __init__(
|
|
152
156
|
self,
|
|
153
157
|
widgets_to_observe: Widget,
|
|
154
|
-
traits_to_observe: Union[
|
|
158
|
+
traits_to_observe: Union[
|
|
159
|
+
str, Sentinel, List[Union[str, Sentinel, List[str]]]
|
|
160
|
+
] = "value",
|
|
155
161
|
widget_to_cue: Optional[Widget] = None,
|
|
156
162
|
cued: bool = True,
|
|
157
163
|
*args,
|
|
@@ -190,7 +196,9 @@ class UpdateCueBox(CueBox):
|
|
|
190
196
|
def __init__(
|
|
191
197
|
self,
|
|
192
198
|
widgets_to_observe: Widget,
|
|
193
|
-
traits_to_observe: Union[
|
|
199
|
+
traits_to_observe: Union[
|
|
200
|
+
str, Sentinel, List[Union[str, Sentinel, List[str]]]
|
|
201
|
+
] = "value",
|
|
194
202
|
widget_to_cue: Optional[Widget] = None,
|
|
195
203
|
cued: bool = True,
|
|
196
204
|
*args,
|
|
@@ -32,8 +32,8 @@ class CueFigure(CueOutput):
|
|
|
32
32
|
Specify `traitlets.All` to observe all traits.
|
|
33
33
|
:param cued:
|
|
34
34
|
Specifies if it is cued on initialization
|
|
35
|
-
:param
|
|
36
|
-
Hide toolbars and headers when using widget mode
|
|
35
|
+
:param show_toolbars:
|
|
36
|
+
Hide toolbars and headers when using in widget mode.
|
|
37
37
|
:param css_syle:
|
|
38
38
|
- **base**: the css style of the box during initialization
|
|
39
39
|
- **cue**: the css style that is added when :param
|
|
@@ -47,10 +47,10 @@ class CueFigure(CueOutput):
|
|
|
47
47
|
figure: Figure,
|
|
48
48
|
widgets_to_observe: Union[None, List[Widget], Widget] = None,
|
|
49
49
|
traits_to_observe: Union[
|
|
50
|
-
None, str, List[str
|
|
50
|
+
None, str, Sentinel, List[Union[str, Sentinel, List[str]]]
|
|
51
51
|
] = None,
|
|
52
52
|
cued: bool = True,
|
|
53
|
-
|
|
53
|
+
show_toolbars: bool = False,
|
|
54
54
|
css_style: Optional[dict] = None,
|
|
55
55
|
**kwargs,
|
|
56
56
|
):
|
|
@@ -87,7 +87,7 @@ class CueFigure(CueOutput):
|
|
|
87
87
|
"that should be supported on all systems."
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
if
|
|
90
|
+
if show_toolbars:
|
|
91
91
|
# hides unnecessary elements shown with %matplotlib widget
|
|
92
92
|
self.figure.canvas.header_visible = False
|
|
93
93
|
self.figure.canvas.footer_visible = False
|