Upload an Excel Report to Dropbox with Python
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 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 Publishing Excel Reports to Cloud Storage, and covers each.
Prerequisites
pip install dropbox pandas openpyxl
In the Dropbox App Console, create a scoped app with access to a single folder ("App folder") or to the whole account, and enable the permissions the job needs: files.content.write to upload and 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; weekly_sales.xlsx is the report being published.
Step 1 — Authenticate with a refresh token
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 refresh token instead, together with the app key and secret, and let the SDK exchange it for access tokens as needed:
import os
import dropbox
dbx = dropbox.Dropbox(
oauth2_refresh_token=os.environ["DROPBOX_REFRESH_TOKEN"],
app_key=os.environ["DROPBOX_APP_KEY"],
app_secret=os.environ["DROPBOX_APP_SECRET"],
)
print(type(dbx).__name__, "ready")
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 Store Credentials for Excel Report Scripts Safely.
Getting the refresh token is a one-time, interactive step: run the OAuth flow with token_access_type="offline", open the URL it prints, approve, and paste the code back:
from dropbox import DropboxOAuth2FlowNoRedirect
flow = DropboxOAuth2FlowNoRedirect(os.environ["DROPBOX_APP_KEY"], os.environ["DROPBOX_APP_SECRET"],
token_access_type="offline")
print("Open:", flow.start())
result = flow.finish(input("Code: ").strip())
print("refresh token:", result.refresh_token)
Step 2 — Upload, replacing last week's file
files_upload sends the bytes and a path. The mode decides what happens if a file already exists there: WriteMode.overwrite replaces it, which is what a report with a fixed name needs; the default, add, fails or renames:
from pathlib import Path
from dropbox.files import WriteMode
def upload(dbx, local_path, dropbox_path):
data = Path(local_path).read_bytes()
meta = dbx.files_upload(data, dropbox_path, mode=WriteMode.overwrite, mute=True)
return meta
meta = upload(dbx, "weekly_sales.xlsx", "/Reports/Sales/weekly_sales.xlsx")
print(meta.path_display, f"{meta.size:,} bytes", "rev", meta.rev)
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 rev identifies this one. Paths are case-insensitive in Dropbox but shown with the case you used, and must start with /.
Step 3 — Large files: upload sessions
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:
from dropbox.files import CommitInfo, UploadSessionCursor
CHUNK = 8 * 1024 * 1024
def upload_large(dbx, local_path, dropbox_path, chunk=CHUNK):
size = Path(local_path).stat().st_size
with open(local_path, "rb") as f:
start = dbx.files_upload_session_start(f.read(chunk))
cursor = UploadSessionCursor(session_id=start.session_id, offset=f.tell())
commit = CommitInfo(path=dropbox_path, mode=WriteMode.overwrite, mute=True)
while size - f.tell() > chunk:
dbx.files_upload_session_append_v2(f.read(chunk), cursor)
cursor.offset = f.tell()
return dbx.files_upload_session_finish(f.read(chunk), cursor, commit)
meta = upload_large(dbx, "weekly_sales.xlsx", "/Reports/Sales/weekly_sales_chunked.xlsx", chunk=2048)
print(meta.path_display, f"{meta.size:,} bytes")
The cursor's offset must equal the number of bytes sent so far, which 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.
Step 4 — Share a link, and reuse it next week
A shared link lets people without access to the folder open the report. Creating a link for a path that already has one, with sharing_create_shared_link_with_settings, raises shared_link_already_exists, which every weekly job hits on its second run. Catch it and fetch the existing link:
from dropbox.exceptions import ApiError
def shared_link(dbx, path):
try:
return dbx.sharing_create_shared_link_with_settings(path).url
except ApiError as err:
if err.error.is_shared_link_already_exists():
links = dbx.sharing_list_shared_links(path=path, direct_only=True).links
return links[0].url
raise
print("week 1:", shared_link(dbx, "/Reports/Sales/weekly_sales.xlsx"))
print("week 2:", shared_link(dbx, "/Reports/Sales/weekly_sales.xlsx"))
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 dl=1 instead of dl=0 makes it download the file directly, which suits links in automated emails.
Step 5 — Confirm what arrived
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 files_download on a sample of runs:
_, response = dbx.files_download("/Reports/Sales/weekly_sales.xlsx")
check = pd.read_excel(io.BytesIO(response.content))
print(check.shape)
Both the simple and the chunked upload delivered exactly the local file's bytes, and the workbook reads back with its four regions.
Performance notes
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 (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: max_retries_on_error (4 by default) retries server errors, and max_retries_on_rate_limit governs rate-limit responses — left at its default of 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 auto_content_hash=True default of the same constructor.
Common pitfalls
- Storing an access token. It expires in hours; store a refresh token with the app key and secret.
- The default write mode.
adddoes not replace the existing file; useWriteMode.overwritefor fixed report names. - Files over 150 MB with
files_upload. Use an upload session. - Creating a shared link every run. The second call fails; catch
shared_link_already_existsand list the existing link. - Missing scopes. An app without
files.content.writeorsharing.writegets an authorisation error on that call; scopes are set in the App Console, and the refresh token must be re-issued after changing them.
Conclusion
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 WriteMode.overwrite and mute=True, switch to upload sessions for large files, create a shared link once and reuse it by catching shared_link_already_exists, and check a sample of uploads by downloading them back.
Frequently asked questions
How do I upload a file to Dropbox with Python? Install the dropbox package, create a client with dropbox.Dropbox(...) and call files_upload(data, "/Folder/file.xlsx", mode=WriteMode.overwrite). The path must start with a slash.
Why does my Dropbox token stop working after a few hours? 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.
How do I upload files larger than 150 MB to Dropbox? 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.
How do I get a Dropbox shared link that stays the same?
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.
Related
- Up one level: Publishing Excel Reports to Cloud Storage — every destination for finished reports.
- Save Excel Reports to Google Drive with Python — the Google equivalent.
- Upload an Excel Report to SharePoint with Python — for Microsoft 365 teams.
- Retry a Failed Excel Report Job in Python — handling network failures around the upload.
More on publishing & sharing files
- How-toRead an Excel File from SharePoint or OneDrive into pandas
- How-toShare an Excel File in OneDrive with a Link Using Graph
- How-toEmail a Download Link Instead of an Excel Attachment
- How-toSend an Excel Report with Amazon SES
- How-toGenerate an HTML Report from Excel Data with Jinja2
- How-toName, Version and Archive Generated Excel Reports