[{"data":1,"prerenderedAt":2111},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid":2076,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid":2084,"children:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid":2089,"related:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid":2090},{"id":4,"title":5,"body":6,"dateModified":2041,"datePublished":2041,"description":2042,"extension":2043,"faq":2044,"keyword":2053,"kind":2054,"meta":2055,"navigation":276,"path":2064,"seo":2065,"slug":2068,"stem":2069,"topics":2070,"type":2074,"__hash__":2075},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid\u002Findex.md","Fix \"Excel Cannot Open the File Because the File Format or Extension Is Not Valid\"",{"type":7,"value":8,"toc":2027},"minimark",[9,18,28,41,192,197,222,225,229,232,826,829,833,854,860,863,988,1008,1012,1029,1126,1133,1200,1221,1356,1360,1385,1388,1485,1507,1511,1524,1633,1650,1759,1763,1770,1880,1887,1891,1931,1935,1945,1949,1962,1966,1972,1978,1984,1990,1994,2023],[10,11,12,13,17],"p",{},"Your script writes ",[14,15,16],"code",{},"report.xlsx"," without complaint, but Excel will not open it:",[19,20,26],"pre",{"className":21,"code":23,"language":24,"meta":25},[22],"language-text","Excel cannot open the file 'report.xlsx' because the file format or file extension\nis not valid. Verify that the file has not been corrupted and that the file\nextension matches the format of the file.\n","text","",[14,27,23],{"__ignoreMap":25},[10,29,30,31,34,35,40],{},"The message means exactly what its last sentence says. An ",[14,32,33],{},".xlsx"," file is a zip archive of XML parts with a manifest that declares what kind of workbook it is. When the bytes are not a zip archive, the archive is damaged, or the manifest declares a different kind of workbook than the extension promises, Excel stops. Python produces each of those states in a handful of recognisable ways. This page, part of ",[36,37,39],"a",{"href":38},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors",", shows how to tell which one you have and how to fix the code that caused it.",[42,43,51,52,51,56,51,60,51,68,51,74,51,84,51,95,51,100,51,105,51,111,51,116,51,120,51,127,51,132,51,136,51,140,51,143,51,148,51,155,51,160,51,164,51,168,51,171,51,176,51,183,51,188],"svg",{"viewBox":44,"role":45,"ariaLabelledBy":46,"xmlns":49,"style":50},"64 0 632 544","img",[47,48],"fev-layers-t","fev-layers-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:632px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[53,54,55],"title",{"id":47},"What Excel checks before it opens an .xlsx file",[57,58,59],"desc",{"id":48},"Four layers checked in order. First the file must start with the zip signature PK. Then the zip archive must be intact. Then it must contain the Content_Types manifest and a workbook part. Finally the workbook's declared type must match the extension: a macro-enabled type needs .xlsm. Failing any layer produces the file format or extension error.",[61,62],"rect",{"x":63,"y":64,"width":65,"height":66,"fill":67},"64","0","632","544","#ffffff",[24,69,73],{"x":70,"y":71,"style":72},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","A valid .xlsx passes all four",[61,75],{"x":76,"y":77,"width":78,"height":79,"rx":80,"fill":81,"stroke":82,"style":83},"80.0","48.0","600.0","93.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[85,86,88],"g",{"transform":87},"translate(368.0,61.0) scale(1.000)",[89,90],"path",{"d":91,"fill":92,"stroke":93,"style":94},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[24,96,99],{"x":70,"y":97,"style":98},"107.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Starts with the zip signature",[24,101,104],{"x":70,"y":102,"style":103},"127.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","the first bytes are PK\\x03\\x04",[106,107],"line",{"x1":70,"y1":108,"x2":70,"y2":109,"stroke":110,"style":83},"146.0","168.0","var(--brand,#5b5cf0)",[112,113],"polygon",{"points":114,"fill":115},"380.0,172.0 375.0,163.0 385.0,163.0","#5b5cf0",[61,117],{"x":76,"y":118,"width":78,"height":79,"rx":80,"fill":119,"stroke":110,"style":83},"177.0","#f0f4ff",[85,121,123],{"transform":122},"translate(368.0,190.0) scale(1.000)",[89,124],{"d":125,"fill":92,"stroke":126,"style":94},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6","var(--brand-strong,#4338ca)",[24,128,131],{"x":70,"y":129,"style":130},"236.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","The archive is intact",[24,133,135],{"x":70,"y":134,"style":103},"256.0","every member readable, directory at the end",[106,137],{"x1":70,"y1":138,"x2":70,"y2":139,"stroke":110,"style":83},"275.0","297.0",[112,141],{"points":142,"fill":115},"380.0,301.0 375.0,292.0 385.0,292.0",[61,144],{"x":76,"y":145,"width":78,"height":79,"rx":80,"fill":146,"stroke":147,"style":83},"306.0","#d9f4f1","var(--teal,#0f9488)",[85,149,151],{"transform":150},"translate(368.0,319.0) scale(1.000)",[89,152],{"d":153,"fill":92,"stroke":154,"style":94},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--teal-ink,#0b6157)",[24,156,159],{"x":70,"y":157,"style":158},"365.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Has [Content_Types].xml and a workbook part",[24,161,163],{"x":70,"y":162,"style":103},"385.0","the manifest that declares what is inside",[106,165],{"x1":70,"y1":166,"x2":70,"y2":167,"stroke":110,"style":83},"404.0","426.0",[112,169],{"points":170,"fill":115},"380.0,430.0 375.0,421.0 385.0,421.0",[61,172],{"x":76,"y":173,"width":78,"height":79,"rx":80,"fill":174,"stroke":175,"style":83},"435.0","#fdefd8","var(--gold,#b4740a)",[85,177,179],{"transform":178},"translate(368.0,448.0) scale(1.000)",[89,180],{"d":181,"fill":92,"stroke":182,"style":94},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[24,184,187],{"x":70,"y":185,"style":186},"494.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Declared type matches the extension",[24,189,191],{"x":70,"y":190,"style":103},"514.0","macro-enabled content needs .xlsm",[193,194,196],"h2",{"id":195},"prerequisites","Prerequisites",[19,198,202],{"className":199,"code":200,"language":201,"meta":25,"style":25},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash",[14,203,204],{"__ignoreMap":25},[205,206,208,212,216,219],"span",{"class":106,"line":207},1,[205,209,211],{"class":210},"sMTad","pip",[205,213,215],{"class":214},"srMev"," install",[205,217,218],{"class":214}," pandas",[205,220,221],{"class":214}," openpyxl\n",[10,223,224],{},"The examples use a set of small files, each broken in one of the ways described below, plus one good file for comparison.",[193,226,228],{"id":227},"step-1-check-the-file-with-a-script-not-by-eye","Step 1 — Check the file with a script, not by eye",[10,230,231],{},"The fastest diagnosis is a function that walks through the same layers Excel does and says where the file fails. It needs only the standard library:",[19,233,237],{"className":234,"code":235,"language":236,"meta":25,"style":25},"language-python shiki shiki-themes github-light github-dark-high-contrast","import zipfile\nimport re\nfrom pathlib import Path\n\nMACRO = \"application\u002Fvnd.ms-excel.sheet.macroEnabled.main+xml\"\nPLAIN = \"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"\n\ndef check_xlsx(path):\n    path = Path(path)\n    head = path.read_bytes()[:8]\n    if not head:\n        return \"empty file (0 bytes) — the writer was probably never closed\"\n    if not head.startswith(b\"PK\\x03\\x04\"):\n        return f\"not a zip archive — starts with {head!r} (CSV, HTML or text?)\"\n    try:\n        with zipfile.ZipFile(path) as z:\n            bad = z.testzip()\n            names = set(z.namelist())\n            manifest = z.read(\"[Content_Types].xml\").decode() if \"[Content_Types].xml\" in names else \"\"\n    except zipfile.BadZipFile as exc:\n        return f\"damaged zip archive: {exc} — bytes altered in transit or text mode?\"\n    if bad:\n        return f\"damaged member: {bad}\"\n    if \"xl\u002Fworkbook.xml\" not in names or not manifest:\n        return \"zip archive, but not a workbook\"\n    declared = re.search(r'PartName=\"\u002Fxl\u002Fworkbook.xml\" ContentType=\"([^\"]+)\"', manifest)\n    declared = declared.group(1) if declared else \"\"\n    if declared == MACRO and path.suffix.lower() == \".xlsx\":\n        return \"macro-enabled workbook saved with .xlsx — rename to .xlsm or drop the macros\"\n    if declared == PLAIN and path.suffix.lower() == \".xlsm\":\n        return \"plain workbook saved with .xlsm — rename to .xlsx\"\n    return \"ok\"\n\nfor name in (\"good.xlsx\", \"csv_named.xlsx\", \"html_named.xlsx\", \"empty.xlsx\", \"mangled.xlsx\"):\n    print(f\"{name:16} {check_xlsx(name)}\")\n","python",[14,238,239,249,257,271,278,291,302,307,320,332,349,361,370,396,422,431,446,457,471,506,520,540,548,568,590,598,644,669,695,703,726,734,743,748,789],{"__ignoreMap":25},[205,240,241,245],{"class":106,"line":207},[205,242,244],{"class":243},"s-kum","import",[205,246,248],{"class":247},"skGVy"," zipfile\n",[205,250,252,254],{"class":106,"line":251},2,[205,253,244],{"class":243},[205,255,256],{"class":247}," re\n",[205,258,260,263,266,268],{"class":106,"line":259},3,[205,261,262],{"class":243},"from",[205,264,265],{"class":247}," pathlib ",[205,267,244],{"class":243},[205,269,270],{"class":247}," Path\n",[205,272,274],{"class":106,"line":273},4,[205,275,277],{"emptyLinePlaceholder":276},true,"\n",[205,279,281,285,288],{"class":106,"line":280},5,[205,282,284],{"class":283},"sP0c6","MACRO",[205,286,287],{"class":243}," =",[205,289,290],{"class":214}," \"application\u002Fvnd.ms-excel.sheet.macroEnabled.main+xml\"\n",[205,292,294,297,299],{"class":106,"line":293},6,[205,295,296],{"class":283},"PLAIN",[205,298,287],{"class":243},[205,300,301],{"class":214}," \"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"\n",[205,303,305],{"class":106,"line":304},7,[205,306,277],{"emptyLinePlaceholder":276},[205,308,310,313,317],{"class":106,"line":309},8,[205,311,312],{"class":243},"def",[205,314,316],{"class":315},"s_Opv"," check_xlsx",[205,318,319],{"class":247},"(path):\n",[205,321,323,326,329],{"class":106,"line":322},9,[205,324,325],{"class":247},"    path ",[205,327,328],{"class":243},"=",[205,330,331],{"class":247}," Path(path)\n",[205,333,335,338,340,343,346],{"class":106,"line":334},10,[205,336,337],{"class":247},"    head ",[205,339,328],{"class":243},[205,341,342],{"class":247}," path.read_bytes()[:",[205,344,345],{"class":283},"8",[205,347,348],{"class":247},"]\n",[205,350,352,355,358],{"class":106,"line":351},11,[205,353,354],{"class":243},"    if",[205,356,357],{"class":243}," not",[205,359,360],{"class":247}," head:\n",[205,362,364,367],{"class":106,"line":363},12,[205,365,366],{"class":243},"        return",[205,368,369],{"class":214}," \"empty file (0 bytes) — the writer was probably never closed\"\n",[205,371,373,375,377,380,383,386,390,393],{"class":106,"line":372},13,[205,374,354],{"class":243},[205,376,357],{"class":243},[205,378,379],{"class":247}," head.startswith(",[205,381,382],{"class":243},"b",[205,384,385],{"class":214},"\"PK",[205,387,389],{"class":388},"sSjpA","\\x03\\x04",[205,391,392],{"class":214},"\"",[205,394,395],{"class":247},"):\n",[205,397,399,401,404,407,410,413,416,419],{"class":106,"line":398},14,[205,400,366],{"class":243},[205,402,403],{"class":243}," f",[205,405,406],{"class":214},"\"not a zip archive — starts with ",[205,408,409],{"class":388},"{",[205,411,412],{"class":247},"head",[205,414,415],{"class":243},"!r",[205,417,418],{"class":388},"}",[205,420,421],{"class":214}," (CSV, HTML or text?)\"\n",[205,423,425,428],{"class":106,"line":424},15,[205,426,427],{"class":243},"    try",[205,429,430],{"class":247},":\n",[205,432,434,437,440,443],{"class":106,"line":433},16,[205,435,436],{"class":243},"        with",[205,438,439],{"class":247}," zipfile.ZipFile(path) ",[205,441,442],{"class":243},"as",[205,444,445],{"class":247}," z:\n",[205,447,449,452,454],{"class":106,"line":448},17,[205,450,451],{"class":247},"            bad ",[205,453,328],{"class":243},[205,455,456],{"class":247}," z.testzip()\n",[205,458,460,463,465,468],{"class":106,"line":459},18,[205,461,462],{"class":247},"            names ",[205,464,328],{"class":243},[205,466,467],{"class":283}," set",[205,469,470],{"class":247},"(z.namelist())\n",[205,472,474,477,479,482,485,488,491,494,497,500,503],{"class":106,"line":473},19,[205,475,476],{"class":247},"            manifest ",[205,478,328],{"class":243},[205,480,481],{"class":247}," z.read(",[205,483,484],{"class":214},"\"[Content_Types].xml\"",[205,486,487],{"class":247},").decode() ",[205,489,490],{"class":243},"if",[205,492,493],{"class":214}," \"[Content_Types].xml\"",[205,495,496],{"class":243}," in",[205,498,499],{"class":247}," names ",[205,501,502],{"class":243},"else",[205,504,505],{"class":214}," \"\"\n",[205,507,509,512,515,517],{"class":106,"line":508},20,[205,510,511],{"class":243},"    except",[205,513,514],{"class":247}," zipfile.BadZipFile ",[205,516,442],{"class":243},[205,518,519],{"class":247}," exc:\n",[205,521,523,525,527,530,532,535,537],{"class":106,"line":522},21,[205,524,366],{"class":243},[205,526,403],{"class":243},[205,528,529],{"class":214},"\"damaged zip archive: ",[205,531,409],{"class":388},[205,533,534],{"class":247},"exc",[205,536,418],{"class":388},[205,538,539],{"class":214}," — bytes altered in transit or text mode?\"\n",[205,541,543,545],{"class":106,"line":542},22,[205,544,354],{"class":243},[205,546,547],{"class":247}," bad:\n",[205,549,551,553,555,558,560,563,565],{"class":106,"line":550},23,[205,552,366],{"class":243},[205,554,403],{"class":243},[205,556,557],{"class":214},"\"damaged member: ",[205,559,409],{"class":388},[205,561,562],{"class":247},"bad",[205,564,418],{"class":388},[205,566,567],{"class":214},"\"\n",[205,569,571,573,576,578,580,582,585,587],{"class":106,"line":570},24,[205,572,354],{"class":243},[205,574,575],{"class":214}," \"xl\u002Fworkbook.xml\"",[205,577,357],{"class":243},[205,579,496],{"class":243},[205,581,499],{"class":247},[205,583,584],{"class":243},"or",[205,586,357],{"class":243},[205,588,589],{"class":247}," manifest:\n",[205,591,593,595],{"class":106,"line":592},25,[205,594,366],{"class":243},[205,596,597],{"class":214}," \"zip archive, but not a workbook\"\n",[205,599,601,604,606,609,612,615,618,621,624,627,629,632,635,638,641],{"class":106,"line":600},26,[205,602,603],{"class":247},"    declared ",[205,605,328],{"class":243},[205,607,608],{"class":247}," re.search(",[205,610,611],{"class":243},"r",[205,613,614],{"class":214},"'PartName=\"\u002Fxl\u002Fworkbook",[205,616,617],{"class":283},".",[205,619,620],{"class":214},"xml\" ContentType=\"",[205,622,623],{"class":283},"([",[205,625,626],{"class":243},"^",[205,628,392],{"class":388},[205,630,631],{"class":283},"]",[205,633,634],{"class":243},"+",[205,636,637],{"class":283},")",[205,639,640],{"class":214},"\"'",[205,642,643],{"class":247},", manifest)\n",[205,645,647,649,651,654,657,660,662,665,667],{"class":106,"line":646},27,[205,648,603],{"class":247},[205,650,328],{"class":243},[205,652,653],{"class":247}," declared.group(",[205,655,656],{"class":283},"1",[205,658,659],{"class":247},") ",[205,661,490],{"class":243},[205,663,664],{"class":247}," declared ",[205,666,502],{"class":243},[205,668,505],{"class":214},[205,670,672,674,676,679,682,685,688,690,693],{"class":106,"line":671},28,[205,673,354],{"class":243},[205,675,664],{"class":247},[205,677,678],{"class":243},"==",[205,680,681],{"class":283}," MACRO",[205,683,684],{"class":243}," and",[205,686,687],{"class":247}," path.suffix.lower() ",[205,689,678],{"class":243},[205,691,692],{"class":214}," \".xlsx\"",[205,694,430],{"class":247},[205,696,698,700],{"class":106,"line":697},29,[205,699,366],{"class":243},[205,701,702],{"class":214}," \"macro-enabled workbook saved with .xlsx — rename to .xlsm or drop the macros\"\n",[205,704,706,708,710,712,715,717,719,721,724],{"class":106,"line":705},30,[205,707,354],{"class":243},[205,709,664],{"class":247},[205,711,678],{"class":243},[205,713,714],{"class":283}," PLAIN",[205,716,684],{"class":243},[205,718,687],{"class":247},[205,720,678],{"class":243},[205,722,723],{"class":214}," \".xlsm\"",[205,725,430],{"class":247},[205,727,729,731],{"class":106,"line":728},31,[205,730,366],{"class":243},[205,732,733],{"class":214}," \"plain workbook saved with .xlsm — rename to .xlsx\"\n",[205,735,737,740],{"class":106,"line":736},32,[205,738,739],{"class":243},"    return",[205,741,742],{"class":214}," \"ok\"\n",[205,744,746],{"class":106,"line":745},33,[205,747,277],{"emptyLinePlaceholder":276},[205,749,751,754,757,760,763,766,769,772,774,777,779,782,784,787],{"class":106,"line":750},34,[205,752,753],{"class":243},"for",[205,755,756],{"class":247}," name ",[205,758,759],{"class":243},"in",[205,761,762],{"class":247}," (",[205,764,765],{"class":214},"\"good.xlsx\"",[205,767,768],{"class":247},", ",[205,770,771],{"class":214},"\"csv_named.xlsx\"",[205,773,768],{"class":247},[205,775,776],{"class":214},"\"html_named.xlsx\"",[205,778,768],{"class":247},[205,780,781],{"class":214},"\"empty.xlsx\"",[205,783,768],{"class":247},[205,785,786],{"class":214},"\"mangled.xlsx\"",[205,788,395],{"class":247},[205,790,792,795,798,801,803,805,808,811,813,816,819,821,823],{"class":106,"line":791},35,[205,793,794],{"class":283},"    print",[205,796,797],{"class":247},"(",[205,799,800],{"class":243},"f",[205,802,392],{"class":214},[205,804,409],{"class":388},[205,806,807],{"class":247},"name",[205,809,810],{"class":243},":16",[205,812,418],{"class":388},[205,814,815],{"class":388}," {",[205,817,818],{"class":247},"check_xlsx(name)",[205,820,418],{"class":388},[205,822,392],{"class":214},[205,824,825],{"class":247},")\n",[10,827,828],{},"Each file in the output fails at a different layer, and each layer points at a different mistake in the writing code. The steps below take them in turn.",[193,830,832],{"id":831},"step-2-text-saved-under-an-excel-name","Step 2 — Text saved under an Excel name",[10,834,835,838,839,842,843,846,847,850,851,853],{},[14,836,837],{},"csv_named.xlsx"," starts with ",[14,840,841],{},"b'region,s'"," and ",[14,844,845],{},"html_named.xlsx"," with ",[14,848,849],{},"b'\u003Ctable b'",". Both are text files wearing an ",[14,852,33],{}," extension. In a script this is usually a one-word slip:",[19,855,858],{"className":856,"code":857,"language":24,"meta":25},[22],"df.to_csv(\"report.xlsx\")        # writes CSV text, whatever the name says\ndf.to_html(\"report.xlsx\")       # writes an HTML table\n",[14,859,857],{"__ignoreMap":25},[10,861,862],{},"The same state arrives from outside: a download whose server returned an HTML error or login page instead of the file, saved under the name you asked for. Fix the writer call, or keep the extension honest:",[19,864,866],{"className":234,"code":865,"language":236,"meta":25,"style":25},"import pandas as pd\n\ndf = pd.DataFrame({\"region\": [\"North\", \"South\"], \"sales\": [410, 388]})\ndf.to_excel(\"report.xlsx\", index=False)     # a real workbook\ndf.to_csv(\"report.csv\", index=False)        # or a CSV with a CSV name\nprint(check_xlsx(\"report.xlsx\"))\n",[14,867,868,880,884,927,953,975],{"__ignoreMap":25},[205,869,870,872,875,877],{"class":106,"line":207},[205,871,244],{"class":243},[205,873,874],{"class":247}," pandas ",[205,876,442],{"class":243},[205,878,879],{"class":247}," pd\n",[205,881,882],{"class":106,"line":251},[205,883,277],{"emptyLinePlaceholder":276},[205,885,886,889,891,894,897,900,903,905,908,911,914,916,919,921,924],{"class":106,"line":259},[205,887,888],{"class":247},"df ",[205,890,328],{"class":243},[205,892,893],{"class":247}," pd.DataFrame({",[205,895,896],{"class":214},"\"region\"",[205,898,899],{"class":247},": [",[205,901,902],{"class":214},"\"North\"",[205,904,768],{"class":247},[205,906,907],{"class":214},"\"South\"",[205,909,910],{"class":247},"], ",[205,912,913],{"class":214},"\"sales\"",[205,915,899],{"class":247},[205,917,918],{"class":283},"410",[205,920,768],{"class":247},[205,922,923],{"class":283},"388",[205,925,926],{"class":247},"]})\n",[205,928,929,932,935,937,941,943,946,949],{"class":106,"line":273},[205,930,931],{"class":247},"df.to_excel(",[205,933,934],{"class":214},"\"report.xlsx\"",[205,936,768],{"class":247},[205,938,940],{"class":939},"sa561","index",[205,942,328],{"class":243},[205,944,945],{"class":283},"False",[205,947,948],{"class":247},")     ",[205,950,952],{"class":951},"s-wDw","# a real workbook\n",[205,954,955,958,961,963,965,967,969,972],{"class":106,"line":280},[205,956,957],{"class":247},"df.to_csv(",[205,959,960],{"class":214},"\"report.csv\"",[205,962,768],{"class":247},[205,964,940],{"class":939},[205,966,328],{"class":243},[205,968,945],{"class":283},[205,970,971],{"class":247},")        ",[205,973,974],{"class":951},"# or a CSV with a CSV name\n",[205,976,977,980,983,985],{"class":106,"line":293},[205,978,979],{"class":283},"print",[205,981,982],{"class":247},"(check_xlsx(",[205,984,934],{"class":214},[205,986,987],{"class":247},"))\n",[10,989,990,991,994,995,998,999,1002,1003,1007],{},"For downloads, check the response before saving: a ",[14,992,993],{},"Content-Type"," of ",[14,996,997],{},"text\u002Fhtml",", or a body that does not start with ",[14,1000,1001],{},"PK",", means the server did not send a workbook. ",[36,1004,1006],{"href":1005},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python\u002F","Read an Excel File from a URL or Bytes in Python"," shows the checks.",[193,1009,1011],{"id":1010},"step-3-an-empty-file-from-a-writer-that-was-never-closed","Step 3 — An empty file from a writer that was never closed",[10,1013,1014,1017,1018,1021,1022,1025,1026,1028],{},[14,1015,1016],{},"empty.xlsx"," is zero bytes. pandas' ",[14,1019,1020],{},"ExcelWriter"," and xlsxwriter build the workbook in memory and write it only when they are closed. The file appears on disk as soon as the writer opens it, so a script that crashes, returns early or simply forgets ",[14,1023,1024],{},"close()"," leaves an empty ",[14,1027,33],{}," behind:",[19,1030,1032],{"className":234,"code":1031,"language":236,"meta":25,"style":25},"import os\n\nwriter = pd.ExcelWriter(\"forgot_to_close.xlsx\", engine=\"openpyxl\")\ndf.to_excel(writer, index=False)\nprint(os.path.getsize(\"forgot_to_close.xlsx\"))   # 0 — nothing written yet\nwriter.close()\nprint(os.path.getsize(\"forgot_to_close.xlsx\") > 0, check_xlsx(\"forgot_to_close.xlsx\"))\n",[14,1033,1034,1041,1045,1070,1083,1098,1103],{"__ignoreMap":25},[205,1035,1036,1038],{"class":106,"line":207},[205,1037,244],{"class":243},[205,1039,1040],{"class":247}," os\n",[205,1042,1043],{"class":106,"line":251},[205,1044,277],{"emptyLinePlaceholder":276},[205,1046,1047,1050,1052,1055,1058,1060,1063,1065,1068],{"class":106,"line":259},[205,1048,1049],{"class":247},"writer ",[205,1051,328],{"class":243},[205,1053,1054],{"class":247}," pd.ExcelWriter(",[205,1056,1057],{"class":214},"\"forgot_to_close.xlsx\"",[205,1059,768],{"class":247},[205,1061,1062],{"class":939},"engine",[205,1064,328],{"class":243},[205,1066,1067],{"class":214},"\"openpyxl\"",[205,1069,825],{"class":247},[205,1071,1072,1075,1077,1079,1081],{"class":106,"line":273},[205,1073,1074],{"class":247},"df.to_excel(writer, ",[205,1076,940],{"class":939},[205,1078,328],{"class":243},[205,1080,945],{"class":283},[205,1082,825],{"class":247},[205,1084,1085,1087,1090,1092,1095],{"class":106,"line":280},[205,1086,979],{"class":283},[205,1088,1089],{"class":247},"(os.path.getsize(",[205,1091,1057],{"class":214},[205,1093,1094],{"class":247},"))   ",[205,1096,1097],{"class":951},"# 0 — nothing written yet\n",[205,1099,1100],{"class":106,"line":293},[205,1101,1102],{"class":247},"writer.close()\n",[205,1104,1105,1107,1109,1111,1113,1116,1119,1122,1124],{"class":106,"line":304},[205,1106,979],{"class":283},[205,1108,1089],{"class":247},[205,1110,1057],{"class":214},[205,1112,659],{"class":247},[205,1114,1115],{"class":243},">",[205,1117,1118],{"class":283}," 0",[205,1120,1121],{"class":247},", check_xlsx(",[205,1123,1057],{"class":214},[205,1125,987],{"class":247},[10,1127,1128,1129,1132],{},"Use a ",[14,1130,1131],{},"with"," block so the writer is closed however the code exits. If the writing can fail half-way, write to a temporary name and rename it into place only after the block completes, so a failed run never leaves a broken file where a reader expects a good one:",[19,1134,1136],{"className":234,"code":1135,"language":236,"meta":25,"style":25},"tmp = \"report.tmp.xlsx\"\nwith pd.ExcelWriter(tmp, engine=\"openpyxl\") as writer:\n    df.to_excel(writer, index=False)\nos.replace(tmp, \"report.xlsx\")\nprint(check_xlsx(\"report.xlsx\"))\n",[14,1137,1138,1148,1168,1181,1190],{"__ignoreMap":25},[205,1139,1140,1143,1145],{"class":106,"line":207},[205,1141,1142],{"class":247},"tmp ",[205,1144,328],{"class":243},[205,1146,1147],{"class":214}," \"report.tmp.xlsx\"\n",[205,1149,1150,1152,1155,1157,1159,1161,1163,1165],{"class":106,"line":251},[205,1151,1131],{"class":243},[205,1153,1154],{"class":247}," pd.ExcelWriter(tmp, ",[205,1156,1062],{"class":939},[205,1158,328],{"class":243},[205,1160,1067],{"class":214},[205,1162,659],{"class":247},[205,1164,442],{"class":243},[205,1166,1167],{"class":247}," writer:\n",[205,1169,1170,1173,1175,1177,1179],{"class":106,"line":259},[205,1171,1172],{"class":247},"    df.to_excel(writer, ",[205,1174,940],{"class":939},[205,1176,328],{"class":243},[205,1178,945],{"class":283},[205,1180,825],{"class":247},[205,1182,1183,1186,1188],{"class":106,"line":273},[205,1184,1185],{"class":247},"os.replace(tmp, ",[205,1187,934],{"class":214},[205,1189,825],{"class":247},[205,1191,1192,1194,1196,1198],{"class":106,"line":280},[205,1193,979],{"class":283},[205,1195,982],{"class":247},[205,1197,934],{"class":214},[205,1199,987],{"class":247},[10,1201,1202,1203,1205,1206,1209,1210,846,1213,1216,1217,1220],{},"Keep ",[14,1204,33],{}," at the end of the temporary name. pandas checks the extension even when you pass ",[14,1207,1208],{},"engine=",", and refuses a name like ",[14,1211,1212],{},"report.xlsx.part",[14,1214,1215],{},"ValueError: Invalid extension for engine",". ",[14,1218,1219],{},"os.replace"," is atomic on the same filesystem, so a reader sees either the old report or the complete new one, never half of each.",[42,1222,51,1228,51,1231,51,1234,51,1239,51,1244,51,1250,51,1253,51,1256,51,1259,51,1263,51,1268,51,1270,51,1272,51,1277,51,1280,51,1288,51,1294,51,1298,51,1301,51,1306,51,1311,51,1314,51,1317,51,1321,51,1325,51,1328,51,1331,51,1336,51,1341,51,1344,51,1347,51,1352],{"viewBox":1223,"role":45,"ariaLabelledBy":1224,"xmlns":49,"style":1227},"4 0 752 340",[1225,1226],"fev-close-t","fev-close-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[53,1229,1230],{"id":1225},"Why an unclosed writer leaves a 0-byte file",[57,1232,1233],{"id":1226},"A sequence between the script, the ExcelWriter and the file on disk. Creating the writer creates an empty file. to_excel adds data to the workbook in memory only. The workbook bytes are written to the file only when close is called or the with block ends; if the script stops before that, the file stays at 0 bytes.",[61,1235],{"x":1236,"y":64,"width":1237,"height":1238,"fill":67},"4","752","340",[61,1240],{"x":1241,"y":1242,"width":1243,"height":77,"rx":80,"fill":119,"stroke":110,"style":83},"20.0","16.0","180.0",[24,1245,1249],{"x":1246,"y":1247,"style":1248},"110.0","44.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","script",[61,1251],{"x":1252,"y":1242,"width":1243,"height":77,"rx":80,"fill":81,"stroke":82,"style":83},"290.0",[24,1254,1020],{"x":70,"y":1247,"style":1255},"font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[61,1257],{"x":1258,"y":1242,"width":1243,"height":77,"rx":80,"fill":81,"stroke":82,"style":83},"560.0",[24,1260,1262],{"x":1261,"y":1247,"style":1255},"650.0","file on disk",[106,1264],{"x1":1246,"y1":1265,"x2":1246,"y2":1266,"stroke":82,"style":1267},"64.0","324.0","stroke-width:1.5px;stroke-dasharray:5 4",[106,1269],{"x1":70,"y1":1265,"x2":70,"y2":1266,"stroke":82,"style":1267},[106,1271],{"x1":1261,"y1":1265,"x2":1261,"y2":1266,"stroke":82,"style":1267},[106,1273],{"x1":1274,"y1":1275,"x2":1276,"y2":1275,"stroke":110,"style":83},"115.0","104.0","371.0",[112,1278],{"points":1279,"fill":115},"375.0,104.0 366.0,109.0 366.0,99.0",[61,1281],{"x":1282,"y":1283,"width":1284,"height":1285,"rx":1286,"fill":67,"stroke":82,"style":1287},"177.5","75.0","134.9","21.0","10","stroke-width:1.2px",[24,1289,1293],{"x":1290,"y":1291,"style":1292},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. ExcelWriter(path)",[106,1295],{"x1":162,"y1":1296,"x2":1297,"y2":1296,"stroke":110,"style":83},"152.0","641.0",[112,1299],{"points":1300,"fill":115},"645.0,152.0 636.0,157.0 636.0,147.0",[61,1302],{"x":1303,"y":1304,"width":1305,"height":1285,"rx":1286,"fill":67,"stroke":82,"style":1287},"438.1","123.0","153.8",[24,1307,1310],{"x":1308,"y":1309,"style":1292},"515.0","137.5","2. create file — 0 bytes",[106,1312],{"x1":1274,"y1":1313,"x2":1276,"y2":1313,"stroke":110,"style":83},"200.0",[112,1315],{"points":1316,"fill":115},"375.0,200.0 366.0,205.0 366.0,195.0",[61,1318],{"x":1296,"y":1319,"width":1320,"height":1285,"rx":1286,"fill":67,"stroke":82,"style":1287},"171.0","186.1",[24,1322,1324],{"x":1290,"y":1323,"style":1292},"185.5","3. to_excel() — memory only",[106,1326],{"x1":1274,"y1":1327,"x2":1276,"y2":1327,"stroke":110,"style":83},"248.0",[112,1329],{"points":1330,"fill":115},"375.0,248.0 366.0,253.0 366.0,243.0",[61,1332],{"x":1333,"y":1334,"width":1335,"height":1285,"rx":1286,"fill":146,"stroke":147,"style":1287},"170.0","219.0","150.0",[24,1337,1340],{"x":1290,"y":1338,"style":1339},"233.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","4. close() \u002F end of with",[106,1342],{"x1":162,"y1":1343,"x2":1297,"y2":1343,"stroke":110,"style":83},"296.0",[112,1345],{"points":1346,"fill":115},"645.0,296.0 636.0,301.0 636.0,291.0",[61,1348],{"x":1349,"y":1350,"width":1351,"height":1285,"rx":1286,"fill":146,"stroke":147,"style":1287},"423.8","267.0","182.4",[24,1353,1355],{"x":1308,"y":1354,"style":1339},"281.5","5. write the workbook bytes",[193,1357,1359],{"id":1358},"step-4-bytes-that-went-through-text-mode","Step 4 — Bytes that went through text mode",[10,1361,1362,1365,1366,1369,1370,1373,1374,1377,1378,1381,1382,1384],{},[14,1363,1364],{},"mangled.xlsx"," starts with the right signature but the archive is damaged. That pattern — correct start, broken body — is what happens when binary data passes through code that treats it as text: a file opened with ",[14,1367,1368],{},"\"w\""," instead of ",[14,1371,1372],{},"\"wb\"",", bytes decoded to a string and encoded back, or a newline conversion that turns every ",[14,1375,1376],{},"\\n"," byte into ",[14,1379,1380],{},"\\r\\n",". The setup for this page produced ",[14,1383,1364],{}," exactly that way.",[10,1386,1387],{},"Every step that touches workbook bytes must handle them as bytes:",[19,1389,1391],{"className":234,"code":1390,"language":236,"meta":25,"style":25},"import io\n\nbuffer = io.BytesIO()\ndf.to_excel(buffer, index=False)\ndata = buffer.getvalue()                 # bytes, not str\n\nwith open(\"from_bytes.xlsx\", \"wb\") as fh:   # \"wb\", never \"w\"\n    fh.write(data)\nprint(check_xlsx(\"from_bytes.xlsx\"))\n",[14,1392,1393,1400,1404,1414,1427,1440,1444,1470,1475],{"__ignoreMap":25},[205,1394,1395,1397],{"class":106,"line":207},[205,1396,244],{"class":243},[205,1398,1399],{"class":247}," io\n",[205,1401,1402],{"class":106,"line":251},[205,1403,277],{"emptyLinePlaceholder":276},[205,1405,1406,1409,1411],{"class":106,"line":259},[205,1407,1408],{"class":247},"buffer ",[205,1410,328],{"class":243},[205,1412,1413],{"class":247}," io.BytesIO()\n",[205,1415,1416,1419,1421,1423,1425],{"class":106,"line":273},[205,1417,1418],{"class":247},"df.to_excel(buffer, ",[205,1420,940],{"class":939},[205,1422,328],{"class":243},[205,1424,945],{"class":283},[205,1426,825],{"class":247},[205,1428,1429,1432,1434,1437],{"class":106,"line":280},[205,1430,1431],{"class":247},"data ",[205,1433,328],{"class":243},[205,1435,1436],{"class":247}," buffer.getvalue()                 ",[205,1438,1439],{"class":951},"# bytes, not str\n",[205,1441,1442],{"class":106,"line":293},[205,1443,277],{"emptyLinePlaceholder":276},[205,1445,1446,1448,1451,1453,1456,1458,1460,1462,1464,1467],{"class":106,"line":304},[205,1447,1131],{"class":243},[205,1449,1450],{"class":283}," open",[205,1452,797],{"class":247},[205,1454,1455],{"class":214},"\"from_bytes.xlsx\"",[205,1457,768],{"class":247},[205,1459,1372],{"class":214},[205,1461,659],{"class":247},[205,1463,442],{"class":243},[205,1465,1466],{"class":247}," fh:   ",[205,1468,1469],{"class":951},"# \"wb\", never \"w\"\n",[205,1471,1472],{"class":106,"line":309},[205,1473,1474],{"class":247},"    fh.write(data)\n",[205,1476,1477,1479,1481,1483],{"class":106,"line":322},[205,1478,979],{"class":283},[205,1480,982],{"class":247},[205,1482,1455],{"class":214},[205,1484,987],{"class":247},[10,1486,1487,1488,1491,1492,1369,1495,1216,1498,1502,1503,1506],{},"Web frameworks are the usual suspects in practice: a handler that returns the bytes as a ",[14,1489,1490],{},"str",", or an HTTP client that reads the response with ",[14,1493,1494],{},".text",[14,1496,1497],{},".content",[36,1499,1501],{"href":1500},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Freturn-an-excel-file-from-a-flask-download-endpoint\u002F","Return an Excel File from a Flask Download Endpoint"," shows a handler that gets this right. When a file is fine on your machine and broken after transfer, compare sizes and a checksum (",[14,1504,1505],{},"hashlib.sha256(data).hexdigest()",") on both ends to find the step that changed it.",[193,1508,1510],{"id":1509},"step-5-a-macro-workbook-saved-as-xlsx","Step 5 — A macro workbook saved as .xlsx",[10,1512,1513,1514,846,1517,1520,1521,1523],{},"The last layer is a mismatch between what the workbook says it is and what its name says. Loading an ",[14,1515,1516],{},".xlsm",[14,1518,1519],{},"keep_vba=True"," keeps the macro project and marks the workbook as macro-enabled; saving it as ",[14,1522,33],{}," writes macro-enabled content under a macro-free name, and Excel refuses:",[19,1525,1527],{"className":234,"code":1526,"language":236,"meta":25,"style":25},"from openpyxl import load_workbook\n\nwb = load_workbook(\"macro_src.xlsm\", keep_vba=True)\nwb.save(\"macro_as.xlsx\")\nprint(check_xlsx(\"macro_as.xlsx\"))\n\nwb = load_workbook(\"macro_src.xlsm\", keep_vba=True)\nwb.save(\"macro_ok.xlsm\")\nprint(check_xlsx(\"macro_ok.xlsm\"))\n",[14,1528,1529,1541,1545,1570,1580,1590,1594,1614,1623],{"__ignoreMap":25},[205,1530,1531,1533,1536,1538],{"class":106,"line":207},[205,1532,262],{"class":243},[205,1534,1535],{"class":247}," openpyxl ",[205,1537,244],{"class":243},[205,1539,1540],{"class":247}," load_workbook\n",[205,1542,1543],{"class":106,"line":251},[205,1544,277],{"emptyLinePlaceholder":276},[205,1546,1547,1550,1552,1555,1558,1560,1563,1565,1568],{"class":106,"line":259},[205,1548,1549],{"class":247},"wb ",[205,1551,328],{"class":243},[205,1553,1554],{"class":247}," load_workbook(",[205,1556,1557],{"class":214},"\"macro_src.xlsm\"",[205,1559,768],{"class":247},[205,1561,1562],{"class":939},"keep_vba",[205,1564,328],{"class":243},[205,1566,1567],{"class":283},"True",[205,1569,825],{"class":247},[205,1571,1572,1575,1578],{"class":106,"line":273},[205,1573,1574],{"class":247},"wb.save(",[205,1576,1577],{"class":214},"\"macro_as.xlsx\"",[205,1579,825],{"class":247},[205,1581,1582,1584,1586,1588],{"class":106,"line":280},[205,1583,979],{"class":283},[205,1585,982],{"class":247},[205,1587,1577],{"class":214},[205,1589,987],{"class":247},[205,1591,1592],{"class":106,"line":293},[205,1593,277],{"emptyLinePlaceholder":276},[205,1595,1596,1598,1600,1602,1604,1606,1608,1610,1612],{"class":106,"line":304},[205,1597,1549],{"class":247},[205,1599,328],{"class":243},[205,1601,1554],{"class":247},[205,1603,1557],{"class":214},[205,1605,768],{"class":247},[205,1607,1562],{"class":939},[205,1609,328],{"class":243},[205,1611,1567],{"class":283},[205,1613,825],{"class":247},[205,1615,1616,1618,1621],{"class":106,"line":309},[205,1617,1574],{"class":247},[205,1619,1620],{"class":214},"\"macro_ok.xlsm\"",[205,1622,825],{"class":247},[205,1624,1625,1627,1629,1631],{"class":106,"line":322},[205,1626,979],{"class":283},[205,1628,982],{"class":247},[205,1630,1620],{"class":214},[205,1632,987],{"class":247},[10,1634,1635,1636,1638,1639,1641,1642,1644,1645,1649],{},"Keep the ",[14,1637,1516],{}," extension when the macros should survive. If they should not, load without ",[14,1640,1562],{}," and save as ",[14,1643,33],{},"; the macro project is dropped and the workbook is declared as a plain one. ",[36,1646,1648],{"href":1647},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fwork-with-macro-enabled-xlsm-files-in-openpyxl\u002F","Work with Macro-Enabled .xlsm Files in openpyxl"," covers both paths.",[42,1651,51,1656,51,1659,51,1662,51,1665,51,1670,51,1674,51,1679,51,1682,51,1686,51,1690,51,1696,51,1698,51,1702,51,1704,51,1707,51,1710,51,1714,51,1716,51,1720,51,1722,51,1725,51,1728,51,1731,51,1733,51,1737,51,1739,51,1742,51,1745,51,1748,51,1750,51,1754,51,1756],{"viewBox":1652,"role":45,"ariaLabelledBy":1653,"xmlns":49,"style":1227},"4 0 752 208",[1654,1655],"fev-causes-t","fev-causes-d",[53,1657,1658],{"id":1654},"Symptoms and the writing mistake behind each",[57,1660,1661],{"id":1655},"A lookup table. A file starting with text such as a comma list or an HTML tag came from to_csv, to_html or an error page saved as .xlsx. A zero-byte file came from a writer never closed. A file that starts with PK but fails the zip check went through text mode. A valid archive declaring macro-enabled content under .xlsx came from saving keep_vba workbooks with the wrong extension.",[61,1663],{"x":1236,"y":64,"width":1237,"height":1664,"fill":67},"208",[61,1666],{"x":1241,"y":1242,"width":1667,"height":1668,"rx":64,"fill":67,"stroke":82,"style":1669},"114.8","40.0","stroke-width:1px",[61,1671],{"x":1672,"y":1242,"width":1673,"height":1668,"rx":64,"fill":119,"stroke":110,"style":1287},"134.8","302.6",[24,1675,1678],{"x":1676,"y":1677,"style":1248},"286.1","41.0","What check_xlsx reports",[61,1680],{"x":1681,"y":1242,"width":1673,"height":1668,"rx":64,"fill":119,"stroke":110,"style":1287},"437.4",[24,1683,1685],{"x":1684,"y":1677,"style":1248},"588.7","Usual cause",[61,1687],{"x":1241,"y":1688,"width":1667,"height":1689,"rx":64,"fill":81,"stroke":82,"style":1669},"56.0","34.0",[24,1691,1695],{"x":1692,"y":1693,"style":1694},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Text or HTML",[61,1697],{"x":1672,"y":1688,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1699,1701],{"x":1676,"y":1700,"style":103},"77.0","not a zip archive",[61,1703],{"x":1681,"y":1688,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1705,1706],{"x":1684,"y":1700,"style":103},"to_csv \u002F to_html \u002F error page",[61,1708],{"x":1241,"y":1709,"width":1667,"height":1689,"rx":64,"fill":81,"stroke":82,"style":1669},"90.0",[24,1711,1713],{"x":1692,"y":1712,"style":1694},"112.0","Empty",[61,1715],{"x":1672,"y":1709,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1717,1719],{"x":1676,"y":1718,"style":103},"111.0","0 bytes",[61,1721],{"x":1681,"y":1709,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1723,1724],{"x":1684,"y":1718,"style":103},"writer never closed",[61,1726],{"x":1241,"y":1727,"width":1667,"height":1689,"rx":64,"fill":81,"stroke":82,"style":1669},"124.0",[24,1729,1730],{"x":1692,"y":108,"style":1694},"Damaged",[61,1732],{"x":1672,"y":1727,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1734,1736],{"x":1676,"y":1735,"style":103},"145.0","starts with PK, zip broken",[61,1738],{"x":1681,"y":1727,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1740,1741],{"x":1684,"y":1735,"style":103},"bytes handled as text",[61,1743],{"x":1241,"y":1744,"width":1667,"height":1689,"rx":64,"fill":81,"stroke":82,"style":1669},"158.0",[24,1746,1747],{"x":1692,"y":1243,"style":1694},"Wrong type",[61,1749],{"x":1672,"y":1744,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1751,1753],{"x":1676,"y":1752,"style":103},"179.0","macro content, .xlsx name",[61,1755],{"x":1681,"y":1744,"width":1673,"height":1689,"rx":64,"fill":67,"stroke":82,"style":1669},[24,1757,1758],{"x":1684,"y":1752,"style":103},"keep_vba saved as .xlsx",[193,1760,1762],{"id":1761},"step-6-check-files-before-they-leave-the-job","Step 6 — Check files before they leave the job",[10,1764,1765,1766,1769],{},"Put the checker at the end of every job that sends or publishes a workbook, and fail the run if it reports anything but ",[14,1767,1768],{},"ok",". A report that fails loudly at 06:00 is fixed before anyone notices; one that reaches forty inboxes broken is not. A final load with openpyxl adds a check that the XML inside is readable, which the zip-level checks cannot see:",[19,1771,1773],{"className":234,"code":1772,"language":236,"meta":25,"style":25},"def verify_before_sending(path):\n    status = check_xlsx(path)\n    if status != \"ok\":\n        raise RuntimeError(f\"{path}: {status}\")\n    load_workbook(path, read_only=True).close()\n    return path\n\nprint(verify_before_sending(\"report.xlsx\"))\n",[14,1774,1775,1784,1794,1809,1843,1858,1865,1869],{"__ignoreMap":25},[205,1776,1777,1779,1782],{"class":106,"line":207},[205,1778,312],{"class":243},[205,1780,1781],{"class":315}," verify_before_sending",[205,1783,319],{"class":247},[205,1785,1786,1789,1791],{"class":106,"line":251},[205,1787,1788],{"class":247},"    status ",[205,1790,328],{"class":243},[205,1792,1793],{"class":247}," check_xlsx(path)\n",[205,1795,1796,1798,1801,1804,1807],{"class":106,"line":259},[205,1797,354],{"class":243},[205,1799,1800],{"class":247}," status ",[205,1802,1803],{"class":243},"!=",[205,1805,1806],{"class":214}," \"ok\"",[205,1808,430],{"class":247},[205,1810,1811,1814,1817,1819,1821,1823,1825,1827,1829,1832,1834,1837,1839,1841],{"class":106,"line":273},[205,1812,1813],{"class":243},"        raise",[205,1815,1816],{"class":283}," RuntimeError",[205,1818,797],{"class":247},[205,1820,800],{"class":243},[205,1822,392],{"class":214},[205,1824,409],{"class":388},[205,1826,89],{"class":247},[205,1828,418],{"class":388},[205,1830,1831],{"class":214},": ",[205,1833,409],{"class":388},[205,1835,1836],{"class":247},"status",[205,1838,418],{"class":388},[205,1840,392],{"class":214},[205,1842,825],{"class":247},[205,1844,1845,1848,1851,1853,1855],{"class":106,"line":280},[205,1846,1847],{"class":247},"    load_workbook(path, ",[205,1849,1850],{"class":939},"read_only",[205,1852,328],{"class":243},[205,1854,1567],{"class":283},[205,1856,1857],{"class":247},").close()\n",[205,1859,1860,1862],{"class":106,"line":293},[205,1861,739],{"class":243},[205,1863,1864],{"class":247}," path\n",[205,1866,1867],{"class":106,"line":304},[205,1868,277],{"emptyLinePlaceholder":276},[205,1870,1871,1873,1876,1878],{"class":106,"line":309},[205,1872,979],{"class":283},[205,1874,1875],{"class":247},"(verify_before_sending(",[205,1877,934],{"class":214},[205,1879,987],{"class":247},[10,1881,1882,1886],{},[36,1883,1885],{"href":1884},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fvalidate-an-excel-report-before-sending-it\u002F","Validate an Excel Report Before Sending It"," extends this with checks on the report's contents.",[193,1888,1890],{"id":1889},"common-pitfalls","Common pitfalls",[1892,1893,1894,1902,1908,1919],"ul",{},[1895,1896,1897,1901],"li",{},[1898,1899,1900],"strong",{},"Trusting the extension."," It is only a name. Check the first bytes of anything you did not write yourself.",[1895,1903,1904,1907],{},[1898,1905,1906],{},"Catching exceptions around the writer and carrying on."," The file is then left empty or partial. Let the error stop the job, or write to a temporary name.",[1895,1909,1910,1913,1914,842,1917,617],{},[1898,1911,1912],{},"Opening files in text mode \"because it worked for CSV\"."," Binary formats need ",[14,1915,1916],{},"\"rb\"",[14,1918,1372],{},[1895,1920,1921,1930],{},[1898,1922,1923,1924,1926,1927,1929],{},"Renaming ",[14,1925,1516],{}," to ",[14,1928,33],{}," to hide macros."," The content still declares macros; rename changes nothing inside.",[193,1932,1934],{"id":1933},"performance-notes","Performance notes",[10,1936,1937,1938,1941,1942,1944],{},"The checker reads the whole file once and decompresses each member for ",[14,1939,1940],{},"testzip()",", which takes milliseconds for typical reports and about a second for a 50 MB workbook. For very large files in a hot path, skip ",[14,1943,1940],{}," and rely on the signature, manifest and a read-only load, which touch far less data.",[193,1946,1948],{"id":1947},"conclusion","Conclusion",[10,1950,1951,1952,1954,1955,1958,1959,1961],{},"Excel's \"file format or file extension is not valid\" means the bytes do not match the ",[14,1953,33],{}," name: text written by ",[14,1956,1957],{},"to_csv"," or an error page, an empty file from a writer that was never closed, an archive damaged by text-mode handling, or macro-enabled content saved with the wrong extension. Check the file layer by layer, fix the writing code that the failing layer points to, write through a ",[14,1960,1131],{}," block and a temporary name, and verify every workbook before it leaves the job.",[193,1963,1965],{"id":1964},"frequently-asked-questions","Frequently asked questions",[10,1967,1968,1971],{},[1898,1969,1970],{},"My .xlsx file is 0 bytes. What happened?","\nThe writer was created but never closed. pandas ExcelWriter and xlsxwriter only write the workbook when they are closed, so a script that exits early or forgets close() leaves an empty file behind. Use a with block.",[10,1973,1974,1977],{},[1898,1975,1976],{},"Can I just rename a CSV to .xlsx?","\nNo. The extension has to match the content. A CSV is plain text and an .xlsx is a zip archive of XML parts; renaming changes neither. Write a real workbook with to_excel, or keep the .csv extension.",[10,1979,1980,1983],{},[1898,1981,1982],{},"Why does Excel reject a workbook I saved with keep_vba=True?","\nLoading with keep_vba=True keeps the macro project and marks the workbook as macro-enabled. Saving that as .xlsx produces a file whose content says .xlsm, and Excel refuses the mismatch. Save it with an .xlsm extension, or load without keep_vba to drop the macros.",[10,1985,1986,1989],{},[1898,1987,1988],{},"The file opens on my machine but not after it is emailed or downloaded. Why?","\nSomething between you and the recipient treated the bytes as text — a web handler returning a str, a file opened in text mode, or a mail library encoding it wrongly. Compare the file size and a checksum on both ends, and make every step handle the data as bytes.",[193,1991,1993],{"id":1992},"related","Related",[1892,1995,1996,2002,2009,2016],{},[1895,1997,1998,1999,2001],{},"Up one level: ",[36,2000,39],{"href":38}," — every common read and write error.",[1895,2003,2004,2008],{},[36,2005,2007],{"href":2006},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-repaired-content-after-writing-with-python\u002F","Fix \"Excel Found Unreadable Content\" After Writing with Python"," — files Excel opens but has to repair.",[1895,2010,2011,2015],{},[36,2012,2014],{"href":2013},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python\u002F","Fix BadZipFile When Reading an Excel File in Python"," — the same damage seen from the reading side.",[1895,2017,2018,2022],{},[36,2019,2021],{"href":2020},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-an-excel-workbook-in-memory-with-bytesio\u002F","Build an Excel Workbook in Memory with BytesIO"," — handling workbook bytes safely.",[2024,2025,2026],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":25,"searchDepth":251,"depth":251,"links":2028},[2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040],{"id":195,"depth":251,"text":196},{"id":227,"depth":251,"text":228},{"id":831,"depth":251,"text":832},{"id":1010,"depth":251,"text":1011},{"id":1358,"depth":251,"text":1359},{"id":1509,"depth":251,"text":1510},{"id":1761,"depth":251,"text":1762},{"id":1889,"depth":251,"text":1890},{"id":1933,"depth":251,"text":1934},{"id":1947,"depth":251,"text":1948},{"id":1964,"depth":251,"text":1965},{"id":1992,"depth":251,"text":1993},"2026-09-25","Excel refuses a file your Python script produced when its bytes do not match its .xlsx name: CSV or HTML saved under an Excel extension, a writer that was never closed, bytes mangled in text mode, or a macro workbook saved as .xlsx. Diagnose which one with a short checker and fix the writing code.","md",[2045,2047,2049,2051],{"q":1970,"a":2046},"The writer was created but never closed. pandas ExcelWriter and xlsxwriter only write the workbook when they are closed, so a script that exits early or forgets close() leaves an empty file behind. Use a with block.",{"q":1976,"a":2048},"No. The extension has to match the content. A CSV is plain text and an .xlsx is a zip archive of XML parts; renaming changes neither. Write a real workbook with to_excel, or keep the .csv extension.",{"q":1982,"a":2050},"Loading with keep_vba=True keeps the macro project and marks the workbook as macro-enabled. Saving that as .xlsx produces a file whose content says .xlsm, and Excel refuses the mismatch. Save it with an .xlsm extension, or load without keep_vba to drop the macros.",{"q":1988,"a":2052},"Something between you and the recipient treated the bytes as text — a web handler returning a str, a file opened in text mode, or a mail library encoding it wrongly. Compare the file size and a checksum on both ends, and make every step handle the data as bytes.","fix excel cannot open the file because the file format or extension is not valid",null,{"breadcrumb":2056},[2057,2060,2063],{"name":2058,"item":2059},"Home","\u002F",{"name":2061,"item":2062},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":39,"item":38},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid",{"title":2066,"description":2067},"Fix \"File Format or File Extension Is Not Valid\" for Python Excel Output","Why Excel says a Python-generated .xlsx has an invalid format or extension — CSV named .xlsx, empty unclosed writers, text-mode corruption, .xlsm content saved as .xlsx — and how to check files before sending them.","fix-excel-file-format-or-extension-is-not-valid","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-or-extension-is-not-valid\u002Findex",[2071,2072,2073],"troubleshooting","writing-excel","file-formats","how-to","tjCpmBX4dm5ksBtJ6zsLGvt5keOmcXdbXJRL1HhpwfA",[2077,2081],{"title":2078,"path":2079,"stem":2080,"children":-1},"Fix \"Excel File Format Cannot Be Determined\" in pandas","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas\u002Findex",{"title":2007,"path":2082,"stem":2083,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-repaired-content-after-writing-with-python","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-repaired-content-after-writing-with-python\u002Findex",[2085,2087],{"path":2086,"title":2061},"\u002Fgetting-started-with-python-excel-automation",{"path":2088,"title":39},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors",[],[2091,2095,2098,2101,2104,2107],{"path":2092,"title":2093,"kind":2094},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python","Handle Permission Denied When Writing Excel in Python","fix",{"path":2096,"title":2097,"kind":2074},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook","What openpyxl Loses When You Save an Existing Workbook",{"path":2099,"title":2100,"kind":2094},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates","Fix Excel Serial Numbers Showing Instead of Dates",{"path":2102,"title":2103,"kind":2074},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps","Handle Daylight Saving Time in Excel Timestamps",{"path":2105,"title":2106,"kind":2074},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel","Handle Microsoft Graph Throttling in Excel Automation",{"path":2108,"title":2109,"kind":2110},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","Error Handling and Logging in Excel Automation","guide",1790972065642]