17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 | class SchemaSpreadsheet:
"""Turns the dataverse schema into a spreadsheet."""
def __init__(self, schema: DataverseSchemaResponse) -> None:
"""Initialize the SchemaSpreadsheet with a DataverseSchemaResponse."""
self.schema = schema.data
@staticmethod
def _header_row() -> list[str]:
return ["Field", "Definition", "Usage", "Repeatable (Y/N)", "Example"]
def _title_fmt(self, workbook: xlsxwriter.Workbook):
return workbook.add_format({"bold": True})
def _block_fmt(self, workbook: xlsxwriter.Workbook):
return workbook.add_format(
{"bold": True, "align": "center", "bg_color": f"#{random.randrange(0x1000000):06x}"}
)
@staticmethod
def _field_rows(field: MetadataField) -> list[Row]:
"""Turn one top-level field (and its children) into spreadsheet rows."""
# ponytail: usage is RQ/O from isRequired; the schema has no "recommended" flag
usage = lambda f: "RQ" if f.isRequired else "O" # ruff:ignore[lambda-assignment]
repeatable = lambda f: "Y" if f.multiple else "N" # ruff:ignore[lambda-assignment]
if not field.childFields:
return [
(
field.title,
field.description,
usage(field),
repeatable(field),
field.watermark,
True,
True,
)
]
# Compound field: parent row carries only the definition, children the rest.
rows: list[Row] = [(field.title, field.description, "", "", "", True, False)]
children = sorted(field.childFields.values(), key=lambda f: f.displayOrder)
rows.extend(
(
child.title,
child.description,
usage(child),
# A child of a repeatable compound is effectively repeatable.
"Y" if (child.multiple or field.multiple) else "N",
child.watermark,
False,
child is children[-1],
)
for child in children
)
return rows
def _write_sheet(
self,
workbook: xlsxwriter.Workbook,
sheet_name: str,
blocks: list[MetadataBlock],
cell_fmts: dict[tuple[bool, bool], Format],
) -> None:
sheet = workbook.add_worksheet(sheet_name)
for col, width in enumerate(COLUMN_WIDTHS):
sheet.set_column(col, col, width)
sheet.write_row(0, 0, self._header_row(), self._title_fmt(workbook))
row_num = 1
for block in blocks:
sheet.merge_range(
row_num, 0, row_num, 4, f"{block.displayName} Block", self._block_fmt(workbook)
)
row_num += 1
for field in sorted(block.fields.values(), key=lambda f: f.displayOrder):
for title, *cells, bold, end in self._field_rows(field):
sheet.write(row_num, 0, title, cell_fmts[bold, end])
for col, value in enumerate(cells, start=1):
sheet.write(row_num, col, value, cell_fmts[False, end])
row_num += 1
def write(self, path: str | Path) -> Path:
"""Write an 'All' worksheet plus one per metadata block to an .xlsx file at `path`.
Parameters
----------
path
The path to the .xlsx file to write. If it exists, it will be overwritten
Returns
-------
Path
The path to the .xlsx file that was written.
"""
path = Path(path)
workbook = xlsxwriter.Workbook(str(path))
cell_fmts = {
(bold, end): workbook.add_format(
{"bold": bold, "bottom": 1 if end else 0, "text_wrap": True, "valign": "top"}
)
for bold in (True, False)
for end in (True, False)
}
blocks = sorted(self.schema, key=lambda b: b.id)
self._write_sheet(workbook, "All", blocks, cell_fmts)
for block in blocks:
self._write_sheet(
workbook, block.displayName.removesuffix(" Metadata")[:31], [block], cell_fmts
)
workbook.close()
return path
|