Skip to content

Commit a8a21d0

Browse files
authored
Add init argument builtins to CodeInput (#91)
This allows one to update the builtins variables when compiling the code which is necessary when the teacher wants to provide variables to the code that do not need to be created by the student.
1 parent 155549a commit a8a21d0

File tree

2 files changed

+38
-2
lines changed

2 files changed

+38
-2
lines changed

src/scwidgets/code/_widget_code_input.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ast
2+
import copy
23
import inspect
34
import re
45
import sys
@@ -7,7 +8,7 @@
78
import types
89
import warnings
910
from functools import wraps
10-
from typing import List, Optional, Tuple
11+
from typing import Any, List, Optional, Tuple
1112

1213
from widget_code_input import WidgetCodeInput
1314
from widget_code_input.utils import (
@@ -34,6 +35,8 @@ class CodeInput(WidgetCodeInput):
3435
`"x, y = 5"`
3536
:param docstring: The docstring of the function
3637
: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
3740
"""
3841

3942
valid_code_themes = ["nord", "solarizedLight", "basicLight"]
@@ -45,6 +48,7 @@ def __init__(
4548
function_parameters: Optional[str] = None,
4649
docstring: Optional[str] = None,
4750
function_body: Optional[str] = None,
51+
builtins: Optional[dict[str, Any]] = None,
4852
code_theme: str = "basicLight",
4953
):
5054
if function is not None:
@@ -69,6 +73,7 @@ def __init__(
6973
function_parameters = "" if function_parameters is None else function_parameters
7074
docstring = "\n" if docstring is None else docstring
7175
function_body = "" if function_body is None else function_body
76+
self._builtins = {} if builtins is None else builtins
7277
super().__init__(
7378
function_name, function_parameters, docstring, function_body, code_theme
7479
)
@@ -94,13 +99,17 @@ def unwrapped_function(self) -> types.FunctionType:
9499
:raise SyntaxError: if the function code has syntax errors (or if
95100
the function name is not a valid identifier)
96101
"""
102+
# we shallow copy the builtins to be able to overwrite it
103+
# if self.builtins changes
97104
globals_dict = {
98-
"__builtins__": globals()["__builtins__"],
105+
"__builtins__": copy.copy(globals()["__builtins__"]),
99106
"__name__": "__main__",
100107
"__doc__": None,
101108
"__package__": None,
102109
}
103110

111+
globals_dict["__builtins__"].update(self._builtins)
112+
104113
if not is_valid_variable_name(self.function_name):
105114
raise SyntaxError("Invalid function name '{}'".format(self.function_name))
106115

@@ -275,6 +284,14 @@ def wrapper(*args, **kwargs):
275284

276285
return catch_exceptions(self.unwrapped_function)
277286

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
294+
278295

279296
# Temporary fix until https://github.com/osscar-org/widget-code-input/pull/26
280297
# is merged

tests/test_code.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,25 @@ def test_call(self):
115115
assert code(1, 1) == 2
116116
assert code(0, 1) == 1
117117

118+
def test_builtins(self):
119+
"""Tests if import work when they are specified by builtins."""
120+
import numpy as np
121+
122+
# to check if annotation works
123+
def function(arr: np.ndarray):
124+
return arr + builtins_variable # noqa: F821
125+
126+
code_input = CodeInput(function, builtins={"np": np, "builtins_variable": 0})
127+
code_input.unwrapped_function(np.array([0]))
128+
129+
# check if builtins is overwritten,
130+
# the builtins_variable should not be there anymore afterwards
131+
code_input.builtins = {"np": np}
132+
with pytest.raises(
133+
NameError, match=r".*name 'builtins_variable' is not defined.*"
134+
):
135+
code_input.unwrapped_function(np.array([0]))
136+
118137

119138
def get_code_exercise(
120139
checks: List[Check],

0 commit comments

Comments
 (0)