[{"data":1,"prerenderedAt":1670},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox":3,"surround:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox":1636,"ancestors:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox":1645,"children:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox":1650,"related:\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox":1651},{"id":4,"title":5,"body":6,"dateModified":1604,"datePublished":1604,"description":1605,"extension":1606,"faq":1607,"keyword":1616,"kind":1617,"meta":1618,"navigation":263,"path":1626,"seo":1627,"slug":1630,"stem":1631,"topics":1632,"type":1634,"__hash__":1635},"docs\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox\u002Findex.md","Upload an Excel Report to Dropbox with Python",{"type":7,"value":8,"toc":1591},"minimark",[9,24,29,60,80,222,226,233,358,366,373,471,475,493,645,658,662,665,933,940,1065,1069,1080,1228,1360,1371,1375,1382,1418,1421,1425,1448,1452,1504,1508,1520,1524,1530,1536,1542,1554,1558,1587],[10,11,12,13,17,18,23],"p",{},"For teams that live in Dropbox, the most convenient place for a weekly report is a shared Dropbox folder: it syncs to everyone's laptop, keeps version history, and can be shared with people outside the company through a link. The Dropbox Python SDK uploads files in a few lines. The parts that trip up scheduled jobs are elsewhere: access tokens that expire after a few hours, a file that must replace last week's rather than creating ",[14,15,16],"code",{},"report (1).xlsx",", the size limit of the simple upload call, and shared links that fail the second time they are created. This page is part of ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002F","Publishing Excel Reports to Cloud Storage",", and covers each.",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install dropbox pandas openpyxl\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54,57],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," dropbox",[39,55,56],{"class":49}," pandas",[39,58,59],{"class":49}," openpyxl\n",[10,61,62,63,67,68,71,72,75,76,79],{},"In the Dropbox App Console, create a ",[64,65,66],"em",{},"scoped"," app with access to a single folder (\"App folder\") or to the whole account, and enable the permissions the job needs: ",[14,69,70],{},"files.content.write"," to upload and ",[14,73,74],{},"sharing.write"," to create links. The examples here run against a local imitation of the Dropbox API, so the code is exactly what would run in production and the uploaded bytes can be inspected afterwards; ",[14,77,78],{},"weekly_sales.xlsx"," is the report being published.",[81,82,90,91,90,95,90,99,90,107,90,117,90,124,90,129,90,134,90,137,90,141,90,146,90,148,90,150,90,155,90,160,90,168,90,174,90,178,90,181,90,186,90,190,90,195,90,198,90,203,90,207,90,210,90,213,90,218],"svg",{"viewBox":83,"role":84,"ariaLabelledBy":85,"xmlns":88,"style":89},"4 0 752 292","img",[86,87],"dbx-seq-t","dbx-seq-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[92,93,94],"title",{"id":86},"One unattended upload",[96,97,98],"desc",{"id":87},"A sequence diagram. The job exchanges its refresh token for a short-lived access token, uploads the workbook with overwrite mode, and asks for a shared link, receiving an existing link if one was created before.",[100,101],"rect",{"x":102,"y":103,"width":104,"height":105,"fill":106},"4","0","752","292","#ffffff",[100,108],{"x":109,"y":110,"width":111,"height":112,"rx":113,"fill":114,"stroke":115,"style":116},"20.0","16.0","180.0","48.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[118,119,123],"text",{"x":120,"y":121,"style":122},"110.0","44.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","report job",[100,125],{"x":126,"y":110,"width":111,"height":112,"rx":113,"fill":127,"stroke":128,"style":116},"290.0","#e7ebef","var(--line,#cdd5e6)",[118,130,133],{"x":131,"y":121,"style":132},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Dropbox OAuth",[100,135],{"x":136,"y":110,"width":111,"height":112,"rx":113,"fill":127,"stroke":128,"style":116},"560.0",[118,138,140],{"x":139,"y":121,"style":132},"650.0","Dropbox API",[41,142],{"x1":120,"y1":143,"x2":120,"y2":144,"stroke":128,"style":145},"64.0","276.0","stroke-width:1.5px;stroke-dasharray:5 4",[41,147],{"x1":131,"y1":143,"x2":131,"y2":144,"stroke":128,"style":145},[41,149],{"x1":139,"y1":143,"x2":139,"y2":144,"stroke":128,"style":145},[41,151],{"x1":152,"y1":153,"x2":154,"y2":153,"stroke":115,"style":116},"115.0","104.0","371.0",[156,157],"polygon",{"points":158,"fill":159},"375.0,104.0 366.0,109.0 366.0,99.0","#5b5cf0",[100,161],{"x":162,"y":163,"width":164,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"142.3","75.0","205.4","21.0","10","stroke-width:1.2px",[118,169,173],{"x":170,"y":171,"style":172},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. refresh token → access token",[41,175],{"x1":152,"y1":176,"x2":177,"y2":176,"stroke":115,"style":116},"152.0","641.0",[156,179],{"points":180,"fill":159},"645.0,152.0 636.0,157.0 636.0,147.0",[100,182],{"x":183,"y":184,"width":185,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"294.1","123.0","171.7",[118,187,189],{"x":131,"y":188,"style":172},"137.5","2. files\u002Fupload (overwrite)",[41,191],{"x1":192,"y1":193,"x2":194,"y2":193,"stroke":115,"style":116},"645.0","200.0","119.0",[156,196],{"points":197,"fill":159},"115.0,200.0 124.0,195.0 124.0,205.0",[100,199],{"x":200,"y":201,"width":202,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"274.0","171.0","212.0",[118,204,206],{"x":131,"y":205,"style":172},"185.5","3. FileMetadata with rev and size",[41,208],{"x1":152,"y1":209,"x2":177,"y2":209,"stroke":115,"style":116},"248.0",[156,211],{"points":212,"fill":159},"645.0,248.0 636.0,253.0 636.0,243.0",[100,214],{"x":215,"y":216,"width":217,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"284.8","219.0","190.3",[118,219,221],{"x":131,"y":220,"style":172},"233.5","4. create or reuse shared link",[25,223,225],{"id":224},"step-1-authenticate-with-a-refresh-token","Step 1 — Authenticate with a refresh token",[10,227,228,229,232],{},"Dropbox access tokens are short-lived — they expire after a few hours — so a job that stores one works on the day it is set up and fails the next week. Store a ",[64,230,231],{},"refresh token"," instead, together with the app key and secret, and let the SDK exchange it for access tokens as needed:",[30,234,238],{"className":235,"code":236,"language":237,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import os\nimport dropbox\n\ndbx = dropbox.Dropbox(\n    oauth2_refresh_token=os.environ[\"DROPBOX_REFRESH_TOKEN\"],\n    app_key=os.environ[\"DROPBOX_APP_KEY\"],\n    app_secret=os.environ[\"DROPBOX_APP_SECRET\"],\n)\nprint(type(dbx).__name__, \"ready\")\n","python",[14,239,240,250,258,265,277,295,310,325,331],{"__ignoreMap":35},[39,241,242,246],{"class":41,"line":42},[39,243,245],{"class":244},"s-kum","import",[39,247,249],{"class":248},"skGVy"," os\n",[39,251,253,255],{"class":41,"line":252},2,[39,254,245],{"class":244},[39,256,257],{"class":248}," dropbox\n",[39,259,261],{"class":41,"line":260},3,[39,262,264],{"emptyLinePlaceholder":263},true,"\n",[39,266,268,271,274],{"class":41,"line":267},4,[39,269,270],{"class":248},"dbx ",[39,272,273],{"class":244},"=",[39,275,276],{"class":248}," dropbox.Dropbox(\n",[39,278,280,284,286,289,292],{"class":41,"line":279},5,[39,281,283],{"class":282},"sa561","    oauth2_refresh_token",[39,285,273],{"class":244},[39,287,288],{"class":248},"os.environ[",[39,290,291],{"class":49},"\"DROPBOX_REFRESH_TOKEN\"",[39,293,294],{"class":248},"],\n",[39,296,298,301,303,305,308],{"class":41,"line":297},6,[39,299,300],{"class":282},"    app_key",[39,302,273],{"class":244},[39,304,288],{"class":248},[39,306,307],{"class":49},"\"DROPBOX_APP_KEY\"",[39,309,294],{"class":248},[39,311,313,316,318,320,323],{"class":41,"line":312},7,[39,314,315],{"class":282},"    app_secret",[39,317,273],{"class":244},[39,319,288],{"class":248},[39,321,322],{"class":49},"\"DROPBOX_APP_SECRET\"",[39,324,294],{"class":248},[39,326,328],{"class":41,"line":327},8,[39,329,330],{"class":248},")\n",[39,332,334,338,341,344,347,350,353,356],{"class":41,"line":333},9,[39,335,337],{"class":336},"sP0c6","print",[39,339,340],{"class":248},"(",[39,342,343],{"class":336},"type",[39,345,346],{"class":248},"(dbx).",[39,348,349],{"class":336},"__name__",[39,351,352],{"class":248},", ",[39,354,355],{"class":49},"\"ready\"",[39,357,330],{"class":248},[10,359,360,361,365],{},"The SDK requests a fresh access token the first time it needs one and again whenever the current one has expired, so a long-running or weekly job never sees an expiry. The three values come from the environment, not the script — see ",[19,362,364],{"href":363},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fstore-credentials-for-excel-report-scripts-safely\u002F","Store Credentials for Excel Report Scripts Safely",".",[10,367,368,369,372],{},"Getting the refresh token is a one-time, interactive step: run the OAuth flow with ",[14,370,371],{},"token_access_type=\"offline\"",", open the URL it prints, approve, and paste the code back:",[30,374,376],{"className":235,"code":375,"language":237,"meta":35,"style":35},"from dropbox import DropboxOAuth2FlowNoRedirect\n\nflow = DropboxOAuth2FlowNoRedirect(os.environ[\"DROPBOX_APP_KEY\"], os.environ[\"DROPBOX_APP_SECRET\"],\n                                   token_access_type=\"offline\")\nprint(\"Open:\", flow.start())\nresult = flow.finish(input(\"Code: \").strip())\nprint(\"refresh token:\", result.refresh_token)\n",[14,377,378,391,395,414,426,438,459],{"__ignoreMap":35},[39,379,380,383,386,388],{"class":41,"line":42},[39,381,382],{"class":244},"from",[39,384,385],{"class":248}," dropbox ",[39,387,245],{"class":244},[39,389,390],{"class":248}," DropboxOAuth2FlowNoRedirect\n",[39,392,393],{"class":41,"line":252},[39,394,264],{"emptyLinePlaceholder":263},[39,396,397,400,402,405,407,410,412],{"class":41,"line":260},[39,398,399],{"class":248},"flow ",[39,401,273],{"class":244},[39,403,404],{"class":248}," DropboxOAuth2FlowNoRedirect(os.environ[",[39,406,307],{"class":49},[39,408,409],{"class":248},"], os.environ[",[39,411,322],{"class":49},[39,413,294],{"class":248},[39,415,416,419,421,424],{"class":41,"line":267},[39,417,418],{"class":282},"                                   token_access_type",[39,420,273],{"class":244},[39,422,423],{"class":49},"\"offline\"",[39,425,330],{"class":248},[39,427,428,430,432,435],{"class":41,"line":279},[39,429,337],{"class":336},[39,431,340],{"class":248},[39,433,434],{"class":49},"\"Open:\"",[39,436,437],{"class":248},", flow.start())\n",[39,439,440,443,445,448,451,453,456],{"class":41,"line":297},[39,441,442],{"class":248},"result ",[39,444,273],{"class":244},[39,446,447],{"class":248}," flow.finish(",[39,449,450],{"class":336},"input",[39,452,340],{"class":248},[39,454,455],{"class":49},"\"Code: \"",[39,457,458],{"class":248},").strip())\n",[39,460,461,463,465,468],{"class":41,"line":312},[39,462,337],{"class":336},[39,464,340],{"class":248},[39,466,467],{"class":49},"\"refresh token:\"",[39,469,470],{"class":248},", result.refresh_token)\n",[25,472,474],{"id":473},"step-2-upload-replacing-last-weeks-file","Step 2 — Upload, replacing last week's file",[10,476,477,480,481,484,485,488,489,492],{},[14,478,479],{},"files_upload"," sends the bytes and a path. The ",[64,482,483],{},"mode"," decides what happens if a file already exists there: ",[14,486,487],{},"WriteMode.overwrite"," replaces it, which is what a report with a fixed name needs; the default, ",[14,490,491],{},"add",", fails or renames:",[30,494,496],{"className":235,"code":495,"language":237,"meta":35,"style":35},"from pathlib import Path\nfrom dropbox.files import WriteMode\n\ndef upload(dbx, local_path, dropbox_path):\n    data = Path(local_path).read_bytes()\n    meta = dbx.files_upload(data, dropbox_path, mode=WriteMode.overwrite, mute=True)\n    return meta\n\nmeta = upload(dbx, \"weekly_sales.xlsx\", \"\u002FReports\u002FSales\u002Fweekly_sales.xlsx\")\nprint(meta.path_display, f\"{meta.size:,} bytes\", \"rev\", meta.rev)\n",[14,497,498,510,522,526,538,548,575,583,587,607],{"__ignoreMap":35},[39,499,500,502,505,507],{"class":41,"line":42},[39,501,382],{"class":244},[39,503,504],{"class":248}," pathlib ",[39,506,245],{"class":244},[39,508,509],{"class":248}," Path\n",[39,511,512,514,517,519],{"class":41,"line":252},[39,513,382],{"class":244},[39,515,516],{"class":248}," dropbox.files ",[39,518,245],{"class":244},[39,520,521],{"class":248}," WriteMode\n",[39,523,524],{"class":41,"line":260},[39,525,264],{"emptyLinePlaceholder":263},[39,527,528,531,535],{"class":41,"line":267},[39,529,530],{"class":244},"def",[39,532,534],{"class":533},"s_Opv"," upload",[39,536,537],{"class":248},"(dbx, local_path, dropbox_path):\n",[39,539,540,543,545],{"class":41,"line":279},[39,541,542],{"class":248},"    data ",[39,544,273],{"class":244},[39,546,547],{"class":248}," Path(local_path).read_bytes()\n",[39,549,550,553,555,558,560,562,565,568,570,573],{"class":41,"line":297},[39,551,552],{"class":248},"    meta ",[39,554,273],{"class":244},[39,556,557],{"class":248}," dbx.files_upload(data, dropbox_path, ",[39,559,483],{"class":282},[39,561,273],{"class":244},[39,563,564],{"class":248},"WriteMode.overwrite, ",[39,566,567],{"class":282},"mute",[39,569,273],{"class":244},[39,571,572],{"class":336},"True",[39,574,330],{"class":248},[39,576,577,580],{"class":41,"line":312},[39,578,579],{"class":244},"    return",[39,581,582],{"class":248}," meta\n",[39,584,585],{"class":41,"line":327},[39,586,264],{"emptyLinePlaceholder":263},[39,588,589,592,594,597,600,602,605],{"class":41,"line":333},[39,590,591],{"class":248},"meta ",[39,593,273],{"class":244},[39,595,596],{"class":248}," upload(dbx, ",[39,598,599],{"class":49},"\"weekly_sales.xlsx\"",[39,601,352],{"class":248},[39,603,604],{"class":49},"\"\u002FReports\u002FSales\u002Fweekly_sales.xlsx\"",[39,606,330],{"class":248},[39,608,610,612,615,618,621,625,628,631,634,637,639,642],{"class":41,"line":609},10,[39,611,337],{"class":336},[39,613,614],{"class":248},"(meta.path_display, ",[39,616,617],{"class":244},"f",[39,619,620],{"class":49},"\"",[39,622,624],{"class":623},"sSjpA","{",[39,626,627],{"class":248},"meta.size",[39,629,630],{"class":244},":,",[39,632,633],{"class":623},"}",[39,635,636],{"class":49}," bytes\"",[39,638,352],{"class":248},[39,640,641],{"class":49},"\"rev\"",[39,643,644],{"class":248},", meta.rev)\n",[10,646,647,650,651,654,655,365],{},[14,648,649],{},"mute=True"," stops Dropbox notifying everyone who shares the folder each week. The SDK computes a content hash of the bytes and sends it with the upload, so Dropbox rejects a file that was corrupted on the way — a check you get without writing anything. Overwriting does not lose history: Dropbox keeps previous versions of the file, and the returned ",[14,652,653],{},"rev"," identifies this one. Paths are case-insensitive in Dropbox but shown with the case you used, and must start with ",[14,656,657],{},"\u002F",[25,659,661],{"id":660},"step-3-large-files-upload-sessions","Step 3 — Large files: upload sessions",[10,663,664],{},"The single upload call accepts files up to 150 MB. Larger workbooks — or exports bundled into a zip — go in chunks through an upload session: start it with the first chunk, append the middle chunks at their offsets, and finish with the last chunk and the destination:",[30,666,668],{"className":235,"code":667,"language":237,"meta":35,"style":35},"from dropbox.files import CommitInfo, UploadSessionCursor\n\nCHUNK = 8 * 1024 * 1024\n\ndef upload_large(dbx, local_path, dropbox_path, chunk=CHUNK):\n    size = Path(local_path).stat().st_size\n    with open(local_path, \"rb\") as f:\n        start = dbx.files_upload_session_start(f.read(chunk))\n        cursor = UploadSessionCursor(session_id=start.session_id, offset=f.tell())\n        commit = CommitInfo(path=dropbox_path, mode=WriteMode.overwrite, mute=True)\n        while size - f.tell() > chunk:\n            dbx.files_upload_session_append_v2(f.read(chunk), cursor)\n            cursor.offset = f.tell()\n        return dbx.files_upload_session_finish(f.read(chunk), cursor, commit)\n\nmeta = upload_large(dbx, \"weekly_sales.xlsx\", \"\u002FReports\u002FSales\u002Fweekly_sales_chunked.xlsx\", chunk=2048)\nprint(meta.path_display, f\"{meta.size:,} bytes\")\n",[14,669,670,681,685,707,711,728,738,761,771,797,829,850,856,867,876,881,910],{"__ignoreMap":35},[39,671,672,674,676,678],{"class":41,"line":42},[39,673,382],{"class":244},[39,675,516],{"class":248},[39,677,245],{"class":244},[39,679,680],{"class":248}," CommitInfo, UploadSessionCursor\n",[39,682,683],{"class":41,"line":252},[39,684,264],{"emptyLinePlaceholder":263},[39,686,687,690,693,696,699,702,704],{"class":41,"line":260},[39,688,689],{"class":336},"CHUNK",[39,691,692],{"class":244}," =",[39,694,695],{"class":336}," 8",[39,697,698],{"class":244}," *",[39,700,701],{"class":336}," 1024",[39,703,698],{"class":244},[39,705,706],{"class":336}," 1024\n",[39,708,709],{"class":41,"line":267},[39,710,264],{"emptyLinePlaceholder":263},[39,712,713,715,718,721,723,725],{"class":41,"line":279},[39,714,530],{"class":244},[39,716,717],{"class":533}," upload_large",[39,719,720],{"class":248},"(dbx, local_path, dropbox_path, chunk",[39,722,273],{"class":244},[39,724,689],{"class":336},[39,726,727],{"class":248},"):\n",[39,729,730,733,735],{"class":41,"line":297},[39,731,732],{"class":248},"    size ",[39,734,273],{"class":244},[39,736,737],{"class":248}," Path(local_path).stat().st_size\n",[39,739,740,743,746,749,752,755,758],{"class":41,"line":312},[39,741,742],{"class":244},"    with",[39,744,745],{"class":336}," open",[39,747,748],{"class":248},"(local_path, ",[39,750,751],{"class":49},"\"rb\"",[39,753,754],{"class":248},") ",[39,756,757],{"class":244},"as",[39,759,760],{"class":248}," f:\n",[39,762,763,766,768],{"class":41,"line":327},[39,764,765],{"class":248},"        start ",[39,767,273],{"class":244},[39,769,770],{"class":248}," dbx.files_upload_session_start(f.read(chunk))\n",[39,772,773,776,778,781,784,786,789,792,794],{"class":41,"line":333},[39,774,775],{"class":248},"        cursor ",[39,777,273],{"class":244},[39,779,780],{"class":248}," UploadSessionCursor(",[39,782,783],{"class":282},"session_id",[39,785,273],{"class":244},[39,787,788],{"class":248},"start.session_id, ",[39,790,791],{"class":282},"offset",[39,793,273],{"class":244},[39,795,796],{"class":248},"f.tell())\n",[39,798,799,802,804,807,810,812,815,817,819,821,823,825,827],{"class":41,"line":609},[39,800,801],{"class":248},"        commit ",[39,803,273],{"class":244},[39,805,806],{"class":248}," CommitInfo(",[39,808,809],{"class":282},"path",[39,811,273],{"class":244},[39,813,814],{"class":248},"dropbox_path, ",[39,816,483],{"class":282},[39,818,273],{"class":244},[39,820,564],{"class":248},[39,822,567],{"class":282},[39,824,273],{"class":244},[39,826,572],{"class":336},[39,828,330],{"class":248},[39,830,832,835,838,841,844,847],{"class":41,"line":831},11,[39,833,834],{"class":244},"        while",[39,836,837],{"class":248}," size ",[39,839,840],{"class":244},"-",[39,842,843],{"class":248}," f.tell() ",[39,845,846],{"class":244},">",[39,848,849],{"class":248}," chunk:\n",[39,851,853],{"class":41,"line":852},12,[39,854,855],{"class":248},"            dbx.files_upload_session_append_v2(f.read(chunk), cursor)\n",[39,857,859,862,864],{"class":41,"line":858},13,[39,860,861],{"class":248},"            cursor.offset ",[39,863,273],{"class":244},[39,865,866],{"class":248}," f.tell()\n",[39,868,870,873],{"class":41,"line":869},14,[39,871,872],{"class":244},"        return",[39,874,875],{"class":248}," dbx.files_upload_session_finish(f.read(chunk), cursor, commit)\n",[39,877,879],{"class":41,"line":878},15,[39,880,264],{"emptyLinePlaceholder":263},[39,882,884,886,888,891,893,895,898,900,903,905,908],{"class":41,"line":883},16,[39,885,591],{"class":248},[39,887,273],{"class":244},[39,889,890],{"class":248}," upload_large(dbx, ",[39,892,599],{"class":49},[39,894,352],{"class":248},[39,896,897],{"class":49},"\"\u002FReports\u002FSales\u002Fweekly_sales_chunked.xlsx\"",[39,899,352],{"class":248},[39,901,902],{"class":282},"chunk",[39,904,273],{"class":244},[39,906,907],{"class":336},"2048",[39,909,330],{"class":248},[39,911,913,915,917,919,921,923,925,927,929,931],{"class":41,"line":912},17,[39,914,337],{"class":336},[39,916,614],{"class":248},[39,918,617],{"class":244},[39,920,620],{"class":49},[39,922,624],{"class":623},[39,924,627],{"class":248},[39,926,630],{"class":244},[39,928,633],{"class":623},[39,930,636],{"class":49},[39,932,330],{"class":248},[10,934,935,936,939],{},"The cursor's offset must equal the number of bytes sent so far, which ",[14,937,938],{},"f.tell()"," provides exactly. The example uses a 2 KB chunk only to exercise the loop with a small file; in production, chunks of 4–16 MB balance the number of requests against how much has to be resent if one fails. Use the session route for everything above about 100 MB, not just above 150 MB, to leave headroom.",[81,941,90,946,90,949,90,952,90,955,90,962,90,966,90,976,90,982,90,987,90,991,90,995,90,999,90,1005,90,1010,90,1013,90,1015,90,1017,90,1020,90,1025,90,1028,90,1031,90,1033,90,1035,90,1040,90,1047,90,1053,90,1057,90,1061,90,1063],{"viewBox":942,"role":84,"ariaLabelledBy":943,"xmlns":88,"style":89},"4 0 752 313",[944,945],"dbx-chunks-t","dbx-chunks-d",[92,947,948],{"id":944},"A chunked upload session",[96,950,951],{"id":945},"A timeline of an upload session. It starts with the first chunk and returns a session id, appends further chunks at increasing offsets, and finishes with the last chunk and the commit information naming the destination path.",[100,953],{"x":102,"y":103,"width":104,"height":954,"fill":106},"313",[41,956],{"x1":957,"y1":958,"x2":959,"y2":958,"stroke":960,"style":961},"94.0","156.5","666.0","var(--muted,#5b6780)","stroke-width:2.5px",[100,963],{"x":109,"y":110,"width":964,"height":965,"rx":113,"fill":114,"stroke":115,"style":116},"172.0","108.5",[967,968,970],"g",{"transform":969},"translate(94.0,37.5) scale(1.000)",[809,971],{"d":972,"fill":973,"stroke":974,"style":975},"M3 11l18-8-8 18-2-8z","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[118,977,981],{"x":978,"y":979,"style":980},"106.0","83.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","start",[118,983,986],{"x":978,"y":984,"style":985},"101.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","first chunk → session_id",[41,988],{"x1":978,"y1":989,"x2":978,"y2":958,"stroke":960,"style":990},"124.5","stroke-width:1.5px",[992,993],"circle",{"cx":978,"cy":958,"r":994,"fill":115,"stroke":106,"style":116},"7.0",[100,996],{"x":997,"y":998,"width":964,"height":965,"rx":113,"fill":114,"stroke":115,"style":116},"202.7","188.5",[967,1000,1002],{"transform":1001},"translate(276.7,210.0) scale(1.000)",[809,1003],{"d":1004,"fill":973,"stroke":974,"style":975},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[118,1006,1009],{"x":1007,"y":1008,"style":980},"288.7","255.5","append",[118,1011,1012],{"x":1007,"y":200,"style":985},"offset = bytes sent",[41,1014],{"x1":1007,"y1":958,"x2":1007,"y2":998,"stroke":960,"style":990},[992,1016],{"cx":1007,"cy":958,"r":994,"fill":115,"stroke":106,"style":116},[100,1018],{"x":1019,"y":110,"width":964,"height":965,"rx":113,"fill":114,"stroke":115,"style":116},"385.3",[967,1021,1023],{"transform":1022},"translate(459.3,37.5) scale(1.000)",[809,1024],{"d":1004,"fill":973,"stroke":974,"style":975},[118,1026,1009],{"x":1027,"y":979,"style":980},"471.3",[118,1029,1030],{"x":1027,"y":984,"style":985},"…as many as needed",[41,1032],{"x1":1027,"y1":989,"x2":1027,"y2":958,"stroke":960,"style":990},[992,1034],{"cx":1027,"cy":958,"r":994,"fill":115,"stroke":106,"style":116},[100,1036],{"x":1037,"y":998,"width":964,"height":965,"rx":113,"fill":1038,"stroke":1039,"style":116},"568.0","#d9f4f1","var(--teal,#0f9488)",[967,1041,1043],{"transform":1042},"translate(642.0,201.5) scale(1.000)",[809,1044],{"d":1045,"fill":973,"stroke":1046,"style":975},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[118,1048,1052],{"x":1049,"y":1050,"style":1051},"654.0","247.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","finish",[118,1054,1056],{"x":1049,"y":1055,"style":985},"265.5","last chunk +",[118,1058,1060],{"x":1049,"y":1059,"style":985},"282.5","CommitInfo(path)",[41,1062],{"x1":1049,"y1":958,"x2":1049,"y2":998,"stroke":960,"style":990},[992,1064],{"cx":1049,"cy":958,"r":994,"fill":1039,"stroke":106,"style":116},[25,1066,1068],{"id":1067},"step-4-share-a-link-and-reuse-it-next-week","Step 4 — Share a link, and reuse it next week",[10,1070,1071,1072,1075,1076,1079],{},"A shared link lets people without access to the folder open the report. Creating a link for a path that already has one, with ",[14,1073,1074],{},"sharing_create_shared_link_with_settings",", raises ",[14,1077,1078],{},"shared_link_already_exists",", which every weekly job hits on its second run. Catch it and fetch the existing link:",[30,1081,1083],{"className":235,"code":1082,"language":237,"meta":35,"style":35},"from dropbox.exceptions import ApiError\n\ndef shared_link(dbx, path):\n    try:\n        return dbx.sharing_create_shared_link_with_settings(path).url\n    except ApiError as err:\n        if err.error.is_shared_link_already_exists():\n            links = dbx.sharing_list_shared_links(path=path, direct_only=True).links\n            return links[0].url\n        raise\n\nprint(\"week 1:\", shared_link(dbx, \"\u002FReports\u002FSales\u002Fweekly_sales.xlsx\"))\nprint(\"week 2:\", shared_link(dbx, \"\u002FReports\u002FSales\u002Fweekly_sales.xlsx\"))\n",[14,1084,1085,1097,1101,1111,1119,1126,1139,1147,1174,1187,1192,1196,1213],{"__ignoreMap":35},[39,1086,1087,1089,1092,1094],{"class":41,"line":42},[39,1088,382],{"class":244},[39,1090,1091],{"class":248}," dropbox.exceptions ",[39,1093,245],{"class":244},[39,1095,1096],{"class":248}," ApiError\n",[39,1098,1099],{"class":41,"line":252},[39,1100,264],{"emptyLinePlaceholder":263},[39,1102,1103,1105,1108],{"class":41,"line":260},[39,1104,530],{"class":244},[39,1106,1107],{"class":533}," shared_link",[39,1109,1110],{"class":248},"(dbx, path):\n",[39,1112,1113,1116],{"class":41,"line":267},[39,1114,1115],{"class":244},"    try",[39,1117,1118],{"class":248},":\n",[39,1120,1121,1123],{"class":41,"line":279},[39,1122,872],{"class":244},[39,1124,1125],{"class":248}," dbx.sharing_create_shared_link_with_settings(path).url\n",[39,1127,1128,1131,1134,1136],{"class":41,"line":297},[39,1129,1130],{"class":244},"    except",[39,1132,1133],{"class":248}," ApiError ",[39,1135,757],{"class":244},[39,1137,1138],{"class":248}," err:\n",[39,1140,1141,1144],{"class":41,"line":312},[39,1142,1143],{"class":244},"        if",[39,1145,1146],{"class":248}," err.error.is_shared_link_already_exists():\n",[39,1148,1149,1152,1154,1157,1159,1161,1164,1167,1169,1171],{"class":41,"line":327},[39,1150,1151],{"class":248},"            links ",[39,1153,273],{"class":244},[39,1155,1156],{"class":248}," dbx.sharing_list_shared_links(",[39,1158,809],{"class":282},[39,1160,273],{"class":244},[39,1162,1163],{"class":248},"path, ",[39,1165,1166],{"class":282},"direct_only",[39,1168,273],{"class":244},[39,1170,572],{"class":336},[39,1172,1173],{"class":248},").links\n",[39,1175,1176,1179,1182,1184],{"class":41,"line":333},[39,1177,1178],{"class":244},"            return",[39,1180,1181],{"class":248}," links[",[39,1183,103],{"class":336},[39,1185,1186],{"class":248},"].url\n",[39,1188,1189],{"class":41,"line":609},[39,1190,1191],{"class":244},"        raise\n",[39,1193,1194],{"class":41,"line":831},[39,1195,264],{"emptyLinePlaceholder":263},[39,1197,1198,1200,1202,1205,1208,1210],{"class":41,"line":852},[39,1199,337],{"class":336},[39,1201,340],{"class":248},[39,1203,1204],{"class":49},"\"week 1:\"",[39,1206,1207],{"class":248},", shared_link(dbx, ",[39,1209,604],{"class":49},[39,1211,1212],{"class":248},"))\n",[39,1214,1215,1217,1219,1222,1224,1226],{"class":41,"line":858},[39,1216,337],{"class":336},[39,1218,340],{"class":248},[39,1220,1221],{"class":49},"\"week 2:\"",[39,1223,1207],{"class":248},[39,1225,604],{"class":49},[39,1227,1212],{"class":248},[81,1229,90,1234,90,1237,90,1240,90,1243,90,1248,90,1254,90,1258,90,1262,90,1266,90,1269,90,1272,90,1277,90,1282,90,1287,90,1292,90,1297,90,1302,90,1304,90,1307,90,1311,90,1314,90,1317,90,1323,90,1326,90,1329,90,1331,90,1334,90,1338,90,1341,90,1346,90,1353,90,1357],{"viewBox":1230,"role":84,"ariaLabelledBy":1231,"xmlns":88,"style":89},"4 0 752 272",[1232,1233],"dbx-link-t","dbx-link-d",[92,1235,1236],{"id":1232},"Getting a shared link on every run",[96,1238,1239],{"id":1233},"A decision diagram. The job asks Dropbox to create a shared link. On the first run a new link is returned; on later runs Dropbox answers shared_link_already_exists and the job lists the existing link instead; any other error fails the job.",[100,1241],{"x":102,"y":103,"width":104,"height":1242,"fill":106},"272",[100,1244],{"x":1245,"y":110,"width":1246,"height":1247,"rx":113,"fill":114,"stroke":115,"style":116},"234.7","290.5","75.5",[967,1249,1251],{"transform":1250},"translate(368.0,29.0) scale(1.000)",[809,1252],{"d":1253,"fill":973,"stroke":974,"style":975},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1",[118,1255,1257],{"x":131,"y":1247,"style":1256},"font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","create_shared_link_with_settings",[41,1259],{"x1":131,"y1":1260,"x2":131,"y2":1261,"stroke":115,"style":116},"91.5","111.5",[41,1263],{"x1":1264,"y1":1261,"x2":1265,"y2":1261,"stroke":115,"style":116},"134.7","625.3",[41,1267],{"x1":1264,"y1":1261,"x2":1264,"y2":1268,"stroke":115,"style":116},"157.5",[156,1270],{"points":1271,"fill":159},"134.7,161.5 129.7,152.5 139.7,152.5",[100,1273],{"x":1274,"y":1275,"width":1276,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"102.2","121.5","65.0",[118,1278,1281],{"x":1264,"y":1279,"style":1280},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","first run",[100,1283],{"x":109,"y":1284,"width":1285,"height":1286,"rx":113,"fill":1038,"stroke":1039,"style":116},"163.5","229.3","93.0",[967,1288,1290],{"transform":1289},"translate(122.7,176.5) scale(1.000)",[809,1291],{"d":1045,"fill":973,"stroke":1046,"style":975},[118,1293,1296],{"x":1264,"y":1294,"style":1295},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","New link",[118,1298,1301],{"x":1264,"y":1299,"style":1300},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","returned directly",[41,1303],{"x1":131,"y1":1261,"x2":131,"y2":1268,"stroke":115,"style":116},[156,1305],{"points":1306,"fill":159},"380.0,161.5 375.0,152.5 385.0,152.5",[100,1308],{"x":1309,"y":1275,"width":1310,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"329.7","100.6",[118,1312,1313],{"x":131,"y":1279,"style":1280},"already exists",[100,1315],{"x":1316,"y":1284,"width":1285,"height":1286,"rx":113,"fill":114,"stroke":115,"style":116},"265.3",[967,1318,1320],{"transform":1319},"translate(368.0,176.5) scale(1.000)",[809,1321],{"d":1322,"fill":973,"stroke":974,"style":975},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[118,1324,1325],{"x":131,"y":1294,"style":1256},"List existing link",[118,1327,1328],{"x":131,"y":1299,"style":1300},"direct_only=True",[41,1330],{"x1":1265,"y1":1261,"x2":1265,"y2":1268,"stroke":115,"style":116},[156,1332],{"points":1333,"fill":159},"625.3,161.5 620.3,152.5 630.3,152.5",[100,1335],{"x":1336,"y":1275,"width":1337,"height":165,"rx":166,"fill":106,"stroke":128,"style":167},"574.5","101.7",[118,1339,1340],{"x":1265,"y":1279,"style":1280},"other ApiError",[100,1342],{"x":1343,"y":1284,"width":1285,"height":1286,"rx":113,"fill":1344,"stroke":1345,"style":116},"510.7","#fce9e9","var(--danger,#dc2626)",[967,1347,1349],{"transform":1348},"translate(613.3,176.5) scale(1.000)",[809,1350],{"d":1351,"fill":973,"stroke":1352,"style":975},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--accent-ink,#be185d)",[118,1354,1356],{"x":1265,"y":1294,"style":1355},"font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Fail the job",[118,1358,1359],{"x":1265,"y":1299,"style":1300},"scopes, path, auth",[10,1361,1362,1363,1366,1367,1370],{},"Both runs return the same URL, so a link published once — in a wiki page or an email signature — keeps working as the file is overwritten each week. The link opens a preview page; appending ",[14,1364,1365],{},"dl=1"," instead of ",[14,1368,1369],{},"dl=0"," makes it download the file directly, which suits links in automated emails.",[25,1372,1374],{"id":1373},"step-5-confirm-what-arrived","Step 5 — Confirm what arrived",[10,1376,1377,1378,1381],{},"The local imitation keeps the bytes each upload delivered, so the check reads the \"uploaded\" workbook back with pandas — the same check a real job can do with ",[14,1379,1380],{},"files_download"," on a sample of runs:",[30,1383,1385],{"className":235,"code":1384,"language":237,"meta":35,"style":35},"_, response = dbx.files_download(\"\u002FReports\u002FSales\u002Fweekly_sales.xlsx\")\ncheck = pd.read_excel(io.BytesIO(response.content))\nprint(check.shape)\n",[14,1386,1387,1401,1411],{"__ignoreMap":35},[39,1388,1389,1392,1394,1397,1399],{"class":41,"line":42},[39,1390,1391],{"class":248},"_, response ",[39,1393,273],{"class":244},[39,1395,1396],{"class":248}," dbx.files_download(",[39,1398,604],{"class":49},[39,1400,330],{"class":248},[39,1402,1403,1406,1408],{"class":41,"line":252},[39,1404,1405],{"class":248},"check ",[39,1407,273],{"class":244},[39,1409,1410],{"class":248}," pd.read_excel(io.BytesIO(response.content))\n",[39,1412,1413,1415],{"class":41,"line":260},[39,1414,337],{"class":336},[39,1416,1417],{"class":248},"(check.shape)\n",[10,1419,1420],{},"Both the simple and the chunked upload delivered exactly the local file's bytes, and the workbook reads back with its four regions.",[25,1422,1424],{"id":1423},"performance-notes","Performance notes",[10,1426,1427,1428,1431,1432,1435,1436,1439,1440,1443,1444,1447],{},"A weekly report of a few megabytes uploads in a second or two; the refresh-token exchange adds one short request per run. Upload sessions add a request per chunk, so very small chunks waste time — the 2 KB chunk above is only for demonstration. For many files at once, such as one workbook per region, Dropbox also offers batch commits for upload sessions (",[14,1429,1430],{},"files_upload_session_finish_batch_v2","), which avoid conflicts between parallel uploads into the same folder; for a handful of files, uploading them one after another is simpler and fast enough. Dropbox applies rate limits per app and user. The client constructor controls how the SDK reacts: ",[14,1433,1434],{},"max_retries_on_error"," (4 by default) retries server errors, and ",[14,1437,1438],{},"max_retries_on_rate_limit"," governs rate-limit responses — left at its default of ",[14,1441,1442],{},"None",", the SDK keeps retrying them, so a scheduled job may want to set a number and fail loudly instead of waiting indefinitely. The content hash mentioned in Step 2 is the ",[14,1445,1446],{},"auto_content_hash=True"," default of the same constructor.",[25,1449,1451],{"id":1450},"common-pitfalls","Common pitfalls",[1453,1454,1455,1463,1475,1483,1492],"ul",{},[1456,1457,1458,1462],"li",{},[1459,1460,1461],"strong",{},"Storing an access token."," It expires in hours; store a refresh token with the app key and secret.",[1456,1464,1465,1468,1469,1471,1472,1474],{},[1459,1466,1467],{},"The default write mode."," ",[14,1470,491],{}," does not replace the existing file; use ",[14,1473,487],{}," for fixed report names.",[1456,1476,1477,1482],{},[1459,1478,1479,1480,365],{},"Files over 150 MB with ",[14,1481,479],{}," Use an upload session.",[1456,1484,1485,1488,1489,1491],{},[1459,1486,1487],{},"Creating a shared link every run."," The second call fails; catch ",[14,1490,1078],{}," and list the existing link.",[1456,1493,1494,1497,1498,1500,1501,1503],{},[1459,1495,1496],{},"Missing scopes."," An app without ",[14,1499,70],{}," or ",[14,1502,74],{}," gets an authorisation error on that call; scopes are set in the App Console, and the refresh token must be re-issued after changing them.",[25,1505,1507],{"id":1506},"conclusion","Conclusion",[10,1509,1510,1511,1513,1514,1516,1517,1519],{},"Create a scoped Dropbox app, obtain a refresh token once with the offline OAuth flow, and build the client from the refresh token, app key and secret so the job never sees an expired token. Upload with ",[14,1512,487],{}," and ",[14,1515,649],{},", switch to upload sessions for large files, create a shared link once and reuse it by catching ",[14,1518,1078],{},", and check a sample of uploads by downloading them back.",[25,1521,1523],{"id":1522},"frequently-asked-questions","Frequently asked questions",[10,1525,1526,1529],{},[1459,1527,1528],{},"How do I upload a file to Dropbox with Python?","\nInstall the dropbox package, create a client with dropbox.Dropbox(...) and call files_upload(data, \"\u002FFolder\u002Ffile.xlsx\", mode=WriteMode.overwrite). The path must start with a slash.",[10,1531,1532,1535],{},[1459,1533,1534],{},"Why does my Dropbox token stop working after a few hours?","\nDropbox access tokens are short-lived. Use a refresh token obtained with token_access_type=\"offline\", and pass it with the app key and secret to dropbox.Dropbox so the SDK refreshes access tokens automatically.",[10,1537,1538,1541],{},[1459,1539,1540],{},"How do I upload files larger than 150 MB to Dropbox?","\nUse an upload session: files_upload_session_start with the first chunk, files_upload_session_append_v2 for each further chunk with the current offset, and files_upload_session_finish with the last chunk and a CommitInfo naming the destination.",[10,1543,1544,1547,1548,1551,1552,365],{},[1459,1545,1546],{},"How do I get a Dropbox shared link that stays the same?","\nCreate it once with the SDK's create-shared-link call. On later runs, catch the shared_link_already_exists error and read the existing link with ",[14,1549,1550],{},"sharing_list_shared_links",", passing the path and ",[14,1553,1328],{},[25,1555,1557],{"id":1556},"related","Related",[1453,1559,1560,1566,1573,1580],{},[1456,1561,1562,1563,1565],{},"Up one level: ",[19,1564,22],{"href":21}," — every destination for finished reports.",[1456,1567,1568,1572],{},[19,1569,1571],{"href":1570},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fsave-excel-reports-to-google-drive-with-python\u002F","Save Excel Reports to Google Drive with Python"," — the Google equivalent.",[1456,1574,1575,1579],{},[19,1576,1578],{"href":1577},"\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"," — for Microsoft 365 teams.",[1456,1581,1582,1586],{},[19,1583,1585],{"href":1584},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fretry-a-failed-excel-report-job-in-python\u002F","Retry a Failed Excel Report Job in Python"," — handling network failures around the upload.",[1588,1589,1590],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":252,"depth":252,"links":1592},[1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603],{"id":27,"depth":252,"text":28},{"id":224,"depth":252,"text":225},{"id":473,"depth":252,"text":474},{"id":660,"depth":252,"text":661},{"id":1067,"depth":252,"text":1068},{"id":1373,"depth":252,"text":1374},{"id":1423,"depth":252,"text":1424},{"id":1450,"depth":252,"text":1451},{"id":1506,"depth":252,"text":1507},{"id":1522,"depth":252,"text":1523},{"id":1556,"depth":252,"text":1557},"2026-09-29","Publish a generated workbook to a Dropbox folder from a scheduled job: a scoped app and a long-lived refresh token instead of expiring access tokens, uploads that overwrite last week's file, chunked upload sessions for large files, a shared link that is reused rather than duplicated, and the errors worth handling.","md",[1608,1610,1612,1614],{"q":1528,"a":1609},"Install the dropbox package, create a client with dropbox.Dropbox(...) and call files_upload(data, \"\u002FFolder\u002Ffile.xlsx\", mode=WriteMode.overwrite). The path must start with a slash.",{"q":1534,"a":1611},"Dropbox access tokens are short-lived. Use a refresh token obtained with token_access_type=\"offline\", and pass it with the app key and secret to dropbox.Dropbox so the SDK refreshes access tokens automatically.",{"q":1540,"a":1613},"Use an upload session: files_upload_session_start with the first chunk, files_upload_session_append_v2 for each further chunk with the current offset, and files_upload_session_finish with the last chunk and a CommitInfo naming the destination.",{"q":1546,"a":1615},"Create it once with the SDK's create-shared-link call. On later runs, catch the shared_link_already_exists error and read the existing link with sharing_list_shared_links, passing the path and direct_only=True.","upload an excel report to dropbox",null,{"breadcrumb":1619},[1620,1622,1625],{"name":1621,"item":657},"Home",{"name":1623,"item":1624},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox",{"title":1628,"description":1629},"Upload Excel Files to Dropbox with the Python SDK","Upload an Excel report to Dropbox with the dropbox Python SDK: refresh-token authentication for unattended jobs, files_upload with WriteMode.overwrite, upload sessions for files over 150 MB, shared links with shared_link_already_exists handling, and ApiError checks.","upload-an-excel-report-to-dropbox","automating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-dropbox\u002Findex",[1633],"publishing","how-to","--HJa9CBLysA-Bylnla9Bsc8cz6pirKJyA4_ZHyiy4o",[1637,1641],{"title":1638,"path":1639,"stem":1640,"children":-1},"Upload an Excel Report to Azure Blob Storage with Python","\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-azure-blob-storage","automating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-azure-blob-storage\u002Findex",{"title":1642,"path":1643,"stem":1644,"children":-1},"Upload an Excel Report to Google Cloud Storage","\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-google-cloud-storage","automating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fupload-an-excel-report-to-google-cloud-storage\u002Findex",[1646,1648],{"path":1647,"title":1623},"\u002Fautomating-reporting-workflows",{"path":1649,"title":22},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage",[],[1652,1655,1658,1661,1664,1667],{"path":1653,"title":1654,"kind":1634},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fread-an-excel-file-from-sharepoint-or-onedrive-into-pandas","Read an Excel File from SharePoint or OneDrive into pandas",{"path":1656,"title":1657,"kind":1634},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fshare-an-excel-file-in-onedrive-with-a-link-using-graph","Share an Excel File in OneDrive with a Link Using Graph",{"path":1659,"title":1660,"kind":1634},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Femail-a-download-link-instead-of-an-excel-attachment","Email a Download Link Instead of an Excel Attachment",{"path":1662,"title":1663,"kind":1634},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-an-excel-report-with-amazon-ses","Send an Excel Report with Amazon SES",{"path":1665,"title":1666,"kind":1634},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-an-html-report-from-excel-data-with-jinja2","Generate an HTML Report from Excel Data with Jinja2",{"path":1668,"title":1669,"kind":1634},"\u002Fautomating-reporting-workflows\u002Fpublishing-excel-reports-to-cloud-storage\u002Fname-version-and-archive-generated-excel-reports","Name, Version and Archive Generated Excel Reports",1790972138495]