CLI¶
The package installs a resumable-upload console script that runs a TUS server with no Python boilerplate. Equivalent invocation: python -m resumable_upload. Try it without installing: uvx resumable-upload --help.
resumable-upload --version prints the installed version. Every command exits 0 on success and 1 with a one-line error: … on stderr for anything the user can act on (missing file, port in use, connection refused, HTTP errors), 2 for bad arguments.
resumable-upload serve¶
serve speaks HTTP/1.1 with keep-alive, so a chunked upload reuses one connection instead of paying a TCP (and TLS) handshake per PATCH. resumable-upload upload (the sync Uploader) keeps that connection open across chunks and falls back to one connection per request against HTTP/1.0 or Connection: close servers. Every response is framed with Content-Length; a request rejected before its body was read (the --max-size / --max-chunk-size gates, malformed chunked encoding) is answered with Connection: close so the unread body can never be parsed as the next request. The same applies to a body the server never reads — on HEAD, GET, DELETE or OPTIONS — and to a request carrying both Transfer-Encoding and Content-Length; a Transfer-Encoding other than chunked, or a Content-Length that is repeated with different values or is not plain digits, are rejected (400/501). It handles each connection on its own thread, so concurrent PATCHes, --parallel uploads and lock contention behave the way they will in production. SIGINT/SIGTERM stop accepting new connections, close idle keep-alive connections right away, and wait for in-flight requests to finish, bounded by --request-timeout (30s default) — set it below your orchestrator's grace period so a stalled client can't push the drain into a SIGKILL. Threads are one-per-connection and uncapped, so put the bundled server behind a reverse proxy rather than exposing it directly.
Flags¶
| Flag | Default | Description |
|---|---|---|
--host |
0.0.0.0 |
Bind host |
--port |
8080 |
Bind port |
--base-path |
/files |
URL base path for uploads |
--upload-dir |
./uploads |
Directory for uploaded files |
--db-path |
./uploads.db |
SQLite database path |
--enable-downloads |
off | Serve completed uploads via GET (tusd-style download endpoint) |
--behind-proxy |
off | Build absolute Location URLs from X-Forwarded-Proto/X-Forwarded-Host (falls back to Host, then relative) |
--location-base-url |
unset | Fixed absolute prefix for Location URLs (e.g. https://cdn.example) |
--disable-termination |
off | Reject client DELETEs with 405 and drop termination from Tus-Extension |
--disable-concatenation |
off | Reject Upload-Concat requests with 400 and drop the concatenation extensions |
--max-size |
0 |
Max upload size in bytes (0 = unlimited) |
--max-chunk-size |
0 |
Max single PATCH size in bytes (0 = unlimited) |
--request-timeout |
30 |
Socket read timeout in seconds; also caps how long a stalled connection delays a graceful shutdown |
--upload-expiry |
unset | Upload expiry in seconds (unset = no expiry) |
--cleanup-interval |
60 |
Minimum seconds between expired-upload cleanup runs |
--cors-origin |
unset | Access-Control-Allow-Origin value (unset = no CORS) |
--cors-credentials |
off | Send Access-Control-Allow-Credentials; a * origin is echoed per request |
--cors-max-age |
unset | Access-Control-Max-Age (seconds) on preflight responses |
--checksum-algorithms |
sha1 |
Comma-separated Upload-Checksum algorithms (e.g. sha1,sha256,sha512,md5) |
--log-level |
INFO |
One of DEBUG, INFO, WARNING, ERROR |
--metrics-path |
unset | Path to expose Prometheus-text metrics on (unset = disabled) |
--lock-backend |
memory |
One of none, memory, redis |
--redis-url |
unset | Redis URL, e.g., redis://localhost:6379/0 (required when --lock-backend=redis) |
--lock-ttl |
60 |
Lock TTL in seconds. Also the bound on the guarantee: a chunk write that outruns it can be joined by a second writer — raise it above your slowest expected chunk write. Ignored with --lock-backend=none |
--lock-wait |
5 |
How long to wait for a contended lock before returning 423 Locked. Ignored with --lock-backend=none |
Examples¶
# Plain server
resumable-upload serve --port 8080
# With Prometheus metrics and an in-memory lock
resumable-upload serve --metrics-path /metrics
# Multi-instance deployment with Redis-backed locks
pip install resumable-upload[redis]
resumable-upload serve \
--port 8080 \
--metrics-path /metrics \
--lock-backend redis \
--redis-url redis://redis:6379/0
# Deny gigantic chunks
resumable-upload serve --max-chunk-size $((50 * 1024 * 1024))
Client commands¶
The same console script also uploads, downloads, and inspects uploads against any TUS server — no Python needed.
# Upload; prints the upload URL (progress line on stderr when it is a terminal)
resumable-upload upload big.bin --url http://host/files
# Resumable: remembers the upload URL in ./.tus_urls.json, so re-running the
# same command after an interruption continues where it stopped
resumable-upload upload big.bin --url http://host/files --resume
# Split into concurrent partials and merge server-side (concatenation)
resumable-upload upload big.bin --url http://host/files --parallel 4
# Attach metadata (repeatable); filename is added automatically
resumable-upload upload report.pdf --url http://host/files --metadata title="Q3"
# Inspect an upload (HEAD): offset, length, complete, metadata
resumable-upload info http://host/files/<id>
# Download a completed upload (needs the server's GET endpoint)
resumable-upload download http://host/files/<id> -o out.bin
# Servers behind auth: --header (repeatable) is accepted by upload, info and download
resumable-upload upload big.bin --url http://host/files --header "Authorization=Bearer $TOKEN"
upload flags¶
| Flag | Default | Description |
|---|---|---|
--url |
required | TUS creation endpoint |
--chunk-size |
4194304 |
Chunk size in bytes (4 MB) |
--parallel |
1 |
Concurrent partial uploads (concatenation) |
--metadata KEY=VALUE |
— | Upload metadata, repeatable |
--metadata-encoding |
utf-8 |
Text encoding applied to metadata values before base64 |
--max-retries |
3 |
Retry attempts per chunk (network errors, 5xx, 408/423/429) |
--retry-delay |
1.0 |
Base retry delay in seconds, doubling each attempt (cap 60s) |
--override-patch-method |
off | Send PATCH as POST + X-HTTP-Method-Override for proxies that block PATCH |
--request-id |
off | Attach a fresh X-Request-ID to every request |
--checksum |
sha1 |
One of md5, sha1, sha256, sha512, or none to disable |
--resume |
off | Remember upload URLs in ./.tus_urls.json (keyed by file fingerprint) so re-running on the same file resumes; a URL the server has forgotten is dropped and the upload recreated. Works with --parallel too (partial URLs are remembered until the merge) |
--no-progress |
off | Suppress the progress line (already off when stderr is not a terminal) |
Flags shared by upload, info and download¶
| Flag | Default | Description |
|---|---|---|
--header KEY=VALUE |
— | Extra request header, repeatable (e.g. Authorization=Bearer TOKEN). download drops Authorization/Cookie/Proxy-Authorization when a redirect changes host, scheme or port |
--timeout |
30 |
Per-request socket timeout in seconds |
--insecure |
off | Skip TLS certificate verification (self-signed or internal CAs) |
Every scalar option of TusServer and TusClient has a CLI flag; tests/test_cli.py::TestCLIParity fails the build when a new constructor option is added without one.
Progress goes to stderr, so URL=$(resumable-upload upload …) captures just the URL. An unsupported --checksum for the server fails immediately with its 400 rather than retrying.
The CLI handles SIGINT / SIGTERM cleanly and shuts the HTTP server down before exiting.