English | νκ΅μ΄
A Python implementation of the TUS resumable upload protocol v1.0.0 for server and client, with zero runtime dependencies.
- π Zero Dependencies: Built using Python standard library only (no external dependencies for core functionality)
- π¦ Server & Client: Complete implementation of both sides
- π Resume Capability: Automatically resume interrupted uploads
- β
Data Integrity: Per-chunk checksums (
sha1/sha256/sha512/md5), sent as header or HTTP trailer - π Retry Logic: Built-in automatic retry with exponential backoff
- π Progress Tracking: Detailed upload progress callbacks with stats
- π Web Framework Support: Integration examples for Flask, FastAPI, and Django
- π Python 3.9+: Supports Python 3.9 through 3.14
- πͺ Storage Backend: SQLite-based storage (extensible to other backends)
- π TLS Support: Certificate verification control and mTLS authentication
- π URL Storage: Persist upload URLs across sessions
- β¬οΈ Download Endpoint: Opt-in tusd-style GET serving of completed uploads with safe headers
- π― TUS Protocol Compliant: TUS v1.0.0 core plus every extension β creation, creation-with-upload, creation-defer-length, termination, checksum, expiration, concatenation, and concatenation-unfinished
# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install the package
uv pip install resumable-uploadpip install resumable-uploadfrom http.server import HTTPServer
from resumable_upload import TusServer, TusHTTPRequestHandler, SQLiteStorage
# Create storage backend
storage = SQLiteStorage(db_path="uploads.db", upload_dir="uploads")
# Create TUS server
tus_server = TusServer(storage=storage, base_path="/files")
# Create HTTP handler
class Handler(TusHTTPRequestHandler):
pass
Handler.tus_server = tus_server
# Start server
server = HTTPServer(("0.0.0.0", 8080), Handler)
print("Server running on http://localhost:8080")
server.serve_forever()from resumable_upload import TusClient
# Create client
client = TusClient("http://localhost:8080/files")
# Upload file with progress callback
from resumable_upload import UploadStats
def progress(stats: UploadStats):
print(
f"Progress: {stats.progress_percent:.1f}% | "
f"{stats.uploaded_bytes}/{stats.total_bytes} bytes | "
f"Speed: {stats.upload_speed_mbps:.2f} MB/s"
)
upload_url = client.upload_file(
"large_file.bin", metadata={"filename": "large_file.bin"}, progress_callback=progress
)
print(f"Upload complete: {upload_url}")# Async client β pip install "resumable-upload[async]"
import asyncio
from resumable_upload import AsyncTusClient
async def main():
async with AsyncTusClient("http://localhost:8080/files") as client:
url = await client.upload_file("large_file.bin")
asyncio.run(main())Every sync TusClient method has an awaitable equivalent (upload, resume, delete, concatenation, parallel_uploads=N, protocol queries).
Pick any subset of sha1, sha256, sha512, md5 to advertise and validate:
TusServer(storage=..., checksum_algorithms=("sha1", "sha256"))Client picks which one to send:
TusClient("...", checksum="sha256")Observability + domain-specific retry gating:
def before(method, url, headers):
print(f"-> {method} {url}")
def after(method, url, status):
print(f"<- {method} {status}")
def should_retry(err, attempt):
return not isinstance(err, PermissionError)
client = TusClient(
"...",
before_request=before,
after_response=after,
on_should_retry=should_retry,
)Three URL-storage backends ship (all implement the same URLStorage ABC):
FileURLStorageβ durable JSON file, multi-process safe via flockSQLiteURLStorageβ durable DB, recommended for multi-process clientsInMemoryURLStorageβ fast, non-durable (tests, short sessions)
Look up a resumable upload by file:
previous = client.find_previous_uploads("big.bin")
if previous:
client.resume_upload("big.bin", previous[0]["upload_url"])Mount a TUS server as an ASGI application:
from fastapi import FastAPI
from resumable_upload import SQLiteStorage, TusServer
from resumable_upload.asgi import TusASGIApp
app = FastAPI()
tus = TusServer(storage=SQLiteStorage(), base_path="/files")
app.mount("/files", TusASGIApp(tus))The adapter awaits TusServer.handle_request_async directly. Storage backends that keep the default *_async implementations inherit asyncio.to_thread-based wrappers, so the event loop stays free with no rewrite required. Storage backends that override *_async with native async I/O run non-blocking end-to-end.
Run a TUS server, or upload/download/inspect against one, from the shell β no Python needed:
# Server
resumable-upload serve --host 0.0.0.0 --port 8080 --upload-dir ./uploads
# Client: upload (resumable, progress line), inspect, download
resumable-upload upload big.bin --url http://host/files --parallel 4
resumable-upload info http://host/files/<id>
resumable-upload download http://host/files/<id> -o out.binServer flags: --host, --port, --base-path, --upload-dir, --db-path, --max-size, --max-chunk-size, --upload-expiry, --cors-origin, --cors-credentials, --cors-max-age, --checksum-algorithms, --enable-downloads, --behind-proxy, --location-base-url, --disable-termination, --disable-concatenation, --metrics-path, --lock-backend, --redis-url, --log-level. Run resumable-upload serve --help for details.
For large files over high-bandwidth connections, split the file into N concurrent partial uploads and merge them server-side via the concatenation extension:
client = TusClient("http://localhost:8080/files", chunk_size=1024 * 1024)
url = client.upload_file("large.bin", parallel_uploads=4)Requires a server that implements the TUS concatenation extension (this library does). Compatible with tus-js-client's parallelUploads option.
For advanced workflows (e.g., resumable uploads split across devices or sessions), use the partial / final primitives directly:
url1 = client.create_partial_upload("part1.bin")
url2 = client.create_partial_upload("part2.bin")
final_url = client.create_final_upload(
partial_urls=[url1, url2],
metadata={"filename": "merged.bin"},
)For detailed guides see the Advanced Usage section on the docs site:
- Automatic retry with exponential backoff and
on_should_retrygating β Retry & Error Handling - Resume interrupted uploads (in-session and cross-session) β Resume & Partial Uploads
- Concatenation extension and
parallel_uploads=Nβ Concatenation & Parallel Uploads - Tracing and retry hooks β Observability & Retry Gating
- Low-level chunk control via
Uploader+ cancellation withstop_eventβ Low-Level Uploader - Web framework integration: Flask, FastAPI, Django, generic ASGI
- Operations: CLI, Metrics, Distributed Locks
Full API documentation is available on the docs site: Client, Server, Storage, Exceptions & Utilities.
| Class | Import | Purpose |
|---|---|---|
TusClient |
from resumable_upload import TusClient |
Upload files via TUS protocol |
TusServer |
from resumable_upload import TusServer |
Serve TUS uploads (framework-agnostic) |
TusHTTPRequestHandler |
from resumable_upload import TusHTTPRequestHandler |
Handler for Python's built-in HTTPServer |
SQLiteStorage |
from resumable_upload import SQLiteStorage |
SQLite + filesystem storage backend |
FileURLStorage |
from resumable_upload import FileURLStorage |
JSON file-based URL persistence |
Uploader |
from resumable_upload.client.uploader import Uploader |
Low-level chunk-by-chunk control |
TusClient: url, chunk_size (default 1 MB), checksum (SHA1, default True), max_retries (default 3), retry_delay (default 1.0s, exponential backoff capped at 60s), timeout (default 30s), store_url / url_storage (cross-session resume), verify_tls_cert, headers β plus request hooks, PATCH-over-POST tunneling, request-ID injection, and more: client API reference
TusServer: storage, base_path (default /files), max_size, upload_expiry, cors_allow_origins, request_timeout (default 30s β Slowloris protection) β plus proxy/Location config, CORS credentials, downloads, checksum trailers, feature toggles, and more: server API reference
SQLiteStorage: db_path (default uploads.db), upload_dir (default uploads) β thread-safe via per-upload lock; process-safe via fcntl.flock
FileURLStorage: storage_path (default .tus_urls.json) β thread-safe via threading.Lock; process-safe via fcntl.flock
This library implements TUS protocol v1.0.0. Full compliance details: TUS Compliance.
| Extension | Status |
|---|---|
| core | β Implemented |
| creation | β Implemented |
| creation-with-upload | β Implemented |
| creation-defer-length | β Implemented |
| termination | β Implemented |
| checksum | β
Implemented (sha1/sha256/sha512/md5, header or trailer) |
| expiration | β Implemented |
| concatenation | β Implemented (SQLite / S3 / GCS / Azure) |
| concatenation-unfinished | β Implemented (SQLite) |
Note: the internal client-side fingerprint for cross-session resume uses SHA-256 and is not part of the TUS protocol. Full header-by-header details: compliance matrix.
| Feature | Status |
|---|---|
X-HTTP-Method-Override |
β Implemented β POST rewrites to PATCH/DELETE/HEAD for environments that block those methods |
# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment and install dependencies
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install all dependencies (dev and test)
make install
# Run minimal tests (excluding web frameworks)
make test-minimal
# Run all tests (including web frameworks)
make test
# Or use Makefile for convenience
make lint # Run linting
make format # Format code
make test-minimal # Run minimal tests
make test # Run all tests
make test-all-versions # Test on all Python versions (3.9-3.14) - requires tox
make ci # Run full CI checks (lint + format + test)
make interop # Cross-implementation interop tests (see below)make interop verifies real wire compatibility against the TUS ecosystem
(in tests/test_interop.py). These run locally only β they are not part
of make ci, and each pairing skips cleanly when its prerequisite is missing:
| Pairing | Covers | Prerequisite |
|---|---|---|
| our server β our client | upload, resume, concatenation, download | none (always runs) |
| our client β tusd server | upload, concatenation, termination | tusd on PATH (or TUSD_BIN=<path>) |
| our server β tus-js-client (Node) | upload, HEAD, download, termination | node + npm install in tests/interop/ |
our server β tus-py-client (tuspy) |
upload, resume, checksum | uv pip install tuspy |
tusd ships no client binary (server only), so the second reference client
against our server is the tus project's Python client. Each pairing exercises
only the features the counterpart implements β e.g. tusd advertises neither
checksum nor expiration, and tus-py-client exposes no termination call, so
those combinations are intentionally skipped.
- Docs site: injaeryou.github.io/resumable-upload
- English README: README.md
- νκ΅μ΄ README: README.ko.md
- Advanced Usage: advanced-usage/retry
- Full API Reference: api-reference/client
- TUS Protocol Compliance: compliance
Contributions are welcome! Please check out the Contributing Guide for guidelines.
MIT License - see LICENSE file for details.
This library is inspired by the official TUS Python client and implements the TUS resumable upload protocol.
- π« Issues: GitHub Issues
- π Documentation: injaeryou.github.io/resumable-upload
- π Star us on GitHub!