From 1c1bf2234611628ab5b132c273adba4d4c201e50 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:35:44 -0700 Subject: [PATCH 1/5] fix(visualize): return empty on extract_code_block language miss A mismatched language hint returned the full raw text, so codegen's `hint or any-fence` fallback never ran and ```json Chart.js fences with trailing prose failed local validation. --- deeptutor/agents/visualize/utils.py | 4 ++++ tests/agents/visualize/test_extract_code_block.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/deeptutor/agents/visualize/utils.py b/deeptutor/agents/visualize/utils.py index 0089b41f28..3633b5e4ce 100644 --- a/deeptutor/agents/visualize/utils.py +++ b/deeptutor/agents/visualize/utils.py @@ -37,6 +37,7 @@ def extract_code_block(text: str, language: str = "") -> str: If *language* is given the block must start with that tag; otherwise any triple-backtick fence is accepted. + A language miss returns ``""`` so callers can fall back with ``or``. """ # Closing fence may sit on the same line as the last content line. if language: @@ -46,6 +47,9 @@ def extract_code_block(text: str, language: str = "") -> str: match = re.search(pattern, text or "", re.IGNORECASE) if match: return match.group(1).strip() + # Language miss must be falsy so `hint or any-fence` can fall through. + if language: + return "" return (text or "").strip() diff --git a/tests/agents/visualize/test_extract_code_block.py b/tests/agents/visualize/test_extract_code_block.py index 79d13244fb..c979a243e9 100644 --- a/tests/agents/visualize/test_extract_code_block.py +++ b/tests/agents/visualize/test_extract_code_block.py @@ -14,3 +14,11 @@ def test_extract_code_block_closing_fence_without_leading_newline() -> None: def test_extract_code_block_normal_fenced_block() -> None: raw = "```javascript\nconst x = 1;\n```" assert extract_code_block(raw, "javascript") == "const x = 1;" + + +def test_extract_code_block_language_miss_is_empty_for_or_fallback() -> None: + cfg = '{"type": "bar", "data": {"labels": ["A"], "datasets": [{"data": [1]}]}}' + raw = f"```json\n{cfg}\n```\nThanks." + assert extract_code_block(raw, "javascript") == "" + extracted = extract_code_block(raw, "javascript") or extract_code_block(raw) + assert extracted == cfg From 6e440690a58b301cc53440ad9347335bb380553a Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:43:44 -0700 Subject: [PATCH 2/5] chore: ruff-format builtin skill markdown --- deeptutor/skills/builtin/docx/SKILL.md | 54 +++++++++----- deeptutor/skills/builtin/pdf/SKILL.md | 98 ++++++++++++++++++-------- deeptutor/skills/builtin/pptx/SKILL.md | 36 ++++++---- deeptutor/skills/builtin/xlsx/SKILL.md | 49 ++++++++----- 4 files changed, 159 insertions(+), 78 deletions(-) diff --git a/deeptutor/skills/builtin/docx/SKILL.md b/deeptutor/skills/builtin/docx/SKILL.md index 0608b794c1..ba0402d42d 100644 --- a/deeptutor/skills/builtin/docx/SKILL.md +++ b/deeptutor/skills/builtin/docx/SKILL.md @@ -26,9 +26,10 @@ After `exec` completes, use the Generated artifacts URL from the tool result in ```python from docx import Document + doc = Document("in.docx") -text = "\n".join(p.text for p in doc.paragraphs) # body paragraphs -for tbl in doc.tables: # tables +text = "\n".join(p.text for p in doc.paragraphs) # body paragraphs +for tbl in doc.tables: # tables for row in tbl.rows: print([c.text for c in row.cells]) ``` @@ -39,6 +40,7 @@ To read **tracked changes**, parse the XML directly — `python-docx` ignores `< ```python import zipfile, re + xml = zipfile.ZipFile("in.docx").read("word/document.xml").decode("utf-8") # inserted text = … deleted = … print(re.findall(r"]*>(.*?)", xml)) @@ -51,20 +53,23 @@ from docx import Document from docx.shared import Pt, Inches, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH -doc = Document() # default template page size -doc.add_heading("Quarterly Report", level=0) # 0 = title; 1..9 = H1..H9 +doc = Document() # default template page size +doc.add_heading("Quarterly Report", level=0) # 0 = title; 1..9 = H1..H9 p = doc.add_paragraph("Intro paragraph. ") -run = p.add_run("Bold tail."); run.bold = True -doc.add_paragraph("First item", style="List Bullet") # real list style, never a "• " literal +run = p.add_run("Bold tail.") +run.bold = True +doc.add_paragraph("First item", style="List Bullet") # real list style, never a "• " literal doc.add_paragraph("Step one", style="List Number") # Table — header row + data -tbl = doc.add_table(rows=1, cols=2); tbl.style = "Light Grid Accent 1" +tbl = doc.add_table(rows=1, cols=2) +tbl.style = "Light Grid Accent 1" tbl.rows[0].cells[0].text, tbl.rows[0].cells[1].text = "Metric", "Value" for k, v in [("Revenue", "1.2M"), ("Growth", "15%")]: - c = tbl.add_row().cells; c[0].text, c[1].text = k, v + c = tbl.add_row().cells + c[0].text, c[1].text = k, v -doc.add_picture("chart.png", width=Inches(5)) # image, scaled to width +doc.add_picture("chart.png", width=Inches(5)) # image, scaled to width doc.add_page_break() doc.save("out.docx") ``` @@ -79,8 +84,9 @@ Rules: ```python from docx.shared import Inches + sec = doc.sections[0] -sec.page_width, sec.page_height = Inches(8.5), Inches(11) # Letter +sec.page_width, sec.page_height = Inches(8.5), Inches(11) # Letter sec.top_margin = sec.bottom_margin = Inches(1) sec.header.paragraphs[0].text = "Confidential" ``` @@ -90,14 +96,22 @@ Page-number fields aren't in the python-docx API; inject the field XML into a fo ```python from docx.oxml.ns import qn from docx.oxml import OxmlElement + + def add_page_number(paragraph): for t in ("begin", "instr", "end"): r = OxmlElement("w:r") if t == "instr": - fld = OxmlElement("w:instrText"); fld.set(qn("xml:space"), "preserve"); fld.text = "PAGE" + fld = OxmlElement("w:instrText") + fld.set(qn("xml:space"), "preserve") + fld.text = "PAGE" else: - fld = OxmlElement("w:fldChar"); fld.set(qn("w:fldCharType"), t) - r.append(fld); paragraph._p.append(r) + fld = OxmlElement("w:fldChar") + fld.set(qn("w:fldCharType"), t) + r.append(fld) + paragraph._p.append(r) + + add_page_number(doc.sections[0].footer.paragraphs[0]) ``` @@ -136,12 +150,16 @@ Only when python-docx can't express it: **tracked changes, comments, exact-fidel ```python import zipfile + src, dst = "in.docx", "out.docx" -with zipfile.ZipFile(src) as z: xml = z.read("word/document.xml").decode("utf-8") -xml = xml.replace("OLD", "NEW") # or splice tracked-change elements (below) +with zipfile.ZipFile(src) as z: + xml = z.read("word/document.xml").decode("utf-8") +xml = xml.replace("OLD", "NEW") # or splice tracked-change elements (below) with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout: for item in zin.infolist(): - data = xml.encode("utf-8") if item.filename == "word/document.xml" else zin.read(item.filename) + data = ( + xml.encode("utf-8") if item.filename == "word/document.xml" else zin.read(item.filename) + ) zout.writestr(item, data) ``` @@ -180,7 +198,9 @@ Always confirm the file reopens cleanly — a silent corruption is the most comm ```python from docx import Document -d = Document("out.docx"); print(len(d.paragraphs), "paragraphs OK") + +d = Document("out.docx") +print(len(d.paragraphs), "paragraphs OK") ``` For raw-OOXML edits also run `python -c "import zipfile; zipfile.ZipFile('out.docx').testzip()"` and well-formedness-check each edited XML part with `lxml.etree.parse`. diff --git a/deeptutor/skills/builtin/pdf/SKILL.md b/deeptutor/skills/builtin/pdf/SKILL.md index 777e4ac837..3d0ee7b500 100644 --- a/deeptutor/skills/builtin/pdf/SKILL.md +++ b/deeptutor/skills/builtin/pdf/SKILL.md @@ -26,11 +26,12 @@ After `exec` completes, use the Generated artifacts URL from the tool result in ```python import pdfplumber + with pdfplumber.open("in.pdf") as pdf: for i, page in enumerate(pdf.pages, 1): print(f"--- page {i} ---") - print(page.extract_text() or "") # layout-aware text - for t in page.extract_tables(): # list of tables; each is list[row] + print(page.extract_text() or "") # layout-aware text + for t in page.extract_tables(): # list of tables; each is list[row] for row in t: print(row) ``` @@ -38,6 +39,7 @@ with pdfplumber.open("in.pdf") as pdf: Tables → DataFrame/Excel: ```python import pdfplumber, pandas as pd + frames = [] with pdfplumber.open("in.pdf") as pdf: for page in pdf.pages: @@ -50,8 +52,12 @@ if frames: Messy tables: pass strategies, or crop a region with `page.within_bbox((x0, top, x1, bottom))` first: ```python -ts = {"vertical_strategy": "lines", "horizontal_strategy": "lines", - "snap_tolerance": 3, "intersection_tolerance": 15} +ts = { + "vertical_strategy": "lines", + "horizontal_strategy": "lines", + "snap_tolerance": 3, + "intersection_tolerance": 15, +} page.extract_tables(ts) ``` @@ -76,11 +82,16 @@ w.write("merged.pdf") # Split: one file per page r = PdfReader("in.pdf") for i, p in enumerate(r.pages, 1): - w = PdfWriter(); w.add_page(p); w.write(f"page_{i}.pdf") + w = PdfWriter() + w.add_page(p) + w.write(f"page_{i}.pdf") # Rotate page 0 by 90 degrees clockwise -r = PdfReader("in.pdf"); w = PdfWriter() -r.pages[0].rotate(90); w.add_page(r.pages[0]); w.write("rotated.pdf") +r = PdfReader("in.pdf") +w = PdfWriter() +r.pages[0].rotate(90) +w.add_page(r.pages[0]) +w.write("rotated.pdf") ``` - **Metadata**: `PdfReader("in.pdf").metadata` (`.title`, `.author`, ...). @@ -91,10 +102,13 @@ r.pages[0].rotate(90); w.add_page(r.pages[0]); w.write("rotated.pdf") Watermark (stamp one page over every page): ```python from pypdf import PdfReader, PdfWriter + wm = PdfReader("stamp.pdf").pages[0] -r = PdfReader("in.pdf"); w = PdfWriter() +r = PdfReader("in.pdf") +w = PdfWriter() for p in r.pages: - p.merge_page(wm); w.add_page(p) + p.merge_page(wm) + w.add_page(p) w.write("stamped.pdf") ``` @@ -108,15 +122,20 @@ from reportlab.lib import colors from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle styles = getSampleStyleSheet() -story = [Paragraph("Report Title", styles["Title"]), Spacer(1, 12), - Paragraph("Body text. " * 20, styles["Normal"])] +story = [ + Paragraph("Report Title", styles["Title"]), + Spacer(1, 12), + Paragraph("Body text. " * 20, styles["Normal"]), +] data = [["Product", "Q1", "Q2"], ["Widgets", "120", "135"]] tbl = Table(data) -tbl.setStyle(TableStyle([ - ("BACKGROUND", (0, 0), (-1, 0), colors.grey), - ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke), - ("GRID", (0, 0), (-1, -1), 0.5, colors.black), -])) +tbl.setStyle( + TableStyle([ + ("BACKGROUND", (0, 0), (-1, 0), colors.grey), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke), + ("GRID", (0, 0), (-1, -1), 0.5, colors.black), + ]) +) story += [Spacer(1, 12), tbl] SimpleDocTemplate("out.pdf", pagesize=letter).build(story) ``` @@ -132,17 +151,18 @@ import os from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont + def register_cjk_font(name="CJK"): # TrueType ONLY — reportlab cannot embed CFF/OpenType outlines, so a .otf # like Noto Sans CJK fails with "postscript outlines are not supported". for path in [ - "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # Linux sandbox (fonts-wqy-zenhei) + "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # Linux sandbox (fonts-wqy-zenhei) "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", - "/System/Library/Fonts/STHeiti Light.ttc", # macOS + "/System/Library/Fonts/STHeiti Light.ttc", # macOS "/System/Library/Fonts/Hiragino Sans GB.ttc", "/System/Library/Fonts/Supplemental/Songti.ttc", "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", - "C:/Windows/Fonts/msyh.ttc", # Windows + "C:/Windows/Fonts/msyh.ttc", # Windows ]: if os.path.exists(path): try: @@ -152,9 +172,10 @@ def register_cjk_font(name="CJK"): continue raise RuntimeError("No CJK-capable TrueType font found — do not emit tofu; say so.") + font = register_cjk_font() styles = getSampleStyleSheet() -for s in styles.byName.values(): # make the CJK font the default everywhere +for s in styles.byName.values(): # make the CJK font the default everywhere s.fontName = font # Tables don't read the stylesheet — set the font in the TableStyle too: # ("FONTNAME", (0, 0), (-1, -1), font) @@ -172,6 +193,7 @@ Markdown/HTML → PDF needs an external converter (`soffice`/`pandoc`) that is u First detect whether the PDF has real fillable (AcroForm) fields: ```python from pypdf import PdfReader + fields = PdfReader("form.pdf").get_fields() print("fillable" if fields else "flat (no fields)") ``` @@ -179,15 +201,16 @@ print("fillable" if fields else "flat (no fields)") **Fillable** — inspect field names/types, then fill and write: ```python from pypdf import PdfReader, PdfWriter + r = PdfReader("form.pdf") for name, f in r.get_fields().items(): - print(name, f.get("/FT"), f.get("/_States_")) # /Tx text, /Btn checkbox/radio, /Ch choice + print(name, f.get("/FT"), f.get("/_States_")) # /Tx text, /Btn checkbox/radio, /Ch choice w = PdfWriter(clone_from=r) -values = {"first_name": "Bart", "agree": "/Yes"} # checkbox/radio: use its on-state, NOT True/False +values = {"first_name": "Bart", "agree": "/Yes"} # checkbox/radio: use its on-state, NOT True/False for page in w.pages: w.update_page_form_field_values(page, values, auto_regenerate=False) -w.set_need_appearances_writer(True) # force viewers to render the values +w.set_need_appearances_writer(True) # force viewers to render the values w.write("filled.pdf") ``` Checkbox/radio values are on-state strings, not booleans — read the field's `/_States_` (e.g. `/Yes`, `/On`); `/Off` clears it. @@ -195,24 +218,36 @@ Checkbox/radio values are on-state strings, not booleans — read the field's `/ **Flat form (no fields)** — overlay text with `FreeText` annotations at PDF coordinates. Get real coordinates from the layout with pdfplumber instead of guessing: ```python import pdfplumber + with pdfplumber.open("form.pdf") as pdf: pg = pdf.pages[0] - for wd in pg.extract_words(): # each has x0, top, x1, bottom (TOP-left origin!) + for wd in pg.extract_words(): # each has x0, top, x1, bottom (TOP-left origin!) print(wd["text"], wd["x0"], wd["top"]) - for rc in pg.rects: # small squares are likely checkboxes + for rc in pg.rects: # small squares are likely checkboxes print("rect", rc["x0"], rc["top"], rc["x1"], rc["bottom"]) ``` pdfplumber `top` is measured from the page top; pypdf rects are bottom-left, so convert: `pdf_y = page_height - top`. Place text just right of the matching label: ```python from pypdf import PdfReader, PdfWriter from pypdf.annotations import FreeText -r = PdfReader("form.pdf"); w = PdfWriter(); w.append(r) + +r = PdfReader("form.pdf") +w = PdfWriter() +w.append(r) h = float(r.pages[0].mediabox.height) top = 700 # pdfplumber 'top' of the label's row -w.add_annotation(page_number=0, annotation=FreeText( - text="Smith", rect=(255, h - top - 14, 720, h - top), # (x0, y0, x1, y1) - font="Helvetica", font_size="10pt", font_color="000000", - border_color=None, background_color=None)) +w.add_annotation( + page_number=0, + annotation=FreeText( + text="Smith", + rect=(255, h - top - 14, 720, h - top), # (x0, y0, x1, y1) + font="Helvetica", + font_size="10pt", + font_color="000000", + border_color=None, + background_color=None, + ), +) w.write("filled.pdf") ``` Verify: re-open the output and re-read `get_fields()` values (fillable) or re-extract text (overlay) to confirm the values landed. @@ -223,9 +258,10 @@ Verify: re-open the output and re-read `get_fields()` values (fillable) or re-ex ```python import fitz # PyMuPDF + doc = fitz.open("in.pdf") for i, page in enumerate(doc, 1): - page.get_pixmap(dpi=150).save(f"page_{i}.png") # higher dpi = sharper + larger + page.get_pixmap(dpi=150).save(f"page_{i}.png") # higher dpi = sharper + larger ``` `fitz` also extracts text (`page.get_text()`) and can render a sub-region via `page.get_pixmap(clip=fitz.Rect(x0, y0, x1, y1))`. It does **not** OCR — a rendered scanned page is still just pixels (see Scanned PDFs above). diff --git a/deeptutor/skills/builtin/pptx/SKILL.md b/deeptutor/skills/builtin/pptx/SKILL.md index 25fd422d34..ab835910ab 100644 --- a/deeptutor/skills/builtin/pptx/SKILL.md +++ b/deeptutor/skills/builtin/pptx/SKILL.md @@ -34,6 +34,7 @@ the final answer so the user can download the deck. ## Read / extract ```python from pptx import Presentation + prs = Presentation("deck.pptx") print(len(prs.slides), prs.slide_width, prs.slide_height) # EMU dims @@ -41,7 +42,7 @@ for i, slide in enumerate(prs.slides, 1): print(f"--- slide {i} (layout: {slide.slide_layout.name}) ---") for shape in slide.shapes: if shape.has_text_frame: - print(shape.text_frame.text) # \n-joined paragraphs + print(shape.text_frame.text) # \n-joined paragraphs elif shape.has_table: for row in shape.table.rows: print([c.text for c in row.cells]) @@ -67,15 +68,17 @@ for idx, lay in enumerate(prs.slide_layouts): # Title slide s = prs.slides.add_slide(prs.slide_layouts[0]) s.shapes.title.text = "My Deck" -s.placeholders[1].text = "Subtitle" # idx from the listing above +s.placeholders[1].text = "Subtitle" # idx from the listing above # Title + bullets s = prs.slides.add_slide(prs.slide_layouts[1]) s.shapes.title.text = "Agenda" tf = s.placeholders[1].text_frame -tf.text = "First point" # first paragraph +tf.text = "First point" # first paragraph for line, lvl in [("Second", 0), ("Sub-point", 1)]: - p = tf.add_paragraph(); p.text = line; p.level = lvl + p = tf.add_paragraph() + p.text = line + p.level = lvl prs.save("out.pptx") ``` @@ -85,7 +88,10 @@ indentation/bullets come from the layout via `paragraph.level`. Add a free text box or picture on any slide: ```python tb = s.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(1)) -r = tb.text_frame.paragraphs[0].add_run(); r.text = "Hi"; r.font.size = Pt(28); r.font.bold = True +r = tb.text_frame.paragraphs[0].add_run() +r.text = "Hi" +r.font.size = Pt(28) +r.font.bold = True s.shapes.add_picture("logo.png", Inches(0.5), Inches(0.5), height=Inches(1)) # omit w to keep ratio ``` @@ -95,7 +101,8 @@ Edit at the **run** level to preserve a run's formatting; rewriting ```python for slide in prs.slides: for shape in slide.shapes: - if not shape.has_text_frame: continue + if not shape.has_text_frame: + continue for para in shape.text_frame.paragraphs: for run in para.runs: if "{{NAME}}" in run.text: @@ -110,6 +117,7 @@ python-pptx has no direct setter; swap the bytes of the related image part. Read the picture's `r:embed` rId off its ``, then overwrite the part's blob. ```python from pptx.oxml.ns import qn + for shape in slide.shapes: if shape.shape_type == 13: # MSO_SHAPE_TYPE.PICTURE blip = shape._element.find(".//" + qn("a:blip")) @@ -121,16 +129,19 @@ for shape in slide.shapes: ### Tables and charts ```python from pptx.util import Inches -tbl = s.shapes.add_table(rows=2, cols=2, left=Inches(1), top=Inches(1), - width=Inches(6), height=Inches(2)).table -tbl.cell(0,0).text = "Header" + +tbl = s.shapes.add_table( + rows=2, cols=2, left=Inches(1), top=Inches(1), width=Inches(6), height=Inches(2) +).table +tbl.cell(0, 0).text = "Header" from pptx.chart.data import CategoryChartData from pptx.enum.chart import XL_CHART_TYPE -cd = CategoryChartData(); cd.categories = ["Q1","Q2","Q3"] + +cd = CategoryChartData() +cd.categories = ["Q1", "Q2", "Q3"] cd.add_series("Sales", (4.5, 5.5, 6.2)) -s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(1), Inches(1), - Inches(8), Inches(4.5), cd) +s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(1), Inches(1), Inches(8), Inches(4.5), cd) ``` ## Design (only when the user wants a polished deck, not a data dump) @@ -193,6 +204,7 @@ Minimal text edit by zip surgery (zip members can't be overwritten in place — rebuild the archive, swapping the one part): ```python import zipfile + target = "ppt/slides/slide1.xml" with zipfile.ZipFile("in.pptx") as zin: xml = zin.read(target).decode().replace("Old title", "New title") diff --git a/deeptutor/skills/builtin/xlsx/SKILL.md b/deeptutor/skills/builtin/xlsx/SKILL.md index e9e284c257..0a37f02e46 100644 --- a/deeptutor/skills/builtin/xlsx/SKILL.md +++ b/deeptutor/skills/builtin/xlsx/SKILL.md @@ -57,9 +57,10 @@ Pick by what the deliverable needs: ```python import pandas as pd -df = pd.read_excel("in.xlsx") # first sheet -sheets = pd.read_excel("in.xlsx", sheet_name=None) # dict of all sheets -df = pd.read_excel("in.xlsx", dtype={"id": str}) # stop id->float coercion + +df = pd.read_excel("in.xlsx") # first sheet +sheets = pd.read_excel("in.xlsx", sheet_name=None) # dict of all sheets +df = pd.read_excel("in.xlsx", dtype={"id": str}) # stop id->float coercion ``` To read **computed results** of formulas (not the formula text), use openpyxl @@ -67,8 +68,9 @@ with `data_only=True` — returns the value Excel last cached: ```python from openpyxl import load_workbook + wb = load_workbook("in.xlsx", data_only=True) -val = wb["Sheet1"]["B10"].value # None if Excel never opened/saved the file +val = wb["Sheet1"]["B10"].value # None if Excel never opened/saved the file ``` Gotcha: never `save()` a workbook loaded with `data_only=True` — that discards @@ -83,19 +85,21 @@ Large file: `load_workbook(path, read_only=True)` streams rows cheaply. from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment -wb = Workbook(); ws = wb.active; ws.title = "Summary" -ws.append(["Region", "Sales"]) # header row +wb = Workbook() +ws = wb.active +ws.title = "Summary" +ws.append(["Region", "Sales"]) # header row for r in [("West", 120), ("East", 95)]: ws.append(r) -ws["B4"] = "=SUM(B2:B3)" # see formula gotcha above +ws["B4"] = "=SUM(B2:B3)" # see formula gotcha above ws["A1"].font = Font(bold=True) ws["A1"].fill = PatternFill("solid", fgColor="DDDDDD") ws["A1"].alignment = Alignment(horizontal="center") -ws["B2"].number_format = "#,##0" # thousands separator +ws["B2"].number_format = "#,##0" # thousands separator ws.column_dimensions["A"].width = 18 -ws.freeze_panes = "A2" # freeze header -wb.create_sheet("Detail") # second sheet +ws.freeze_panes = "A2" # freeze header +wb.create_sheet("Detail") # second sheet wb.save("out.xlsx") ``` @@ -112,7 +116,8 @@ rewrites the whole sheet, losing styles). ```python from openpyxl import load_workbook -wb = load_workbook("in.xlsx") # keep formulas (data_only=False) + +wb = load_workbook("in.xlsx") # keep formulas (data_only=False) ws = wb["Sheet1"] ws["C2"] = "Updated" wb.save("in.xlsx") @@ -129,10 +134,13 @@ affected formulas yourself, or avoid structural shifts in formula-heavy sheets. ```python from openpyxl.chart import BarChart, Reference -ch = BarChart(); ch.title = "Sales" -data = Reference(ws, min_col=2, min_row=1, max_row=3) # include header for title + +ch = BarChart() +ch.title = "Sales" +data = Reference(ws, min_col=2, min_row=1, max_row=3) # include header for title cats = Reference(ws, min_col=1, min_row=2, max_row=3) -ch.add_data(data, titles_from_data=True); ch.set_categories(cats) +ch.add_data(data, titles_from_data=True) +ch.set_categories(cats) ws.add_chart(ch, "E2") ``` LineChart / PieChart / ScatterChart follow the same shape. @@ -145,10 +153,15 @@ that recalc surfaced (`#REF!` bad reference, `#DIV/0!` zero denominator, ```python from openpyxl import load_workbook + wb = load_workbook("out.xlsx", data_only=True) -errs = [f"{s}!{c.coordinate}={c.value}" - for s in wb.sheetnames for row in wb[s].iter_rows() for c in row - if isinstance(c.value, str) and c.value.startswith("#")] +errs = [ + f"{s}!{c.coordinate}={c.value}" + for s in wb.sheetnames + for row in wb[s].iter_rows() + for c in row + if isinstance(c.value, str) and c.value.startswith("#") +] print(errs or "clean") ``` This only catches errors in *cached* values. If you wrote formulas and couldn't @@ -159,7 +172,7 @@ sidesteps this. ## CSV / TSV ```python -df = pd.read_csv("in.csv") # sep="\t" for TSV +df = pd.read_csv("in.csv") # sep="\t" for TSV df.to_csv("out.csv", index=False) ``` For messy input (junk rows, header not on row 1, ragged columns): inspect raw From e73531d6850c98fc253812ab30cf3c451eefb647 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:46:37 -0700 Subject: [PATCH 3/5] chore: trigger CI after skill format --- tests/agents/visualize/test_extract_code_block.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/agents/visualize/test_extract_code_block.py b/tests/agents/visualize/test_extract_code_block.py index c979a243e9..5145daf4db 100644 --- a/tests/agents/visualize/test_extract_code_block.py +++ b/tests/agents/visualize/test_extract_code_block.py @@ -22,3 +22,4 @@ def test_extract_code_block_language_miss_is_empty_for_or_fallback() -> None: assert extract_code_block(raw, "javascript") == "" extracted = extract_code_block(raw, "javascript") or extract_code_block(raw) assert extracted == cfg + From f7f849d866d815a59f249ad2a85f8e680fca7353 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:33:24 -0700 Subject: [PATCH 4/5] test: document extract code block contracts --- tests/agents/visualize/test_extract_code_block.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/agents/visualize/test_extract_code_block.py b/tests/agents/visualize/test_extract_code_block.py index 5145daf4db..0402c235b8 100644 --- a/tests/agents/visualize/test_extract_code_block.py +++ b/tests/agents/visualize/test_extract_code_block.py @@ -6,17 +6,20 @@ def test_extract_code_block_closing_fence_without_leading_newline() -> None: + """Extract a closing fence attached to the final content line.""" raw = "```mermaid\ngraph TD\n A-->B```" assert extract_code_block(raw, "mermaid") == "graph TD\n A-->B" assert extract_code_block(raw) == "graph TD\n A-->B" def test_extract_code_block_normal_fenced_block() -> None: + """Extract a standard fenced block with its requested language.""" raw = "```javascript\nconst x = 1;\n```" assert extract_code_block(raw, "javascript") == "const x = 1;" def test_extract_code_block_language_miss_is_empty_for_or_fallback() -> None: + """Return empty on a language miss so generic extraction can run.""" cfg = '{"type": "bar", "data": {"labels": ["A"], "datasets": [{"data": [1]}]}}' raw = f"```json\n{cfg}\n```\nThanks." assert extract_code_block(raw, "javascript") == "" From 288dcf1571528f47fa44e8a08dd61bf2f3cfcbd8 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:45:04 -0700 Subject: [PATCH 5/5] style: format extract code block tests --- tests/agents/visualize/test_extract_code_block.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/agents/visualize/test_extract_code_block.py b/tests/agents/visualize/test_extract_code_block.py index 0402c235b8..bdbee712e4 100644 --- a/tests/agents/visualize/test_extract_code_block.py +++ b/tests/agents/visualize/test_extract_code_block.py @@ -25,4 +25,3 @@ def test_extract_code_block_language_miss_is_empty_for_or_fallback() -> None: assert extract_code_block(raw, "javascript") == "" extracted = extract_code_block(raw, "javascript") or extract_code_block(raw) assert extracted == cfg -