resumable-upload 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. resumable_upload-0.0.1/CHANGELOG.md +41 -0
  2. resumable_upload-0.0.1/LICENSE +21 -0
  3. resumable_upload-0.0.1/MANIFEST.in +9 -0
  4. resumable_upload-0.0.1/PKG-INFO +424 -0
  5. resumable_upload-0.0.1/README.ko.md +380 -0
  6. resumable_upload-0.0.1/README.md +380 -0
  7. resumable_upload-0.0.1/TUS_COMPLIANCE.md +164 -0
  8. resumable_upload-0.0.1/examples/advanced_client_example.py +377 -0
  9. resumable_upload-0.0.1/examples/client_example.py +54 -0
  10. resumable_upload-0.0.1/examples/django_example.py +113 -0
  11. resumable_upload-0.0.1/examples/fastapi_example.py +64 -0
  12. resumable_upload-0.0.1/examples/flask_example.py +53 -0
  13. resumable_upload-0.0.1/examples/server_example.py +47 -0
  14. resumable_upload-0.0.1/pyproject.toml +102 -0
  15. resumable_upload-0.0.1/resumable_upload/__init__.py +29 -0
  16. resumable_upload-0.0.1/resumable_upload/client/__init__.py +7 -0
  17. resumable_upload-0.0.1/resumable_upload/client/base.py +379 -0
  18. resumable_upload-0.0.1/resumable_upload/client/retry.py +203 -0
  19. resumable_upload-0.0.1/resumable_upload/client/stats.py +66 -0
  20. resumable_upload-0.0.1/resumable_upload/exceptions.py +30 -0
  21. resumable_upload-0.0.1/resumable_upload/fingerprint.py +60 -0
  22. resumable_upload-0.0.1/resumable_upload/server.py +347 -0
  23. resumable_upload-0.0.1/resumable_upload/storage.py +170 -0
  24. resumable_upload-0.0.1/resumable_upload/url_storage.py +104 -0
  25. resumable_upload-0.0.1/resumable_upload.egg-info/PKG-INFO +424 -0
  26. resumable_upload-0.0.1/resumable_upload.egg-info/SOURCES.txt +28 -0
  27. resumable_upload-0.0.1/resumable_upload.egg-info/dependency_links.txt +1 -0
  28. resumable_upload-0.0.1/resumable_upload.egg-info/requires.txt +16 -0
  29. resumable_upload-0.0.1/resumable_upload.egg-info/top_level.txt +1 -0
  30. resumable_upload-0.0.1/setup.cfg +4 -0
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.0.1] - 2025-01-06
9
+
10
+ ### Added
11
+
12
+ - Initial release of resumable-upload library
13
+ - TUS protocol v1.0.0 server implementation
14
+ - TUS protocol v1.0.0 client implementation
15
+ - TusClientWithRetry with automatic retry and exponential backoff
16
+ - SQLiteStorage backend for upload state management
17
+ - Comprehensive logging support (INFO, WARNING, ERROR, DEBUG levels)
18
+ - Custom exceptions: TusCommunicationError and TusUploadFailed
19
+ - File fingerprinting for cross-session resumability
20
+ - URL storage interface with FileURLStorage implementation
21
+ - Progress tracking with UploadStats dataclass
22
+ - Support for file streams alongside file paths
23
+ - Optional SHA1 checksum verification
24
+ - TLS certificate verification control
25
+ - Integration examples for Flask, FastAPI, and Django
26
+ - 70 comprehensive tests with 90%+ coverage
27
+ - Support for Python 3.9 through 3.14
28
+ - Zero runtime dependencies
29
+ - Complete documentation in English and Korean
30
+ - TUS protocol compliance documentation
31
+
32
+ ### Features
33
+
34
+ - Sequential chunk uploads per TUS protocol requirements
35
+ - Automatic resume of interrupted uploads
36
+ - Configurable chunk size
37
+ - Metadata support with proper encoding
38
+ - Cross-platform compatibility
39
+ - PyPI-ready package structure
40
+
41
+ [0.0.1]: https://github.com/sts07142/resumable-upload/releases/tag/v0.0.1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Injae Ryou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include README.ko.md
3
+ include LICENSE
4
+ include CHANGELOG.md
5
+ include TUS_COMPLIANCE.md
6
+ recursive-include resumable_upload *.py
7
+ recursive-include examples *.py
8
+ recursive-exclude tests *
9
+ recursive-exclude .github *
@@ -0,0 +1,424 @@
1
+ Metadata-Version: 2.4
2
+ Name: resumable-upload
3
+ Version: 0.0.1
4
+ Summary: A Python implementation of the `TUS resumable upload protocol` for server and client, with zero runtime dependencies.
5
+ Author-email: Injae Ryou <sts07142@naver.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sts07142/resumable-upload
8
+ Project-URL: Documentation, https://github.com/sts07142/resumable-upload#readme
9
+ Project-URL: Repository, https://github.com/sts07142/resumable-upload
10
+ Project-URL: Issues, https://github.com/sts07142/resumable-upload/issues
11
+ Project-URL: Changelog, https://github.com/sts07142/resumable-upload/releases
12
+ Keywords: tus,upload,resumable,file-upload,server,client,http
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
17
+ Classifier: Topic :: System :: Networking
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Programming Language :: Python :: 3 :: Only
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
33
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
34
+ Requires-Dist: tox>=4.0.0; extra == "dev"
35
+ Provides-Extra: test
36
+ Requires-Dist: pytest>=7.0.0; extra == "test"
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
38
+ Requires-Dist: flask>=2.0.0; extra == "test"
39
+ Requires-Dist: fastapi>=0.100.0; extra == "test"
40
+ Requires-Dist: uvicorn>=0.38.0; extra == "test"
41
+ Requires-Dist: httpx>=0.24.0; extra == "test"
42
+ Requires-Dist: django>=3.2.0; extra == "test"
43
+ Dynamic: license-file
44
+
45
+ # Resumable Upload
46
+
47
+ [![Python Version](https://img.shields.io/pypi/pyversions/resumable-upload.svg)](https://pypi.org/project/resumable-upload/)
48
+ [![PyPI Version](https://img.shields.io/pypi/v/resumable-upload.svg)](https://pypi.org/project/resumable-upload/)
49
+ [![License](https://img.shields.io/pypi/l/resumable-upload.svg)](https://github.com/sts07142/resumable-upload/blob/main/LICENSE)
50
+
51
+ **English** | [ํ•œ๊ตญ์–ด](README.ko.md)
52
+
53
+ A Python implementation of the [TUS resumable upload protocol](https://tus.io/) v1.0.0 for server and client, with zero runtime dependencies.
54
+
55
+ ## โœจ Features
56
+
57
+ - ๐Ÿš€ **Zero Dependencies**: Built using Python standard library only (no external dependencies for core functionality)
58
+ - ๐Ÿ“ฆ **Server & Client**: Complete implementation of both sides
59
+ - ๐Ÿ”„ **Resume Capability**: Automatically resume interrupted uploads
60
+ - โœ… **Data Integrity**: Optional SHA1 checksum verification
61
+ - ๐Ÿ” **Retry Logic**: Built-in automatic retry with exponential backoff
62
+ - ๐Ÿ“Š **Progress Tracking**: Detailed upload progress callbacks with stats
63
+ - ๐ŸŒ **Web Framework Support**: Integration examples for Flask, FastAPI, and Django
64
+ - ๐Ÿ **Python 3.9+**: Supports Python 3.9 through 3.14
65
+ - ๐Ÿช **Storage Backend**: SQLite-based storage (extensible to other backends)
66
+ - ๐Ÿ” **TLS Support**: Certificate verification control and mTLS authentication
67
+ - ๐Ÿ“ **URL Storage**: Persist upload URLs across sessions
68
+ - ๐ŸŽฏ **TUS Protocol Compliant**: Implements TUS v1.0.0 core protocol with creation, termination, and checksum extensions
69
+
70
+ ## ๐Ÿ“ฆ Installation
71
+
72
+ ### Using uv (Recommended)
73
+
74
+ ```bash
75
+ # Install uv if you haven't already
76
+ curl -LsSf https://astral.sh/uv/install.sh | sh
77
+
78
+ # Install the package
79
+ uv pip install resumable-upload
80
+ ```
81
+
82
+ ### Using pip
83
+
84
+ ```bash
85
+ pip install resumable-upload
86
+ ```
87
+
88
+ ## ๐Ÿš€ Quick Start
89
+
90
+ ### Basic Server
91
+
92
+ ```python
93
+ from http.server import HTTPServer
94
+ from resumable_upload import TusServer, TusHTTPRequestHandler, SQLiteStorage
95
+
96
+ # Create storage backend
97
+ storage = SQLiteStorage(db_path="uploads.db", upload_dir="uploads")
98
+
99
+ # Create TUS server
100
+ tus_server = TusServer(storage=storage, base_path="/files")
101
+
102
+ # Create HTTP handler
103
+ class Handler(TusHTTPRequestHandler):
104
+ pass
105
+
106
+ Handler.tus_server = tus_server
107
+
108
+ # Start server
109
+ server = HTTPServer(("0.0.0.0", 8080), Handler)
110
+ print("Server running on http://localhost:8080")
111
+ server.serve_forever()
112
+ ```
113
+
114
+ ### Basic Client
115
+
116
+ ```python
117
+ from resumable_upload import TusClient
118
+
119
+ # Create client
120
+ client = TusClient("http://localhost:8080/files")
121
+
122
+ # Upload file with progress callback
123
+ def progress(uploaded, total):
124
+ print(f"Progress: {uploaded}/{total} bytes ({uploaded/total*100:.1f}%)")
125
+
126
+ upload_url = client.upload_file(
127
+ "large_file.bin",
128
+ metadata={"filename": "large_file.bin"},
129
+ progress_callback=progress
130
+ )
131
+
132
+ print(f"Upload complete: {upload_url}")
133
+ ```
134
+
135
+ ## ๐Ÿ”ง Advanced Usage
136
+
137
+ ### Client with Automatic Retry
138
+
139
+ ```python
140
+ from resumable_upload import TusClientWithRetry
141
+
142
+ # Create client with retry capability
143
+ client = TusClientWithRetry(
144
+ "http://localhost:8080/files",
145
+ chunk_size=1.5*1024*1024, # 1.5MB chunks (float is allowed)
146
+ max_retries=3, # Retry up to 3 times
147
+ retry_delay=1.0, # Initial delay between retries
148
+ checksum=True # Enable checksum verification
149
+ )
150
+
151
+ # Upload with detailed progress tracking
152
+ def progress_callback(stats):
153
+ print(f"Progress: {stats.progress_percent:.1f}% | "
154
+ f"Speed: {stats.upload_speed/1024/1024:.2f} MB/s | "
155
+ f"ETA: {stats.eta_seconds:.0f}s | "
156
+ f"Chunks: {stats.chunks_completed}/{stats.total_chunks} | "
157
+ f"Retried: {stats.chunks_retried}")
158
+
159
+ upload_url = client.upload_file(
160
+ "large_file.bin",
161
+ metadata={"filename": "large_file.bin"},
162
+ progress_callback=progress_callback
163
+ )
164
+ ```
165
+
166
+ ### Resume Interrupted Uploads
167
+
168
+ ```python
169
+ # Resume an interrupted upload
170
+ upload_url = client.resume_upload("large_file.bin", upload_url)
171
+ ```
172
+
173
+ ### Cross-Session Resumability
174
+
175
+ ```python
176
+ from resumable_upload import TusClient, FileURLStorage
177
+
178
+ # Enable URL storage for resumability across sessions
179
+ storage = FileURLStorage(".tus_urls.json")
180
+ client = TusClient(
181
+ "http://localhost:8080/files",
182
+ store_url=True,
183
+ url_storage=storage
184
+ )
185
+
186
+ # Upload will automatically resume if interrupted and restarted
187
+ upload_url = client.upload_file("large_file.bin")
188
+ ```
189
+
190
+ ### Using File Streams
191
+
192
+ ```python
193
+ # Upload from a file stream instead of a path
194
+ with open("file.bin", "rb") as fs:
195
+ client = TusClient("http://localhost:8080/files")
196
+ upload_url = client.upload_file(
197
+ file_stream=fs,
198
+ metadata={"filename": "file.bin"}
199
+ )
200
+ ```
201
+
202
+ ### Exception Handling
203
+
204
+ ```python
205
+ from resumable_upload import TusClient
206
+ from resumable_upload.exceptions import TusCommunicationError, TusUploadFailed
207
+
208
+ client = TusClient("http://localhost:8080/files")
209
+
210
+ try:
211
+ upload_url = client.upload_file("file.bin")
212
+ except TusCommunicationError as e:
213
+ print(f"Communication error: {e.message}, status: {e.status_code}")
214
+ except TusUploadFailed as e:
215
+ print(f"Upload failed: {e.message}")
216
+ ```
217
+
218
+ ## ๐ŸŒ Web Framework Integration
219
+
220
+ ### Flask
221
+
222
+ ```python
223
+ from flask import Flask, request, make_response
224
+ from resumable_upload import TusServer, SQLiteStorage
225
+
226
+ app = Flask(__name__)
227
+ tus_server = TusServer(storage=SQLiteStorage())
228
+
229
+ @app.route('/files', methods=['OPTIONS', 'POST'])
230
+ @app.route('/files/<upload_id>', methods=['HEAD', 'PATCH', 'DELETE'])
231
+ def handle_upload(upload_id=None):
232
+ status, headers, body = tus_server.handle_request(
233
+ request.method, request.path, dict(request.headers), request.get_data()
234
+ )
235
+ response = make_response(body, status)
236
+ for key, value in headers.items():
237
+ response.headers[key] = value
238
+ return response
239
+ ```
240
+
241
+ ### FastAPI
242
+
243
+ ```python
244
+ from fastapi import FastAPI, Request, Response
245
+ from resumable_upload import TusServer, SQLiteStorage
246
+
247
+ app = FastAPI()
248
+ tus_server = TusServer(storage=SQLiteStorage())
249
+
250
+ @app.post("/files")
251
+ @app.head("/files/{upload_id}")
252
+ @app.patch("/files/{upload_id}")
253
+ @app.delete("/files/{upload_id}")
254
+ async def handle_upload(request: Request):
255
+ body = await request.body()
256
+ status, headers, response_body = tus_server.handle_request(
257
+ request.method, request.url.path, dict(request.headers), body
258
+ )
259
+ return Response(content=response_body, status_code=status, headers=headers)
260
+ ```
261
+
262
+ ### Django
263
+
264
+ ```python
265
+ from django.http import HttpResponse
266
+ from django.views.decorators.csrf import csrf_exempt
267
+ from resumable_upload import TusServer, SQLiteStorage
268
+
269
+ tus_server = TusServer(storage=SQLiteStorage())
270
+
271
+ @csrf_exempt
272
+ def tus_upload_view(request, upload_id=None):
273
+ headers = {key[5:].replace('_', '-'): value
274
+ for key, value in request.META.items() if key.startswith('HTTP_')}
275
+ status, response_headers, response_body = tus_server.handle_request(
276
+ request.method, request.path, headers, request.body
277
+ )
278
+ response = HttpResponse(response_body, status=status)
279
+ for key, value in response_headers.items():
280
+ response[key] = value
281
+ return response
282
+ ```
283
+
284
+ ## ๐Ÿ“š API Reference
285
+
286
+ ### TusClient
287
+
288
+ Main client class for uploading files.
289
+
290
+ **Parameters:**
291
+
292
+ - `url` (str): TUS server base URL
293
+ - `chunk_size` (int): Size of each upload chunk in bytes (default: 1MB)
294
+ - `checksum` (bool): Enable SHA1 checksum verification (default: False)
295
+ - `store_url` (bool): Store upload URLs for resumability (default: False)
296
+ - `url_storage` (URLStorage): URL storage backend (default: FileURLStorage)
297
+ - `verify_tls_cert` (bool): Verify TLS certificates (default: True)
298
+ - `metadata_encoding` (str): Metadata encoding (default: "utf-8")
299
+
300
+ **Methods:**
301
+
302
+ - `upload_file(file_path=None, file_stream=None, metadata={}, progress_callback=None)`: Upload a file
303
+ - `resume_upload(file_path, upload_url, progress_callback=None)`: Resume an interrupted upload
304
+ - `delete_upload(upload_url)`: Delete an upload
305
+ - `get_offset(upload_url)`: Get current upload offset
306
+
307
+ ### TusClientWithRetry
308
+
309
+ Enhanced client with automatic retry capability (inherits from TusClient).
310
+
311
+ **Additional Parameters:**
312
+
313
+ - `max_retries` (int): Maximum number of retry attempts (default: 3)
314
+ - `retry_delay` (float): Initial delay between retries in seconds (default: 1.0)
315
+ - `max_retry_delay` (float): Maximum delay between retries in seconds (default: 60.0)
316
+
317
+ ### TusServer
318
+
319
+ Server implementation of TUS protocol.
320
+
321
+ **Parameters:**
322
+
323
+ - `storage` (Storage): Storage backend for managing uploads
324
+ - `base_path` (str): Base path for TUS endpoints (default: "/files")
325
+ - `max_size` (int): Maximum upload size in bytes (default: None)
326
+
327
+ **Methods:**
328
+
329
+ - `handle_request(method, path, headers, body)`: Handle TUS protocol requests
330
+
331
+ ### SQLiteStorage
332
+
333
+ SQLite-based storage backend.
334
+
335
+ **Parameters:**
336
+
337
+ - `db_path` (str): Path to SQLite database file (default: "uploads.db")
338
+ - `upload_dir` (str): Directory for storing upload files (default: "uploads")
339
+
340
+ ## ๐Ÿ” TUS Protocol Compliance
341
+
342
+ This library implements TUS protocol version 1.0.0 with the following extensions:
343
+
344
+ - โœ… **Core Protocol**: Basic upload functionality (POST, HEAD, PATCH)
345
+ - โœ… **Creation**: Upload creation via POST
346
+ - โœ… **Termination**: Upload deletion via DELETE
347
+ - โœ… **Checksum**: SHA1 checksum verification
348
+
349
+ ### Sequential Upload Requirement
350
+
351
+ **Important:** The TUS protocol requires chunks to be uploaded **sequentially**, not in parallel.
352
+
353
+ **Why Sequential?**
354
+
355
+ 1. **Offset Validation**: Each chunk must be uploaded at the correct byte offset
356
+ 2. **Data Integrity**: Prevents data corruption from race conditions
357
+ 3. **Resume Capability**: Makes tracking received bytes straightforward and reliable
358
+ 4. **Protocol Compliance**: TUS specification requires `Upload-Offset` to match current position
359
+
360
+ ```python
361
+ # โŒ Parallel uploads cause conflicts:
362
+ # Chunk 1 at offset 0 โ†’ OK
363
+ # Chunk 3 at offset 2048 โ†’ FAIL (409: expected offset 1024)
364
+ # Chunk 2 at offset 1024 โ†’ FAIL (409: offset mismatch)
365
+
366
+ # โœ… Sequential uploads work correctly:
367
+ # Chunk 1 at offset 0 โ†’ OK (offset now 1024)
368
+ # Chunk 2 at offset 1024 โ†’ OK (offset now 2048)
369
+ # Chunk 3 at offset 2048 โ†’ OK (offset now 3072)
370
+ ```
371
+
372
+ ## ๐Ÿงช Testing
373
+
374
+ ### Using uv (Recommended)
375
+
376
+ ```bash
377
+ # Install uv if you haven't already
378
+ curl -LsSf https://astral.sh/uv/install.sh | sh
379
+
380
+ # Create virtual environment and install dependencies
381
+ uv venv
382
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
383
+
384
+ # Install all dependencies (dev and test)
385
+ make install
386
+
387
+ # Run minimal tests (excluding web frameworks)
388
+ make test-minimal
389
+
390
+ # Run all tests (including web frameworks)
391
+ make test
392
+
393
+ # Or use Makefile for convenience
394
+ make lint # Run linting
395
+ make format # Format code
396
+ make test-minimal # Run minimal tests
397
+ make test # Run all tests
398
+ make test-all-versions # Test on all Python versions (3.9-3.14) - requires tox
399
+ make ci # Run full CI checks (lint + format + test)
400
+ ```
401
+
402
+ ## ๐Ÿ“– Documentation
403
+
404
+ - **English**: [README.md](README.md)
405
+ - **ํ•œ๊ตญ์–ด (Korean)**: [README.ko.md](README.ko.md)
406
+ - **TUS Protocol Compliance**: [TUS_COMPLIANCE.md](TUS_COMPLIANCE.md)
407
+
408
+ ## ๐Ÿค Contributing
409
+
410
+ Contributions are welcome! Please check out the [Contributing Guide](.github/CONTRIBUTING.md) for guidelines.
411
+
412
+ ## ๐Ÿ“„ License
413
+
414
+ MIT License - see [LICENSE](LICENSE) file for details.
415
+
416
+ ## ๐Ÿ™ Acknowledgments
417
+
418
+ This library is inspired by the official [TUS Python client](https://github.com/tus/tus-py-client) and implements the [TUS resumable upload protocol](https://tus.io/).
419
+
420
+ ## ๐Ÿ“ž Support
421
+
422
+ - ๐Ÿ“ซ Issues: [GitHub Issues](https://github.com/sts07142/resumable-upload/issues)
423
+ - ๐Ÿ“– Documentation: [GitHub README](https://github.com/sts07142/resumable-upload#readme)
424
+ - ๐ŸŒŸ Star us on GitHub!