[{"data":1,"prerenderedAt":2058},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python":2050},{"id":4,"title":5,"body":6,"dateModified":2023,"datePublished":2023,"description":2024,"extension":2025,"faq":2026,"meta":2035,"navigation":230,"path":2043,"seo":2044,"slug":2046,"stem":2047,"type":2048,"__hash__":2049},"docs\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python\u002Findex.md","Handle COM Errors and Excel Dialog Prompts in Python",{"type":7,"value":8,"toc":2008},"minimark",[9,24,147,152,176,180,194,497,504,508,607,629,819,822,826,829,921,935,939,946,1063,1073,1077,1080,1260,1268,1272,1279,1555,1558,1562,1565,1685,1697,1701,1823,1827,1836,1912,1922,1926,1935,1939,1946,1952,1958,1964,1968,2004],[10,11,12,13,17,18,23],"p",{},"COM failures come in two shapes, and only one of them looks like an error. The first is a\n",[14,15,16],"code",{},"pywintypes.com_error"," carrying a tuple of numbers that mean nothing at a glance. The second is\nworse: no exception at all, because Excel is waiting on a dialog that is invisible and the script\nsimply stops. This guide, part of\n",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002F","Automating Excel with COM and pywin32",",\ncovers decoding the first and preventing the second.",[25,26,34,35,34,39,34,43,34,50,34,60,34,67,34,74,34,79,34,83,34,87,34,92,34,97,34,101,34,104,34,107,34,110,34,119,34,125,34,130,34,135,34,139,34,142],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 201","img",[30,31],"err-two-t","err-two-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"Two failure shapes, only one of which raises",[40,41,42],"desc",{"id":31},"A COM error arrives as an exception carrying a tuple of numbers that needs decoding, while a modal dialog produces no exception at all and blocks the call indefinitely.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","201","#ffffff",[44,51],{"x":52,"y":53,"width":54,"height":55,"rx":56,"fill":57,"stroke":58,"style":59},"20","28","270.0","139","14","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[61,62,66],"text",{"x":63,"y":64,"style":65},"155.0","54","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","com_error",[68,69],"line",{"x1":70,"y1":71,"x2":72,"y2":71,"stroke":58,"style":73},"36","64","274.0","stroke-width:1px",[61,75,78],{"x":63,"y":76,"style":77},"86","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","an exception is raised",[61,80,82],{"x":63,"y":81,"style":77},"109","numbers, not a message",[61,84,86],{"x":63,"y":85,"style":77},"132","decode excepinfo",[44,88],{"x":89,"y":53,"width":54,"height":55,"rx":56,"fill":90,"stroke":91,"style":59},"470.0","#fee8f2","var(--accent,#d81b73)",[61,93,96],{"x":94,"y":64,"style":95},"605.0","font-size:13px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:middle","invisible dialog",[68,98],{"x1":99,"y1":71,"x2":100,"y2":71,"stroke":91,"style":73},"486.0","724.0",[61,102,103],{"x":94,"y":76,"style":77},"no exception at all",[61,105,106],{"x":94,"y":81,"style":77},"the call never returns",[61,108,109],{"x":94,"y":85,"style":77},"prevent, do not catch",[44,111],{"x":112,"y":113,"width":114,"height":115,"rx":116,"fill":117,"stroke":118},"316.0","78.5","128","38","19","#ebebfd","var(--brand,#5b5cf0)",[61,120,124],{"x":121,"y":122,"style":123},"380.0","102.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","two shapes",[68,126],{"x1":127,"y1":128,"x2":129,"y2":128,"stroke":118,"style":59},"295.0","97.5","309.0",[131,132],"polygon",{"points":133,"fill":134},"309.0,97.5 300.0,92.5 300.0,102.5","#5b5cf0",[68,136],{"x1":137,"y1":128,"x2":138,"y2":128,"stroke":118,"style":59},"449.0","463.0",[131,140],{"points":141,"fill":134},"463.0,97.5 454.0,92.5 454.0,102.5",[61,143,146],{"x":121,"y":144,"style":145},"187","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the silent one is the expensive one",[148,149,151],"h2",{"id":150},"prerequisites","Prerequisites",[153,154,159],"pre",{"className":155,"code":156,"language":157,"meta":158,"style":158},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pywin32\n","bash","",[14,160,161],{"__ignoreMap":158},[162,163,165,169,173],"span",{"class":68,"line":164},1,[162,166,168],{"class":167},"sMTad","pip",[162,170,172],{"class":171},"srMev"," install",[162,174,175],{"class":171}," pywin32\n",[148,177,179],{"id":178},"decoding-a-com_error","Decoding a com_error",[10,181,182,183,186,187,190,191,193],{},"The exception's ",[14,184,185],{},"args"," is a four-element tuple: the HRESULT, a short description of it, an\n",[14,188,189],{},"excepinfo"," structure and an argument index. Nearly all the useful information is in ",[14,192,189],{},",\nand nearly all published examples ignore it.",[153,195,199],{"className":196,"code":197,"language":198,"meta":158,"style":158},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pywintypes\nimport win32com.client as win32\n\ndef describe(error: pywintypes.com_error) -> str:\n    hresult, message, excepinfo, argument = error.args\n    detail = \"\"\n    if excepinfo:\n        source = excepinfo[1] or \"\"\n        description = excepinfo[2] or \"\"\n        detail = f\" — {source}: {description}\".rstrip(\": \")\n    return f\"COM 0x{hresult & 0xFFFFFFFF:08X} {message}{detail} (arg {argument})\"\n\ntry:\n    excel.Workbooks.Open(r\"C:\\missing\\nothing.xlsx\")\nexcept pywintypes.com_error as error:\n    print(describe(error))\n","python",[14,200,201,211,225,232,252,264,275,284,306,325,371,428,433,441,474,488],{"__ignoreMap":158},[162,202,203,207],{"class":68,"line":164},[162,204,206],{"class":205},"s-kum","import",[162,208,210],{"class":209},"skGVy"," pywintypes\n",[162,212,214,216,219,222],{"class":68,"line":213},2,[162,215,206],{"class":205},[162,217,218],{"class":209}," win32com.client ",[162,220,221],{"class":205},"as",[162,223,224],{"class":209}," win32\n",[162,226,228],{"class":68,"line":227},3,[162,229,231],{"emptyLinePlaceholder":230},true,"\n",[162,233,235,238,242,245,249],{"class":68,"line":234},4,[162,236,237],{"class":205},"def",[162,239,241],{"class":240},"s_Opv"," describe",[162,243,244],{"class":209},"(error: pywintypes.com_error) -> ",[162,246,248],{"class":247},"sP0c6","str",[162,250,251],{"class":209},":\n",[162,253,255,258,261],{"class":68,"line":254},5,[162,256,257],{"class":209},"    hresult, message, excepinfo, argument ",[162,259,260],{"class":205},"=",[162,262,263],{"class":209}," error.args\n",[162,265,267,270,272],{"class":68,"line":266},6,[162,268,269],{"class":209},"    detail ",[162,271,260],{"class":205},[162,273,274],{"class":171}," \"\"\n",[162,276,278,281],{"class":68,"line":277},7,[162,279,280],{"class":205},"    if",[162,282,283],{"class":209}," excepinfo:\n",[162,285,287,290,292,295,298,301,304],{"class":68,"line":286},8,[162,288,289],{"class":209},"        source ",[162,291,260],{"class":205},[162,293,294],{"class":209}," excepinfo[",[162,296,297],{"class":247},"1",[162,299,300],{"class":209},"] ",[162,302,303],{"class":205},"or",[162,305,274],{"class":171},[162,307,309,312,314,316,319,321,323],{"class":68,"line":308},9,[162,310,311],{"class":209},"        description ",[162,313,260],{"class":205},[162,315,294],{"class":209},[162,317,318],{"class":247},"2",[162,320,300],{"class":209},[162,322,303],{"class":205},[162,324,274],{"class":171},[162,326,328,331,333,336,339,343,346,349,352,354,357,359,362,365,368],{"class":68,"line":327},10,[162,329,330],{"class":209},"        detail ",[162,332,260],{"class":205},[162,334,335],{"class":205}," f",[162,337,338],{"class":171},"\" — ",[162,340,342],{"class":341},"sSjpA","{",[162,344,345],{"class":209},"source",[162,347,348],{"class":341},"}",[162,350,351],{"class":171},": ",[162,353,342],{"class":341},[162,355,356],{"class":209},"description",[162,358,348],{"class":341},[162,360,361],{"class":171},"\"",[162,363,364],{"class":209},".rstrip(",[162,366,367],{"class":171},"\": \"",[162,369,370],{"class":209},")\n",[162,372,374,377,379,382,384,387,390,393,396,399,401,404,407,410,413,415,418,420,423,425],{"class":68,"line":373},11,[162,375,376],{"class":205},"    return",[162,378,335],{"class":205},[162,380,381],{"class":171},"\"COM 0x",[162,383,342],{"class":341},[162,385,386],{"class":209},"hresult ",[162,388,389],{"class":205},"&",[162,391,392],{"class":205}," 0x",[162,394,395],{"class":247},"FFFFFFFF",[162,397,398],{"class":205},":08X",[162,400,348],{"class":341},[162,402,403],{"class":341}," {",[162,405,406],{"class":209},"message",[162,408,409],{"class":341},"}{",[162,411,412],{"class":209},"detail",[162,414,348],{"class":341},[162,416,417],{"class":171}," (arg ",[162,419,342],{"class":341},[162,421,422],{"class":209},"argument",[162,424,348],{"class":341},[162,426,427],{"class":171},")\"\n",[162,429,431],{"class":68,"line":430},12,[162,432,231],{"emptyLinePlaceholder":230},[162,434,436,439],{"class":68,"line":435},13,[162,437,438],{"class":205},"try",[162,440,251],{"class":209},[162,442,444,447,450,453,457,460,463,466,469,472],{"class":68,"line":443},14,[162,445,446],{"class":209},"    excel.Workbooks.Open(",[162,448,449],{"class":205},"r",[162,451,452],{"class":171},"\"C:",[162,454,456],{"class":455},"s_b0D","\\m",[162,458,459],{"class":171},"issing",[162,461,462],{"class":455},"\\n",[162,464,465],{"class":171},"othing",[162,467,468],{"class":247},".",[162,470,471],{"class":171},"xlsx\"",[162,473,370],{"class":209},[162,475,477,480,483,485],{"class":68,"line":476},15,[162,478,479],{"class":205},"except",[162,481,482],{"class":209}," pywintypes.com_error ",[162,484,221],{"class":205},[162,486,487],{"class":209}," error:\n",[162,489,491,494],{"class":68,"line":490},16,[162,492,493],{"class":247},"    print",[162,495,496],{"class":209},"(describe(error))\n",[10,498,499,500,503],{},"That turns ",[14,501,502],{},"(-2147352567, 'Exception occurred.', (0, 'Microsoft Excel', \"'nothing.xlsx' could not be found...\", ...), None)"," into a single line naming the file and the reason — which is the difference\nbetween a loggable failure and a screenshot in a support ticket.",[148,505,507],{"id":506},"the-hresults-worth-recognising","The HRESULTs worth recognising",[25,509,34,514,34,517,34,520,34,523,34,532,34,538,34,542,34,546,34,550,34,555,34,558,34,561,34,564,34,569,34,572,34,575,34,580,34,585,34,588,34,591,34,593,34,597,34,600,34,603],{"viewBox":510,"role":28,"ariaLabelledBy":511,"xmlns":32,"style":33},"0 0 760 256",[512,513],"err-codes-t","err-codes-d",[36,515,516],{"id":512},"The four COM result codes a reporting script meets",[40,518,519],{"id":513},"DISP_E_EXCEPTION means the call raised inside Excel, 800A03EC is an invalid operation such as a wrong range, call rejected means Excel is busy and is worth retrying, and MK_E_UNAVAILABLE means no instance is running.",[44,521],{"x":46,"y":46,"width":47,"height":522,"fill":49},"256",[44,524],{"x":525,"y":526,"width":527,"height":528,"rx":529,"fill":530,"stroke":531},"18","30","724","34","8","#e7ebef","var(--line,#cdd5e6)",[61,533,537],{"x":534,"y":535,"style":536},"126.6","52","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","Code",[61,539,541],{"x":540,"y":535,"style":536},"361.9","Meaning",[61,543,545],{"x":544,"y":535,"style":536},"615.3000000000001","What to do",[44,547],{"x":525,"y":548,"width":527,"height":528,"rx":529,"fill":549,"stroke":118},"67","#f0f4ff",[61,551,554],{"x":534,"y":552,"style":553},"88.0","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","0x80020009",[61,556,557],{"x":540,"y":552,"style":77},"raised inside Excel",[61,559,560],{"x":544,"y":552,"style":77},"read excepinfo",[44,562],{"x":525,"y":563,"width":527,"height":528,"rx":529,"fill":57,"stroke":58},"107",[61,565,568],{"x":534,"y":566,"style":567},"128.0","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","0x800A03EC",[61,570,571],{"x":540,"y":566,"style":77},"invalid operation",[61,573,574],{"x":544,"y":566,"style":77},"check range or sheet",[44,576],{"x":525,"y":577,"width":527,"height":528,"rx":529,"fill":578,"stroke":579},"147","#d9f4f1","var(--teal,#0f9488)",[61,581,584],{"x":534,"y":582,"style":583},"168.0","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","0x80010001",[61,586,587],{"x":540,"y":582,"style":77},"Excel is busy",[61,589,590],{"x":544,"y":582,"style":77},"retry with backoff",[44,592],{"x":525,"y":144,"width":527,"height":528,"rx":529,"fill":530,"stroke":531},[61,594,596],{"x":534,"y":595,"style":536},"208.0","0x800401E3",[61,598,599],{"x":540,"y":595,"style":77},"no running instance",[61,601,602],{"x":544,"y":595,"style":77},"fall back to DispatchEx",[61,604,606],{"x":121,"y":605,"style":145},"244","only one of these four is worth retrying",[10,608,609,610,612,613,615,616,618,619,621,622,624,625,628],{},"Four codes cover most of what a reporting script encounters. ",[14,611,554],{}," (DISP_E_EXCEPTION) means\nthe call raised inside Excel and the real detail is in ",[14,614,189],{},". ",[14,617,568],{}," is Excel's generic\n\"that operation is not valid here\" and usually means a wrong range address, a missing sheet or a\nprotected cell. ",[14,620,584],{}," (RPC_E_CALL_REJECTED) means Excel was busy — typically because a dialog\nis open or a user is dragging something — and is the one worth retrying. ",[14,623,596],{},"\n(MK_E_UNAVAILABLE) comes from ",[14,626,627],{},"GetActiveObject"," when no instance is running.",[153,630,632],{"className":196,"code":631,"language":198,"meta":158,"style":158},"RETRYABLE = {0x80010001, 0x8001010A}      # call rejected, message filter timed out\n\ndef call_with_retry(operation, attempts=5, delay=1.0):\n    import time\n    for attempt in range(attempts):\n        try:\n            return operation()\n        except pywintypes.com_error as error:\n            if (error.args[0] & 0xFFFFFFFF) not in RETRYABLE or attempt == attempts - 1:\n                raise\n            time.sleep(delay * (attempt + 1))\n",[14,633,634,665,669,695,703,720,727,735,746,795,800],{"__ignoreMap":158},[162,635,636,639,642,644,647,650,653,655,658,661],{"class":68,"line":164},[162,637,638],{"class":247},"RETRYABLE",[162,640,641],{"class":205}," =",[162,643,403],{"class":209},[162,645,646],{"class":205},"0x",[162,648,649],{"class":247},"80010001",[162,651,652],{"class":209},", ",[162,654,646],{"class":205},[162,656,657],{"class":247},"8001010A",[162,659,660],{"class":209},"}      ",[162,662,664],{"class":663},"s-wDw","# call rejected, message filter timed out\n",[162,666,667],{"class":68,"line":213},[162,668,231],{"emptyLinePlaceholder":230},[162,670,671,673,676,679,681,684,687,689,692],{"class":68,"line":227},[162,672,237],{"class":205},[162,674,675],{"class":240}," call_with_retry",[162,677,678],{"class":209},"(operation, attempts",[162,680,260],{"class":205},[162,682,683],{"class":247},"5",[162,685,686],{"class":209},", delay",[162,688,260],{"class":205},[162,690,691],{"class":247},"1.0",[162,693,694],{"class":209},"):\n",[162,696,697,700],{"class":68,"line":234},[162,698,699],{"class":205},"    import",[162,701,702],{"class":209}," time\n",[162,704,705,708,711,714,717],{"class":68,"line":254},[162,706,707],{"class":205},"    for",[162,709,710],{"class":209}," attempt ",[162,712,713],{"class":205},"in",[162,715,716],{"class":247}," range",[162,718,719],{"class":209},"(attempts):\n",[162,721,722,725],{"class":68,"line":266},[162,723,724],{"class":205},"        try",[162,726,251],{"class":209},[162,728,729,732],{"class":68,"line":277},[162,730,731],{"class":205},"            return",[162,733,734],{"class":209}," operation()\n",[162,736,737,740,742,744],{"class":68,"line":286},[162,738,739],{"class":205},"        except",[162,741,482],{"class":209},[162,743,221],{"class":205},[162,745,487],{"class":209},[162,747,748,751,754,756,758,760,762,764,767,770,773,776,779,781,784,787,790,793],{"class":68,"line":308},[162,749,750],{"class":205},"            if",[162,752,753],{"class":209}," (error.args[",[162,755,46],{"class":247},[162,757,300],{"class":209},[162,759,389],{"class":205},[162,761,392],{"class":205},[162,763,395],{"class":247},[162,765,766],{"class":209},") ",[162,768,769],{"class":205},"not",[162,771,772],{"class":205}," in",[162,774,775],{"class":247}," RETRYABLE",[162,777,778],{"class":205}," or",[162,780,710],{"class":209},[162,782,783],{"class":205},"==",[162,785,786],{"class":209}," attempts ",[162,788,789],{"class":205},"-",[162,791,792],{"class":247}," 1",[162,794,251],{"class":209},[162,796,797],{"class":68,"line":327},[162,798,799],{"class":205},"                raise\n",[162,801,802,805,808,811,814,816],{"class":68,"line":373},[162,803,804],{"class":209},"            time.sleep(delay ",[162,806,807],{"class":205},"*",[162,809,810],{"class":209}," (attempt ",[162,812,813],{"class":205},"+",[162,815,792],{"class":247},[162,817,818],{"class":209},"))\n",[10,820,821],{},"Retrying only the two codes that genuinely mean \"busy\" is the point. A blanket retry around a\nmissing-file error just delays the failure by five seconds and makes the log harder to read.",[148,823,825],{"id":824},"preventing-the-invisible-dialog","Preventing the invisible dialog",[10,827,828],{},"Every dialog Excel can raise during an unattended run has a property that suppresses it, and setting\nthem all before opening anything is the single highest-value habit in COM automation.",[153,830,832],{"className":196,"code":831,"language":198,"meta":158,"style":158},"XL_AUTOMATION_SECURITY_FORCE_DISABLE = 3\n\nexcel.Visible = False\nexcel.DisplayAlerts = False                 # save prompts, overwrite prompts\nexcel.AskToUpdateLinks = False              # \"update external links?\"\nexcel.EnableEvents = False                  # workbook Open macros that show forms\nexcel.AutomationSecurity = XL_AUTOMATION_SECURITY_FORCE_DISABLE   # macro prompts\nexcel.FeatureInstall = 0                    # \"a feature must be installed\" prompt\n",[14,833,834,844,848,858,871,883,895,908],{"__ignoreMap":158},[162,835,836,839,841],{"class":68,"line":164},[162,837,838],{"class":247},"XL_AUTOMATION_SECURITY_FORCE_DISABLE",[162,840,641],{"class":205},[162,842,843],{"class":247}," 3\n",[162,845,846],{"class":68,"line":213},[162,847,231],{"emptyLinePlaceholder":230},[162,849,850,853,855],{"class":68,"line":227},[162,851,852],{"class":209},"excel.Visible ",[162,854,260],{"class":205},[162,856,857],{"class":247}," False\n",[162,859,860,863,865,868],{"class":68,"line":234},[162,861,862],{"class":209},"excel.DisplayAlerts ",[162,864,260],{"class":205},[162,866,867],{"class":247}," False",[162,869,870],{"class":663},"                 # save prompts, overwrite prompts\n",[162,872,873,876,878,880],{"class":68,"line":254},[162,874,875],{"class":209},"excel.AskToUpdateLinks ",[162,877,260],{"class":205},[162,879,867],{"class":247},[162,881,882],{"class":663},"              # \"update external links?\"\n",[162,884,885,888,890,892],{"class":68,"line":266},[162,886,887],{"class":209},"excel.EnableEvents ",[162,889,260],{"class":205},[162,891,867],{"class":247},[162,893,894],{"class":663},"                  # workbook Open macros that show forms\n",[162,896,897,900,902,905],{"class":68,"line":277},[162,898,899],{"class":209},"excel.AutomationSecurity ",[162,901,260],{"class":205},[162,903,904],{"class":247}," XL_AUTOMATION_SECURITY_FORCE_DISABLE",[162,906,907],{"class":663},"   # macro prompts\n",[162,909,910,913,915,918],{"class":68,"line":286},[162,911,912],{"class":209},"excel.FeatureInstall ",[162,914,260],{"class":205},[162,916,917],{"class":247}," 0",[162,919,920],{"class":663},"                    # \"a feature must be installed\" prompt\n",[10,922,923,926,927,930,931,934],{},[14,924,925],{},"EnableEvents = False"," deserves the most attention. A workbook with a ",[14,928,929],{},"Workbook_Open"," macro will run\nit the moment your script opens the file, and if that macro shows a message box the script hangs\nbefore it has done anything at all. Disabling events also means ",[14,932,933],{},"Worksheet_Change"," handlers do not\nfire while you write, which is usually what you want and occasionally not — a workbook that relies\non those handlers to maintain a summary will need them back on.",[148,936,938],{"id":937},"opening-a-file-without-prompts","Opening a file without prompts",[10,940,941,942,945],{},"The ",[14,943,944],{},"Open"," call has its own set of arguments that suppress the remaining prompts, and they are worth\npassing explicitly rather than relying on defaults.",[153,947,949],{"className":196,"code":948,"language":198,"meta":158,"style":158},"book = excel.Workbooks.Open(\n    Filename=r\"C:\\reports\\model.xlsx\",\n    UpdateLinks=0,          # do not update, do not ask\n    ReadOnly=False,\n    IgnoreReadOnlyRecommended=True,\n    Notify=False,           # do not queue a \"now available\" notification\n    CorruptLoad=0,          # normal load; 1 repairs, 2 extracts data\n)\n",[14,950,951,961,991,1006,1018,1030,1045,1059],{"__ignoreMap":158},[162,952,953,956,958],{"class":68,"line":164},[162,954,955],{"class":209},"book ",[162,957,260],{"class":205},[162,959,960],{"class":209}," excel.Workbooks.Open(\n",[162,962,963,967,969,971,973,976,979,981,984,986,988],{"class":68,"line":213},[162,964,966],{"class":965},"sa561","    Filename",[162,968,260],{"class":205},[162,970,449],{"class":205},[162,972,452],{"class":171},[162,974,975],{"class":455},"\\r",[162,977,978],{"class":171},"eports",[162,980,456],{"class":455},[162,982,983],{"class":171},"odel",[162,985,468],{"class":247},[162,987,471],{"class":171},[162,989,990],{"class":209},",\n",[162,992,993,996,998,1000,1003],{"class":68,"line":227},[162,994,995],{"class":965},"    UpdateLinks",[162,997,260],{"class":205},[162,999,46],{"class":247},[162,1001,1002],{"class":209},",          ",[162,1004,1005],{"class":663},"# do not update, do not ask\n",[162,1007,1008,1011,1013,1016],{"class":68,"line":234},[162,1009,1010],{"class":965},"    ReadOnly",[162,1012,260],{"class":205},[162,1014,1015],{"class":247},"False",[162,1017,990],{"class":209},[162,1019,1020,1023,1025,1028],{"class":68,"line":254},[162,1021,1022],{"class":965},"    IgnoreReadOnlyRecommended",[162,1024,260],{"class":205},[162,1026,1027],{"class":247},"True",[162,1029,990],{"class":209},[162,1031,1032,1035,1037,1039,1042],{"class":68,"line":266},[162,1033,1034],{"class":965},"    Notify",[162,1036,260],{"class":205},[162,1038,1015],{"class":247},[162,1040,1041],{"class":209},",           ",[162,1043,1044],{"class":663},"# do not queue a \"now available\" notification\n",[162,1046,1047,1050,1052,1054,1056],{"class":68,"line":277},[162,1048,1049],{"class":965},"    CorruptLoad",[162,1051,260],{"class":205},[162,1053,46],{"class":247},[162,1055,1002],{"class":209},[162,1057,1058],{"class":663},"# normal load; 1 repairs, 2 extracts data\n",[162,1060,1061],{"class":68,"line":286},[162,1062,370],{"class":209},[10,1064,1065,1068,1069,1072],{},[14,1066,1067],{},"IgnoreReadOnlyRecommended=True"," handles workbooks saved with the read-only recommendation, which\notherwise produce a prompt that is invisible and blocking. ",[14,1070,1071],{},"Notify=False"," matters when the file may\nbe locked by somebody else: without it Excel queues a notification and the call behaves unexpectedly.",[148,1074,1076],{"id":1075},"a-watchdog-for-the-hangs-you-cannot-prevent","A watchdog for the hangs you cannot prevent",[10,1078,1079],{},"Some hangs survive every setting — a corrupt file that triggers repair, a licence activation prompt,\nan add-in with its own dialog. For unattended runs, the answer is a timeout around the whole\noperation rather than a better guess about which flag was missed.",[153,1081,1083],{"className":196,"code":1082,"language":198,"meta":158,"style":158},"import concurrent.futures as futures\nimport pythoncom\n\ndef run_with_timeout(work, seconds=300):\n    def target():\n        pythoncom.CoInitialize()\n        try:\n            return work()\n        finally:\n            pythoncom.CoUninitialize()\n\n    with futures.ThreadPoolExecutor(max_workers=1) as pool:\n        future = pool.submit(target)\n        try:\n            return future.result(timeout=seconds)\n        except futures.TimeoutError:\n            raise TimeoutError(f\"Excel did not respond within {seconds}s — likely a dialog\")\n",[14,1084,1085,1097,1104,1108,1125,1136,1141,1147,1154,1161,1166,1170,1192,1202,1208,1223,1230],{"__ignoreMap":158},[162,1086,1087,1089,1092,1094],{"class":68,"line":164},[162,1088,206],{"class":205},[162,1090,1091],{"class":209}," concurrent.futures ",[162,1093,221],{"class":205},[162,1095,1096],{"class":209}," futures\n",[162,1098,1099,1101],{"class":68,"line":213},[162,1100,206],{"class":205},[162,1102,1103],{"class":209}," pythoncom\n",[162,1105,1106],{"class":68,"line":227},[162,1107,231],{"emptyLinePlaceholder":230},[162,1109,1110,1112,1115,1118,1120,1123],{"class":68,"line":234},[162,1111,237],{"class":205},[162,1113,1114],{"class":240}," run_with_timeout",[162,1116,1117],{"class":209},"(work, seconds",[162,1119,260],{"class":205},[162,1121,1122],{"class":247},"300",[162,1124,694],{"class":209},[162,1126,1127,1130,1133],{"class":68,"line":254},[162,1128,1129],{"class":205},"    def",[162,1131,1132],{"class":240}," target",[162,1134,1135],{"class":209},"():\n",[162,1137,1138],{"class":68,"line":266},[162,1139,1140],{"class":209},"        pythoncom.CoInitialize()\n",[162,1142,1143,1145],{"class":68,"line":277},[162,1144,724],{"class":205},[162,1146,251],{"class":209},[162,1148,1149,1151],{"class":68,"line":286},[162,1150,731],{"class":205},[162,1152,1153],{"class":209}," work()\n",[162,1155,1156,1159],{"class":68,"line":308},[162,1157,1158],{"class":205},"        finally",[162,1160,251],{"class":209},[162,1162,1163],{"class":68,"line":327},[162,1164,1165],{"class":209},"            pythoncom.CoUninitialize()\n",[162,1167,1168],{"class":68,"line":373},[162,1169,231],{"emptyLinePlaceholder":230},[162,1171,1172,1175,1178,1181,1183,1185,1187,1189],{"class":68,"line":430},[162,1173,1174],{"class":205},"    with",[162,1176,1177],{"class":209}," futures.ThreadPoolExecutor(",[162,1179,1180],{"class":965},"max_workers",[162,1182,260],{"class":205},[162,1184,297],{"class":247},[162,1186,766],{"class":209},[162,1188,221],{"class":205},[162,1190,1191],{"class":209}," pool:\n",[162,1193,1194,1197,1199],{"class":68,"line":435},[162,1195,1196],{"class":209},"        future ",[162,1198,260],{"class":205},[162,1200,1201],{"class":209}," pool.submit(target)\n",[162,1203,1204,1206],{"class":68,"line":443},[162,1205,724],{"class":205},[162,1207,251],{"class":209},[162,1209,1210,1212,1215,1218,1220],{"class":68,"line":476},[162,1211,731],{"class":205},[162,1213,1214],{"class":209}," future.result(",[162,1216,1217],{"class":965},"timeout",[162,1219,260],{"class":205},[162,1221,1222],{"class":209},"seconds)\n",[162,1224,1225,1227],{"class":68,"line":490},[162,1226,739],{"class":205},[162,1228,1229],{"class":209}," futures.TimeoutError:\n",[162,1231,1233,1236,1239,1242,1245,1248,1250,1253,1255,1258],{"class":68,"line":1232},17,[162,1234,1235],{"class":205},"            raise",[162,1237,1238],{"class":247}," TimeoutError",[162,1240,1241],{"class":209},"(",[162,1243,1244],{"class":205},"f",[162,1246,1247],{"class":171},"\"Excel did not respond within ",[162,1249,342],{"class":341},[162,1251,1252],{"class":209},"seconds",[162,1254,348],{"class":341},[162,1256,1257],{"class":171},"s — likely a dialog\"",[162,1259,370],{"class":209},[10,1261,1262,1263,1267],{},"The thread cannot be killed, so this has to be paired with terminating the Excel process you started\n— the technique in\n",[19,1264,1266],{"href":1265},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes\u002F","Close Excel Cleanly and Avoid Orphan COM Processes",".\nTogether they turn an indefinite hang into a failed run the scheduler can report and retry.",[148,1269,1271],{"id":1270},"failing-where-the-cause-is","Failing where the cause is",[10,1273,1274,1275,1278],{},"The pattern that saves the most time is not a better exception handler but a check placed earlier.\nMost COM failures are consequences: a missing sheet name surfaces as an invalid-operation error\nthree calls later, a locked file surfaces as a save failure at the end of a long job, an empty range\nsurfaces as a ",[14,1276,1277],{},"None"," that propagates into a report. Each of those has a cheap precondition.",[153,1280,1282],{"className":196,"code":1281,"language":198,"meta":158,"style":158},"def open_checked(excel, path, required_sheets):\n    from pathlib import Path\n    path = Path(path)\n    if not path.exists():\n        raise FileNotFoundError(f\"{path} does not exist\")\n    if path.stat().st_size == 0:\n        raise ValueError(f\"{path} is zero bytes — an interrupted download?\")\n\n    book = excel.Workbooks.Open(str(path), UpdateLinks=0, Notify=False)\n    names = {sheet.Name for sheet in book.Sheets}\n    missing = [name for name in required_sheets if name not in names]\n    if missing:\n        book.Close(SaveChanges=False)\n        raise KeyError(f\"{path.name} is missing sheet(s): {missing}; has {sorted(names)}\")\n    return book\n",[14,1283,1284,1294,1307,1317,1327,1353,1366,1390,1394,1427,1448,1480,1487,1501,1548],{"__ignoreMap":158},[162,1285,1286,1288,1291],{"class":68,"line":164},[162,1287,237],{"class":205},[162,1289,1290],{"class":240}," open_checked",[162,1292,1293],{"class":209},"(excel, path, required_sheets):\n",[162,1295,1296,1299,1302,1304],{"class":68,"line":213},[162,1297,1298],{"class":205},"    from",[162,1300,1301],{"class":209}," pathlib ",[162,1303,206],{"class":205},[162,1305,1306],{"class":209}," Path\n",[162,1308,1309,1312,1314],{"class":68,"line":227},[162,1310,1311],{"class":209},"    path ",[162,1313,260],{"class":205},[162,1315,1316],{"class":209}," Path(path)\n",[162,1318,1319,1321,1324],{"class":68,"line":234},[162,1320,280],{"class":205},[162,1322,1323],{"class":205}," not",[162,1325,1326],{"class":209}," path.exists():\n",[162,1328,1329,1332,1335,1337,1339,1341,1343,1346,1348,1351],{"class":68,"line":254},[162,1330,1331],{"class":205},"        raise",[162,1333,1334],{"class":247}," FileNotFoundError",[162,1336,1241],{"class":209},[162,1338,1244],{"class":205},[162,1340,361],{"class":171},[162,1342,342],{"class":341},[162,1344,1345],{"class":209},"path",[162,1347,348],{"class":341},[162,1349,1350],{"class":171}," does not exist\"",[162,1352,370],{"class":209},[162,1354,1355,1357,1360,1362,1364],{"class":68,"line":266},[162,1356,280],{"class":205},[162,1358,1359],{"class":209}," path.stat().st_size ",[162,1361,783],{"class":205},[162,1363,917],{"class":247},[162,1365,251],{"class":209},[162,1367,1368,1370,1373,1375,1377,1379,1381,1383,1385,1388],{"class":68,"line":277},[162,1369,1331],{"class":205},[162,1371,1372],{"class":247}," ValueError",[162,1374,1241],{"class":209},[162,1376,1244],{"class":205},[162,1378,361],{"class":171},[162,1380,342],{"class":341},[162,1382,1345],{"class":209},[162,1384,348],{"class":341},[162,1386,1387],{"class":171}," is zero bytes — an interrupted download?\"",[162,1389,370],{"class":209},[162,1391,1392],{"class":68,"line":286},[162,1393,231],{"emptyLinePlaceholder":230},[162,1395,1396,1399,1401,1404,1406,1409,1412,1414,1416,1418,1421,1423,1425],{"class":68,"line":308},[162,1397,1398],{"class":209},"    book ",[162,1400,260],{"class":205},[162,1402,1403],{"class":209}," excel.Workbooks.Open(",[162,1405,248],{"class":247},[162,1407,1408],{"class":209},"(path), ",[162,1410,1411],{"class":965},"UpdateLinks",[162,1413,260],{"class":205},[162,1415,46],{"class":247},[162,1417,652],{"class":209},[162,1419,1420],{"class":965},"Notify",[162,1422,260],{"class":205},[162,1424,1015],{"class":247},[162,1426,370],{"class":209},[162,1428,1429,1432,1434,1437,1440,1443,1445],{"class":68,"line":327},[162,1430,1431],{"class":209},"    names ",[162,1433,260],{"class":205},[162,1435,1436],{"class":209}," {sheet.Name ",[162,1438,1439],{"class":205},"for",[162,1441,1442],{"class":209}," sheet ",[162,1444,713],{"class":205},[162,1446,1447],{"class":209}," book.Sheets}\n",[162,1449,1450,1453,1455,1458,1460,1463,1465,1468,1471,1473,1475,1477],{"class":68,"line":373},[162,1451,1452],{"class":209},"    missing ",[162,1454,260],{"class":205},[162,1456,1457],{"class":209}," [name ",[162,1459,1439],{"class":205},[162,1461,1462],{"class":209}," name ",[162,1464,713],{"class":205},[162,1466,1467],{"class":209}," required_sheets ",[162,1469,1470],{"class":205},"if",[162,1472,1462],{"class":209},[162,1474,769],{"class":205},[162,1476,772],{"class":205},[162,1478,1479],{"class":209}," names]\n",[162,1481,1482,1484],{"class":68,"line":430},[162,1483,280],{"class":205},[162,1485,1486],{"class":209}," missing:\n",[162,1488,1489,1492,1495,1497,1499],{"class":68,"line":435},[162,1490,1491],{"class":209},"        book.Close(",[162,1493,1494],{"class":965},"SaveChanges",[162,1496,260],{"class":205},[162,1498,1015],{"class":247},[162,1500,370],{"class":209},[162,1502,1503,1505,1508,1510,1512,1514,1516,1519,1521,1524,1526,1529,1531,1534,1536,1539,1542,1544,1546],{"class":68,"line":443},[162,1504,1331],{"class":205},[162,1506,1507],{"class":247}," KeyError",[162,1509,1241],{"class":209},[162,1511,1244],{"class":205},[162,1513,361],{"class":171},[162,1515,342],{"class":341},[162,1517,1518],{"class":209},"path.name",[162,1520,348],{"class":341},[162,1522,1523],{"class":171}," is missing sheet(s): ",[162,1525,342],{"class":341},[162,1527,1528],{"class":209},"missing",[162,1530,348],{"class":341},[162,1532,1533],{"class":171},"; has ",[162,1535,342],{"class":341},[162,1537,1538],{"class":247},"sorted",[162,1540,1541],{"class":209},"(names)",[162,1543,348],{"class":341},[162,1545,361],{"class":171},[162,1547,370],{"class":209},[162,1549,1550,1552],{"class":68,"line":476},[162,1551,376],{"class":205},[162,1553,1554],{"class":209}," book\n",[10,1556,1557],{},"The error message names the file, the missing sheets and the ones that are actually present, which\nis everything the person fixing it needs. Compare that with the alternative — a COM exception\nmentioning an invalid range, raised somewhere in the middle of the run — and the ten lines pay for\nthemselves the first time an upstream export renames a tab.",[148,1559,1561],{"id":1560},"making-failures-visible-to-the-people-who-care","Making failures visible to the people who care",[10,1563,1564],{},"An unattended job that fails silently is worse than one that fails loudly, and COM automation fails\nsilently by default: the process ends, the file is not updated, and the only evidence is an absence.\nTwo conventions fix that. Write the run's outcome somewhere the recipients already look — a\ntimestamp cell in the workbook, a line in a status sheet — and make the failure path notify a person\nrather than a log file nobody opens.",[153,1566,1568],{"className":196,"code":1567,"language":198,"meta":158,"style":158},"from datetime import datetime\n\ndef stamp(book, status: str) -> None:\n    sheet = book.Sheets(\"Summary\")\n    sheet.Range(\"A1\").Value = f\"{status} — {datetime.now():%Y-%m-%d %H:%M}\"\n",[14,1569,1570,1583,1587,1606,1621],{"__ignoreMap":158},[162,1571,1572,1575,1578,1580],{"class":68,"line":164},[162,1573,1574],{"class":205},"from",[162,1576,1577],{"class":209}," datetime ",[162,1579,206],{"class":205},[162,1581,1582],{"class":209}," datetime\n",[162,1584,1585],{"class":68,"line":213},[162,1586,231],{"emptyLinePlaceholder":230},[162,1588,1589,1591,1594,1597,1599,1602,1604],{"class":68,"line":227},[162,1590,237],{"class":205},[162,1592,1593],{"class":240}," stamp",[162,1595,1596],{"class":209},"(book, status: ",[162,1598,248],{"class":247},[162,1600,1601],{"class":209},") -> ",[162,1603,1277],{"class":247},[162,1605,251],{"class":209},[162,1607,1608,1611,1613,1616,1619],{"class":68,"line":234},[162,1609,1610],{"class":209},"    sheet ",[162,1612,260],{"class":205},[162,1614,1615],{"class":209}," book.Sheets(",[162,1617,1618],{"class":171},"\"Summary\"",[162,1620,370],{"class":209},[162,1622,1623,1626,1629,1632,1634,1636,1638,1640,1643,1645,1648,1650,1653,1656,1659,1662,1665,1667,1670,1672,1675,1677,1680,1682],{"class":68,"line":254},[162,1624,1625],{"class":209},"    sheet.Range(",[162,1627,1628],{"class":171},"\"A1\"",[162,1630,1631],{"class":209},").Value ",[162,1633,260],{"class":205},[162,1635,335],{"class":205},[162,1637,361],{"class":171},[162,1639,342],{"class":341},[162,1641,1642],{"class":209},"status",[162,1644,348],{"class":341},[162,1646,1647],{"class":171}," — ",[162,1649,342],{"class":341},[162,1651,1652],{"class":209},"datetime.now():",[162,1654,1655],{"class":205},"%",[162,1657,1658],{"class":209},"Y",[162,1660,1661],{"class":205},"-%",[162,1663,1664],{"class":209},"m",[162,1666,1661],{"class":205},[162,1668,1669],{"class":209},"d ",[162,1671,1655],{"class":205},[162,1673,1674],{"class":209},"H:",[162,1676,1655],{"class":205},[162,1678,1679],{"class":209},"M",[162,1681,348],{"class":341},[162,1683,1684],{"class":171},"\"\n",[10,1686,1687,1688,1692,1693,468],{},"A recipient who can see when the file was last refreshed will notice a stale report themselves,\nwhich is a far more reliable detector than any monitoring you are likely to build for a single job.\nThe notification half is covered in\n",[19,1689,1691],{"href":1690},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002F","Emailing Excel Reports with smtplib",", and\nthe validation that decides whether to send at all in\n",[19,1694,1696],{"href":1695},"\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",[148,1698,1700],{"id":1699},"common-pitfalls","Common pitfalls",[1702,1703,1704,1720],"table",{},[1705,1706,1707],"thead",{},[1708,1709,1710,1714,1717],"tr",{},[1711,1712,1713],"th",{},"Symptom",[1711,1715,1716],{},"Cause",[1711,1718,1719],{},"Fix",[1721,1722,1723,1749,1766,1781,1797,1812],"tbody",{},[1708,1724,1725,1729,1732],{},[1726,1727,1728],"td",{},"The script stops with no error and no output",[1726,1730,1731],{},"An invisible modal dialog",[1726,1733,1734,1735,652,1738,652,1741,1744,1745,1748],{},"Set ",[14,1736,1737],{},"DisplayAlerts",[14,1739,1740],{},"AskToUpdateLinks",[14,1742,1743],{},"EnableEvents"," and ",[14,1746,1747],{},"AutomationSecurity"," before opening",[1708,1750,1751,1756,1759],{},[1726,1752,1753],{},[14,1754,1755],{},"com_error (-2147352567, 'Exception occurred.', ...)",[1726,1757,1758],{},"The call raised inside Excel",[1726,1760,1761,1762,1765],{},"Read ",[14,1763,1764],{},"excepinfo[2]"," for Excel's own message",[1708,1767,1768,1773,1776],{},[1726,1769,1770],{},[14,1771,1772],{},"Call was rejected by callee",[1726,1774,1775],{},"Excel busy — a dialog, or a user interacting",[1726,1777,1778,1779],{},"Retry with a short backoff on ",[14,1780,584],{},[1708,1782,1783,1788,1791],{},[1726,1784,1785,1787],{},[14,1786,627],{}," fails on a clean machine",[1726,1789,1790],{},"No Excel instance running",[1726,1792,1793,1794],{},"Fall back to ",[14,1795,1796],{},"DispatchEx",[1708,1798,1799,1802,1807],{},[1726,1800,1801],{},"A macro in the file runs unexpectedly",[1726,1803,1804,1806],{},[14,1805,929],{}," fired on open",[1726,1808,1809,1748],{},[14,1810,1811],{},"excel.EnableEvents = False",[1708,1813,1814,1817,1820],{},[1726,1815,1816],{},"Errors reference the wrong file",[1726,1818,1819],{},"Excel reports the active workbook, not the one you meant",[1726,1821,1822],{},"Log the path yourself alongside the decoded error",[148,1824,1826],{"id":1825},"performance-and-scale","Performance and scale",[10,1828,1829,1830,1832,1833,1835],{},"Error handling has a performance dimension that is easy to miss: a ",[14,1831,438],{},"\u002F",[14,1834,479],{}," around every COM\ncall adds another boundary crossing when the guard itself reads a property. Wrap the operation, not\nthe property access.",[25,1837,34,1842,34,1845,34,1848,34,1851,34,1856,34,1862,34,1867,34,1872,34,1877,34,1880,34,1883,34,1887,34,1890,34,1894,34,1897,34,1900,34,1905,34,1908],{"viewBox":1838,"role":28,"ariaLabelledBy":1839,"xmlns":32,"style":33},"0 0 760 232",[1840,1841],"err-wrap-t","err-wrap-d",[36,1843,1844],{"id":1840},"Where to put the guard",[40,1846,1847],{"id":1841},"Wrapping the whole operation costs one boundary crossing, while a try block around each property access adds a crossing per guard and makes the log harder to read.",[44,1849],{"x":46,"y":46,"width":47,"height":1850,"fill":49},"232",[61,1852,1855],{"x":121,"y":1853,"style":1854},"32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","handling shape",[44,1857],{"x":1858,"y":1859,"width":595,"height":1860,"rx":1861,"fill":578,"stroke":579,"style":59},"24.0","74","96","12",[61,1863,1866],{"x":566,"y":1864,"style":1865},"114","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","wrap the operation",[61,1868,1871],{"x":566,"y":1869,"style":1870},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","one guard, one crossing",[68,1873],{"x1":1874,"y1":1875,"x2":1876,"y2":1875,"stroke":118,"style":59},"237.0","122.0","269.0",[131,1878],{"points":1879,"fill":134},"269.0,122.0 260.0,117.0 260.0,127.0",[44,1881],{"x":1882,"y":1859,"width":595,"height":1860,"rx":1861,"fill":549,"stroke":118,"style":59},"276.0",[61,1884,1886],{"x":121,"y":1864,"style":1885},"font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","decode the error",[61,1888,1889],{"x":121,"y":1869,"style":1870},"excepinfo names the cause",[68,1891],{"x1":1892,"y1":1875,"x2":1893,"y2":1875,"stroke":118,"style":59},"489.0","521.0",[131,1895],{"points":1896,"fill":134},"521.0,122.0 512.0,117.0 512.0,127.0",[44,1898],{"x":1899,"y":1859,"width":595,"height":1860,"rx":1861,"fill":57,"stroke":58,"style":59},"528.0",[61,1901,1904],{"x":1902,"y":1864,"style":1903},"632.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","log one line",[61,1906,1907],{"x":1902,"y":1869,"style":1870},"not thirty of traceback",[61,1909,1911],{"x":121,"y":1910,"style":145},"210","guard the unit of work, not every property read",[10,1913,1914,1915,1917,1918,468],{},"The other scale concern is log volume. A decoded ",[14,1916,66],{}," is one useful line; the raw traceback of\na pywin32 exception is thirty lines that say very little. On a job that processes hundreds of\nworkbooks, logging the decoded form and keeping the traceback at debug level is the difference\nbetween a log somebody reads and one nobody does — the same argument made in\n",[19,1919,1921],{"href":1920},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Flog-python-excel-script-output-to-a-file\u002F","Log Python Excel Script Output to a File",[148,1923,1925],{"id":1924},"conclusion","Conclusion",[10,1927,1928,1929,1931,1932,1934],{},"Suppress the dialogs before opening anything, decode ",[14,1930,66],{}," through its ",[14,1933,189],{}," member so\nthe log names Excel's own message, retry only the codes that genuinely mean \"busy\", and put a\ntimeout around unattended work so a hang becomes a failure. Those four habits turn COM automation\nfrom something that mysteriously stops into something that reports what went wrong.",[148,1936,1938],{"id":1937},"frequently-asked-questions","Frequently asked questions",[10,1940,1941,1945],{},[1942,1943,1944],"strong",{},"What do the numbers in a com_error mean?","\nThe tuple is (hresult, message, excepinfo, argument_index). The second element is a short description of the HRESULT, and excepinfo — when present — is a nested tuple whose third and fourth elements carry Excel's own source and description, which is the part worth logging.",[10,1947,1948,1951],{},[1942,1949,1950],{},"Why does my script hang instead of raising?","\nExcel is showing a modal dialog. With Visible set to False the dialog is invisible, and the COM call blocks until somebody dismisses it — which nobody can. Setting DisplayAlerts, AskToUpdateLinks and AutomationSecurity before opening anything prevents nearly all of them.",[10,1953,1954,1957],{},[1942,1955,1956],{},"Is -2147352567 always the same problem?","\nNo. 0x80020009 is DISP_E_EXCEPTION, which just means the call raised inside Excel. The useful detail is in the excepinfo member, not the HRESULT itself.",[10,1959,1960,1963],{},[1942,1961,1962],{},"How do I stop 'file in use' prompts on open?","\nOpen with ReadOnly=True and Notify=False when you only need to read; that suppresses the prompt and the notification. If you must write, resolve the lock first rather than suppressing the warning.",[148,1965,1967],{"id":1966},"related","Related",[1969,1970,1971,1978,1983,1990,1997],"ul",{},[1972,1973,1974,1975,1977],"li",{},"Up one level: ",[19,1976,22],{"href":21}," — the session settings these errors come from.",[1972,1979,1980,1982],{},[19,1981,1266],{"href":1265}," — cleaning up after the hang a watchdog catches.",[1972,1984,1985,1989],{},[19,1986,1988],{"href":1987},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002F","Error Handling and Logging in Excel Automation"," — the surrounding job's resilience patterns.",[1972,1991,1992,1996],{},[19,1993,1995],{"href":1994},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fretry-a-failed-excel-report-job-in-python\u002F","Retry a Failed Excel Report Job in Python"," — backoff strategies for the retryable codes.",[1972,1998,1999,2003],{},[19,2000,2002],{"href":2001},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors"," — the file-level errors that have nothing to do with COM.",[2005,2006,2007],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":158,"searchDepth":213,"depth":213,"links":2009},[2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022],{"id":150,"depth":213,"text":151},{"id":178,"depth":213,"text":179},{"id":506,"depth":213,"text":507},{"id":824,"depth":213,"text":825},{"id":937,"depth":213,"text":938},{"id":1075,"depth":213,"text":1076},{"id":1270,"depth":213,"text":1271},{"id":1560,"depth":213,"text":1561},{"id":1699,"depth":213,"text":1700},{"id":1825,"depth":213,"text":1826},{"id":1924,"depth":213,"text":1925},{"id":1937,"depth":213,"text":1938},{"id":1966,"depth":213,"text":1967},"2026-09-04","Decode pywintypes.com_error through excepinfo, recognise the four HRESULTs that matter, suppress every dialog before opening a file, and add a watchdog for the hangs left over.","md",[2027,2029,2031,2033],{"q":1944,"a":2028},"The tuple is (hresult, message, excepinfo, argument_index). The second element is a short description of the HRESULT, and excepinfo — when present — is a nested tuple whose third and fourth elements carry Excel's own source and description, which is the part worth logging.",{"q":1950,"a":2030},"Excel is showing a modal dialog. With Visible set to False the dialog is invisible, and the COM call blocks until somebody dismisses it — which nobody can. Setting DisplayAlerts, AskToUpdateLinks and AutomationSecurity before opening anything prevents nearly all of them.",{"q":1956,"a":2032},"No. 0x80020009 is DISP_E_EXCEPTION, which just means the call raised inside Excel. The useful detail is in the excepinfo member, not the HRESULT itself.",{"q":1962,"a":2034},"Open with ReadOnly=True and Notify=False when you only need to read; that suppresses the prompt and the notification. If you must write, resolve the lock first rather than suppressing the warning.",{"breadcrumb":2036},[2037,2039,2042],{"name":2038,"item":1832},"Home",{"name":2040,"item":2041},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":22,"item":21},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python",{"title":5,"description":2045},"Turn opaque COM failures into readable logs: decode com_error, retry only 'call rejected', disable alerts, links, events and macro prompts, and time out invisible dialogs.","handle-com-errors-and-excel-dialog-prompts-in-python","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python\u002Findex","how-to","1fk-as0ue5gInjKNL8t49IqRgbpLyHk3xl2GkOHJ1NI",[2051,2054],{"title":1266,"path":2052,"stem":2053,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes\u002Findex",{"title":2055,"path":2056,"stem":2057,"children":-1},"Read and Write Cell Ranges with win32com","\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fread-and-write-cell-ranges-with-win32com","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fread-and-write-cell-ranges-with-win32com\u002Findex",1788710154301]