Skip to content

Commit d8741ed

Browse files
authored
Merge pull request #27 from takluyver/parameters-tag
Select parameters cell based on a 'parameters' tag
2 parents a703e66 + a38a712 commit d8741ed

File tree

6 files changed

+173
-23
lines changed

6 files changed

+173
-23
lines changed

README.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
This is a tool to run notebooks with input values. When you write the notebook,
2-
these are defined in the first code cell, with regular assignments like this:
2+
these are defined in the first code cell - or a cell with a 'parameters' cell
3+
tag - with regular assignments like this:
34

45
.. code-block:: python
56

nbparameterise/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
__version__ = '0.5'
44

55
from .code import (
6-
Parameter, extract_parameters, replace_definitions, parameter_values,
6+
Parameter, extract_parameters, replace_definitions, parameter_values, get_parameter_cell,
77
)

nbparameterise/code.py

+44-17
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ def __eq__(self, other):
3737
and self.value == other.value
3838
)
3939

40+
def get_parameter_cell(nb, tag='parameters'):
41+
cell = find_first_tagged_cell(nb, tag)
42+
if cell is None:
43+
cell = first_code_cell(nb)
44+
return cell
45+
46+
def find_first_tagged_cell(nb, tag):
47+
tag = tag.lower()
48+
for cell in nb.cells:
49+
if cell.cell_type == 'code':
50+
tags = cell.get('metadata', {}).get('tags', [])
51+
if any([i.lower() == tag for i in tags]):
52+
return cell
53+
54+
4055
def first_code_cell(nb):
4156
for cell in nb.cells:
4257
if cell.cell_type == 'code':
@@ -52,31 +67,41 @@ def get_driver_module(nb, override=None):
5267
assert kernel_name_re.match(module_name)
5368
return importlib.import_module('nbparameterise.code_drivers.%s' % module_name)
5469

55-
def extract_parameter_dict(nb, lang=None):
70+
def extract_parameter_dict(nb, lang=None, tag='Parameters'):
5671
"""Returns a dictionary of Parameter objects derived from the notebook.
5772
5873
This looks for assignments (like 'n = 50') in the first code cell of the
59-
notebook. The parameters may also have some metadata stored in the notebook
60-
metadata; this will be attached as the .metadata instance on each one.
74+
notebook, or the first cell with a 'parameters' tag. The parameters may also
75+
have some metadata stored in the notebook metadata; this will be attached as
76+
the .metadata instance on each one.
6177
62-
lang may be used to override the kernel name embedded in the notebook. For
78+
*lang* may be used to override the kernel name embedded in the notebook. For
6379
now, nbparameterise only handles 'python'.
80+
81+
*tag* specifies the cell tag which it will look for, with case-insensitive
82+
matching. If no code cell has the tag, it will take the first code cell.
6483
"""
65-
params = extract_parameters(nb, lang)
84+
params = extract_parameters(nb, lang, tag=tag)
6685
return {p.name: p for p in params}
6786

68-
def extract_parameters(nb, lang=None):
87+
def extract_parameters(nb, lang=None, tag='Parameters'):
6988
"""Returns a list of Parameter instances derived from the notebook.
7089
7190
This looks for assignments (like 'n = 50') in the first code cell of the
72-
notebook. The parameters may also have some metadata stored in the notebook
73-
metadata; this will be attached as the .metadata instance on each one.
91+
notebook, or the first cell with a 'parameters' tag. The parameters may also
92+
have some metadata stored in the notebook metadata; this will be attached as
93+
the .metadata instance on each one.
7494
75-
lang may be used to override the kernel name embedded in the notebook. For
95+
*lang* may be used to override the kernel name embedded in the notebook. For
7696
now, nbparameterise only handles 'python'.
97+
98+
*tag* specifies the cell tag which it will look for, with case-insensitive
99+
matching. If no code cell has the tag, it will take the first code cell.
77100
"""
78101
drv = get_driver_module(nb, override=lang)
79-
params = list(drv.extract_definitions(first_code_cell(nb).source))
102+
cell = get_parameter_cell(nb,tag)
103+
104+
params = list(drv.extract_definitions(cell.source))
80105

81106
# Add extra info from notebook metadata
82107
for param in params:
@@ -126,8 +151,8 @@ def parameter_values(params, new_values=None, new='ignore', **kwargs):
126151
return res
127152

