Convert python text to a notebook. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
text: Python text to convert.
\n
use_black: if True use black to re-format Python code
Convert python text to a notebook. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
py_file: Path to python source file.
\n
ipynb_file: Path to notebook result file.
\n
use_black: if True use black to re-format Python code
Convert ipython notebook inputs to a py code. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
nb: notebook
\n
use_black: if True use black to re-format Python code
Convert ipython notebook inputs to a py file. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
ipynb_file: Path to notebook input file.
\n
py_file: Path to python result file.
\n
use_black: if True use black to re-format Python code
Override if you want to apply some preprocessing to each cell.\nMust return modified cell and resource dictionary.
\n\n
Parameters
\n\n
cell : NotebookNode cell\n Notebook cell being processed\nresources : dictionary\n Additional resources used in the conversion process. Allows\n preprocessors to pass variables into the Jinja engine.\nindex : int\n Index of the cell being processed
Copy env[\"sheet_vars\"][k] into env[k] for all k in sheet_vars.keys() if \"sheet_vars\" defined in env.\nOnly variables that are first assigned in the with block of this task manager are allowed to be assigned.
\n\n
Parameters
\n\n
\n
env: working environment, setting to globals() is usually the correct choice.
\n
result_map: empty dictionary to return results in. result_map[\"sheet_vars\"] is the dictionary if incoming assignments, result_map[\"declared_vars\"] is the dictionary of default names and values.
Convert python text to a notebook. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
text: Python text to convert.
\n
use_black: if True use black to re-format Python code
Convert python text to a notebook. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
py_file: Path to python source file.
\n
ipynb_file: Path to notebook result file.
\n
use_black: if True use black to re-format Python code
Convert ipython notebook inputs to a py code. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
nb: notebook
\n
use_black: if True use black to re-format Python code
Convert ipython notebook inputs to a py file. \n\"''' begin text\" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)\n\"''' # end text\" ends text, and starts a new code block (triple double quotes also allowed)\n\"'''end code'''\" ends code blocks, and starts a new code block (triple double quotes also allowed)
\n\n
Parameters
\n\n
\n
ipynb_file: Path to notebook input file.
\n
py_file: Path to python result file.
\n
use_black: if True use black to re-format Python code
Override if you want to apply some preprocessing to each cell.\nMust return modified cell and resource dictionary.
\n\n
Parameters
\n\n
cell : NotebookNode cell\n Notebook cell being processed\nresources : dictionary\n Additional resources used in the conversion process. Allows\n preprocessors to pass variables into the Jinja engine.\nindex : int\n Index of the cell being processed
Copy env[\"sheet_vars\"][k] into env[k] for all k in sheet_vars.keys() if \"sheet_vars\" defined in env.\nOnly variables that are first assigned in the with block of this task manager are allowed to be assigned.
\n\n
Parameters
\n\n
\n
env: working environment, setting to globals() is usually the correct choice.
\n
result_map: empty dictionary to return results in. result_map[\"sheet_vars\"] is the dictionary if incoming assignments, result_map[\"declared_vars\"] is the dictionary of default names and values.
\n", "signature": "(line) -> str:", "funcdef": "def"}];
// mirrored in build-search-index.js (part 1)
// Also split on html tags. this is a cheap heuristic, but good enough.
diff --git a/pkg/docs/wvpy.html b/pkg/docs/wvpy.html
index c811902..663359c 100644
--- a/pkg/docs/wvpy.html
+++ b/pkg/docs/wvpy.html
@@ -24,6 +24,7 @@
29@contextmanager
+30defrecord_assignments(
+31env,
+32*,
+33result:Dict[str,Any],
+34)->None:
+35"""
+36 Context manager to record all assignments to new variables in a with-block.
+37 New variables being variables not set prior to entering the with block.
+38
+39 Example:
+40 ```python
+41 from wvpy.assignment import dict_to_assignments_str, record_assignments
+42 assignments = {}
+43 with record_assignments(globals(), result=assignments):
+44 a = 1
+45 b = 2
+46 print(assignments)
+47 print(dict_to_assignments_str(assignments))
+48 ```
+49
+50 :param env: working environment, setting to `globals()` is usually the correct choice.
+51 :param result: dictionary to store results in. function calls `.clear()` on result.
+52 :return None:
+53 """
+54assertisinstance(result,dict)
+55pre_known_vars=set(env.keys())
+56result.clear()
+57try:
+58yield
+59finally:
+60post_known_vars=set(env.keys())
+61declared_vars=post_known_vars-pre_known_vars
+62forkinsorted(declared_vars):
+63result[k]=env[k]
+
+
+
+
Context manager to record all assignments to new variables in a with-block.
+New variables being variables not set prior to entering the with block.
66defensure_names_not_already_assigned(env,*,keys:Iterable[str])->None:
+67"""
+68 Check that no key in keys is already set in the environment env.
+69 Raises ValueError if keys are already assigned.
+70
+71 :param env: working environment, setting to `globals()` is usually the correct choice.
+72 :param keys: keys to confirm not already set.
+73 :return None:
+74 """
+75already_assigned_vars=set(keys).intersection(set(env.keys()))
+76iflen(already_assigned_vars)>0:
+77raiseValueError(f"variables already set: {sorted(already_assigned_vars)}")
+
+
+
+
Check that no key in keys is already set in the environment env.
+Raises ValueError if keys are already assigned.
+
+
Parameters
+
+
+
env: working environment, setting to globals() is usually the correct choice.
80defassign_values_from_map(
+ 81env,*,values:Dict[str,Any],expected_keys:Optional[Iterable[str]]
+ 82)->None:
+ 83"""
+ 84 Assign values from map into environment.
+ 85
+ 86 :param env: working environment, setting to `globals()` is usually the correct choice.
+ 87 :param values: dictionary to copy into environment.
+ 88 :param expected_keys: if not null a set of keys we are restricting to
+ 89 :return None:
+ 90 """
+ 91assertisinstance(values,dict)
+ 92forkinvalues.keys():
+ 93assertisinstance(k,str)
+ 94ifexpected_keysisnotNone:
+ 95unexpected_vars=set(values.keys())-set(expected_keys)
+ 96iflen(unexpected_vars)>0:
+ 97raiseValueError(
+ 98f"attempting to assign undeclared variables: {sorted(unexpected_vars)}"
+ 99)
+100# do the assignments
+101fork,vinvalues.items():
+102env[k]=v
+
+
+
+
Assign values from map into environment.
+
+
Parameters
+
+
+
env: working environment, setting to globals() is usually the correct choice.
+
values: dictionary to copy into environment.
+
expected_keys: if not null a set of keys we are restricting to
+
+
+
Returns
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkg/docs/wvpy/jtools.html b/pkg/docs/wvpy/jtools.html
index 49f8195..4992409 100644
--- a/pkg/docs/wvpy/jtools.html
+++ b/pkg/docs/wvpy/jtools.html
@@ -93,9 +93,6 @@
15fromtypingimportAny,Dict,Iterable,List,Optional 16fromfunctoolsimporttotal_ordering 17fromcontextlibimportcontextmanager
- 18fromcollectionsimportnamedtuple
- 19
- 20fromwvpy.utilimportescape_ansi
- 21fromwvpy.ptoolsimportexecute_py
+ 18
+ 19fromwvpy.utilimportescape_ansi
+ 20fromwvpy.ptoolsimportexecute_py
+ 21 22
- 23
- 24have_black=False
- 25try:
- 26importblack
- 27have_black=True
- 28exceptModuleNotFoundError:
- 29pass
- 30
- 31nbf_v=nbformat.v4
- 32
- 33#def _normalize(nb):
- 34# # try and work around version mismatches
- 35# return nbformat.validator.normalize(nb, version=4, version_minor=5)[1]
+ 23have_black=False
+ 24try:
+ 25importblack
+ 26have_black=True
+ 27exceptModuleNotFoundError:
+ 28pass
+ 29
+ 30nbf_v=nbformat.v4
+ 31
+ 32#def _normalize(nb):
+ 33# # try and work around version mismatches
+ 34# return nbformat.validator.normalize(nb, version=4, version_minor=5)[1]
+ 35 36
- 37
- 38# noinspection PyBroadException
- 39defpretty_format_python(python_txt:str,*,black_mode=None)->str:
- 40"""
- 41 Format Python code, using black.
- 42
- 43 :param python_txt: Python code
- 44 :param black_mode: options for black
- 45 :return: formatted Python code
- 46 """
- 47asserthave_black
- 48assertisinstance(python_txt,str)
- 49formatted_python=python_txt.strip('\n')+'\n'
- 50iflen(formatted_python.strip())>0:
- 51ifblack_modeisNone:
- 52black_mode=black.FileMode()
- 53try:
- 54formatted_python=black.format_str(formatted_python,mode=black_mode)
- 55formatted_python=formatted_python.strip('\n')+'\n'
- 56exceptException:
- 57pass
- 58returnformatted_python
+ 37# noinspection PyBroadException
+ 38defpretty_format_python(python_txt:str,*,black_mode=None)->str:
+ 39"""
+ 40 Format Python code, using black.
+ 41
+ 42 :param python_txt: Python code
+ 43 :param black_mode: options for black
+ 44 :return: formatted Python code
+ 45 """
+ 46asserthave_black
+ 47assertisinstance(python_txt,str)
+ 48formatted_python=python_txt.strip('\n')+'\n'
+ 49iflen(formatted_python.strip())>0:
+ 50ifblack_modeisNone:
+ 51black_mode=black.FileMode()
+ 52try:
+ 53formatted_python=black.format_str(formatted_python,mode=black_mode)
+ 54formatted_python=formatted_python.strip('\n')+'\n'
+ 55exceptException:
+ 56pass
+ 57returnformatted_python
+ 58 59
- 60
- 61defconvert_py_code_to_notebook(
- 62text:str,
- 63*,
- 64use_black:bool=False)->nbformat.notebooknode.NotebookNode:
- 65"""
- 66 Convert python text to a notebook.
- 67 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
- 68 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
- 69 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
- 70
- 71 :param text: Python text to convert.
- 72 :param use_black: if True use black to re-format Python code
- 73 :return: a notebook
- 74 """
- 75# https://stackoverflow.com/a/23729611/6901725
- 76# https://nbviewer.org/gist/fperez/9716279
- 77assertisinstance(text,str)
- 78assertisinstance(use_black,bool)
- 79lines=text.splitlines()
- 80begin_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*begin\s+text\s*$")
- 81end_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*#\s*end\s+text\s*$")
- 82end_code_regexp=re.compile(r"(^\s*r?'''\s*end\s+code\s*'''\s*$)|(^\s*r?\"\"\"\s*end\s+code\s*\"\"\"\s*$)")
- 83# run a little code collecting state machine
- 84cells=[]
- 85collecting_python=[]
- 86collecting_text=None
- 87lines.append(None)# append an ending sentinel
- 88# scan input
- 89forlineinlines:
- 90iflineisNone:
- 91is_end=True
- 92text_start=False
- 93code_start=False
- 94code_end=False
- 95else:
- 96is_end=False
- 97text_start=begin_text_regexp.match(line)
- 98code_start=end_text_regexp.match(line)
- 99code_end=end_code_regexp.match(line)
-100ifis_endortext_startorcode_startorcode_end:
-101if(collecting_pythonisnotNone)and(len(collecting_python)>0):
-102python_block=('\n'.join(collecting_python)).strip('\n')+'\n'
-103iflen(python_block.strip())>0:
-104ifuse_blackandhave_black:
-105python_block=pretty_format_python(python_block)
-106cells.append(nbf_v.new_code_cell(python_block))
-107if(collecting_textisnotNone)and(len(collecting_text)>0):
-108txt_block=('\n'.join(collecting_text)).strip('\n')+'\n'
-109iflen(txt_block.strip())>0:
-110cells.append(nbf_v.new_markdown_cell(txt_block))
-111collecting_python=None
-112collecting_text=None
-113ifnotis_end:
-114iftext_start:
-115collecting_text=[]
-116else:
-117collecting_python=[]
-118else:
-119ifcollecting_pythonisnotNone:
-120collecting_python.append(line)
-121ifcollecting_textisnotNone:
-122collecting_text.append(line)
-123foriinrange(len(cells)):
-124cells[i]["id"]=f"cell{i}"
-125nb=nbf_v.new_notebook(cells=cells)
-126# nb = _normalize(nb)
-127returnnb
+ 60defconvert_py_code_to_notebook(
+ 61text:str,
+ 62*,
+ 63use_black:bool=False)->nbformat.notebooknode.NotebookNode:
+ 64"""
+ 65 Convert python text to a notebook.
+ 66 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+ 67 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+ 68 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+ 69
+ 70 :param text: Python text to convert.
+ 71 :param use_black: if True use black to re-format Python code
+ 72 :return: a notebook
+ 73 """
+ 74# https://stackoverflow.com/a/23729611/6901725
+ 75# https://nbviewer.org/gist/fperez/9716279
+ 76assertisinstance(text,str)
+ 77assertisinstance(use_black,bool)
+ 78lines=text.splitlines()
+ 79begin_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*begin\s+text\s*$")
+ 80end_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*#\s*end\s+text\s*$")
+ 81end_code_regexp=re.compile(r"(^\s*r?'''\s*end\s+code\s*'''\s*$)|(^\s*r?\"\"\"\s*end\s+code\s*\"\"\"\s*$)")
+ 82# run a little code collecting state machine
+ 83cells=[]
+ 84collecting_python=[]
+ 85collecting_text=None
+ 86lines.append(None)# append an ending sentinel
+ 87# scan input
+ 88forlineinlines:
+ 89iflineisNone:
+ 90is_end=True
+ 91text_start=False
+ 92code_start=False
+ 93code_end=False
+ 94else:
+ 95is_end=False
+ 96text_start=begin_text_regexp.match(line)
+ 97code_start=end_text_regexp.match(line)
+ 98code_end=end_code_regexp.match(line)
+ 99ifis_endortext_startorcode_startorcode_end:
+100if(collecting_pythonisnotNone)and(len(collecting_python)>0):
+101python_block=('\n'.join(collecting_python)).strip('\n')+'\n'
+102iflen(python_block.strip())>0:
+103ifuse_blackandhave_black:
+104python_block=pretty_format_python(python_block)
+105cells.append(nbf_v.new_code_cell(python_block))
+106if(collecting_textisnotNone)and(len(collecting_text)>0):
+107txt_block=('\n'.join(collecting_text)).strip('\n')+'\n'
+108iflen(txt_block.strip())>0:
+109cells.append(nbf_v.new_markdown_cell(txt_block))
+110collecting_python=None
+111collecting_text=None
+112ifnotis_end:
+113iftext_start:
+114collecting_text=[]
+115else:
+116collecting_python=[]
+117else:
+118ifcollecting_pythonisnotNone:
+119collecting_python.append(line)
+120ifcollecting_textisnotNone:
+121collecting_text.append(line)
+122foriinrange(len(cells)):
+123cells[i]["id"]=f"cell{i}"
+124nb=nbf_v.new_notebook(cells=cells)
+125# nb = _normalize(nb)
+126returnnb
+127128
-129
-130defprepend_code_cell_to_notebook(
-131nb:nbformat.notebooknode.NotebookNode,
-132*,
-133code_text:str,
-134)->nbformat.notebooknode.NotebookNode:
-135"""
-136 Prepend a code cell to a Jupyter notebook.
-137
-138 :param nb: Jupyter notebook to alter
-139 :param code_text: Python source code to add
-140 :return: new notebook
-141 """
-142header_cell=nbf_v.new_code_cell(code_text)
-143# set cell ids to avoid:
-144# "MissingIDFieldWarning: Code cell is missing an id field, this will become a hard error in future nbformat versions."
-145header_cell["id"]="wvpy_header_cell"
-146orig_cells=[c.copy()forcinnb.cells]
-147foriinrange(len(orig_cells)):
-148orig_cells[i]["id"]=f"cell{i}"
-149cells=[header_cell]+orig_cells
-150nb_out=nbf_v.new_notebook(
-151cells=cells
-152)
-153# nb_out = _normalize(nb_out)
-154returnnb_out
+129defprepend_code_cell_to_notebook(
+130nb:nbformat.notebooknode.NotebookNode,
+131*,
+132code_text:str,
+133)->nbformat.notebooknode.NotebookNode:
+134"""
+135 Prepend a code cell to a Jupyter notebook.
+136
+137 :param nb: Jupyter notebook to alter
+138 :param code_text: Python source code to add
+139 :return: new notebook
+140 """
+141header_cell=nbf_v.new_code_cell(code_text)
+142# set cell ids to avoid:
+143# "MissingIDFieldWarning: Code cell is missing an id field, this will become a hard error in future nbformat versions."
+144header_cell["id"]="wvpy_header_cell"
+145orig_cells=[c.copy()forcinnb.cells]
+146foriinrange(len(orig_cells)):
+147orig_cells[i]["id"]=f"cell{i}"
+148cells=[header_cell]+orig_cells
+149nb_out=nbf_v.new_notebook(
+150cells=cells
+151)
+152# nb_out = _normalize(nb_out)
+153returnnb_out
+154155
-156
-157defconvert_py_file_to_notebook(
-158py_file:str,
-159*,
-160ipynb_file:str,
-161use_black:bool=False,
-162)->None:
-163"""
-164 Convert python text to a notebook.
-165 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
-166 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
-167 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
-168
-169 :param py_file: Path to python source file.
-170 :param ipynb_file: Path to notebook result file.
-171 :param use_black: if True use black to re-format Python code
-172 :return: nothing
-173 """
-174assertisinstance(py_file,str)
-175assertisinstance(ipynb_file,str)
-176assertisinstance(use_black,bool)
-177assertpy_file!=ipynb_file# prevent clobber
-178withopen(py_file,'r')asf:
-179text=f.read()
-180nb=convert_py_code_to_notebook(text,use_black=use_black)
-181withopen(ipynb_file,'w')asf:
-182nbformat.write(nb,f)
+156defconvert_py_file_to_notebook(
+157py_file:str,
+158*,
+159ipynb_file:str,
+160use_black:bool=False,
+161)->None:
+162"""
+163 Convert python text to a notebook.
+164 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+165 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+166 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+167
+168 :param py_file: Path to python source file.
+169 :param ipynb_file: Path to notebook result file.
+170 :param use_black: if True use black to re-format Python code
+171 :return: nothing
+172 """
+173assertisinstance(py_file,str)
+174assertisinstance(ipynb_file,str)
+175assertisinstance(use_black,bool)
+176assertpy_file!=ipynb_file# prevent clobber
+177withopen(py_file,'r')asf:
+178text=f.read()
+179nb=convert_py_code_to_notebook(text,use_black=use_black)
+180withopen(ipynb_file,'w')asf:
+181nbformat.write(nb,f)
+182183
-184
-185defconvert_notebook_code_to_py(
-186nb:nbformat.notebooknode.NotebookNode,
-187*,
-188use_black:bool=False,
-189)->str:
-190"""
-191 Convert ipython notebook inputs to a py code.
-192 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
-193 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
-194 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
-195
-196 :param nb: notebook
-197 :param use_black: if True use black to re-format Python code
-198 :return: Python source code
-199 """
-200assertisinstance(use_black,bool)
-201res=[]
-202code_needs_end=False
-203forcellinnb.cells:
-204iflen(cell.source.strip())>0:
-205ifcell.cell_type=='code':
-206ifcode_needs_end:
-207res.append('\n"""end code"""\n')
-208py_text=cell.source.strip('\n')+'\n'
-209ifuse_blackandhave_black:
-210py_text=pretty_format_python(py_text)
-211res.append(py_text)
-212code_needs_end=True
-213else:
-214res.append('\n""" begin text')
-215res.append(cell.source.strip('\n'))
-216res.append('""" # end text\n')
-217code_needs_end=False
-218res_text='\n'+('\n'.join(res)).strip('\n')+'\n\n'
-219returnres_text
+184defconvert_notebook_code_to_py(
+185nb:nbformat.notebooknode.NotebookNode,
+186*,
+187use_black:bool=False,
+188)->str:
+189"""
+190 Convert ipython notebook inputs to a py code.
+191 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+192 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+193 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+194
+195 :param nb: notebook
+196 :param use_black: if True use black to re-format Python code
+197 :return: Python source code
+198 """
+199assertisinstance(use_black,bool)
+200res=[]
+201code_needs_end=False
+202forcellinnb.cells:
+203iflen(cell.source.strip())>0:
+204ifcell.cell_type=='code':
+205ifcode_needs_end:
+206res.append('\n"""end code"""\n')
+207py_text=cell.source.strip('\n')+'\n'
+208ifuse_blackandhave_black:
+209py_text=pretty_format_python(py_text)
+210res.append(py_text)
+211code_needs_end=True
+212else:
+213res.append('\n""" begin text')
+214res.append(cell.source.strip('\n'))
+215res.append('""" # end text\n')
+216code_needs_end=False
+217res_text='\n'+('\n'.join(res)).strip('\n')+'\n\n'
+218returnres_text
+219220
-221
-222defconvert_notebook_file_to_py(
-223ipynb_file:str,
-224*,
-225py_file:str,
-226use_black:bool=False,
-227)->None:
-228"""
-229 Convert ipython notebook inputs to a py file.
-230 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
-231 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
-232 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
-233
-234 :param ipynb_file: Path to notebook input file.
-235 :param py_file: Path to python result file.
-236 :param use_black: if True use black to re-format Python code
-237 :return: nothing
-238 """
-239assertisinstance(py_file,str)
-240assertisinstance(ipynb_file,str)
-241assertisinstance(use_black,bool)
-242assertpy_file!=ipynb_file# prevent clobber
-243withopen(ipynb_file,"rb")asf:
-244nb=nbformat.read(f,as_version=4)
-245py_source=convert_notebook_code_to_py(nb,use_black=use_black)
-246withopen(py_file,'w')asf:
-247f.write(py_source)
+221defconvert_notebook_file_to_py(
+222ipynb_file:str,
+223*,
+224py_file:str,
+225use_black:bool=False,
+226)->None:
+227"""
+228 Convert ipython notebook inputs to a py file.
+229 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+230 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+231 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+232
+233 :param ipynb_file: Path to notebook input file.
+234 :param py_file: Path to python result file.
+235 :param use_black: if True use black to re-format Python code
+236 :return: nothing
+237 """
+238assertisinstance(py_file,str)
+239assertisinstance(ipynb_file,str)
+240assertisinstance(use_black,bool)
+241assertpy_file!=ipynb_file# prevent clobber
+242withopen(ipynb_file,"rb")asf:
+243nb=nbformat.read(f,as_version=4)
+244py_source=convert_notebook_code_to_py(nb,use_black=use_black)
+245withopen(py_file,'w')asf:
+246f.write(py_source)
+247248
-249
-250classOurExecutor(ExecutePreprocessor):
-251"""Catch exception in notebook processing"""
-252def__init__(self,**kw):
-253"""Initialize the preprocessor."""
-254ExecutePreprocessor.__init__(self,**kw)
-255self.caught_exception=None
-256
-257defpreprocess_cell(self,cell,resources,index):
-258"""
-259 Override if you want to apply some preprocessing to each cell.
-260 Must return modified cell and resource dictionary.
-261
-262 Parameters
-263 ----------
-264 cell : NotebookNode cell
-265 Notebook cell being processed
-266 resources : dictionary
-267 Additional resources used in the conversion process. Allows
-268 preprocessors to pass variables into the Jinja engine.
-269 index : int
-270 Index of the cell being processed
-271 """
-272ifself.caught_exceptionisNone:
-273try:
-274returnExecutePreprocessor.preprocess_cell(self,cell,resources,index)
-275exceptExceptionasex:
-276self.caught_exception=ex
-277returncell,self.resources
+249classOurExecutor(ExecutePreprocessor):
+250"""Catch exception in notebook processing"""
+251def__init__(self,**kw):
+252"""Initialize the preprocessor."""
+253ExecutePreprocessor.__init__(self,**kw)
+254self.caught_exception=None
+255
+256defpreprocess_cell(self,cell,resources,index):
+257"""
+258 Override if you want to apply some preprocessing to each cell.
+259 Must return modified cell and resource dictionary.
+260
+261 Parameters
+262 ----------
+263 cell : NotebookNode cell
+264 Notebook cell being processed
+265 resources : dictionary
+266 Additional resources used in the conversion process. Allows
+267 preprocessors to pass variables into the Jinja engine.
+268 index : int
+269 Index of the cell being processed
+270 """
+271ifself.caught_exceptionisNone:
+272try:
+273returnExecutePreprocessor.preprocess_cell(self,cell,resources,index)
+274exceptExceptionasex:
+275self.caught_exception=ex
+276returncell,self.resources
+277278
-279
-280# https://nbconvert.readthedocs.io/en/latest/execute_api.html
-281# https://nbconvert.readthedocs.io/en/latest/nbconvert_library.html
-282# HTML element we are trying to delete:
-283# <div class="jp-OutputPrompt jp-OutputArea-prompt">Out[5]:</div>
-284defrender_as_html(
-285notebook_file_name:str,
-286*,
-287output_suffix:Optional[str]=None,
-288timeout:int=60000,
-289kernel_name:Optional[str]=None,
-290verbose:bool=True,
-291sheet_vars=None,
-292init_code:Optional[str]=None,
-293exclude_input:bool=False,
-294prompt_strip_regexp:Optional[str]=r'<\s*div\s+class\s*=\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\s*/div\s*>',
-295)->None:
-296"""
-297 Render a Jupyter notebook in the current directory as HTML.
-298 Exceptions raised in the rendering notebook are allowed to pass trough.
-299
-300 :param notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
-301 :param output_suffix: optional name to add to result name
-302 :param timeout: Maximum time in seconds each notebook cell is allowed to run.
-303 passed to nbconvert.preprocessors.ExecutePreprocessor.
-304 :param kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor.
-305 :param verbose logical, if True print while running
-306 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
-307 :param init_code: Python init code for first cell
-308 :param exclude_input: if True, exclude input cells
-309 :param prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
-310 :return: None
-311 """
-312assertisinstance(notebook_file_name,str)
-313# deal with no suffix case
-314if(notnotebook_file_name.endswith(".ipynb"))and(notnotebook_file_name.endswith(".py")):
-315py_name=notebook_file_name+".py"
-316py_exists=os.path.exists(py_name)
-317ipynb_name=notebook_file_name+".ipynb"
-318ipynb_exists=os.path.exists(ipynb_name)
-319if(py_exists+ipynb_exists)!=1:
-320raiseValueError('{ipynb_exists}: if file suffix is not specified then exactly one of .py or .ipynb file must exist')
-321ifipynb_exists:
-322notebook_file_name=notebook_file_name+'.ipynb'
-323else:
-324notebook_file_name=notebook_file_name+'.py'
-325# get the input
-326assertos.path.exists(notebook_file_name)
-327ifnotebook_file_name.endswith(".ipynb"):
-328suffix=".ipynb"
-329withopen(notebook_file_name,"rb")asf:
-330nb=nbformat.read(f,as_version=4)
-331elifnotebook_file_name.endswith(".py"):
-332suffix=".py"
-333withopen(notebook_file_name,'r')asf:
-334text=f.read()
-335nb=convert_py_code_to_notebook(text)
-336else:
-337raiseValueError('{ipynb_exists}: file must end with .py or .ipynb')
-338tmp_path=None
-339# do the conversion
-340ifsheet_varsisnotNone:
-341withtempfile.NamedTemporaryFile(delete=False)asntf:
-342tmp_path=ntf.name
-343pickle.dump(sheet_vars,file=ntf)
-344if(init_codeisNone)or(len(init_code)<=0):
-345init_code=""
-346pickle_code=f"""
-347import pickle
-348with open({tmp_path.__repr__()}, 'rb') as pf:
-349 sheet_vars = pickle.load(pf)
-350"""
-351init_code=init_code+"\n\n"+pickle_code
-352if(init_codeisnotNone)and(len(init_code)>0):
-353assertisinstance(init_code,str)
-354nb=prepend_code_cell_to_notebook(
-355nb,
-356code_text=f'\n\n{init_code}\n\n')
-357html_name=os.path.basename(notebook_file_name)
-358html_name=html_name.removesuffix(suffix)
-359exec_note=""
-360ifoutput_suffixisnotNone:
-361assertisinstance(output_suffix,str)
-362html_name=html_name+output_suffix
-363exec_note=f'"{output_suffix}"'
-364html_name=html_name+".html"
-365try:
-366os.remove(html_name)
-367exceptFileNotFoundError:
-368pass
-369caught=None
-370trace=None
-371html_body=""
-372ifverbose:
-373print(
-374f'start render_as_html "{notebook_file_name}" {exec_note}{datetime.datetime.now()}'
-375)
-376try:
-377ifkernel_nameisnotNone:
-378ep=OurExecutor(
-379timeout=timeout,kernel_name=kernel_name
-380)
-381else:
-382ep=OurExecutor(timeout=timeout)
-383nb_res,nb_resources=ep.preprocess(nb)
-384html_exporter=HTMLExporter(exclude_input=exclude_input)
-385html_body,html_resources=html_exporter.from_notebook_node(nb_res)
-386caught=ep.caught_exception
-387exceptExceptionase:
-388caught=e
-389trace=traceback.format_exc()
-390ifexclude_inputand(prompt_strip_regexpisnotNone):
-391# strip output prompts
-392html_body=re.sub(
-393prompt_strip_regexp,
-394' ',
-395html_body)
-396withopen(html_name,"wt",encoding="utf-8")asf:
-397f.write(html_body)
-398ifcaughtisnotNone:
-399f.write("\n<pre>\n")
-400f.write(escape_ansi(str(caught)))
-401f.write("\n</pre>\n")
-402iftraceisnotNone:
-403f.write("\n<pre>\n")
-404f.write(escape_ansi(str(trace)))
-405f.write("\n</pre>\n")
-406nw=datetime.datetime.now()
-407iftmp_pathisnotNone:
-408try:
-409os.remove(tmp_path)
-410exceptFileNotFoundError:
-411pass
-412ifcaughtisnotNone:
-413ifverbose:
-414print(f'\n\n\texception in render_as_html "{notebook_file_name}" {nw}{escape_ansi(str(caught))}\n\n')
-415iftraceisnotNone:
-416print(f'\n\n\t\ttrace {escape_ansi(str(trace))}\n\n')
-417raisecaught
-418ifverbose:
-419print(f'\tdone render_as_html "{notebook_file_name}" {nw}')
+279# https://nbconvert.readthedocs.io/en/latest/execute_api.html
+280# https://nbconvert.readthedocs.io/en/latest/nbconvert_library.html
+281# HTML element we are trying to delete:
+282# <div class="jp-OutputPrompt jp-OutputArea-prompt">Out[5]:</div>
+283defrender_as_html(
+284notebook_file_name:str,
+285*,
+286output_suffix:Optional[str]=None,
+287timeout:int=60000,
+288kernel_name:Optional[str]=None,
+289verbose:bool=True,
+290sheet_vars=None,
+291init_code:Optional[str]=None,
+292exclude_input:bool=False,
+293prompt_strip_regexp:Optional[str]=r'<\s*div\s+class\s*=\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\s*/div\s*>',
+294)->None:
+295"""
+296 Render a Jupyter notebook in the current directory as HTML.
+297 Exceptions raised in the rendering notebook are allowed to pass trough.
+298
+299 :param notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
+300 :param output_suffix: optional name to add to result name
+301 :param timeout: Maximum time in seconds each notebook cell is allowed to run.
+302 passed to nbconvert.preprocessors.ExecutePreprocessor.
+303 :param kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor.
+304 :param verbose logical, if True print while running
+305 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
+306 :param init_code: Python init code for first cell
+307 :param exclude_input: if True, exclude input cells
+308 :param prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
+309 :return: None
+310 """
+311assertisinstance(notebook_file_name,str)
+312# deal with no suffix case
+313if(notnotebook_file_name.endswith(".ipynb"))and(notnotebook_file_name.endswith(".py")):
+314py_name=notebook_file_name+".py"
+315py_exists=os.path.exists(py_name)
+316ipynb_name=notebook_file_name+".ipynb"
+317ipynb_exists=os.path.exists(ipynb_name)
+318if(py_exists+ipynb_exists)!=1:
+319raiseValueError('{ipynb_exists}: if file suffix is not specified then exactly one of .py or .ipynb file must exist')
+320ifipynb_exists:
+321notebook_file_name=notebook_file_name+'.ipynb'
+322else:
+323notebook_file_name=notebook_file_name+'.py'
+324# get the input
+325assertos.path.exists(notebook_file_name)
+326ifnotebook_file_name.endswith(".ipynb"):
+327suffix=".ipynb"
+328withopen(notebook_file_name,"rb")asf:
+329nb=nbformat.read(f,as_version=4)
+330elifnotebook_file_name.endswith(".py"):
+331suffix=".py"
+332withopen(notebook_file_name,'r')asf:
+333text=f.read()
+334nb=convert_py_code_to_notebook(text)
+335else:
+336raiseValueError('{ipynb_exists}: file must end with .py or .ipynb')
+337tmp_path=None
+338# do the conversion
+339ifsheet_varsisnotNone:
+340withtempfile.NamedTemporaryFile(delete=False)asntf:
+341tmp_path=ntf.name
+342pickle.dump(sheet_vars,file=ntf)
+343if(init_codeisNone)or(len(init_code)<=0):
+344init_code=""
+345pickle_code=f"""
+346import pickle
+347with open({tmp_path.__repr__()}, 'rb') as pf:
+348 sheet_vars = pickle.load(pf)
+349"""
+350init_code=init_code+"\n\n"+pickle_code
+351if(init_codeisnotNone)and(len(init_code)>0):
+352assertisinstance(init_code,str)
+353nb=prepend_code_cell_to_notebook(
+354nb,
+355code_text=f'\n\n{init_code}\n\n')
+356html_name=os.path.basename(notebook_file_name)
+357html_name=html_name.removesuffix(suffix)
+358exec_note=""
+359ifoutput_suffixisnotNone:
+360assertisinstance(output_suffix,str)
+361html_name=html_name+output_suffix
+362exec_note=f'"{output_suffix}"'
+363html_name=html_name+".html"
+364try:
+365os.remove(html_name)
+366exceptFileNotFoundError:
+367pass
+368caught=None
+369trace=None
+370html_body=""
+371ifverbose:
+372print(
+373f'start render_as_html "{notebook_file_name}" {exec_note}{datetime.datetime.now()}'
+374)
+375try:
+376ifkernel_nameisnotNone:
+377ep=OurExecutor(
+378timeout=timeout,kernel_name=kernel_name
+379)
+380else:
+381ep=OurExecutor(timeout=timeout)
+382nb_res,nb_resources=ep.preprocess(nb)
+383html_exporter=HTMLExporter(exclude_input=exclude_input)
+384html_body,html_resources=html_exporter.from_notebook_node(nb_res)
+385caught=ep.caught_exception
+386exceptExceptionase:
+387caught=e
+388trace=traceback.format_exc()
+389ifexclude_inputand(prompt_strip_regexpisnotNone):
+390# strip output prompts
+391html_body=re.sub(
+392prompt_strip_regexp,
+393' ',
+394html_body)
+395withopen(html_name,"wt",encoding="utf-8")asf:
+396f.write(html_body)
+397ifcaughtisnotNone:
+398f.write("\n<pre>\n")
+399f.write(escape_ansi(str(caught)))
+400f.write("\n</pre>\n")
+401iftraceisnotNone:
+402f.write("\n<pre>\n")
+403f.write(escape_ansi(str(trace)))
+404f.write("\n</pre>\n")
+405nw=datetime.datetime.now()
+406iftmp_pathisnotNone:
+407try:
+408os.remove(tmp_path)
+409exceptFileNotFoundError:
+410pass
+411ifcaughtisnotNone:
+412ifverbose:
+413print(f'\n\n\texception in render_as_html "{notebook_file_name}" {nw}{escape_ansi(str(caught))}\n\n')
+414iftraceisnotNone:
+415print(f'\n\n\t\ttrace {escape_ansi(str(trace))}\n\n')
+416raisecaught
+417ifverbose:
+418print(f'\tdone render_as_html "{notebook_file_name}" {nw}')
+419420
-421
-422_jtask_comparison_attributes=[
-423"sheet_name","output_suffix","exclude_input","init_code","path_prefix"]
+421_jtask_comparison_attributes=[
+422"sheet_name","output_suffix","exclude_input","init_code","path_prefix"]
+423424
-425
-426@total_ordering
-427classJTask:
-428def__init__(
-429self,
-430sheet_name:str,
-431*,
-432output_suffix:Optional[str]=None,
-433exclude_input:bool=True,
-434sheet_vars=None,
-435init_code:Optional[str]=None,
-436path_prefix:Optional[str]=None,
-437strict:bool=True,
-438)->None:
-439"""
-440 Create a Jupyter task.
-441
-442 :param sheet_name: name of sheet to run can be .ipynb or .py, and suffix can be omitted.
-443 :param output_suffix: optional string to append to rendered HTML file name.
-444 :param exclude_input: if True strip input cells out of HTML render.
-445 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
-446 :param init_code: optional code to insert at the top of the Jupyter sheet, used to pass parameters.
-447 :param path_prefix: optional prefix to add to sheet_name to find Jupyter source.
-448 :param strict: if True check paths path_prefix and path_prefix/sheetname[.py|.ipynb] exist.
-449 """
-450assertisinstance(sheet_name,str)
-451assertisinstance(output_suffix,(str,type(None)))
-452assertisinstance(exclude_input,bool)
-453assertisinstance(init_code,(str,type(None)))
-454assertisinstance(path_prefix,(str,type(None)))
-455ifstrict:
-456path=sheet_name
-457if(isinstance(path_prefix,str))and(len(path_prefix)>9):
-458assertos.path.exists(path_prefix)
-459path=os.path.join(path_prefix,sheet_name)
-460ifpath.endswith(".py"):
-461path=path.removesuffix(".py")
-462ifpath.endswith(".ipynb"):
-463path=path.removesuffix(".ipynb")
-464py_exists=os.path.exists(path+".py")
-465ipynb_exists=os.path.exists(path+".ipynb")
-466assert(py_exists+ipynb_exists)>=1
-467if(notsheet_name.endswith(".py"))and(notsheet_name.endswith(".ipynb")):
-468# no suffix, so must be unambiguous
-469assert(py_exists+ipynb_exists)==1
-470self.sheet_name=sheet_name
-471self.output_suffix=output_suffix
-472self.exclude_input=exclude_input
-473self.sheet_vars=sheet_vars
-474self.init_code=init_code
-475self.path_prefix=path_prefix
-476
-477defrender_as_html(self)->None:
-478"""
-479 Render Jupyter notebook or Python (treated as notebook) to HTML.
-480 """
-481path=self.sheet_name
-482ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
-483path=os.path.join(self.path_prefix,self.sheet_name)
-484render_as_html(
-485path,
-486exclude_input=self.exclude_input,
-487output_suffix=self.output_suffix,
-488sheet_vars=self.sheet_vars,
-489init_code=self.init_code,
-490)
-491
-492defrender_py_txt(self)->None:
-493"""
-494 Render Python to text (without nbconver, nbformat Jupyter bindings)
-495 """
-496path=self.sheet_name
-497ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
-498path=os.path.join(self.path_prefix,self.sheet_name)
-499execute_py(
-500path,
-501output_suffix=self.output_suffix,
-502sheet_vars=self.sheet_vars,
-503init_code=self.init_code,
-504)
-505
-506def__getitem__(self,item):
-507returngetattr(self,item)
-508
-509def_is_valid_operand(self,other):
-510returnisinstance(other,JTask)
-511
-512def__eq__(self,other):
-513ifnotself._is_valid_operand(other):
-514returnFalse
-515ifstr(type(self))!=str(type(other)):
-516returnFalse
-517forvin_jtask_comparison_attributes:
-518ifself[v]!=other[v]:
-519returnFalse
-520ifstr(self.sheet_vars)!=str(other.sheet_vars):
-521returnFalse
-522returnTrue
-523
-524def__lt__(self,other):
-525ifnotself._is_valid_operand(other):
-526returnNotImplemented
-527ifstr(type(self))<str(type(other)):
-528returnTrue
-529forvin_jtask_comparison_attributes:
-530v_self=self[v]
-531v_other=other[v]
-532# can't order compare None to None
-533if((v_selfisNone)or(v_otherisNone)):
-534if((v_selfisNone)!=(v_otherisNone)):
-535returnv_selfisNone
-536else:
-537ifself[v]<other[v]:
-538returnTrue
-539ifstr(self.sheet_vars)<str(other.sheet_vars):
-540returnTrue
-541returnFalse
-542
-543def__str__(self)->str:
-544args_str=",\n".join([
-545f" {v}={repr(self[v])}"
-546forvin_jtask_comparison_attributes+["sheet_vars"]
-547])
-548return'JTask(\n'+args_str+",\n)"
-549
-550def__repr__(self)->str:
-551returnself.__str__()
+425@total_ordering
+426classJTask:
+427def__init__(
+428self,
+429sheet_name:str,
+430*,
+431output_suffix:Optional[str]=None,
+432exclude_input:bool=True,
+433sheet_vars=None,
+434init_code:Optional[str]=None,
+435path_prefix:Optional[str]=None,
+436strict:bool=True,
+437)->None:
+438"""
+439 Create a Jupyter task.
+440
+441 :param sheet_name: name of sheet to run can be .ipynb or .py, and suffix can be omitted.
+442 :param output_suffix: optional string to append to rendered HTML file name.
+443 :param exclude_input: if True strip input cells out of HTML render.
+444 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
+445 :param init_code: optional code to insert at the top of the Jupyter sheet, used to pass parameters.
+446 :param path_prefix: optional prefix to add to sheet_name to find Jupyter source.
+447 :param strict: if True check paths path_prefix and path_prefix/sheetname[.py|.ipynb] exist.
+448 """
+449assertisinstance(sheet_name,str)
+450assertisinstance(output_suffix,(str,type(None)))
+451assertisinstance(exclude_input,bool)
+452assertisinstance(init_code,(str,type(None)))
+453assertisinstance(path_prefix,(str,type(None)))
+454ifstrict:
+455path=sheet_name
+456if(isinstance(path_prefix,str))and(len(path_prefix)>9):
+457assertos.path.exists(path_prefix)
+458path=os.path.join(path_prefix,sheet_name)
+459ifpath.endswith(".py"):
+460path=path.removesuffix(".py")
+461ifpath.endswith(".ipynb"):
+462path=path.removesuffix(".ipynb")
+463py_exists=os.path.exists(path+".py")
+464ipynb_exists=os.path.exists(path+".ipynb")
+465assert(py_exists+ipynb_exists)>=1
+466if(notsheet_name.endswith(".py"))and(notsheet_name.endswith(".ipynb")):
+467# no suffix, so must be unambiguous
+468assert(py_exists+ipynb_exists)==1
+469self.sheet_name=sheet_name
+470self.output_suffix=output_suffix
+471self.exclude_input=exclude_input
+472self.sheet_vars=sheet_vars
+473self.init_code=init_code
+474self.path_prefix=path_prefix
+475
+476defrender_as_html(self)->None:
+477"""
+478 Render Jupyter notebook or Python (treated as notebook) to HTML.
+479 """
+480path=self.sheet_name
+481ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
+482path=os.path.join(self.path_prefix,self.sheet_name)
+483render_as_html(
+484path,
+485exclude_input=self.exclude_input,
+486output_suffix=self.output_suffix,
+487sheet_vars=self.sheet_vars,
+488init_code=self.init_code,
+489)
+490
+491defrender_py_txt(self)->None:
+492"""
+493 Render Python to text (without nbconver, nbformat Jupyter bindings)
+494 """
+495path=self.sheet_name
+496ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
+497path=os.path.join(self.path_prefix,self.sheet_name)
+498execute_py(
+499path,
+500output_suffix=self.output_suffix,
+501sheet_vars=self.sheet_vars,
+502init_code=self.init_code,
+503)
+504
+505def__getitem__(self,item):
+506returngetattr(self,item)
+507
+508def_is_valid_operand(self,other):
+509returnisinstance(other,JTask)
+510
+511def__eq__(self,other):
+512ifnotself._is_valid_operand(other):
+513returnFalse
+514ifstr(type(self))!=str(type(other)):
+515returnFalse
+516forvin_jtask_comparison_attributes:
+517ifself[v]!=other[v]:
+518returnFalse
+519ifstr(self.sheet_vars)!=str(other.sheet_vars):
+520returnFalse
+521returnTrue
+522
+523def__lt__(self,other):
+524ifnotself._is_valid_operand(other):
+525returnNotImplemented
+526ifstr(type(self))<str(type(other)):
+527returnTrue
+528forvin_jtask_comparison_attributes:
+529v_self=self[v]
+530v_other=other[v]
+531# can't order compare None to None
+532if((v_selfisNone)or(v_otherisNone)):
+533if((v_selfisNone)!=(v_otherisNone)):
+534returnv_selfisNone
+535else:
+536ifself[v]<other[v]:
+537returnTrue
+538ifstr(self.sheet_vars)<str(other.sheet_vars):
+539returnTrue
+540returnFalse
+541
+542def__str__(self)->str:
+543args_str=",\n".join([
+544f" {v}={repr(self[v])}"
+545forvin_jtask_comparison_attributes+["sheet_vars"]
+546])
+547return'JTask(\n'+args_str+",\n)"
+548
+549def__repr__(self)->str:
+550returnself.__str__()
+551552
-553
-554defjob_fn(arg:JTask):
-555"""
-556 Function to run a JTask job for Jupyter notebook. Exceptions pass through
-557 """
-558assertisinstance(arg,JTask)
-559# render notebook
-560returnarg.render_as_html()
+553defjob_fn(arg:JTask):
+554"""
+555 Function to run a JTask job for Jupyter notebook. Exceptions pass through
+556 """
+557assertisinstance(arg,JTask)
+558# render notebook
+559returnarg.render_as_html()
+560561
-562
-563defjob_fn_eat_exception(arg:JTask):
-564"""
-565 Function to run a JTask job for Jupyter notebook, catching any exception and returning it as a value
-566 """
-567assertisinstance(arg,JTask)
-568# render notebook
-569try:
-570returnarg.render_as_html()
-571exceptExceptionase:
-572print(f"{arg} caught {e}")
-573return(arg,e)
+562defjob_fn_eat_exception(arg:JTask):
+563"""
+564 Function to run a JTask job for Jupyter notebook, catching any exception and returning it as a value
+565 """
+566assertisinstance(arg,JTask)
+567# render notebook
+568try:
+569returnarg.render_as_html()
+570exceptExceptionase:
+571print(f"{arg} caught {e}")
+572return(arg,e)
+573574
-575
-576defjob_fn_py_txt(arg:JTask):
-577"""
-578 Function to run a JTask job for Python to txt. Exceptions pass through
-579 """
-580assertisinstance(arg,JTask)
-581# render Python
-582returnarg.render_py_txt()
+575defjob_fn_py_txt(arg:JTask):
+576"""
+577 Function to run a JTask job for Python to txt. Exceptions pass through
+578 """
+579assertisinstance(arg,JTask)
+580# render Python
+581returnarg.render_py_txt()
+582583
-584
-585defjob_fn_py_txt_eat_exception(arg:JTask):
-586"""
-587 Function to run a JTask job for Python to txt, catching any exception and returning it as a value
-588 """
-589assertisinstance(arg,JTask)
-590# render Python
-591try:
-592returnarg.render_py_txt()
-593exceptExceptionase:
-594print(f"{arg} caught {e}")
-595return(arg,e)
+584defjob_fn_py_txt_eat_exception(arg:JTask):
+585"""
+586 Function to run a JTask job for Python to txt, catching any exception and returning it as a value
+587 """
+588assertisinstance(arg,JTask)
+589# render Python
+590try:
+591returnarg.render_py_txt()
+592exceptExceptionase:
+593print(f"{arg} caught {e}")
+594return(arg,e)
+595596
-597
-598defrun_pool(
-599tasks:Iterable,
-600*,
-601njobs:int=4,
-602verbose:bool=True,
-603stop_on_error:bool=True,
-604use_Jupyter:bool=True,
-605)->List:
-606"""
-607 Run a pool of tasks.
-608
-609 :param tasks: iterable of tasks
-610 :param njobs: degree of parallelism
-611 :param verbose: if True, print on failure
-612 :param stop_on_error: if True, stop pool on error
-613 :param use_Jupyter: if True, use nbconvert, nbformat Jupyter fns.
-614 """
-615tasks=list(tasks)
-616assertisinstance(njobs,int)
-617assertnjobs>0
-618assertisinstance(verbose,bool)
-619assertisinstance(stop_on_error,bool)
-620assertisinstance(use_Jupyter,bool)
-621iflen(tasks)<=0:
-622return
-623fortaskintasks:
-624assertisinstance(task,JTask)
-625res=None
-626ifstop_on_error:
-627# # complex way, allowing a stop on job failure
-628# https://stackoverflow.com/a/25791961/6901725
-629ifuse_Jupyter:
-630fn=job_fn
-631else:
-632fn=job_fn_py_txt
-633withPool(njobs)aspool:
-634try:
-635res=list(pool.imap_unordered(fn,tasks))# list is forcing iteration over tasks for side-effects
-636exceptException:
-637ifverbose:
-638sys.stdout.flush()
-639print("!!! run_pool: a worker raised an Exception, aborting...")
-640sys.stdout.flush()
-641pool.close()
-642pool.terminate()
-643raise# re-raise Exception
-644else:
-645pool.close()
-646pool.join()
-647else:
-648ifuse_Jupyter:
-649fn=job_fn_eat_exception
-650else:
-651fn=job_fn_py_txt_eat_exception
-652# simple way, but doesn't exit until all jobs succeed or fail
-653withPool(njobs)aspool:
-654res=list(pool.map(fn,tasks))
-655returnres
+597defrun_pool(
+598tasks:Iterable,
+599*,
+600njobs:int=4,
+601verbose:bool=True,
+602stop_on_error:bool=True,
+603use_Jupyter:bool=True,
+604)->List:
+605"""
+606 Run a pool of tasks.
+607
+608 :param tasks: iterable of tasks
+609 :param njobs: degree of parallelism
+610 :param verbose: if True, print on failure
+611 :param stop_on_error: if True, stop pool on error
+612 :param use_Jupyter: if True, use nbconvert, nbformat Jupyter fns.
+613 """
+614tasks=list(tasks)
+615assertisinstance(njobs,int)
+616assertnjobs>0
+617assertisinstance(verbose,bool)
+618assertisinstance(stop_on_error,bool)
+619assertisinstance(use_Jupyter,bool)
+620iflen(tasks)<=0:
+621return
+622fortaskintasks:
+623assertisinstance(task,JTask)
+624res=None
+625ifstop_on_error:
+626# # complex way, allowing a stop on job failure
+627# https://stackoverflow.com/a/25791961/6901725
+628ifuse_Jupyter:
+629fn=job_fn
+630else:
+631fn=job_fn_py_txt
+632withPool(njobs)aspool:
+633try:
+634res=list(pool.imap_unordered(fn,tasks))# list is forcing iteration over tasks for side-effects
+635exceptException:
+636ifverbose:
+637sys.stdout.flush()
+638print("!!! run_pool: a worker raised an Exception, aborting...")
+639sys.stdout.flush()
+640pool.close()
+641pool.terminate()
+642raise# re-raise Exception
+643else:
+644pool.close()
+645pool.join()
+646else:
+647ifuse_Jupyter:
+648fn=job_fn_eat_exception
+649else:
+650fn=job_fn_py_txt_eat_exception
+651# simple way, but doesn't exit until all jobs succeed or fail
+652withPool(njobs)aspool:
+653res=list(pool.map(fn,tasks))
+654returnres
+655656
-657
-658defwrite_dict_as_assignments(values:Dict[str,Any])->str:
-659"""
-660 Write a dictionary as a block of Python assignment statements.
-661
-662 :param values: dictionary
-663 :return: assignment statements
-664 """
-665return"\n"+"\n".join([
-666f"{k} = {repr(values[k])}"forkinsorted(values.keys())
-667])+"\n"
-668
-669
-670@contextmanager
-671defdeclare_task_variables(
-672env,
-673*,
-674result_map:Optional[Dict[str,Any]]=None,
-675)->None:
-676"""
-677 Copy env["sheet_vars"][k] into env[k] for all k in sheet_vars.keys() if "sheet_vars" defined in env.
-678 Only variables that are first assigned in the with block of this task manager are allowed to be assigned.
-679
-680 :param env: working environment, setting to globals() is usually the correct choice.
-681 :param result_map: empty dictionary to return results in. result_map["sheet_vars"] is the dictionary if incoming assignments, result_map["declared_vars"] is the dictionary of default names and values.
-682 :return None:
-683 """
-684sheet_vars=dict()
-685pre_known_vars=set()
-686ifenvisnotNone:
-687pre_known_vars=set(env.keys())
-688ifresult_mapisnotNone:
-689result_map["sheet_vars"]=sheet_vars
-690result_map["declared_vars"]=set()
-691if"sheet_vars"inpre_known_vars:
-692sheet_vars=env["sheet_vars"]
-693ifresult_mapisnotNone:
-694result_map["sheet_vars"]=sheet_vars
-695already_assigned_vars=set(sheet_vars.keys()).intersection(pre_known_vars)
-696iflen(already_assigned_vars)>0:
-697raiseValueError(f"declare_task_variables(): attempting to set pre-with variables: {sorted(already_assigned_vars)}")
-698try:
-699yield
-700finally:
-701post_known_vars=set(env.keys())
-702declared_vars=post_known_vars-pre_known_vars
-703ifresult_mapisnotNone:
-704result_map["declared_vars"]={k:env[k]forkindeclared_vars}
-705if"sheet_vars"inpre_known_vars:
-706unexpected_vars=set(sheet_vars.keys())-declared_vars
-707iflen(unexpected_vars)>0:
-708raiseValueError(f"declare_task_variables(): attempting to assign undeclared variables: {sorted(unexpected_vars)}")
-709# do the assignments
-710fork,vinsheet_vars.items():
-711env[k]=v
+657@contextmanager
+658defdeclare_task_variables(
+659env,
+660*,
+661result_map:Optional[Dict[str,Any]]=None,
+662)->None:
+663"""
+664 Copy env["sheet_vars"][k] into env[k] for all k in sheet_vars.keys() if "sheet_vars" defined in env.
+665 Only variables that are first assigned in the with block of this task manager are allowed to be assigned.
+666
+667 :param env: working environment, setting to globals() is usually the correct choice.
+668 :param result_map: empty dictionary to return results in. result_map["sheet_vars"] is the dictionary if incoming assignments, result_map["declared_vars"] is the dictionary of default names and values.
+669 :return None:
+670 """
+671sheet_vars=dict()
+672pre_known_vars=set()
+673ifenvisnotNone:
+674pre_known_vars=set(env.keys())
+675ifresult_mapisnotNone:
+676result_map["sheet_vars"]=sheet_vars
+677result_map["declared_vars"]=set()
+678if"sheet_vars"inpre_known_vars:
+679sheet_vars=env["sheet_vars"]
+680ifresult_mapisnotNone:
+681result_map["sheet_vars"]=sheet_vars
+682already_assigned_vars=set(sheet_vars.keys()).intersection(pre_known_vars)
+683iflen(already_assigned_vars)>0:
+684raiseValueError(f"declare_task_variables(): attempting to set pre-with variables: {sorted(already_assigned_vars)}")
+685try:
+686yield
+687finally:
+688post_known_vars=set(env.keys())
+689declared_vars=post_known_vars-pre_known_vars
+690ifresult_mapisnotNone:
+691result_map["declared_vars"]={k:env[k]forkindeclared_vars}
+692if"sheet_vars"inpre_known_vars:
+693unexpected_vars=set(sheet_vars.keys())-declared_vars
+694iflen(unexpected_vars)>0:
+695raiseValueError(f"declare_task_variables(): attempting to assign undeclared variables: {sorted(unexpected_vars)}")
+696# do the assignments
+697fork,vinsheet_vars.items():
+698env[k]=v
@@ -848,26 +832,26 @@
-
41defpretty_format_python(python_txt:str,*,black_mode=None)->str:
-42"""
-43 Format Python code, using black.
-44
-45 :param python_txt: Python code
-46 :param black_mode: options for black
-47 :return: formatted Python code
-48 """
-49asserthave_black
-50assertisinstance(python_txt,str)
-51formatted_python=python_txt.strip('\n')+'\n'
-52iflen(formatted_python.strip())>0:
-53ifblack_modeisNone:
-54black_mode=black.FileMode()
-55try:
-56formatted_python=black.format_str(formatted_python,mode=black_mode)
-57formatted_python=formatted_python.strip('\n')+'\n'
-58exceptException:
-59pass
-60returnformatted_python
+
40defpretty_format_python(python_txt:str,*,black_mode=None)->str:
+41"""
+42 Format Python code, using black.
+43
+44 :param python_txt: Python code
+45 :param black_mode: options for black
+46 :return: formatted Python code
+47 """
+48asserthave_black
+49assertisinstance(python_txt,str)
+50formatted_python=python_txt.strip('\n')+'\n'
+51iflen(formatted_python.strip())>0:
+52ifblack_modeisNone:
+53black_mode=black.FileMode()
+54try:
+55formatted_python=black.format_str(formatted_python,mode=black_mode)
+56formatted_python=formatted_python.strip('\n')+'\n'
+57exceptException:
+58pass
+59returnformatted_python
@@ -900,73 +884,73 @@
Returns
-
63defconvert_py_code_to_notebook(
- 64text:str,
- 65*,
- 66use_black:bool=False)->nbformat.notebooknode.NotebookNode:
- 67"""
- 68 Convert python text to a notebook.
- 69 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
- 70 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
- 71 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
- 72
- 73 :param text: Python text to convert.
- 74 :param use_black: if True use black to re-format Python code
- 75 :return: a notebook
- 76 """
- 77# https://stackoverflow.com/a/23729611/6901725
- 78# https://nbviewer.org/gist/fperez/9716279
- 79assertisinstance(text,str)
- 80assertisinstance(use_black,bool)
- 81lines=text.splitlines()
- 82begin_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*begin\s+text\s*$")
- 83end_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*#\s*end\s+text\s*$")
- 84end_code_regexp=re.compile(r"(^\s*r?'''\s*end\s+code\s*'''\s*$)|(^\s*r?\"\"\"\s*end\s+code\s*\"\"\"\s*$)")
- 85# run a little code collecting state machine
- 86cells=[]
- 87collecting_python=[]
- 88collecting_text=None
- 89lines.append(None)# append an ending sentinel
- 90# scan input
- 91forlineinlines:
- 92iflineisNone:
- 93is_end=True
- 94text_start=False
- 95code_start=False
- 96code_end=False
- 97else:
- 98is_end=False
- 99text_start=begin_text_regexp.match(line)
-100code_start=end_text_regexp.match(line)
-101code_end=end_code_regexp.match(line)
-102ifis_endortext_startorcode_startorcode_end:
-103if(collecting_pythonisnotNone)and(len(collecting_python)>0):
-104python_block=('\n'.join(collecting_python)).strip('\n')+'\n'
-105iflen(python_block.strip())>0:
-106ifuse_blackandhave_black:
-107python_block=pretty_format_python(python_block)
-108cells.append(nbf_v.new_code_cell(python_block))
-109if(collecting_textisnotNone)and(len(collecting_text)>0):
-110txt_block=('\n'.join(collecting_text)).strip('\n')+'\n'
-111iflen(txt_block.strip())>0:
-112cells.append(nbf_v.new_markdown_cell(txt_block))
-113collecting_python=None
-114collecting_text=None
-115ifnotis_end:
-116iftext_start:
-117collecting_text=[]
-118else:
-119collecting_python=[]
-120else:
-121ifcollecting_pythonisnotNone:
-122collecting_python.append(line)
-123ifcollecting_textisnotNone:
-124collecting_text.append(line)
-125foriinrange(len(cells)):
-126cells[i]["id"]=f"cell{i}"
-127nb=nbf_v.new_notebook(cells=cells)
-128# nb = _normalize(nb)
-129returnnb
+
62defconvert_py_code_to_notebook(
+ 63text:str,
+ 64*,
+ 65use_black:bool=False)->nbformat.notebooknode.NotebookNode:
+ 66"""
+ 67 Convert python text to a notebook.
+ 68 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+ 69 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+ 70 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+ 71
+ 72 :param text: Python text to convert.
+ 73 :param use_black: if True use black to re-format Python code
+ 74 :return: a notebook
+ 75 """
+ 76# https://stackoverflow.com/a/23729611/6901725
+ 77# https://nbviewer.org/gist/fperez/9716279
+ 78assertisinstance(text,str)
+ 79assertisinstance(use_black,bool)
+ 80lines=text.splitlines()
+ 81begin_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*begin\s+text\s*$")
+ 82end_text_regexp=re.compile(r"^\s*r?((''')|(\"\"\"))\s*#\s*end\s+text\s*$")
+ 83end_code_regexp=re.compile(r"(^\s*r?'''\s*end\s+code\s*'''\s*$)|(^\s*r?\"\"\"\s*end\s+code\s*\"\"\"\s*$)")
+ 84# run a little code collecting state machine
+ 85cells=[]
+ 86collecting_python=[]
+ 87collecting_text=None
+ 88lines.append(None)# append an ending sentinel
+ 89# scan input
+ 90forlineinlines:
+ 91iflineisNone:
+ 92is_end=True
+ 93text_start=False
+ 94code_start=False
+ 95code_end=False
+ 96else:
+ 97is_end=False
+ 98text_start=begin_text_regexp.match(line)
+ 99code_start=end_text_regexp.match(line)
+100code_end=end_code_regexp.match(line)
+101ifis_endortext_startorcode_startorcode_end:
+102if(collecting_pythonisnotNone)and(len(collecting_python)>0):
+103python_block=('\n'.join(collecting_python)).strip('\n')+'\n'
+104iflen(python_block.strip())>0:
+105ifuse_blackandhave_black:
+106python_block=pretty_format_python(python_block)
+107cells.append(nbf_v.new_code_cell(python_block))
+108if(collecting_textisnotNone)and(len(collecting_text)>0):
+109txt_block=('\n'.join(collecting_text)).strip('\n')+'\n'
+110iflen(txt_block.strip())>0:
+111cells.append(nbf_v.new_markdown_cell(txt_block))
+112collecting_python=None
+113collecting_text=None
+114ifnotis_end:
+115iftext_start:
+116collecting_text=[]
+117else:
+118collecting_python=[]
+119else:
+120ifcollecting_pythonisnotNone:
+121collecting_python.append(line)
+122ifcollecting_textisnotNone:
+123collecting_text.append(line)
+124foriinrange(len(cells)):
+125cells[i]["id"]=f"cell{i}"
+126nb=nbf_v.new_notebook(cells=cells)
+127# nb = _normalize(nb)
+128returnnb
@@ -1002,31 +986,31 @@
Returns
-
132defprepend_code_cell_to_notebook(
-133nb:nbformat.notebooknode.NotebookNode,
-134*,
-135code_text:str,
-136)->nbformat.notebooknode.NotebookNode:
-137"""
-138 Prepend a code cell to a Jupyter notebook.
-139
-140 :param nb: Jupyter notebook to alter
-141 :param code_text: Python source code to add
-142 :return: new notebook
-143 """
-144header_cell=nbf_v.new_code_cell(code_text)
-145# set cell ids to avoid:
-146# "MissingIDFieldWarning: Code cell is missing an id field, this will become a hard error in future nbformat versions."
-147header_cell["id"]="wvpy_header_cell"
-148orig_cells=[c.copy()forcinnb.cells]
-149foriinrange(len(orig_cells)):
-150orig_cells[i]["id"]=f"cell{i}"
-151cells=[header_cell]+orig_cells
-152nb_out=nbf_v.new_notebook(
-153cells=cells
-154)
-155# nb_out = _normalize(nb_out)
-156returnnb_out
+
131defprepend_code_cell_to_notebook(
+132nb:nbformat.notebooknode.NotebookNode,
+133*,
+134code_text:str,
+135)->nbformat.notebooknode.NotebookNode:
+136"""
+137 Prepend a code cell to a Jupyter notebook.
+138
+139 :param nb: Jupyter notebook to alter
+140 :param code_text: Python source code to add
+141 :return: new notebook
+142 """
+143header_cell=nbf_v.new_code_cell(code_text)
+144# set cell ids to avoid:
+145# "MissingIDFieldWarning: Code cell is missing an id field, this will become a hard error in future nbformat versions."
+146header_cell["id"]="wvpy_header_cell"
+147orig_cells=[c.copy()forcinnb.cells]
+148foriinrange(len(orig_cells)):
+149orig_cells[i]["id"]=f"cell{i}"
+150cells=[header_cell]+orig_cells
+151nb_out=nbf_v.new_notebook(
+152cells=cells
+153)
+154# nb_out = _normalize(nb_out)
+155returnnb_out
@@ -1059,32 +1043,32 @@
Returns
-
159defconvert_py_file_to_notebook(
-160py_file:str,
-161*,
-162ipynb_file:str,
-163use_black:bool=False,
-164)->None:
-165"""
-166 Convert python text to a notebook.
-167 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
-168 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
-169 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
-170
-171 :param py_file: Path to python source file.
-172 :param ipynb_file: Path to notebook result file.
-173 :param use_black: if True use black to re-format Python code
-174 :return: nothing
-175 """
-176assertisinstance(py_file,str)
-177assertisinstance(ipynb_file,str)
-178assertisinstance(use_black,bool)
-179assertpy_file!=ipynb_file# prevent clobber
-180withopen(py_file,'r')asf:
-181text=f.read()
-182nb=convert_py_code_to_notebook(text,use_black=use_black)
-183withopen(ipynb_file,'w')asf:
-184nbformat.write(nb,f)
+
158defconvert_py_file_to_notebook(
+159py_file:str,
+160*,
+161ipynb_file:str,
+162use_black:bool=False,
+163)->None:
+164"""
+165 Convert python text to a notebook.
+166 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+167 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+168 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+169
+170 :param py_file: Path to python source file.
+171 :param ipynb_file: Path to notebook result file.
+172 :param use_black: if True use black to re-format Python code
+173 :return: nothing
+174 """
+175assertisinstance(py_file,str)
+176assertisinstance(ipynb_file,str)
+177assertisinstance(use_black,bool)
+178assertpy_file!=ipynb_file# prevent clobber
+179withopen(py_file,'r')asf:
+180text=f.read()
+181nb=convert_py_code_to_notebook(text,use_black=use_black)
+182withopen(ipynb_file,'w')asf:
+183nbformat.write(nb,f)
@@ -1121,41 +1105,41 @@
Returns
-
187defconvert_notebook_code_to_py(
-188nb:nbformat.notebooknode.NotebookNode,
-189*,
-190use_black:bool=False,
-191)->str:
-192"""
-193 Convert ipython notebook inputs to a py code.
-194 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
-195 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
-196 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
-197
-198 :param nb: notebook
-199 :param use_black: if True use black to re-format Python code
-200 :return: Python source code
-201 """
-202assertisinstance(use_black,bool)
-203res=[]
-204code_needs_end=False
-205forcellinnb.cells:
-206iflen(cell.source.strip())>0:
-207ifcell.cell_type=='code':
-208ifcode_needs_end:
-209res.append('\n"""end code"""\n')
-210py_text=cell.source.strip('\n')+'\n'
-211ifuse_blackandhave_black:
-212py_text=pretty_format_python(py_text)
-213res.append(py_text)
-214code_needs_end=True
-215else:
-216res.append('\n""" begin text')
-217res.append(cell.source.strip('\n'))
-218res.append('""" # end text\n')
-219code_needs_end=False
-220res_text='\n'+('\n'.join(res)).strip('\n')+'\n\n'
-221returnres_text
+
186defconvert_notebook_code_to_py(
+187nb:nbformat.notebooknode.NotebookNode,
+188*,
+189use_black:bool=False,
+190)->str:
+191"""
+192 Convert ipython notebook inputs to a py code.
+193 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+194 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+195 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+196
+197 :param nb: notebook
+198 :param use_black: if True use black to re-format Python code
+199 :return: Python source code
+200 """
+201assertisinstance(use_black,bool)
+202res=[]
+203code_needs_end=False
+204forcellinnb.cells:
+205iflen(cell.source.strip())>0:
+206ifcell.cell_type=='code':
+207ifcode_needs_end:
+208res.append('\n"""end code"""\n')
+209py_text=cell.source.strip('\n')+'\n'
+210ifuse_blackandhave_black:
+211py_text=pretty_format_python(py_text)
+212res.append(py_text)
+213code_needs_end=True
+214else:
+215res.append('\n""" begin text')
+216res.append(cell.source.strip('\n'))
+217res.append('""" # end text\n')
+218code_needs_end=False
+219res_text='\n'+('\n'.join(res)).strip('\n')+'\n\n'
+220returnres_text
@@ -1191,32 +1175,32 @@
Returns
-
224defconvert_notebook_file_to_py(
-225ipynb_file:str,
-226*,
-227py_file:str,
-228use_black:bool=False,
-229)->None:
-230"""
-231 Convert ipython notebook inputs to a py file.
-232 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
-233 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
-234 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
-235
-236 :param ipynb_file: Path to notebook input file.
-237 :param py_file: Path to python result file.
-238 :param use_black: if True use black to re-format Python code
-239 :return: nothing
-240 """
-241assertisinstance(py_file,str)
-242assertisinstance(ipynb_file,str)
-243assertisinstance(use_black,bool)
-244assertpy_file!=ipynb_file# prevent clobber
-245withopen(ipynb_file,"rb")asf:
-246nb=nbformat.read(f,as_version=4)
-247py_source=convert_notebook_code_to_py(nb,use_black=use_black)
-248withopen(py_file,'w')asf:
-249f.write(py_source)
+
223defconvert_notebook_file_to_py(
+224ipynb_file:str,
+225*,
+226py_file:str,
+227use_black:bool=False,
+228)->None:
+229"""
+230 Convert ipython notebook inputs to a py file.
+231 "''' begin text" ends any open blocks, and starts a new markdown block (triple double quotes also allowed)
+232 "''' # end text" ends text, and starts a new code block (triple double quotes also allowed)
+233 "'''end code'''" ends code blocks, and starts a new code block (triple double quotes also allowed)
+234
+235 :param ipynb_file: Path to notebook input file.
+236 :param py_file: Path to python result file.
+237 :param use_black: if True use black to re-format Python code
+238 :return: nothing
+239 """
+240assertisinstance(py_file,str)
+241assertisinstance(ipynb_file,str)
+242assertisinstance(use_black,bool)
+243assertpy_file!=ipynb_file# prevent clobber
+244withopen(ipynb_file,"rb")asf:
+245nb=nbformat.read(f,as_version=4)
+246py_source=convert_notebook_code_to_py(nb,use_black=use_black)
+247withopen(py_file,'w')asf:
+248f.write(py_source)
@@ -1253,34 +1237,34 @@
Returns
-
252classOurExecutor(ExecutePreprocessor):
-253"""Catch exception in notebook processing"""
-254def__init__(self,**kw):
-255"""Initialize the preprocessor."""
-256ExecutePreprocessor.__init__(self,**kw)
-257self.caught_exception=None
-258
-259defpreprocess_cell(self,cell,resources,index):
-260"""
-261 Override if you want to apply some preprocessing to each cell.
-262 Must return modified cell and resource dictionary.
-263
-264 Parameters
-265 ----------
-266 cell : NotebookNode cell
-267 Notebook cell being processed
-268 resources : dictionary
-269 Additional resources used in the conversion process. Allows
-270 preprocessors to pass variables into the Jinja engine.
-271 index : int
-272 Index of the cell being processed
-273 """
-274ifself.caught_exceptionisNone:
-275try:
-276returnExecutePreprocessor.preprocess_cell(self,cell,resources,index)
-277exceptExceptionasex:
-278self.caught_exception=ex
-279returncell,self.resources
+
251classOurExecutor(ExecutePreprocessor):
+252"""Catch exception in notebook processing"""
+253def__init__(self,**kw):
+254"""Initialize the preprocessor."""
+255ExecutePreprocessor.__init__(self,**kw)
+256self.caught_exception=None
+257
+258defpreprocess_cell(self,cell,resources,index):
+259"""
+260 Override if you want to apply some preprocessing to each cell.
+261 Must return modified cell and resource dictionary.
+262
+263 Parameters
+264 ----------
+265 cell : NotebookNode cell
+266 Notebook cell being processed
+267 resources : dictionary
+268 Additional resources used in the conversion process. Allows
+269 preprocessors to pass variables into the Jinja engine.
+270 index : int
+271 Index of the cell being processed
+272 """
+273ifself.caught_exceptionisNone:
+274try:
+275returnExecutePreprocessor.preprocess_cell(self,cell,resources,index)
+276exceptExceptionasex:
+277self.caught_exception=ex
+278returncell,self.resources
@@ -1298,10 +1282,10 @@
Returns
-
254def__init__(self,**kw):
-255"""Initialize the preprocessor."""
-256ExecutePreprocessor.__init__(self,**kw)
-257self.caught_exception=None
+
253def__init__(self,**kw):
+254"""Initialize the preprocessor."""
+255ExecutePreprocessor.__init__(self,**kw)
+256self.caught_exception=None
@@ -1321,27 +1305,27 @@
Returns
-
259defpreprocess_cell(self,cell,resources,index):
-260"""
-261 Override if you want to apply some preprocessing to each cell.
-262 Must return modified cell and resource dictionary.
-263
-264 Parameters
-265 ----------
-266 cell : NotebookNode cell
-267 Notebook cell being processed
-268 resources : dictionary
-269 Additional resources used in the conversion process. Allows
-270 preprocessors to pass variables into the Jinja engine.
-271 index : int
-272 Index of the cell being processed
-273 """
-274ifself.caught_exceptionisNone:
-275try:
-276returnExecutePreprocessor.preprocess_cell(self,cell,resources,index)
-277exceptExceptionasex:
-278self.caught_exception=ex
-279returncell,self.resources
+
258defpreprocess_cell(self,cell,resources,index):
+259"""
+260 Override if you want to apply some preprocessing to each cell.
+261 Must return modified cell and resource dictionary.
+262
+263 Parameters
+264 ----------
+265 cell : NotebookNode cell
+266 Notebook cell being processed
+267 resources : dictionary
+268 Additional resources used in the conversion process. Allows
+269 preprocessors to pass variables into the Jinja engine.
+270 index : int
+271 Index of the cell being processed
+272 """
+273ifself.caught_exceptionisNone:
+274try:
+275returnExecutePreprocessor.preprocess_cell(self,cell,resources,index)
+276exceptExceptionasex:
+277self.caught_exception=ex
+278returncell,self.resources
@@ -1484,142 +1468,142 @@
Inherited Members
-
286defrender_as_html(
-287notebook_file_name:str,
-288*,
-289output_suffix:Optional[str]=None,
-290timeout:int=60000,
-291kernel_name:Optional[str]=None,
-292verbose:bool=True,
-293sheet_vars=None,
-294init_code:Optional[str]=None,
-295exclude_input:bool=False,
-296prompt_strip_regexp:Optional[str]=r'<\s*div\s+class\s*=\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\s*/div\s*>',
-297)->None:
-298"""
-299 Render a Jupyter notebook in the current directory as HTML.
-300 Exceptions raised in the rendering notebook are allowed to pass trough.
-301
-302 :param notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
-303 :param output_suffix: optional name to add to result name
-304 :param timeout: Maximum time in seconds each notebook cell is allowed to run.
-305 passed to nbconvert.preprocessors.ExecutePreprocessor.
-306 :param kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor.
-307 :param verbose logical, if True print while running
-308 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
-309 :param init_code: Python init code for first cell
-310 :param exclude_input: if True, exclude input cells
-311 :param prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
-312 :return: None
-313 """
-314assertisinstance(notebook_file_name,str)
-315# deal with no suffix case
-316if(notnotebook_file_name.endswith(".ipynb"))and(notnotebook_file_name.endswith(".py")):
-317py_name=notebook_file_name+".py"
-318py_exists=os.path.exists(py_name)
-319ipynb_name=notebook_file_name+".ipynb"
-320ipynb_exists=os.path.exists(ipynb_name)
-321if(py_exists+ipynb_exists)!=1:
-322raiseValueError('{ipynb_exists}: if file suffix is not specified then exactly one of .py or .ipynb file must exist')
-323ifipynb_exists:
-324notebook_file_name=notebook_file_name+'.ipynb'
-325else:
-326notebook_file_name=notebook_file_name+'.py'
-327# get the input
-328assertos.path.exists(notebook_file_name)
-329ifnotebook_file_name.endswith(".ipynb"):
-330suffix=".ipynb"
-331withopen(notebook_file_name,"rb")asf:
-332nb=nbformat.read(f,as_version=4)
-333elifnotebook_file_name.endswith(".py"):
-334suffix=".py"
-335withopen(notebook_file_name,'r')asf:
-336text=f.read()
-337nb=convert_py_code_to_notebook(text)
-338else:
-339raiseValueError('{ipynb_exists}: file must end with .py or .ipynb')
-340tmp_path=None
-341# do the conversion
-342ifsheet_varsisnotNone:
-343withtempfile.NamedTemporaryFile(delete=False)asntf:
-344tmp_path=ntf.name
-345pickle.dump(sheet_vars,file=ntf)
-346if(init_codeisNone)or(len(init_code)<=0):
-347init_code=""
-348pickle_code=f"""
-349import pickle
-350with open({tmp_path.__repr__()}, 'rb') as pf:
-351 sheet_vars = pickle.load(pf)
-352"""
-353init_code=init_code+"\n\n"+pickle_code
-354if(init_codeisnotNone)and(len(init_code)>0):
-355assertisinstance(init_code,str)
-356nb=prepend_code_cell_to_notebook(
-357nb,
-358code_text=f'\n\n{init_code}\n\n')
-359html_name=os.path.basename(notebook_file_name)
-360html_name=html_name.removesuffix(suffix)
-361exec_note=""
-362ifoutput_suffixisnotNone:
-363assertisinstance(output_suffix,str)
-364html_name=html_name+output_suffix
-365exec_note=f'"{output_suffix}"'
-366html_name=html_name+".html"
-367try:
-368os.remove(html_name)
-369exceptFileNotFoundError:
-370pass
-371caught=None
-372trace=None
-373html_body=""
-374ifverbose:
-375print(
-376f'start render_as_html "{notebook_file_name}" {exec_note}{datetime.datetime.now()}'
-377)
-378try:
-379ifkernel_nameisnotNone:
-380ep=OurExecutor(
-381timeout=timeout,kernel_name=kernel_name
-382)
-383else:
-384ep=OurExecutor(timeout=timeout)
-385nb_res,nb_resources=ep.preprocess(nb)
-386html_exporter=HTMLExporter(exclude_input=exclude_input)
-387html_body,html_resources=html_exporter.from_notebook_node(nb_res)
-388caught=ep.caught_exception
-389exceptExceptionase:
-390caught=e
-391trace=traceback.format_exc()
-392ifexclude_inputand(prompt_strip_regexpisnotNone):
-393# strip output prompts
-394html_body=re.sub(
-395prompt_strip_regexp,
-396' ',
-397html_body)
-398withopen(html_name,"wt",encoding="utf-8")asf:
-399f.write(html_body)
-400ifcaughtisnotNone:
-401f.write("\n<pre>\n")
-402f.write(escape_ansi(str(caught)))
-403f.write("\n</pre>\n")
-404iftraceisnotNone:
-405f.write("\n<pre>\n")
-406f.write(escape_ansi(str(trace)))
-407f.write("\n</pre>\n")
-408nw=datetime.datetime.now()
-409iftmp_pathisnotNone:
-410try:
-411os.remove(tmp_path)
-412exceptFileNotFoundError:
-413pass
-414ifcaughtisnotNone:
-415ifverbose:
-416print(f'\n\n\texception in render_as_html "{notebook_file_name}" {nw}{escape_ansi(str(caught))}\n\n')
-417iftraceisnotNone:
-418print(f'\n\n\t\ttrace {escape_ansi(str(trace))}\n\n')
-419raisecaught
-420ifverbose:
-421print(f'\tdone render_as_html "{notebook_file_name}" {nw}')
+
285defrender_as_html(
+286notebook_file_name:str,
+287*,
+288output_suffix:Optional[str]=None,
+289timeout:int=60000,
+290kernel_name:Optional[str]=None,
+291verbose:bool=True,
+292sheet_vars=None,
+293init_code:Optional[str]=None,
+294exclude_input:bool=False,
+295prompt_strip_regexp:Optional[str]=r'<\s*div\s+class\s*=\s*"jp-OutputPrompt[^<>]*>[^<>]*Out[^<>]*<\s*/div\s*>',
+296)->None:
+297"""
+298 Render a Jupyter notebook in the current directory as HTML.
+299 Exceptions raised in the rendering notebook are allowed to pass trough.
+300
+301 :param notebook_file_name: name of source file, must end with .ipynb or .py (or type gotten from file system)
+302 :param output_suffix: optional name to add to result name
+303 :param timeout: Maximum time in seconds each notebook cell is allowed to run.
+304 passed to nbconvert.preprocessors.ExecutePreprocessor.
+305 :param kernel_name: Jupyter kernel to use. passed to nbconvert.preprocessors.ExecutePreprocessor.
+306 :param verbose logical, if True print while running
+307 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
+308 :param init_code: Python init code for first cell
+309 :param exclude_input: if True, exclude input cells
+310 :param prompt_strip_regexp: regexp to strip prompts, only used if exclude_input is True
+311 :return: None
+312 """
+313assertisinstance(notebook_file_name,str)
+314# deal with no suffix case
+315if(notnotebook_file_name.endswith(".ipynb"))and(notnotebook_file_name.endswith(".py")):
+316py_name=notebook_file_name+".py"
+317py_exists=os.path.exists(py_name)
+318ipynb_name=notebook_file_name+".ipynb"
+319ipynb_exists=os.path.exists(ipynb_name)
+320if(py_exists+ipynb_exists)!=1:
+321raiseValueError('{ipynb_exists}: if file suffix is not specified then exactly one of .py or .ipynb file must exist')
+322ifipynb_exists:
+323notebook_file_name=notebook_file_name+'.ipynb'
+324else:
+325notebook_file_name=notebook_file_name+'.py'
+326# get the input
+327assertos.path.exists(notebook_file_name)
+328ifnotebook_file_name.endswith(".ipynb"):
+329suffix=".ipynb"
+330withopen(notebook_file_name,"rb")asf:
+331nb=nbformat.read(f,as_version=4)
+332elifnotebook_file_name.endswith(".py"):
+333suffix=".py"
+334withopen(notebook_file_name,'r')asf:
+335text=f.read()
+336nb=convert_py_code_to_notebook(text)
+337else:
+338raiseValueError('{ipynb_exists}: file must end with .py or .ipynb')
+339tmp_path=None
+340# do the conversion
+341ifsheet_varsisnotNone:
+342withtempfile.NamedTemporaryFile(delete=False)asntf:
+343tmp_path=ntf.name
+344pickle.dump(sheet_vars,file=ntf)
+345if(init_codeisNone)or(len(init_code)<=0):
+346init_code=""
+347pickle_code=f"""
+348import pickle
+349with open({tmp_path.__repr__()}, 'rb') as pf:
+350 sheet_vars = pickle.load(pf)
+351"""
+352init_code=init_code+"\n\n"+pickle_code
+353if(init_codeisnotNone)and(len(init_code)>0):
+354assertisinstance(init_code,str)
+355nb=prepend_code_cell_to_notebook(
+356nb,
+357code_text=f'\n\n{init_code}\n\n')
+358html_name=os.path.basename(notebook_file_name)
+359html_name=html_name.removesuffix(suffix)
+360exec_note=""
+361ifoutput_suffixisnotNone:
+362assertisinstance(output_suffix,str)
+363html_name=html_name+output_suffix
+364exec_note=f'"{output_suffix}"'
+365html_name=html_name+".html"
+366try:
+367os.remove(html_name)
+368exceptFileNotFoundError:
+369pass
+370caught=None
+371trace=None
+372html_body=""
+373ifverbose:
+374print(
+375f'start render_as_html "{notebook_file_name}" {exec_note}{datetime.datetime.now()}'
+376)
+377try:
+378ifkernel_nameisnotNone:
+379ep=OurExecutor(
+380timeout=timeout,kernel_name=kernel_name
+381)
+382else:
+383ep=OurExecutor(timeout=timeout)
+384nb_res,nb_resources=ep.preprocess(nb)
+385html_exporter=HTMLExporter(exclude_input=exclude_input)
+386html_body,html_resources=html_exporter.from_notebook_node(nb_res)
+387caught=ep.caught_exception
+388exceptExceptionase:
+389caught=e
+390trace=traceback.format_exc()
+391ifexclude_inputand(prompt_strip_regexpisnotNone):
+392# strip output prompts
+393html_body=re.sub(
+394prompt_strip_regexp,
+395' ',
+396html_body)
+397withopen(html_name,"wt",encoding="utf-8")asf:
+398f.write(html_body)
+399ifcaughtisnotNone:
+400f.write("\n<pre>\n")
+401f.write(escape_ansi(str(caught)))
+402f.write("\n</pre>\n")
+403iftraceisnotNone:
+404f.write("\n<pre>\n")
+405f.write(escape_ansi(str(trace)))
+406f.write("\n</pre>\n")
+407nw=datetime.datetime.now()
+408iftmp_pathisnotNone:
+409try:
+410os.remove(tmp_path)
+411exceptFileNotFoundError:
+412pass
+413ifcaughtisnotNone:
+414ifverbose:
+415print(f'\n\n\texception in render_as_html "{notebook_file_name}" {nw}{escape_ansi(str(caught))}\n\n')
+416iftraceisnotNone:
+417print(f'\n\n\t\ttrace {escape_ansi(str(trace))}\n\n')
+418raisecaught
+419ifverbose:
+420print(f'\tdone render_as_html "{notebook_file_name}" {nw}')
@@ -1662,132 +1646,132 @@
Returns
-
428@total_ordering
-429classJTask:
-430def__init__(
-431self,
-432sheet_name:str,
-433*,
-434output_suffix:Optional[str]=None,
-435exclude_input:bool=True,
-436sheet_vars=None,
-437init_code:Optional[str]=None,
-438path_prefix:Optional[str]=None,
-439strict:bool=True,
-440)->None:
-441"""
-442 Create a Jupyter task.
-443
-444 :param sheet_name: name of sheet to run can be .ipynb or .py, and suffix can be omitted.
-445 :param output_suffix: optional string to append to rendered HTML file name.
-446 :param exclude_input: if True strip input cells out of HTML render.
-447 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
-448 :param init_code: optional code to insert at the top of the Jupyter sheet, used to pass parameters.
-449 :param path_prefix: optional prefix to add to sheet_name to find Jupyter source.
-450 :param strict: if True check paths path_prefix and path_prefix/sheetname[.py|.ipynb] exist.
-451 """
-452assertisinstance(sheet_name,str)
-453assertisinstance(output_suffix,(str,type(None)))
-454assertisinstance(exclude_input,bool)
-455assertisinstance(init_code,(str,type(None)))
-456assertisinstance(path_prefix,(str,type(None)))
-457ifstrict:
-458path=sheet_name
-459if(isinstance(path_prefix,str))and(len(path_prefix)>9):
-460assertos.path.exists(path_prefix)
-461path=os.path.join(path_prefix,sheet_name)
-462ifpath.endswith(".py"):
-463path=path.removesuffix(".py")
-464ifpath.endswith(".ipynb"):
-465path=path.removesuffix(".ipynb")
-466py_exists=os.path.exists(path+".py")
-467ipynb_exists=os.path.exists(path+".ipynb")
-468assert(py_exists+ipynb_exists)>=1
-469if(notsheet_name.endswith(".py"))and(notsheet_name.endswith(".ipynb")):
-470# no suffix, so must be unambiguous
-471assert(py_exists+ipynb_exists)==1
-472self.sheet_name=sheet_name
-473self.output_suffix=output_suffix
-474self.exclude_input=exclude_input
-475self.sheet_vars=sheet_vars
-476self.init_code=init_code
-477self.path_prefix=path_prefix
-478
-479defrender_as_html(self)->None:
-480"""
-481 Render Jupyter notebook or Python (treated as notebook) to HTML.
-482 """
-483path=self.sheet_name
-484ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
-485path=os.path.join(self.path_prefix,self.sheet_name)
-486render_as_html(
-487path,
-488exclude_input=self.exclude_input,
-489output_suffix=self.output_suffix,
-490sheet_vars=self.sheet_vars,
-491init_code=self.init_code,
-492)
-493
-494defrender_py_txt(self)->None:
-495"""
-496 Render Python to text (without nbconver, nbformat Jupyter bindings)
-497 """
-498path=self.sheet_name
-499ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
-500path=os.path.join(self.path_prefix,self.sheet_name)
-501execute_py(
-502path,
-503output_suffix=self.output_suffix,
-504sheet_vars=self.sheet_vars,
-505init_code=self.init_code,
-506)
-507
-508def__getitem__(self,item):
-509returngetattr(self,item)
-510
-511def_is_valid_operand(self,other):
-512returnisinstance(other,JTask)
-513
-514def__eq__(self,other):
-515ifnotself._is_valid_operand(other):
-516returnFalse
-517ifstr(type(self))!=str(type(other)):
-518returnFalse
-519forvin_jtask_comparison_attributes:
-520ifself[v]!=other[v]:
-521returnFalse
-522ifstr(self.sheet_vars)!=str(other.sheet_vars):
-523returnFalse
-524returnTrue
-525
-526def__lt__(self,other):
-527ifnotself._is_valid_operand(other):
-528returnNotImplemented
-529ifstr(type(self))<str(type(other)):
-530returnTrue
-531forvin_jtask_comparison_attributes:
-532v_self=self[v]
-533v_other=other[v]
-534# can't order compare None to None
-535if((v_selfisNone)or(v_otherisNone)):
-536if((v_selfisNone)!=(v_otherisNone)):
-537returnv_selfisNone
-538else:
-539ifself[v]<other[v]:
-540returnTrue
-541ifstr(self.sheet_vars)<str(other.sheet_vars):
-542returnTrue
-543returnFalse
-544
-545def__str__(self)->str:
-546args_str=",\n".join([
-547f" {v}={repr(self[v])}"
-548forvin_jtask_comparison_attributes+["sheet_vars"]
-549])
-550return'JTask(\n'+args_str+",\n)"
-551
-552def__repr__(self)->str:
-553returnself.__str__()
+
427@total_ordering
+428classJTask:
+429def__init__(
+430self,
+431sheet_name:str,
+432*,
+433output_suffix:Optional[str]=None,
+434exclude_input:bool=True,
+435sheet_vars=None,
+436init_code:Optional[str]=None,
+437path_prefix:Optional[str]=None,
+438strict:bool=True,
+439)->None:
+440"""
+441 Create a Jupyter task.
+442
+443 :param sheet_name: name of sheet to run can be .ipynb or .py, and suffix can be omitted.
+444 :param output_suffix: optional string to append to rendered HTML file name.
+445 :param exclude_input: if True strip input cells out of HTML render.
+446 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
+447 :param init_code: optional code to insert at the top of the Jupyter sheet, used to pass parameters.
+448 :param path_prefix: optional prefix to add to sheet_name to find Jupyter source.
+449 :param strict: if True check paths path_prefix and path_prefix/sheetname[.py|.ipynb] exist.
+450 """
+451assertisinstance(sheet_name,str)
+452assertisinstance(output_suffix,(str,type(None)))
+453assertisinstance(exclude_input,bool)
+454assertisinstance(init_code,(str,type(None)))
+455assertisinstance(path_prefix,(str,type(None)))
+456ifstrict:
+457path=sheet_name
+458if(isinstance(path_prefix,str))and(len(path_prefix)>9):
+459assertos.path.exists(path_prefix)
+460path=os.path.join(path_prefix,sheet_name)
+461ifpath.endswith(".py"):
+462path=path.removesuffix(".py")
+463ifpath.endswith(".ipynb"):
+464path=path.removesuffix(".ipynb")
+465py_exists=os.path.exists(path+".py")
+466ipynb_exists=os.path.exists(path+".ipynb")
+467assert(py_exists+ipynb_exists)>=1
+468if(notsheet_name.endswith(".py"))and(notsheet_name.endswith(".ipynb")):
+469# no suffix, so must be unambiguous
+470assert(py_exists+ipynb_exists)==1
+471self.sheet_name=sheet_name
+472self.output_suffix=output_suffix
+473self.exclude_input=exclude_input
+474self.sheet_vars=sheet_vars
+475self.init_code=init_code
+476self.path_prefix=path_prefix
+477
+478defrender_as_html(self)->None:
+479"""
+480 Render Jupyter notebook or Python (treated as notebook) to HTML.
+481 """
+482path=self.sheet_name
+483ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
+484path=os.path.join(self.path_prefix,self.sheet_name)
+485render_as_html(
+486path,
+487exclude_input=self.exclude_input,
+488output_suffix=self.output_suffix,
+489sheet_vars=self.sheet_vars,
+490init_code=self.init_code,
+491)
+492
+493defrender_py_txt(self)->None:
+494"""
+495 Render Python to text (without nbconver, nbformat Jupyter bindings)
+496 """
+497path=self.sheet_name
+498ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
+499path=os.path.join(self.path_prefix,self.sheet_name)
+500execute_py(
+501path,
+502output_suffix=self.output_suffix,
+503sheet_vars=self.sheet_vars,
+504init_code=self.init_code,
+505)
+506
+507def__getitem__(self,item):
+508returngetattr(self,item)
+509
+510def_is_valid_operand(self,other):
+511returnisinstance(other,JTask)
+512
+513def__eq__(self,other):
+514ifnotself._is_valid_operand(other):
+515returnFalse
+516ifstr(type(self))!=str(type(other)):
+517returnFalse
+518forvin_jtask_comparison_attributes:
+519ifself[v]!=other[v]:
+520returnFalse
+521ifstr(self.sheet_vars)!=str(other.sheet_vars):
+522returnFalse
+523returnTrue
+524
+525def__lt__(self,other):
+526ifnotself._is_valid_operand(other):
+527returnNotImplemented
+528ifstr(type(self))<str(type(other)):
+529returnTrue
+530forvin_jtask_comparison_attributes:
+531v_self=self[v]
+532v_other=other[v]
+533# can't order compare None to None
+534if((v_selfisNone)or(v_otherisNone)):
+535if((v_selfisNone)!=(v_otherisNone)):
+536returnv_selfisNone
+537else:
+538ifself[v]<other[v]:
+539returnTrue
+540ifstr(self.sheet_vars)<str(other.sheet_vars):
+541returnTrue
+542returnFalse
+543
+544def__str__(self)->str:
+545args_str=",\n".join([
+546f" {v}={repr(self[v])}"
+547forvin_jtask_comparison_attributes+["sheet_vars"]
+548])
+549return'JTask(\n'+args_str+",\n)"
+550
+551def__repr__(self)->str:
+552returnself.__str__()
@@ -1803,54 +1787,54 @@
Returns
-
430def__init__(
-431self,
-432sheet_name:str,
-433*,
-434output_suffix:Optional[str]=None,
-435exclude_input:bool=True,
-436sheet_vars=None,
-437init_code:Optional[str]=None,
-438path_prefix:Optional[str]=None,
-439strict:bool=True,
-440)->None:
-441"""
-442 Create a Jupyter task.
-443
-444 :param sheet_name: name of sheet to run can be .ipynb or .py, and suffix can be omitted.
-445 :param output_suffix: optional string to append to rendered HTML file name.
-446 :param exclude_input: if True strip input cells out of HTML render.
-447 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
-448 :param init_code: optional code to insert at the top of the Jupyter sheet, used to pass parameters.
-449 :param path_prefix: optional prefix to add to sheet_name to find Jupyter source.
-450 :param strict: if True check paths path_prefix and path_prefix/sheetname[.py|.ipynb] exist.
-451 """
-452assertisinstance(sheet_name,str)
-453assertisinstance(output_suffix,(str,type(None)))
-454assertisinstance(exclude_input,bool)
-455assertisinstance(init_code,(str,type(None)))
-456assertisinstance(path_prefix,(str,type(None)))
-457ifstrict:
-458path=sheet_name
-459if(isinstance(path_prefix,str))and(len(path_prefix)>9):
-460assertos.path.exists(path_prefix)
-461path=os.path.join(path_prefix,sheet_name)
-462ifpath.endswith(".py"):
-463path=path.removesuffix(".py")
-464ifpath.endswith(".ipynb"):
-465path=path.removesuffix(".ipynb")
-466py_exists=os.path.exists(path+".py")
-467ipynb_exists=os.path.exists(path+".ipynb")
-468assert(py_exists+ipynb_exists)>=1
-469if(notsheet_name.endswith(".py"))and(notsheet_name.endswith(".ipynb")):
-470# no suffix, so must be unambiguous
-471assert(py_exists+ipynb_exists)==1
-472self.sheet_name=sheet_name
-473self.output_suffix=output_suffix
-474self.exclude_input=exclude_input
-475self.sheet_vars=sheet_vars
-476self.init_code=init_code
-477self.path_prefix=path_prefix
+
429def__init__(
+430self,
+431sheet_name:str,
+432*,
+433output_suffix:Optional[str]=None,
+434exclude_input:bool=True,
+435sheet_vars=None,
+436init_code:Optional[str]=None,
+437path_prefix:Optional[str]=None,
+438strict:bool=True,
+439)->None:
+440"""
+441 Create a Jupyter task.
+442
+443 :param sheet_name: name of sheet to run can be .ipynb or .py, and suffix can be omitted.
+444 :param output_suffix: optional string to append to rendered HTML file name.
+445 :param exclude_input: if True strip input cells out of HTML render.
+446 :param sheet_vars: if not None value is de-serialized as a variable named "sheet_vars"
+447 :param init_code: optional code to insert at the top of the Jupyter sheet, used to pass parameters.
+448 :param path_prefix: optional prefix to add to sheet_name to find Jupyter source.
+449 :param strict: if True check paths path_prefix and path_prefix/sheetname[.py|.ipynb] exist.
+450 """
+451assertisinstance(sheet_name,str)
+452assertisinstance(output_suffix,(str,type(None)))
+453assertisinstance(exclude_input,bool)
+454assertisinstance(init_code,(str,type(None)))
+455assertisinstance(path_prefix,(str,type(None)))
+456ifstrict:
+457path=sheet_name
+458if(isinstance(path_prefix,str))and(len(path_prefix)>9):
+459assertos.path.exists(path_prefix)
+460path=os.path.join(path_prefix,sheet_name)
+461ifpath.endswith(".py"):
+462path=path.removesuffix(".py")
+463ifpath.endswith(".ipynb"):
+464path=path.removesuffix(".ipynb")
+465py_exists=os.path.exists(path+".py")
+466ipynb_exists=os.path.exists(path+".ipynb")
+467assert(py_exists+ipynb_exists)>=1
+468if(notsheet_name.endswith(".py"))and(notsheet_name.endswith(".ipynb")):
+469# no suffix, so must be unambiguous
+470assert(py_exists+ipynb_exists)==1
+471self.sheet_name=sheet_name
+472self.output_suffix=output_suffix
+473self.exclude_input=exclude_input
+474self.sheet_vars=sheet_vars
+475self.init_code=init_code
+476self.path_prefix=path_prefix
@@ -1882,20 +1866,20 @@
Parameters
-
479defrender_as_html(self)->None:
-480"""
-481 Render Jupyter notebook or Python (treated as notebook) to HTML.
-482 """
-483path=self.sheet_name
-484ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
-485path=os.path.join(self.path_prefix,self.sheet_name)
-486render_as_html(
-487path,
-488exclude_input=self.exclude_input,
-489output_suffix=self.output_suffix,
-490sheet_vars=self.sheet_vars,
-491init_code=self.init_code,
-492)
+
478defrender_as_html(self)->None:
+479"""
+480 Render Jupyter notebook or Python (treated as notebook) to HTML.
+481 """
+482path=self.sheet_name
+483ifisinstance(self.path_prefix,str)and(len(self.path_prefix)>0):
+484path=os.path.join(self.path_prefix,self.sheet_name)
+485render_as_html(
+486path,
+487exclude_input=self.exclude_input,
+488output_suffix=self.output_suffix,
+489sheet_vars=self.sheet_vars,
+490init_code=self.init_code,
+491)
556defjob_fn(arg:JTask):
-557"""
-558 Function to run a JTask job for Jupyter notebook. Exceptions pass through
-559 """
-560assertisinstance(arg,JTask)
-561# render notebook
-562returnarg.render_as_html()
+
555defjob_fn(arg:JTask):
+556"""
+557 Function to run a JTask job for Jupyter notebook. Exceptions pass through
+558 """
+559assertisinstance(arg,JTask)
+560# render notebook
+561returnarg.render_as_html()
@@ -1974,17 +1958,17 @@
Parameters
-
565defjob_fn_eat_exception(arg:JTask):
-566"""
-567 Function to run a JTask job for Jupyter notebook, catching any exception and returning it as a value
-568 """
-569assertisinstance(arg,JTask)
-570# render notebook
-571try:
-572returnarg.render_as_html()
-573exceptExceptionase:
-574print(f"{arg} caught {e}")
-575return(arg,e)
+
564defjob_fn_eat_exception(arg:JTask):
+565"""
+566 Function to run a JTask job for Jupyter notebook, catching any exception and returning it as a value
+567 """
+568assertisinstance(arg,JTask)
+569# render notebook
+570try:
+571returnarg.render_as_html()
+572exceptExceptionase:
+573print(f"{arg} caught {e}")
+574return(arg,e)
@@ -2004,13 +1988,13 @@
Parameters
-
578defjob_fn_py_txt(arg:JTask):
-579"""
-580 Function to run a JTask job for Python to txt. Exceptions pass through
-581 """
-582assertisinstance(arg,JTask)
-583# render Python
-584returnarg.render_py_txt()
+
577defjob_fn_py_txt(arg:JTask):
+578"""
+579 Function to run a JTask job for Python to txt. Exceptions pass through
+580 """
+581assertisinstance(arg,JTask)
+582# render Python
+583returnarg.render_py_txt()
@@ -2030,17 +2014,17 @@
Parameters
-
587defjob_fn_py_txt_eat_exception(arg:JTask):
-588"""
-589 Function to run a JTask job for Python to txt, catching any exception and returning it as a value
-590 """
-591assertisinstance(arg,JTask)
-592# render Python
-593try:
-594returnarg.render_py_txt()
-595exceptExceptionase:
-596print(f"{arg} caught {e}")
-597return(arg,e)
+
586defjob_fn_py_txt_eat_exception(arg:JTask):
+587"""
+588 Function to run a JTask job for Python to txt, catching any exception and returning it as a value
+589 """
+590assertisinstance(arg,JTask)
+591# render Python
+592try:
+593returnarg.render_py_txt()
+594exceptExceptionase:
+595print(f"{arg} caught {e}")
+596return(arg,e)
@@ -2060,64 +2044,64 @@
Parameters
-
600defrun_pool(
-601tasks:Iterable,
-602*,
-603njobs:int=4,
-604verbose:bool=True,
-605stop_on_error:bool=True,
-606use_Jupyter:bool=True,
-607)->List:
-608"""
-609 Run a pool of tasks.
-610
-611 :param tasks: iterable of tasks
-612 :param njobs: degree of parallelism
-613 :param verbose: if True, print on failure
-614 :param stop_on_error: if True, stop pool on error
-615 :param use_Jupyter: if True, use nbconvert, nbformat Jupyter fns.
-616 """
-617tasks=list(tasks)
-618assertisinstance(njobs,int)
-619assertnjobs>0
-620assertisinstance(verbose,bool)
-621assertisinstance(stop_on_error,bool)
-622assertisinstance(use_Jupyter,bool)
-623iflen(tasks)<=0:
-624return
-625fortaskintasks:
-626assertisinstance(task,JTask)
-627res=None
-628ifstop_on_error:
-629# # complex way, allowing a stop on job failure
-630# https://stackoverflow.com/a/25791961/6901725
-631ifuse_Jupyter:
-632fn=job_fn
-633else:
-634fn=job_fn_py_txt
-635withPool(njobs)aspool:
-636try:
-637res=list(pool.imap_unordered(fn,tasks))# list is forcing iteration over tasks for side-effects
-638exceptException:
-639ifverbose:
-640sys.stdout.flush()
-641print("!!! run_pool: a worker raised an Exception, aborting...")
-642sys.stdout.flush()
-643pool.close()
-644pool.terminate()
-645raise# re-raise Exception
-646else:
-647pool.close()
-648pool.join()
-649else:
-650ifuse_Jupyter:
-651fn=job_fn_eat_exception
-652else:
-653fn=job_fn_py_txt_eat_exception
-654# simple way, but doesn't exit until all jobs succeed or fail
-655withPool(njobs)aspool:
-656res=list(pool.map(fn,tasks))
-657returnres
+
599defrun_pool(
+600tasks:Iterable,
+601*,
+602njobs:int=4,
+603verbose:bool=True,
+604stop_on_error:bool=True,
+605use_Jupyter:bool=True,
+606)->List:
+607"""
+608 Run a pool of tasks.
+609
+610 :param tasks: iterable of tasks
+611 :param njobs: degree of parallelism
+612 :param verbose: if True, print on failure
+613 :param stop_on_error: if True, stop pool on error
+614 :param use_Jupyter: if True, use nbconvert, nbformat Jupyter fns.
+615 """
+616tasks=list(tasks)
+617assertisinstance(njobs,int)
+618assertnjobs>0
+619assertisinstance(verbose,bool)
+620assertisinstance(stop_on_error,bool)
+621assertisinstance(use_Jupyter,bool)
+622iflen(tasks)<=0:
+623return
+624fortaskintasks:
+625assertisinstance(task,JTask)
+626res=None
+627ifstop_on_error:
+628# # complex way, allowing a stop on job failure
+629# https://stackoverflow.com/a/25791961/6901725
+630ifuse_Jupyter:
+631fn=job_fn
+632else:
+633fn=job_fn_py_txt
+634withPool(njobs)aspool:
+635try:
+636res=list(pool.imap_unordered(fn,tasks))# list is forcing iteration over tasks for side-effects
+637exceptException:
+638ifverbose:
+639sys.stdout.flush()
+640print("!!! run_pool: a worker raised an Exception, aborting...")
+641sys.stdout.flush()
+642pool.close()
+643pool.terminate()
+644raise# re-raise Exception
+645else:
+646pool.close()
+647pool.join()
+648else:
+649ifuse_Jupyter:
+650fn=job_fn_eat_exception
+651else:
+652fn=job_fn_py_txt_eat_exception
+653# simple way, but doesn't exit until all jobs succeed or fail
+654withPool(njobs)aspool:
+655res=list(pool.map(fn,tasks))
+656returnres
660defwrite_dict_as_assignments(values:Dict[str,Any])->str:
-661"""
-662 Write a dictionary as a block of Python assignment statements.
-663
-664 :param values: dictionary
-665 :return: assignment statements
-666 """
-667return"\n"+"\n".join([
-668f"{k} = {repr(values[k])}"forkinsorted(values.keys())
-669])+"\n"
-
-
-
-
Write a dictionary as a block of Python assignment statements.
-
-
Parameters
-
-
-
values: dictionary
-
-
-
Returns
-
-
-
assignment statements
-
-
-
-
@@ -2189,48 +2132,48 @@
Returns
-
672@contextmanager
-673defdeclare_task_variables(
-674env,
-675*,
-676result_map:Optional[Dict[str,Any]]=None,
-677)->None:
-678"""
-679 Copy env["sheet_vars"][k] into env[k] for all k in sheet_vars.keys() if "sheet_vars" defined in env.
-680 Only variables that are first assigned in the with block of this task manager are allowed to be assigned.
-681
-682 :param env: working environment, setting to globals() is usually the correct choice.
-683 :param result_map: empty dictionary to return results in. result_map["sheet_vars"] is the dictionary if incoming assignments, result_map["declared_vars"] is the dictionary of default names and values.
-684 :return None:
-685 """
-686sheet_vars=dict()
-687pre_known_vars=set()
-688ifenvisnotNone:
-689pre_known_vars=set(env.keys())
-690ifresult_mapisnotNone:
-691result_map["sheet_vars"]=sheet_vars
-692result_map["declared_vars"]=set()
-693if"sheet_vars"inpre_known_vars:
-694sheet_vars=env["sheet_vars"]
-695ifresult_mapisnotNone:
-696result_map["sheet_vars"]=sheet_vars
-697already_assigned_vars=set(sheet_vars.keys()).intersection(pre_known_vars)
-698iflen(already_assigned_vars)>0:
-699raiseValueError(f"declare_task_variables(): attempting to set pre-with variables: {sorted(already_assigned_vars)}")
-700try:
-701yield
-702finally:
-703post_known_vars=set(env.keys())
-704declared_vars=post_known_vars-pre_known_vars
-705ifresult_mapisnotNone:
-706result_map["declared_vars"]={k:env[k]forkindeclared_vars}
-707if"sheet_vars"inpre_known_vars:
-708unexpected_vars=set(sheet_vars.keys())-declared_vars
-709iflen(unexpected_vars)>0:
-710raiseValueError(f"declare_task_variables(): attempting to assign undeclared variables: {sorted(unexpected_vars)}")
-711# do the assignments
-712fork,vinsheet_vars.items():
-713env[k]=v
+
659@contextmanager
+660defdeclare_task_variables(
+661env,
+662*,
+663result_map:Optional[Dict[str,Any]]=None,
+664)->None:
+665"""
+666 Copy env["sheet_vars"][k] into env[k] for all k in sheet_vars.keys() if "sheet_vars" defined in env.
+667 Only variables that are first assigned in the with block of this task manager are allowed to be assigned.
+668
+669 :param env: working environment, setting to globals() is usually the correct choice.
+670 :param result_map: empty dictionary to return results in. result_map["sheet_vars"] is the dictionary if incoming assignments, result_map["declared_vars"] is the dictionary of default names and values.
+671 :return None:
+672 """
+673sheet_vars=dict()
+674pre_known_vars=set()
+675ifenvisnotNone:
+676pre_known_vars=set(env.keys())
+677ifresult_mapisnotNone:
+678result_map["sheet_vars"]=sheet_vars
+679result_map["declared_vars"]=set()
+680if"sheet_vars"inpre_known_vars:
+681sheet_vars=env["sheet_vars"]
+682ifresult_mapisnotNone:
+683result_map["sheet_vars"]=sheet_vars
+684already_assigned_vars=set(sheet_vars.keys()).intersection(pre_known_vars)
+685iflen(already_assigned_vars)>0:
+686raiseValueError(f"declare_task_variables(): attempting to set pre-with variables: {sorted(already_assigned_vars)}")
+687try:
+688yield
+689finally:
+690post_known_vars=set(env.keys())
+691declared_vars=post_known_vars-pre_known_vars
+692ifresult_mapisnotNone:
+693result_map["declared_vars"]={k:env[k]forkindeclared_vars}
+694if"sheet_vars"inpre_known_vars:
+695unexpected_vars=set(sheet_vars.keys())-declared_vars
+696iflen(unexpected_vars)>0:
+697raiseValueError(f"declare_task_variables(): attempting to assign undeclared variables: {sorted(unexpected_vars)}")
+698# do the assignments
+699fork,vinsheet_vars.items():
+700env[k]=v
diff --git a/pkg/wvpy.egg-info/SOURCES.txt b/pkg/wvpy.egg-info/SOURCES.txt
index 0ab8e79..9db9c9b 100644
--- a/pkg/wvpy.egg-info/SOURCES.txt
+++ b/pkg/wvpy.egg-info/SOURCES.txt
@@ -4,6 +4,7 @@ README.txt
setup.py
Doc/documentation.txt
wvpy/__init__.py
+wvpy/assignment.py
wvpy/jtools.py
wvpy/ptools.py
wvpy/pysheet.py
diff --git a/pkg/wvpy/assignment.py b/pkg/wvpy/assignment.py
new file mode 100644
index 0000000..7a27d98
--- /dev/null
+++ b/pkg/wvpy/assignment.py
@@ -0,0 +1,101 @@
+"""Classes and functions for working with variable assignments"""
+
+from typing import Any, Dict, Iterable, Optional
+from contextlib import contextmanager
+
+
+def dict_to_assignments_str(values: Dict[str, Any]) -> str:
+ """
+ Format a dictionary as a block of Python assignment statements.
+
+ Example:
+ ```python
+ from wvpy.assignment import dict_to_assignments_str
+ print(dict_to_assignments_str({"a": 1, "b": 2}))
+ ```
+
+ :param values: dictionary
+ :return: assignment statements
+ """
+ assert isinstance(values, dict)
+ return (
+ "\n"
+ + "\n".join([f"{k} = {repr(values[k])}" for k in sorted(values.keys())])
+ + "\n"
+ )
+
+
+@contextmanager
+def record_assignments(
+ env,
+ *,
+ result: Dict[str, Any],
+) -> None:
+ """
+ Context manager to record all assignments to new variables in a with-block.
+ New variables being variables not set prior to entering the with block.
+
+ Example:
+ ```python
+ from wvpy.assignment import dict_to_assignments_str, record_assignments
+ assignments = {}
+ with record_assignments(globals(), result=assignments):
+ a = 1
+ b = 2
+ print(assignments)
+ print(dict_to_assignments_str(assignments))
+ ```
+
+ :param env: working environment, setting to `globals()` is usually the correct choice.
+ :param result: dictionary to store results in. function calls `.clear()` on result.
+ :return None:
+ """
+ assert isinstance(result, dict)
+ pre_known_vars = set(env.keys())
+ result.clear()
+ try:
+ yield
+ finally:
+ post_known_vars = set(env.keys())
+ declared_vars = post_known_vars - pre_known_vars
+ for k in sorted(declared_vars):
+ result[k] = env[k]
+
+
+def ensure_names_not_already_assigned(env, *, keys: Iterable[str]) -> None:
+ """
+ Check that no key in keys is already set in the environment env.
+ Raises ValueError if keys are already assigned.
+
+ :param env: working environment, setting to `globals()` is usually the correct choice.
+ :param keys: keys to confirm not already set.
+ :return None:
+ """
+ already_assigned_vars = set(keys).intersection(set(env.keys()))
+ if len(already_assigned_vars) > 0:
+ raise ValueError(f"variables already set: {sorted(already_assigned_vars)}")
+
+
+def assign_values_from_map(
+ env, *, values: Dict[str, Any], expected_keys: Optional[Iterable[str]]
+) -> None:
+ """
+ Assign values from map into environment.
+
+ :param env: working environment, setting to `globals()` is usually the correct choice.
+ :param values: dictionary to copy into environment.
+ :param expected_keys: if not null a set of keys we are restricting to
+ :return None:
+ """
+ assert isinstance(values, dict)
+ for k in values.keys():
+ assert isinstance(k, str)
+ if expected_keys is not None:
+ unexpected_vars = set(values.keys()) - set(expected_keys)
+ if len(unexpected_vars) > 0:
+ raise ValueError(
+ f"attempting to assign undeclared variables: {sorted(unexpected_vars)}"
+ )
+ # do the assignments
+ for k, v in values.items():
+ env[k] = v
diff --git a/pkg/wvpy/jtools.py b/pkg/wvpy/jtools.py
index 7be7bcc..d355eaa 100644
--- a/pkg/wvpy/jtools.py
+++ b/pkg/wvpy/jtools.py
@@ -16,7 +16,6 @@
from typing import Any, Dict, Iterable, List, Optional
from functools import total_ordering
from contextlib import contextmanager
-from collections import namedtuple
from wvpy.util import escape_ansi
from wvpy.ptools import execute_py
@@ -656,18 +655,6 @@ def run_pool(
return res
-def write_dict_as_assignments(values: Dict[str, Any]) -> str:
- """
- Write a dictionary as a block of Python assignment statements.
-
- :param values: dictionary
- :return: assignment statements
- """
- return "\n" + "\n".join([
- f"{k} = {repr(values[k])}" for k in sorted(values.keys())
- ]) + "\n"
-
-
@contextmanager
def declare_task_variables(
env,