[{"data":1,"prerenderedAt":2878},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fsave-excel-reports-to-google-drive-with-python":3,"surround:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fsave-excel-reports-to-google-drive-with-python":2871},{"id":4,"title":5,"body":6,"dateModified":2847,"datePublished":2847,"description":2848,"extension":2849,"faq":2850,"meta":2862,"navigation":262,"path":2863,"seo":2864,"slug":2867,"stem":2868,"type":2869,"__hash__":2870},"docs\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fsave-excel-reports-to-google-drive-with-python\u002Findex.md","Save Excel Reports to Google Drive with Python",{"type":7,"value":8,"toc":2834},"minimark",[9,19,136,141,176,183,206,209,243,311,314,318,321,495,506,510,513,906,929,933,940,1028,1031,1458,1475,1478,1482,1485,1656,1667,1671,1734,1737,1879,1891,1895,2031,2035,2041,2281,2301,2508,2523,2712,2716,2734,2738,2749,2765,2777,2783,2792,2796,2830],[10,11,12,13,18],"p",{},"Google Drive is the natural destination for a recurring report in a Google Workspace organisation: readers already live there, sharing is familiar, and revision history comes free. From Python it is the Drive API v3 with a service account — and three details decide whether it works unattended. The service account must actually be able to see the folder, uploads must update rather than duplicate, and shared drives need an extra parameter on every call. This guide covers all of it. It is the Google Workspace path from ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002F","Publishing Excel Reports to Cloud Storage",".",[20,21,30,31,30,35,30,39,30,46,30,55,30,62,30,66,30,71,30,75,30,80,30,93,30,103,30,112,30,118,30,122,30,127,30,132],"svg",{"viewBox":22,"role":23,"ariaLabel":24,"ariaLabelledBy":25,"xmlns":28,"style":29},"0 0 800 250","img","Access model: a service account has its own identity and no storage, so the target folder must be shared with its client_email address or live on a shared drive the account is a member of.",[26,27],"svcacct-t","svcacct-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[32,33,34],"title",{"id":26},"Why a service account cannot see your Drive by default",[36,37,38],"desc",{"id":27},"A service account is shown as a separate identity with its own client_email address and no storage quota of its own. It cannot see a personal My Drive at all. Two routes give it access: sharing a specific folder with its client_email, which works but leaves ownership with a person, or adding it as a member of a shared drive, which is the recommended arrangement because the shared drive supplies the storage.",[40,41],"rect",{"x":42,"y":42,"width":43,"height":44,"fill":45},"0","800","250","#ffffff",[40,47],{"x":48,"y":49,"width":50,"height":51,"rx":48,"fill":52,"stroke":53,"style":54},"14","76","196","106","#ebebfd","var(--brand,#5b5cf0)","stroke-width:2px",[56,57,61],"text",{"x":58,"y":59,"style":60},"112","60","font-size:11px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","the job runs as",[56,63,65],{"x":58,"y":51,"style":64},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","service account",[56,67,70],{"x":58,"y":68,"style":69},"130","font-size:10.5px;fill:var(--text,#172033);text-anchor:middle","reports@project",[56,72,74],{"x":58,"y":73,"style":69},"148",".iam.gserviceaccount.com",[56,76,79],{"x":58,"y":77,"style":78},"170","font-size:10.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","no storage of its own",[81,82,85,86,85,90,30],"g",{"stroke":83,"style":54,"fill":84},"var(--line,#cdd5e6)","none","\n    ",[87,88],"path",{"d":89},"M210 106 H 248 V 62 H 284",[87,91],{"d":92},"M210 152 H 248 V 190 H 284",[81,94,85,96,85,100,30],{"fill":95},"#5b5cf0",[97,98],"polygon",{"points":99},"292,62 280,56 280,68",[97,101],{"points":102},"292,190 280,184 280,196",[40,104],{"x":105,"y":106,"width":107,"height":108,"rx":109,"fill":110,"stroke":111,"style":54},"300","30","484","66","13","#fdefd8","var(--gold,#b4740a)",[56,113,117],{"x":114,"y":115,"style":116},"324","54","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06)","share one folder with its client_email",[56,119,121],{"x":114,"y":49,"style":120},"font-size:10.5px;fill:var(--muted,#5b6780)","works · but a person still owns the files, and their quota is used",[40,123],{"x":105,"y":124,"width":107,"height":108,"rx":109,"fill":125,"stroke":126,"style":54},"158","#d9f4f1","var(--teal,#0f9488)",[56,128,131],{"x":114,"y":129,"style":130},"182","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157)","add it to a shared drive",[56,133,135],{"x":114,"y":134,"style":120},"204","recommended · the drive supplies the storage and survives people leaving",[137,138,140],"h2",{"id":139},"prerequisites","Prerequisites",[142,143,148],"pre",{"className":144,"code":145,"language":146,"meta":147,"style":147},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install google-api-python-client google-auth pandas xlsxwriter\n","bash","",[149,150,151],"code",{"__ignoreMap":147},[152,153,156,160,164,167,170,173],"span",{"class":154,"line":155},"line",1,[152,157,159],{"class":158},"sMTad","pip",[152,161,163],{"class":162},"srMev"," install",[152,165,166],{"class":162}," google-api-python-client",[152,168,169],{"class":162}," google-auth",[152,171,172],{"class":162}," pandas",[152,174,175],{"class":162}," xlsxwriter\n",[10,177,178,179,182],{},"A ",[180,181,65],"strong",{}," in a Google Cloud project with the Drive API enabled, and its JSON key downloaded. Then the step people skip: give it somewhere to write.",[184,185,186,197],"ul",{},[187,188,189,192,193,196],"li",{},[180,190,191],{},"Preferred:"," create a shared drive for reports and add the service account's ",[149,194,195],{},"client_email"," as a Content manager. The shared drive owns the files and supplies the storage.",[187,198,199,202,203,205],{},[180,200,201],{},"Workable:"," share an ordinary folder with the ",[149,204,195],{}," address, giving Editor access. Files remain owned by a person and consume their quota.",[10,207,208],{},"The address to share with is inside the key file, and it is not your own email:",[142,210,212],{"className":144,"code":211,"language":146,"meta":147,"style":147},"export GOOGLE_APPLICATION_CREDENTIALS=\u002Fsecrets\u002Freporting-sa.json\nexport DRIVE_FOLDER_ID=1AbCdEfGhIjKlMnOpQrStUvWxYz\n",[149,213,214,230],{"__ignoreMap":147},[152,215,216,220,224,227],{"class":154,"line":155},[152,217,219],{"class":218},"s-kum","export",[152,221,223],{"class":222},"skGVy"," GOOGLE_APPLICATION_CREDENTIALS",[152,225,226],{"class":218},"=",[152,228,229],{"class":222},"\u002Fsecrets\u002Freporting-sa.json\n",[152,231,233,235,238,240],{"class":154,"line":232},2,[152,234,219],{"class":218},[152,236,237],{"class":222}," DRIVE_FOLDER_ID",[152,239,226],{"class":218},[152,241,242],{"class":222},"1AbCdEfGhIjKlMnOpQrStUvWxYz\n",[142,244,248],{"className":245,"code":246,"language":247,"meta":147,"style":147},"language-python shiki shiki-themes github-light github-dark-high-contrast","import json, os\n\nwith open(os.environ[\"GOOGLE_APPLICATION_CREDENTIALS\"]) as fh:\n    print(json.load(fh)[\"client_email\"])\n# reports@my-project.iam.gserviceaccount.com  \u003C- share the folder with THIS\n","python",[149,249,250,258,264,289,304],{"__ignoreMap":147},[152,251,252,255],{"class":154,"line":155},[152,253,254],{"class":218},"import",[152,256,257],{"class":222}," json, os\n",[152,259,260],{"class":154,"line":232},[152,261,263],{"emptyLinePlaceholder":262},true,"\n",[152,265,267,270,274,277,280,283,286],{"class":154,"line":266},3,[152,268,269],{"class":218},"with",[152,271,273],{"class":272},"sP0c6"," open",[152,275,276],{"class":222},"(os.environ[",[152,278,279],{"class":162},"\"GOOGLE_APPLICATION_CREDENTIALS\"",[152,281,282],{"class":222},"]) ",[152,284,285],{"class":218},"as",[152,287,288],{"class":222}," fh:\n",[152,290,292,295,298,301],{"class":154,"line":291},4,[152,293,294],{"class":272},"    print",[152,296,297],{"class":222},"(json.load(fh)[",[152,299,300],{"class":162},"\"client_email\"",[152,302,303],{"class":222},"])\n",[152,305,307],{"class":154,"line":306},5,[152,308,310],{"class":309},"s-wDw","# reports@my-project.iam.gserviceaccount.com  \u003C- share the folder with THIS\n",[10,312,313],{},"The folder ID is the trailing segment of the folder's URL in the browser.",[137,315,317],{"id":316},"step-1-authenticate","Step 1 — Authenticate",[10,319,320],{},"Service-account credentials need no interactive flow, which is exactly what a scheduled job wants:",[142,322,324],{"className":245,"code":323,"language":247,"meta":147,"style":147},"import os\nfrom google.oauth2 import service_account\nfrom googleapiclient.discovery import build\n\nSCOPES = [\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive.file\"]\n\ndef drive_client():\n    creds = service_account.Credentials.from_service_account_file(\n        os.environ[\"GOOGLE_APPLICATION_CREDENTIALS\"], scopes=SCOPES\n    )\n    # cache_discovery=False avoids a noisy warning and a stale on-disk cache.\n    return build(\"drive\", \"v3\", credentials=creds, cache_discovery=False)\n\nservice = drive_client()\n",[149,325,326,333,346,358,362,379,384,397,408,428,434,440,479,484],{"__ignoreMap":147},[152,327,328,330],{"class":154,"line":155},[152,329,254],{"class":218},[152,331,332],{"class":222}," os\n",[152,334,335,338,341,343],{"class":154,"line":232},[152,336,337],{"class":218},"from",[152,339,340],{"class":222}," google.oauth2 ",[152,342,254],{"class":218},[152,344,345],{"class":222}," service_account\n",[152,347,348,350,353,355],{"class":154,"line":266},[152,349,337],{"class":218},[152,351,352],{"class":222}," googleapiclient.discovery ",[152,354,254],{"class":218},[152,356,357],{"class":222}," build\n",[152,359,360],{"class":154,"line":291},[152,361,263],{"emptyLinePlaceholder":262},[152,363,364,367,370,373,376],{"class":154,"line":306},[152,365,366],{"class":272},"SCOPES",[152,368,369],{"class":218}," =",[152,371,372],{"class":222}," [",[152,374,375],{"class":162},"\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fdrive.file\"",[152,377,378],{"class":222},"]\n",[152,380,382],{"class":154,"line":381},6,[152,383,263],{"emptyLinePlaceholder":262},[152,385,387,390,394],{"class":154,"line":386},7,[152,388,389],{"class":218},"def",[152,391,393],{"class":392},"s_Opv"," drive_client",[152,395,396],{"class":222},"():\n",[152,398,400,403,405],{"class":154,"line":399},8,[152,401,402],{"class":222},"    creds ",[152,404,226],{"class":218},[152,406,407],{"class":222}," service_account.Credentials.from_service_account_file(\n",[152,409,411,414,416,419,423,425],{"class":154,"line":410},9,[152,412,413],{"class":222},"        os.environ[",[152,415,279],{"class":162},[152,417,418],{"class":222},"], ",[152,420,422],{"class":421},"sa561","scopes",[152,424,226],{"class":218},[152,426,427],{"class":272},"SCOPES\n",[152,429,431],{"class":154,"line":430},10,[152,432,433],{"class":222},"    )\n",[152,435,437],{"class":154,"line":436},11,[152,438,439],{"class":309},"    # cache_discovery=False avoids a noisy warning and a stale on-disk cache.\n",[152,441,443,446,449,452,455,458,460,463,465,468,471,473,476],{"class":154,"line":442},12,[152,444,445],{"class":218},"    return",[152,447,448],{"class":222}," build(",[152,450,451],{"class":162},"\"drive\"",[152,453,454],{"class":222},", ",[152,456,457],{"class":162},"\"v3\"",[152,459,454],{"class":222},[152,461,462],{"class":421},"credentials",[152,464,226],{"class":218},[152,466,467],{"class":222},"creds, ",[152,469,470],{"class":421},"cache_discovery",[152,472,226],{"class":218},[152,474,475],{"class":272},"False",[152,477,478],{"class":222},")\n",[152,480,482],{"class":154,"line":481},13,[152,483,263],{"emptyLinePlaceholder":262},[152,485,487,490,492],{"class":154,"line":486},14,[152,488,489],{"class":222},"service ",[152,491,226],{"class":218},[152,493,494],{"class":222}," drive_client()\n",[10,496,497,498,501,502,505],{},"Pick the narrowest scope that works. ",[149,499,500],{},"drive.file"," grants access only to files the application itself created or that were explicitly shared with it — which covers a reporting job completely. The broader ",[149,503,504],{},"drive"," scope reaches everything the account can see and is rarely justified.",[137,507,509],{"id":508},"step-2-upload-a-report-into-a-folder","Step 2 — Upload a report into a folder",[10,511,512],{},"Drive uploads take metadata plus a media body. Building the workbook in memory keeps the job filesystem-free:",[142,514,516],{"className":245,"code":515,"language":247,"meta":147,"style":147},"import io\nimport pandas as pd\nfrom googleapiclient.http import MediaIoBaseUpload\n\nXLSX = \"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n\ndef build_workbook(df, sheet_name=\"Summary\"):\n    buffer = io.BytesIO()\n    with pd.ExcelWriter(buffer, engine=\"xlsxwriter\") as writer:\n        df.to_excel(writer, sheet_name=sheet_name, index=False)\n    buffer.seek(0)\n    return buffer\n\ndef upload(service, buffer, name, folder_id):\n    \"\"\"Create a new file in the given folder.\"\"\"\n    metadata = {\"name\": name, \"parents\": [folder_id]}\n    media = MediaIoBaseUpload(buffer, mimetype=XLSX, resumable=True)\n\n    created = (\n        service.files()\n        .create(body=metadata, media_body=media,\n                fields=\"id, name, webViewLink\",\n                supportsAllDrives=True)\n        .execute()\n    )\n    return created\n\ndf = pd.DataFrame({\"region\": [\"North\", \"South\"], \"revenue\": [159.92, 247.50]})\nitem = upload(service, build_workbook(df),\n              \"regional-2026-08.xlsx\", os.environ[\"DRIVE_FOLDER_ID\"])\nprint(item[\"webViewLink\"])\n",[149,517,518,525,537,549,553,563,567,585,595,619,641,650,657,661,671,677,700,730,735,746,752,774,788,800,806,811,819,824,867,878,892],{"__ignoreMap":147},[152,519,520,522],{"class":154,"line":155},[152,521,254],{"class":218},[152,523,524],{"class":222}," io\n",[152,526,527,529,532,534],{"class":154,"line":232},[152,528,254],{"class":218},[152,530,531],{"class":222}," pandas ",[152,533,285],{"class":218},[152,535,536],{"class":222}," pd\n",[152,538,539,541,544,546],{"class":154,"line":266},[152,540,337],{"class":218},[152,542,543],{"class":222}," googleapiclient.http ",[152,545,254],{"class":218},[152,547,548],{"class":222}," MediaIoBaseUpload\n",[152,550,551],{"class":154,"line":291},[152,552,263],{"emptyLinePlaceholder":262},[152,554,555,558,560],{"class":154,"line":306},[152,556,557],{"class":272},"XLSX",[152,559,369],{"class":218},[152,561,562],{"class":162}," \"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n",[152,564,565],{"class":154,"line":381},[152,566,263],{"emptyLinePlaceholder":262},[152,568,569,571,574,577,579,582],{"class":154,"line":386},[152,570,389],{"class":218},[152,572,573],{"class":392}," build_workbook",[152,575,576],{"class":222},"(df, sheet_name",[152,578,226],{"class":218},[152,580,581],{"class":162},"\"Summary\"",[152,583,584],{"class":222},"):\n",[152,586,587,590,592],{"class":154,"line":399},[152,588,589],{"class":222},"    buffer ",[152,591,226],{"class":218},[152,593,594],{"class":222}," io.BytesIO()\n",[152,596,597,600,603,606,608,611,614,616],{"class":154,"line":410},[152,598,599],{"class":218},"    with",[152,601,602],{"class":222}," pd.ExcelWriter(buffer, ",[152,604,605],{"class":421},"engine",[152,607,226],{"class":218},[152,609,610],{"class":162},"\"xlsxwriter\"",[152,612,613],{"class":222},") ",[152,615,285],{"class":218},[152,617,618],{"class":222}," writer:\n",[152,620,621,624,627,629,632,635,637,639],{"class":154,"line":430},[152,622,623],{"class":222},"        df.to_excel(writer, ",[152,625,626],{"class":421},"sheet_name",[152,628,226],{"class":218},[152,630,631],{"class":222},"sheet_name, ",[152,633,634],{"class":421},"index",[152,636,226],{"class":218},[152,638,475],{"class":272},[152,640,478],{"class":222},[152,642,643,646,648],{"class":154,"line":436},[152,644,645],{"class":222},"    buffer.seek(",[152,647,42],{"class":272},[152,649,478],{"class":222},[152,651,652,654],{"class":154,"line":442},[152,653,445],{"class":218},[152,655,656],{"class":222}," buffer\n",[152,658,659],{"class":154,"line":481},[152,660,263],{"emptyLinePlaceholder":262},[152,662,663,665,668],{"class":154,"line":486},[152,664,389],{"class":218},[152,666,667],{"class":392}," upload",[152,669,670],{"class":222},"(service, buffer, name, folder_id):\n",[152,672,674],{"class":154,"line":673},15,[152,675,676],{"class":162},"    \"\"\"Create a new file in the given folder.\"\"\"\n",[152,678,680,683,685,688,691,694,697],{"class":154,"line":679},16,[152,681,682],{"class":222},"    metadata ",[152,684,226],{"class":218},[152,686,687],{"class":222}," {",[152,689,690],{"class":162},"\"name\"",[152,692,693],{"class":222},": name, ",[152,695,696],{"class":162},"\"parents\"",[152,698,699],{"class":222},": [folder_id]}\n",[152,701,703,706,708,711,714,716,718,720,723,725,728],{"class":154,"line":702},17,[152,704,705],{"class":222},"    media ",[152,707,226],{"class":218},[152,709,710],{"class":222}," MediaIoBaseUpload(buffer, ",[152,712,713],{"class":421},"mimetype",[152,715,226],{"class":218},[152,717,557],{"class":272},[152,719,454],{"class":222},[152,721,722],{"class":421},"resumable",[152,724,226],{"class":218},[152,726,727],{"class":272},"True",[152,729,478],{"class":222},[152,731,733],{"class":154,"line":732},18,[152,734,263],{"emptyLinePlaceholder":262},[152,736,738,741,743],{"class":154,"line":737},19,[152,739,740],{"class":222},"    created ",[152,742,226],{"class":218},[152,744,745],{"class":222}," (\n",[152,747,749],{"class":154,"line":748},20,[152,750,751],{"class":222},"        service.files()\n",[152,753,755,758,761,763,766,769,771],{"class":154,"line":754},21,[152,756,757],{"class":222},"        .create(",[152,759,760],{"class":421},"body",[152,762,226],{"class":218},[152,764,765],{"class":222},"metadata, ",[152,767,768],{"class":421},"media_body",[152,770,226],{"class":218},[152,772,773],{"class":222},"media,\n",[152,775,777,780,782,785],{"class":154,"line":776},22,[152,778,779],{"class":421},"                fields",[152,781,226],{"class":218},[152,783,784],{"class":162},"\"id, name, webViewLink\"",[152,786,787],{"class":222},",\n",[152,789,791,794,796,798],{"class":154,"line":790},23,[152,792,793],{"class":421},"                supportsAllDrives",[152,795,226],{"class":218},[152,797,727],{"class":272},[152,799,478],{"class":222},[152,801,803],{"class":154,"line":802},24,[152,804,805],{"class":222},"        .execute()\n",[152,807,809],{"class":154,"line":808},25,[152,810,433],{"class":222},[152,812,814,816],{"class":154,"line":813},26,[152,815,445],{"class":218},[152,817,818],{"class":222}," created\n",[152,820,822],{"class":154,"line":821},27,[152,823,263],{"emptyLinePlaceholder":262},[152,825,827,830,832,835,838,841,844,846,849,851,854,856,859,861,864],{"class":154,"line":826},28,[152,828,829],{"class":222},"df ",[152,831,226],{"class":218},[152,833,834],{"class":222}," pd.DataFrame({",[152,836,837],{"class":162},"\"region\"",[152,839,840],{"class":222},": [",[152,842,843],{"class":162},"\"North\"",[152,845,454],{"class":222},[152,847,848],{"class":162},"\"South\"",[152,850,418],{"class":222},[152,852,853],{"class":162},"\"revenue\"",[152,855,840],{"class":222},[152,857,858],{"class":272},"159.92",[152,860,454],{"class":222},[152,862,863],{"class":272},"247.50",[152,865,866],{"class":222},"]})\n",[152,868,870,873,875],{"class":154,"line":869},29,[152,871,872],{"class":222},"item ",[152,874,226],{"class":218},[152,876,877],{"class":222}," upload(service, build_workbook(df),\n",[152,879,881,884,887,890],{"class":154,"line":880},30,[152,882,883],{"class":162},"              \"regional-2026-08.xlsx\"",[152,885,886],{"class":222},", os.environ[",[152,888,889],{"class":162},"\"DRIVE_FOLDER_ID\"",[152,891,303],{"class":222},[152,893,895,898,901,904],{"class":154,"line":894},31,[152,896,897],{"class":272},"print",[152,899,900],{"class":222},"(item[",[152,902,903],{"class":162},"\"webViewLink\"",[152,905,303],{"class":222},[10,907,908,909,912,913,916,917,920,921,924,925,928],{},"Two arguments carry more weight than they look. ",[149,910,911],{},"supportsAllDrives=True"," must be on ",[180,914,915],{},"every"," call that touches a shared drive — omit it and the API behaves as though the drive does not exist, returning a confusing ",[149,918,919],{},"404"," for a folder you can see in the browser. And ",[149,922,923],{},"fields="," limits the response to what you need; without it the API returns a small default set that often lacks ",[149,926,927],{},"webViewLink",", so people conclude the link is unavailable when it simply was not requested.",[137,930,932],{"id":931},"step-3-update-instead-of-duplicating","Step 3 — Update instead of duplicating",[10,934,935,936,939],{},"Drive permits several files with the same name in one folder. A monthly job that always calls ",[149,937,938],{},"create"," therefore builds up a pile of identically named reports, and readers cannot tell which link is current.",[20,941,30,947,30,950,30,953,30,956,30,962,30,968,30,974,30,978,30,981,30,985,30,988,30,992,30,996,30,999,30,1004,30,1008,30,1013,30,1017,30,1022,30,1025],{"viewBox":942,"role":23,"ariaLabel":943,"ariaLabelledBy":944,"xmlns":28,"style":29},"0 0 800 240","Create versus update: repeated create calls leave three separate files with the same name, while searching then updating keeps one file ID with three revisions.",[945,946],"dupe-t","dupe-d",[32,948,949],{"id":945},"Why a monthly create call produces duplicates",[36,951,952],{"id":946},"Two outcomes after three monthly runs. On the left, calling create each time leaves three distinct files all named regional-latest.xlsx, each with its own ID and its own link, so circulated links point at different months. On the right, searching for the existing file and calling update keeps a single file ID with three revisions in its history, so every circulated link resolves to the current report.",[40,954],{"x":42,"y":42,"width":43,"height":955,"fill":45},"240",[40,957],{"x":48,"y":958,"width":959,"height":50,"rx":48,"fill":960,"stroke":961,"style":54},"24","368","#fee8f2","var(--accent,#f43f8f)",[56,963,967],{"x":964,"y":965,"style":966},"198","52","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","files().create() every month",[40,969],{"x":970,"y":971,"width":972,"height":106,"rx":973,"fill":45,"stroke":83},"40","68","316","6",[56,975,977],{"x":964,"y":976,"style":69},"88","regional-latest.xlsx · id 1Ab… · June",[40,979],{"x":970,"y":980,"width":972,"height":106,"rx":973,"fill":45,"stroke":83},"104",[56,982,984],{"x":964,"y":983,"style":69},"124","regional-latest.xlsx · id 2Cd… · July",[40,986],{"x":970,"y":987,"width":972,"height":106,"rx":973,"fill":45,"stroke":83},"140",[56,989,991],{"x":964,"y":990,"style":69},"160","regional-latest.xlsx · id 3Ef… · August",[56,993,995],{"x":964,"y":964,"style":994},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","three files, three links, no way to tell which",[40,997],{"x":998,"y":958,"width":959,"height":50,"rx":48,"fill":125,"stroke":126,"style":54},"418",[56,1000,1003],{"x":1001,"y":965,"style":1002},"602","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","search, then files().update()",[40,1005],{"x":1006,"y":971,"width":972,"height":1007,"rx":973,"fill":45,"stroke":126,"style":54},"444","38",[56,1009,1012],{"x":1001,"y":1010,"style":1011},"92","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","regional-latest.xlsx · id 1Ab…",[56,1014,1016],{"x":1001,"y":1015,"style":69},"128","revision 3 · August · current",[56,1018,1021],{"x":1001,"y":1019,"style":1020},"150","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","revision 2 · July",[56,1023,1024],{"x":1001,"y":77,"style":1020},"revision 1 · June",[56,1026,1027],{"x":1001,"y":964,"style":1011},"one link, always current, history intact",[10,1029,1030],{},"Search first, then branch:",[142,1032,1034],{"className":245,"code":1033,"language":247,"meta":147,"style":147},"from googleapiclient.http import MediaIoBaseUpload\n\ndef find_file(service, name, folder_id):\n    \"\"\"Return the ID of a non-trashed file with this name in the folder.\"\"\"\n    safe = name.replace(\"'\", r\"\\'\")\n    query = (\n        f\"name = '{safe}' and '{folder_id}' in parents and trashed = false\"\n    )\n    result = (\n        service.files()\n        .list(q=query, fields=\"files(id, name)\", pageSize=2,\n              supportsAllDrives=True, includeItemsFromAllDrives=True)\n        .execute()\n    )\n    files = result.get(\"files\", [])\n    return files[0][\"id\"] if files else None\n\ndef publish(service, buffer, name, folder_id):\n    \"\"\"Create the file, or add a revision if it already exists.\"\"\"\n    media = MediaIoBaseUpload(buffer, mimetype=XLSX, resumable=True)\n    existing = find_file(service, name, folder_id)\n\n    if existing:\n        return (\n            service.files()\n            .update(fileId=existing, media_body=media,\n                    fields=\"id, name, webViewLink\", supportsAllDrives=True)\n            .execute()\n        )\n\n    return (\n        service.files()\n        .create(body={\"name\": name, \"parents\": [folder_id]},\n                media_body=media, fields=\"id, name, webViewLink\",\n                supportsAllDrives=True)\n        .execute()\n    )\n",[149,1035,1036,1046,1050,1060,1065,1094,1103,1134,1138,1147,1151,1184,1204,1208,1212,1228,1258,1262,1271,1276,1300,1310,1314,1322,1329,1334,1353,1373,1378,1383,1387,1393,1398,1418,1437,1448,1453],{"__ignoreMap":147},[152,1037,1038,1040,1042,1044],{"class":154,"line":155},[152,1039,337],{"class":218},[152,1041,543],{"class":222},[152,1043,254],{"class":218},[152,1045,548],{"class":222},[152,1047,1048],{"class":154,"line":232},[152,1049,263],{"emptyLinePlaceholder":262},[152,1051,1052,1054,1057],{"class":154,"line":266},[152,1053,389],{"class":218},[152,1055,1056],{"class":392}," find_file",[152,1058,1059],{"class":222},"(service, name, folder_id):\n",[152,1061,1062],{"class":154,"line":291},[152,1063,1064],{"class":162},"    \"\"\"Return the ID of a non-trashed file with this name in the folder.\"\"\"\n",[152,1066,1067,1070,1072,1075,1078,1080,1083,1086,1090,1092],{"class":154,"line":306},[152,1068,1069],{"class":222},"    safe ",[152,1071,226],{"class":218},[152,1073,1074],{"class":222}," name.replace(",[152,1076,1077],{"class":162},"\"'\"",[152,1079,454],{"class":222},[152,1081,1082],{"class":218},"r",[152,1084,1085],{"class":162},"\"",[152,1087,1089],{"class":1088},"s_b0D","\\'",[152,1091,1085],{"class":162},[152,1093,478],{"class":222},[152,1095,1096,1099,1101],{"class":154,"line":381},[152,1097,1098],{"class":222},"    query ",[152,1100,226],{"class":218},[152,1102,745],{"class":222},[152,1104,1105,1108,1111,1115,1118,1121,1124,1126,1129,1131],{"class":154,"line":386},[152,1106,1107],{"class":218},"        f",[152,1109,1110],{"class":162},"\"name = '",[152,1112,1114],{"class":1113},"sSjpA","{",[152,1116,1117],{"class":222},"safe",[152,1119,1120],{"class":1113},"}",[152,1122,1123],{"class":162},"' and '",[152,1125,1114],{"class":1113},[152,1127,1128],{"class":222},"folder_id",[152,1130,1120],{"class":1113},[152,1132,1133],{"class":162},"' in parents and trashed = false\"\n",[152,1135,1136],{"class":154,"line":399},[152,1137,433],{"class":222},[152,1139,1140,1143,1145],{"class":154,"line":410},[152,1141,1142],{"class":222},"    result ",[152,1144,226],{"class":218},[152,1146,745],{"class":222},[152,1148,1149],{"class":154,"line":430},[152,1150,751],{"class":222},[152,1152,1153,1156,1159,1161,1164,1167,1169,1172,1174,1177,1179,1182],{"class":154,"line":436},[152,1154,1155],{"class":222},"        .list(",[152,1157,1158],{"class":421},"q",[152,1160,226],{"class":218},[152,1162,1163],{"class":222},"query, ",[152,1165,1166],{"class":421},"fields",[152,1168,226],{"class":218},[152,1170,1171],{"class":162},"\"files(id, name)\"",[152,1173,454],{"class":222},[152,1175,1176],{"class":421},"pageSize",[152,1178,226],{"class":218},[152,1180,1181],{"class":272},"2",[152,1183,787],{"class":222},[152,1185,1186,1189,1191,1193,1195,1198,1200,1202],{"class":154,"line":442},[152,1187,1188],{"class":421},"              supportsAllDrives",[152,1190,226],{"class":218},[152,1192,727],{"class":272},[152,1194,454],{"class":222},[152,1196,1197],{"class":421},"includeItemsFromAllDrives",[152,1199,226],{"class":218},[152,1201,727],{"class":272},[152,1203,478],{"class":222},[152,1205,1206],{"class":154,"line":481},[152,1207,805],{"class":222},[152,1209,1210],{"class":154,"line":486},[152,1211,433],{"class":222},[152,1213,1214,1217,1219,1222,1225],{"class":154,"line":673},[152,1215,1216],{"class":222},"    files ",[152,1218,226],{"class":218},[152,1220,1221],{"class":222}," result.get(",[152,1223,1224],{"class":162},"\"files\"",[152,1226,1227],{"class":222},", [])\n",[152,1229,1230,1232,1235,1237,1240,1243,1246,1249,1252,1255],{"class":154,"line":679},[152,1231,445],{"class":218},[152,1233,1234],{"class":222}," files[",[152,1236,42],{"class":272},[152,1238,1239],{"class":222},"][",[152,1241,1242],{"class":162},"\"id\"",[152,1244,1245],{"class":222},"] ",[152,1247,1248],{"class":218},"if",[152,1250,1251],{"class":222}," files ",[152,1253,1254],{"class":218},"else",[152,1256,1257],{"class":272}," None\n",[152,1259,1260],{"class":154,"line":702},[152,1261,263],{"emptyLinePlaceholder":262},[152,1263,1264,1266,1269],{"class":154,"line":732},[152,1265,389],{"class":218},[152,1267,1268],{"class":392}," publish",[152,1270,670],{"class":222},[152,1272,1273],{"class":154,"line":737},[152,1274,1275],{"class":162},"    \"\"\"Create the file, or add a revision if it already exists.\"\"\"\n",[152,1277,1278,1280,1282,1284,1286,1288,1290,1292,1294,1296,1298],{"class":154,"line":748},[152,1279,705],{"class":222},[152,1281,226],{"class":218},[152,1283,710],{"class":222},[152,1285,713],{"class":421},[152,1287,226],{"class":218},[152,1289,557],{"class":272},[152,1291,454],{"class":222},[152,1293,722],{"class":421},[152,1295,226],{"class":218},[152,1297,727],{"class":272},[152,1299,478],{"class":222},[152,1301,1302,1305,1307],{"class":154,"line":754},[152,1303,1304],{"class":222},"    existing ",[152,1306,226],{"class":218},[152,1308,1309],{"class":222}," find_file(service, name, folder_id)\n",[152,1311,1312],{"class":154,"line":776},[152,1313,263],{"emptyLinePlaceholder":262},[152,1315,1316,1319],{"class":154,"line":790},[152,1317,1318],{"class":218},"    if",[152,1320,1321],{"class":222}," existing:\n",[152,1323,1324,1327],{"class":154,"line":802},[152,1325,1326],{"class":218},"        return",[152,1328,745],{"class":222},[152,1330,1331],{"class":154,"line":808},[152,1332,1333],{"class":222},"            service.files()\n",[152,1335,1336,1339,1342,1344,1347,1349,1351],{"class":154,"line":813},[152,1337,1338],{"class":222},"            .update(",[152,1340,1341],{"class":421},"fileId",[152,1343,226],{"class":218},[152,1345,1346],{"class":222},"existing, ",[152,1348,768],{"class":421},[152,1350,226],{"class":218},[152,1352,773],{"class":222},[152,1354,1355,1358,1360,1362,1364,1367,1369,1371],{"class":154,"line":821},[152,1356,1357],{"class":421},"                    fields",[152,1359,226],{"class":218},[152,1361,784],{"class":162},[152,1363,454],{"class":222},[152,1365,1366],{"class":421},"supportsAllDrives",[152,1368,226],{"class":218},[152,1370,727],{"class":272},[152,1372,478],{"class":222},[152,1374,1375],{"class":154,"line":826},[152,1376,1377],{"class":222},"            .execute()\n",[152,1379,1380],{"class":154,"line":869},[152,1381,1382],{"class":222},"        )\n",[152,1384,1385],{"class":154,"line":880},[152,1386,263],{"emptyLinePlaceholder":262},[152,1388,1389,1391],{"class":154,"line":894},[152,1390,445],{"class":218},[152,1392,745],{"class":222},[152,1394,1396],{"class":154,"line":1395},32,[152,1397,751],{"class":222},[152,1399,1401,1403,1405,1407,1409,1411,1413,1415],{"class":154,"line":1400},33,[152,1402,757],{"class":222},[152,1404,760],{"class":421},[152,1406,226],{"class":218},[152,1408,1114],{"class":222},[152,1410,690],{"class":162},[152,1412,693],{"class":222},[152,1414,696],{"class":162},[152,1416,1417],{"class":222},": [folder_id]},\n",[152,1419,1421,1424,1426,1429,1431,1433,1435],{"class":154,"line":1420},34,[152,1422,1423],{"class":421},"                media_body",[152,1425,226],{"class":218},[152,1427,1428],{"class":222},"media, ",[152,1430,1166],{"class":421},[152,1432,226],{"class":218},[152,1434,784],{"class":162},[152,1436,787],{"class":222},[152,1438,1440,1442,1444,1446],{"class":154,"line":1439},35,[152,1441,793],{"class":421},[152,1443,226],{"class":218},[152,1445,727],{"class":272},[152,1447,478],{"class":222},[152,1449,1451],{"class":154,"line":1450},36,[152,1452,805],{"class":222},[152,1454,1456],{"class":154,"line":1455},37,[152,1457,433],{"class":222},[10,1459,1460,1463,1464,1466,1467,1469,1470,1474],{},[149,1461,1462],{},"files().update"," adds a revision rather than replacing history, so the ID and every circulated link stay valid while the content moves forward. Note that ",[149,1465,1197],{}," is needed alongside ",[149,1468,1366],{}," on the ",[1471,1472,1473],"em",{},"list"," call specifically — the two flags do different things and both are required for search on a shared drive.",[10,1476,1477],{},"Escaping the apostrophe in the query matters: Drive's query language is string-delimited, and a report name containing one otherwise produces a syntax error rather than an empty result.",[137,1479,1481],{"id":1480},"step-4-publish-dated-and-latest-together","Step 4 — Publish dated and latest together",[10,1483,1484],{},"Combine the two naming needs from the parent topic in one call:",[142,1486,1488],{"className":245,"code":1487,"language":247,"meta":147,"style":147},"from datetime import date\n\ndef publish_report(service, df, base, folder_id, on=None):\n    \"\"\"Write a dated file for history and update a stable latest file for links.\"\"\"\n    on = on or date.today()\n\n    dated_name = f\"{base}-{on:%Y-%m}.xlsx\"\n    latest_name = f\"{base}-latest.xlsx\"\n\n    dated = publish(service, build_workbook(df), dated_name, folder_id)\n    latest = publish(service, build_workbook(df), latest_name, folder_id)\n\n    return dated[\"webViewLink\"], latest[\"webViewLink\"]\n",[149,1489,1490,1502,1506,1523,1528,1544,1548,1592,1612,1616,1626,1636,1640],{"__ignoreMap":147},[152,1491,1492,1494,1497,1499],{"class":154,"line":155},[152,1493,337],{"class":218},[152,1495,1496],{"class":222}," datetime ",[152,1498,254],{"class":218},[152,1500,1501],{"class":222}," date\n",[152,1503,1504],{"class":154,"line":232},[152,1505,263],{"emptyLinePlaceholder":262},[152,1507,1508,1510,1513,1516,1518,1521],{"class":154,"line":266},[152,1509,389],{"class":218},[152,1511,1512],{"class":392}," publish_report",[152,1514,1515],{"class":222},"(service, df, base, folder_id, on",[152,1517,226],{"class":218},[152,1519,1520],{"class":272},"None",[152,1522,584],{"class":222},[152,1524,1525],{"class":154,"line":291},[152,1526,1527],{"class":162},"    \"\"\"Write a dated file for history and update a stable latest file for links.\"\"\"\n",[152,1529,1530,1533,1535,1538,1541],{"class":154,"line":306},[152,1531,1532],{"class":222},"    on ",[152,1534,226],{"class":218},[152,1536,1537],{"class":222}," on ",[152,1539,1540],{"class":218},"or",[152,1542,1543],{"class":222}," date.today()\n",[152,1545,1546],{"class":154,"line":381},[152,1547,263],{"emptyLinePlaceholder":262},[152,1549,1550,1553,1555,1558,1560,1562,1565,1567,1570,1572,1575,1578,1581,1584,1587,1589],{"class":154,"line":386},[152,1551,1552],{"class":222},"    dated_name ",[152,1554,226],{"class":218},[152,1556,1557],{"class":218}," f",[152,1559,1085],{"class":162},[152,1561,1114],{"class":1113},[152,1563,1564],{"class":222},"base",[152,1566,1120],{"class":1113},[152,1568,1569],{"class":162},"-",[152,1571,1114],{"class":1113},[152,1573,1574],{"class":222},"on:",[152,1576,1577],{"class":218},"%",[152,1579,1580],{"class":222},"Y",[152,1582,1583],{"class":218},"-%",[152,1585,1586],{"class":222},"m",[152,1588,1120],{"class":1113},[152,1590,1591],{"class":162},".xlsx\"\n",[152,1593,1594,1597,1599,1601,1603,1605,1607,1609],{"class":154,"line":399},[152,1595,1596],{"class":222},"    latest_name ",[152,1598,226],{"class":218},[152,1600,1557],{"class":218},[152,1602,1085],{"class":162},[152,1604,1114],{"class":1113},[152,1606,1564],{"class":222},[152,1608,1120],{"class":1113},[152,1610,1611],{"class":162},"-latest.xlsx\"\n",[152,1613,1614],{"class":154,"line":410},[152,1615,263],{"emptyLinePlaceholder":262},[152,1617,1618,1621,1623],{"class":154,"line":430},[152,1619,1620],{"class":222},"    dated ",[152,1622,226],{"class":218},[152,1624,1625],{"class":222}," publish(service, build_workbook(df), dated_name, folder_id)\n",[152,1627,1628,1631,1633],{"class":154,"line":436},[152,1629,1630],{"class":222},"    latest ",[152,1632,226],{"class":218},[152,1634,1635],{"class":222}," publish(service, build_workbook(df), latest_name, folder_id)\n",[152,1637,1638],{"class":154,"line":442},[152,1639,263],{"emptyLinePlaceholder":262},[152,1641,1642,1644,1647,1649,1652,1654],{"class":154,"line":481},[152,1643,445],{"class":218},[152,1645,1646],{"class":222}," dated[",[152,1648,903],{"class":162},[152,1650,1651],{"class":222},"], latest[",[152,1653,903],{"class":162},[152,1655,378],{"class":222},[10,1657,1658,1659,1662,1663,1666],{},"Build the workbook twice rather than reusing one buffer — a ",[149,1660,1661],{},"MediaIoBaseUpload"," consumes the stream, so passing the same buffer to a second upload sends zero bytes. Rewinding with ",[149,1664,1665],{},"seek(0)"," also works; building twice is simply harder to get wrong.",[137,1668,1670],{"id":1669},"step-5-convert-to-a-google-sheet-when-that-is-what-people-want","Step 5 — Convert to a Google Sheet, when that is what people want",[20,1672,30,1678,30,1681,30,1684,30,1687,30,1691,30,1695,30,1700,30,1703,30,1707,30,1710,30,1714,30,1716,30,1719,30,1722,30,1725,30,1728,30,1731],{"viewBox":1673,"role":23,"ariaLabel":1674,"ariaLabelledBy":1675,"xmlns":28,"style":29},"0 0 800 226","What survives conversion to a Google Sheet: values, formulas and basic formatting carry over, while conditional formatting rules, some chart types and macros do not.",[1676,1677],"conv-t","conv-d",[32,1679,1680],{"id":1676},"What conversion to a native Google Sheet keeps and drops",[36,1682,1683],{"id":1677},"Two columns listing outcomes of converting an xlsx to a Google Sheet on upload. Kept: cell values, most formulas, fonts and fills, number formats, and multiple sheets. Dropped or degraded: some conditional formatting rules, certain Excel chart types, VBA macros, and pixel-exact column widths. The choice therefore turns on whether people will collaborate in the browser or need the report to look exactly as generated.",[40,1685],{"x":42,"y":42,"width":43,"height":1686,"fill":45},"226",[40,1688],{"x":48,"y":1689,"width":959,"height":1690,"rx":48,"fill":125,"stroke":126,"style":54},"20","192",[56,1692,1694],{"x":964,"y":1693,"style":1002},"48","survives conversion",[56,1696,1699],{"x":964,"y":1697,"style":1698},"80","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","cell values and multiple sheets",[56,1701,1702],{"x":964,"y":51,"style":1698},"most formulas",[56,1704,1706],{"x":964,"y":1705,"style":1698},"132","fonts, fills and borders",[56,1708,1709],{"x":964,"y":124,"style":1698},"number and date formats",[56,1711,1713],{"x":964,"y":1712,"style":1011},"190","good enough to collaborate on",[40,1715],{"x":998,"y":1689,"width":959,"height":1690,"rx":48,"fill":960,"stroke":961,"style":54},[56,1717,1718],{"x":1001,"y":1693,"style":966},"dropped or degraded",[56,1720,1721],{"x":1001,"y":1697,"style":1698},"some conditional formatting rules",[56,1723,1724],{"x":1001,"y":51,"style":1698},"certain Excel chart types",[56,1726,1727],{"x":1001,"y":1705,"style":1698},"VBA macros, entirely",[56,1729,1730],{"x":1001,"y":124,"style":1698},"pixel-exact widths and layout",[56,1732,1733],{"x":1001,"y":1712,"style":994},"upload unconverted if the look matters",[10,1735,1736],{},"If readers will collaborate in the browser rather than download, ask Drive to convert on upload by naming the target MIME type:",[142,1738,1740],{"className":245,"code":1739,"language":247,"meta":147,"style":147},"SHEET = \"application\u002Fvnd.google-apps.spreadsheet\"\n\ndef upload_as_sheet(service, buffer, name, folder_id):\n    \"\"\"Upload an .xlsx and have Drive convert it to a native Google Sheet.\"\"\"\n    media = MediaIoBaseUpload(buffer, mimetype=XLSX, resumable=True)\n    return (\n        service.files()\n        .create(\n            body={\"name\": name, \"parents\": [folder_id], \"mimeType\": SHEET},\n            media_body=media, fields=\"id, webViewLink\",\n            supportsAllDrives=True,\n        )\n        .execute()\n    )\n",[149,1741,1742,1752,1756,1765,1770,1794,1800,1804,1809,1838,1856,1867,1871,1875],{"__ignoreMap":147},[152,1743,1744,1747,1749],{"class":154,"line":155},[152,1745,1746],{"class":272},"SHEET",[152,1748,369],{"class":218},[152,1750,1751],{"class":162}," \"application\u002Fvnd.google-apps.spreadsheet\"\n",[152,1753,1754],{"class":154,"line":232},[152,1755,263],{"emptyLinePlaceholder":262},[152,1757,1758,1760,1763],{"class":154,"line":266},[152,1759,389],{"class":218},[152,1761,1762],{"class":392}," upload_as_sheet",[152,1764,670],{"class":222},[152,1766,1767],{"class":154,"line":291},[152,1768,1769],{"class":162},"    \"\"\"Upload an .xlsx and have Drive convert it to a native Google Sheet.\"\"\"\n",[152,1771,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792],{"class":154,"line":306},[152,1773,705],{"class":222},[152,1775,226],{"class":218},[152,1777,710],{"class":222},[152,1779,713],{"class":421},[152,1781,226],{"class":218},[152,1783,557],{"class":272},[152,1785,454],{"class":222},[152,1787,722],{"class":421},[152,1789,226],{"class":218},[152,1791,727],{"class":272},[152,1793,478],{"class":222},[152,1795,1796,1798],{"class":154,"line":381},[152,1797,445],{"class":218},[152,1799,745],{"class":222},[152,1801,1802],{"class":154,"line":386},[152,1803,751],{"class":222},[152,1805,1806],{"class":154,"line":399},[152,1807,1808],{"class":222},"        .create(\n",[152,1810,1811,1814,1816,1818,1820,1822,1824,1827,1830,1833,1835],{"class":154,"line":410},[152,1812,1813],{"class":421},"            body",[152,1815,226],{"class":218},[152,1817,1114],{"class":222},[152,1819,690],{"class":162},[152,1821,693],{"class":222},[152,1823,696],{"class":162},[152,1825,1826],{"class":222},": [folder_id], ",[152,1828,1829],{"class":162},"\"mimeType\"",[152,1831,1832],{"class":222},": ",[152,1834,1746],{"class":272},[152,1836,1837],{"class":222},"},\n",[152,1839,1840,1843,1845,1847,1849,1851,1854],{"class":154,"line":430},[152,1841,1842],{"class":421},"            media_body",[152,1844,226],{"class":218},[152,1846,1428],{"class":222},[152,1848,1166],{"class":421},[152,1850,226],{"class":218},[152,1852,1853],{"class":162},"\"id, webViewLink\"",[152,1855,787],{"class":222},[152,1857,1858,1861,1863,1865],{"class":154,"line":436},[152,1859,1860],{"class":421},"            supportsAllDrives",[152,1862,226],{"class":218},[152,1864,727],{"class":272},[152,1866,787],{"class":222},[152,1868,1869],{"class":154,"line":442},[152,1870,1382],{"class":222},[152,1872,1873],{"class":154,"line":481},[152,1874,805],{"class":222},[152,1876,1877],{"class":154,"line":486},[152,1878,433],{"class":222},[10,1880,1881,1882,1886,1887,1890],{},"The trade-off is real: conversion keeps values, formulas and basic formatting, but drops Excel-specific features — some conditional formatting rules, certain chart types, and any macros. Where the report's appearance is the deliverable, as with the styled output from ",[14,1883,1885],{"href":1884},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","writing a formatted Excel report with xlsxwriter",", upload the ",[149,1888,1889],{},".xlsx"," unconverted.",[137,1892,1894],{"id":1893},"common-pitfalls-and-fixes","Common pitfalls and fixes",[1896,1897,1898,1914],"table",{},[1899,1900,1901],"thead",{},[1902,1903,1904,1908,1911],"tr",{},[1905,1906,1907],"th",{},"Symptom",[1905,1909,1910],{},"Cause",[1905,1912,1913],{},"Fix",[1915,1916,1917,1933,1949,1962,1979,1994,2007,2020],"tbody",{},[1902,1918,1919,1925,1930],{},[1920,1921,1922,1924],"td",{},[149,1923,919],{}," on a folder you can see",[1920,1926,1927,1928],{},"Shared drive without ",[149,1929,1366],{},[1920,1931,1932],{},"Pass it on every call.",[1902,1934,1935,1938,1943],{},[1920,1936,1937],{},"Search returns nothing on a shared drive",[1920,1939,1940,1942],{},[149,1941,1197],{}," missing",[1920,1944,1945,1946,1948],{},"Add it to the ",[149,1947,1473],{}," call.",[1902,1950,1951,1956,1959],{},[1920,1952,1953],{},[149,1954,1955],{},"storageQuotaExceeded",[1920,1957,1958],{},"Service account has no storage",[1920,1960,1961],{},"Use a shared drive, or a user-owned folder.",[1902,1963,1964,1967,1972],{},[1920,1965,1966],{},"Duplicate files each month",[1920,1968,1969,1971],{},[149,1970,938],{}," called unconditionally",[1920,1973,1974,1975,1978],{},"Search by name and ",[149,1976,1977],{},"update"," when found.",[1902,1980,1981,1986,1989],{},[1920,1982,1983,1985],{},[149,1984,927],{}," missing from the response",[1920,1987,1988],{},"Not requested",[1920,1990,1991,1992,18],{},"Add it to ",[149,1993,923],{},[1902,1995,1996,1999,2002],{},[1920,1997,1998],{},"Second upload writes 0 bytes",[1920,2000,2001],{},"Buffer already consumed",[1920,2003,2004,2005,18],{},"Build a fresh buffer, or ",[149,2006,1665],{},[1902,2008,2009,2012,2015],{},[1920,2010,2011],{},"Service account cannot see the folder",[1920,2013,2014],{},"Shared with the wrong address",[1920,2016,2017,2018,18],{},"Share with the key's ",[149,2019,195],{},[1902,2021,2022,2025,2028],{},[1920,2023,2024],{},"Query syntax error on some names",[1920,2026,2027],{},"Apostrophe in the file name",[1920,2029,2030],{},"Escape it before interpolating.",[137,2032,2034],{"id":2033},"performance-and-scale-notes","Performance and scale notes",[10,2036,2037,2040],{},[149,2038,2039],{},"resumable=True"," switches the client to a session-based upload that survives a dropped connection. For small files it adds a round trip; above a few megabytes it is clearly worth it, and it lets you report progress:",[142,2042,2044],{"className":245,"code":2043,"language":247,"meta":147,"style":147},"from googleapiclient.http import MediaIoBaseUpload\n\ndef upload_with_progress(service, buffer, name, folder_id, chunk_mb=5):\n    media = MediaIoBaseUpload(\n        buffer, mimetype=XLSX, resumable=True,\n        chunksize=chunk_mb * 1024 * 1024,\n    )\n    request = service.files().create(\n        body={\"name\": name, \"parents\": [folder_id]},\n        media_body=media, fields=\"id, webViewLink\", supportsAllDrives=True,\n    )\n\n    response = None\n    while response is None:\n        status, response = request.next_chunk()\n        if status:\n            print(f\"{int(status.progress() * 100)}%\")\n    return response\n",[149,2045,2046,2056,2060,2077,2086,2107,2130,2134,2144,2161,2186,2190,2194,2203,2220,2230,2238,2274],{"__ignoreMap":147},[152,2047,2048,2050,2052,2054],{"class":154,"line":155},[152,2049,337],{"class":218},[152,2051,543],{"class":222},[152,2053,254],{"class":218},[152,2055,548],{"class":222},[152,2057,2058],{"class":154,"line":232},[152,2059,263],{"emptyLinePlaceholder":262},[152,2061,2062,2064,2067,2070,2072,2075],{"class":154,"line":266},[152,2063,389],{"class":218},[152,2065,2066],{"class":392}," upload_with_progress",[152,2068,2069],{"class":222},"(service, buffer, name, folder_id, chunk_mb",[152,2071,226],{"class":218},[152,2073,2074],{"class":272},"5",[152,2076,584],{"class":222},[152,2078,2079,2081,2083],{"class":154,"line":291},[152,2080,705],{"class":222},[152,2082,226],{"class":218},[152,2084,2085],{"class":222}," MediaIoBaseUpload(\n",[152,2087,2088,2091,2093,2095,2097,2099,2101,2103,2105],{"class":154,"line":306},[152,2089,2090],{"class":222},"        buffer, ",[152,2092,713],{"class":421},[152,2094,226],{"class":218},[152,2096,557],{"class":272},[152,2098,454],{"class":222},[152,2100,722],{"class":421},[152,2102,226],{"class":218},[152,2104,727],{"class":272},[152,2106,787],{"class":222},[152,2108,2109,2112,2114,2117,2120,2123,2126,2128],{"class":154,"line":381},[152,2110,2111],{"class":421},"        chunksize",[152,2113,226],{"class":218},[152,2115,2116],{"class":222},"chunk_mb ",[152,2118,2119],{"class":218},"*",[152,2121,2122],{"class":272}," 1024",[152,2124,2125],{"class":218}," *",[152,2127,2122],{"class":272},[152,2129,787],{"class":222},[152,2131,2132],{"class":154,"line":386},[152,2133,433],{"class":222},[152,2135,2136,2139,2141],{"class":154,"line":399},[152,2137,2138],{"class":222},"    request ",[152,2140,226],{"class":218},[152,2142,2143],{"class":222}," service.files().create(\n",[152,2145,2146,2149,2151,2153,2155,2157,2159],{"class":154,"line":410},[152,2147,2148],{"class":421},"        body",[152,2150,226],{"class":218},[152,2152,1114],{"class":222},[152,2154,690],{"class":162},[152,2156,693],{"class":222},[152,2158,696],{"class":162},[152,2160,1417],{"class":222},[152,2162,2163,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184],{"class":154,"line":430},[152,2164,2165],{"class":421},"        media_body",[152,2167,226],{"class":218},[152,2169,1428],{"class":222},[152,2171,1166],{"class":421},[152,2173,226],{"class":218},[152,2175,1853],{"class":162},[152,2177,454],{"class":222},[152,2179,1366],{"class":421},[152,2181,226],{"class":218},[152,2183,727],{"class":272},[152,2185,787],{"class":222},[152,2187,2188],{"class":154,"line":436},[152,2189,433],{"class":222},[152,2191,2192],{"class":154,"line":442},[152,2193,263],{"emptyLinePlaceholder":262},[152,2195,2196,2199,2201],{"class":154,"line":481},[152,2197,2198],{"class":222},"    response ",[152,2200,226],{"class":218},[152,2202,1257],{"class":272},[152,2204,2205,2208,2211,2214,2217],{"class":154,"line":486},[152,2206,2207],{"class":218},"    while",[152,2209,2210],{"class":222}," response ",[152,2212,2213],{"class":218},"is",[152,2215,2216],{"class":272}," None",[152,2218,2219],{"class":222},":\n",[152,2221,2222,2225,2227],{"class":154,"line":673},[152,2223,2224],{"class":222},"        status, response ",[152,2226,226],{"class":218},[152,2228,2229],{"class":222}," request.next_chunk()\n",[152,2231,2232,2235],{"class":154,"line":679},[152,2233,2234],{"class":218},"        if",[152,2236,2237],{"class":222}," status:\n",[152,2239,2240,2243,2246,2249,2251,2253,2256,2259,2261,2264,2267,2269,2272],{"class":154,"line":702},[152,2241,2242],{"class":272},"            print",[152,2244,2245],{"class":222},"(",[152,2247,2248],{"class":218},"f",[152,2250,1085],{"class":162},[152,2252,1114],{"class":1113},[152,2254,2255],{"class":272},"int",[152,2257,2258],{"class":222},"(status.progress() ",[152,2260,2119],{"class":218},[152,2262,2263],{"class":272}," 100",[152,2265,2266],{"class":222},")",[152,2268,1120],{"class":1113},[152,2270,2271],{"class":162},"%\"",[152,2273,478],{"class":222},[152,2275,2276,2278],{"class":154,"line":732},[152,2277,445],{"class":218},[152,2279,2280],{"class":222}," response\n",[10,2282,2283,2284,2287,2288,2291,2292,2295,2296,2300],{},"Drive throttles per project, and the client raises ",[149,2285,2286],{},"HttpError"," with status ",[149,2289,2290],{},"403"," and a rate-limit reason or ",[149,2293,2294],{},"429",". Back off exponentially with jitter — the same discipline as every other API in this section, and covered in ",[14,2297,2299],{"href":2298},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fretry-a-failed-excel-report-job-in-python\u002F","retrying a failed Excel report job",":",[142,2302,2304],{"className":245,"code":2303,"language":247,"meta":147,"style":147},"import random, time\nfrom googleapiclient.errors import HttpError\n\ndef with_retry(call, attempts=5):\n    for attempt in range(1, attempts + 1):\n        try:\n            return call()\n        except HttpError as exc:\n            transient = exc.resp.status in (403, 429, 500, 502, 503, 504)\n            if not transient or attempt == attempts:\n                raise\n            time.sleep(min(2 ** attempt, 30) + random.uniform(0, 1))\n",[149,2305,2306,2313,2325,2329,2345,2375,2382,2390,2403,2446,2467,2472],{"__ignoreMap":147},[152,2307,2308,2310],{"class":154,"line":155},[152,2309,254],{"class":218},[152,2311,2312],{"class":222}," random, time\n",[152,2314,2315,2317,2320,2322],{"class":154,"line":232},[152,2316,337],{"class":218},[152,2318,2319],{"class":222}," googleapiclient.errors ",[152,2321,254],{"class":218},[152,2323,2324],{"class":222}," HttpError\n",[152,2326,2327],{"class":154,"line":266},[152,2328,263],{"emptyLinePlaceholder":262},[152,2330,2331,2333,2336,2339,2341,2343],{"class":154,"line":291},[152,2332,389],{"class":218},[152,2334,2335],{"class":392}," with_retry",[152,2337,2338],{"class":222},"(call, attempts",[152,2340,226],{"class":218},[152,2342,2074],{"class":272},[152,2344,584],{"class":222},[152,2346,2347,2350,2353,2356,2359,2361,2364,2367,2370,2373],{"class":154,"line":306},[152,2348,2349],{"class":218},"    for",[152,2351,2352],{"class":222}," attempt ",[152,2354,2355],{"class":218},"in",[152,2357,2358],{"class":272}," range",[152,2360,2245],{"class":222},[152,2362,2363],{"class":272},"1",[152,2365,2366],{"class":222},", attempts ",[152,2368,2369],{"class":218},"+",[152,2371,2372],{"class":272}," 1",[152,2374,584],{"class":222},[152,2376,2377,2380],{"class":154,"line":381},[152,2378,2379],{"class":218},"        try",[152,2381,2219],{"class":222},[152,2383,2384,2387],{"class":154,"line":386},[152,2385,2386],{"class":218},"            return",[152,2388,2389],{"class":222}," call()\n",[152,2391,2392,2395,2398,2400],{"class":154,"line":399},[152,2393,2394],{"class":218},"        except",[152,2396,2397],{"class":222}," HttpError ",[152,2399,285],{"class":218},[152,2401,2402],{"class":222}," exc:\n",[152,2404,2405,2408,2410,2413,2415,2418,2420,2422,2424,2426,2429,2431,2434,2436,2439,2441,2444],{"class":154,"line":410},[152,2406,2407],{"class":222},"            transient ",[152,2409,226],{"class":218},[152,2411,2412],{"class":222}," exc.resp.status ",[152,2414,2355],{"class":218},[152,2416,2417],{"class":222}," (",[152,2419,2290],{"class":272},[152,2421,454],{"class":222},[152,2423,2294],{"class":272},[152,2425,454],{"class":222},[152,2427,2428],{"class":272},"500",[152,2430,454],{"class":222},[152,2432,2433],{"class":272},"502",[152,2435,454],{"class":222},[152,2437,2438],{"class":272},"503",[152,2440,454],{"class":222},[152,2442,2443],{"class":272},"504",[152,2445,478],{"class":222},[152,2447,2448,2451,2454,2457,2459,2461,2464],{"class":154,"line":430},[152,2449,2450],{"class":218},"            if",[152,2452,2453],{"class":218}," not",[152,2455,2456],{"class":222}," transient ",[152,2458,1540],{"class":218},[152,2460,2352],{"class":222},[152,2462,2463],{"class":218},"==",[152,2465,2466],{"class":222}," attempts:\n",[152,2468,2469],{"class":154,"line":436},[152,2470,2471],{"class":218},"                raise\n",[152,2473,2474,2477,2480,2482,2484,2487,2490,2492,2494,2496,2499,2501,2503,2505],{"class":154,"line":442},[152,2475,2476],{"class":222},"            time.sleep(",[152,2478,2479],{"class":272},"min",[152,2481,2245],{"class":222},[152,2483,1181],{"class":272},[152,2485,2486],{"class":218}," **",[152,2488,2489],{"class":222}," attempt, ",[152,2491,106],{"class":272},[152,2493,613],{"class":222},[152,2495,2369],{"class":218},[152,2497,2498],{"class":222}," random.uniform(",[152,2500,42],{"class":272},[152,2502,454],{"class":222},[152,2504,2363],{"class":272},[152,2506,2507],{"class":222},"))\n",[10,2509,2510,2511,2514,2515,2518,2519,2522],{},"Two habits that matter across a fan-out. ",[180,2512,2513],{},"Reuse the service object"," — building it resolves credentials and fetches the API discovery document, which is far more expensive than the upload for a small report. And ",[180,2516,2517],{},"do the existence search once per name, not per attempt",": for a job publishing forty regional files monthly, cache the folder listing in one ",[149,2520,2521],{},"files().list"," call and look names up in a dict, rather than issuing forty separate searches.",[142,2524,2526],{"className":245,"code":2525,"language":247,"meta":147,"style":147},"def index_folder(service, folder_id):\n    \"\"\"One listing, then O(1) name lookups instead of a search per file.\"\"\"\n    index, token = {}, None\n    while True:\n        result = service.files().list(\n            q=f\"'{folder_id}' in parents and trashed = false\",\n            fields=\"nextPageToken, files(id, name)\", pageSize=1000,\n            pageToken=token, supportsAllDrives=True,\n            includeItemsFromAllDrives=True,\n        ).execute()\n        index.update({f[\"name\"]: f[\"id\"] for f in result.get(\"files\", [])})\n        token = result.get(\"nextPageToken\")\n        if not token:\n            return index\n",[149,2527,2528,2538,2543,2556,2565,2575,2598,2619,2637,2648,2653,2682,2696,2705],{"__ignoreMap":147},[152,2529,2530,2532,2535],{"class":154,"line":155},[152,2531,389],{"class":218},[152,2533,2534],{"class":392}," index_folder",[152,2536,2537],{"class":222},"(service, folder_id):\n",[152,2539,2540],{"class":154,"line":232},[152,2541,2542],{"class":162},"    \"\"\"One listing, then O(1) name lookups instead of a search per file.\"\"\"\n",[152,2544,2545,2548,2550,2553],{"class":154,"line":266},[152,2546,2547],{"class":222},"    index, token ",[152,2549,226],{"class":218},[152,2551,2552],{"class":222}," {}, ",[152,2554,2555],{"class":272},"None\n",[152,2557,2558,2560,2563],{"class":154,"line":291},[152,2559,2207],{"class":218},[152,2561,2562],{"class":272}," True",[152,2564,2219],{"class":222},[152,2566,2567,2570,2572],{"class":154,"line":306},[152,2568,2569],{"class":222},"        result ",[152,2571,226],{"class":218},[152,2573,2574],{"class":222}," service.files().list(\n",[152,2576,2577,2580,2582,2584,2587,2589,2591,2593,2596],{"class":154,"line":381},[152,2578,2579],{"class":421},"            q",[152,2581,226],{"class":218},[152,2583,2248],{"class":218},[152,2585,2586],{"class":162},"\"'",[152,2588,1114],{"class":1113},[152,2590,1128],{"class":222},[152,2592,1120],{"class":1113},[152,2594,2595],{"class":162},"' in parents and trashed = false\"",[152,2597,787],{"class":222},[152,2599,2600,2603,2605,2608,2610,2612,2614,2617],{"class":154,"line":386},[152,2601,2602],{"class":421},"            fields",[152,2604,226],{"class":218},[152,2606,2607],{"class":162},"\"nextPageToken, files(id, name)\"",[152,2609,454],{"class":222},[152,2611,1176],{"class":421},[152,2613,226],{"class":218},[152,2615,2616],{"class":272},"1000",[152,2618,787],{"class":222},[152,2620,2621,2624,2626,2629,2631,2633,2635],{"class":154,"line":399},[152,2622,2623],{"class":421},"            pageToken",[152,2625,226],{"class":218},[152,2627,2628],{"class":222},"token, ",[152,2630,1366],{"class":421},[152,2632,226],{"class":218},[152,2634,727],{"class":272},[152,2636,787],{"class":222},[152,2638,2639,2642,2644,2646],{"class":154,"line":410},[152,2640,2641],{"class":421},"            includeItemsFromAllDrives",[152,2643,226],{"class":218},[152,2645,727],{"class":272},[152,2647,787],{"class":222},[152,2649,2650],{"class":154,"line":430},[152,2651,2652],{"class":222},"        ).execute()\n",[152,2654,2655,2658,2660,2663,2665,2667,2670,2673,2675,2677,2679],{"class":154,"line":436},[152,2656,2657],{"class":222},"        index.update({f[",[152,2659,690],{"class":162},[152,2661,2662],{"class":222},"]: f[",[152,2664,1242],{"class":162},[152,2666,1245],{"class":222},[152,2668,2669],{"class":218},"for",[152,2671,2672],{"class":222}," f ",[152,2674,2355],{"class":218},[152,2676,1221],{"class":222},[152,2678,1224],{"class":162},[152,2680,2681],{"class":222},", [])})\n",[152,2683,2684,2687,2689,2691,2694],{"class":154,"line":442},[152,2685,2686],{"class":222},"        token ",[152,2688,226],{"class":218},[152,2690,1221],{"class":222},[152,2692,2693],{"class":162},"\"nextPageToken\"",[152,2695,478],{"class":222},[152,2697,2698,2700,2702],{"class":154,"line":481},[152,2699,2234],{"class":218},[152,2701,2453],{"class":218},[152,2703,2704],{"class":222}," token:\n",[152,2706,2707,2709],{"class":154,"line":486},[152,2708,2386],{"class":218},[152,2710,2711],{"class":222}," index\n",[137,2713,2715],{"id":2714},"conclusion","Conclusion",[10,2717,2718,2719,2721,2722,2724,2725,2727,2728,2730,2731,2733],{},"Publishing an Excel report to Google Drive is a service account, a folder it can genuinely see, and a create-or-update decision. Share the target with the key's ",[149,2720,195],{}," — ideally a shared drive, so the storage quota problem never arises — and pass ",[149,2723,1366],{}," on every call, plus ",[149,2726,1197],{}," when searching. Search by name and call ",[149,2729,1462],{}," so each month adds a revision instead of a duplicate, keeping one stable link. Use ",[149,2732,2039],{}," for anything sizeable, and convert to a native Sheet only when collaboration matters more than exact formatting.",[137,2735,2737],{"id":2736},"frequently-asked-questions","Frequently asked questions",[10,2739,2740,2743,2744,2746,2747,18],{},[180,2741,2742],{},"Why can't the service account see the folder I shared with it?","\nTwo usual causes. The folder was shared with the wrong address — it must be the service account's own ",[149,2745,195],{},", not your account — or the folder lives on a shared drive and the request is missing ",[149,2748,1366],{},[10,2750,2751,2757,2758,2760,2761,2764],{},[180,2752,2753,2754,2756],{},"Should I upload an ",[149,2755,1889],{}," or convert it to a Google Sheet?","\nUpload the ",[149,2759,1889],{}," when readers need the formatting and formulas exactly as generated. Convert to a Google Sheet, by setting the target ",[149,2762,2763],{},"mimeType",", when people will collaborate on it in the browser — conversion drops some Excel-specific formatting.",[10,2766,2767,2770,2771,2773,2774,2776],{},[180,2768,2769],{},"How do I overwrite last month's file instead of creating a duplicate?","\nDrive allows several files with the same name in one folder, so a plain ",[149,2772,938],{}," always adds another. Search for the existing file by name and parent, then call ",[149,2775,1462],{}," with its ID to add a new revision.",[10,2778,2779,2782],{},[180,2780,2781],{},"What is the service account storage quota problem?","\nA service account has no Drive storage of its own. Files it creates in My Drive count against a quota it does not have, so uploads fail. Put the target folder on a shared drive, or have the account write into a folder owned by a real user.",[10,2784,2785,2788,2789,2791],{},[180,2786,2787],{},"Do I need resumable uploads for a report?","\nFor anything above a few megabytes, yes. The Drive client switches to a resumable session when you pass ",[149,2790,2039],{},", which survives a dropped connection instead of restarting the transfer.",[137,2793,2795],{"id":2794},"related","Related",[184,2797,2798,2804,2811,2818,2824],{},[187,2799,2800,2801,2803],{},"Up to the parent: ",[14,2802,17],{"href":16}," — the naming and verification patterns behind this guide.",[187,2805,2806,2810],{},[14,2807,2809],{"href":2808},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-amazon-s3-with-boto3\u002F","Upload an Excel Report to Amazon S3 with boto3"," — the object-storage alternative.",[187,2812,2813,2817],{},[14,2814,2816],{"href":2815},"\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 Microsoft 365 equivalent.",[187,2819,2820,2823],{},[14,2821,2822],{"href":1884},"Write a Formatted Excel Report with xlsxwriter"," — the styled output that conversion would degrade.",[187,2825,2826,2829],{},[14,2827,2828],{"href":2298},"Retry a Failed Excel Report Job in Python"," — backoff for the throttling above.",[2831,2832,2833],"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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":147,"searchDepth":232,"depth":232,"links":2835},[2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846],{"id":139,"depth":232,"text":140},{"id":316,"depth":232,"text":317},{"id":508,"depth":232,"text":509},{"id":931,"depth":232,"text":932},{"id":1480,"depth":232,"text":1481},{"id":1669,"depth":232,"text":1670},{"id":1893,"depth":232,"text":1894},{"id":2033,"depth":232,"text":2034},{"id":2714,"depth":232,"text":2715},{"id":2736,"depth":232,"text":2737},{"id":2794,"depth":232,"text":2795},"2026-08-15","Publish a generated .xlsx to Google Drive from a scheduled job — service-account auth, folder targeting, resumable uploads, updating in place, and shared drive gotchas.","md",[2851,2853,2856,2858,2860],{"q":2742,"a":2852},"Two usual causes. The folder was shared with the wrong address — it must be the service account's own client_email, not your account — or the folder lives on a shared drive and the request is missing supportsAllDrives.",{"q":2854,"a":2855},"Should I upload an .xlsx or convert it to a Google Sheet?","Upload the .xlsx when readers need the formatting and formulas exactly as generated. Convert to a Google Sheet, by setting the target mimeType, when people will collaborate on it in the browser — conversion drops some Excel-specific formatting.",{"q":2769,"a":2857},"Drive allows several files with the same name in one folder, so a plain create always adds another. Search for the existing file by name and parent, then call files().update with its ID to add a new revision.",{"q":2781,"a":2859},"A service account has no Drive storage of its own. Files it creates in My Drive count against a quota it does not have, so uploads fail. Put the target folder on a shared drive, or have the account write into a folder owned by a real user.",{"q":2787,"a":2861},"For anything above a few megabytes, yes. The Drive client switches to a resumable session when you pass resumable=True, which survives a dropped connection instead of restarting the transfer.",{},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fsave-excel-reports-to-google-drive-with-python",{"title":2865,"description":2866},"Upload an Excel File to Google Drive with Python","Use the Drive API v3 from Python to publish Excel reports: service accounts, finding the target folder, create vs update, resumable uploads, and shared drive parameters.","save-excel-reports-to-google-drive-with-python","automating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fsave-excel-reports-to-google-drive-with-python\u002Findex","how-to","KFOK5gdCyKsU8RF_TB4K72CVcrwE6W3uzJmnAn3GBxQ",[2872,2875],{"title":17,"path":2873,"stem":2874,"children":-1},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage","automating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Findex",{"title":2809,"path":2876,"stem":2877,"children":-1},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-amazon-s3-with-boto3","automating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-amazon-s3-with-boto3\u002Findex",1786800027148]