dp_wizard_templates 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dp_wizard_templates might be problematic. Click here for more details.
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/CHANGELOG.md +4 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/PKG-INFO +1 -1
- dp_wizard_templates-0.3.0/dp_wizard_templates/VERSION +1 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/dp_wizard_templates/code_template.py +94 -46
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/dp_wizard_templates/converters.py +4 -4
- dp_wizard_templates-0.3.0/dp_wizard_templates/py.typed +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/examples/_block_demo.py +1 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/examples/hello-world.html +4 -1
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/examples/hello-world.ipynb +4 -1
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/index.html +23 -7
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/test_code_template.py +58 -23
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/test_index_html.py +22 -4
- dp_wizard_templates-0.2.0/dp_wizard_templates/VERSION +0 -1
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.coveragerc +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.flake8 +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.github/workflows/test.yml +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.gitignore +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.nojekyll +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.pre-commit-config.yaml +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/.pytest.ini +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/LICENSE +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/README.md +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/dp_wizard_templates/__init__.py +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/pyproject.toml +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/requirements-dev.in +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/requirements-dev.txt +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/requirements.in +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/requirements.txt +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/scripts/changelog.py +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/scripts/ci.sh +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/scripts/requirements.py +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/fixtures/fake-executed.ipynb +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/fixtures/fake.ipynb +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/fixtures/fake.py +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/test_converters.py +0 -0
- {dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/tests/test_misc.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.0
|
{dp_wizard_templates-0.2.0 → dp_wizard_templates-0.3.0}/dp_wizard_templates/code_template.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional, Callable
|
|
2
|
+
from pathlib import Path
|
|
1
3
|
import inspect
|
|
2
4
|
import re
|
|
3
5
|
import black
|
|
@@ -30,7 +32,11 @@ def _get_body(func):
|
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class Template:
|
|
33
|
-
def __init__(
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
template: str | Callable,
|
|
38
|
+
root: Optional[Path] = None,
|
|
39
|
+
):
|
|
34
40
|
if root is not None:
|
|
35
41
|
template_name = f"_{template}.py"
|
|
36
42
|
template_path = root / template_name
|
|
@@ -47,7 +53,7 @@ class Template:
|
|
|
47
53
|
# can produce sequences of upper case letters that could be mistaken for slots.
|
|
48
54
|
self._initial_slots = self._find_slots()
|
|
49
55
|
|
|
50
|
-
def _find_slots(self):
|
|
56
|
+
def _find_slots(self) -> set[str]:
|
|
51
57
|
# Slots:
|
|
52
58
|
# - are all caps or underscores
|
|
53
59
|
# - have word boundary on either side
|
|
@@ -55,75 +61,117 @@ class Template:
|
|
|
55
61
|
slot_re = r"\b[A-Z][A-Z_]{2,}\b"
|
|
56
62
|
return set(re.findall(slot_re, self._template))
|
|
57
63
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for k, v in kwargs.items():
|
|
63
|
-
k_re = re.escape(k)
|
|
64
|
-
self._template, count = re.subn(rf"\b{k_re}\b", str(v), self._template)
|
|
65
|
-
if count == 0:
|
|
66
|
-
raise Exception(
|
|
67
|
-
f"No '{k}' slot to fill with '{v}' in "
|
|
68
|
-
f"{self._source}:\n\n{self._template}"
|
|
69
|
-
)
|
|
70
|
-
return self
|
|
64
|
+
def _make_message(self, errors: list[str]) -> str:
|
|
65
|
+
return (
|
|
66
|
+
f"In {self._source}, " + ", ".join(sorted(errors)) + f":\n{self._template}"
|
|
67
|
+
)
|
|
71
68
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
def _loop_kwargs(
|
|
70
|
+
self,
|
|
71
|
+
function: Callable[[str, str, list[str]], None],
|
|
72
|
+
**kwargs,
|
|
73
|
+
) -> None:
|
|
74
|
+
errors = []
|
|
76
75
|
for k, v in kwargs.items():
|
|
76
|
+
function(k, v, errors)
|
|
77
|
+
if errors:
|
|
78
|
+
raise Exception(self._make_message(errors))
|
|
79
|
+
|
|
80
|
+
def _fill_inline_slots(
|
|
81
|
+
self,
|
|
82
|
+
stringifier: Callable[[str], str],
|
|
83
|
+
**kwargs,
|
|
84
|
+
) -> None:
|
|
85
|
+
def function(k, v, errors):
|
|
77
86
|
k_re = re.escape(k)
|
|
78
|
-
self._template, count = re.subn(
|
|
87
|
+
self._template, count = re.subn(
|
|
88
|
+
rf"\b{k_re}\b", stringifier(v), self._template
|
|
89
|
+
)
|
|
79
90
|
if count == 0:
|
|
80
|
-
|
|
81
|
-
f"No '{k}' slot to fill with '{v}' in "
|
|
82
|
-
f"{self._source}:\n\n{self._template}"
|
|
83
|
-
)
|
|
84
|
-
return self
|
|
91
|
+
errors.append(f"no '{k}' slot to fill with '{v}'")
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
self._loop_kwargs(function, **kwargs)
|
|
94
|
+
|
|
95
|
+
def _fill_block_slots(
|
|
96
|
+
self,
|
|
97
|
+
prefix_re: str,
|
|
98
|
+
splitter: Callable[[str], list[str]],
|
|
99
|
+
**kwargs,
|
|
100
|
+
) -> None:
|
|
101
|
+
def function(k, v, errors):
|
|
91
102
|
if not isinstance(v, str):
|
|
92
|
-
|
|
103
|
+
errors.append(f"for '{k}' slot, expected string, not '{v}'")
|
|
104
|
+
return
|
|
93
105
|
|
|
94
106
|
def match_indent(match):
|
|
95
107
|
# This does what we want, but binding is confusing.
|
|
96
108
|
return "\n".join(
|
|
97
|
-
match.group(1) + line for line in v
|
|
109
|
+
match.group(1) + line for line in splitter(v) # noqa: B023
|
|
98
110
|
)
|
|
99
111
|
|
|
100
112
|
k_re = re.escape(k)
|
|
101
113
|
self._template, count = re.subn(
|
|
102
|
-
rf"^([ \t]*){k_re}$",
|
|
114
|
+
rf"^([ \t]*{prefix_re}){k_re}$",
|
|
103
115
|
match_indent,
|
|
104
116
|
self._template,
|
|
105
117
|
flags=re.MULTILINE,
|
|
106
118
|
)
|
|
107
119
|
if count == 0:
|
|
108
|
-
base_message =
|
|
109
|
-
f"No '{k}' slot to fill with '{v}' in "
|
|
110
|
-
f"{self._source}:\n\n{self._template}"
|
|
111
|
-
)
|
|
120
|
+
base_message = f"no '{k}' slot to fill with '{v}'"
|
|
112
121
|
if k in self._template:
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
note = (
|
|
123
|
+
"comment slots must be prefixed with '#'"
|
|
124
|
+
if prefix_re
|
|
125
|
+
else "block slots must be alone on line"
|
|
115
126
|
)
|
|
116
|
-
|
|
127
|
+
errors.append(f"{base_message} ({note})")
|
|
128
|
+
else:
|
|
129
|
+
errors.append(base_message)
|
|
130
|
+
|
|
131
|
+
self._loop_kwargs(function, **kwargs)
|
|
132
|
+
|
|
133
|
+
def fill_expressions(self, **kwargs) -> "Template":
|
|
134
|
+
"""
|
|
135
|
+
Fill in variable names, or dicts or lists represented as strings.
|
|
136
|
+
"""
|
|
137
|
+
self._fill_inline_slots(stringifier=str, **kwargs)
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def fill_values(self, **kwargs) -> "Template":
|
|
141
|
+
"""
|
|
142
|
+
Fill in string or numeric values. `repr` is called before filling.
|
|
143
|
+
"""
|
|
144
|
+
self._fill_inline_slots(stringifier=repr, **kwargs)
|
|
117
145
|
return self
|
|
118
146
|
|
|
119
|
-
def
|
|
147
|
+
def fill_code_blocks(self, **kwargs) -> "Template":
|
|
148
|
+
"""
|
|
149
|
+
Fill in code blocks. Slot must be alone on line.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def splitter(s):
|
|
153
|
+
return s.split("\n")
|
|
154
|
+
|
|
155
|
+
self._fill_block_slots(prefix_re=r"", splitter=splitter, **kwargs)
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def fill_comment_blocks(self, **kwargs) -> "Template":
|
|
159
|
+
"""
|
|
160
|
+
Fill in comment blocks. Slot must be commented.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def splitter(s):
|
|
164
|
+
stripped = [line.strip() for line in s.split("\n")]
|
|
165
|
+
return [line for line in stripped if line]
|
|
166
|
+
|
|
167
|
+
self._fill_block_slots(prefix_re=r"#\s+", splitter=splitter, **kwargs)
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def finish(self, reformat: bool = False) -> str:
|
|
120
171
|
unfilled_slots = self._initial_slots & self._find_slots()
|
|
121
172
|
if unfilled_slots:
|
|
122
|
-
|
|
123
|
-
raise Exception(
|
|
124
|
-
f"{slots_str} slot not filled "
|
|
125
|
-
f"in {self._source}:\n\n{self._template}"
|
|
126
|
-
)
|
|
173
|
+
errors = [f"'{slot}' slot not filled" for slot in unfilled_slots]
|
|
174
|
+
raise Exception(self._make_message(errors))
|
|
127
175
|
|
|
128
176
|
if reformat:
|
|
129
177
|
self._template = black.format_str(self._template, mode=black.Mode())
|
|
@@ -30,7 +30,7 @@ class ConversionException(Exception):
|
|
|
30
30
|
|
|
31
31
|
def convert_py_to_nb(
|
|
32
32
|
python_str: str, title: str, execute: bool = False, reformat: bool = True
|
|
33
|
-
):
|
|
33
|
+
) -> str:
|
|
34
34
|
"""
|
|
35
35
|
Given Python code as a string, returns a notebook as a string.
|
|
36
36
|
Calls jupytext as a subprocess:
|
|
@@ -71,13 +71,13 @@ def convert_py_to_nb(
|
|
|
71
71
|
return _clean_nb(json.dumps(nb_dict))
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def _stable_hash(lines: list[str]):
|
|
74
|
+
def _stable_hash(lines: list[str]) -> str:
|
|
75
75
|
import hashlib
|
|
76
76
|
|
|
77
77
|
return hashlib.sha1("\n".join(lines).encode()).hexdigest()[:8]
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def _clean_nb(nb_json: str):
|
|
80
|
+
def _clean_nb(nb_json: str) -> str:
|
|
81
81
|
"""
|
|
82
82
|
Given a notebook as a string of JSON, remove the coda and pip output.
|
|
83
83
|
(The code may produce reports that we do need,
|
|
@@ -104,7 +104,7 @@ def _clean_nb(nb_json: str):
|
|
|
104
104
|
return json.dumps(nb, indent=1)
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
def convert_nb_to_html(python_nb: str, numbered=True):
|
|
107
|
+
def convert_nb_to_html(python_nb: str, numbered=True) -> str:
|
|
108
108
|
notebook = nbformat.reads(python_nb, as_version=4)
|
|
109
109
|
exporter = nbconvert.HTMLExporter(
|
|
110
110
|
template_name="lab",
|
|
File without changes
|
|
@@ -7523,7 +7523,7 @@ even though the lines are not contiguous</p>
|
|
|
7523
7523
|
</div>
|
|
7524
7524
|
</div>
|
|
7525
7525
|
</div>
|
|
7526
|
-
</div><div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=
|
|
7526
|
+
</div><div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=d75dbbc1">
|
|
7527
7527
|
<div class="jp-Cell-inputWrapper" tabindex="0">
|
|
7528
7528
|
<div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser">
|
|
7529
7529
|
</div>
|
|
@@ -7535,6 +7535,9 @@ even though the lines are not contiguous</p>
|
|
|
7535
7535
|
<span class="w"> </span><span class="sd">"""</span>
|
|
7536
7536
|
<span class="sd"> This demonstrates how larger blocks of code can be built compositionally.</span>
|
|
7537
7537
|
<span class="sd"> """</span>
|
|
7538
|
+
<span class="c1"># Water freezes at:</span>
|
|
7539
|
+
<span class="c1"># 32 Fahrenheit</span>
|
|
7540
|
+
<span class="c1"># 0 Celsius</span>
|
|
7538
7541
|
<span class="k">if</span> <span class="n">temp_c</span> <span class="o"><</span> <span class="mi">0</span><span class="p">:</span>
|
|
7539
7542
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"It is freezing!"</span><span class="p">)</span>
|
|
7540
7543
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
{
|
|
18
18
|
"cell_type": "code",
|
|
19
19
|
"execution_count": 1,
|
|
20
|
-
"id": "
|
|
20
|
+
"id": "d75dbbc1",
|
|
21
21
|
"metadata": {},
|
|
22
22
|
"outputs": [
|
|
23
23
|
{
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
" \"\"\"\n",
|
|
34
34
|
" This demonstrates how larger blocks of code can be built compositionally.\n",
|
|
35
35
|
" \"\"\"\n",
|
|
36
|
+
" # Water freezes at:\n",
|
|
37
|
+
" # 32 Fahrenheit\n",
|
|
38
|
+
" # 0 Celsius\n",
|
|
36
39
|
" if temp_c < 0:\n",
|
|
37
40
|
" print(\"It is freezing!\")\n",
|
|
38
41
|
"\n",
|
|
@@ -7574,13 +7574,18 @@ itself can be parsed as Python code, so syntax highlighting and linting still wo
|
|
|
7574
7574
|
</div>
|
|
7575
7575
|
</div>
|
|
7576
7576
|
</div>
|
|
7577
|
-
<div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=
|
|
7577
|
+
<div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=39e34438">
|
|
7578
7578
|
<div class="jp-Cell-inputWrapper" tabindex="0">
|
|
7579
7579
|
<div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser">
|
|
7580
7580
|
</div>
|
|
7581
7581
|
<div class="jp-InputArea jp-Cell-inputArea"><div class="jp-InputPrompt jp-InputArea-prompt">
|
|
7582
7582
|
</div><div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown">
|
|
7583
|
-
<p>Note
|
|
7583
|
+
<p>Note that <code>conditional_print_template</code> is not called: Instead,
|
|
7584
|
+
the <code>inspect</code> package is used to load its source, and the slots
|
|
7585
|
+
in all-caps are filled. Including a parameter list is optional,
|
|
7586
|
+
but providing args which match the names of your slots can prevent
|
|
7587
|
+
warnings from your IDE.</p>
|
|
7588
|
+
<p>Different methods are available on the <code>Template</code> object:</p>
|
|
7584
7589
|
<ul>
|
|
7585
7590
|
<li><code>fill_expressions()</code> fills the slot with verbatim text.
|
|
7586
7591
|
It can be used for an expression like this, or for variable names.</li>
|
|
@@ -7590,14 +7595,15 @@ data structure, as long as it has a usable repr.</li>
|
|
|
7590
7595
|
<li><code>finish()</code> converts the template to a string, and will error
|
|
7591
7596
|
if not all slots have been filled.</li>
|
|
7592
7597
|
</ul>
|
|
7593
|
-
<p>
|
|
7598
|
+
<p>(The next section will introduce <code>fill_code_block()</code> and <code>fill_comment_block()</code>.)</p>
|
|
7599
|
+
<p>Templates can also be standalone files. If a <code>root</code> parameter is provided,
|
|
7594
7600
|
the system will prepend <code>_</code> and append <code>.py</code> and look for a corresponding file.
|
|
7595
7601
|
(The convention of prepending <code>_</code> reminds us that although these files
|
|
7596
7602
|
can be parsed, they should not be imported or executed as-is.)</p>
|
|
7597
7603
|
</div>
|
|
7598
7604
|
</div>
|
|
7599
7605
|
</div>
|
|
7600
|
-
</div><div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=
|
|
7606
|
+
</div><div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=c3374f03">
|
|
7601
7607
|
<div class="jp-Cell-inputWrapper" tabindex="0">
|
|
7602
7608
|
<div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser">
|
|
7603
7609
|
</div>
|
|
@@ -7612,7 +7618,14 @@ can be parsed, they should not be imported or executed as-is.)</p>
|
|
|
7612
7618
|
<span class="n">block_demo</span> <span class="o">=</span> <span class="p">(</span>
|
|
7613
7619
|
<span class="n">Template</span><span class="p">(</span><span class="s2">"block_demo"</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="n">root</span> <span class="o">/</span> <span class="s2">"examples"</span><span class="p">)</span>
|
|
7614
7620
|
<span class="o">.</span><span class="n">fill_expressions</span><span class="p">(</span><span class="n">FUNCTION_NAME</span><span class="o">=</span><span class="s2">"freeze_warning"</span><span class="p">,</span> <span class="n">PARAMS</span><span class="o">=</span><span class="s2">"temp_c"</span><span class="p">)</span>
|
|
7615
|
-
<span class="o">.</span><span class="n">
|
|
7621
|
+
<span class="o">.</span><span class="n">fill_code_blocks</span><span class="p">(</span><span class="n">INNER_BLOCK</span><span class="o">=</span><span class="n">conditional_print</span><span class="p">)</span>
|
|
7622
|
+
<span class="o">.</span><span class="n">fill_comment_blocks</span><span class="p">(</span>
|
|
7623
|
+
<span class="n">COMMENT</span><span class="o">=</span><span class="s2">"""</span>
|
|
7624
|
+
<span class="s2"> Water freezes at:</span>
|
|
7625
|
+
<span class="s2"> 32 Fahrenheit</span>
|
|
7626
|
+
<span class="s2"> 0 Celsius</span>
|
|
7627
|
+
<span class="s2"> """</span>
|
|
7628
|
+
<span class="p">)</span>
|
|
7616
7629
|
<span class="o">.</span><span class="n">finish</span><span class="p">()</span>
|
|
7617
7630
|
<span class="p">)</span>
|
|
7618
7631
|
|
|
@@ -7622,6 +7635,9 @@ can be parsed, they should not be imported or executed as-is.)</p>
|
|
|
7622
7635
|
<span class="s1"> """</span>
|
|
7623
7636
|
<span class="s1"> This demonstrates how larger blocks of code can be built compositionally.</span>
|
|
7624
7637
|
<span class="s1"> """</span>
|
|
7638
|
+
<span class="s1"> # Water freezes at:</span>
|
|
7639
|
+
<span class="s1"> # 32 Fahrenheit</span>
|
|
7640
|
+
<span class="s1"> # 0 Celsius</span>
|
|
7625
7641
|
<span class="s1"> if temp_c < 0:</span>
|
|
7626
7642
|
<span class="s1"> print('It is freezing!')</span>
|
|
7627
7643
|
<span class="s1">'''</span>
|
|
@@ -7683,7 +7699,7 @@ to produce other artifacts without adding clutter.</p>
|
|
|
7683
7699
|
</div>
|
|
7684
7700
|
</div>
|
|
7685
7701
|
</div>
|
|
7686
|
-
</div><div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=
|
|
7702
|
+
</div><div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=ccf10f98">
|
|
7687
7703
|
<div class="jp-Cell-inputWrapper" tabindex="0">
|
|
7688
7704
|
<div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser">
|
|
7689
7705
|
</div>
|
|
@@ -7720,7 +7736,7 @@ to produce other artifacts without adding clutter.</p>
|
|
|
7720
7736
|
<span class="n">title</span> <span class="o">=</span> <span class="s2">"Hello World!"</span>
|
|
7721
7737
|
<span class="n">notebook_py</span> <span class="o">=</span> <span class="p">(</span>
|
|
7722
7738
|
<span class="n">Template</span><span class="p">(</span><span class="n">notebook_template</span><span class="p">)</span>
|
|
7723
|
-
<span class="o">.</span><span class="n">
|
|
7739
|
+
<span class="o">.</span><span class="n">fill_code_blocks</span><span class="p">(</span><span class="n">BLOCK</span><span class="o">=</span><span class="n">block_demo</span><span class="p">)</span>
|
|
7724
7740
|
<span class="o">.</span><span class="n">fill_expressions</span><span class="p">(</span><span class="n">FUNCTION_NAME</span><span class="o">=</span><span class="s2">"freeze_warning"</span><span class="p">,</span> <span class="n">TITLE</span><span class="o">=</span><span class="n">title</span><span class="p">)</span>
|
|
7725
7741
|
<span class="o">.</span><span class="n">finish</span><span class="p">()</span>
|
|
7726
7742
|
<span class="p">)</span>
|
|
@@ -33,9 +33,14 @@ def test_fill_expressions():
|
|
|
33
33
|
assert filled == "No one expects the Spanish Inquisition!"
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
template = Template("No one ... the
|
|
38
|
-
with pytest.raises(
|
|
36
|
+
def test_fill_expressions_missing_slots_in_template():
|
|
37
|
+
template = Template("No one ... the ... ...!")
|
|
38
|
+
with pytest.raises(
|
|
39
|
+
Exception,
|
|
40
|
+
match=r"no 'ADJ' slot to fill with 'Spanish', "
|
|
41
|
+
r"no 'NOUN' slot to fill with 'Inquisition', "
|
|
42
|
+
r"no 'VERB' slot to fill with 'expects':",
|
|
43
|
+
):
|
|
39
44
|
template.fill_expressions(
|
|
40
45
|
VERB="expects",
|
|
41
46
|
ADJ="Spanish",
|
|
@@ -43,11 +48,12 @@ def test_fill_expressions_missing_slot_in_template():
|
|
|
43
48
|
).finish()
|
|
44
49
|
|
|
45
50
|
|
|
46
|
-
def
|
|
51
|
+
def test_fill_expressions_extra_slots_in_template():
|
|
47
52
|
template = Template("No one VERB ARTICLE ADJ NOUN!")
|
|
48
|
-
with pytest.raises(
|
|
53
|
+
with pytest.raises(
|
|
54
|
+
Exception, match=r"'ARTICLE' slot not filled, 'VERB' slot not filled"
|
|
55
|
+
):
|
|
49
56
|
template.fill_expressions(
|
|
50
|
-
VERB="expects",
|
|
51
57
|
ADJ="Spanish",
|
|
52
58
|
NOUN="Inquisition",
|
|
53
59
|
).finish()
|
|
@@ -65,7 +71,7 @@ def test_fill_values():
|
|
|
65
71
|
|
|
66
72
|
def test_fill_values_missing_slot_in_template():
|
|
67
73
|
template = Template("assert [STRING] * ... == LIST")
|
|
68
|
-
with pytest.raises(Exception, match=r"
|
|
74
|
+
with pytest.raises(Exception, match=r"no 'NUM' slot to fill with '3'"):
|
|
69
75
|
template.fill_values(
|
|
70
76
|
STRING="🙂",
|
|
71
77
|
NUM=3,
|
|
@@ -91,18 +97,27 @@ def test_fill_blocks():
|
|
|
91
97
|
FIRST
|
|
92
98
|
|
|
93
99
|
with fake:
|
|
94
|
-
|
|
100
|
+
my_tuple = (
|
|
101
|
+
# SECOND
|
|
102
|
+
VALUE,
|
|
103
|
+
)
|
|
95
104
|
if True:
|
|
96
105
|
THIRD
|
|
97
106
|
""",
|
|
98
107
|
)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
filled = (
|
|
109
|
+
template.fill_code_blocks(
|
|
110
|
+
FIRST="\n".join(f"import {i}" for i in "abc"),
|
|
111
|
+
THIRD="\n".join(f"{i}()" for i in "xyz"),
|
|
112
|
+
)
|
|
113
|
+
.fill_comment_blocks(
|
|
114
|
+
SECOND="This is a\nmulti-line comment",
|
|
115
|
+
)
|
|
116
|
+
.fill_values(VALUE=42)
|
|
117
|
+
.finish()
|
|
103
118
|
)
|
|
104
119
|
assert (
|
|
105
|
-
|
|
120
|
+
filled
|
|
106
121
|
== """# MixedCase is OK
|
|
107
122
|
|
|
108
123
|
import a
|
|
@@ -110,9 +125,11 @@ import b
|
|
|
110
125
|
import c
|
|
111
126
|
|
|
112
127
|
with fake:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
my_tuple = (
|
|
129
|
+
# This is a
|
|
130
|
+
# multi-line comment
|
|
131
|
+
42,
|
|
132
|
+
)
|
|
116
133
|
if True:
|
|
117
134
|
x()
|
|
118
135
|
y()
|
|
@@ -121,30 +138,48 @@ with fake:
|
|
|
121
138
|
)
|
|
122
139
|
|
|
123
140
|
|
|
141
|
+
def test_fill_comment_block():
|
|
142
|
+
template = Template("# SLOT")
|
|
143
|
+
filled = template.fill_comment_blocks(SLOT="placeholder").finish()
|
|
144
|
+
assert filled == "# placeholder"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_fill_comment_block_without_comment():
|
|
148
|
+
template = Template("SLOT")
|
|
149
|
+
with pytest.raises(
|
|
150
|
+
Exception,
|
|
151
|
+
match=r"In string template, no 'SLOT' slot to fill with 'placeholder' "
|
|
152
|
+
r"\(comment slots must be prefixed with '#'\)",
|
|
153
|
+
):
|
|
154
|
+
template.fill_comment_blocks(SLOT="placeholder").finish()
|
|
155
|
+
|
|
156
|
+
|
|
124
157
|
def test_fill_blocks_missing_slot_in_template_alone():
|
|
125
158
|
template = Template("No block slot")
|
|
126
|
-
with pytest.raises(Exception, match=r"
|
|
127
|
-
template.
|
|
159
|
+
with pytest.raises(Exception, match=r"no 'SLOT' slot to fill with 'placeholder':"):
|
|
160
|
+
template.fill_code_blocks(SLOT="placeholder").finish()
|
|
128
161
|
|
|
129
162
|
|
|
130
163
|
def test_fill_blocks_missing_slot_in_template_not_alone():
|
|
131
164
|
template = Template("No block SLOT")
|
|
132
165
|
with pytest.raises(
|
|
133
|
-
Exception,
|
|
166
|
+
Exception,
|
|
167
|
+
match=r"no 'SLOT' slot to fill with 'placeholder' "
|
|
168
|
+
r"\(block slots must be alone on line\)",
|
|
134
169
|
):
|
|
135
|
-
template.
|
|
170
|
+
template.fill_code_blocks(SLOT="placeholder").finish()
|
|
136
171
|
|
|
137
172
|
|
|
138
173
|
def test_fill_blocks_extra_slot_in_template():
|
|
139
174
|
template = Template("EXTRA\nSLOT")
|
|
140
175
|
with pytest.raises(Exception, match=r"'EXTRA' slot not filled"):
|
|
141
|
-
template.
|
|
176
|
+
template.fill_code_blocks(SLOT="placeholder").finish()
|
|
142
177
|
|
|
143
178
|
|
|
144
179
|
def test_fill_blocks_not_string():
|
|
145
180
|
template = Template("SOMETHING")
|
|
146
181
|
with pytest.raises(
|
|
147
182
|
Exception,
|
|
148
|
-
match=r"
|
|
183
|
+
match=r"for 'SOMETHING' slot, expected string, not '123'",
|
|
149
184
|
):
|
|
150
|
-
template.
|
|
185
|
+
template.fill_code_blocks(SOMETHING=123).finish()
|
|
@@ -54,7 +54,13 @@ assert conditional_print == "if temp_c < 0:\n print('It is freezing!')"
|
|
|
54
54
|
|
|
55
55
|
# -
|
|
56
56
|
|
|
57
|
-
# Note
|
|
57
|
+
# Note that `conditional_print_template` is not called: Instead,
|
|
58
|
+
# the `inspect` package is used to load its source, and the slots
|
|
59
|
+
# in all-caps are filled. Including a parameter list is optional,
|
|
60
|
+
# but providing args which match the names of your slots can prevent
|
|
61
|
+
# warnings from your IDE.
|
|
62
|
+
#
|
|
63
|
+
# Different methods are available on the `Template` object:
|
|
58
64
|
# - `fill_expressions()` fills the slot with verbatim text.
|
|
59
65
|
# It can be used for an expression like this, or for variable names.
|
|
60
66
|
# - `fill_values()` fills the slot with the repr of the provided value.
|
|
@@ -63,7 +69,9 @@ assert conditional_print == "if temp_c < 0:\n print('It is freezing!')"
|
|
|
63
69
|
# - `finish()` converts the template to a string, and will error
|
|
64
70
|
# if not all slots have been filled.
|
|
65
71
|
#
|
|
66
|
-
#
|
|
72
|
+
# (The next section will introduce `fill_code_block()` and `fill_comment_block()`.)
|
|
73
|
+
#
|
|
74
|
+
# Templates can also be standalone files. If a `root` parameter is provided,
|
|
67
75
|
# the system will prepend `_` and append `.py` and look for a corresponding file.
|
|
68
76
|
# (The convention of prepending `_` reminds us that although these files
|
|
69
77
|
# can be parsed, they should not be imported or executed as-is.)
|
|
@@ -77,7 +85,14 @@ root = Path(__file__).parent.parent
|
|
|
77
85
|
block_demo = (
|
|
78
86
|
Template("block_demo", root=root / "examples")
|
|
79
87
|
.fill_expressions(FUNCTION_NAME="freeze_warning", PARAMS="temp_c")
|
|
80
|
-
.
|
|
88
|
+
.fill_code_blocks(INNER_BLOCK=conditional_print)
|
|
89
|
+
.fill_comment_blocks(
|
|
90
|
+
COMMENT="""
|
|
91
|
+
Water freezes at:
|
|
92
|
+
32 Fahrenheit
|
|
93
|
+
0 Celsius
|
|
94
|
+
"""
|
|
95
|
+
)
|
|
81
96
|
.finish()
|
|
82
97
|
)
|
|
83
98
|
|
|
@@ -87,6 +102,9 @@ assert (
|
|
|
87
102
|
"""
|
|
88
103
|
This demonstrates how larger blocks of code can be built compositionally.
|
|
89
104
|
"""
|
|
105
|
+
# Water freezes at:
|
|
106
|
+
# 32 Fahrenheit
|
|
107
|
+
# 0 Celsius
|
|
90
108
|
if temp_c < 0:
|
|
91
109
|
print('It is freezing!')
|
|
92
110
|
'''
|
|
@@ -152,7 +170,7 @@ def notebook_template(TITLE, BLOCK, FUNCTION_NAME):
|
|
|
152
170
|
title = "Hello World!"
|
|
153
171
|
notebook_py = (
|
|
154
172
|
Template(notebook_template)
|
|
155
|
-
.
|
|
173
|
+
.fill_code_blocks(BLOCK=block_demo)
|
|
156
174
|
.fill_expressions(FUNCTION_NAME="freeze_warning", TITLE=title)
|
|
157
175
|
.finish()
|
|
158
176
|
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|