Skip to main content

Resumable Upload

Python Version PyPI Version PyPI Downloads License

English | 한국어

A Python implementation of the TUS resumable upload protocol v1.0.0 for server and client, with zero runtime dependencies.

✨ Features

  • 🚀 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, parallel ones included
  • ✅ 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 Backends: SQLite by default; S3, Google Cloud Storage and Azure Blob Storage via optional extras (and extensible to your own)
  • 🔐 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

📦 Installation

# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install the package
uv pip install resumable-upload

Using pip

pip install resumable-upload

Optional extras

The core install has zero runtime dependencies. Opt into the features you need:

pip install "resumable-upload[async]"         # AsyncTusClient (httpx)
pip install "resumable-upload[s3]"            # AWS S3 storage backend
pip install "resumable-upload[gcs]"           # Google Cloud Storage backend
pip install "resumable-upload[azure]"         # Azure Blob Storage backend
pip install "resumable-upload[redis]"         # RedisLockBackend
pip install "resumable-upload[all-storage]"   # All cloud backends
pip install "resumable-upload[all]"           # Every optional feature

🚀 Quick Start

Basic Server

from 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()

Basic Client

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

# 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).

Checksum algorithms

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")

Client hooks and URL storage

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 flock
  • SQLiteURLStorage — durable DB, recommended for multi-process clients
  • InMemoryURLStorage — 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"])

ASGI (FastAPI, Starlette, Quart, etc.)

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.

Command-line Server & Client

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 (--resume to continue an interrupted one), inspect, download
resumable-upload upload big.bin --url http://host/files --resume
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.bin
# Auth headers (repeatable), --timeout and --insecure work on upload, info and download
resumable-upload info http://host/files/<id> --header "Authorization=Bearer $TOKEN"

Server flags: --host, --port, --base-path, --upload-dir, --db-path, --max-size, --max-chunk-size, --request-timeout, --upload-expiry, --cleanup-interval, --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, --lock-ttl, --lock-wait, --log-level. Run resumable-upload serve --help for details.

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. The bundled client does the same: TusClient/Uploader keep one connection for the HEAD + PATCH sequence, reconnect per request against an HTTP/1.0 or Connection: close server, and reopen a kept-alive socket the server has since dropped. 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.

Parallel chunk uploads

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.

Manual partial / final control

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"},
)

🔧 Advanced Usage

For detailed guides see the Advanced Usage section on the docs site:

📚 API Reference

Full API documentation is available on the docs site: Client, Server, Storage, Exceptions & Utilities.

Quick Reference

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

Key Parameters

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

lock_backend: TusServer installs an in-process InMemoryLockBackend by default, so concurrent PATCH/DELETE on one upload are serialized within a process — beyond lock_wait_seconds (5s) the loser gets 423 Locked. Multi-process or multi-node deployments must pass a RedisLockBackend; pass lock_backend=None to opt out, or use TusServerCore, which stays lock-free: lock backends

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

🔍 TUS Protocol Compliance

This library implements TUS protocol v1.0.0. Full compliance details: TUS Compliance.

Extensions

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.

Non-standard but supported

Feature Status
X-HTTP-Method-Override ✅ Implemented — POST rewrites to PATCH/DELETE/HEAD for environments that block those methods
423 Locked ✅ Implemented — returned when a LockBackend holder still owns the upload and lock_wait_seconds elapses
GET download endpoint ✅ Implemented — opt-in via enable_downloads; tusd-style serving of completed uploads

🧪 Testing

# 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)

Interoperability tests (local, opt-in)

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.

📖 Documentation

🤝 Contributing

Contributions are welcome! Please check out the Contributing Guide for guidelines.

📄 License

MIT License - see LICENSE file for details.

🙏 Acknowledgments

This library is inspired by the official TUS Python client and implements the TUS resumable upload protocol.

📞 Support

Metadata

Release files for resumable-upload 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for resumable-upload 0.3.0
File Size Uploaded
resumable_upload-0.3.0.tar.gz 178.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for resumable-upload 0.3.0
File Interpreter ABI Platform
resumable_upload-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 300.4 kB

Release files / resumable_upload-0.3.0.tar.gz

Download URL resumable_upload-0.3.0.tar.gz
Size 178.0 kB
Tags Source
SHA-256 checksum
How to use checksums
187a3ba5be297e50269586767c86487cd6c2f29f03cd476aa6804afa6f9099c1
BLAKE2b-256 checksum
How to use checksums
804bcff6933b98250c677e780bc9f8c35d96ed19cb9beaf3feb2c06b4126f584
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / resumable_upload-0.3.0-py3-none-any.whl

Download URL resumable_upload-0.3.0-py3-none-any.whl
Size 122.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f8643db557f41d94a24f7b9976fe2c32e28e4049829af95f7739a131db55b321
BLAKE2b-256 checksum
How to use checksums
5aac78779da7f9b23d393309dfa6ff7034a8be6a6df6582eec0e5a5c9bf1cd5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page