[{"data":1,"prerenderedAt":2629},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas":3,"surround:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas":2596,"ancestors:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas":2604,"children:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas":2610,"related:\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas":2611},{"id":4,"title":5,"body":6,"dateModified":2561,"datePublished":2561,"description":2562,"extension":2563,"faq":2564,"keyword":2573,"kind":2574,"meta":2575,"navigation":276,"path":2584,"seo":2585,"slug":2587,"stem":2588,"topics":2589,"type":2594,"__hash__":2595},"docs\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas\u002Findex.md","Read an Excel File from SharePoint or OneDrive into pandas",{"type":7,"value":8,"toc":2547},"minimark",[9,24,155,160,193,220,223,227,238,631,635,655,982,989,1067,1071,1090,1265,1293,1297,1308,1648,1651,1732,1736,1746,2050,2057,2061,2070,2295,2318,2322,2436,2440,2461,2465,2477,2481,2487,2493,2499,2505,2509,2543],[10,11,12,13,17,18,23],"p",{},"The finance team keeps the source data for your report in a SharePoint document library, and the sales\nteam keeps theirs in someone's OneDrive. Neither is a path your script can open, and asking people to\nexport copies to a network share every morning is how reports end up a day stale. Microsoft Graph turns\nboth locations into HTTP endpoints: resolve the library, ask for the file's content, and hand the bytes\nto ",[14,15,16],"code",{},"pd.read_excel",". 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 the whole read path with an app-only token, so it can run unattended.",[25,26,34,35,34,39,34,43,34,50,34,59,34,66,34,71,34,78,34,83,34,87,34,92,34,96,34,100,34,103,34,106,34,110,34,113,34,117,34,120,34,123,34,127,34,130,34,134,34,137,34,142,34,147,34,150],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 136","img",[30,31],"spr-path-t","spr-path-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},"From a SharePoint path to a pandas DataFrame",[40,41,42],"desc",{"id":31},"Get an app-only token, resolve the site from its host name and path, resolve the document library drive, download the file content by path into memory, and read it with pandas.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","136","#ffffff",[44,51],{"x":52,"y":52,"width":53,"height":54,"rx":55,"fill":56,"stroke":57,"style":58},"20","121.6","66","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[60,61,65],"text",{"x":62,"y":63,"style":64},"80.8","50","font-size:14px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","token",[60,67,70],{"x":62,"y":68,"style":69},"72","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","app-only",[72,73],"line",{"x1":74,"y1":75,"x2":76,"y2":75,"stroke":77,"style":58},"146.6","53","163.6","var(--brand,#5b5cf0)",[79,80],"polygon",{"points":81,"fill":82},"166.6,53 157.6,48 157.6,58","#5b5cf0",[44,84],{"x":85,"y":52,"width":53,"height":54,"rx":55,"fill":86,"stroke":77,"style":58},"169.6","#f0f4ff",[60,88,91],{"x":89,"y":63,"style":90},"230.4","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","site id",[60,93,95],{"x":89,"y":68,"style":94},"font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","host + site path",[72,97],{"x1":98,"y1":75,"x2":99,"y2":75,"stroke":77,"style":58},"296.2","313.2",[79,101],{"points":102,"fill":82},"316.2,53 307.2,48 307.2,58",[44,104],{"x":105,"y":52,"width":53,"height":54,"rx":55,"fill":86,"stroke":77,"style":58},"319.2",[60,107,109],{"x":108,"y":63,"style":90},"380","drive id",[60,111,112],{"x":108,"y":68,"style":94},"document library",[72,114],{"x1":115,"y1":75,"x2":116,"y2":75,"stroke":77,"style":58},"445.8","462.8",[79,118],{"points":119,"fill":82},"465.8,53 456.8,48 456.8,58",[44,121],{"x":122,"y":52,"width":53,"height":54,"rx":55,"fill":86,"stroke":77,"style":58},"468.8",[60,124,126],{"x":125,"y":63,"style":90},"529.6","…\u002Fcontent",[60,128,129],{"x":125,"y":68,"style":94},"bytes in memory",[72,131],{"x1":132,"y1":75,"x2":133,"y2":75,"stroke":77,"style":58},"595.4","612.4",[79,135],{"points":136,"fill":82},"615.4,53 606.4,48 606.4,58",[44,138],{"x":139,"y":52,"width":53,"height":54,"rx":55,"fill":140,"stroke":141,"style":58},"618.4","#d9f4f1","var(--teal,#0f9488)",[60,143,146],{"x":144,"y":63,"style":145},"679.2","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","read_excel",[60,148,149],{"x":144,"y":68,"style":94},"DataFrame",[60,151,154],{"x":108,"y":152,"style":153},"120","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","resolve IDs once, cache them, then every read is one request",[156,157,159],"h2",{"id":158},"prerequisites","Prerequisites",[161,162,167],"pre",{"className":163,"code":164,"language":165,"meta":166,"style":166},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install msal requests pandas openpyxl\n","bash","",[14,168,169],{"__ignoreMap":166},[170,171,173,177,181,184,187,190],"span",{"class":72,"line":172},1,[170,174,176],{"class":175},"sMTad","pip",[170,178,180],{"class":179},"srMev"," install",[170,182,183],{"class":179}," msal",[170,185,186],{"class":179}," requests",[170,188,189],{"class":179}," pandas",[170,191,192],{"class":179}," openpyxl\n",[194,195,196,208],"ul",{},[197,198,199,200,203,204,207],"li",{},"An app registration with an application permission that covers the file: ",[14,201,202],{},"Sites.Selected"," with the site granted to the app\n(the least-privileged choice for SharePoint), or ",[14,205,206],{},"Files.Read.All"," for broader access including users' OneDrive. Admin consent\nis required either way.",[197,209,210,211,214,215,219],{},"An app-only token function such as ",[14,212,213],{},"get_app_token()"," from\n",[19,216,218],{"href":217},"\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",".",[10,221,222],{},"Downloading file content is one of the Graph operations that accepts application permissions, unlike the workbook endpoints that\nedit ranges and tables. That is what makes this read path suitable for a scheduled job without a signed-in user.",[156,224,226],{"id":225},"step-1-create-a-session-with-the-token","Step 1 — Create a session with the token",[10,228,229,230,233,234,237],{},"Wrap the token in a ",[14,231,232],{},"requests.Session"," so every call carries the ",[14,235,236],{},"Authorization"," header and reuses one connection. A small helper\nthat raises on errors with Graph's own message keeps the later steps short.",[161,239,243],{"className":240,"code":241,"language":242,"meta":166,"style":166},"language-python shiki shiki-themes github-light github-dark-high-contrast","import os\nimport msal\nimport requests\n\nGRAPH = \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\n\ndef get_app_token() -> str:\n    app = msal.ConfidentialClientApplication(\n        os.environ[\"GRAPH_CLIENT_ID\"],\n        authority=f\"https:\u002F\u002Flogin.microsoftonline.com\u002F{os.environ['GRAPH_TENANT_ID']}\",\n        client_credential=os.environ[\"GRAPH_CLIENT_SECRET\"],\n    )\n    result = app.acquire_token_for_client(scopes=[\"https:\u002F\u002Fgraph.microsoft.com\u002F.default\"])\n    return result[\"access_token\"]\n\nsession = requests.Session()\nsession.headers[\"Authorization\"] = f\"Bearer {get_app_token()}\"\n\ndef graph_get(path: str, **kwargs) -> requests.Response:\n    response = session.get(f\"{GRAPH}{path}\", timeout=60, **kwargs)\n    if response.status_code >= 400:\n        raise RuntimeError(f\"GET {path} failed: {response.status_code} {response.text[:300]}\")\n    return response\n","python",[14,244,245,255,263,271,278,291,296,315,327,339,376,391,397,422,437,442,453,482,487,509,555,572,623],{"__ignoreMap":166},[170,246,247,251],{"class":72,"line":172},[170,248,250],{"class":249},"s-kum","import",[170,252,254],{"class":253},"skGVy"," os\n",[170,256,258,260],{"class":72,"line":257},2,[170,259,250],{"class":249},[170,261,262],{"class":253}," msal\n",[170,264,266,268],{"class":72,"line":265},3,[170,267,250],{"class":249},[170,269,270],{"class":253}," requests\n",[170,272,274],{"class":72,"line":273},4,[170,275,277],{"emptyLinePlaceholder":276},true,"\n",[170,279,281,285,288],{"class":72,"line":280},5,[170,282,284],{"class":283},"sP0c6","GRAPH",[170,286,287],{"class":249}," =",[170,289,290],{"class":179}," \"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\"\n",[170,292,294],{"class":72,"line":293},6,[170,295,277],{"emptyLinePlaceholder":276},[170,297,299,302,306,309,312],{"class":72,"line":298},7,[170,300,301],{"class":249},"def",[170,303,305],{"class":304},"s_Opv"," get_app_token",[170,307,308],{"class":253},"() -> ",[170,310,311],{"class":283},"str",[170,313,314],{"class":253},":\n",[170,316,318,321,324],{"class":72,"line":317},8,[170,319,320],{"class":253},"    app ",[170,322,323],{"class":249},"=",[170,325,326],{"class":253}," msal.ConfidentialClientApplication(\n",[170,328,330,333,336],{"class":72,"line":329},9,[170,331,332],{"class":253},"        os.environ[",[170,334,335],{"class":179},"\"GRAPH_CLIENT_ID\"",[170,337,338],{"class":253},"],\n",[170,340,342,346,348,351,354,358,361,364,367,370,373],{"class":72,"line":341},10,[170,343,345],{"class":344},"sa561","        authority",[170,347,323],{"class":249},[170,349,350],{"class":249},"f",[170,352,353],{"class":179},"\"https:\u002F\u002Flogin.microsoftonline.com\u002F",[170,355,357],{"class":356},"sSjpA","{",[170,359,360],{"class":253},"os.environ[",[170,362,363],{"class":179},"'GRAPH_TENANT_ID'",[170,365,366],{"class":253},"]",[170,368,369],{"class":356},"}",[170,371,372],{"class":179},"\"",[170,374,375],{"class":253},",\n",[170,377,379,382,384,386,389],{"class":72,"line":378},11,[170,380,381],{"class":344},"        client_credential",[170,383,323],{"class":249},[170,385,360],{"class":253},[170,387,388],{"class":179},"\"GRAPH_CLIENT_SECRET\"",[170,390,338],{"class":253},[170,392,394],{"class":72,"line":393},12,[170,395,396],{"class":253},"    )\n",[170,398,400,403,405,408,411,413,416,419],{"class":72,"line":399},13,[170,401,402],{"class":253},"    result ",[170,404,323],{"class":249},[170,406,407],{"class":253}," app.acquire_token_for_client(",[170,409,410],{"class":344},"scopes",[170,412,323],{"class":249},[170,414,415],{"class":253},"[",[170,417,418],{"class":179},"\"https:\u002F\u002Fgraph.microsoft.com\u002F.default\"",[170,420,421],{"class":253},"])\n",[170,423,425,428,431,434],{"class":72,"line":424},14,[170,426,427],{"class":249},"    return",[170,429,430],{"class":253}," result[",[170,432,433],{"class":179},"\"access_token\"",[170,435,436],{"class":253},"]\n",[170,438,440],{"class":72,"line":439},15,[170,441,277],{"emptyLinePlaceholder":276},[170,443,445,448,450],{"class":72,"line":444},16,[170,446,447],{"class":253},"session ",[170,449,323],{"class":249},[170,451,452],{"class":253}," requests.Session()\n",[170,454,456,459,462,465,467,470,473,475,477,479],{"class":72,"line":455},17,[170,457,458],{"class":253},"session.headers[",[170,460,461],{"class":179},"\"Authorization\"",[170,463,464],{"class":253},"] ",[170,466,323],{"class":249},[170,468,469],{"class":249}," f",[170,471,472],{"class":179},"\"Bearer ",[170,474,357],{"class":356},[170,476,213],{"class":253},[170,478,369],{"class":356},[170,480,481],{"class":179},"\"\n",[170,483,485],{"class":72,"line":484},18,[170,486,277],{"emptyLinePlaceholder":276},[170,488,490,492,495,498,500,503,506],{"class":72,"line":489},19,[170,491,301],{"class":249},[170,493,494],{"class":304}," graph_get",[170,496,497],{"class":253},"(path: ",[170,499,311],{"class":283},[170,501,502],{"class":253},", ",[170,504,505],{"class":249},"**",[170,507,508],{"class":253},"kwargs) -> requests.Response:\n",[170,510,512,515,517,520,522,524,526,528,531,534,536,538,540,543,545,548,550,552],{"class":72,"line":511},20,[170,513,514],{"class":253},"    response ",[170,516,323],{"class":249},[170,518,519],{"class":253}," session.get(",[170,521,350],{"class":249},[170,523,372],{"class":179},[170,525,357],{"class":356},[170,527,284],{"class":283},[170,529,530],{"class":356},"}{",[170,532,533],{"class":253},"path",[170,535,369],{"class":356},[170,537,372],{"class":179},[170,539,502],{"class":253},[170,541,542],{"class":344},"timeout",[170,544,323],{"class":249},[170,546,547],{"class":283},"60",[170,549,502],{"class":253},[170,551,505],{"class":249},[170,553,554],{"class":253},"kwargs)\n",[170,556,558,561,564,567,570],{"class":72,"line":557},21,[170,559,560],{"class":249},"    if",[170,562,563],{"class":253}," response.status_code ",[170,565,566],{"class":249},">=",[170,568,569],{"class":283}," 400",[170,571,314],{"class":253},[170,573,575,578,581,584,586,589,591,593,595,598,600,603,605,608,611,614,616,618,620],{"class":72,"line":574},22,[170,576,577],{"class":249},"        raise",[170,579,580],{"class":283}," RuntimeError",[170,582,583],{"class":253},"(",[170,585,350],{"class":249},[170,587,588],{"class":179},"\"GET ",[170,590,357],{"class":356},[170,592,533],{"class":253},[170,594,369],{"class":356},[170,596,597],{"class":179}," failed: ",[170,599,357],{"class":356},[170,601,602],{"class":253},"response.status_code",[170,604,369],{"class":356},[170,606,607],{"class":356}," {",[170,609,610],{"class":253},"response.text[:",[170,612,613],{"class":283},"300",[170,615,366],{"class":253},[170,617,369],{"class":356},[170,619,372],{"class":179},[170,621,622],{"class":253},")\n",[170,624,626,628],{"class":72,"line":625},23,[170,627,427],{"class":249},[170,629,630],{"class":253}," response\n",[156,632,634],{"id":633},"step-2-resolve-the-site-and-its-document-library","Step 2 — Resolve the site and its document library",[10,636,637,638,642,643,646,647,650,651,654],{},"SharePoint URLs are for browsers; Graph wants IDs. A site is addressed by its host name and server-relative path, and its default\ndocument library (the one called ",[639,640,641],"strong",{},"Documents"," in the UI, ",[639,644,645],{},"Shared Documents"," in URLs) is the site's ",[14,648,649],{},"drive",". For a different\nlibrary, list ",[14,652,653],{},"drives"," and pick it by name.",[161,656,658],{"className":240,"code":657,"language":242,"meta":166,"style":166},"def site_id(hostname: str, site_path: str) -> str:\n    return graph_get(f\"\u002Fsites\u002F{hostname}:\u002Fsites\u002F{site_path}\").json()[\"id\"]\n\ndef drive_id(site: str, library: str | None = None) -> str:\n    if library is None:\n        return graph_get(f\"\u002Fsites\u002F{site}\u002Fdrive\").json()[\"id\"]\n    drives = graph_get(f\"\u002Fsites\u002F{site}\u002Fdrives\").json()[\"value\"]\n    for drive in drives:\n        if drive[\"name\"] == library:\n            return drive[\"id\"]\n    raise LookupError(f\"no library named {library!r}; found {[d['name'] for d in drives]}\")\n\nSITE = site_id(\"contoso.sharepoint.com\", \"Finance\")\nDRIVE = drive_id(SITE)\nprint(SITE, DRIVE)\n",[14,659,660,684,723,727,760,774,801,830,844,863,874,929,933,953,967],{"__ignoreMap":166},[170,661,662,664,667,670,672,675,677,680,682],{"class":72,"line":172},[170,663,301],{"class":249},[170,665,666],{"class":304}," site_id",[170,668,669],{"class":253},"(hostname: ",[170,671,311],{"class":283},[170,673,674],{"class":253},", site_path: ",[170,676,311],{"class":283},[170,678,679],{"class":253},") -> ",[170,681,311],{"class":283},[170,683,314],{"class":253},[170,685,686,688,691,693,696,698,701,703,706,708,711,713,715,718,721],{"class":72,"line":257},[170,687,427],{"class":249},[170,689,690],{"class":253}," graph_get(",[170,692,350],{"class":249},[170,694,695],{"class":179},"\"\u002Fsites\u002F",[170,697,357],{"class":356},[170,699,700],{"class":253},"hostname",[170,702,369],{"class":356},[170,704,705],{"class":179},":\u002Fsites\u002F",[170,707,357],{"class":356},[170,709,710],{"class":253},"site_path",[170,712,369],{"class":356},[170,714,372],{"class":179},[170,716,717],{"class":253},").json()[",[170,719,720],{"class":179},"\"id\"",[170,722,436],{"class":253},[170,724,725],{"class":72,"line":265},[170,726,277],{"emptyLinePlaceholder":276},[170,728,729,731,734,737,739,742,744,747,750,752,754,756,758],{"class":72,"line":273},[170,730,301],{"class":249},[170,732,733],{"class":304}," drive_id",[170,735,736],{"class":253},"(site: ",[170,738,311],{"class":283},[170,740,741],{"class":253},", library: ",[170,743,311],{"class":283},[170,745,746],{"class":249}," |",[170,748,749],{"class":283}," None",[170,751,287],{"class":249},[170,753,749],{"class":283},[170,755,679],{"class":253},[170,757,311],{"class":283},[170,759,314],{"class":253},[170,761,762,764,767,770,772],{"class":72,"line":280},[170,763,560],{"class":249},[170,765,766],{"class":253}," library ",[170,768,769],{"class":249},"is",[170,771,749],{"class":283},[170,773,314],{"class":253},[170,775,776,779,781,783,785,787,790,792,795,797,799],{"class":72,"line":293},[170,777,778],{"class":249},"        return",[170,780,690],{"class":253},[170,782,350],{"class":249},[170,784,695],{"class":179},[170,786,357],{"class":356},[170,788,789],{"class":253},"site",[170,791,369],{"class":356},[170,793,794],{"class":179},"\u002Fdrive\"",[170,796,717],{"class":253},[170,798,720],{"class":179},[170,800,436],{"class":253},[170,802,803,806,808,810,812,814,816,818,820,823,825,828],{"class":72,"line":298},[170,804,805],{"class":253},"    drives ",[170,807,323],{"class":249},[170,809,690],{"class":253},[170,811,350],{"class":249},[170,813,695],{"class":179},[170,815,357],{"class":356},[170,817,789],{"class":253},[170,819,369],{"class":356},[170,821,822],{"class":179},"\u002Fdrives\"",[170,824,717],{"class":253},[170,826,827],{"class":179},"\"value\"",[170,829,436],{"class":253},[170,831,832,835,838,841],{"class":72,"line":317},[170,833,834],{"class":249},"    for",[170,836,837],{"class":253}," drive ",[170,839,840],{"class":249},"in",[170,842,843],{"class":253}," drives:\n",[170,845,846,849,852,855,857,860],{"class":72,"line":329},[170,847,848],{"class":249},"        if",[170,850,851],{"class":253}," drive[",[170,853,854],{"class":179},"\"name\"",[170,856,464],{"class":253},[170,858,859],{"class":249},"==",[170,861,862],{"class":253}," library:\n",[170,864,865,868,870,872],{"class":72,"line":341},[170,866,867],{"class":249},"            return",[170,869,851],{"class":253},[170,871,720],{"class":179},[170,873,436],{"class":253},[170,875,876,879,882,884,886,889,891,894,897,899,902,904,907,910,912,915,918,920,923,925,927],{"class":72,"line":378},[170,877,878],{"class":249},"    raise",[170,880,881],{"class":283}," LookupError",[170,883,583],{"class":253},[170,885,350],{"class":249},[170,887,888],{"class":179},"\"no library named ",[170,890,357],{"class":356},[170,892,893],{"class":253},"library",[170,895,896],{"class":249},"!r",[170,898,369],{"class":356},[170,900,901],{"class":179},"; found ",[170,903,357],{"class":356},[170,905,906],{"class":253},"[d[",[170,908,909],{"class":179},"'name'",[170,911,464],{"class":253},[170,913,914],{"class":249},"for",[170,916,917],{"class":253}," d ",[170,919,840],{"class":249},[170,921,922],{"class":253}," drives]",[170,924,369],{"class":356},[170,926,372],{"class":179},[170,928,622],{"class":253},[170,930,931],{"class":72,"line":393},[170,932,277],{"emptyLinePlaceholder":276},[170,934,935,938,940,943,946,948,951],{"class":72,"line":399},[170,936,937],{"class":283},"SITE",[170,939,287],{"class":249},[170,941,942],{"class":253}," site_id(",[170,944,945],{"class":179},"\"contoso.sharepoint.com\"",[170,947,502],{"class":253},[170,949,950],{"class":179},"\"Finance\"",[170,952,622],{"class":253},[170,954,955,958,960,963,965],{"class":72,"line":424},[170,956,957],{"class":283},"DRIVE",[170,959,287],{"class":249},[170,961,962],{"class":253}," drive_id(",[170,964,937],{"class":283},[170,966,622],{"class":253},[170,968,969,972,974,976,978,980],{"class":72,"line":439},[170,970,971],{"class":283},"print",[170,973,583],{"class":253},[170,975,937],{"class":283},[170,977,502],{"class":253},[170,979,957],{"class":283},[170,981,622],{"class":253},[10,983,984,985,988],{},"These IDs are stable, so resolve them once and keep them in configuration rather than looking them up on every run. For a user's\nOneDrive the drive is simply ",[14,986,987],{},"\u002Fusers\u002F{user-principal-name}\u002Fdrive",", and everything after this step is identical.",[25,990,34,996,34,999,34,1002,34,1005,34,1009,34,1014,34,1019,34,1022,34,1026,34,1029,34,1032,34,1037,34,1040,34,1043,34,1047,34,1050,34,1055,34,1060,34,1063],{"viewBox":991,"role":28,"ariaLabelledBy":992,"xmlns":32,"style":995},"0 0 720 380",[993,994],"spr-address-t","spr-address-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,997,998],{"id":993},"Addressing a file in SharePoint and OneDrive through Graph",[40,1000,1001],{"id":994},"A SharePoint site is resolved from host name and path to a site id, its document library to a drive id, and the file is then addressed by path under the drive root. A OneDrive file is addressed through the user drive and the same root path syntax.",[44,1003],{"x":46,"y":46,"width":1004,"height":108,"fill":49},"720",[44,1006],{"x":52,"y":52,"width":1007,"height":1008,"rx":55,"fill":86,"stroke":77,"style":58},"680","52",[60,1010,789],{"x":1011,"y":1012,"style":1013},"40","51","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start",[60,1015,1018],{"x":1016,"y":1012,"style":1017},"240","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","\u002Fsites\u002Fcontoso.sharepoint.com:\u002Fsites\u002FFinance",[44,1020],{"x":52,"y":1021,"width":1007,"height":1008,"rx":55,"fill":86,"stroke":77,"style":58},"84",[60,1023,1025],{"x":1011,"y":1024,"style":1013},"115","library (drive)",[60,1027,1028],{"x":1016,"y":1024,"style":1017},"\u002Fsites\u002F{site-id}\u002Fdrive  or  \u002Fdrives\u002F{drive-id}",[44,1030],{"x":52,"y":1031,"width":1007,"height":1008,"rx":55,"fill":140,"stroke":141,"style":58},"148",[60,1033,1036],{"x":1011,"y":1034,"style":1035},"179","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","file by path",[60,1038,1039],{"x":1016,"y":1034,"style":1017},"\u002Fdrives\u002F{drive-id}\u002Froot:\u002FReports\u002Fsales.xlsx",[44,1041],{"x":52,"y":1042,"width":1007,"height":1008,"rx":55,"fill":140,"stroke":141,"style":58},"212",[60,1044,1046],{"x":1011,"y":1045,"style":1035},"243","file content",[60,1048,1049],{"x":1016,"y":1045,"style":1017},"…\u002Froot:\u002FReports\u002Fsales.xlsx:\u002Fcontent",[44,1051],{"x":52,"y":1052,"width":1007,"height":1008,"rx":55,"fill":1053,"stroke":1054,"style":58},"276","#fdefd8","var(--gold,#b4740a)",[60,1056,1059],{"x":1011,"y":1057,"style":1058},"307","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","OneDrive",[60,1061,1062],{"x":1016,"y":1057,"style":1017},"\u002Fusers\u002F{upn}\u002Fdrive\u002Froot:\u002FReports\u002Fsales.xlsx",[60,1064,1066],{"x":1065,"y":1065,"style":153},"360","after the drive, SharePoint and OneDrive look the same",[156,1068,1070],{"id":1069},"step-3-download-the-workbook-into-memory","Step 3 — Download the workbook into memory",[10,1072,1073,1074,1077,1078,1081,1082,1085,1086,1089],{},"Address the file by its path under the drive root and append ",[14,1075,1076],{},":\u002Fcontent",". Graph answers with a redirect to a short-lived download\nURL, which ",[14,1079,1080],{},"requests"," follows automatically, and the body is the ",[14,1083,1084],{},".xlsx"," file itself. Wrap the bytes in ",[14,1087,1088],{},"BytesIO"," and pandas reads\nthem exactly as it would a file on disk.",[161,1091,1093],{"className":240,"code":1092,"language":242,"meta":166,"style":166},"from io import BytesIO\nfrom urllib.parse import quote\nimport pandas as pd\n\ndef read_sharepoint_excel(drive: str, path: str, **read_kwargs) -> pd.DataFrame:\n    encoded = quote(path.strip(\"\u002F\"))\n    content = graph_get(f\"\u002Fdrives\u002F{drive}\u002Froot:\u002F{encoded}:\u002Fcontent\").content\n    return pd.read_excel(BytesIO(content), **read_kwargs)\n\nsales = read_sharepoint_excel(DRIVE, \"Reports\u002F2026\u002Fsales.xlsx\", sheet_name=\"Sales\")\nprint(sales.head())\n",[14,1094,1095,1108,1120,1133,1137,1161,1177,1213,1225,1229,1258],{"__ignoreMap":166},[170,1096,1097,1100,1103,1105],{"class":72,"line":172},[170,1098,1099],{"class":249},"from",[170,1101,1102],{"class":253}," io ",[170,1104,250],{"class":249},[170,1106,1107],{"class":253}," BytesIO\n",[170,1109,1110,1112,1115,1117],{"class":72,"line":257},[170,1111,1099],{"class":249},[170,1113,1114],{"class":253}," urllib.parse ",[170,1116,250],{"class":249},[170,1118,1119],{"class":253}," quote\n",[170,1121,1122,1124,1127,1130],{"class":72,"line":265},[170,1123,250],{"class":249},[170,1125,1126],{"class":253}," pandas ",[170,1128,1129],{"class":249},"as",[170,1131,1132],{"class":253}," pd\n",[170,1134,1135],{"class":72,"line":273},[170,1136,277],{"emptyLinePlaceholder":276},[170,1138,1139,1141,1144,1147,1149,1152,1154,1156,1158],{"class":72,"line":280},[170,1140,301],{"class":249},[170,1142,1143],{"class":304}," read_sharepoint_excel",[170,1145,1146],{"class":253},"(drive: ",[170,1148,311],{"class":283},[170,1150,1151],{"class":253},", path: ",[170,1153,311],{"class":283},[170,1155,502],{"class":253},[170,1157,505],{"class":249},[170,1159,1160],{"class":253},"read_kwargs) -> pd.DataFrame:\n",[170,1162,1163,1166,1168,1171,1174],{"class":72,"line":293},[170,1164,1165],{"class":253},"    encoded ",[170,1167,323],{"class":249},[170,1169,1170],{"class":253}," quote(path.strip(",[170,1172,1173],{"class":179},"\"\u002F\"",[170,1175,1176],{"class":253},"))\n",[170,1178,1179,1182,1184,1186,1188,1191,1193,1195,1197,1200,1202,1205,1207,1210],{"class":72,"line":298},[170,1180,1181],{"class":253},"    content ",[170,1183,323],{"class":249},[170,1185,690],{"class":253},[170,1187,350],{"class":249},[170,1189,1190],{"class":179},"\"\u002Fdrives\u002F",[170,1192,357],{"class":356},[170,1194,649],{"class":253},[170,1196,369],{"class":356},[170,1198,1199],{"class":179},"\u002Froot:\u002F",[170,1201,357],{"class":356},[170,1203,1204],{"class":253},"encoded",[170,1206,369],{"class":356},[170,1208,1209],{"class":179},":\u002Fcontent\"",[170,1211,1212],{"class":253},").content\n",[170,1214,1215,1217,1220,1222],{"class":72,"line":317},[170,1216,427],{"class":249},[170,1218,1219],{"class":253}," pd.read_excel(BytesIO(content), ",[170,1221,505],{"class":249},[170,1223,1224],{"class":253},"read_kwargs)\n",[170,1226,1227],{"class":72,"line":329},[170,1228,277],{"emptyLinePlaceholder":276},[170,1230,1231,1234,1236,1239,1241,1243,1246,1248,1251,1253,1256],{"class":72,"line":341},[170,1232,1233],{"class":253},"sales ",[170,1235,323],{"class":249},[170,1237,1238],{"class":253}," read_sharepoint_excel(",[170,1240,957],{"class":283},[170,1242,502],{"class":253},[170,1244,1245],{"class":179},"\"Reports\u002F2026\u002Fsales.xlsx\"",[170,1247,502],{"class":253},[170,1249,1250],{"class":344},"sheet_name",[170,1252,323],{"class":249},[170,1254,1255],{"class":179},"\"Sales\"",[170,1257,622],{"class":253},[170,1259,1260,1262],{"class":72,"line":378},[170,1261,971],{"class":283},[170,1263,1264],{"class":253},"(sales.head())\n",[10,1266,1267,1270,1271,1274,1275,1279,1280,1282,1283,502,1286,502,1289,1292],{},[14,1268,1269],{},"quote"," matters as soon as a folder or file name contains a space, a ",[14,1272,1273],{},"#"," or non-ASCII characters, which SharePoint libraries are full\nof. The in-memory read is the same technique described for URLs and byte strings in\n",[19,1276,1278],{"href":1277},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python\u002F","Read an Excel File from a URL or Bytes in Python",",\nand every ",[14,1281,146],{}," option — ",[14,1284,1285],{},"sheet_name=None",[14,1287,1288],{},"usecols",[14,1290,1291],{},"dtype"," — passes straight through.",[156,1294,1296],{"id":1295},"step-4-skip-the-download-when-nothing-changed","Step 4 — Skip the download when nothing changed",[10,1298,1299,1300,1303,1304,1307],{},"Source workbooks usually change once a day, while a job might run every hour. The file's metadata is a small JSON response that\nincludes its ",[14,1301,1302],{},"eTag"," and ",[14,1305,1306],{},"lastModifiedDateTime","; compare the eTag with the one from the last run and only download when it differs.",[161,1309,1311],{"className":240,"code":1310,"language":242,"meta":166,"style":166},"import json\nfrom pathlib import Path\n\nSTATE = Path(\"sharepoint_state.json\")\n\ndef changed_since_last_run(drive: str, path: str) -> tuple[bool, dict]:\n    meta = graph_get(f\"\u002Fdrives\u002F{drive}\u002Froot:\u002F{quote(path)}\").json()\n    seen = json.loads(STATE.read_text()) if STATE.exists() else {}\n    is_new = seen.get(path) != meta[\"eTag\"]\n    return is_new, meta\n\ndef remember(path: str, meta: dict) -> None:\n    seen = json.loads(STATE.read_text()) if STATE.exists() else {}\n    seen[path] = meta[\"eTag\"]\n    STATE.write_text(json.dumps(seen, indent=2))\n\nis_new, meta = changed_since_last_run(DRIVE, \"Reports\u002F2026\u002Fsales.xlsx\")\nif is_new:\n    sales = read_sharepoint_excel(DRIVE, \"Reports\u002F2026\u002Fsales.xlsx\", sheet_name=\"Sales\")\n    remember(\"Reports\u002F2026\u002Fsales.xlsx\", meta)\nprint(meta[\"lastModifiedDateTime\"], \"changed\" if is_new else \"unchanged\")\n",[14,1312,1313,1320,1332,1336,1351,1355,1384,1417,1447,1468,1475,1479,1502,1524,1537,1555,1559,1577,1584,1609,1619],{"__ignoreMap":166},[170,1314,1315,1317],{"class":72,"line":172},[170,1316,250],{"class":249},[170,1318,1319],{"class":253}," json\n",[170,1321,1322,1324,1327,1329],{"class":72,"line":257},[170,1323,1099],{"class":249},[170,1325,1326],{"class":253}," pathlib ",[170,1328,250],{"class":249},[170,1330,1331],{"class":253}," Path\n",[170,1333,1334],{"class":72,"line":265},[170,1335,277],{"emptyLinePlaceholder":276},[170,1337,1338,1341,1343,1346,1349],{"class":72,"line":273},[170,1339,1340],{"class":283},"STATE",[170,1342,287],{"class":249},[170,1344,1345],{"class":253}," Path(",[170,1347,1348],{"class":179},"\"sharepoint_state.json\"",[170,1350,622],{"class":253},[170,1352,1353],{"class":72,"line":280},[170,1354,277],{"emptyLinePlaceholder":276},[170,1356,1357,1359,1362,1364,1366,1368,1370,1373,1376,1378,1381],{"class":72,"line":293},[170,1358,301],{"class":249},[170,1360,1361],{"class":304}," changed_since_last_run",[170,1363,1146],{"class":253},[170,1365,311],{"class":283},[170,1367,1151],{"class":253},[170,1369,311],{"class":283},[170,1371,1372],{"class":253},") -> tuple[",[170,1374,1375],{"class":283},"bool",[170,1377,502],{"class":253},[170,1379,1380],{"class":283},"dict",[170,1382,1383],{"class":253},"]:\n",[170,1385,1386,1389,1391,1393,1395,1397,1399,1401,1403,1405,1407,1410,1412,1414],{"class":72,"line":298},[170,1387,1388],{"class":253},"    meta ",[170,1390,323],{"class":249},[170,1392,690],{"class":253},[170,1394,350],{"class":249},[170,1396,1190],{"class":179},[170,1398,357],{"class":356},[170,1400,649],{"class":253},[170,1402,369],{"class":356},[170,1404,1199],{"class":179},[170,1406,357],{"class":356},[170,1408,1409],{"class":253},"quote(path)",[170,1411,369],{"class":356},[170,1413,372],{"class":179},[170,1415,1416],{"class":253},").json()\n",[170,1418,1419,1422,1424,1427,1429,1432,1435,1438,1441,1444],{"class":72,"line":317},[170,1420,1421],{"class":253},"    seen ",[170,1423,323],{"class":249},[170,1425,1426],{"class":253}," json.loads(",[170,1428,1340],{"class":283},[170,1430,1431],{"class":253},".read_text()) ",[170,1433,1434],{"class":249},"if",[170,1436,1437],{"class":283}," STATE",[170,1439,1440],{"class":253},".exists() ",[170,1442,1443],{"class":249},"else",[170,1445,1446],{"class":253}," {}\n",[170,1448,1449,1452,1454,1457,1460,1463,1466],{"class":72,"line":329},[170,1450,1451],{"class":253},"    is_new ",[170,1453,323],{"class":249},[170,1455,1456],{"class":253}," seen.get(path) ",[170,1458,1459],{"class":249},"!=",[170,1461,1462],{"class":253}," meta[",[170,1464,1465],{"class":179},"\"eTag\"",[170,1467,436],{"class":253},[170,1469,1470,1472],{"class":72,"line":341},[170,1471,427],{"class":249},[170,1473,1474],{"class":253}," is_new, meta\n",[170,1476,1477],{"class":72,"line":378},[170,1478,277],{"emptyLinePlaceholder":276},[170,1480,1481,1483,1486,1488,1490,1493,1495,1497,1500],{"class":72,"line":393},[170,1482,301],{"class":249},[170,1484,1485],{"class":304}," remember",[170,1487,497],{"class":253},[170,1489,311],{"class":283},[170,1491,1492],{"class":253},", meta: ",[170,1494,1380],{"class":283},[170,1496,679],{"class":253},[170,1498,1499],{"class":283},"None",[170,1501,314],{"class":253},[170,1503,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522],{"class":72,"line":399},[170,1505,1421],{"class":253},[170,1507,323],{"class":249},[170,1509,1426],{"class":253},[170,1511,1340],{"class":283},[170,1513,1431],{"class":253},[170,1515,1434],{"class":249},[170,1517,1437],{"class":283},[170,1519,1440],{"class":253},[170,1521,1443],{"class":249},[170,1523,1446],{"class":253},[170,1525,1526,1529,1531,1533,1535],{"class":72,"line":424},[170,1527,1528],{"class":253},"    seen[path] ",[170,1530,323],{"class":249},[170,1532,1462],{"class":253},[170,1534,1465],{"class":179},[170,1536,436],{"class":253},[170,1538,1539,1542,1545,1548,1550,1553],{"class":72,"line":439},[170,1540,1541],{"class":283},"    STATE",[170,1543,1544],{"class":253},".write_text(json.dumps(seen, ",[170,1546,1547],{"class":344},"indent",[170,1549,323],{"class":249},[170,1551,1552],{"class":283},"2",[170,1554,1176],{"class":253},[170,1556,1557],{"class":72,"line":444},[170,1558,277],{"emptyLinePlaceholder":276},[170,1560,1561,1564,1566,1569,1571,1573,1575],{"class":72,"line":455},[170,1562,1563],{"class":253},"is_new, meta ",[170,1565,323],{"class":249},[170,1567,1568],{"class":253}," changed_since_last_run(",[170,1570,957],{"class":283},[170,1572,502],{"class":253},[170,1574,1245],{"class":179},[170,1576,622],{"class":253},[170,1578,1579,1581],{"class":72,"line":484},[170,1580,1434],{"class":249},[170,1582,1583],{"class":253}," is_new:\n",[170,1585,1586,1589,1591,1593,1595,1597,1599,1601,1603,1605,1607],{"class":72,"line":489},[170,1587,1588],{"class":253},"    sales ",[170,1590,323],{"class":249},[170,1592,1238],{"class":253},[170,1594,957],{"class":283},[170,1596,502],{"class":253},[170,1598,1245],{"class":179},[170,1600,502],{"class":253},[170,1602,1250],{"class":344},[170,1604,323],{"class":249},[170,1606,1255],{"class":179},[170,1608,622],{"class":253},[170,1610,1611,1614,1616],{"class":72,"line":511},[170,1612,1613],{"class":253},"    remember(",[170,1615,1245],{"class":179},[170,1617,1618],{"class":253},", meta)\n",[170,1620,1621,1623,1626,1629,1632,1635,1638,1641,1643,1646],{"class":72,"line":557},[170,1622,971],{"class":283},[170,1624,1625],{"class":253},"(meta[",[170,1627,1628],{"class":179},"\"lastModifiedDateTime\"",[170,1630,1631],{"class":253},"], ",[170,1633,1634],{"class":179},"\"changed\"",[170,1636,1637],{"class":249}," if",[170,1639,1640],{"class":253}," is_new ",[170,1642,1443],{"class":249},[170,1644,1645],{"class":179}," \"unchanged\"",[170,1647,622],{"class":253},[10,1649,1650],{},"Record the eTag only after the downstream work has succeeded. If the report build fails halfway, the next run should see the file as\nnew and try again rather than skipping it.",[25,1652,34,1657,34,1660,34,1663,34,1666,34,1669,34,1672,34,1675,34,1679,34,1682,34,1685,34,1690,34,1695,34,1699,34,1703,34,1707,34,1709,34,1712,34,1715,34,1719,34,1722,34,1725,34,1728],{"viewBox":1653,"role":28,"ariaLabelledBy":1654,"xmlns":32,"style":33},"0 0 760 282",[1655,1656],"spr-etag-t","spr-etag-d",[36,1658,1659],{"id":1655},"Deciding whether to download a source workbook",[40,1661,1662],{"id":1656},"Fetch the file metadata and compare its eTag with the stored value. If it matches, skip the download. If it differs or no value is stored, download, process, and store the new eTag only after processing succeeds.",[44,1664],{"x":46,"y":46,"width":47,"height":1665,"fill":49},"282",[44,1667],{"x":1668,"y":52,"width":1065,"height":1008,"rx":55,"fill":86,"stroke":77,"style":58},"200",[60,1670,1671],{"x":108,"y":1008,"style":90},"eTag same as last successful run?",[72,1673],{"x1":108,"y1":68,"x2":108,"y2":1674,"stroke":77,"style":58},"100",[72,1676],{"x1":1677,"y1":1674,"x2":1678,"y2":1674,"stroke":77,"style":58},"195","565",[72,1680],{"x1":1677,"y1":1674,"x2":1677,"y2":1681,"stroke":77,"style":58},"124",[79,1683],{"points":1684,"fill":82},"195,127 190,118 200,118",[44,1686],{"x":52,"y":1687,"width":1688,"height":1689,"rx":55,"fill":56,"stroke":57,"style":58},"128","350","104",[60,1691,1694],{"x":1677,"y":1692,"style":1693},"150","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","yes",[60,1696,1698],{"x":1677,"y":1697,"style":64},"174","skip",[60,1700,1702],{"x":1677,"y":1701,"style":94},"196","no download,",[60,1704,1706],{"x":1677,"y":1705,"style":94},"216","report unchanged",[72,1708],{"x1":1678,"y1":1674,"x2":1678,"y2":1681,"stroke":77,"style":58},[79,1710],{"points":1711,"fill":82},"565,127 560,118 570,118",[44,1713],{"x":1714,"y":1687,"width":1688,"height":1689,"rx":55,"fill":140,"stroke":141,"style":58},"390",[60,1716,1718],{"x":1678,"y":1692,"style":1717},"font-size:11px;font-weight:600;fill:var(--teal-ink,#0b6157);text-anchor:middle","no, or first run",[60,1720,1721],{"x":1678,"y":1697,"style":145},"download + build",[60,1723,1724],{"x":1678,"y":1701,"style":94},"store the eTag",[60,1726,1727],{"x":1678,"y":1705,"style":94},"after success",[60,1729,1731],{"x":108,"y":1730,"style":153},"266","metadata is a few hundred bytes; the workbook may be megabytes",[156,1733,1735],{"id":1734},"step-5-read-every-workbook-in-a-folder","Step 5 — Read every workbook in a folder",[10,1737,1738,1739,1741,1742,1745],{},"Monthly or per-branch files usually share a folder. List its children, keep the ",[14,1740,1084],{}," files, and read each into a frame tagged with\nits source name. Graph pages long listings with ",[14,1743,1744],{},"@odata.nextLink",", so follow it until it runs out.",[161,1747,1749],{"className":240,"code":1748,"language":242,"meta":166,"style":166},"def list_folder(drive: str, folder: str) -> list[dict]:\n    items, url = [], f\"\u002Fdrives\u002F{drive}\u002Froot:\u002F{quote(folder)}:\u002Fchildren\"\n    while url:\n        page = graph_get(url).json()\n        items.extend(page.get(\"value\", []))\n        next_link = page.get(\"@odata.nextLink\")\n        url = next_link.replace(GRAPH, \"\") if next_link else None\n    return items\n\nframes = []\nfor item in list_folder(DRIVE, \"Reports\u002F2026\u002Fbranches\"):\n    if item.get(\"name\", \"\").lower().endswith(\".xlsx\"):\n        frame = read_sharepoint_excel(DRIVE, f\"Reports\u002F2026\u002Fbranches\u002F{item['name']}\")\n        frames.append(frame.assign(source=item[\"name\"]))\ncombined = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()\nprint(len(frames), \"workbooks,\", len(combined), \"rows\")\n",[14,1750,1751,1774,1806,1814,1824,1834,1849,1879,1886,1890,1900,1922,1943,1976,1993,2023],{"__ignoreMap":166},[170,1752,1753,1755,1758,1760,1762,1765,1767,1770,1772],{"class":72,"line":172},[170,1754,301],{"class":249},[170,1756,1757],{"class":304}," list_folder",[170,1759,1146],{"class":253},[170,1761,311],{"class":283},[170,1763,1764],{"class":253},", folder: ",[170,1766,311],{"class":283},[170,1768,1769],{"class":253},") -> list[",[170,1771,1380],{"class":283},[170,1773,1383],{"class":253},[170,1775,1776,1779,1781,1784,1786,1788,1790,1792,1794,1796,1798,1801,1803],{"class":72,"line":257},[170,1777,1778],{"class":253},"    items, url ",[170,1780,323],{"class":249},[170,1782,1783],{"class":253}," [], ",[170,1785,350],{"class":249},[170,1787,1190],{"class":179},[170,1789,357],{"class":356},[170,1791,649],{"class":253},[170,1793,369],{"class":356},[170,1795,1199],{"class":179},[170,1797,357],{"class":356},[170,1799,1800],{"class":253},"quote(folder)",[170,1802,369],{"class":356},[170,1804,1805],{"class":179},":\u002Fchildren\"\n",[170,1807,1808,1811],{"class":72,"line":265},[170,1809,1810],{"class":249},"    while",[170,1812,1813],{"class":253}," url:\n",[170,1815,1816,1819,1821],{"class":72,"line":273},[170,1817,1818],{"class":253},"        page ",[170,1820,323],{"class":249},[170,1822,1823],{"class":253}," graph_get(url).json()\n",[170,1825,1826,1829,1831],{"class":72,"line":280},[170,1827,1828],{"class":253},"        items.extend(page.get(",[170,1830,827],{"class":179},[170,1832,1833],{"class":253},", []))\n",[170,1835,1836,1839,1841,1844,1847],{"class":72,"line":293},[170,1837,1838],{"class":253},"        next_link ",[170,1840,323],{"class":249},[170,1842,1843],{"class":253}," page.get(",[170,1845,1846],{"class":179},"\"@odata.nextLink\"",[170,1848,622],{"class":253},[170,1850,1851,1854,1856,1859,1861,1863,1866,1869,1871,1874,1876],{"class":72,"line":298},[170,1852,1853],{"class":253},"        url ",[170,1855,323],{"class":249},[170,1857,1858],{"class":253}," next_link.replace(",[170,1860,284],{"class":283},[170,1862,502],{"class":253},[170,1864,1865],{"class":179},"\"\"",[170,1867,1868],{"class":253},") ",[170,1870,1434],{"class":249},[170,1872,1873],{"class":253}," next_link ",[170,1875,1443],{"class":249},[170,1877,1878],{"class":283}," None\n",[170,1880,1881,1883],{"class":72,"line":317},[170,1882,427],{"class":249},[170,1884,1885],{"class":253}," items\n",[170,1887,1888],{"class":72,"line":329},[170,1889,277],{"emptyLinePlaceholder":276},[170,1891,1892,1895,1897],{"class":72,"line":341},[170,1893,1894],{"class":253},"frames ",[170,1896,323],{"class":249},[170,1898,1899],{"class":253}," []\n",[170,1901,1902,1904,1907,1909,1912,1914,1916,1919],{"class":72,"line":378},[170,1903,914],{"class":249},[170,1905,1906],{"class":253}," item ",[170,1908,840],{"class":249},[170,1910,1911],{"class":253}," list_folder(",[170,1913,957],{"class":283},[170,1915,502],{"class":253},[170,1917,1918],{"class":179},"\"Reports\u002F2026\u002Fbranches\"",[170,1920,1921],{"class":253},"):\n",[170,1923,1924,1926,1929,1931,1933,1935,1938,1941],{"class":72,"line":393},[170,1925,560],{"class":249},[170,1927,1928],{"class":253}," item.get(",[170,1930,854],{"class":179},[170,1932,502],{"class":253},[170,1934,1865],{"class":179},[170,1936,1937],{"class":253},").lower().endswith(",[170,1939,1940],{"class":179},"\".xlsx\"",[170,1942,1921],{"class":253},[170,1944,1945,1948,1950,1952,1954,1956,1958,1961,1963,1966,1968,1970,1972,1974],{"class":72,"line":399},[170,1946,1947],{"class":253},"        frame ",[170,1949,323],{"class":249},[170,1951,1238],{"class":253},[170,1953,957],{"class":283},[170,1955,502],{"class":253},[170,1957,350],{"class":249},[170,1959,1960],{"class":179},"\"Reports\u002F2026\u002Fbranches\u002F",[170,1962,357],{"class":356},[170,1964,1965],{"class":253},"item[",[170,1967,909],{"class":179},[170,1969,366],{"class":253},[170,1971,369],{"class":356},[170,1973,372],{"class":179},[170,1975,622],{"class":253},[170,1977,1978,1981,1984,1986,1988,1990],{"class":72,"line":424},[170,1979,1980],{"class":253},"        frames.append(frame.assign(",[170,1982,1983],{"class":344},"source",[170,1985,323],{"class":249},[170,1987,1965],{"class":253},[170,1989,854],{"class":179},[170,1991,1992],{"class":253},"]))\n",[170,1994,1995,1998,2000,2003,2006,2008,2011,2013,2015,2018,2020],{"class":72,"line":439},[170,1996,1997],{"class":253},"combined ",[170,1999,323],{"class":249},[170,2001,2002],{"class":253}," pd.concat(frames, ",[170,2004,2005],{"class":344},"ignore_index",[170,2007,323],{"class":249},[170,2009,2010],{"class":283},"True",[170,2012,1868],{"class":253},[170,2014,1434],{"class":249},[170,2016,2017],{"class":253}," frames ",[170,2019,1443],{"class":249},[170,2021,2022],{"class":253}," pd.DataFrame()\n",[170,2024,2025,2027,2029,2032,2035,2038,2040,2042,2045,2048],{"class":72,"line":444},[170,2026,971],{"class":283},[170,2028,583],{"class":253},[170,2030,2031],{"class":283},"len",[170,2033,2034],{"class":253},"(frames), ",[170,2036,2037],{"class":179},"\"workbooks,\"",[170,2039,502],{"class":253},[170,2041,2031],{"class":283},[170,2043,2044],{"class":253},"(combined), ",[170,2046,2047],{"class":179},"\"rows\"",[170,2049,622],{"class":253},[10,2051,2052,2053,219],{},"Branch files are rarely perfectly consistent, so the combining step benefits from the checks in\n",[19,2054,2056],{"href":2055},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcombine-multiple-excel-files-into-one-python\u002F","Combine Multiple Excel Files into One in Python",[156,2058,2060],{"id":2059},"step-6-stream-large-workbooks-to-a-temporary-file","Step 6 — Stream large workbooks to a temporary file",[10,2062,2063,2066,2067,2069],{},[14,2064,2065],{},"response.content"," holds the entire file in memory, and ",[14,2068,1088],{}," then holds a second reference while pandas parses it. For a\ntypical 5 MB reporting workbook that is irrelevant. For a 300 MB extract on a small container it is the difference between a job that\nruns and one that is killed. Streaming the download to a temporary file keeps memory flat, and pandas reads the file from disk as usual.",[161,2071,2073],{"className":240,"code":2072,"language":242,"meta":166,"style":166},"import tempfile\n\ndef read_large_sharepoint_excel(drive: str, path: str, **read_kwargs) -> pd.DataFrame:\n    url = f\"{GRAPH}\u002Fdrives\u002F{drive}\u002Froot:\u002F{quote(path.strip('\u002F'))}:\u002Fcontent\"\n    with tempfile.NamedTemporaryFile(suffix=\".xlsx\") as tmp:\n        with session.get(url, stream=True, timeout=300) as response:\n            response.raise_for_status()\n            for chunk in response.iter_content(chunk_size=1024 * 1024):\n                tmp.write(chunk)\n        tmp.flush()\n        return pd.read_excel(tmp.name, **read_kwargs)\n\nbig = read_large_sharepoint_excel(DRIVE, \"Reports\u002F2026\u002Fsales.xlsx\", sheet_name=\"Sales\")\nprint(big.shape)\n",[14,2074,2075,2082,2086,2107,2151,2173,2203,2208,2237,2242,2247,2258,2262,2288],{"__ignoreMap":166},[170,2076,2077,2079],{"class":72,"line":172},[170,2078,250],{"class":249},[170,2080,2081],{"class":253}," tempfile\n",[170,2083,2084],{"class":72,"line":257},[170,2085,277],{"emptyLinePlaceholder":276},[170,2087,2088,2090,2093,2095,2097,2099,2101,2103,2105],{"class":72,"line":265},[170,2089,301],{"class":249},[170,2091,2092],{"class":304}," read_large_sharepoint_excel",[170,2094,1146],{"class":253},[170,2096,311],{"class":283},[170,2098,1151],{"class":253},[170,2100,311],{"class":283},[170,2102,502],{"class":253},[170,2104,505],{"class":249},[170,2106,1160],{"class":253},[170,2108,2109,2112,2114,2116,2118,2120,2122,2124,2127,2129,2131,2133,2135,2137,2140,2143,2146,2148],{"class":72,"line":273},[170,2110,2111],{"class":253},"    url ",[170,2113,323],{"class":249},[170,2115,469],{"class":249},[170,2117,372],{"class":179},[170,2119,357],{"class":356},[170,2121,284],{"class":283},[170,2123,369],{"class":356},[170,2125,2126],{"class":179},"\u002Fdrives\u002F",[170,2128,357],{"class":356},[170,2130,649],{"class":253},[170,2132,369],{"class":356},[170,2134,1199],{"class":179},[170,2136,357],{"class":356},[170,2138,2139],{"class":253},"quote(path.strip(",[170,2141,2142],{"class":179},"'\u002F'",[170,2144,2145],{"class":253},"))",[170,2147,369],{"class":356},[170,2149,2150],{"class":179},":\u002Fcontent\"\n",[170,2152,2153,2156,2159,2162,2164,2166,2168,2170],{"class":72,"line":280},[170,2154,2155],{"class":249},"    with",[170,2157,2158],{"class":253}," tempfile.NamedTemporaryFile(",[170,2160,2161],{"class":344},"suffix",[170,2163,323],{"class":249},[170,2165,1940],{"class":179},[170,2167,1868],{"class":253},[170,2169,1129],{"class":249},[170,2171,2172],{"class":253}," tmp:\n",[170,2174,2175,2178,2181,2184,2186,2188,2190,2192,2194,2196,2198,2200],{"class":72,"line":293},[170,2176,2177],{"class":249},"        with",[170,2179,2180],{"class":253}," session.get(url, ",[170,2182,2183],{"class":344},"stream",[170,2185,323],{"class":249},[170,2187,2010],{"class":283},[170,2189,502],{"class":253},[170,2191,542],{"class":344},[170,2193,323],{"class":249},[170,2195,613],{"class":283},[170,2197,1868],{"class":253},[170,2199,1129],{"class":249},[170,2201,2202],{"class":253}," response:\n",[170,2204,2205],{"class":72,"line":298},[170,2206,2207],{"class":253},"            response.raise_for_status()\n",[170,2209,2210,2213,2216,2218,2221,2224,2226,2229,2232,2235],{"class":72,"line":317},[170,2211,2212],{"class":249},"            for",[170,2214,2215],{"class":253}," chunk ",[170,2217,840],{"class":249},[170,2219,2220],{"class":253}," response.iter_content(",[170,2222,2223],{"class":344},"chunk_size",[170,2225,323],{"class":249},[170,2227,2228],{"class":283},"1024",[170,2230,2231],{"class":249}," *",[170,2233,2234],{"class":283}," 1024",[170,2236,1921],{"class":253},[170,2238,2239],{"class":72,"line":329},[170,2240,2241],{"class":253},"                tmp.write(chunk)\n",[170,2243,2244],{"class":72,"line":341},[170,2245,2246],{"class":253},"        tmp.flush()\n",[170,2248,2249,2251,2254,2256],{"class":72,"line":378},[170,2250,778],{"class":249},[170,2252,2253],{"class":253}," pd.read_excel(tmp.name, ",[170,2255,505],{"class":249},[170,2257,1224],{"class":253},[170,2259,2260],{"class":72,"line":393},[170,2261,277],{"emptyLinePlaceholder":276},[170,2263,2264,2267,2269,2272,2274,2276,2278,2280,2282,2284,2286],{"class":72,"line":399},[170,2265,2266],{"class":253},"big ",[170,2268,323],{"class":249},[170,2270,2271],{"class":253}," read_large_sharepoint_excel(",[170,2273,957],{"class":283},[170,2275,502],{"class":253},[170,2277,1245],{"class":179},[170,2279,502],{"class":253},[170,2281,1250],{"class":344},[170,2283,323],{"class":249},[170,2285,1255],{"class":179},[170,2287,622],{"class":253},[170,2289,2290,2292],{"class":72,"line":424},[170,2291,971],{"class":283},[170,2293,2294],{"class":253},"(big.shape)\n",[10,2296,2297,2298,2301,2302,2305,2306,2308,2309,2313,2314,2317],{},"The temporary file is deleted when the ",[14,2299,2300],{},"with"," block ends, so nothing is left behind on the worker. If the parse itself is the bottleneck\nrather than the download, combine this with a faster engine — ",[14,2303,2304],{},"engine=\"calamine\""," — or read only the columns you need with ",[14,2307,1288],{},", as\nin ",[19,2310,2312],{"href":2311},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002F","Working with Large Excel Files in Python",". On\nWindows, open the temporary file with ",[14,2315,2316],{},"delete=False"," and remove it yourself, because a file held open by one handle cannot be reopened\nby pandas there.",[156,2319,2321],{"id":2320},"common-pitfalls","Common pitfalls",[2323,2324,2325,2341],"table",{},[2326,2327,2328],"thead",{},[2329,2330,2331,2335,2338],"tr",{},[2332,2333,2334],"th",{},"Symptom",[2332,2336,2337],{},"Cause",[2332,2339,2340],{},"Fix",[2342,2343,2344,2362,2380,2398,2409,2423],"tbody",{},[2329,2345,2346,2353,2359],{},[2347,2348,2349,2352],"td",{},[14,2350,2351],{},"403 accessDenied"," with a valid token",[2347,2354,2355,2356,2358],{},"Admin consent missing, or ",[14,2357,202],{}," without a site grant",[2347,2360,2361],{},"Consent the permission and grant the app the site",[2329,2363,2364,2370,2373],{},[2347,2365,2366,2369],{},[14,2367,2368],{},"404 itemNotFound"," for a file you can see",[2347,2371,2372],{},"Path relative to the wrong library, or unencoded characters",[2347,2374,2375,2376,2379],{},"Check the library name; ",[14,2377,2378],{},"quote()"," the path",[2329,2381,2382,2388,2395],{},[2347,2383,2384,2387],{},[14,2385,2386],{},"BadZipFile"," from pandas",[2347,2389,2390,2391,2394],{},"The response was an error page or a ",[14,2392,2393],{},".xls"," file",[2347,2396,2397],{},"Check the status code; use the right engine for the extension",[2329,2399,2400,2403,2406],{},[2347,2401,2402],{},"File seems out of date",[2347,2404,2405],{},"Reading while someone is editing in Excel for the web",[2347,2407,2408],{},"Graph returns the last saved version; co-authoring saves continuously, but rely on eTags rather than timing",[2329,2410,2411,2414,2417],{},[2347,2412,2413],{},"Paths stop working after a rename",[2347,2415,2416],{},"Path-based addressing follows the name",[2347,2418,2419,2420],{},"Store the item ID and use ",[14,2421,2422],{},"\u002Fdrives\u002F{drive}\u002Fitems\u002F{id}\u002Fcontent",[2329,2424,2425,2428,2431],{},[2347,2426,2427],{},"Only the first 200 files processed",[2347,2429,2430],{},"Listing is paged",[2347,2432,2433,2434],{},"Follow ",[14,2435,1744],{},[156,2437,2439],{"id":2438},"performance-and-scale-notes","Performance and scale notes",[10,2441,2442,2443,2445,2446,1303,2449,2452,2453,2456,2457,2460],{},"The download is a single streamed response, so the cost is the file size and the network. Reading into ",[14,2444,1088],{}," is fine for typical\nreporting workbooks; for files in the hundreds of megabytes, stream to a temporary file with ",[14,2447,2448],{},"stream=True",[14,2450,2451],{},"iter_content"," instead\nof holding the whole response in memory. When a job reads many files, run the downloads sequentially and respect any ",[14,2454,2455],{},"Retry-After","\nheader on a ",[14,2458,2459],{},"429"," response — SharePoint throttles bursts from one app, and a polite client finishes sooner than one that retries\nimmediately.",[156,2462,2464],{"id":2463},"conclusion","Conclusion",[10,2466,2467,2468,2470,2471,2473,2474,2476],{},"Reading a SharePoint or OneDrive workbook is three lookups and a download: resolve the site, resolve the library's drive, and request\nthe file's ",[14,2469,1076],{}," by path, then give the bytes to ",[14,2472,16],{}," through ",[14,2475,1088],{},". An app-only token is enough, because file\ncontent supports application permissions. Cache the site and drive IDs, check the eTag before downloading, follow paging when reading a\nfolder, and record progress only after the report built on the data has succeeded.",[156,2478,2480],{"id":2479},"frequently-asked-questions","Frequently asked questions",[10,2482,2483,2486],{},[639,2484,2485],{},"Can I read a SharePoint Excel file without downloading it?","\nYou can read cell values through the workbook API instead, but it needs a delegated token and a call per range. For loading a whole sheet into pandas, downloading the file is simpler and faster.",[10,2488,2489,2492],{},[639,2490,2491],{},"How do I find the site path from a SharePoint URL?","\nFor a library URL on contoso.sharepoint.com under \u002Fsites\u002FFinance\u002F, the host name is contoso.sharepoint.com and the site path is Finance. Everything after the library name is the file path under the drive root.",[10,2494,2495,2498],{},[639,2496,2497],{},"Does this work for files shared with me from another tenant?","\nNot with an app-only token from your tenant. Cross-tenant sharing is granted to users, so it needs a delegated token for an account that has been given access.",[10,2500,2501,2504],{},[639,2502,2503],{},"Can pandas read the file directly from the download URL?","\nThe redirect target is a pre-authenticated, short-lived URL, so pandas could fetch it, but going through requests keeps error handling, timeouts and retries in one place.",[156,2506,2508],{"id":2507},"related","Related",[194,2510,2511,2517,2522,2529,2536],{},[197,2512,2513,2514,2516],{},"Up one level: ",[19,2515,22],{"href":21}," — reading, editing and recalculating workbooks in Microsoft 365.",[197,2518,2519,2521],{},[19,2520,218],{"href":217}," — the tokens used above.",[197,2523,2524,2528],{},[19,2525,2527],{"href":2526},"\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"," — the write direction for finished reports.",[197,2530,2531,2535],{},[19,2532,2534],{"href":2533},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fread-an-excel-file-directly-from-amazon-s3-with-pandas\u002F","Read an Excel File Directly from Amazon S3 with pandas"," — the same pattern for S3.",[197,2537,2538,2542],{},[19,2539,2541],{"href":2540},"\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 the workbook in place instead of downloading it.",[2544,2545,2546],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .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}",{"title":166,"searchDepth":257,"depth":257,"links":2548},[2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560],{"id":158,"depth":257,"text":159},{"id":225,"depth":257,"text":226},{"id":633,"depth":257,"text":634},{"id":1069,"depth":257,"text":1070},{"id":1295,"depth":257,"text":1296},{"id":1734,"depth":257,"text":1735},{"id":2059,"depth":257,"text":2060},{"id":2320,"depth":257,"text":2321},{"id":2438,"depth":257,"text":2439},{"id":2463,"depth":257,"text":2464},{"id":2479,"depth":257,"text":2480},{"id":2507,"depth":257,"text":2508},"2026-09-17","Download a workbook from a SharePoint library or a user's OneDrive with Microsoft Graph and read it straight into pandas from memory — resolving sites and drives, skipping unchanged files, and handling whole folders.","md",[2565,2567,2569,2571],{"q":2485,"a":2566},"You can read cell values through the workbook API instead, but it needs a delegated token and a call per range. For loading a whole sheet into pandas, downloading the file is simpler and faster.",{"q":2491,"a":2568},"For a library URL on contoso.sharepoint.com under \u002Fsites\u002FFinance\u002F, the host name is contoso.sharepoint.com and the site path is Finance. Everything after the library name is the file path under the drive root.",{"q":2497,"a":2570},"Not with an app-only token from your tenant. Cross-tenant sharing is granted to users, so it needs a delegated token for an account that has been given access.",{"q":2503,"a":2572},"The redirect target is a pre-authenticated, short-lived URL, so pandas could fetch it, but going through requests keeps error handling, timeouts and retries in one place.","read an excel file from sharepoint or onedrive into pandas",null,{"breadcrumb":2576},[2577,2580,2583],{"name":2578,"item":2579},"Home","\u002F",{"name":2581,"item":2582},"Automating Reporting Workflows","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas",{"title":5,"description":2586},"Use Microsoft Graph and requests to load SharePoint or OneDrive Excel files into pandas: resolve the site and drive, download content into BytesIO, check eTags, and read every workbook in a folder.","read-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",[2590,2591,2592,2593],"excel-online","publishing","reading-excel","pandas","how-to","rr0kjS5hGbSSdBYd3pJUc9o0gxtDF5Nuf7pT7QZ_YeE",[2597,2601],{"title":2598,"path":2599,"stem":2600,"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",{"title":2541,"path":2602,"stem":2603,"children":-1},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-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",[2605,2607],{"path":2606,"title":22},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph",{"path":2608,"title":2609},"\u002Fautomating-reporting-workflows","Automating Reporting Workflows with Python and Excel",[],[2612,2615,2618,2621,2624,2626],{"path":2613,"title":2614,"kind":2594},"\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":2616,"title":2617,"kind":2594},"\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":2619,"title":2620,"kind":2594},"\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":2622,"title":2623,"kind":2594},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fappend-rows-to-an-excel-online-table-with-microsoft-graph","Append Rows to an Excel Online Table with Microsoft Graph",{"path":2625,"title":218,"kind":2594},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fauthenticate-to-microsoft-graph-with-msal-for-excel-automation",{"path":2627,"title":2628,"kind":2594},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",1790972119031]