146 target_comment_col: int = 36,
147 max_line_length: int = 110,
150 Pretty-print a structured NumPy dtype (with Felis-derived metadata)
151 as valid, readable Python code:
153 # Wrapped table description...
155 <table_name>Dtype = np.dtype([
156 ('field1', '<i8'), # comment...
157 ('very_long_field', ... # comment juts, next long field aligned
158 # to same jutter column...
164 Structured dtype with metadata fields:
165 metadata["description"] : table-level description (optional)
166 metadata["columns"] : {col_name: per-column description}
168 Name used for the assignment, e.g. <table_name>Dtype.
169 target_comment_col : int, default=36
170 Preferred starting column for comments when the field fits before it.
171 If the field text is longer than this, a "juttering group" alignment
172 logic kicks in to prevent jagged right edges.
173 max_line_length : int, default=110
174 Maximum line length for wrapping table descriptions and comments.
175 Lines will be wrapped to be strictly less than this length.
179 * Table description is wrapped to < max_line_length chars, placed above
180 dtype assignment, followed by a blank line.
182 - If the field length <= target_comment_col - 1 → comment starts at
183 target_comment_col, and the "juttering group" resets.
184 - If the field length >= target_comment_col → the comment "juts out".
185 + First such field sets the group's jutter column.
186 + Next juttering fields use max(previous_jut_col, natural_jut_col).
187 + This avoids jaggedness.
188 * Final lines never exceed max_line_length - 1 chars.
189 * Per-field comments wrap into at most 2 lines, with "..." if needed.
190 * dtype.metadata is NOT emitted; only used for comments.
195 Pretty Python code string.
197 if not isinstance(dtype, np.dtype)
or not dtype.fields:
198 raise TypeError(
"Expected a structured numpy.dtype with fields")
200 md = dtype.metadata
or {}
201 table_desc = md.get(
"description")
202 col_descs = md.get(
"columns", {})
204 lines: list[str] = []
208 txt = f
"{table_name}: {table_desc}"
209 for w
in wrap(txt, width=max_line_length - 3):
210 lines.append(f
"# {w}")
213 lines.append(f
"{table_name}Dtype = np.dtype([")
217 for name, (ftype, _)
in dtype.fields.items():
218 base = f
" ({name!r}, {ftype.str!r}),"
219 comment = col_descs.get(name)
221 comment =
" ".join(str(comment).split())
222 field_entries.append((base, comment))
224 last_jut_comment_col =
None
227 for base, comment
in field_entries:
233 if base_len <= target_comment_col - 1:
234 last_jut_comment_col =
None
238 if base_len <= target_comment_col - 1:
240 comment_col = target_comment_col
241 last_jut_comment_col =
None
244 natural_col = base_len + 1
246 if last_jut_comment_col
is None:
248 comment_col = natural_col
249 last_jut_comment_col = comment_col
252 if natural_col <= last_jut_comment_col:
254 comment_col = last_jut_comment_col
257 comment_col = natural_col
258 last_jut_comment_col = comment_col
261 max_comment_width = max(10, (max_line_length - 1) - (comment_col + 2))
264 words = comment.split()
270 elif len(cur) + 1 + len(w) <= max_comment_width:
279 if len(segments) > 2:
280 segments = segments[:2]
281 if len(segments[-1]) + 3 > max_comment_width:
282 segments[-1] = segments[-1][: max_comment_width - 3].rstrip()
283 segments[-1] +=
"..."
286 pad =
" " * (comment_col - base_len)
287 lines.append(f
"{base}{pad}# {segments[0]}")
290 cont_prefix =
" " * comment_col +
"# "
291 for seg
in segments[1:]:
292 lines.append(f
"{cont_prefix}{seg}")
295 return "\n".join(lines)
299 parser = argparse.ArgumentParser(description=
"Generate NumPy dtypes from Felis YAML schema")
300 parser.add_argument(
"felis_yaml_file", help=
"Path to the YAML schema file")
304 default=DEFAULT_TABLES,
305 help=(f
"Names of tables to process (default: {', '.join(DEFAULT_TABLES)})"),
307 args = parser.parse_args()
309 with open(args.felis_yaml_file)
as fp:
310 schema = yaml.safe_load(fp)
312 table_schemas = {t[
"name"]: t
for t
in schema[
"tables"]}
315 print(
"# ***** GENERATED FILE, DO NOT EDIT BY HAND *****")
316 print(
"# ruff: noqa: W505")
317 print(f
"# generated with {' '.join(sys.argv)} # noqa: E501")
319 print(
"import numpy as np")
322 for i, table
in enumerate(args.table_names):
325 if i < len(args.table_names) - 1: