Skip to main content

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

Project description

Resumable Upload

Python Version PyPI Version 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
  • Data Integrity: Optional SHA1 checksum verification
  • 🔁 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
  • 🎯 TUS Protocol Compliant: Implements TUS v1.0.0 core protocol with creation, termination, and checksum extensions

📦 Installation

Using uv (Recommended)

# 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

🚀 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

Run a TUS server from the shell without writing any Python:

# Console script (installed via pip/uv)
resumable-upload serve --host 0.0.0.0 --port 8080 --upload-dir ./uploads

# Or via module invocation
python -m resumable_upload serve --port 8080

Flags: --host, --port, --base-path, --upload-dir, --db-path, --max-size, --max-chunk-size, --upload-expiry, --cors-origin, --log-level. Run resumable-upload serve --help for details.

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

TusServer: storage, base_path (default /files), max_size, upload_expiry, cors_allow_origins, request_timeout (default 30s — Slowloris protection)

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
termination ✅ Implemented
checksum ✅ Implemented (SHA1)
expiration ✅ Implemented
concatenation ✅ Implemented (SQLite / S3 / GCS / Azure)

Note: TUS Upload-Checksum uses SHA1 as required by the spec. The internal client-side fingerprint for cross-session resume uses SHA-256 and is not part of the TUS protocol.

Non-standard but supported

Feature Status
X-HTTP-Method-Override ✅ Implemented — POST rewrites to PATCH/DELETE/HEAD for environments that block those methods

🧪 Testing

Using uv (Recommended)

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

📖 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

resumable_upload-0.1.0.tar.gz (86.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

resumable_upload-0.1.0-py3-none-any.whl (92.7 kB view details)

Uploaded Python 3

File details

Details for the file resumable_upload-0.1.0.tar.gz.

File metadata

  • Download URL: resumable_upload-0.1.0.tar.gz
  • Upload date:
  • Size: 86.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for resumable_upload-0.1.0.tar.gz
Algorithm Hash digest
SHA256 09d37dc1bee72faa1d79369505e90f1929a0d0895b6b4c728d2f8d68aaac466e
MD5 9ff462077a8a8c03b364debdf9d4efd4
BLAKE2b-256 8c9d9953e3badd0cd52974fab26fff9174af1456c03a8d3476b08b5e7a591306

See more details on using hashes here.

Provenance

The following attestation bundles were made for resumable_upload-0.1.0.tar.gz:

Publisher: publish.yml on injaeryou/resumable-upload

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file resumable_upload-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for resumable_upload-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 aadab4753f72ae6974141a1df15ea8365281a0701626deb7696f5688bcb24a04
MD5 27d14240103880d07d6b5ca734c4c23d
BLAKE2b-256 342e96ca14c5a3190b8e6118837857ee167f6a51f5fb5a2352b8f1c92afa4904

See more details on using hashes here.

Provenance

The following attestation bundles were made for resumable_upload-0.1.0-py3-none-any.whl:

Publisher: publish.yml on injaeryou/resumable-upload

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page