1
1
import copy
2
2
import importlib
3
3
import re
4
+ from warnings import warn
4
5
5
6
from nbconvert .preprocessors import ExecutePreprocessor
6
7
@@ -51,6 +52,19 @@ def get_driver_module(nb, override=None):
51
52
assert kernel_name_re .match (module_name )
52
53
return importlib .import_module ('nbparameterise.code_drivers.%s' % module_name )
53
54
55
+ def extract_parameter_dict (nb , lang = None ):
56
+ """Returns a dictionary of Parameter objects derived from the notebook.
57
+
58
+ 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.
61
+
62
+ lang may be used to override the kernel name embedded in the notebook. For
63
+ now, nbparameterise only handles 'python'.
64
+ """
65
+ params = extract_parameters (nb , lang )
66
+ return {p .name : p for p in params }
67
+
54
68
def extract_parameters (nb , lang = None ):
55
69
"""Returns a list of Parameter instances derived from the notebook.
56
70
@@ -59,7 +73,7 @@ def extract_parameters(nb, lang=None):
59
73
metadata; this will be attached as the .metadata instance on each one.
60
74
61
75
lang may be used to override the kernel name embedded in the notebook. For
62
- now, nbparameterise only handles 'python3' and 'python2 '.
76
+ now, nbparameterise only handles 'python '.
63
77
"""
64
78
drv = get_driver_module (nb , override = lang )
65
79
params = list (drv .extract_definitions (first_code_cell (nb ).source ))
@@ -70,8 +84,8 @@ def extract_parameters(nb, lang=None):
70
84
71
85
return params
72
86
73
- def parameter_values (params , ** kwargs ):
74
- """Return a copy of the parameter list, substituting values from kwargs.
87
+ def parameter_values (params , new_values = None , new = 'ignore' , ** kwargs ):
88
+ """Return a new parameter list/dict , substituting values from kwargs.
75
89
76
90
Usage example::
77
91
@@ -81,20 +95,42 @@ def parameter_values(params, **kwargs):
81
95
)
82
96
83
97
Any parameters not supplied will keep their original value.
98
+ Names not already in params are ignored by default, but can be added with
99
+ ``new='add'`` or cause an error with ``new='error'``.
100
+
101
+ This can be used with either a dict from :func:`extract_parameter_dict`
102
+ or a list from :func:`extract_parameters`. It will return the corresponding
103
+ container type.
84
104
"""
85
- res = []
86
- for p in params :
87
- if p .name in kwargs :
88
- res .append (p .with_value (kwargs [p .name ]))
89
- else :
90
- res .append (p )
105
+ if new not in {'ignore' , 'add' , 'error' }:
106
+ raise ValueError ("new= must be one of 'ignore'/'add'/'error'" )
107
+ new_values = (new_values or {}).copy ()
108
+ new_values .update (kwargs )
109
+
110
+ if isinstance (params , dict ):
111
+ new_list = parameter_values (params .values (), new_values , new = new )
112
+ return {p .name : p for p in new_list }
113
+
114
+ res = [p .with_value (new_values [p .name ]) if p .name in new_values else p
115
+ for p in params ]
116
+
117
+ new_keys = set (new_values ) - {p .name for p in params }
118
+ if new == 'error' :
119
+ if new_keys :
120
+ raise KeyError (f"Unexpected keys: { sorted (new_keys )} " )
121
+ elif new == 'add' :
122
+ for k in new_keys :
123
+ value = new_values [k ]
124
+ res .append (Parameter (k , type (value ), value ))
125
+
91
126
return res
92
127
93
128
def replace_definitions (nb , values , execute = False , execute_resources = None ,
94
129
lang = None , * , comments = True ):
95
130
"""Return a copy of nb with the first code cell defining the given parameters.
96
131
97
- values should be a list of Parameter objects (as returned by extract_parameters),
132
+ values should be a dict (from :func:`extract_parameter_dict`) or a list
133
+ (from :func:`extract_parameters`) of :class:`Parameter` objects,
98
134
with their .value attribute set to the desired value.
99
135
100
136
If execute is True, the notebook is executed with the new values.
@@ -104,13 +140,20 @@ def replace_definitions(nb, values, execute=False, execute_resources=None,
104
140
105
141
lang may be used to override the kernel name embedded in the notebook. For
106
142
now, nbparameterise only handles 'python3' and 'python2'.
107
-
108
- If comment is True, comments attached to the parameters will be included
109
- in the replaced code, on the same line as the definition.
110
143
"""
144
+ if isinstance (values , list ):
145
+ values = {p .name : p for p in values }
146
+
147
+ if not comments :
148
+ warn ("comments=False is now ignored" , stacklevel = 2 )
149
+
111
150
nb = copy .deepcopy (nb )
151
+ params_cell = first_code_cell (nb )
152
+
112
153
drv = get_driver_module (nb , override = lang )
113
- first_code_cell (nb ).source = drv .build_definitions (values , comments = comments )
154
+ params_cell .source = drv .build_definitions (
155
+ values , prev_code = params_cell .source
156
+ )
114
157
if execute :
115
158
resources = execute_resources or {}
116
159
nb , resources = ExecutePreprocessor ().preprocess (nb , resources )
0 commit comments