{"id":37084548,"url":"https://github.com/sts07142/resumable-upload","last_synced_at":"2026-01-14T10:22:22.349Z","repository":{"id":322935329,"uuid":"1091492771","full_name":"sts07142/resumable-upload","owner":"sts07142","description":"A Python implementation of the [TUS resumable upload protocol](https://tus.io/) v1.0.0 for server and client, with zero runtime dependencies.","archived":false,"fork":false,"pushed_at":"2025-11-25T04:52:50.000Z","size":76,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-27T12:34:36.839Z","etag":null,"topics":["client","python","resumable","resumable-upload","server","tus"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sts07142.png","metadata":{"files":{"readme":"README.ko.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-07T05:00:32.000Z","updated_at":"2025-11-25T04:51:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sts07142/resumable-upload","commit_stats":null,"previous_names":["sts07142/resumable-upload"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sts07142/resumable-upload","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sts07142%2Fresumable-upload","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sts07142%2Fresumable-upload/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sts07142%2Fresumable-upload/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sts07142%2Fresumable-upload/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sts07142","download_url":"https://codeload.github.com/sts07142/resumable-upload/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sts07142%2Fresumable-upload/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28416992,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:18:03.274Z","status":"ssl_error","status_checked_at":"2026-01-14T10:16:11.865Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","python","resumable","resumable-upload","server","tus"],"created_at":"2026-01-14T10:22:21.650Z","updated_at":"2026-01-14T10:22:22.341Z","avatar_url":"https://github.com/sts07142.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Resumable Upload\n\n[![Python Version](https://img.shields.io/pypi/pyversions/resumable-upload.svg)](https://pypi.org/project/resumable-upload/)\n[![PyPI Version](https://img.shields.io/pypi/v/resumable-upload.svg)](https://pypi.org/project/resumable-upload/)\n[![License](https://img.shields.io/pypi/l/resumable-upload.svg)](https://github.com/sts07142/resumable-upload/blob/main/LICENSE)\n\n**English** | [한국어](README.ko.md)\n\nPython 서버와 클라이언트를 위한 [TUS 재개 가능 업로드 프로토콜](https://tus.io/) v1.0.0의 구현체로, 런타임 의존성이 없습니다.\n\n## ✨ 특징\n\n- 🚀 **의존성 없음**: Python 표준 라이브러리만 사용 (코어 기능에 외부 의존성 없음)\n- 📦 **서버 \u0026 클라이언트**: 양쪽 모두 완전한 구현\n- 🔄 **재개 기능**: 중단된 업로드 자동 재개\n- ✅ **데이터 무결성**: 선택적 SHA1 체크섬 검증\n- 🔁 **재시도 로직**: 지수 백오프를 사용한 내장 자동 재시도\n- 📊 **진행률 추적**: 통계와 함께 상세한 업로드 진행률 콜백\n- 🌐 **웹 프레임워크 지원**: Flask, FastAPI, Django 통합 예제\n- 🐍 **Python 3.9+**: Python 3.9부터 3.14까지 지원\n- 🏪 **스토리지 백엔드**: SQLite 기반 스토리지 (다른 백엔드로 확장 가능)\n- 🔐 **TLS 지원**: 인증서 검증 제어 및 mTLS 인증\n- 📝 **URL 스토리지**: 세션 간 업로드 URL 유지\n- 🎯 **TUS 프로토콜 준수**: TUS v1.0.0 코어 프로토콜과 creation, termination, checksum 확장 구현\n\n## 📦 설치\n\n### uv 사용 (권장)\n\n```bash\n# uv가 설치되어 있지 않다면 설치\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n\n# 패키지 설치\nuv pip install resumable-upload\n```\n\n### pip 사용\n\n```bash\npip install resumable-upload\n```\n\n## 🚀 빠른 시작\n\n### 기본 서버\n\n```python\nfrom http.server import HTTPServer\nfrom resumable_upload import TusServer, TusHTTPRequestHandler, SQLiteStorage\n\n# 스토리지 백엔드 생성\nstorage = SQLiteStorage(db_path=\"uploads.db\", upload_dir=\"uploads\")\n\n# TUS 서버 생성\ntus_server = TusServer(storage=storage, base_path=\"/files\")\n\n# HTTP 핸들러 생성\nclass Handler(TusHTTPRequestHandler):\n    pass\n\nHandler.tus_server = tus_server\n\n# 서버 시작\nserver = HTTPServer((\"0.0.0.0\", 8080), Handler)\nprint(\"서버가 http://localhost:8080 에서 실행 중입니다\")\nserver.serve_forever()\n```\n\n### 기본 클라이언트\n\n```python\nfrom resumable_upload import TusClient\n\n# 클라이언트 생성\nclient = TusClient(\"http://localhost:8080/files\")\n\n# 진행률 콜백과 함께 파일 업로드\nfrom resumable_upload import UploadStats\n\ndef progress(stats: UploadStats):\n    print(f\"진행률: {stats.progress_percent:.1f}% | \"\n          f\"{stats.uploaded_bytes}/{stats.total_bytes} 바이트 | \"\n          f\"속도: {stats.upload_speed_mbps:.2f} MB/s\")\n\nupload_url = client.upload_file(\n    \"large_file.bin\",\n    metadata={\"filename\": \"large_file.bin\"},\n    progress_callback=progress\n)\n\nprint(f\"업로드 완료: {upload_url}\")\n```\n\n## 🔧 고급 사용법\n\n### 자동 재시도가 있는 클라이언트\n\n```python\nfrom resumable_upload import TusClient\n\n# 재시도 기능을 갖춘 클라이언트 생성 (기본적으로 활성화됨)\nclient = TusClient(\n    \"http://localhost:8080/files\",\n    chunk_size=1.5*1024*1024,  # 1.5MB 청크 (float 허용)\n    max_retries=3,         # 최대 3회 재시도 (기본값: 3)\n    retry_delay=1.0,       # 재시도 간 초기 지연 시간 (기본값: 1.0)\n    checksum=True          # 체크섬 검증 활성화\n)\n\n# UploadStats를 사용한 진행률 추적과 함께 업로드\nfrom resumable_upload import UploadStats\n\ndef progress_callback(stats: UploadStats):\n    print(f\"진행률: {stats.progress_percent:.1f}% | \"\n          f\"속도: {stats.upload_speed_mbps:.2f} MB/s | \"\n          f\"예상 시간: {stats.eta_seconds:.0f}초 | \"\n          f\"청크: {stats.chunks_completed}/{stats.total_chunks} | \"\n          f\"재시도: {stats.chunks_retried}\")\n\nupload_url = client.upload_file(\n    \"large_file.bin\",\n    metadata={\"filename\": \"large_file.bin\"},\n    progress_callback=progress_callback\n)\n```\n\n### 중단된 업로드 재개\n\n```python\n# 중단된 업로드 재개\nupload_url = client.resume_upload(\"large_file.bin\", upload_url)\n```\n\n### 세션 간 재개 가능성\n\n```python\nfrom resumable_upload import TusClient, FileURLStorage\n\n# 세션 간 재개를 위한 URL 스토리지 활성화\nstorage = FileURLStorage(\".tus_urls.json\")\nclient = TusClient(\n    \"http://localhost:8080/files\",\n    store_url=True,\n    url_storage=storage\n)\n\n# 중단 후 재시작 시 자동으로 재개됩니다\nupload_url = client.upload_file(\"large_file.bin\")\n```\n\n### 파일 스트림 사용\n\n```python\n# 경로 대신 파일 스트림에서 업로드\nwith open(\"file.bin\", \"rb\") as fs:\n    client = TusClient(\"http://localhost:8080/files\")\n    upload_url = client.upload_file(\n        file_stream=fs,\n        metadata={\"filename\": \"file.bin\"}\n    )\n```\n\n### 예외 처리\n\n```python\nfrom resumable_upload import TusClient\nfrom resumable_upload.exceptions import TusCommunicationError, TusUploadFailed\n\nclient = TusClient(\"http://localhost:8080/files\")\n\ntry:\n    upload_url = client.upload_file(\"file.bin\")\nexcept TusCommunicationError as e:\n    print(f\"통신 오류: {e.message}, 상태: {e.status_code}\")\nexcept TusUploadFailed as e:\n    print(f\"업로드 실패: {e.message}\")\n```\n\n## 🌐 웹 프레임워크 통합\n\n### Flask\n\n```python\nfrom flask import Flask, request, make_response\nfrom resumable_upload import TusServer, SQLiteStorage\n\napp = Flask(__name__)\ntus_server = TusServer(storage=SQLiteStorage())\n\n@app.route('/files', methods=['OPTIONS', 'POST'])\n@app.route('/files/\u003cupload_id\u003e', methods=['HEAD', 'PATCH', 'DELETE'])\ndef handle_upload(upload_id=None):\n    status, headers, body = tus_server.handle_request(\n        request.method, request.path, dict(request.headers), request.get_data()\n    )\n    response = make_response(body, status)\n    for key, value in headers.items():\n        response.headers[key] = value\n    return response\n```\n\n### FastAPI\n\n```python\nfrom fastapi import FastAPI, Request, Response\nfrom resumable_upload import TusServer, SQLiteStorage\n\napp = FastAPI()\ntus_server = TusServer(storage=SQLiteStorage())\n\n@app.post(\"/files\")\n@app.head(\"/files/{upload_id}\")\n@app.patch(\"/files/{upload_id}\")\n@app.delete(\"/files/{upload_id}\")\nasync def handle_upload(request: Request):\n    body = await request.body()\n    status, headers, response_body = tus_server.handle_request(\n        request.method, request.url.path, dict(request.headers), body\n    )\n    return Response(content=response_body, status_code=status, headers=headers)\n```\n\n### Django\n\n```python\nfrom django.http import HttpResponse\nfrom django.views.decorators.csrf import csrf_exempt\nfrom resumable_upload import TusServer, SQLiteStorage\n\ntus_server = TusServer(storage=SQLiteStorage())\n\n@csrf_exempt\ndef tus_upload_view(request, upload_id=None):\n    headers = {key[5:].replace('_', '-'): value\n               for key, value in request.META.items() if key.startswith('HTTP_')}\n    status, response_headers, response_body = tus_server.handle_request(\n        request.method, request.path, headers, request.body\n    )\n    response = HttpResponse(response_body, status=status)\n    for key, value in response_headers.items():\n        response[key] = value\n    return response\n```\n\n## 📚 API 참조\n\n### TusClient\n\n파일 업로드를 위한 메인 클라이언트 클래스입니다.\n\n**매개변수:**\n\n- `url` (str): TUS 서버 기본 URL\n- `chunk_size` (int): 각 업로드 청크의 크기(바이트) (기본값: 1MB)\n- `checksum` (bool): SHA1 체크섬 검증 활성화 (기본값: True)\n- `store_url` (bool): 재개를 위한 업로드 URL 저장 (기본값: False)\n- `url_storage` (URLStorage): URL 스토리지 백엔드 (기본값: FileURLStorage)\n- `verify_tls_cert` (bool): TLS 인증서 검증 (기본값: True)\n- `metadata_encoding` (str): 메타데이터 인코딩 (기본값: \"utf-8\")\n- `headers` (dict): 모든 요청에 포함할 커스텀 헤더 (기본값: {})\n- `max_retries` (int): 청크당 최대 재시도 횟수 (기본값: 3)\n- `retry_delay` (float): 재시도 간 기본 지연 시간(초) (기본값: 1.0)\n\n**메서드:**\n\n- `upload_file(file_path=None, file_stream=None, metadata={}, progress_callback=None, stop_at=None)`: 파일 업로드\n  - `progress_callback`: `UploadStats` 객체를 받는 콜백 함수\n- `resume_upload(file_path, upload_url, progress_callback=None)`: 중단된 업로드 재개\n  - `progress_callback`: `UploadStats` 객체를 받는 콜백 함수\n- `delete_upload(upload_url)`: 업로드 삭제\n- `get_upload_info(upload_url)`: 업로드 정보 조회 (offset, length, complete, metadata)\n- `get_metadata(upload_url)`: 업로드 메타데이터 조회\n- `get_server_info()`: 서버 기능 및 정보 조회\n- `update_headers(headers)`: 런타임에 커스텀 헤더 업데이트\n- `get_headers()`: 현재 커스텀 헤더 가져오기\n- `create_uploader(file_path=None, file_stream=None, upload_url=None, metadata={}, chunk_size=None)`: Uploader 인스턴스 생성\n\n### TusClient 재시도 설정\n\n`TusClient`는 지수 백오프를 사용한 내장 재시도 기능을 포함합니다.\n\n**재시도 매개변수:**\n\n- `max_retries` (int): 청크당 최대 재시도 횟수 (기본값: 3)\n- `retry_delay` (float): 재시도 간 기본 지연 시간(초) (기본값: 1.0)\n  - 지수 백오프 사용: delay = retry_delay * (2^attempt)\n- 재시도를 비활성화하려면 `max_retries=0` 설정\n\n### TusServer\n\nTUS 프로토콜의 서버 구현입니다.\n\n**매개변수:**\n\n- `storage` (Storage): 업로드를 관리하기 위한 스토리지 백엔드\n- `base_path` (str): TUS 엔드포인트의 기본 경로 (기본값: \"/files\")\n- `max_size` (int): 최대 업로드 크기(바이트) (기본값: None)\n\n**메서드:**\n\n- `handle_request(method, path, headers, body)`: TUS 프로토콜 요청 처리\n\n### SQLiteStorage\n\nSQLite 기반 스토리지 백엔드입니다.\n\n**매개변수:**\n\n- `db_path` (str): SQLite 데이터베이스 파일 경로 (기본값: \"uploads.db\")\n- `upload_dir` (str): 업로드 파일을 저장할 디렉토리 (기본값: \"uploads\")\n\n## 🔍 TUS 프로토콜 준수\n\n이 라이브러리는 다음 확장 기능을 포함한 TUS 프로토콜 버전 1.0.0을 구현합니다:\n\n- ✅ **코어 프로토콜**: 기본 업로드 기능 (POST, HEAD, PATCH)\n- ✅ **Creation**: POST를 통한 업로드 생성\n- ✅ **Termination**: DELETE를 통한 업로드 삭제\n- ✅ **Checksum**: SHA1 체크섬 검증\n\n### 순차적 업로드 요구사항\n\n**중요:** TUS 프로토콜은 청크를 **순차적으로** 업로드해야 하며, 병렬로 업로드할 수 없습니다.\n\n**왜 순차적인가?**\n\n1. **오프셋 검증**: 각 청크는 올바른 바이트 오프셋에 업로드되어야 합니다\n2. **데이터 무결성**: 경쟁 조건으로 인한 데이터 손상을 방지합니다\n3. **재개 기능**: 수신된 바이트 추적을 간단하고 신뢰할 수 있게 만듭니다\n4. **프로토콜 준수**: TUS 사양은 `Upload-Offset`이 현재 위치와 일치해야 한다고 요구합니다\n\n```python\n# ❌ 병렬 업로드는 충돌을 일으킵니다:\n# 청크 1 (오프셋 0)    → 성공\n# 청크 3 (오프셋 2048) → 실패 (409: 예상 오프셋 1024)\n# 청크 2 (오프셋 1024) → 실패 (409: 오프셋 불일치)\n\n# ✅ 순차적 업로드는 올바르게 작동합니다:\n# 청크 1 (오프셋 0)    → 성공 (오프셋 이제 1024)\n# 청크 2 (오프셋 1024) → 성공 (오프셋 이제 2048)\n# 청크 3 (오프셋 2048) → 성공 (오프셋 이제 3072)\n```\n\n## 🧪 테스트\n\n### uv 사용 (권장)\n\n```bash\n# uv가 설치되어 있지 않다면 설치\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n\n# 가상 환경 생성 및 의존성 설치\nuv venv\nsource .venv/bin/activate  # Windows: .venv\\Scripts\\activate\n\n# 모든 의존성 설치 (dev 및 test 포함)\nmake install\n\n# 최소 테스트 실행 (웹 프레임워크 제외)\nmake test-minimal\n\n# 모든 테스트 실행 (웹 프레임워크 포함)\nmake test\n\n# 또는 Makefile 사용 (편리함)\nmake lint              # 린팅 실행\nmake format            # 코드 포맷팅\nmake test-minimal      # 최소 테스트 실행\nmake test              # 모든 테스트 실행\nmake test-all-versions # 모든 Python 버전에서 테스트 (3.9-3.14) - tox 필요\nmake ci                # 전체 CI 검사 실행 (린팅 + 포맷팅 + 테스트)\n```\n\n## 📖 문서\n\n- **English**: [README.md](README.md)\n- **한국어 (Korean)**: [README.ko.md](README.ko.md)\n- **TUS Protocol Compliance**: [TUS_COMPLIANCE.md](TUS_COMPLIANCE.md)\n\n## 🤝 기여하기\n\n기여를 환영합니다! 가이드라인은 [Contributing Guide](.github/CONTRIBUTING.md)를 확인해주세요.\n\n## 📄 라이선스\n\nMIT License - 자세한 내용은 [LICENSE](LICENSE) 파일을 참조하세요.\n\n## 🙏 감사의 말\n\n이 라이브러리는 공식 [TUS Python client](https://github.com/tus/tus-py-client)에서 영감을 받았으며 [TUS 재개 가능 업로드 프로토콜](https://tus.io/)을 구현합니다.\n\n## 📞 지원\n\n- 📫 Issues: [GitHub Issues](https://github.com/sts07142/resumable-upload/issues)\n- 📖 Documentation: [GitHub README](https://github.com/sts07142/resumable-upload#readme)\n- 🌟 GitHub에서 스타를 눌러주세요!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsts07142%2Fresumable-upload","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsts07142%2Fresumable-upload","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsts07142%2Fresumable-upload/lists"}