mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The workbook kept every cell as an object: a 100 000-row Logs download took some 400 MB in a web worker. openpyxl's write-only mode writes the rows as they come (85 MB for the same file, measured), and the Logs rows are read as tuples, not model objects. The bold, frozen, filtered header, the column widths and the text cells stay as they were.
45 lines
1.9 KiB
Python
45 lines
1.9 KiB
Python
"""An Excel workbook: rows under a bold, filterable header, and a sheet of notes."""
|
|
|
|
from io import BytesIO
|
|
|
|
from openpyxl import Workbook
|
|
from openpyxl.cell import WriteOnlyCell
|
|
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
|
|
from openpyxl.styles import Font
|
|
from openpyxl.utils import get_column_letter
|
|
|
|
MAX_WIDTH = 60
|
|
|
|
|
|
def workbook(title, header, rows, notes_title, notes):
|
|
"""The .xlsx file's bytes, written row by row (openpyxl's write-only mode:
|
|
a large download does not hold every cell in memory). Every text cell
|
|
stays text: openpyxl would write one starting with "=" as a formula, and
|
|
the text comes from VM names and guests."""
|
|
book = Workbook(write_only=True)
|
|
sheet = book.create_sheet(title)
|
|
# control characters (a name typed at a failed login) cannot be in the file: written as \x01
|
|
rows = [[ILLEGAL_CHARACTERS_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", value) if isinstance(value, str) else value
|
|
for value in values] for values in [header, *rows]]
|
|
for index, column in enumerate(zip(*rows), start=1):
|
|
width = max(len(str(value or "")) for value in column) + 2
|
|
sheet.column_dimensions[get_column_letter(index)].width = min(width, MAX_WIDTH)
|
|
sheet.freeze_panes = "A2"
|
|
for number, values in enumerate(rows):
|
|
cells = []
|
|
for value in values:
|
|
cell = WriteOnlyCell(sheet, value=value)
|
|
if isinstance(value, str):
|
|
cell.data_type = "s"
|
|
if number == 0:
|
|
cell.font = Font(bold=True)
|
|
cells.append(cell)
|
|
sheet.append(cells)
|
|
sheet.auto_filter.ref = f"A1:{get_column_letter(len(header))}{len(rows)}"
|
|
notes_sheet = book.create_sheet(notes_title)
|
|
notes_sheet.column_dimensions["A"].width = MAX_WIDTH * 2
|
|
for line in notes:
|
|
notes_sheet.append([line])
|
|
out = BytesIO()
|
|
book.save(out)
|
|
return out.getvalue()
|