128153
def replace_definitions(nb, values, execute=False, execute_resources=None,
129-
lang=None, *, comments=True):
130-
"""Return a copy of nb with the first code cell defining the given parameters.
154+
lang=None, *, comments=True, tag='Parameters'):
155+
"""Return a copy of nb with the parameter cell defining the given parameters.
131156
132157
values should be a dict (from :func:`extract_parameter_dict`) or a list
133158
(from :func:`extract_parameters`) of :class:`Parameter` objects,
@@ -138,8 +163,12 @@ def replace_definitions(nb, values, execute=False, execute_resources=None,
138163
and if possible should contain a 'path' key for the working directory in
139164
which to run the notebook.
140165
141-
lang may be used to override the kernel name embedded in the notebook. For
166+
*lang* may be used to override the kernel name embedded in the notebook. For
142167
now, nbparameterise only handles 'python3' and 'python2'.
168+
169+
*tag* specifies the cell tag which the parameter cell should have, with
170+
case-insensitive matching. If no code cell has the tag, it will replace the
171+
first code cell.
143172
"""
144173
if isinstance(values, list):
145174
values = {p.name: p for p in values}
@@ -148,12 +177,10 @@ def replace_definitions(nb, values, execute=False, execute_resources=None,
148177
warn("comments=False is now ignored", stacklevel=2)
149178

150179
nb = copy.deepcopy(nb)
151-
params_cell = first_code_cell(nb)
152180

153181
drv = get_driver_module(nb, override=lang)
154-
params_cell.source = drv.build_definitions(
155-
values, prev_code=params_cell.source
156-
)
182+
cell = get_parameter_cell(nb, tag)
183+
cell.source = drv.build_definitions(values, prev_code=cell.source)
157184
if execute:
158185
resources = execute_resources or {}
159186
nb, resources = ExecutePreprocessor().preprocess(nb, resources)

tests/sample_parameters_tag.ipynb

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"woooo=\"1123\""
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": null,
15+
"metadata": {
16+
"tags": [
17+
"Parameters"
18+
]
19+
},
20+
"outputs": [],
21+
"source": [
22+
"a = \"Some text\"\n",
23+
"b = 12\n",
24+
"b2 = -7\n",
25+
"c = 14.0\n",
26+
"d = False # comment:bool\n",
27+
"e = [0, 1.0, True, \"text\", [0, 1]]\n",
28+
"f = {0: 0, \"item\": True, \"dict\": {0: \"text\"}} # comment:dict\n",
29+
"print(\"This should be ignored\")"
30+
]
31+
},
32+
{
33+
"cell_type": "code",
34+
"execution_count": null,
35+
"metadata": {},
36+
"outputs": [],
37+
"source": [
38+
"a, b, c, d, woooo"
39+
]
40+
}
41+
],
42+
"metadata": {
43+
"celltoolbar": "Tags",
44+
"kernelspec": {
45+
"display_name": "Python 3 (ipykernel)",
46+
"language": "python",
47+
"name": "python3"
48+
},
49+
"language_info": {
50+
"codemirror_mode": {
51+
"name": "ipython",
52+
"version": 3
53+
},
54+
"file_extension": ".py",
55+
"mimetype": "text/x-python",
56+
"name": "python",
57+
"nbconvert_exporter": "python",
58+
"pygments_lexer": "ipython3",
59+
"version": "3.8.10"
60+
},
61+
"parameterise": {
62+
"c": {
63+
"display_name": "Sea"
64+
}
65+
}
66+
},
67+
"nbformat": 4,
68+
"nbformat_minor": 1
69+
}

tests/test_basic.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import unittest
33

44
import nbformat
5-
from nbparameterise import code, Parameter
5+
from nbparameterise import code, Parameter,get_parameter_cell
66

77
samplenb = os.path.join(os.path.dirname(__file__), 'sample.ipynb')
88

@@ -50,11 +50,11 @@ def test_rebuild(self):
5050
self.params[4].with_value(True),
5151
]
5252
nb = code.replace_definitions(self.nb, from_form, execute=False)
53-
54-
assert "# comment:bool" in nb.cells[0].source
53+
cell = get_parameter_cell(nb)
54+
assert "# comment:bool" in cell.source
5555

5656
ns = {}
57-
exec(nb.cells[0].source, ns)
57+
exec(cell.source, ns)
5858
assert ns['a'] == "New text"
5959
assert ns['b'] == 21
6060
assert ns['b2'] == -3

tests/test_parameters_tag.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import os.path
2+
3+
import pytest
4+
5+
import nbformat
6+
from nbparameterise import (
7+
extract_parameters, replace_definitions, Parameter, get_parameter_cell,
8+
)
9+
10+
samplenb = os.path.join(os.path.dirname(__file__), 'sample_parameters_tag.ipynb')
11+
12+
@pytest.fixture()
13+
def tagged_cell_nb():
14+
return nbformat.read(samplenb, as_version=4)
15+
16+
17+
def test_extract(tagged_cell_nb):
18+
# Tag is not case-sensitive
19+
params = extract_parameters(tagged_cell_nb, tag='paraMeters')
20+
assert params == [
21+
Parameter('a', str, "Some text"),
22+
Parameter('b', int, 12),
23+
Parameter('b2', int, -7),
24+
Parameter('c', float, 14.0),
25+
Parameter('d', bool, False),
26+
Parameter('e', list, [0, 1.0, True, "text", [0, 1]]),
27+
Parameter('f', dict, {0: 0, "item": True, "dict": {0: "text"}}),
28+
]
29+
assert params[4].comment == '# comment:bool'
30+
assert params[6].comment == '# comment:dict'
31+
assert params[3].metadata == {'display_name': 'Sea'}
32+
33+
def test_rebuild(tagged_cell_nb):
34+
params = extract_parameters(tagged_cell_nb)
35+
from_form = [
36+
params[0].with_value("New text"),
37+
params[1].with_value(21),
38+
params[2].with_value(-3),
39+
params[3].with_value(0.25),
40+
params[4].with_value(True),
41+
]
42+
nb = replace_definitions(tagged_cell_nb, from_form, execute=False)
43+
cell = get_parameter_cell(nb, 'ParametErs') # Not case-sensitive
44+
assert "# comment:bool" in cell.source
45+
46+
ns = {}
47+
exec(cell.source, ns)
48+
assert ns['a'] == "New text"
49+
assert ns['b'] == 21
50+
assert ns['b2'] == -3
51+
assert ns['c'] == 0.25
52+
assert ns['d'] == True
53+

0 commit comments

Comments
 (0)