[{"data":1,"prerenderedAt":2703},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph":3,"surround:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph":2672,"ancestors:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph":2679,"children:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph":2685,"related:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph":2686},{"id":4,"title":5,"body":6,"dateModified":2638,"datePublished":2638,"description":2639,"extension":2640,"faq":2641,"keyword":2650,"kind":2651,"meta":2652,"navigation":254,"path":2660,"seo":2661,"slug":2664,"stem":2665,"topics":2666,"type":2670,"__hash__":2671},"docs\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph\u002Findex.md","Read and Write Excel Online Ranges with Microsoft Graph",{"type":7,"value":8,"toc":2624},"minimark",[9,24,122,127,161,199,203,210,675,682,686,696,844,864,868,879,1028,1039,1243,1247,1261,1827,1853,1857,1868,2136,2150,2238,2242,2251,2381,2395,2399,2512,2516,2530,2534,2554,2558,2564,2570,2576,2582,2586,2620],[10,11,12,13,17,18,23],"p",{},"Downloading a workbook, editing it with openpyxl and uploading it again works until two things happen at\nonce: someone has the file open in Excel for the web, and your upload replaces their afternoon's work.\nThe Graph ",[14,15,16],"strong",{},"workbook API"," avoids that race by editing the workbook where it lives. You read and write\nranges through Excel's own online service, the file never leaves SharePoint, formulas recalculate, and\npeople who have it open see the new values appear. This guide, part of\n",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002F","Automating Excel Online with Microsoft Graph",",\ncovers reading ranges into pandas and writing DataFrames back.",[25,26,34,35,34,39,34,43,34,50,34,59,34,66,34,73,34,78,34,82,34,86,34,90,34,95,34,100,34,104,34,107,34,110,34,113,34,116],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 214","img",[30,31],"rng-vs-t","rng-vs-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},"Replacing a file versus editing its ranges",[40,41,42],"desc",{"id":31},"Downloading, editing and uploading replaces the whole file and can overwrite concurrent edits. The workbook API edits specific ranges inside the online workbook, recalculates formulas, and leaves the rest of the file and other editors untouched.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","214","#ffffff",[44,51],{"x":52,"y":52,"width":53,"height":54,"rx":55,"fill":56,"stroke":57,"style":58},"20","350","150","14","#fee8f2","var(--accent,#f43f8f)","stroke-width:2px",[60,61,65],"text",{"x":62,"y":63,"style":64},"195","46","font-size:13px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","download, edit, upload",[67,68],"line",{"x1":69,"y1":70,"x2":71,"y2":70,"stroke":57,"style":72},"36","56","354","stroke-width:1px",[60,74,77],{"x":62,"y":75,"style":76},"78","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","whole file replaced",[60,79,81],{"x":62,"y":80,"style":76},"101","races with open editors",[60,83,85],{"x":62,"y":84,"style":76},"124","no recalculation",[60,87,89],{"x":62,"y":88,"style":76},"147","app-only token is enough",[44,91],{"x":92,"y":52,"width":53,"height":54,"rx":55,"fill":93,"stroke":94,"style":58},"390","#d9f4f1","var(--teal,#0f9488)",[60,96,99],{"x":97,"y":63,"style":98},"565","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","workbook API ranges",[67,101],{"x1":102,"y1":70,"x2":103,"y2":70,"stroke":94,"style":72},"406","724",[60,105,106],{"x":97,"y":75,"style":76},"only the range changes",[60,108,109],{"x":97,"y":80,"style":76},"co-authoring safe",[60,111,112],{"x":97,"y":84,"style":76},"Excel recalculates",[60,114,115],{"x":97,"y":88,"style":76},"delegated token required",[60,117,121],{"x":118,"y":119,"style":120},"380","200","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","small, targeted writes belong in the workbook API",[123,124,126],"h2",{"id":125},"prerequisites","Prerequisites",[128,129,134],"pre",{"className":130,"code":131,"language":132,"meta":133,"style":133},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install msal requests pandas openpyxl\n","bash","",[135,136,137],"code",{"__ignoreMap":133},[138,139,141,145,149,152,155,158],"span",{"class":67,"line":140},1,[138,142,144],{"class":143},"sMTad","pip",[138,146,148],{"class":147},"srMev"," install",[138,150,151],{"class":147}," msal",[138,153,154],{"class":147}," requests",[138,156,157],{"class":147}," pandas",[138,159,160],{"class":147}," openpyxl\n",[162,163,164,181],"ul",{},[165,166,167,168,171,172,175,176,180],"li",{},"A ",[14,169,170],{},"delegated"," access token with ",[135,173,174],{},"Files.ReadWrite",". The workbook endpoints do not support application permissions; the\ndevice-code-and-cache setup in ",[19,177,179],{"href":178},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation\u002F","Authenticate to Microsoft Graph with MSAL for Excel Automation","\nprovides one that works on a schedule.",[165,182,183,184,187,188,190,191,194,195,198],{},"The drive ID and item ID of an ",[135,185,186],{},".xlsx"," workbook. The workbook API works on ",[135,189,186],{}," files only — not ",[135,192,193],{},".xls",", and not ",[135,196,197],{},".csv",".",[123,200,202],{"id":201},"step-1-build-the-workbook-base-url","Step 1 — Build the workbook base URL",[10,204,205,206,209],{},"Every workbook call hangs off the file's item URL followed by ",[135,207,208],{},"\u002Fworkbook",". Build it once, and give the session a helper that\nraises with Graph's error message, which for the workbook API is usually specific about the problem.",[128,211,215],{"className":212,"code":213,"language":214,"meta":133,"style":133},"language-python shiki shiki-themes github-light github-dark-high-contrast","import os\nfrom urllib.parse import quote\nimport requests\n\nGRAPH = \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\nDRIVE_ID = os.environ[\"GRAPH_DRIVE_ID\"]\nITEM_ID = os.environ[\"GRAPH_ITEM_ID\"]\nBOOK = f\"{GRAPH}\u002Fdrives\u002F{DRIVE_ID}\u002Fitems\u002F{ITEM_ID}\u002Fworkbook\"\n\nsession = requests.Session()\nsession.headers[\"Authorization\"] = \"Bearer \" + os.environ.get(\"GRAPH_DELEGATED_TOKEN\", \"token\")\n\ndef call(method: str, url: str, **kwargs) -> dict:\n    response = session.request(method, url, timeout=60, **kwargs)\n    if response.status_code >= 400:\n        raise RuntimeError(f\"{method} {url} -> {response.status_code}: {response.text[:400]}\")\n    return response.json() if response.content else {}\n\ndef sheet_url(name: str) -> str:\n    return f\"{BOOK}\u002Fworksheets\u002F{quote(name)}\"\n\nprint([ws[\"name\"] for ws in call(\"GET\", f\"{BOOK}\u002Fworksheets\")[\"value\"]])\n","python",[135,216,217,227,241,249,256,269,286,301,345,350,362,397,402,437,464,481,543,564,569,589,617,622],{"__ignoreMap":133},[138,218,219,223],{"class":67,"line":140},[138,220,222],{"class":221},"s-kum","import",[138,224,226],{"class":225},"skGVy"," os\n",[138,228,230,233,236,238],{"class":67,"line":229},2,[138,231,232],{"class":221},"from",[138,234,235],{"class":225}," urllib.parse ",[138,237,222],{"class":221},[138,239,240],{"class":225}," quote\n",[138,242,244,246],{"class":67,"line":243},3,[138,245,222],{"class":221},[138,247,248],{"class":225}," requests\n",[138,250,252],{"class":67,"line":251},4,[138,253,255],{"emptyLinePlaceholder":254},true,"\n",[138,257,259,263,266],{"class":67,"line":258},5,[138,260,262],{"class":261},"sP0c6","GRAPH",[138,264,265],{"class":221}," =",[138,267,268],{"class":147}," \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\n",[138,270,272,275,277,280,283],{"class":67,"line":271},6,[138,273,274],{"class":261},"DRIVE_ID",[138,276,265],{"class":221},[138,278,279],{"class":225}," os.environ[",[138,281,282],{"class":147},"\"GRAPH_DRIVE_ID\"",[138,284,285],{"class":225},"]\n",[138,287,289,292,294,296,299],{"class":67,"line":288},7,[138,290,291],{"class":261},"ITEM_ID",[138,293,265],{"class":221},[138,295,279],{"class":225},[138,297,298],{"class":147},"\"GRAPH_ITEM_ID\"",[138,300,285],{"class":225},[138,302,304,307,309,312,315,319,321,324,327,329,331,333,336,338,340,342],{"class":67,"line":303},8,[138,305,306],{"class":261},"BOOK",[138,308,265],{"class":221},[138,310,311],{"class":221}," f",[138,313,314],{"class":147},"\"",[138,316,318],{"class":317},"sSjpA","{",[138,320,262],{"class":261},[138,322,323],{"class":317},"}",[138,325,326],{"class":147},"\u002Fdrives\u002F",[138,328,318],{"class":317},[138,330,274],{"class":261},[138,332,323],{"class":317},[138,334,335],{"class":147},"\u002Fitems\u002F",[138,337,318],{"class":317},[138,339,291],{"class":261},[138,341,323],{"class":317},[138,343,344],{"class":147},"\u002Fworkbook\"\n",[138,346,348],{"class":67,"line":347},9,[138,349,255],{"emptyLinePlaceholder":254},[138,351,353,356,359],{"class":67,"line":352},10,[138,354,355],{"class":225},"session ",[138,357,358],{"class":221},"=",[138,360,361],{"class":225}," requests.Session()\n",[138,363,365,368,371,374,376,379,382,385,388,391,394],{"class":67,"line":364},11,[138,366,367],{"class":225},"session.headers[",[138,369,370],{"class":147},"\"Authorization\"",[138,372,373],{"class":225},"] ",[138,375,358],{"class":221},[138,377,378],{"class":147}," \"Bearer \"",[138,380,381],{"class":221}," +",[138,383,384],{"class":225}," os.environ.get(",[138,386,387],{"class":147},"\"GRAPH_DELEGATED_TOKEN\"",[138,389,390],{"class":225},", ",[138,392,393],{"class":147},"\"token\"",[138,395,396],{"class":225},")\n",[138,398,400],{"class":67,"line":399},12,[138,401,255],{"emptyLinePlaceholder":254},[138,403,405,408,412,415,418,421,423,425,428,431,434],{"class":67,"line":404},13,[138,406,407],{"class":221},"def",[138,409,411],{"class":410},"s_Opv"," call",[138,413,414],{"class":225},"(method: ",[138,416,417],{"class":261},"str",[138,419,420],{"class":225},", url: ",[138,422,417],{"class":261},[138,424,390],{"class":225},[138,426,427],{"class":221},"**",[138,429,430],{"class":225},"kwargs) -> ",[138,432,433],{"class":261},"dict",[138,435,436],{"class":225},":\n",[138,438,440,443,445,448,452,454,457,459,461],{"class":67,"line":439},14,[138,441,442],{"class":225},"    response ",[138,444,358],{"class":221},[138,446,447],{"class":225}," session.request(method, url, ",[138,449,451],{"class":450},"sa561","timeout",[138,453,358],{"class":221},[138,455,456],{"class":261},"60",[138,458,390],{"class":225},[138,460,427],{"class":221},[138,462,463],{"class":225},"kwargs)\n",[138,465,467,470,473,476,479],{"class":67,"line":466},15,[138,468,469],{"class":221},"    if",[138,471,472],{"class":225}," response.status_code ",[138,474,475],{"class":221},">=",[138,477,478],{"class":261}," 400",[138,480,436],{"class":225},[138,482,484,487,490,493,496,498,500,503,505,508,511,513,516,518,521,523,526,528,531,534,537,539,541],{"class":67,"line":483},16,[138,485,486],{"class":221},"        raise",[138,488,489],{"class":261}," RuntimeError",[138,491,492],{"class":225},"(",[138,494,495],{"class":221},"f",[138,497,314],{"class":147},[138,499,318],{"class":317},[138,501,502],{"class":225},"method",[138,504,323],{"class":317},[138,506,507],{"class":317}," {",[138,509,510],{"class":225},"url",[138,512,323],{"class":317},[138,514,515],{"class":147}," -> ",[138,517,318],{"class":317},[138,519,520],{"class":225},"response.status_code",[138,522,323],{"class":317},[138,524,525],{"class":147},": ",[138,527,318],{"class":317},[138,529,530],{"class":225},"response.text[:",[138,532,533],{"class":261},"400",[138,535,536],{"class":225},"]",[138,538,323],{"class":317},[138,540,314],{"class":147},[138,542,396],{"class":225},[138,544,546,549,552,555,558,561],{"class":67,"line":545},17,[138,547,548],{"class":221},"    return",[138,550,551],{"class":225}," response.json() ",[138,553,554],{"class":221},"if",[138,556,557],{"class":225}," response.content ",[138,559,560],{"class":221},"else",[138,562,563],{"class":225}," {}\n",[138,565,567],{"class":67,"line":566},18,[138,568,255],{"emptyLinePlaceholder":254},[138,570,572,574,577,580,582,585,587],{"class":67,"line":571},19,[138,573,407],{"class":221},[138,575,576],{"class":410}," sheet_url",[138,578,579],{"class":225},"(name: ",[138,581,417],{"class":261},[138,583,584],{"class":225},") -> ",[138,586,417],{"class":261},[138,588,436],{"class":225},[138,590,592,594,596,598,600,602,604,607,609,612,614],{"class":67,"line":591},20,[138,593,548],{"class":221},[138,595,311],{"class":221},[138,597,314],{"class":147},[138,599,318],{"class":317},[138,601,306],{"class":261},[138,603,323],{"class":317},[138,605,606],{"class":147},"\u002Fworksheets\u002F",[138,608,318],{"class":317},[138,610,611],{"class":225},"quote(name)",[138,613,323],{"class":317},[138,615,616],{"class":147},"\"\n",[138,618,620],{"class":67,"line":619},21,[138,621,255],{"emptyLinePlaceholder":254},[138,623,625,628,631,634,636,639,642,645,648,651,653,655,657,659,661,663,666,669,672],{"class":67,"line":624},22,[138,626,627],{"class":261},"print",[138,629,630],{"class":225},"([ws[",[138,632,633],{"class":147},"\"name\"",[138,635,373],{"class":225},[138,637,638],{"class":221},"for",[138,640,641],{"class":225}," ws ",[138,643,644],{"class":221},"in",[138,646,647],{"class":225}," call(",[138,649,650],{"class":147},"\"GET\"",[138,652,390],{"class":225},[138,654,495],{"class":221},[138,656,314],{"class":147},[138,658,318],{"class":317},[138,660,306],{"class":261},[138,662,323],{"class":317},[138,664,665],{"class":147},"\u002Fworksheets\"",[138,667,668],{"class":225},")[",[138,670,671],{"class":147},"\"value\"",[138,673,674],{"class":225},"]])\n",[10,676,677,678,681],{},"Quoting the worksheet name handles spaces and punctuation in tab names. Reuse this ",[135,679,680],{},"call"," helper for everything that follows;\nthe other guides in this section build on the same shape.",[123,683,685],{"id":684},"step-2-read-a-range-into-pandas","Step 2 — Read a range into pandas",[10,687,688,691,692,695],{},[135,689,690],{},"range(address='A1:C20')"," returns a JSON object whose ",[135,693,694],{},"values"," property is a list of rows. The first row is the header when the\nrange starts at one, so turning it into a DataFrame is a single constructor call.",[128,697,699],{"className":212,"code":698,"language":214,"meta":133,"style":133},"import pandas as pd\n\ndef read_range(sheet: str, address: str) -> pd.DataFrame:\n    data = call(\"GET\", f\"{sheet_url(sheet)}\u002Frange(address='{address}')\")\n    header, *rows = data[\"values\"]\n    return pd.DataFrame(rows, columns=header)\n\nsales = read_range(\"Sales\", \"A1:C3\")\nprint(sales)\n",[135,700,701,714,718,738,777,798,813,817,837],{"__ignoreMap":133},[138,702,703,705,708,711],{"class":67,"line":140},[138,704,222],{"class":221},[138,706,707],{"class":225}," pandas ",[138,709,710],{"class":221},"as",[138,712,713],{"class":225}," pd\n",[138,715,716],{"class":67,"line":229},[138,717,255],{"emptyLinePlaceholder":254},[138,719,720,722,725,728,730,733,735],{"class":67,"line":243},[138,721,407],{"class":221},[138,723,724],{"class":410}," read_range",[138,726,727],{"class":225},"(sheet: ",[138,729,417],{"class":261},[138,731,732],{"class":225},", address: ",[138,734,417],{"class":261},[138,736,737],{"class":225},") -> pd.DataFrame:\n",[138,739,740,743,745,747,749,751,753,755,757,760,762,765,767,770,772,775],{"class":67,"line":251},[138,741,742],{"class":225},"    data ",[138,744,358],{"class":221},[138,746,647],{"class":225},[138,748,650],{"class":147},[138,750,390],{"class":225},[138,752,495],{"class":221},[138,754,314],{"class":147},[138,756,318],{"class":317},[138,758,759],{"class":225},"sheet_url(sheet)",[138,761,323],{"class":317},[138,763,764],{"class":147},"\u002Frange(address='",[138,766,318],{"class":317},[138,768,769],{"class":225},"address",[138,771,323],{"class":317},[138,773,774],{"class":147},"')\"",[138,776,396],{"class":225},[138,778,779,782,785,788,790,793,796],{"class":67,"line":258},[138,780,781],{"class":225},"    header, ",[138,783,784],{"class":221},"*",[138,786,787],{"class":225},"rows ",[138,789,358],{"class":221},[138,791,792],{"class":225}," data[",[138,794,795],{"class":147},"\"values\"",[138,797,285],{"class":225},[138,799,800,802,805,808,810],{"class":67,"line":271},[138,801,548],{"class":221},[138,803,804],{"class":225}," pd.DataFrame(rows, ",[138,806,807],{"class":450},"columns",[138,809,358],{"class":221},[138,811,812],{"class":225},"header)\n",[138,814,815],{"class":67,"line":288},[138,816,255],{"emptyLinePlaceholder":254},[138,818,819,822,824,827,830,832,835],{"class":67,"line":303},[138,820,821],{"class":225},"sales ",[138,823,358],{"class":221},[138,825,826],{"class":225}," read_range(",[138,828,829],{"class":147},"\"Sales\"",[138,831,390],{"class":225},[138,833,834],{"class":147},"\"A1:C3\"",[138,836,396],{"class":225},[138,838,839,841],{"class":67,"line":347},[138,840,627],{"class":261},[138,842,843],{"class":225},"(sales)\n",[10,845,846,847,849,850,853,854,857,858,860,861,863],{},"The response also carries ",[135,848,60],{}," — the values as formatted strings, exactly as Excel displays them — as well as ",[135,851,852],{},"formulas"," and\n",[135,855,856],{},"numberFormat",". ",[135,859,694],{}," is what you want for analysis, because it keeps numbers as numbers; ",[135,862,60],{}," is useful when you need to see a\ndate or currency the way a reader does.",[123,865,867],{"id":866},"step-3-read-whatever-is-used-on-a-sheet","Step 3 — Read whatever is used on a sheet",[10,869,870,871,874,875,878],{},"When the size of the data is not known in advance, ask for the sheet's ",[14,872,873],{},"used range",". The ",[135,876,877],{},"valuesOnly=true"," argument ignores cells that\nhave formatting but no value, which stops a sheet where somebody once coloured row 5,000 from returning thousands of empty rows.",[128,880,882],{"className":212,"code":881,"language":214,"meta":133,"style":133},"def read_used_range(sheet: str) -> pd.DataFrame:\n    data = call(\"GET\", f\"{sheet_url(sheet)}\u002FusedRange(valuesOnly=true)\")\n    header, *rows = data[\"values\"]\n    frame = pd.DataFrame(rows, columns=header)\n    print(\"used range:\", data[\"address\"])\n    return frame.replace(\"\", pd.NA).dropna(how=\"all\")\n\nused = read_used_range(\"Sales\")\nprint(used.shape)\n",[135,883,884,897,924,940,955,974,1003,1007,1021],{"__ignoreMap":133},[138,885,886,888,891,893,895],{"class":67,"line":140},[138,887,407],{"class":221},[138,889,890],{"class":410}," read_used_range",[138,892,727],{"class":225},[138,894,417],{"class":261},[138,896,737],{"class":225},[138,898,899,901,903,905,907,909,911,913,915,917,919,922],{"class":67,"line":229},[138,900,742],{"class":225},[138,902,358],{"class":221},[138,904,647],{"class":225},[138,906,650],{"class":147},[138,908,390],{"class":225},[138,910,495],{"class":221},[138,912,314],{"class":147},[138,914,318],{"class":317},[138,916,759],{"class":225},[138,918,323],{"class":317},[138,920,921],{"class":147},"\u002FusedRange(valuesOnly=true)\"",[138,923,396],{"class":225},[138,925,926,928,930,932,934,936,938],{"class":67,"line":243},[138,927,781],{"class":225},[138,929,784],{"class":221},[138,931,787],{"class":225},[138,933,358],{"class":221},[138,935,792],{"class":225},[138,937,795],{"class":147},[138,939,285],{"class":225},[138,941,942,945,947,949,951,953],{"class":67,"line":251},[138,943,944],{"class":225},"    frame ",[138,946,358],{"class":221},[138,948,804],{"class":225},[138,950,807],{"class":450},[138,952,358],{"class":221},[138,954,812],{"class":225},[138,956,957,960,962,965,968,971],{"class":67,"line":258},[138,958,959],{"class":261},"    print",[138,961,492],{"class":225},[138,963,964],{"class":147},"\"used range:\"",[138,966,967],{"class":225},", data[",[138,969,970],{"class":147},"\"address\"",[138,972,973],{"class":225},"])\n",[138,975,976,978,981,984,987,990,993,996,998,1001],{"class":67,"line":271},[138,977,548],{"class":221},[138,979,980],{"class":225}," frame.replace(",[138,982,983],{"class":147},"\"\"",[138,985,986],{"class":225},", pd.",[138,988,989],{"class":261},"NA",[138,991,992],{"class":225},").dropna(",[138,994,995],{"class":450},"how",[138,997,358],{"class":221},[138,999,1000],{"class":147},"\"all\"",[138,1002,396],{"class":225},[138,1004,1005],{"class":67,"line":288},[138,1006,255],{"emptyLinePlaceholder":254},[138,1008,1009,1012,1014,1017,1019],{"class":67,"line":303},[138,1010,1011],{"class":225},"used ",[138,1013,358],{"class":221},[138,1015,1016],{"class":225}," read_used_range(",[138,1018,829],{"class":147},[138,1020,396],{"class":225},[138,1022,1023,1025],{"class":67,"line":347},[138,1024,627],{"class":261},[138,1026,1027],{"class":225},"(used.shape)\n",[10,1029,1030,1031,1034,1035,1038],{},"Empty cells come back as empty strings rather than nulls, which is why the ",[135,1032,1033],{},"replace"," is there before any blank-row cleanup. The address in the\nresponse, such as ",[135,1036,1037],{},"Sales!A1:C3",", is worth logging: it shows at a glance when a sheet has grown unexpectedly.",[25,1040,34,1046,34,1049,34,1052,34,1056,34,1061,34,1064,34,1070,34,1073,34,1077,34,1080,34,1084,34,1087,34,1092,34,1096,34,1100,34,1102,34,1105,34,1107,34,1110,34,1112,34,1116,34,1118,34,1122,34,1124,34,1127,34,1129,34,1132,34,1135,34,1139,34,1141,34,1144,34,1146,34,1149,34,1151,34,1154,34,1161,34,1166,34,1169,34,1173,34,1175,34,1177,34,1179,34,1181,34,1185,34,1187,34,1189,34,1191,34,1193,34,1197,34,1199,34,1201,34,1203,34,1206,34,1210,34,1212,34,1214,34,1216,34,1219,34,1222,34,1226,34,1228,34,1230,34,1233,34,1238],{"viewBox":1041,"role":28,"ariaLabelledBy":1042,"xmlns":32,"style":1045},"0 0 506 344",[1043,1044],"rng-used-t","rng-used-d","width:100%;max-width:506px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,1047,1048],{"id":1043},"Used range with and without valuesOnly",[40,1050,1051],{"id":1044},"A sheet holds data in A1 to C3, but row 8 once had a fill colour applied. The plain used range extends to row 8 because of formatting; with valuesOnly equal to true it stops at row 3.",[44,1053],{"x":46,"y":46,"width":1054,"height":1055,"fill":49},"506","344",[44,1057],{"x":52,"y":52,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"30","#e7ebef","var(--line,#cdd5e6)",[44,1062],{"x":70,"y":52,"width":1063,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"80",[60,1065,1069],{"x":1066,"y":1067,"style":1068},"96","40","font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[44,1071],{"x":1072,"y":52,"width":1063,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"136",[60,1074,1076],{"x":1075,"y":1067,"style":1068},"176","B",[44,1078],{"x":1079,"y":52,"width":1063,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"216",[60,1081,1083],{"x":1082,"y":1067,"style":1068},"256","C",[44,1085],{"x":52,"y":1086,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"50",[60,1088,1091],{"x":1089,"y":1090,"style":1068},"38","70","1",[44,1093],{"x":70,"y":1086,"width":1063,"height":1058,"rx":46,"fill":1094,"stroke":1095,"style":72},"#f0f4ff","var(--brand,#5b5cf0)",[60,1097,1099],{"x":1066,"y":1090,"style":1098},"font-size:11.5px;font-weight:600;fill:var(--brand-strong,#4338ca);text-anchor:middle","Region",[44,1101],{"x":1072,"y":1086,"width":1063,"height":1058,"rx":46,"fill":1094,"stroke":1095,"style":72},[60,1103,1104],{"x":1075,"y":1090,"style":1098},"Rep",[44,1106],{"x":1079,"y":1086,"width":1063,"height":1058,"rx":46,"fill":1094,"stroke":1095,"style":72},[60,1108,1109],{"x":1082,"y":1090,"style":1098},"Amount",[44,1111],{"x":52,"y":1063,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},[60,1113,1115],{"x":1089,"y":1114,"style":1068},"100","2",[44,1117],{"x":70,"y":1063,"width":1063,"height":1058,"rx":46,"fill":93,"stroke":94,"style":72},[60,1119,1121],{"x":1066,"y":1114,"style":1120},"font-size:11.5px;font-weight:600;fill:var(--teal-ink,#0b6157);text-anchor:middle","North",[44,1123],{"x":1072,"y":1063,"width":1063,"height":1058,"rx":46,"fill":93,"stroke":94,"style":72},[60,1125,1126],{"x":1075,"y":1114,"style":1120},"Ana",[44,1128],{"x":1079,"y":1063,"width":1063,"height":1058,"rx":46,"fill":93,"stroke":94,"style":72},[60,1130,1131],{"x":1082,"y":1114,"style":1120},"1200",[44,1133],{"x":52,"y":1134,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"110",[60,1136,1138],{"x":1089,"y":1137,"style":1068},"130","3",[44,1140],{"x":70,"y":1134,"width":1063,"height":1058,"rx":46,"fill":93,"stroke":94,"style":72},[60,1142,1143],{"x":1066,"y":1137,"style":1120},"South",[44,1145],{"x":1072,"y":1134,"width":1063,"height":1058,"rx":46,"fill":93,"stroke":94,"style":72},[60,1147,1148],{"x":1075,"y":1137,"style":1120},"Ben",[44,1150],{"x":1079,"y":1134,"width":1063,"height":1058,"rx":46,"fill":93,"stroke":94,"style":72},[60,1152,1153],{"x":1082,"y":1137,"style":1120},"950",[44,1155],{"x":1156,"y":1157,"width":1158,"height":1159,"rx":1160,"fill":93,"stroke":94,"style":72},"316","112","170","26","8",[60,1162,1165],{"x":1163,"y":1164,"style":1120},"401","129","valuesOnly stops here",[44,1167],{"x":52,"y":1168,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"140",[60,1170,1172],{"x":1089,"y":1171,"style":1068},"160","4",[44,1174],{"x":70,"y":1168,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1176],{"x":1072,"y":1168,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1178],{"x":1079,"y":1168,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1180],{"x":52,"y":1158,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},[60,1182,1184],{"x":1089,"y":1183,"style":1068},"190","5",[44,1186],{"x":70,"y":1158,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1188],{"x":1072,"y":1158,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1190],{"x":1079,"y":1158,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1192],{"x":52,"y":119,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},[60,1194,1196],{"x":1089,"y":1195,"style":1068},"220","6",[44,1198],{"x":70,"y":119,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1200],{"x":1072,"y":119,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1202],{"x":1079,"y":119,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1204],{"x":52,"y":1205,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"230",[60,1207,1209],{"x":1089,"y":1208,"style":1068},"250","7",[44,1211],{"x":70,"y":1205,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1213],{"x":1072,"y":1205,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1215],{"x":1079,"y":1205,"width":1063,"height":1058,"rx":46,"fill":49,"stroke":1060,"style":72},[44,1217],{"x":52,"y":1218,"width":69,"height":1058,"rx":46,"fill":1059,"stroke":1060,"style":72},"260",[60,1220,1160],{"x":1089,"y":1221,"style":1068},"280",[44,1223],{"x":70,"y":1218,"width":1063,"height":1058,"rx":46,"fill":1224,"stroke":1225,"style":72},"#fdefd8","var(--gold,#b4740a)",[44,1227],{"x":1072,"y":1218,"width":1063,"height":1058,"rx":46,"fill":1224,"stroke":1225,"style":72},[44,1229],{"x":1079,"y":1218,"width":1063,"height":1058,"rx":46,"fill":1224,"stroke":1225,"style":72},[44,1231],{"x":1156,"y":1232,"width":1158,"height":1159,"rx":1160,"fill":1224,"stroke":1225,"style":72},"262",[60,1234,1237],{"x":1163,"y":1235,"style":1236},"279","font-size:11.5px;font-weight:600;fill:var(--gold-ink,#7a4e06);text-anchor:middle","formatting only: row 8",[60,1239,1242],{"x":1240,"y":1241,"style":120},"253","322","usedRange(valuesOnly=true) ignores format-only cells",[123,1244,1246],{"id":1245},"step-4-write-a-dataframe-back-with-the-right-address","Step 4 — Write a DataFrame back with the right address",[10,1248,1249,1250,1253,1254,1256,1257,1260],{},"Writing is a ",[135,1251,1252],{},"PATCH"," to a range with a ",[135,1255,694],{}," array whose dimensions ",[14,1258,1259],{},"exactly"," match the address. Compute the address from the frame's\nshape instead of typing it, so the two can never disagree, and convert every value into something JSON can carry.",[128,1262,1264],{"className":212,"code":1263,"language":214,"meta":133,"style":133},"from openpyxl.utils import get_column_letter\n\ndef address_for(frame: pd.DataFrame, top_left_row: int = 1, top_left_col: int = 1, header: bool = True) -> str:\n    rows = len(frame) + (1 if header else 0)\n    first = f\"{get_column_letter(top_left_col)}{top_left_row}\"\n    last = f\"{get_column_letter(top_left_col + frame.shape[1] - 1)}{top_left_row + rows - 1}\"\n    return f\"{first}:{last}\"\n\ndef to_cell(value):\n    if value is None or (isinstance(value, float) and pd.isna(value)) or value is pd.NA:\n        return \"\"                     # empty string clears a cell; null would leave it unchanged\n    if isinstance(value, pd.Timestamp):\n        return (value.normalize() - pd.Timestamp(\"1899-12-30\")).days + value.hour \u002F 24 + value.minute \u002F 1440\n    if hasattr(value, \"item\"):\n        return value.item()           # NumPy scalar -> plain Python number\n    return value\n\ndef write_frame(sheet: str, frame: pd.DataFrame, top_left_row: int = 1, top_left_col: int = 1) -> str:\n    values = [list(frame.columns)] + [[to_cell(v) for v in row] for row in frame.itertuples(index=False)]\n    address = address_for(frame, top_left_row, top_left_col)\n    call(\"PATCH\", f\"{sheet_url(sheet)}\u002Frange(address='{address}')\", json={\"values\": values})\n    return address\n\nsummary = sales.groupby(\"Region\", as_index=False)[\"Amount\"].sum()\nprint(\"wrote\", write_frame(\"Summary\", summary))\n",[135,1265,1266,1278,1282,1326,1360,1386,1437,1464,1468,1478,1528,1540,1550,1589,1604,1614,1621,1625,1659,1711,1721,1765,1772,1777,1808],{"__ignoreMap":133},[138,1267,1268,1270,1273,1275],{"class":67,"line":140},[138,1269,232],{"class":221},[138,1271,1272],{"class":225}," openpyxl.utils ",[138,1274,222],{"class":221},[138,1276,1277],{"class":225}," get_column_letter\n",[138,1279,1280],{"class":67,"line":229},[138,1281,255],{"emptyLinePlaceholder":254},[138,1283,1284,1286,1289,1292,1295,1297,1300,1303,1305,1307,1309,1312,1315,1317,1320,1322,1324],{"class":67,"line":243},[138,1285,407],{"class":221},[138,1287,1288],{"class":410}," address_for",[138,1290,1291],{"class":225},"(frame: pd.DataFrame, top_left_row: ",[138,1293,1294],{"class":261},"int",[138,1296,265],{"class":221},[138,1298,1299],{"class":261}," 1",[138,1301,1302],{"class":225},", top_left_col: ",[138,1304,1294],{"class":261},[138,1306,265],{"class":221},[138,1308,1299],{"class":261},[138,1310,1311],{"class":225},", header: ",[138,1313,1314],{"class":261},"bool",[138,1316,265],{"class":221},[138,1318,1319],{"class":261}," True",[138,1321,584],{"class":225},[138,1323,417],{"class":261},[138,1325,436],{"class":225},[138,1327,1328,1331,1333,1336,1339,1342,1345,1347,1350,1353,1355,1358],{"class":67,"line":251},[138,1329,1330],{"class":225},"    rows ",[138,1332,358],{"class":221},[138,1334,1335],{"class":261}," len",[138,1337,1338],{"class":225},"(frame) ",[138,1340,1341],{"class":221},"+",[138,1343,1344],{"class":225}," (",[138,1346,1091],{"class":261},[138,1348,1349],{"class":221}," if",[138,1351,1352],{"class":225}," header ",[138,1354,560],{"class":221},[138,1356,1357],{"class":261}," 0",[138,1359,396],{"class":225},[138,1361,1362,1365,1367,1369,1371,1373,1376,1379,1382,1384],{"class":67,"line":258},[138,1363,1364],{"class":225},"    first ",[138,1366,358],{"class":221},[138,1368,311],{"class":221},[138,1370,314],{"class":147},[138,1372,318],{"class":317},[138,1374,1375],{"class":225},"get_column_letter(top_left_col)",[138,1377,1378],{"class":317},"}{",[138,1380,1381],{"class":225},"top_left_row",[138,1383,323],{"class":317},[138,1385,616],{"class":147},[138,1387,1388,1391,1393,1395,1397,1399,1402,1404,1407,1409,1411,1414,1416,1419,1421,1424,1426,1429,1431,1433,1435],{"class":67,"line":271},[138,1389,1390],{"class":225},"    last ",[138,1392,358],{"class":221},[138,1394,311],{"class":221},[138,1396,314],{"class":147},[138,1398,318],{"class":317},[138,1400,1401],{"class":225},"get_column_letter(top_left_col ",[138,1403,1341],{"class":221},[138,1405,1406],{"class":225}," frame.shape[",[138,1408,1091],{"class":261},[138,1410,373],{"class":225},[138,1412,1413],{"class":221},"-",[138,1415,1299],{"class":261},[138,1417,1418],{"class":225},")",[138,1420,1378],{"class":317},[138,1422,1423],{"class":225},"top_left_row ",[138,1425,1341],{"class":221},[138,1427,1428],{"class":225}," rows ",[138,1430,1413],{"class":221},[138,1432,1299],{"class":261},[138,1434,323],{"class":317},[138,1436,616],{"class":147},[138,1438,1439,1441,1443,1445,1447,1450,1452,1455,1457,1460,1462],{"class":67,"line":288},[138,1440,548],{"class":221},[138,1442,311],{"class":221},[138,1444,314],{"class":147},[138,1446,318],{"class":317},[138,1448,1449],{"class":225},"first",[138,1451,323],{"class":317},[138,1453,1454],{"class":147},":",[138,1456,318],{"class":317},[138,1458,1459],{"class":225},"last",[138,1461,323],{"class":317},[138,1463,616],{"class":147},[138,1465,1466],{"class":67,"line":303},[138,1467,255],{"emptyLinePlaceholder":254},[138,1469,1470,1472,1475],{"class":67,"line":347},[138,1471,407],{"class":221},[138,1473,1474],{"class":410}," to_cell",[138,1476,1477],{"class":225},"(value):\n",[138,1479,1480,1482,1485,1488,1491,1494,1496,1499,1502,1505,1508,1511,1514,1517,1519,1521,1524,1526],{"class":67,"line":352},[138,1481,469],{"class":221},[138,1483,1484],{"class":225}," value ",[138,1486,1487],{"class":221},"is",[138,1489,1490],{"class":261}," None",[138,1492,1493],{"class":221}," or",[138,1495,1344],{"class":225},[138,1497,1498],{"class":261},"isinstance",[138,1500,1501],{"class":225},"(value, ",[138,1503,1504],{"class":261},"float",[138,1506,1507],{"class":225},") ",[138,1509,1510],{"class":221},"and",[138,1512,1513],{"class":225}," pd.isna(value)) ",[138,1515,1516],{"class":221},"or",[138,1518,1484],{"class":225},[138,1520,1487],{"class":221},[138,1522,1523],{"class":225}," pd.",[138,1525,989],{"class":261},[138,1527,436],{"class":225},[138,1529,1530,1533,1536],{"class":67,"line":364},[138,1531,1532],{"class":221},"        return",[138,1534,1535],{"class":147}," \"\"",[138,1537,1539],{"class":1538},"s-wDw","                     # empty string clears a cell; null would leave it unchanged\n",[138,1541,1542,1544,1547],{"class":67,"line":399},[138,1543,469],{"class":221},[138,1545,1546],{"class":261}," isinstance",[138,1548,1549],{"class":225},"(value, pd.Timestamp):\n",[138,1551,1552,1554,1557,1559,1562,1565,1568,1570,1573,1576,1579,1581,1584,1586],{"class":67,"line":404},[138,1553,1532],{"class":221},[138,1555,1556],{"class":225}," (value.normalize() ",[138,1558,1413],{"class":221},[138,1560,1561],{"class":225}," pd.Timestamp(",[138,1563,1564],{"class":147},"\"1899-12-30\"",[138,1566,1567],{"class":225},")).days ",[138,1569,1341],{"class":221},[138,1571,1572],{"class":225}," value.hour ",[138,1574,1575],{"class":221},"\u002F",[138,1577,1578],{"class":261}," 24",[138,1580,381],{"class":221},[138,1582,1583],{"class":225}," value.minute ",[138,1585,1575],{"class":221},[138,1587,1588],{"class":261}," 1440\n",[138,1590,1591,1593,1596,1598,1601],{"class":67,"line":439},[138,1592,469],{"class":221},[138,1594,1595],{"class":261}," hasattr",[138,1597,1501],{"class":225},[138,1599,1600],{"class":147},"\"item\"",[138,1602,1603],{"class":225},"):\n",[138,1605,1606,1608,1611],{"class":67,"line":466},[138,1607,1532],{"class":221},[138,1609,1610],{"class":225}," value.item()           ",[138,1612,1613],{"class":1538},"# NumPy scalar -> plain Python number\n",[138,1615,1616,1618],{"class":67,"line":483},[138,1617,548],{"class":221},[138,1619,1620],{"class":225}," value\n",[138,1622,1623],{"class":67,"line":545},[138,1624,255],{"emptyLinePlaceholder":254},[138,1626,1627,1629,1632,1634,1636,1639,1641,1643,1645,1647,1649,1651,1653,1655,1657],{"class":67,"line":566},[138,1628,407],{"class":221},[138,1630,1631],{"class":410}," write_frame",[138,1633,727],{"class":225},[138,1635,417],{"class":261},[138,1637,1638],{"class":225},", frame: pd.DataFrame, top_left_row: ",[138,1640,1294],{"class":261},[138,1642,265],{"class":221},[138,1644,1299],{"class":261},[138,1646,1302],{"class":225},[138,1648,1294],{"class":261},[138,1650,265],{"class":221},[138,1652,1299],{"class":261},[138,1654,584],{"class":225},[138,1656,417],{"class":261},[138,1658,436],{"class":225},[138,1660,1661,1664,1666,1669,1672,1675,1677,1680,1682,1685,1687,1690,1692,1695,1697,1700,1703,1705,1708],{"class":67,"line":571},[138,1662,1663],{"class":225},"    values ",[138,1665,358],{"class":221},[138,1667,1668],{"class":225}," [",[138,1670,1671],{"class":261},"list",[138,1673,1674],{"class":225},"(frame.columns)] ",[138,1676,1341],{"class":221},[138,1678,1679],{"class":225}," [[to_cell(v) ",[138,1681,638],{"class":221},[138,1683,1684],{"class":225}," v ",[138,1686,644],{"class":221},[138,1688,1689],{"class":225}," row] ",[138,1691,638],{"class":221},[138,1693,1694],{"class":225}," row ",[138,1696,644],{"class":221},[138,1698,1699],{"class":225}," frame.itertuples(",[138,1701,1702],{"class":450},"index",[138,1704,358],{"class":221},[138,1706,1707],{"class":261},"False",[138,1709,1710],{"class":225},")]\n",[138,1712,1713,1716,1718],{"class":67,"line":591},[138,1714,1715],{"class":225},"    address ",[138,1717,358],{"class":221},[138,1719,1720],{"class":225}," address_for(frame, top_left_row, top_left_col)\n",[138,1722,1723,1726,1729,1731,1733,1735,1737,1739,1741,1743,1745,1747,1749,1751,1753,1756,1758,1760,1762],{"class":67,"line":619},[138,1724,1725],{"class":225},"    call(",[138,1727,1728],{"class":147},"\"PATCH\"",[138,1730,390],{"class":225},[138,1732,495],{"class":221},[138,1734,314],{"class":147},[138,1736,318],{"class":317},[138,1738,759],{"class":225},[138,1740,323],{"class":317},[138,1742,764],{"class":147},[138,1744,318],{"class":317},[138,1746,769],{"class":225},[138,1748,323],{"class":317},[138,1750,774],{"class":147},[138,1752,390],{"class":225},[138,1754,1755],{"class":450},"json",[138,1757,358],{"class":221},[138,1759,318],{"class":225},[138,1761,795],{"class":147},[138,1763,1764],{"class":225},": values})\n",[138,1766,1767,1769],{"class":67,"line":624},[138,1768,548],{"class":221},[138,1770,1771],{"class":225}," address\n",[138,1773,1775],{"class":67,"line":1774},23,[138,1776,255],{"emptyLinePlaceholder":254},[138,1778,1780,1783,1785,1788,1791,1793,1796,1798,1800,1802,1805],{"class":67,"line":1779},24,[138,1781,1782],{"class":225},"summary ",[138,1784,358],{"class":221},[138,1786,1787],{"class":225}," sales.groupby(",[138,1789,1790],{"class":147},"\"Region\"",[138,1792,390],{"class":225},[138,1794,1795],{"class":450},"as_index",[138,1797,358],{"class":221},[138,1799,1707],{"class":261},[138,1801,668],{"class":225},[138,1803,1804],{"class":147},"\"Amount\"",[138,1806,1807],{"class":225},"].sum()\n",[138,1809,1811,1813,1815,1818,1821,1824],{"class":67,"line":1810},25,[138,1812,627],{"class":261},[138,1814,492],{"class":225},[138,1816,1817],{"class":147},"\"wrote\"",[138,1819,1820],{"class":225},", write_frame(",[138,1822,1823],{"class":147},"\"Summary\"",[138,1825,1826],{"class":225},", summary))\n",[10,1828,1829,1830,1833,1834,1837,1838,1841,1842,1844,1845,1848,1849,1852],{},"Two conversions in ",[135,1831,1832],{},"to_cell"," prevent the most confusing failures. In a range update, ",[135,1835,1836],{},"null"," does not mean \"empty\" — it means \"leave this cell\nas it is\", so a ",[135,1839,1840],{},"NaN"," sent as ",[135,1843,1836],{}," silently keeps last week's value in place. Send an empty string to clear a cell instead. And NumPy\nintegers and floats are not JSON-serialisable, so ",[135,1846,1847],{},".item()"," turns them into plain Python numbers before ",[135,1850,1851],{},"requests"," encodes the body.",[123,1854,1856],{"id":1855},"step-5-send-dates-and-number-formats-deliberately","Step 5 — Send dates and number formats deliberately",[10,1858,1859,1860,1862,1863,1865,1866,198],{},"Excel stores dates as serial numbers and shows them as dates through a number format. The workbook API behaves the same way: send a serial number\nand a date format together in one ",[135,1861,1252],{},", and the cells behave like real dates for sorting, filtering and formulas. ",[135,1864,856],{}," takes an array\nwith the same dimensions as ",[135,1867,694],{},[128,1869,1871],{"className":212,"code":1870,"language":214,"meta":133,"style":133},"monthly = pd.DataFrame({\n    \"Month\": pd.to_datetime([\"2026-07-01\", \"2026-08-01\", \"2026-09-01\"]),\n    \"Revenue\": [48210.5, 51990.0, 47305.25],\n})\naddress = address_for(monthly, top_left_row=1, top_left_col=5)          # E1:F4\nvalues = [list(monthly.columns)] + [[to_cell(v) for v in row] for row in monthly.itertuples(index=False)]\nformats = [[\"General\", \"General\"]] + [[\"mmm yyyy\", \"#,##0.00\"] for _ in range(len(monthly))]\n\ncall(\"PATCH\", f\"{sheet_url('Summary')}\u002Frange(address='{address}')\",\n     json={\"values\": values, \"numberFormat\": formats})\nprint(\"dates written to\", address)\n",[135,1872,1873,1883,1907,1931,1936,1967,2010,2062,2066,2104,2124],{"__ignoreMap":133},[138,1874,1875,1878,1880],{"class":67,"line":140},[138,1876,1877],{"class":225},"monthly ",[138,1879,358],{"class":221},[138,1881,1882],{"class":225}," pd.DataFrame({\n",[138,1884,1885,1888,1891,1894,1896,1899,1901,1904],{"class":67,"line":229},[138,1886,1887],{"class":147},"    \"Month\"",[138,1889,1890],{"class":225},": pd.to_datetime([",[138,1892,1893],{"class":147},"\"2026-07-01\"",[138,1895,390],{"class":225},[138,1897,1898],{"class":147},"\"2026-08-01\"",[138,1900,390],{"class":225},[138,1902,1903],{"class":147},"\"2026-09-01\"",[138,1905,1906],{"class":225},"]),\n",[138,1908,1909,1912,1915,1918,1920,1923,1925,1928],{"class":67,"line":243},[138,1910,1911],{"class":147},"    \"Revenue\"",[138,1913,1914],{"class":225},": [",[138,1916,1917],{"class":261},"48210.5",[138,1919,390],{"class":225},[138,1921,1922],{"class":261},"51990.0",[138,1924,390],{"class":225},[138,1926,1927],{"class":261},"47305.25",[138,1929,1930],{"class":225},"],\n",[138,1932,1933],{"class":67,"line":251},[138,1934,1935],{"class":225},"})\n",[138,1937,1938,1941,1943,1946,1948,1950,1952,1954,1957,1959,1961,1964],{"class":67,"line":258},[138,1939,1940],{"class":225},"address ",[138,1942,358],{"class":221},[138,1944,1945],{"class":225}," address_for(monthly, ",[138,1947,1381],{"class":450},[138,1949,358],{"class":221},[138,1951,1091],{"class":261},[138,1953,390],{"class":225},[138,1955,1956],{"class":450},"top_left_col",[138,1958,358],{"class":221},[138,1960,1184],{"class":261},[138,1962,1963],{"class":225},")          ",[138,1965,1966],{"class":1538},"# E1:F4\n",[138,1968,1969,1972,1974,1976,1978,1981,1983,1985,1987,1989,1991,1993,1995,1997,1999,2002,2004,2006,2008],{"class":67,"line":271},[138,1970,1971],{"class":225},"values ",[138,1973,358],{"class":221},[138,1975,1668],{"class":225},[138,1977,1671],{"class":261},[138,1979,1980],{"class":225},"(monthly.columns)] ",[138,1982,1341],{"class":221},[138,1984,1679],{"class":225},[138,1986,638],{"class":221},[138,1988,1684],{"class":225},[138,1990,644],{"class":221},[138,1992,1689],{"class":225},[138,1994,638],{"class":221},[138,1996,1694],{"class":225},[138,1998,644],{"class":221},[138,2000,2001],{"class":225}," monthly.itertuples(",[138,2003,1702],{"class":450},[138,2005,358],{"class":221},[138,2007,1707],{"class":261},[138,2009,1710],{"class":225},[138,2011,2012,2015,2017,2020,2023,2025,2027,2030,2032,2034,2037,2039,2042,2044,2046,2049,2051,2054,2056,2059],{"class":67,"line":288},[138,2013,2014],{"class":225},"formats ",[138,2016,358],{"class":221},[138,2018,2019],{"class":225}," [[",[138,2021,2022],{"class":147},"\"General\"",[138,2024,390],{"class":225},[138,2026,2022],{"class":147},[138,2028,2029],{"class":225},"]] ",[138,2031,1341],{"class":221},[138,2033,2019],{"class":225},[138,2035,2036],{"class":147},"\"mmm yyyy\"",[138,2038,390],{"class":225},[138,2040,2041],{"class":147},"\"#,##0.00\"",[138,2043,373],{"class":225},[138,2045,638],{"class":221},[138,2047,2048],{"class":225}," _ ",[138,2050,644],{"class":221},[138,2052,2053],{"class":261}," range",[138,2055,492],{"class":225},[138,2057,2058],{"class":261},"len",[138,2060,2061],{"class":225},"(monthly))]\n",[138,2063,2064],{"class":67,"line":303},[138,2065,255],{"emptyLinePlaceholder":254},[138,2067,2068,2071,2073,2075,2077,2079,2081,2084,2087,2089,2091,2093,2095,2097,2099,2101],{"class":67,"line":347},[138,2069,2070],{"class":225},"call(",[138,2072,1728],{"class":147},[138,2074,390],{"class":225},[138,2076,495],{"class":221},[138,2078,314],{"class":147},[138,2080,318],{"class":317},[138,2082,2083],{"class":225},"sheet_url(",[138,2085,2086],{"class":147},"'Summary'",[138,2088,1418],{"class":225},[138,2090,323],{"class":317},[138,2092,764],{"class":147},[138,2094,318],{"class":317},[138,2096,769],{"class":225},[138,2098,323],{"class":317},[138,2100,774],{"class":147},[138,2102,2103],{"class":225},",\n",[138,2105,2106,2109,2111,2113,2115,2118,2121],{"class":67,"line":352},[138,2107,2108],{"class":450},"     json",[138,2110,358],{"class":221},[138,2112,318],{"class":225},[138,2114,795],{"class":147},[138,2116,2117],{"class":225},": values, ",[138,2119,2120],{"class":147},"\"numberFormat\"",[138,2122,2123],{"class":225},": formats})\n",[138,2125,2126,2128,2130,2133],{"class":67,"line":364},[138,2127,627],{"class":261},[138,2129,492],{"class":225},[138,2131,2132],{"class":147},"\"dates written to\"",[138,2134,2135],{"class":225},", address)\n",[10,2137,2138,2139,2141,2142,2145,2146,198],{},"Sending ISO date strings such as ",[135,2140,1903],{}," also often works, because Excel parses the text as it would typed input — but the result depends on the\nworkbook's locale, and a day-first locale turns ",[135,2143,2144],{},"2026-03-04"," into April. Serial numbers are unambiguous. The format codes are the same ones used from\nopenpyxl, listed in ",[19,2147,2149],{"href":2148},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fwrite-custom-number-format-codes-in-excel-with-python\u002F","Write Custom Number Format Codes in Excel with Python",[25,2151,34,2156,34,2159,34,2162,34,2165,34,2169,34,2173,34,2178,34,2183,34,2188,34,2191,34,2195,34,2198,34,2202,34,2206,34,2209,34,2211,34,2214,34,2217,34,2221,34,2224,34,2227,34,2232,34,2235],{"viewBox":2152,"role":28,"ariaLabelledBy":2153,"xmlns":32,"style":33},"0 0 760 156",[2154,2155],"rng-patch-t","rng-patch-d",[36,2157,2158],{"id":2154},"Anatomy of a range update request",[40,2160,2161],{"id":2155},"The address is computed from the DataFrame shape, values are converted so blanks become empty strings and dates become serial numbers, number formats are sent with matching dimensions, and a PATCH updates the range in one request.",[44,2163],{"x":46,"y":46,"width":47,"height":2164,"fill":49},"156",[44,2166],{"x":52,"y":52,"width":54,"height":2167,"rx":2168,"fill":1059,"stroke":1060,"style":58},"86","12",[60,2170,769],{"x":2171,"y":1086,"style":2172},"95","font-size:14px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle",[60,2174,2177],{"x":2171,"y":2175,"style":2176},"72","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","from frame shape",[67,2179],{"x1":2180,"y1":2181,"x2":2182,"y2":2181,"stroke":1095,"style":58},"175","63","204",[2184,2185],"polygon",{"points":2186,"fill":2187},"207,63 198,58 198,68","#5b5cf0",[44,2189],{"x":2190,"y":52,"width":54,"height":2167,"rx":2168,"fill":1094,"stroke":1095,"style":58},"210",[60,2192,694],{"x":2193,"y":1086,"style":2194},"285","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[60,2196,2197],{"x":2193,"y":2175,"style":76},"blanks → \"\",",[60,2199,2201],{"x":2193,"y":2200,"style":76},"92","dates → serials",[67,2203],{"x1":2204,"y1":2181,"x2":2205,"y2":2181,"stroke":1095,"style":58},"365","394",[2184,2207],{"points":2208,"fill":2187},"397,63 388,58 388,68",[44,2210],{"x":533,"y":52,"width":54,"height":2167,"rx":2168,"fill":1094,"stroke":1095,"style":58},[60,2212,856],{"x":2213,"y":1086,"style":2194},"475",[60,2215,2216],{"x":2213,"y":2175,"style":76},"same dimensions",[67,2218],{"x1":2219,"y1":2181,"x2":2220,"y2":2181,"stroke":1095,"style":58},"555","584",[2184,2222],{"points":2223,"fill":2187},"587,63 578,58 578,68",[44,2225],{"x":2226,"y":52,"width":54,"height":2167,"rx":2168,"fill":93,"stroke":94,"style":58},"590",[60,2228,2231],{"x":2229,"y":1086,"style":2230},"665","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","PATCH range",[60,2233,2234],{"x":2229,"y":2175,"style":76},"one request",[60,2236,2237],{"x":118,"y":1168,"style":120},"dimensions must match exactly, in every array you send",[123,2239,2241],{"id":2240},"step-6-write-formulas-instead-of-values","Step 6 — Write formulas instead of values",[10,2243,2244,2245,2247,2248,2250],{},"The same ",[135,2246,1252],{}," accepts a ",[135,2249,852],{}," array. Writing a formula lets Excel compute totals and ratios in the workbook, so they stay correct when someone\nedits the numbers by hand later.",[128,2252,2254],{"className":212,"code":2253,"language":214,"meta":133,"style":133},"last_row = len(summary) + 1\ntotal_row = last_row + 1\ncall(\"PATCH\", f\"{sheet_url('Summary')}\u002Frange(address='A{total_row}:B{total_row}')\",\n     json={\"formulas\": [[\"Total\", f\"=SUM(B2:B{last_row})\"]]})\nprint(\"total formula in row\", total_row)\n",[135,2255,2256,2273,2287,2332,2369],{"__ignoreMap":133},[138,2257,2258,2261,2263,2265,2268,2270],{"class":67,"line":140},[138,2259,2260],{"class":225},"last_row ",[138,2262,358],{"class":221},[138,2264,1335],{"class":261},[138,2266,2267],{"class":225},"(summary) ",[138,2269,1341],{"class":221},[138,2271,2272],{"class":261}," 1\n",[138,2274,2275,2278,2280,2283,2285],{"class":67,"line":229},[138,2276,2277],{"class":225},"total_row ",[138,2279,358],{"class":221},[138,2281,2282],{"class":225}," last_row ",[138,2284,1341],{"class":221},[138,2286,2272],{"class":261},[138,2288,2289,2291,2293,2295,2297,2299,2301,2303,2305,2307,2309,2312,2314,2317,2319,2322,2324,2326,2328,2330],{"class":67,"line":243},[138,2290,2070],{"class":225},[138,2292,1728],{"class":147},[138,2294,390],{"class":225},[138,2296,495],{"class":221},[138,2298,314],{"class":147},[138,2300,318],{"class":317},[138,2302,2083],{"class":225},[138,2304,2086],{"class":147},[138,2306,1418],{"class":225},[138,2308,323],{"class":317},[138,2310,2311],{"class":147},"\u002Frange(address='A",[138,2313,318],{"class":317},[138,2315,2316],{"class":225},"total_row",[138,2318,323],{"class":317},[138,2320,2321],{"class":147},":B",[138,2323,318],{"class":317},[138,2325,2316],{"class":225},[138,2327,323],{"class":317},[138,2329,774],{"class":147},[138,2331,2103],{"class":225},[138,2333,2334,2336,2338,2340,2343,2346,2349,2351,2353,2356,2358,2361,2363,2366],{"class":67,"line":251},[138,2335,2108],{"class":450},[138,2337,358],{"class":221},[138,2339,318],{"class":225},[138,2341,2342],{"class":147},"\"formulas\"",[138,2344,2345],{"class":225},": [[",[138,2347,2348],{"class":147},"\"Total\"",[138,2350,390],{"class":225},[138,2352,495],{"class":221},[138,2354,2355],{"class":147},"\"=SUM(B2:B",[138,2357,318],{"class":317},[138,2359,2360],{"class":225},"last_row",[138,2362,323],{"class":317},[138,2364,2365],{"class":147},")\"",[138,2367,2368],{"class":225},"]]})\n",[138,2370,2371,2373,2375,2378],{"class":67,"line":258},[138,2372,627],{"class":261},[138,2374,492],{"class":225},[138,2376,2377],{"class":147},"\"total formula in row\"",[138,2379,2380],{"class":225},", total_row)\n",[10,2382,2383,2384,2387,2388,2390,2391,198],{},"A plain string such as ",[135,2385,2386],{},"Total"," is accepted in the ",[135,2389,852],{}," array and written as a constant. The formula is calculated by the service according to the\nworkbook's calculation mode; if the workbook is set to manual calculation, trigger a recalculation as described in\n",[19,2392,2394],{"href":2393},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions\u002F","Recalculate Excel Online Workbooks with Graph Sessions",[123,2396,2398],{"id":2397},"common-pitfalls","Common pitfalls",[2400,2401,2402,2418],"table",{},[2403,2404,2405],"thead",{},[2406,2407,2408,2412,2415],"tr",{},[2409,2410,2411],"th",{},"Symptom",[2409,2413,2414],{},"Cause",[2409,2416,2417],{},"Fix",[2419,2420,2421,2438,2456,2471,2484,2495],"tbody",{},[2406,2422,2423,2430,2435],{},[2424,2425,2426,2429],"td",{},[135,2427,2428],{},"InvalidArgument"," on PATCH",[2424,2431,2432,2434],{},[135,2433,694],{}," dimensions differ from the address",[2424,2436,2437],{},"Compute the address from the frame shape",[2406,2439,2440,2443,2450],{},[2424,2441,2442],{},"Old values remain in cleared cells",[2424,2444,2445,1841,2447,2449],{},[135,2446,1840],{},[135,2448,1836],{},", which means \"skip\"",[2424,2451,2452,2453,2455],{},"Send ",[135,2454,983],{}," to clear",[2406,2457,2458,2463,2466],{},[2424,2459,2460],{},[135,2461,2462],{},"TypeError: Object of type int64 is not JSON serializable",[2424,2464,2465],{},"NumPy scalars in the body",[2424,2467,2468,2469],{},"Convert with ",[135,2470,1847],{},[2406,2472,2473,2476,2479],{},[2424,2474,2475],{},"Dates land as text or with day and month swapped",[2424,2477,2478],{},"ISO strings parsed by locale",[2424,2480,2481,2482],{},"Send serial numbers plus a date ",[135,2483,856],{},[2406,2485,2486,2489,2492],{},[2424,2487,2488],{},"Workbook API fails on an app-only token",[2424,2490,2491],{},"Application permissions not supported",[2424,2493,2494],{},"Use a delegated token",[2406,2496,2497,2503,2506],{},[2424,2498,2499,2502],{},[135,2500,2501],{},"404"," on the worksheet",[2424,2504,2505],{},"Tab name with spaces or punctuation not encoded",[2424,2507,2508,2511],{},[135,2509,2510],{},"quote()"," the name",[123,2513,2515],{"id":2514},"performance-and-scale-notes","Performance and scale notes",[10,2517,2518,2519,2521,2522,2525,2526,2529],{},"Each call opens the workbook in the online service, which is noticeably slower than a local file operation, so write in blocks rather than cell by cell:\none ",[135,2520,1252],{}," for a 500-row table is far faster than 500 single-cell updates. For more than a couple of calls against the same file, create a workbook session\nand pass its ID with each request, which avoids reopening the workbook every time. Very large writes — tens of thousands of cells — are better split into\nchunks of a few thousand rows, sent sequentially, with a retry on ",[135,2523,2524],{},"429"," and ",[135,2527,2528],{},"504"," responses.",[123,2531,2533],{"id":2532},"conclusion","Conclusion",[10,2535,2536,2537,2540,2541,2544,2545,2547,2548,2550,2551,2553],{},"The workbook API edits a OneDrive or SharePoint workbook in place, which makes it the right tool whenever people may have the file open. Read with\n",[135,2538,2539],{},"range(address=…)"," or ",[135,2542,2543],{},"usedRange(valuesOnly=true)"," and build the DataFrame from ",[135,2546,694],{},". Write with a ",[135,2549,1252],{}," whose address is computed from the frame's\nshape, convert blanks to empty strings and NumPy scalars to Python numbers, and send dates as serial numbers with a matching ",[135,2552,856],{},". Use a delegated\ntoken, write in blocks, and move to sessions once a job makes more than a few calls.",[123,2555,2557],{"id":2556},"frequently-asked-questions","Frequently asked questions",[10,2559,2560,2563],{},[14,2561,2562],{},"Can I append rows below existing data with a range update?","\nYou can, by reading the used range to find the last row and writing below it. If the data is an Excel table, adding rows to the table is simpler and keeps formulas and formatting extending automatically.",[10,2565,2566,2569],{},[14,2567,2568],{},"Why does my range update succeed but nothing changes?","\nMost often the values were null, which the API treats as leave unchanged. Also check that you wrote to the sheet you are looking at, and that the request was not made in a non-persistent session.",[10,2571,2572,2575],{},[14,2573,2574],{},"Can I write to a workbook that is open in Excel for the web?","\nYes. The workbook API works with co-authoring, and people who have the file open see the changes. Avoid rewriting ranges someone is actively editing at the same moment.",[10,2577,2578,2581],{},[14,2579,2580],{},"Is there a limit to how much I can write in one request?","\nGraph does not publish a fixed cell limit, but large payloads are slower and more likely to time out. Chunks of a few thousand rows per request are a reliable starting point.",[123,2583,2585],{"id":2584},"related","Related",[162,2587,2588,2594,2601,2606,2613],{},[165,2589,2590,2591,2593],{},"Up one level: ",[19,2592,22],{"href":21}," — the whole toolkit for workbooks in Microsoft 365.",[165,2595,2596,2600],{},[19,2597,2599],{"href":2598},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fappend-rows-to-an-excel-online-table-with-microsoft-graph\u002F","Append Rows to an Excel Online Table with Microsoft Graph"," — the easier write path when the data is a table.",[165,2602,2603,2605],{},[19,2604,2394],{"href":2393}," — sessions, calculation and batching many calls.",[165,2607,2608,2612],{},[19,2609,2611],{"href":2610},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas\u002F","Read an Excel File from SharePoint or OneDrive into pandas"," — the download path when you only need to read.",[165,2614,2615,2619],{},[19,2616,2618],{"href":2617},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates\u002F","Fix Excel Serial Numbers Showing Instead of Dates"," — how Excel date serials work.",[2621,2622,2623],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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":133,"searchDepth":229,"depth":229,"links":2625},[2626,2627,2628,2629,2630,2631,2632,2633,2634,2635,2636,2637],{"id":125,"depth":229,"text":126},{"id":201,"depth":229,"text":202},{"id":684,"depth":229,"text":685},{"id":866,"depth":229,"text":867},{"id":1245,"depth":229,"text":1246},{"id":1855,"depth":229,"text":1856},{"id":2240,"depth":229,"text":2241},{"id":2397,"depth":229,"text":2398},{"id":2514,"depth":229,"text":2515},{"id":2532,"depth":229,"text":2533},{"id":2556,"depth":229,"text":2557},{"id":2584,"depth":229,"text":2585},"2026-09-17","Edit a workbook in OneDrive or SharePoint in place: read a range or the used range into pandas, write a DataFrame back with the right address, send dates and number formats correctly, and avoid the null-means-skip trap.","md",[2642,2644,2646,2648],{"q":2562,"a":2643},"You can, by reading the used range to find the last row and writing below it. If the data is an Excel table, adding rows to the table is simpler and keeps formulas and formatting extending automatically.",{"q":2568,"a":2645},"Most often the values were null, which the API treats as leave unchanged. Also check that you wrote to the sheet you are looking at, and that the request was not made in a non-persistent session.",{"q":2574,"a":2647},"Yes. The workbook API works with co-authoring, and people who have the file open see the changes. Avoid rewriting ranges someone is actively editing at the same moment.",{"q":2580,"a":2649},"Graph does not publish a fixed cell limit, but large payloads are slower and more likely to time out. Chunks of a few thousand rows per request are a reliable starting point.","read and write excel online ranges with microsoft graph",null,{"breadcrumb":2653},[2654,2656,2659],{"name":2655,"item":1575},"Home",{"name":2657,"item":2658},"Automating Reporting Workflows","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph",{"title":2662,"description":2663},"Read and Write Excel Online Ranges with Microsoft Graph in Python","Use the Microsoft Graph workbook API from Python to read ranges and usedRange into pandas and PATCH values, formulas and number formats back — with correct addresses, dates and blanks.","read-and-write-excel-online-ranges-with-microsoft-graph","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph\u002Findex",[2667,2668,2669],"excel-online","reading-excel","writing-excel","how-to","gUXn2bc9CA9T2TT-7MMOclvgGJZLsV1t3VlJ5Uc_65w",[2673,2676],{"title":2611,"path":2674,"stem":2675,"children":-1},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas\u002Findex",{"title":2394,"path":2677,"stem":2678,"children":-1},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions\u002Findex",[2680,2682],{"path":2681,"title":22},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph",{"path":2683,"title":2684},"\u002Fautomating-reporting-workflows","Automating Reporting Workflows with Python and Excel",[],[2687,2690,2693,2696,2698,2700],{"path":2688,"title":2689,"kind":2670},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-with-microsoft-graph-api","Send an Excel Report with the Microsoft Graph API",{"path":2691,"title":2692,"kind":2670},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fadd-a-chart-to-an-excel-online-workbook-with-microsoft-graph","Add a Chart to an Excel Online Workbook with Microsoft Graph",{"path":2694,"title":2695,"kind":2670},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fadd-rename-and-delete-worksheets-with-microsoft-graph","Add, Rename and Delete Worksheets with Microsoft Graph",{"path":2697,"title":2599,"kind":2670},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fappend-rows-to-an-excel-online-table-with-microsoft-graph",{"path":2699,"title":179,"kind":2670},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation",{"path":2701,"title":2702,"kind":2670},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",1790972119053]