[{"data":1,"prerenderedAt":2300},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph":3,"surround:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph":2269,"ancestors:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph":2277,"children:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph":2282,"related:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph":2283},{"id":4,"title":5,"body":6,"dateModified":2237,"datePublished":2237,"description":2238,"extension":2239,"faq":2240,"keyword":2249,"kind":2250,"meta":2251,"navigation":138,"path":2259,"seo":2260,"slug":2263,"stem":2264,"topics":2265,"type":2267,"__hash__":2268},"docs\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph\u002Findex.md","Create a New Workbook in OneDrive with Microsoft Graph",{"type":7,"value":8,"toc":2225},"minimark",[9,29,34,65,89,93,96,323,337,341,352,763,816,979,983,986,1385,1410,1522,1526,1533,1743,1773,1905,1909,1916,2079,2090,2094,2135,2139,2160,2164,2170,2176,2182,2188,2192,2221],[10,11,12,13,17,18,22,23,28],"p",{},"Most Microsoft Graph examples for Excel start from a workbook that already exists in OneDrive or SharePoint. Often, though, the job is to ",[14,15,16],"em",{},"create"," one: a weekly report that should appear in a team's document library, a fresh tracking workbook for each new project, a file colleagues will edit in Excel for the web. Graph has no \"create workbook\" call as such — you create the file by uploading an ",[19,20,21],"code",{},".xlsx",", and then, if needed, use the workbook API on it. This page builds the workbook in memory with pandas, uploads it with a single request for ordinary file sizes and an upload session for larger ones, controls what happens when a file of the same name exists, turns its data into an Excel table so later jobs can append rows through Graph, and checks the requests the code sends. It belongs to ",[24,25,27],"a",{"href":26},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002F","Automating Excel Online with Microsoft Graph",".",[30,31,33],"h2",{"id":32},"prerequisites","Prerequisites",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install requests pandas openpyxl\n","bash","",[19,42,43],{"__ignoreMap":40},[44,45,48,52,56,59,62],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," requests",[44,60,61],{"class":54}," pandas",[44,63,64],{"class":54}," openpyxl\n",[10,66,67,68,71,72,75,76,79,80,84,85,88],{},"You need an access token for Microsoft Graph with ",[19,69,70],{},"Files.ReadWrite"," (delegated) or ",[19,73,74],{},"Files.ReadWrite.All"," \u002F ",[19,77,78],{},"Sites.ReadWrite.All"," (application), and the drive ID of the target OneDrive or SharePoint document library; getting a token is covered in ",[24,81,83],{"href":82},"\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",". The outputs on this page were produced against a mocked Graph API (using the ",[19,86,87],{},"responses"," library) that returns the documented response shapes, so the requests can be shown and checked without a tenant; the code is unchanged against the real service.",[30,90,92],{"id":91},"step-1-build-the-workbook-in-memory","Step 1 — Build the workbook in memory",[10,94,95],{},"There is no need to write a temporary file: build the workbook into a bytes buffer and upload those bytes:",[35,97,101],{"className":98,"code":99,"language":100,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","from io import BytesIO\nimport pandas as pd\n\norders = pd.DataFrame({\"Order\": [\"SO-1001\", \"SO-1002\", \"SO-1003\"], \"Customer\": [\"Oakridge Farms\", \"Café Señor\", \"Zenith Logistics\"],\n                       \"Amount\": [1200.0, 380.0, 5600.0]})\nbuf = BytesIO()\nwith pd.ExcelWriter(buf, engine=\"openpyxl\") as xw:\n    orders.to_excel(xw, sheet_name=\"Orders\", index=False)\npayload = buf.getvalue()\nprint(len(payload), \"bytes; starts with\", payload[:2])\n","python",[19,102,103,119,133,140,196,221,232,258,285,296],{"__ignoreMap":40},[44,104,105,109,113,116],{"class":46,"line":47},[44,106,108],{"class":107},"s-kum","from",[44,110,112],{"class":111},"skGVy"," io ",[44,114,115],{"class":107},"import",[44,117,118],{"class":111}," BytesIO\n",[44,120,122,124,127,130],{"class":46,"line":121},2,[44,123,115],{"class":107},[44,125,126],{"class":111}," pandas ",[44,128,129],{"class":107},"as",[44,131,132],{"class":111}," pd\n",[44,134,136],{"class":46,"line":135},3,[44,137,139],{"emptyLinePlaceholder":138},true,"\n",[44,141,143,146,149,152,155,158,161,164,167,169,172,175,178,180,183,185,188,190,193],{"class":46,"line":142},4,[44,144,145],{"class":111},"orders ",[44,147,148],{"class":107},"=",[44,150,151],{"class":111}," pd.DataFrame({",[44,153,154],{"class":54},"\"Order\"",[44,156,157],{"class":111},": [",[44,159,160],{"class":54},"\"SO-1001\"",[44,162,163],{"class":111},", ",[44,165,166],{"class":54},"\"SO-1002\"",[44,168,163],{"class":111},[44,170,171],{"class":54},"\"SO-1003\"",[44,173,174],{"class":111},"], ",[44,176,177],{"class":54},"\"Customer\"",[44,179,157],{"class":111},[44,181,182],{"class":54},"\"Oakridge Farms\"",[44,184,163],{"class":111},[44,186,187],{"class":54},"\"Café Señor\"",[44,189,163],{"class":111},[44,191,192],{"class":54},"\"Zenith Logistics\"",[44,194,195],{"class":111},"],\n",[44,197,199,202,204,208,210,213,215,218],{"class":46,"line":198},5,[44,200,201],{"class":54},"                       \"Amount\"",[44,203,157],{"class":111},[44,205,207],{"class":206},"sP0c6","1200.0",[44,209,163],{"class":111},[44,211,212],{"class":206},"380.0",[44,214,163],{"class":111},[44,216,217],{"class":206},"5600.0",[44,219,220],{"class":111},"]})\n",[44,222,224,227,229],{"class":46,"line":223},6,[44,225,226],{"class":111},"buf ",[44,228,148],{"class":107},[44,230,231],{"class":111}," BytesIO()\n",[44,233,235,238,241,245,247,250,253,255],{"class":46,"line":234},7,[44,236,237],{"class":107},"with",[44,239,240],{"class":111}," pd.ExcelWriter(buf, ",[44,242,244],{"class":243},"sa561","engine",[44,246,148],{"class":107},[44,248,249],{"class":54},"\"openpyxl\"",[44,251,252],{"class":111},") ",[44,254,129],{"class":107},[44,256,257],{"class":111}," xw:\n",[44,259,261,264,267,269,272,274,277,279,282],{"class":46,"line":260},8,[44,262,263],{"class":111},"    orders.to_excel(xw, ",[44,265,266],{"class":243},"sheet_name",[44,268,148],{"class":107},[44,270,271],{"class":54},"\"Orders\"",[44,273,163],{"class":111},[44,275,276],{"class":243},"index",[44,278,148],{"class":107},[44,280,281],{"class":206},"False",[44,283,284],{"class":111},")\n",[44,286,288,291,293],{"class":46,"line":287},9,[44,289,290],{"class":111},"payload ",[44,292,148],{"class":107},[44,294,295],{"class":111}," buf.getvalue()\n",[44,297,299,302,305,308,311,314,317,320],{"class":46,"line":298},10,[44,300,301],{"class":206},"print",[44,303,304],{"class":111},"(",[44,306,307],{"class":206},"len",[44,309,310],{"class":111},"(payload), ",[44,312,313],{"class":54},"\"bytes; starts with\"",[44,315,316],{"class":111},", payload[:",[44,318,319],{"class":206},"2",[44,321,322],{"class":111},"])\n",[10,324,325,326,328,329,332,333,28],{},"A few kilobytes of ",[19,327,21],{}," — a zip file, hence the ",[19,330,331],{},"PK"," signature. Anything pandas, openpyxl or XlsxWriter can produce can be uploaded this way, including formatting and charts. Building in memory avoids temporary-file clean-up and works in serverless functions with read-only file systems; the pattern is covered in ",[24,334,336],{"href":335},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-an-excel-workbook-in-memory-with-bytesio\u002F","Build an Excel Workbook in Memory with BytesIO",[30,338,340],{"id":339},"step-2-upload-with-one-put-choosing-what-happens-on-a-name-clash","Step 2 — Upload with one PUT, choosing what happens on a name clash",[10,342,343,344,347,348,351],{},"For files up to 250 MB Graph accepts a single ",[19,345,346],{},"PUT"," to the item's ",[19,349,350],{},"content",", addressed by path. A query parameter decides what happens if a file with that name already exists:",[35,353,355],{"className":98,"code":354,"language":100,"meta":40,"style":40},"import os\nimport requests\nfrom urllib.parse import quote\n\nGRAPH = \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\nDRIVE = \"b!finance-drive-id\"\nsession = requests.Session()\nsession.headers[\"Authorization\"] = f\"Bearer {os.environ['GRAPH_TOKEN']}\"\n\ndef upload_small(path, data, conflict=\"replace\"):\n    url = f\"{GRAPH}\u002Fdrives\u002F{DRIVE}\u002Froot:\u002F{quote(path)}:\u002Fcontent\"\n    r = session.put(url, data=data, params={\"@microsoft.graph.conflictBehavior\": conflict},\n                    headers={\"Content-Type\": \"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\"}, timeout=120)\n    if r.status_code >= 400:\n        raise RuntimeError(f\"{r.status_code}: {r.json()['error']['message']}\")\n    return r.json()\n\nitem = upload_small(\"Reports\u002F2026\u002Forders_2026-09.xlsx\", payload)\nprint(item[\"id\"], item[\"name\"], item[\"webUrl\"])\ntry:\n    upload_small(\"Reports\u002Fexisting.xlsx\", payload, conflict=\"fail\")\nexcept RuntimeError as e:\n    print(\"with conflictBehavior=fail:\", e)\n",[19,356,357,364,371,383,387,398,408,418,456,460,480,521,553,585,603,650,659,664,681,705,713,735,749],{"__ignoreMap":40},[44,358,359,361],{"class":46,"line":47},[44,360,115],{"class":107},[44,362,363],{"class":111}," os\n",[44,365,366,368],{"class":46,"line":121},[44,367,115],{"class":107},[44,369,370],{"class":111}," requests\n",[44,372,373,375,378,380],{"class":46,"line":135},[44,374,108],{"class":107},[44,376,377],{"class":111}," urllib.parse ",[44,379,115],{"class":107},[44,381,382],{"class":111}," quote\n",[44,384,385],{"class":46,"line":142},[44,386,139],{"emptyLinePlaceholder":138},[44,388,389,392,395],{"class":46,"line":198},[44,390,391],{"class":206},"GRAPH",[44,393,394],{"class":107}," =",[44,396,397],{"class":54}," \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\n",[44,399,400,403,405],{"class":46,"line":223},[44,401,402],{"class":206},"DRIVE",[44,404,394],{"class":107},[44,406,407],{"class":54}," \"b!finance-drive-id\"\n",[44,409,410,413,415],{"class":46,"line":234},[44,411,412],{"class":111},"session ",[44,414,148],{"class":107},[44,416,417],{"class":111}," requests.Session()\n",[44,419,420,423,426,429,431,434,437,441,444,447,450,453],{"class":46,"line":260},[44,421,422],{"class":111},"session.headers[",[44,424,425],{"class":54},"\"Authorization\"",[44,427,428],{"class":111},"] ",[44,430,148],{"class":107},[44,432,433],{"class":107}," f",[44,435,436],{"class":54},"\"Bearer ",[44,438,440],{"class":439},"sSjpA","{",[44,442,443],{"class":111},"os.environ[",[44,445,446],{"class":54},"'GRAPH_TOKEN'",[44,448,449],{"class":111},"]",[44,451,452],{"class":439},"}",[44,454,455],{"class":54},"\"\n",[44,457,458],{"class":46,"line":287},[44,459,139],{"emptyLinePlaceholder":138},[44,461,462,465,469,472,474,477],{"class":46,"line":298},[44,463,464],{"class":107},"def",[44,466,468],{"class":467},"s_Opv"," upload_small",[44,470,471],{"class":111},"(path, data, conflict",[44,473,148],{"class":107},[44,475,476],{"class":54},"\"replace\"",[44,478,479],{"class":111},"):\n",[44,481,483,486,488,490,493,495,497,499,502,504,506,508,511,513,516,518],{"class":46,"line":482},11,[44,484,485],{"class":111},"    url ",[44,487,148],{"class":107},[44,489,433],{"class":107},[44,491,492],{"class":54},"\"",[44,494,440],{"class":439},[44,496,391],{"class":206},[44,498,452],{"class":439},[44,500,501],{"class":54},"\u002Fdrives\u002F",[44,503,440],{"class":439},[44,505,402],{"class":206},[44,507,452],{"class":439},[44,509,510],{"class":54},"\u002Froot:\u002F",[44,512,440],{"class":439},[44,514,515],{"class":111},"quote(path)",[44,517,452],{"class":439},[44,519,520],{"class":54},":\u002Fcontent\"\n",[44,522,524,527,529,532,535,537,540,543,545,547,550],{"class":46,"line":523},12,[44,525,526],{"class":111},"    r ",[44,528,148],{"class":107},[44,530,531],{"class":111}," session.put(url, ",[44,533,534],{"class":243},"data",[44,536,148],{"class":107},[44,538,539],{"class":111},"data, ",[44,541,542],{"class":243},"params",[44,544,148],{"class":107},[44,546,440],{"class":111},[44,548,549],{"class":54},"\"@microsoft.graph.conflictBehavior\"",[44,551,552],{"class":111},": conflict},\n",[44,554,556,559,561,563,566,569,572,575,578,580,583],{"class":46,"line":555},13,[44,557,558],{"class":243},"                    headers",[44,560,148],{"class":107},[44,562,440],{"class":111},[44,564,565],{"class":54},"\"Content-Type\"",[44,567,568],{"class":111},": ",[44,570,571],{"class":54},"\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\"",[44,573,574],{"class":111},"}, ",[44,576,577],{"class":243},"timeout",[44,579,148],{"class":107},[44,581,582],{"class":206},"120",[44,584,284],{"class":111},[44,586,588,591,594,597,600],{"class":46,"line":587},14,[44,589,590],{"class":107},"    if",[44,592,593],{"class":111}," r.status_code ",[44,595,596],{"class":107},">=",[44,598,599],{"class":206}," 400",[44,601,602],{"class":111},":\n",[44,604,606,609,612,614,617,619,621,624,626,628,630,633,636,639,642,644,646,648],{"class":46,"line":605},15,[44,607,608],{"class":107},"        raise",[44,610,611],{"class":206}," RuntimeError",[44,613,304],{"class":111},[44,615,616],{"class":107},"f",[44,618,492],{"class":54},[44,620,440],{"class":439},[44,622,623],{"class":111},"r.status_code",[44,625,452],{"class":439},[44,627,568],{"class":54},[44,629,440],{"class":439},[44,631,632],{"class":111},"r.json()[",[44,634,635],{"class":54},"'error'",[44,637,638],{"class":111},"][",[44,640,641],{"class":54},"'message'",[44,643,449],{"class":111},[44,645,452],{"class":439},[44,647,492],{"class":54},[44,649,284],{"class":111},[44,651,653,656],{"class":46,"line":652},16,[44,654,655],{"class":107},"    return",[44,657,658],{"class":111}," r.json()\n",[44,660,662],{"class":46,"line":661},17,[44,663,139],{"emptyLinePlaceholder":138},[44,665,667,670,672,675,678],{"class":46,"line":666},18,[44,668,669],{"class":111},"item ",[44,671,148],{"class":107},[44,673,674],{"class":111}," upload_small(",[44,676,677],{"class":54},"\"Reports\u002F2026\u002Forders_2026-09.xlsx\"",[44,679,680],{"class":111},", payload)\n",[44,682,684,686,689,692,695,698,700,703],{"class":46,"line":683},19,[44,685,301],{"class":206},[44,687,688],{"class":111},"(item[",[44,690,691],{"class":54},"\"id\"",[44,693,694],{"class":111},"], item[",[44,696,697],{"class":54},"\"name\"",[44,699,694],{"class":111},[44,701,702],{"class":54},"\"webUrl\"",[44,704,322],{"class":111},[44,706,708,711],{"class":46,"line":707},20,[44,709,710],{"class":107},"try",[44,712,602],{"class":111},[44,714,716,719,722,725,728,730,733],{"class":46,"line":715},21,[44,717,718],{"class":111},"    upload_small(",[44,720,721],{"class":54},"\"Reports\u002Fexisting.xlsx\"",[44,723,724],{"class":111},", payload, ",[44,726,727],{"class":243},"conflict",[44,729,148],{"class":107},[44,731,732],{"class":54},"\"fail\"",[44,734,284],{"class":111},[44,736,738,741,743,746],{"class":46,"line":737},22,[44,739,740],{"class":107},"except",[44,742,611],{"class":206},[44,744,745],{"class":107}," as",[44,747,748],{"class":111}," e:\n",[44,750,752,755,757,760],{"class":46,"line":751},23,[44,753,754],{"class":206},"    print",[44,756,304],{"class":111},[44,758,759],{"class":54},"\"with conflictBehavior=fail:\"",[44,761,762],{"class":111},", e)\n",[10,764,765,766,769,770,773,774,777,778,781,782,785,786,789,790,793,794,797,798,800,801,803,804,807,808,811,812,815],{},"The path after ",[19,767,768],{},"root:\u002F"," is relative to the drive's root; folders in it must exist (create them with a ",[19,771,772],{},"POST"," to the parent's ",[19,775,776],{},"children"," with a ",[19,779,780],{},"folder"," facet). ",[19,783,784],{},"@microsoft.graph.conflictBehavior"," takes ",[19,787,788],{},"replace"," (overwrite — the default for this call), ",[19,791,792],{},"fail"," (refuse with HTTP 409, as shown) or ",[19,795,796],{},"rename"," (keep both, adding a number to the new file's name). For scheduled reports, ",[19,799,788],{}," with a dated file name is usually right; for one-off project workbooks, ",[19,802,792],{}," protects a file someone may already have edited. The response is a ",[19,805,806],{},"driveItem"," with the new file's ",[19,809,810],{},"id"," — needed for the workbook API — and ",[19,813,814],{},"webUrl",", the link people open in the browser. Graph's documentation recommends the simple upload only for small files (a few megabytes); larger ones should use an upload session.",[817,818,826,827,826,831,826,835,826,843,826,853,826,864,826,869,826,873,826,877,826,880,826,885,826,894,826,898,826,906,826,913,826,918,826,923,826,925,826,928,826,932,826,934,826,939,826,946,826,950,826,953,826,955,826,958,826,962,826,964,826,967,826,973,826,976],"svg",{"viewBox":819,"role":820,"ariaLabelledBy":821,"xmlns":824,"style":825},"4 0 752 272","img",[822,823],"od-conflict-t","od-conflict-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[828,829,830],"title",{"id":822},"What happens when the file name already exists",[832,833,834],"desc",{"id":823},"With conflictBehavior replace the existing file is overwritten and keeps its sharing. With fail the upload is refused with HTTP 409. With rename the new file is saved alongside with a number added to its name.",[836,837],"rect",{"x":838,"y":839,"width":840,"height":841,"fill":842},"4","0","752","272","#ffffff",[836,844],{"x":845,"y":846,"width":847,"height":848,"rx":849,"fill":850,"stroke":851,"style":852},"240.0","16.0","280.0","75.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[854,855,857],"g",{"transform":856},"translate(368.0,29.0) scale(1.000)",[858,859],"path",{"d":860,"fill":861,"stroke":862,"style":863},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[865,866,868],"text",{"x":212,"y":848,"style":867},"font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","A file with this name exists…",[46,870],{"x1":212,"y1":871,"x2":212,"y2":872,"stroke":851,"style":852},"91.5","111.5",[46,874],{"x1":875,"y1":872,"x2":876,"y2":872,"stroke":851,"style":852},"134.7","625.3",[46,878],{"x1":875,"y1":872,"x2":875,"y2":879,"stroke":851,"style":852},"157.5",[881,882],"polygon",{"points":883,"fill":884},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[836,886],{"x":887,"y":888,"width":889,"height":890,"rx":891,"fill":842,"stroke":892,"style":893},"103.5","121.5","62.3","21.0","10","var(--line,#cdd5e6)","stroke-width:1.2px",[865,895,788],{"x":875,"y":896,"style":897},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle",[836,899],{"x":900,"y":901,"width":902,"height":903,"rx":849,"fill":904,"stroke":905,"style":852},"20.0","163.5","229.3","93.0","#d9f4f1","var(--teal,#0f9488)",[854,907,909],{"transform":908},"translate(122.7,176.5) scale(1.000)",[858,910],{"d":911,"fill":861,"stroke":912,"style":863},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--teal-ink,#0b6157)",[865,914,917],{"x":875,"y":915,"style":916},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","overwrite it",[865,919,922],{"x":875,"y":920,"style":921},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","scheduled reports, dated names",[46,924],{"x1":212,"y1":872,"x2":212,"y2":879,"stroke":851,"style":852},[881,926],{"points":927,"fill":884},"380.0,161.5 375.0,152.5 385.0,152.5",[836,929],{"x":930,"y":888,"width":931,"height":890,"rx":891,"fill":842,"stroke":892,"style":893},"361.8","36.4",[865,933,792],{"x":212,"y":896,"style":897},[836,935],{"x":936,"y":901,"width":902,"height":903,"rx":849,"fill":937,"stroke":938,"style":852},"265.3","#fdefd8","var(--gold,#b4740a)",[854,940,942],{"transform":941},"translate(368.0,176.5) scale(1.000)",[858,943],{"d":944,"fill":861,"stroke":945,"style":863},"M6 11h12v9H6z M8.5 11V8a3.5 3.5 0 0 1 7 0v3","var(--gold-ink,#7a4e06)",[865,947,949],{"x":212,"y":915,"style":948},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","HTTP 409, nothing changed",[865,951,952],{"x":212,"y":920,"style":921},"protect edited workbooks",[46,954],{"x1":876,"y1":872,"x2":876,"y2":879,"stroke":851,"style":852},[881,956],{"points":957,"fill":884},"625.3,161.5 620.3,152.5 630.3,152.5",[836,959],{"x":960,"y":888,"width":961,"height":890,"rx":891,"fill":842,"stroke":892,"style":893},"593.9","62.9",[865,963,796],{"x":876,"y":896,"style":897},[836,965],{"x":966,"y":901,"width":902,"height":903,"rx":849,"fill":850,"stroke":851,"style":852},"510.7",[854,968,970],{"transform":969},"translate(613.3,176.5) scale(1.000)",[858,971],{"d":972,"fill":861,"stroke":862,"style":863},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[865,974,975],{"x":876,"y":915,"style":867},"save as 'name 1.xlsx'",[865,977,978],{"x":876,"y":920,"style":921},"keep both versions",[30,980,982],{"id":981},"step-3-larger-files-an-upload-session-in-chunks","Step 3 — Larger files: an upload session in chunks",[10,984,985],{},"For larger workbooks, create an upload session and send the bytes in chunks. Each chunk's size must be a multiple of 320 KiB (327,680 bytes), except the last:",[35,987,989],{"className":98,"code":988,"language":100,"meta":40,"style":40},"CHUNK = 320 * 1024 * 10                                   # 3.2 MiB, a multiple of 320 KiB\n\ndef upload_large(path, data, conflict=\"replace\"):\n    url = f\"{GRAPH}\u002Fdrives\u002F{DRIVE}\u002Froot:\u002F{quote(path)}:\u002FcreateUploadSession\"\n    s = session.post(url, json={\"item\": {\"@microsoft.graph.conflictBehavior\": conflict}}, timeout=60).json()\n    upload_url, total = s[\"uploadUrl\"], len(data)\n    for start in range(0, total, CHUNK):\n        end = min(start + CHUNK, total) - 1\n        r = requests.put(upload_url, data=data[start:end + 1], timeout=300,\n                         headers={\"Content-Range\": f\"bytes {start}-{end}\u002F{total}\", \"Content-Length\": str(end - start + 1)})\n        r.raise_for_status()\n    return r.json()\n\nbig = payload * 2000                                      # stand-in for a larger workbook's bytes\ndone = upload_large(\"Reports\u002F2026\u002Flarge.xlsx\", big)\nprint(done[\"name\"], done[\"size\"], \"bytes in\", len(chunks), \"chunks:\", chunks[0], \"…\", chunks[-1])\n",[19,990,991,1016,1020,1035,1070,1108,1128,1153,1181,1215,1286,1291,1297,1301,1320,1336],{"__ignoreMap":40},[44,992,993,996,998,1001,1004,1007,1009,1012],{"class":46,"line":47},[44,994,995],{"class":206},"CHUNK",[44,997,394],{"class":107},[44,999,1000],{"class":206}," 320",[44,1002,1003],{"class":107}," *",[44,1005,1006],{"class":206}," 1024",[44,1008,1003],{"class":107},[44,1010,1011],{"class":206}," 10",[44,1013,1015],{"class":1014},"s-wDw","                                   # 3.2 MiB, a multiple of 320 KiB\n",[44,1017,1018],{"class":46,"line":121},[44,1019,139],{"emptyLinePlaceholder":138},[44,1021,1022,1024,1027,1029,1031,1033],{"class":46,"line":135},[44,1023,464],{"class":107},[44,1025,1026],{"class":467}," upload_large",[44,1028,471],{"class":111},[44,1030,148],{"class":107},[44,1032,476],{"class":54},[44,1034,479],{"class":111},[44,1036,1037,1039,1041,1043,1045,1047,1049,1051,1053,1055,1057,1059,1061,1063,1065,1067],{"class":46,"line":142},[44,1038,485],{"class":111},[44,1040,148],{"class":107},[44,1042,433],{"class":107},[44,1044,492],{"class":54},[44,1046,440],{"class":439},[44,1048,391],{"class":206},[44,1050,452],{"class":439},[44,1052,501],{"class":54},[44,1054,440],{"class":439},[44,1056,402],{"class":206},[44,1058,452],{"class":439},[44,1060,510],{"class":54},[44,1062,440],{"class":439},[44,1064,515],{"class":111},[44,1066,452],{"class":439},[44,1068,1069],{"class":54},":\u002FcreateUploadSession\"\n",[44,1071,1072,1075,1077,1080,1083,1085,1087,1090,1093,1095,1098,1100,1102,1105],{"class":46,"line":198},[44,1073,1074],{"class":111},"    s ",[44,1076,148],{"class":107},[44,1078,1079],{"class":111}," session.post(url, ",[44,1081,1082],{"class":243},"json",[44,1084,148],{"class":107},[44,1086,440],{"class":111},[44,1088,1089],{"class":54},"\"item\"",[44,1091,1092],{"class":111},": {",[44,1094,549],{"class":54},[44,1096,1097],{"class":111},": conflict}}, ",[44,1099,577],{"class":243},[44,1101,148],{"class":107},[44,1103,1104],{"class":206},"60",[44,1106,1107],{"class":111},").json()\n",[44,1109,1110,1113,1115,1118,1121,1123,1125],{"class":46,"line":223},[44,1111,1112],{"class":111},"    upload_url, total ",[44,1114,148],{"class":107},[44,1116,1117],{"class":111}," s[",[44,1119,1120],{"class":54},"\"uploadUrl\"",[44,1122,174],{"class":111},[44,1124,307],{"class":206},[44,1126,1127],{"class":111},"(data)\n",[44,1129,1130,1133,1136,1139,1142,1144,1146,1149,1151],{"class":46,"line":234},[44,1131,1132],{"class":107},"    for",[44,1134,1135],{"class":111}," start ",[44,1137,1138],{"class":107},"in",[44,1140,1141],{"class":206}," range",[44,1143,304],{"class":111},[44,1145,839],{"class":206},[44,1147,1148],{"class":111},", total, ",[44,1150,995],{"class":206},[44,1152,479],{"class":111},[44,1154,1155,1158,1160,1163,1166,1169,1172,1175,1178],{"class":46,"line":260},[44,1156,1157],{"class":111},"        end ",[44,1159,148],{"class":107},[44,1161,1162],{"class":206}," min",[44,1164,1165],{"class":111},"(start ",[44,1167,1168],{"class":107},"+",[44,1170,1171],{"class":206}," CHUNK",[44,1173,1174],{"class":111},", total) ",[44,1176,1177],{"class":107},"-",[44,1179,1180],{"class":206}," 1\n",[44,1182,1183,1186,1188,1191,1193,1195,1198,1200,1203,1205,1207,1209,1212],{"class":46,"line":287},[44,1184,1185],{"class":111},"        r ",[44,1187,148],{"class":107},[44,1189,1190],{"class":111}," requests.put(upload_url, ",[44,1192,534],{"class":243},[44,1194,148],{"class":107},[44,1196,1197],{"class":111},"data[start:end ",[44,1199,1168],{"class":107},[44,1201,1202],{"class":206}," 1",[44,1204,174],{"class":111},[44,1206,577],{"class":243},[44,1208,148],{"class":107},[44,1210,1211],{"class":206},"300",[44,1213,1214],{"class":111},",\n",[44,1216,1217,1220,1222,1224,1227,1229,1231,1234,1236,1239,1241,1243,1245,1248,1250,1253,1255,1258,1260,1262,1264,1267,1269,1272,1275,1277,1279,1281,1283],{"class":46,"line":298},[44,1218,1219],{"class":243},"                         headers",[44,1221,148],{"class":107},[44,1223,440],{"class":111},[44,1225,1226],{"class":54},"\"Content-Range\"",[44,1228,568],{"class":111},[44,1230,616],{"class":107},[44,1232,1233],{"class":54},"\"bytes ",[44,1235,440],{"class":439},[44,1237,1238],{"class":111},"start",[44,1240,452],{"class":439},[44,1242,1177],{"class":54},[44,1244,440],{"class":439},[44,1246,1247],{"class":111},"end",[44,1249,452],{"class":439},[44,1251,1252],{"class":54},"\u002F",[44,1254,440],{"class":439},[44,1256,1257],{"class":111},"total",[44,1259,452],{"class":439},[44,1261,492],{"class":54},[44,1263,163],{"class":111},[44,1265,1266],{"class":54},"\"Content-Length\"",[44,1268,568],{"class":111},[44,1270,1271],{"class":206},"str",[44,1273,1274],{"class":111},"(end ",[44,1276,1177],{"class":107},[44,1278,1135],{"class":111},[44,1280,1168],{"class":107},[44,1282,1202],{"class":206},[44,1284,1285],{"class":111},")})\n",[44,1287,1288],{"class":46,"line":482},[44,1289,1290],{"class":111},"        r.raise_for_status()\n",[44,1292,1293,1295],{"class":46,"line":523},[44,1294,655],{"class":107},[44,1296,658],{"class":111},[44,1298,1299],{"class":46,"line":555},[44,1300,139],{"emptyLinePlaceholder":138},[44,1302,1303,1306,1308,1311,1314,1317],{"class":46,"line":587},[44,1304,1305],{"class":111},"big ",[44,1307,148],{"class":107},[44,1309,1310],{"class":111}," payload ",[44,1312,1313],{"class":107},"*",[44,1315,1316],{"class":206}," 2000",[44,1318,1319],{"class":1014},"                                      # stand-in for a larger workbook's bytes\n",[44,1321,1322,1325,1327,1330,1333],{"class":46,"line":605},[44,1323,1324],{"class":111},"done ",[44,1326,148],{"class":107},[44,1328,1329],{"class":111}," upload_large(",[44,1331,1332],{"class":54},"\"Reports\u002F2026\u002Flarge.xlsx\"",[44,1334,1335],{"class":111},", big)\n",[44,1337,1338,1340,1343,1345,1348,1351,1353,1356,1358,1360,1363,1366,1369,1371,1373,1376,1378,1380,1383],{"class":46,"line":652},[44,1339,301],{"class":206},[44,1341,1342],{"class":111},"(done[",[44,1344,697],{"class":54},[44,1346,1347],{"class":111},"], done[",[44,1349,1350],{"class":54},"\"size\"",[44,1352,174],{"class":111},[44,1354,1355],{"class":54},"\"bytes in\"",[44,1357,163],{"class":111},[44,1359,307],{"class":206},[44,1361,1362],{"class":111},"(chunks), ",[44,1364,1365],{"class":54},"\"chunks:\"",[44,1367,1368],{"class":111},", chunks[",[44,1370,839],{"class":206},[44,1372,174],{"class":111},[44,1374,1375],{"class":54},"\"…\"",[44,1377,1368],{"class":111},[44,1379,1177],{"class":107},[44,1381,1382],{"class":206},"1",[44,1384,322],{"class":111},[10,1386,1387,1390,1391,1394,1395,1398,1399,1402,1403,1406,1407,1409],{},[19,1388,1389],{},"createUploadSession"," returns a pre-authenticated ",[19,1392,1393],{},"uploadUrl","; chunks are sent there ",[14,1396,1397],{},"without"," the Graph bearer token, which is why the code uses ",[19,1400,1401],{},"requests.put"," rather than the authorised session — sending the token to the upload URL can make the request fail. Each response before the last returns ",[19,1404,1405],{},"202"," with the next expected byte range; the final chunk returns the created ",[19,1408,806],{},". Chunks of a few megabytes are a good balance between the number of requests and the cost of retrying one after a network error; the session also lets an interrupted upload be resumed from the next expected range rather than restarted.",[817,1411,826,1416,826,1419,826,1422,826,1425,826,1430,826,1437,826,1441,826,1445,826,1449,826,1454,826,1457,826,1460,826,1465,826,1469,826,1472,826,1475,826,1479,826,1482,826,1485,826,1490,826,1495,826,1499,826,1503,826,1506,826,1509,826,1515,826,1519],{"viewBox":1412,"role":820,"ariaLabelledBy":1413,"xmlns":824,"style":825},"4 0 752 142",[1414,1415],"od-session-t","od-session-d",[828,1417,1418],{"id":1414},"An upload session in chunks",[832,1420,1421],{"id":1415},"A POST to createUploadSession returns an upload URL. The file is sent in chunks whose sizes are multiples of 320 KiB, each with a Content-Range header and without the Graph token. Intermediate chunks return 202 with the next expected range and the last returns the created file.",[836,1423],{"x":838,"y":839,"width":840,"height":1424,"fill":842},"142",[836,1426],{"x":900,"y":846,"width":1427,"height":1428,"rx":849,"fill":1429,"stroke":892,"style":852},"153.0","110.5","#e7ebef",[854,1431,1433],{"transform":1432},"translate(84.5,29.0) scale(1.000)",[858,1434],{"d":1435,"fill":861,"stroke":1436,"style":863},"M8 7l-5 5 5 5 M16 7l5 5-5 5 M14 4l-4 16","var(--text,#172033)",[865,1438,1389],{"x":1439,"y":848,"style":1440},"96.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[865,1442,1444],{"x":1439,"y":1443,"style":921},"95.0","POST,",[865,1446,1448],{"x":1439,"y":1447,"style":921},"112.5","conflictBehavior",[46,1450],{"x1":1451,"y1":1452,"x2":1453,"y2":1452,"stroke":851,"style":852},"178.0","71.2","200.0",[881,1455],{"points":1456,"fill":884},"204.0,71.2 195.0,76.2 195.0,66.2",[836,1458],{"x":1459,"y":846,"width":1427,"height":1428,"rx":849,"fill":850,"stroke":851,"style":852},"209.0",[854,1461,1463],{"transform":1462},"translate(273.5,29.0) scale(1.000)",[858,1464],{"d":972,"fill":861,"stroke":862,"style":863},[865,1466,1468],{"x":1467,"y":848,"style":867},"285.5","PUT chunk",[865,1470,1471],{"x":1467,"y":1443,"style":921},"Content-Range bytes",[865,1473,1474],{"x":1467,"y":1447,"style":921},"a-b\u002Ftotal",[46,1476],{"x1":1477,"y1":1452,"x2":1478,"y2":1452,"stroke":851,"style":852},"367.0","389.0",[881,1480],{"points":1481,"fill":884},"393.0,71.2 384.0,76.2 384.0,66.2",[836,1483],{"x":1484,"y":846,"width":1427,"height":1428,"rx":849,"fill":937,"stroke":938,"style":852},"398.0",[854,1486,1488],{"transform":1487},"translate(462.5,37.8) scale(1.000)",[858,1489],{"d":911,"fill":861,"stroke":945,"style":863},[865,1491,1494],{"x":1492,"y":1493,"style":948},"474.5","84.2","202 Accepted",[865,1496,1498],{"x":1492,"y":1497,"style":921},"103.8","nextExpectedRanges",[46,1500],{"x1":1501,"y1":1452,"x2":1502,"y2":1452,"stroke":851,"style":852},"556.0","578.0",[881,1504],{"points":1505,"fill":884},"582.0,71.2 573.0,76.2 573.0,66.2",[836,1507],{"x":1508,"y":846,"width":1427,"height":1428,"rx":849,"fill":904,"stroke":905,"style":852},"587.0",[854,1510,1512],{"transform":1511},"translate(651.5,37.8) scale(1.000)",[858,1513],{"d":1514,"fill":861,"stroke":912,"style":863},"M5 12.5l4.5 4.5L19 7",[865,1516,1518],{"x":1517,"y":1493,"style":916},"663.5","201 Created",[865,1520,1521],{"x":1517,"y":1497,"style":921},"driveItem with id",[30,1523,1525],{"id":1524},"step-4-turn-the-data-into-an-excel-table","Step 4 — Turn the data into an Excel table",[10,1527,1528,1529,1532],{},"Later jobs that append rows through Graph need an Excel ",[14,1530,1531],{},"table",". With the new item's ID, add one over the data range and give it a stable name:",[35,1534,1536],{"className":98,"code":1535,"language":100,"meta":40,"style":40},"BOOK = f\"{GRAPH}\u002Fdrives\u002F{DRIVE}\u002Fitems\u002F{item['id']}\u002Fworkbook\"\ntable = session.post(f\"{BOOK}\u002Ftables\u002Fadd\", json={\"address\": f\"Orders!A1:C{len(orders) + 1}\", \"hasHeaders\": True}, timeout=60).json()\nrenamed = session.patch(f\"{BOOK}\u002Ftables\u002F{table['id']}\", json={\"name\": \"Orders\"}, timeout=60).json()\nprint(\"table created:\", table[\"name\"], \"→ renamed to\", renamed[\"name\"])\n",[19,1537,1538,1581,1657,1717],{"__ignoreMap":40},[44,1539,1540,1543,1545,1547,1549,1551,1553,1555,1557,1559,1561,1563,1566,1568,1571,1574,1576,1578],{"class":46,"line":47},[44,1541,1542],{"class":206},"BOOK",[44,1544,394],{"class":107},[44,1546,433],{"class":107},[44,1548,492],{"class":54},[44,1550,440],{"class":439},[44,1552,391],{"class":206},[44,1554,452],{"class":439},[44,1556,501],{"class":54},[44,1558,440],{"class":439},[44,1560,402],{"class":206},[44,1562,452],{"class":439},[44,1564,1565],{"class":54},"\u002Fitems\u002F",[44,1567,440],{"class":439},[44,1569,1570],{"class":111},"item[",[44,1572,1573],{"class":54},"'id'",[44,1575,449],{"class":111},[44,1577,452],{"class":439},[44,1579,1580],{"class":54},"\u002Fworkbook\"\n",[44,1582,1583,1586,1588,1591,1593,1595,1597,1599,1601,1604,1606,1608,1610,1612,1615,1617,1619,1622,1624,1626,1629,1631,1633,1635,1637,1639,1642,1644,1647,1649,1651,1653,1655],{"class":46,"line":121},[44,1584,1585],{"class":111},"table ",[44,1587,148],{"class":107},[44,1589,1590],{"class":111}," session.post(",[44,1592,616],{"class":107},[44,1594,492],{"class":54},[44,1596,440],{"class":439},[44,1598,1542],{"class":206},[44,1600,452],{"class":439},[44,1602,1603],{"class":54},"\u002Ftables\u002Fadd\"",[44,1605,163],{"class":111},[44,1607,1082],{"class":243},[44,1609,148],{"class":107},[44,1611,440],{"class":111},[44,1613,1614],{"class":54},"\"address\"",[44,1616,568],{"class":111},[44,1618,616],{"class":107},[44,1620,1621],{"class":54},"\"Orders!A1:C",[44,1623,440],{"class":439},[44,1625,307],{"class":206},[44,1627,1628],{"class":111},"(orders) ",[44,1630,1168],{"class":107},[44,1632,1202],{"class":206},[44,1634,452],{"class":439},[44,1636,492],{"class":54},[44,1638,163],{"class":111},[44,1640,1641],{"class":54},"\"hasHeaders\"",[44,1643,568],{"class":111},[44,1645,1646],{"class":206},"True",[44,1648,574],{"class":111},[44,1650,577],{"class":243},[44,1652,148],{"class":107},[44,1654,1104],{"class":206},[44,1656,1107],{"class":111},[44,1658,1659,1662,1664,1667,1669,1671,1673,1675,1677,1680,1682,1685,1687,1689,1691,1693,1695,1697,1699,1701,1703,1705,1707,1709,1711,1713,1715],{"class":46,"line":135},[44,1660,1661],{"class":111},"renamed ",[44,1663,148],{"class":107},[44,1665,1666],{"class":111}," session.patch(",[44,1668,616],{"class":107},[44,1670,492],{"class":54},[44,1672,440],{"class":439},[44,1674,1542],{"class":206},[44,1676,452],{"class":439},[44,1678,1679],{"class":54},"\u002Ftables\u002F",[44,1681,440],{"class":439},[44,1683,1684],{"class":111},"table[",[44,1686,1573],{"class":54},[44,1688,449],{"class":111},[44,1690,452],{"class":439},[44,1692,492],{"class":54},[44,1694,163],{"class":111},[44,1696,1082],{"class":243},[44,1698,148],{"class":107},[44,1700,440],{"class":111},[44,1702,697],{"class":54},[44,1704,568],{"class":111},[44,1706,271],{"class":54},[44,1708,574],{"class":111},[44,1710,577],{"class":243},[44,1712,148],{"class":107},[44,1714,1104],{"class":206},[44,1716,1107],{"class":111},[44,1718,1719,1721,1723,1726,1729,1731,1733,1736,1739,1741],{"class":46,"line":142},[44,1720,301],{"class":206},[44,1722,304],{"class":111},[44,1724,1725],{"class":54},"\"table created:\"",[44,1727,1728],{"class":111},", table[",[44,1730,697],{"class":54},[44,1732,174],{"class":111},[44,1734,1735],{"class":54},"\"→ renamed to\"",[44,1737,1738],{"class":111},", renamed[",[44,1740,697],{"class":54},[44,1742,322],{"class":111},[10,1744,1745,1748,1749,1752,1753,1756,1757,1760,1761,1764,1765,1768,1769,28],{},[19,1746,1747],{},"tables\u002Fadd"," turns a range — addressed as ",[19,1750,1751],{},"Sheet!A1:C4"," — into a table with its first row as headers; Graph names it ",[19,1754,1755],{},"Table1",", and a ",[19,1758,1759],{},"PATCH"," renames it to something a later job can rely on. Alternatively, create the table in the file before uploading, with openpyxl's ",[19,1762,1763],{},"Table"," or XlsxWriter's ",[19,1766,1767],{},"add_table"," — one request fewer, and the table's style is under your control. Appending rows to it later is covered in ",[24,1770,1772],{"href":1771},"\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",[817,1774,826,1779,826,1782,826,1785,826,1788,826,1792,826,1798,826,1801,826,1805,826,1808,826,1812,826,1817,826,1819,826,1821,826,1826,826,1829,826,1834,826,1840,826,1845,826,1848,826,1853,826,1858,826,1861,826,1864,826,1869,826,1873,826,1877,826,1880,826,1885,826,1890,826,1893,826,1896,826,1901],{"viewBox":1775,"role":820,"ariaLabelledBy":1776,"xmlns":824,"style":825},"4 0 752 340",[1777,1778],"od-requests-t","od-requests-d",[828,1780,1781],{"id":1777},"From upload to a named table",[832,1783,1784],{"id":1778},"The script uploads the workbook bytes and receives a drive item with an id. It then posts to tables\u002Fadd on that item's workbook, receives Table1, and patches the table's name to Orders so later jobs can find it.",[836,1786],{"x":838,"y":839,"width":840,"height":1787,"fill":842},"340",[836,1789],{"x":900,"y":846,"width":1790,"height":1791,"rx":849,"fill":850,"stroke":851,"style":852},"180.0","48.0",[865,1793,1797],{"x":1794,"y":1795,"style":1796},"110.0","44.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Script",[836,1799],{"x":1800,"y":846,"width":1790,"height":1791,"rx":849,"fill":1429,"stroke":892,"style":852},"290.0",[865,1802,1804],{"x":212,"y":1795,"style":1803},"font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Graph drive",[836,1806],{"x":1807,"y":846,"width":1790,"height":1791,"rx":849,"fill":1429,"stroke":892,"style":852},"560.0",[865,1809,1811],{"x":1810,"y":1795,"style":1803},"650.0","Workbook API",[46,1813],{"x1":1794,"y1":1814,"x2":1794,"y2":1815,"stroke":892,"style":1816},"64.0","324.0","stroke-width:1.5px;stroke-dasharray:5 4",[46,1818],{"x1":212,"y1":1814,"x2":212,"y2":1815,"stroke":892,"style":1816},[46,1820],{"x1":1810,"y1":1814,"x2":1810,"y2":1815,"stroke":892,"style":1816},[46,1822],{"x1":1823,"y1":1824,"x2":1825,"y2":1824,"stroke":851,"style":852},"115.0","104.0","371.0",[881,1827],{"points":1828,"fill":884},"375.0,104.0 366.0,109.0 366.0,99.0",[836,1830],{"x":1831,"y":1832,"width":1833,"height":890,"rx":891,"fill":842,"stroke":892,"style":893},"159.3","75.0","171.4",[865,1835,1839],{"x":1836,"y":1837,"style":1838},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. PUT root:\u002Fpath:\u002Fcontent",[46,1841],{"x1":1842,"y1":1843,"x2":1844,"y2":1843,"stroke":851,"style":852},"375.0","152.0","119.0",[881,1846],{"points":1847,"fill":884},"115.0,152.0 124.0,147.0 124.0,157.0",[836,1849],{"x":1850,"y":1851,"width":1852,"height":890,"rx":891,"fill":904,"stroke":905,"style":893},"181.8","123.0","126.3",[865,1854,1857],{"x":1836,"y":1855,"style":1856},"137.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","2. 201 driveItem id",[46,1859],{"x1":1823,"y1":1453,"x2":1860,"y2":1453,"stroke":851,"style":852},"641.0",[881,1862],{"points":1863,"fill":884},"645.0,200.0 636.0,205.0 636.0,195.0",[836,1865],{"x":1866,"y":1867,"width":1868,"height":890,"rx":891,"fill":842,"stroke":892,"style":893},"295.0","171.0","170.1",[865,1870,1872],{"x":212,"y":1871,"style":1838},"185.5","3. POST tables\u002Fadd A1:C4",[46,1874],{"x1":1875,"y1":1876,"x2":1844,"y2":1876,"stroke":851,"style":852},"645.0","248.0",[881,1878],{"points":1879,"fill":884},"115.0,248.0 124.0,243.0 124.0,253.0",[836,1881],{"x":1882,"y":1883,"width":1884,"height":890,"rx":891,"fill":937,"stroke":938,"style":893},"344.3","219.0","71.4",[865,1886,1889],{"x":212,"y":1887,"style":1888},"233.5","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","4. Table1",[46,1891],{"x1":1823,"y1":1892,"x2":1860,"y2":1892,"stroke":851,"style":852},"296.0",[881,1894],{"points":1895,"fill":884},"645.0,296.0 636.0,301.0 636.0,291.0",[836,1897],{"x":1898,"y":1899,"width":1900,"height":890,"rx":891,"fill":842,"stroke":892,"style":893},"300.1","267.0","159.8",[865,1902,1904],{"x":212,"y":1903,"style":1838},"281.5","5. PATCH name=Orders",[30,1906,1908],{"id":1907},"step-5-check-what-was-sent","Step 5 — Check what was sent",[10,1910,1911,1912,1915],{},"Because every call above went through ",[19,1913,1914],{},"requests",", a test can inspect the exact requests — here against the mock, in your own tests with the same technique:",[35,1917,1919],{"className":98,"code":1918,"language":100,"meta":40,"style":40},"for call in mock.calls[:3]:\n    req = call.request\n    print(req.method, req.url.split(\"?\")[0].replace(GRAPH, \"\"), \"| conflict:\", \"fail\" if \"fail\" in req.url else \"replace\" if \"replace\" in req.url else \"-\",\n          \"| auth:\", \"Authorization\" in req.headers)\nprint(\"upload-session chunks carried no token:\", all(\"Authorization\" not in c.request.headers for c in mock.calls if \"upload.example.com\" in c.request.url))\n",[19,1920,1921,1940,1950,2018,2032],{"__ignoreMap":40},[44,1922,1923,1926,1929,1931,1934,1937],{"class":46,"line":47},[44,1924,1925],{"class":107},"for",[44,1927,1928],{"class":111}," call ",[44,1930,1138],{"class":107},[44,1932,1933],{"class":111}," mock.calls[:",[44,1935,1936],{"class":206},"3",[44,1938,1939],{"class":111},"]:\n",[44,1941,1942,1945,1947],{"class":46,"line":121},[44,1943,1944],{"class":111},"    req ",[44,1946,148],{"class":107},[44,1948,1949],{"class":111}," call.request\n",[44,1951,1952,1954,1957,1960,1963,1965,1968,1970,1972,1975,1978,1981,1983,1985,1988,1991,1994,1997,2000,2003,2005,2007,2009,2011,2013,2016],{"class":46,"line":135},[44,1953,754],{"class":206},[44,1955,1956],{"class":111},"(req.method, req.url.split(",[44,1958,1959],{"class":54},"\"?\"",[44,1961,1962],{"class":111},")[",[44,1964,839],{"class":206},[44,1966,1967],{"class":111},"].replace(",[44,1969,391],{"class":206},[44,1971,163],{"class":111},[44,1973,1974],{"class":54},"\"\"",[44,1976,1977],{"class":111},"), ",[44,1979,1980],{"class":54},"\"| conflict:\"",[44,1982,163],{"class":111},[44,1984,732],{"class":54},[44,1986,1987],{"class":107}," if",[44,1989,1990],{"class":54}," \"fail\"",[44,1992,1993],{"class":107}," in",[44,1995,1996],{"class":111}," req.url ",[44,1998,1999],{"class":107},"else",[44,2001,2002],{"class":54}," \"replace\"",[44,2004,1987],{"class":107},[44,2006,2002],{"class":54},[44,2008,1993],{"class":107},[44,2010,1996],{"class":111},[44,2012,1999],{"class":107},[44,2014,2015],{"class":54}," \"-\"",[44,2017,1214],{"class":111},[44,2019,2020,2023,2025,2027,2029],{"class":46,"line":142},[44,2021,2022],{"class":54},"          \"| auth:\"",[44,2024,163],{"class":111},[44,2026,425],{"class":54},[44,2028,1993],{"class":107},[44,2030,2031],{"class":111}," req.headers)\n",[44,2033,2034,2036,2038,2041,2043,2046,2048,2050,2053,2055,2058,2060,2063,2065,2068,2071,2074,2076],{"class":46,"line":198},[44,2035,301],{"class":206},[44,2037,304],{"class":111},[44,2039,2040],{"class":54},"\"upload-session chunks carried no token:\"",[44,2042,163],{"class":111},[44,2044,2045],{"class":206},"all",[44,2047,304],{"class":111},[44,2049,425],{"class":54},[44,2051,2052],{"class":107}," not",[44,2054,1993],{"class":107},[44,2056,2057],{"class":111}," c.request.headers ",[44,2059,1925],{"class":107},[44,2061,2062],{"class":111}," c ",[44,2064,1138],{"class":107},[44,2066,2067],{"class":111}," mock.calls ",[44,2069,2070],{"class":107},"if",[44,2072,2073],{"class":54}," \"upload.example.com\"",[44,2075,1993],{"class":107},[44,2077,2078],{"class":111}," c.request.url))\n",[10,2080,2081,2082,2085,2086,28],{},"The simple uploads went to ",[19,2083,2084],{},"\u002Fdrives\u002F{id}\u002Froot:\u002F{path}:\u002Fcontent"," with the chosen conflict behaviour and the bearer token; the chunk uploads went to the session URL without it. Checks like these — that paths are quoted, the conflict behaviour is what you intended and tokens go only where they should — catch the mistakes that otherwise surface as a confusing 401 or a silently overwritten file in production. Uploading to SharePoint document libraries by site and path is covered in ",[24,2087,2089],{"href":2088},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-sharepoint-with-python\u002F","Upload an Excel Report to SharePoint with Python",[30,2091,2093],{"id":2092},"common-pitfalls","Common pitfalls",[2095,2096,2097,2105,2117,2123,2129],"ul",{},[2098,2099,2100,2104],"li",{},[2101,2102,2103],"strong",{},"Uploading to a folder that does not exist."," Create missing folders first.",[2098,2106,2107,2110,2111,2113,2114,2116],{},[2101,2108,2109],{},"Overwriting by accident."," Set ",[19,2112,1448],{}," deliberately; ",[19,2115,788],{}," is the default.",[2098,2118,2119,2122],{},[2101,2120,2121],{},"Large files in one PUT."," Use an upload session with chunks in multiples of 320 KiB.",[2098,2124,2125,2128],{},[2101,2126,2127],{},"Sending the bearer token to the upload URL."," The session URL is pre-authenticated.",[2098,2130,2131,2134],{},[2101,2132,2133],{},"Relying on the default table name."," Rename it so later jobs can find it.",[30,2136,2138],{"id":2137},"conclusion","Conclusion",[10,2140,2141,2142,2144,2145,2148,2149,2151,2152,163,2154,2156,2157,2159],{},"To create a workbook in OneDrive or SharePoint with Microsoft Graph, build it in memory and upload the bytes: a single ",[19,2143,346],{}," to ",[19,2146,2147],{},"root:\u002F{path}:\u002Fcontent"," for small files, an upload session with chunks in multiples of 320 KiB for larger ones, with ",[19,2150,784],{}," set to ",[19,2153,788],{},[19,2155,792],{}," or ",[19,2158,796],{}," as the situation needs. Use the returned item ID for the workbook API — for example to add and name a table — and test the requests your code sends before running it against a real tenant.",[30,2161,2163],{"id":2162},"frequently-asked-questions","Frequently asked questions",[10,2165,2166,2169],{},[2101,2167,2168],{},"How do I create an Excel file in OneDrive with Python?","\nBuild the xlsx in memory with pandas or openpyxl and upload the bytes with a PUT request to the drive item's content endpoint, addressed by path under root, using a Microsoft Graph access token.",[10,2171,2172,2175],{},[2101,2173,2174],{},"How do I stop Graph overwriting an existing file?","\nSet the conflictBehavior query parameter to fail, which makes the upload fail with HTTP 409 if the name exists, or use rename to keep both files.",[10,2177,2178,2181],{},[2101,2179,2180],{},"How do I upload a large Excel file with Microsoft Graph?","\nCreate an upload session with createUploadSession and send the file in chunks whose sizes are multiples of 320 KiB, each with a Content-Range header, to the returned upload URL.",[10,2183,2184,2187],{},[2101,2185,2186],{},"How do I add a table to a workbook through Graph?","\nPOST to \u002Fworkbook\u002Ftables\u002Fadd on the item with the range address and hasHeaders, then PATCH the table to give it a meaningful name.",[30,2189,2191],{"id":2190},"related","Related",[2095,2193,2194,2200,2207,2214],{},[2098,2195,2196,2197,2199],{},"Up one level: ",[24,2198,27],{"href":26}," — Excel in the cloud from Python.",[2098,2201,2202,2206],{},[24,2203,2205],{"href":2204},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcopy-a-template-workbook-in-sharepoint-with-microsoft-graph\u002F","Copy a Template Workbook in SharePoint with Microsoft Graph"," — starting from a template instead.",[2098,2208,2209,2213],{},[24,2210,2212],{"href":2211},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fshare-an-excel-file-in-onedrive-with-a-link-using-graph\u002F","Share an Excel File in OneDrive with a Link Using Graph"," — sharing the new workbook.",[2098,2215,2216,2220],{},[24,2217,2219],{"href":2218},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-and-write-excel-online-ranges-with-microsoft-graph\u002F","Read and Write Excel Online Ranges with Microsoft Graph"," — editing it afterwards.",[2222,2223,2224],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":40,"searchDepth":121,"depth":121,"links":2226},[2227,2228,2229,2230,2231,2232,2233,2234,2235,2236],{"id":32,"depth":121,"text":33},{"id":91,"depth":121,"text":92},{"id":339,"depth":121,"text":340},{"id":981,"depth":121,"text":982},{"id":1524,"depth":121,"text":1525},{"id":1907,"depth":121,"text":1908},{"id":2092,"depth":121,"text":2093},{"id":2137,"depth":121,"text":2138},{"id":2162,"depth":121,"text":2163},{"id":2190,"depth":121,"text":2191},"2026-10-02","Create an Excel workbook in OneDrive or SharePoint from Python with Microsoft Graph: build the xlsx in memory, upload it with a simple PUT, control what happens when a file with the same name exists, use an upload session with correctly sized chunks for larger files, turn a range into an Excel table through the workbook API, and verify the requests before pointing the code at a real tenant.","md",[2241,2243,2245,2247],{"q":2168,"a":2242},"Build the xlsx in memory with pandas or openpyxl and upload the bytes with a PUT request to the drive item's content endpoint, addressed by path under root, using a Microsoft Graph access token.",{"q":2174,"a":2244},"Set the conflictBehavior query parameter to fail, which makes the upload fail with HTTP 409 if the name exists, or use rename to keep both files.",{"q":2180,"a":2246},"Create an upload session with createUploadSession and send the file in chunks whose sizes are multiples of 320 KiB, each with a Content-Range header, to the returned upload URL.",{"q":2186,"a":2248},"POST to \u002Fworkbook\u002Ftables\u002Fadd on the item with the range address and hasHeaders, then PATCH the table to give it a meaningful name.","create a new workbook in onedrive with microsoft graph",null,{"breadcrumb":2252},[2253,2255,2258],{"name":2254,"item":1252},"Home",{"name":2256,"item":2257},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":27,"item":26},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph",{"title":2261,"description":2262},"Create a New Excel Workbook in OneDrive with Microsoft Graph","Upload a new xlsx to OneDrive or SharePoint with Microsoft Graph from Python: in-memory workbook, PUT \u002Fcontent, conflictBehavior, chunked upload sessions with 320 KiB multiples, and adding a table via the workbook API.","create-a-new-workbook-in-onedrive-with-microsoft-graph","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcreate-a-new-workbook-in-onedrive-with-microsoft-graph\u002Findex",[2266],"excel-online","how-to","YjDy5hkajB63C6fqiircAhDHC-qKNSVPrDioY7eKBvQ",[2270,2273],{"title":2205,"path":2271,"stem":2272,"children":-1},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcopy-a-template-workbook-in-sharepoint-with-microsoft-graph","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcopy-a-template-workbook-in-sharepoint-with-microsoft-graph\u002Findex",{"title":2274,"path":2275,"stem":2276,"children":-1},"Handle Microsoft Graph Throttling in Excel Automation","\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel","automating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel\u002Findex",[2278,2280],{"path":2279,"title":27},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph",{"path":2281,"title":2256},"\u002Fautomating-reporting-workflows",[],[2284,2287,2290,2293,2295,2297],{"path":2285,"title":2286,"kind":2267},"\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":2288,"title":2289,"kind":2267},"\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":2291,"title":2292,"kind":2267},"\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":2294,"title":1772,"kind":2267},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fappend-rows-to-an-excel-online-table-with-microsoft-graph",{"path":2296,"title":83,"kind":2267},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation",{"path":2298,"title":2299,"kind":2267},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",1790972118985]