Skip to content

Commit 271b018

Browse files
committed
Add setup_code parameter to extract_variables
This parameter allows the caller to pass an additional code block which is run before the parameter-defining block. This is a first step towards implementing improved parameter extraction (Issue 9), but will require some work in ScriptCreator to become useful.
1 parent ab307c1 commit 271b018

File tree

4 files changed

+36
-12
lines changed

4 files changed

+36
-12
lines changed

examples/dynamic.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@
212212
"name": "python",
213213
"nbconvert_exporter": "python",
214214
"pygments_lexer": "ipython3",
215-
"version": "3.12.5"
215+
"version": "3.12.8"
216216
}
217217
},
218218
"nbformat": 4,

test/test_core.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ def test_image_builder_init(init_mock, tmp_path, tag):
4747
init_mock.assert_called_once_with(nb_path)
4848

4949

50-
def test_init_runner_invalid_image_type():
50+
def test_runner_init_invalid_image_type():
5151
with pytest.raises(ValueError, match='Invalid type "int"'):
5252
# noinspection PyTypeChecker
5353
xcengine.core.ContainerRunner(666, pathlib.Path("/foo"))
5454

5555

56-
def test_init_runner_with_string():
56+
def test_runner_init_with_string():
5757
image_name = "foo"
5858
image_mock = Mock(docker.models.images.Image)
5959
client_mock = Mock(docker.client.DockerClient)
@@ -69,7 +69,7 @@ def get_mock(name):
6969
assert image_mock == runner.image
7070

7171

72-
def test_init_runner_with_image():
72+
def test_runner_init_with_image():
7373
runner = xcengine.core.ContainerRunner(
7474
image := Mock(docker.models.images.Image), pathlib.Path("/foo")
7575
)

test/test_parameters.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,25 @@ def test_parameters_from_code(expected_vars):
156156
)
157157

158158

159+
def test_parameters_from_code_with_setup(expected_vars):
160+
assert (
161+
xcengine.parameters.NotebookParameters.from_code(
162+
"""
163+
some_int = 2 * half_of_some_int
164+
some_float = 3.14159
165+
some_string = some_uppercase_string.lower()
166+
some_bool = not not_some_bool
167+
""",
168+
setup_code="""
169+
half_of_some_int = 21
170+
some_uppercase_string = "FOO"
171+
not_some_bool = True
172+
"""
173+
).params
174+
== expected_vars
175+
)
176+
177+
159178
def test_parameters_get_workflow_inputs(notebook_parameters):
160179
assert notebook_parameters.get_cwl_workflow_inputs() == {
161180
"some_int": {

xcengine/parameters.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ def make_cwl_params(self):
3535
self.cwl_params["product"] = "Directory", None
3636

3737
@classmethod
38-
def from_code(cls, code: str) -> "NotebookParameters":
38+
def from_code(cls, code: str, setup_code: str | None = None) -> "NotebookParameters":
3939
# TODO run whole notebook up to params cell, not just the params cell!
4040
# (Because it might use imports etc. from earlier in the notebook.)
4141
# This will need some tweaking of the parameter extraction -- see
4242
# comment therein.
43-
return cls(cls.extract_variables(code))
43+
return cls(cls.extract_variables(code, setup_code))
4444

4545
@classmethod
4646
def from_yaml(cls, yaml_content: str | typing.IO) -> "NotebookParameters":
@@ -61,12 +61,17 @@ def from_yaml_file(cls, path: str | os.PathLike) -> "NotebookParameters":
6161
return cls.from_yaml(fh)
6262

6363
@classmethod
64-
def extract_variables(cls, code: str) -> dict[str, tuple[type, Any]]:
65-
# TODO: just working on a single code block is insufficient:
66-
# We should execute everything up to the params cell, but only extract
67-
# variables defined in the params cell.
68-
exec(code, globals(), locals_ := {})
69-
return {k: cls.make_param_tuple(locals_[k]) for k in (locals_.keys())}
64+
def extract_variables(cls, code: str, setup_code: str | None = None) -> dict[str, tuple[type, Any]]:
65+
pass
66+
if setup_code is None:
67+
locals_ = {}
68+
old_locals = {}
69+
else:
70+
exec(setup_code, globals(), locals_ := {})
71+
old_locals = locals_.copy()
72+
exec(code, globals(), locals_)
73+
new_vars = locals_.keys() - old_locals.keys()
74+
return {k: cls.make_param_tuple(locals_[k]) for k in new_vars}
7075

7176
@staticmethod
7277
def make_param_tuple(value: Any) -> tuple[type, Any]:

0 commit comments

Comments
 (0)