dp_wizard_templates 0.2.0__py2.py3-none-any.whl → 0.3.0__py2.py3-none-any.whl

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.

@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -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__(self, template, root=None):
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 fill_expressions(self, **kwargs):
59
- """
60
- Fill in variable names, or dicts or lists represented as strings.
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 fill_values(self, **kwargs):
73
- """
74
- Fill in string or numeric values. `repr` is called before filling.
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(rf"\b{k_re}\b", repr(v), self._template)
87
+ self._template, count = re.subn(
88
+ rf"\b{k_re}\b", stringifier(v), self._template
89
+ )
79
90
  if count == 0:
80
- raise Exception(
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
- def fill_blocks(self, **kwargs):
87
- """
88
- Fill in code blocks. Slot must be alone on line.
89
- """
90
- for k, v in kwargs.items():
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
- raise Exception(f"For {k} in {self._source}, expected string, not {v}")
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.split("\n") # noqa: B023
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
- raise Exception(
114
- f"Block slots must be alone on line; {base_message}"
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
- raise Exception(base_message)
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 finish(self, reformat=False) -> str:
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
- slots_str = ", ".join(sorted(f"'{slot}'" for slot in unfilled_slots))
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dp_wizard_templates
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Code templating tools
5
5
  Author-email: The OpenDP Project <info@opendp.org>
6
6
  Description-Content-Type: text/markdown
@@ -0,0 +1,9 @@
1
+ dp_wizard_templates/VERSION,sha256=VQYTU3_EiPOzcq90pAAYefASyEZbgW8bhcbTRGss-0k,5
2
+ dp_wizard_templates/__init__.py,sha256=E2xrnvZGY24pU3PCryH4TmvhNYsLmxXCtfvIcYNbTYw,126
3
+ dp_wizard_templates/code_template.py,sha256=BAt9vsvrusqR3mETsUE4HfsPgF3D0yPHgL8Q0no01h4,5735
4
+ dp_wizard_templates/converters.py,sha256=0Ml_V71Z6zce2SaxDQMzYXg-5jQw2-NWChrY_9SCGIo,4359
5
+ dp_wizard_templates/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ dp_wizard_templates-0.3.0.dist-info/licenses/LICENSE,sha256=FDrIMeZPiT4g_4w0i1Ec4Bc8h9wfNytroheQN4508yU,1063
7
+ dp_wizard_templates-0.3.0.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
8
+ dp_wizard_templates-0.3.0.dist-info/METADATA,sha256=uBCyEVM3KWsLChzbFCYT8nxjwdxvQeWMsGAKSKCiEYA,1881
9
+ dp_wizard_templates-0.3.0.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- dp_wizard_templates/VERSION,sha256=kR_AxIywxwYB21d1qb7xt0DcTMn5tGOJufBWP-frlNc,5
2
- dp_wizard_templates/__init__.py,sha256=E2xrnvZGY24pU3PCryH4TmvhNYsLmxXCtfvIcYNbTYw,126
3
- dp_wizard_templates/code_template.py,sha256=HwnmKmJStkan9jkdyRQ4zVnJcvHWymzzCt8lwdTdtX4,4504
4
- dp_wizard_templates/converters.py,sha256=voLc2KlJ7WZGYMtA8x0pB1xBcLJs974uoX2nCJO7uD0,4331
5
- dp_wizard_templates-0.2.0.dist-info/licenses/LICENSE,sha256=FDrIMeZPiT4g_4w0i1Ec4Bc8h9wfNytroheQN4508yU,1063
6
- dp_wizard_templates-0.2.0.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
7
- dp_wizard_templates-0.2.0.dist-info/METADATA,sha256=zvMOoJELX1KP5e_3gIcimNjp62lDNyOd7BfkGahvdLg,1881
8
- dp_wizard_templates-0.2.0.dist-info/RECORD,,